diff --git a/.pylon/features.yaml b/.pylon/features.yaml index 515d4a78c4..188a0708c4 100644 --- a/.pylon/features.yaml +++ b/.pylon/features.yaml @@ -266,6 +266,23 @@ decisions: - Prime upstream exposes an equivalent generation-scoped, post-attach public proof that is false before attach and after invalidation. - Pylon and Comet can remove the fork SDK token/accessor without enabling optional behavior from a server offer, version, or method presence. + nonpersistent-daemon-worker: + area: runtime-reliability + state: candidate + owner: shared + decision: retain + pylon_refs: + - https://github.com/pylon-code/prime-agent/issues/20 + - https://github.com/pylon-code/pylon/issues/114 + - https://github.com/rynfar/comet/issues/7 + upstream_refs: + - https://github.com/PrimeIntellect-ai/prime-agent/tree/a903d4b6768f484bd6d459b7b0aa7dee38e461e2 + fork_change: nonpersistent-daemon-worker-v1 + upstream_support: Prime through a903d4b6768f has recoverable daemon workers and client-owned cleanup, but no capability-gated fresh worker mode that keeps create secrets, prompt payloads, correlation identities, transcripts, and snapshot caches out of durable state and permanently excludes that worker from recovery, adoption, relaunch, residency, and update snapshots. + revisit_when: + - Prime upstream exposes an equivalent frozen SDK token, exact create-only live-generation receipt, memory-only private retry and snapshot handling, and strict disabled-recovery provenance. + - Comet and Pylon can remove the fork token without weakening prompt privacy, worker-loss containment, owner cleanup, or ordinary-worker compatibility. + child-scoped-provider-identity: area: runtime-reliability state: shipped diff --git a/.pylon/upstream-review.md b/.pylon/upstream-review.md index 6c30eb4268..7d89678104 100644 --- a/.pylon/upstream-review.md +++ b/.pylon/upstream-review.md @@ -204,3 +204,15 @@ This ledger records Prime upstream evidence and the decision taken for each over - Build/verify, attestation, and publication remain separate privilege domains. Publication writers do not checkout or execute repository/downloaded code. Normal attesters carry the one direct environment approval and OIDC/attestation writes; downstream draft/final jobs alone get contents write. Stable recovery uses a mutually exclusive zero-write direct approval and the old exact attestation. Actions and the reviewed attestation composite chain are full-SHA pinned. `pylon-preview` and `pylon-stable` use exact `pylon` custom-branch policies and explicit solo-maintainer approval. Active no-bypass tag ruleset `21950766` allows creation but prevents update/deletion of `pylon-build-*` and `pylon-stable-*` refs, including N-only sequence reservations. Immutable Releases remains enabled. - Offline publication tests cover canonical bytes, closed current/historical recipes, rerun-stable preview run sequencing and consumer high-water, exact check/workflow-run proof, wrong signer/source/subject/invocation and missing-Rekor rejection, stable consumer rollback/equivocation, digest-chained history, append-only revocations, exact approval/content-writer graphs, action-chain pins, and publisher no-source-execution. The operator and independent-verification runbook is `docs/pylon-publication.md`. - Revisit only if Prime provides a repository-neutral immutable publication primitive that fully preserves Pylon's protected-source, provenance, history, and withdrawal guarantees, or if Pylon deliberately replaces GitHub Releases with an equivalent verifiable transport. + +## 2026-09-10 — schema-31 nonpersistent daemon-worker candidate + +- Fork base: `pylon-code/prime-agent@68603ed89bb597cd715fd6a77bc1c39d7e110298`; upstream baseline remains `PrimeIntellect-ai/prime-agent@a903d4b6768f484bd6d459b7b0aa7dee38e461e2` / `v0.8.1`. +- Reviewed upstream daemon create, client-owned cleanup, command and worker recovery journals, worker authentication, descriptor loading, snapshot transfer caches, recoverable-owned adoption, update restart, and force-stop discovery. Upstream has no equivalent capability-gated mode or exact live-generation receipt. +- `nonpersistent-daemon-worker`: **retain** the schema-31 fork candidate. It accepts only a fresh client-owned, in-memory, tool-free, extension-free worker and proves `workerRecovery: "disabled"` only on the exact create response for the ready authenticated incarnation. +- Disabled create and correlated retry identity stays in bounded process memory as defensively cloned full requests with deep equality. Disabled creates have a 1 MiB per-request bound; the keyed retry store has both a 32-entry and 8 MiB cumulative input-byte cap; worker and supervisor JSONL lines have a 64 MiB ingress cap. The client snapshots each public command before any asynchronous handshake wait, and requires and clones the exact disabled create receipt before selecting private identity handling; disabled creates and every proof-bound request force transport-bound, no-replay client options even on a shared recovery-enabled client, while ordinary connections retain constant-size request fingerprints. No disabled request digest enters a journal. Correlated input is nonempty non-slash text only, has an 8 KiB UTF-8 bound, carries no images, and has no session JSONL. Worker and supervisor snapshot caches fail closed above a 16 MiB memory-only bound instead of spilling payloads. +- Version 3 descriptor plus exact `.nonpersistent` marker content binds disabled recovery provenance. Missing, malformed, downgraded, or inconsistent provenance is contained and retired rather than repaired into ordinary recovery. Authentication binds the expected mode after token and supervisor-generation validation, so an invalid token is not a remote worker-kill primitive. +- Disabled workers clear inherited tool, extension, and autonomous configuration; prove the effective runtime has no active tools, loaded extensions, autonomous continuation, or shell gates; and accept only a positive command allowlist. Contradictory explicit creates are rejected. Transcript-transforming and durable commands such as Bash, cron, `compact`, `export_jsonl`, and `refine` are rejected before worker dispatch and supervisor journaling. Disabled workers are excluded from command and worker journals, recovery, relaunch, retry, residency, attach-after-disconnect, recoverable-owned stores and MCP authority, and update snapshots. Each disabled worker is bound to its exact creating transport rather than only its logical client ID; loss of that transport starts immediate retirement, and proof-bound connections permanently reject reuse after transport loss as well as daemon recovery. Replacement and normal/adoptive shutdown retire them with PID, start-ID, and incarnation checks; descriptor-first cleanup preserves provenance across partial unlink, and forced CLI cleanup recognizes journal-less version 3 records. +- Ordinary schema-31 workers preserve existing undefined-identity command-journal lookup, recovery, caller-owned environment cleanup, recoverable adoption, and update behavior. No Pylon provider source change is needed for this private process-lifecycle slice. +- Validation includes protocol/client/CLI/mode/snapshot/supervisor units, strict provenance and auth tests, lost-response and collision tests, identity-safe stop tests, real normal-text privacy canaries across the agent artifact tree, replacement containment, affected recovery/update regressions, package build, and exact-artifact reproduction. The candidate remains unshipped until exact-head independent review and reproduction complete. +- Revisit when upstream exposes an equivalent frozen token, exact create receipt, memory-only privacy contract, strict disabled provenance, identity-safe retirement, and ordinary-worker compatibility. diff --git a/packages/ai/.changes/res-1269-deterministic-model-catalog-build.md b/packages/ai/.changes/res-1269-deterministic-model-catalog-build.md new file mode 100644 index 0000000000..a54a001b40 --- /dev/null +++ b/packages/ai/.changes/res-1269-deterministic-model-catalog-build.md @@ -0,0 +1 @@ +- Changed the build to compile from the committed model catalog instead of refetching it; run `npm run generate-models` explicitly to refresh `models.generated.ts`. diff --git a/packages/ai/package.json b/packages/ai/package.json index 14a75d4a82..3062d17eb0 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -65,7 +65,7 @@ "scripts": { "clean": "shx rm -rf dist", "generate-models": "npx tsx scripts/generate-models.ts", - "build": "npm run generate-models && tsgo -p tsconfig.build.json", + "build": "tsgo -p tsconfig.build.json", "build:offline": "tsgo -p tsconfig.build.json", "dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput", "dev:tsc": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput", diff --git a/packages/coding-agent/.changes/prime-20-nonpersistent-daemon-worker.md b/packages/coding-agent/.changes/prime-20-nonpersistent-daemon-worker.md new file mode 100644 index 0000000000..d3b0c69b90 --- /dev/null +++ b/packages/coding-agent/.changes/prime-20-nonpersistent-daemon-worker.md @@ -0,0 +1,2 @@ +- Added a capability-gated nonpersistent daemon-worker mode for private fresh client-owned turns, with exact create proof, memory-only retry and snapshot handling, strict no-recovery provenance, and identity-safe cleanup ([#20](https://github.com/pylon-code/prime-agent/issues/20)). +- Kept disabled-worker prompts, create secrets, and correlation identities out of session files and recovery journals with exact receipt-bound in-memory retries, no effective tools, extensions, autonomous continuation, or shell gates, bounded ingress/retry memory, and a positive private command allowlist, while preserving ordinary daemon retry, recovery, and adoption behavior. diff --git a/packages/coding-agent/docs/sdk.md b/packages/coding-agent/docs/sdk.md index dcbf977199..5ab0b5f61f 100644 --- a/packages/coding-agent/docs/sdk.md +++ b/packages/coding-agent/docs/sdk.md @@ -77,6 +77,69 @@ if (!connection.supportsNegotiatedCapability("correlated_prompt_lifecycle_v1")) `supportsNegotiatedCapability()` is false before attach, while a new attach or reattach is pending, after transport or attachment invalidation, and after disposal. It becomes true only after the same physical transport returns a validated client capability echo and the exact snapshot commit succeeds. `supportsCorrelatedPromptLifecycle()` remains server-offer evidence used to construct the attach request. It is not negotiation proof. Do not substitute a hello offer, method presence, attach success, or package version for the post-attach accessor. Correlated runtime frames are withheld until the attach-side echo commits and are discarded when the echo omits the capability. Pre-proof retention is bounded by both frame count and conservative cumulative structural weight; overflow fails the adapter closed without retaining or reporting attributed payload content. A chunked replacement uses the same count and weight bounds for frames held behind its atomic snapshot fence. New same-connection attachment admission, attachment-epoch change, transport loss, disposal, or matching session close retires that old fence before any later proof can publish; delayed old snapshot frames are ignored until a fresh attachment commits. +### Nonpersistent daemon workers + +Hosts that need a private one-turn worker must require the package-root `nonpersistent_daemon_worker_v1` token and the same server hello capability before create. Then send one fresh create with `lifecycle: "client_owned"`, `workerRecovery: "disabled"`, `noSession: true`, and explicit `config.noTools: true` plus `config.noExtensions: true`. `sessionPath` and `continueRecent: true` are invalid. The raw protocol supports bounded same-supervisor convergence for a keyed client/command envelope; an unkeyed raw create is one-shot. Public `DaemonClient.request()` snapshots the command before any asynchronous handshake wait, then allocates and owns that command ID for the lifetime of its request. Callers cannot supply `id`, and must not start a second create after an uncertain return. A disabled create is always transport-bound and is never retained for reconnect replay, even when the `DaemonClient` is shared with an ordinary recovery-enabled connection. + +```typescript +import { + DaemonAgentConnection, + DaemonClient, + PRIME_AGENT_SDK_FEATURES, + type DaemonNonpersistentWorkerCreateProof, +} from "@earendil-works/pi-coding-agent"; + +function isNonpersistentWorkerCreateProof(value: unknown): value is DaemonNonpersistentWorkerCreateProof { + if (!value || typeof value !== "object") return false; + const candidate = value as Record; + return ( + typeof candidate.id === "string" && + (candidate.activeSessionId === undefined || typeof candidate.activeSessionId === "string") && + typeof candidate.sessionId === "string" && + candidate.workerRecovery === "disabled" && + candidate.sessionFile === undefined + ); +} + +if (!PRIME_AGENT_SDK_FEATURES.includes("nonpersistent_daemon_worker_v1")) { + throw new Error("The local SDK does not support private daemon workers"); +} + +const client = new DaemonClient(socketPath, { maxInboundFrameBytes: 64 * 1024 * 1024 }); +await client.connect(); +if (!client.supportsServerCapability("nonpersistent_daemon_worker_v1")) { + throw new Error("The daemon does not support private daemon workers"); +} + +const response = await client.request({ + type: "create", + lifecycle: "client_owned", + workerRecovery: "disabled", + noSession: true, + config: { + cwd: process.cwd(), + noTools: true, + noExtensions: true, + }, +}); +if (!response.success || !isNonpersistentWorkerCreateProof(response.data)) { + throw new Error("Private worker proof is unavailable"); +} +const proof = response.data; +const connection = new DaemonAgentConnection(client, proof.activeSessionId ?? proof.id, { + ownedSession: true, + nonpersistentWorkerCreateProof: proof, +}); +``` + +The `workerRecovery: "disabled"` field is an exact create-only receipt. List and attach summaries omit it. The receipt is returned only for the same ready, authenticated worker incarnation with a valid version 3 descriptor and `.nonpersistent` marker, the exact owner and root session, no session file, and no recovery or adoption state. A hello offer, schema number, create success without the receipt, attach success, or package version is not proof. + +A disabled worker never creates a session JSONL or worker recovery journal. A disabled create is limited to 1 MiB, and keyed retry requests are cloned and compared only in supervisor memory with both a 32-entry cap and an 8 MiB cumulative input-byte cap. Pass the exact create receipt as `nonpersistentWorkerCreateProof` when constructing the owned `DaemonAgentConnection`; the adapter clones and revalidates it against the attached active/session generation before retaining private prompt retry requests as exact bounded in-memory clones. Ordinary connections continue to retain constant-size SHA-256 retry fingerprints. Disabled payloads are not fingerprinted. Supervisor and worker JSONL ingress reject a line above 64 MiB. Worker and supervisor snapshot caches remain memory-only and reject a transcript above 16 MiB instead of spilling it to disk. Correlated submissions accept only nonempty, non-slash text up to 8 KiB UTF-8 with no images. The fixed privacy-safe failures do not echo prompts, identifiers, paths, credentials, receipts, or digests. + +The mode is deliberately narrower than an ordinary client-owned session. Effective tool and extension sets must be empty. Autonomous continuation and shell gates must also be disabled and empty. Inherited daemon defaults are cleared, contradictory explicit creates are rejected, and runtime proof fails closed if any tool, extension, autonomous continuation, or gate becomes active. A positive command allowlist admits only creation, live attachment controls, correlated submit/cancel/reconciliation, abort, and shutdown/owned cleanup. It rejects legacy prompt paths and transcript-transforming or durable commands such as Bash, cron, `compact`, `export_jsonl`, and `refine`. A disabled worker cannot change its root session, attach after disconnect, recover uncertain operations, retry, relaunch, become resident, enter recoverable-owned adoption, transfer MCP adoption authority, or participate in update-restart snapshots. Owner transport loss starts immediate identity-safe retirement; the worker is bound to its exact creating transport rather than only its logical client ID. A connection carrying the create proof rejects daemon-recovery options, forces every request to opt out of shared-client reconnect replay, and permanently treats every transport loss as terminal. Supervisor replacement, worker loss, normal shutdown, and `daemon stop --force` also retire the worker with process-incarnation checks. Ordinary recoverable workers keep their existing grace period, journal, hashed retry identity, replay, adoption, and update behavior. + +After create, attach and separately require current-generation `correlated_prompt_lifecycle_v1` proof before using correlated prompt APIs. Do not resubmit across a transport or supervisor generation. Treat an unavailable create receipt, failed attachment proof, oversized snapshot, disconnect, or uncertain cleanup as terminal for that worker. + ### Caller-owned daemon session environments and cleanup Native multi-instance hosts must gate caller-owned daemon sessions with the exact `caller_owned_session_environment_cleanup_v1` contract. Require all three proofs: diff --git a/packages/coding-agent/src/cli/daemon-ps.ts b/packages/coding-agent/src/cli/daemon-ps.ts index 42c05f9a73..c2c6deffc9 100644 --- a/packages/coding-agent/src/cli/daemon-ps.ts +++ b/packages/coding-agent/src/cli/daemon-ps.ts @@ -18,7 +18,11 @@ import { } from "../modes/daemon/daemon-protocol.js"; import { defaultDaemonSocketDir, defaultDaemonSocketPath, normalizeSocketPath } from "../modes/daemon/daemon-socket.js"; import { acquireDaemonShutdownAdmission } from "../modes/daemon/daemon-supervisor-ownership.js"; -import type { DaemonWorkerDescriptor } from "../modes/daemon/daemon-worker-protocol.js"; +import { + DAEMON_NONPERSISTENT_WORKER_MARKER, + DAEMON_NONPERSISTENT_WORKER_MARKER_SUFFIX, + type DaemonWorkerDescriptor, +} from "../modes/daemon/daemon-worker-protocol.js"; import { signalProcessGroupOrProcess } from "../utils/child-process.js"; import { formatDaemonListTable } from "./daemon-ps-format.js"; import { promptYesNo } from "./daemon-stop-confirm.js"; @@ -919,12 +923,23 @@ async function stopBackgroundService( return { reaped: `force-killed unresponsive background service (pid ${pid})` }; } +interface TrackedWorkerDescriptor { + workerId: string; + pid: number; + processStartId?: string; + socketPath: string; + supervisorSocketPath: string; + recoveryJournalPath?: string; + orphanProcessJournalPath?: string; + workerRecovery?: "disabled"; +} + interface TrackedWorker { - descriptor: DaemonWorkerDescriptor; + descriptor: TrackedWorkerDescriptor; descriptorPath: string; } -async function forceStopTrackedWorkers( +export async function forceStopTrackedWorkers( supervisorSocketPath: string, assertAdmission: () => Promise, ): Promise { @@ -974,11 +989,21 @@ async function forceStopTrackedWorkers( if (cleanupWorkerRecords) { try { removeSocketFile(descriptor.socketPath); - rmSync(worker.descriptorPath, { force: true }); - rmSync(descriptor.recoveryJournalPath, { force: true }); + if (descriptor.recoveryJournalPath) rmSync(descriptor.recoveryJournalPath, { force: true }); + if (descriptor.workerRecovery === "disabled") { + rmSync(join(dirname(worker.descriptorPath), `${descriptor.workerId}.recovery.jsonl`), { force: true }); + } if (descriptor.orphanProcessJournalPath) { rmSync(descriptor.orphanProcessJournalPath, { force: true }); } + const descriptorName = basename(worker.descriptorPath); + for (const name of readdirSync(dirname(worker.descriptorPath))) { + if (name.startsWith(`${descriptorName}.`) && name.endsWith(".tmp")) { + rmSync(join(dirname(worker.descriptorPath), name), { force: true }); + } + } + rmSync(`${worker.descriptorPath}${DAEMON_NONPERSISTENT_WORKER_MARKER_SUFFIX}`, { force: true }); + rmSync(worker.descriptorPath, { force: true }); } catch (error) { failures.push(`could not clean up worker ${descriptor.workerId}: ${String(error)}`); } @@ -1028,9 +1053,8 @@ function findAllTrackedWorkers(): TrackedWorker[] { const descriptorPath = join(directory, fileName); try { const value: unknown = JSON.parse(readFileSync(descriptorPath, "utf8")); - if (isTrackedWorkerDescriptor(value)) { - workers.push({ descriptor: value, descriptorPath }); - } + const descriptor = trackedWorkerDescriptor(value, descriptorPath); + if (descriptor) workers.push({ descriptor, descriptorPath }); } catch { // Invalid or concurrently removed descriptors are not safe shutdown targets. } @@ -1039,23 +1063,56 @@ function findAllTrackedWorkers(): TrackedWorker[] { return workers; } -function isTrackedWorkerDescriptor(value: unknown): value is DaemonWorkerDescriptor { - if (!value || typeof value !== "object") { - return false; - } - const descriptor = value as Partial; +function isTrackedWorkerIdentity(value: unknown): value is TrackedWorkerDescriptor & Record { + if (!value || typeof value !== "object") return false; + const descriptor = value as Partial; return ( - (descriptor.version === 1 || descriptor.version === 2) && typeof descriptor.supervisorSocketPath === "string" && typeof descriptor.workerId === "string" && Number.isInteger(descriptor.pid) && (descriptor.pid ?? 0) > 0 && (descriptor.processStartId === undefined || typeof descriptor.processStartId === "string") && typeof descriptor.socketPath === "string" && - typeof descriptor.recoveryJournalPath === "string" + (descriptor.orphanProcessJournalPath === undefined || typeof descriptor.orphanProcessJournalPath === "string") ); } +function isTrackedWorkerDescriptor(value: unknown): value is DaemonWorkerDescriptor { + if (!isTrackedWorkerIdentity(value)) return false; + const descriptor = value as Partial; + return descriptor.version === 3 + ? descriptor.workerRecovery === "disabled" && + typeof descriptor.ownerClientId === "string" && + descriptor.recoveryJournalPath === undefined + : (descriptor.version === 1 || descriptor.version === 2) && + descriptor.workerRecovery === undefined && + typeof descriptor.recoveryJournalPath === "string"; +} + +function trackedWorkerDescriptor(value: unknown, descriptorPath: string): TrackedWorkerDescriptor | undefined { + if (isTrackedWorkerDescriptor(value)) return value; + if (!isTrackedWorkerIdentity(value)) return undefined; + try { + if ( + readFileSync(`${descriptorPath}${DAEMON_NONPERSISTENT_WORKER_MARKER_SUFFIX}`, "utf8") !== + DAEMON_NONPERSISTENT_WORKER_MARKER + ) { + return undefined; + } + } catch { + return undefined; + } + return { + workerId: value.workerId, + pid: value.pid, + ...(value.processStartId ? { processStartId: value.processStartId } : {}), + socketPath: value.socketPath, + supervisorSocketPath: value.supervisorSocketPath, + ...(value.orphanProcessJournalPath ? { orphanProcessJournalPath: value.orphanProcessJournalPath } : {}), + workerRecovery: "disabled", + }; +} + async function stopTrackedProcess( pid: number, expectedStartId: string | undefined, diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index ce02ec4af6..a129e6540b 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -535,10 +535,18 @@ export interface PromptOptions { customMessage?: CustomMessage; } +const NONPERSISTENT_DAEMON_PROMPT = Symbol("nonpersistent-daemon-prompt"); + interface InternalPromptOptions extends PromptOptions { skipPrePromptWork?: boolean; returnAfterAccepted?: boolean; agentMessageId?: string; + [NONPERSISTENT_DAEMON_PROMPT]?: true; +} + +/** @internal Trusted daemon-worker boundary; intentionally not re-exported by the SDK entry point. */ +export function prepareNonpersistentDaemonPromptOptions(options: PromptOptions): PromptOptions { + return { ...options, [NONPERSISTENT_DAEMON_PROMPT]: true } as InternalPromptOptions; } type SubmissionExtensionCommandPolicy = "execute" | "reject" | "ignore"; @@ -5307,11 +5315,13 @@ export class AgentSession { this._beginPromptLifecycle( promptCorrelationId, kind, - createPromptRequestFingerprint({ - message: text, - images: options?.images, - queueIfBusy: options?.queueIfBusy, - }), + (options as InternalPromptOptions | undefined)?.[NONPERSISTENT_DAEMON_PROMPT] + ? undefined + : createPromptRequestFingerprint({ + message: text, + images: options?.images, + queueIfBusy: options?.queueIfBusy, + }), ); correlatedAdmissionController = new AbortController(); this._pendingCorrelatedPromptAdmissions.set(promptCorrelationId, correlatedAdmissionController); @@ -12597,6 +12607,10 @@ export class AgentSession { return context; } + hasLoadedExtensions(): boolean { + return this._extensionRunner.hasExtensions(); + } + hasExtensionHandlers(eventType: string): boolean { return this._extensionRunner.hasHandlers(eventType); } diff --git a/packages/coding-agent/src/core/extensions/runner.ts b/packages/coding-agent/src/core/extensions/runner.ts index eb1511ebc9..0484889c4a 100644 --- a/packages/coding-agent/src/core/extensions/runner.ts +++ b/packages/coding-agent/src/core/extensions/runner.ts @@ -526,6 +526,10 @@ export class ExtensionRunner { } } + hasExtensions(): boolean { + return this.extensions.length > 0; + } + hasHandlers(eventType: string): boolean { for (const ext of this.extensions) { const handlers = ext.handlers.get(eventType); diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts index 61a2c27e81..80ee090689 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -294,6 +294,7 @@ export { type AgentConnectionState, DaemonAgentConnection, type DaemonAgentConnectionOptions, + type DaemonNonpersistentWorkerCreateProof, type DaemonOwnedSessionContractProof, type DaemonOwnedSessionDaemonIdentity, type DaemonOwnedSessionDisposeOptions, @@ -440,6 +441,7 @@ export { } from "./modes/interactive/theme/theme.js"; export { CALLER_OWNED_SESSION_ENVIRONMENT_CLEANUP_FEATURE, + NONPERSISTENT_DAEMON_WORKER_FEATURE, PRIME_AGENT_SDK_FEATURES, type PrimeAgentSdkFeature, RECOVERABLE_OWNED_SESSION_ADOPTION_FEATURE, diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index a98a1e1028..f2fb20161e 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -1361,6 +1361,7 @@ export async function main(args: string[], options?: MainOptions) { restoreActiveSessionId: daemonWorkerBootstrap.activeSessionId, supervisorSocketPath: daemonWorkerBootstrap.supervisorSocketPath, supervisorAgentDir: daemonWorkerBootstrap.supervisorAgentDir, + recoveryMode: daemonWorkerBootstrap.recoveryMode, recoveryJournalPath: daemonWorkerBootstrap.recoveryJournalPath, }, }); diff --git a/packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts b/packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts index d6e85d9a54..911f1244fb 100644 --- a/packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts +++ b/packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts @@ -179,7 +179,13 @@ interface DaemonRuntimeSnapshotAttempt { interface CorrelatedPromptRoute { activeSessionId: string; sessionId: string; - requestFingerprint: string; + request: + | string + | { + message: string; + images: AgentConnectionCorrelatedPromptOptions["images"]; + queueIfBusy: AgentConnectionCorrelatedPromptOptions["queueIfBusy"]; + }; pending: boolean; } @@ -338,6 +344,14 @@ function reconnectDaemonTransportAfterUpdate(client: DaemonClient): Promise