@gajae-code/coding-agent 0.15.5 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -1
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
- package/dist/types/cli/model-presets-cli.d.ts +9 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +15 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +120 -0
- package/dist/types/config/file-lock.d.ts +54 -2
- package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
- package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +3 -2
- package/dist/types/config/model-preset-registry.d.ts +368 -0
- package/dist/types/config/model-profile-activation.d.ts +39 -0
- package/dist/types/config/model-profiles.d.ts +5 -3
- package/dist/types/config/model-registry.d.ts +17 -5
- package/dist/types/config/settings-schema.d.ts +19 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +2 -5
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/jobs-observer.d.ts +36 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +80 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/state-version.d.ts +4 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/index.d.ts +6 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/model-pin.d.ts +7 -2
- package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +67 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +2 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
- package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +24 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
- package/dist/types/session/sdk-run-capability.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/telemetry/control.d.ts +11 -0
- package/dist/types/telemetry/events.d.ts +20 -0
- package/dist/types/telemetry/index.d.ts +3 -0
- package/dist/types/telemetry/transport.d.ts +22 -0
- package/dist/types/tools/bash-interactive.d.ts +23 -0
- package/dist/types/tools/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -5
- package/dist/types/web/search/providers/base.d.ts +9 -4
- package/dist/types/web/search/providers/xai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +14 -0
- package/package.json +10 -7
- package/scripts/generate-hotkeys-docs.ts +4 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -3
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +671 -39
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/auth-broker-cli.ts +40 -12
- package/src/cli/auth-gateway-cli.ts +273 -97
- package/src/cli/config-cli.ts +2 -2
- package/src/cli/fast-help.ts +4 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +117 -16
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/launch.ts +86 -18
- package/src/commands/model-presets.ts +24 -0
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +44 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +890 -99
- package/src/config/internal/model-preset-registry-test-state.ts +28 -0
- package/src/config/internal/model-preset-registry-test-support.ts +11 -0
- package/src/config/keybindings.ts +6 -2
- package/src/config/model-preset-registry.ts +2795 -0
- package/src/config/model-profile-activation.ts +115 -28
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +56 -14
- package/src/config/model-registry.ts +1044 -122
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +19 -1
- package/src/config/settings.ts +11 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/projection-scan.ts +592 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +111 -42
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/extensibility/extensions/loader.ts +0 -1
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
- package/src/gjc-runtime/empty-delete-gc.ts +301 -0
- package/src/gjc-runtime/gc-render.ts +29 -0
- package/src/gjc-runtime/gc-runtime.ts +174 -11
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +1321 -147
- package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +66 -4
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +616 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +29 -10
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +51 -6
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/fold-receipt.md +3 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +17 -2
- package/src/sdk/broker/broker.ts +1671 -34
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +217 -46
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +548 -0
- package/src/sdk/broker/state-version.ts +26 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
- package/src/sdk/bus/index.ts +51 -4
- package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
- package/src/sdk/bus/prompt-reconciliation.ts +28 -4
- package/src/sdk/bus/slack-conversation.ts +5 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +11 -8
- package/src/sdk/cli/session-cli.ts +276 -6
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +101 -37
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/host.ts +27 -2
- package/src/sdk/host/model-pin.ts +185 -25
- package/src/sdk/host/sdk-run-capability.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +581 -43
- package/src/sdk/host/session-runtime.ts +655 -101
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- package/src/sdk/prompt-deadline-manager.ts +16 -1
- package/src/sdk/protocol/adapter-validation.ts +27 -0
- package/src/sdk/protocol/operation-inventory.generated.json +53 -2
- package/src/sdk/protocol/operation-registry.ts +17 -4
- package/src/sdk/router/session-router.ts +294 -154
- package/src/sdk/session.ts +440 -167
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +1292 -180
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/sdk-run-capability-internal.ts +15 -0
- package/src/session/sdk-run-capability.ts +2 -0
- package/src/session/session-manager.ts +342 -28
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/setup/provider-presets.json +1 -1
- package/src/slash-commands/helpers/credential-switch.ts +6 -1
- package/src/task/executor.ts +19 -3
- package/src/telemetry/control.ts +26 -0
- package/src/telemetry/events.ts +1268 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/transport.ts +87 -0
- package/src/tools/bash-interactive.ts +182 -85
- package/src/tools/bash.ts +1028 -109
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +11 -5
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/web/search/provider.ts +120 -11
- package/src/web/search/providers/anthropic.ts +89 -17
- package/src/web/search/providers/base.ts +9 -4
- package/src/web/search/providers/gemini.ts +87 -15
- package/src/web/search/providers/openai-compatible.ts +23 -8
- package/src/web/search/providers/xai.ts +98 -12
- package/src/web/search/types.ts +16 -0
|
@@ -7299,8 +7299,49 @@ export class SessionManager {
|
|
|
7299
7299
|
/** Serializes model, SDK, and ACP cwd transitions; dispose joins this tail. */
|
|
7300
7300
|
#cwdTransitionTail: Promise<void> = Promise.resolve();
|
|
7301
7301
|
#cwdTransitionOwner: symbol | undefined;
|
|
7302
|
+
#cwdMoveAdmissionClosed = false;
|
|
7302
7303
|
#cwdReadLeaseOwner = Symbol("cwd-read-lease-owner");
|
|
7303
7304
|
#cwdGeneration = 0;
|
|
7305
|
+
/**
|
|
7306
|
+
* Listeners invoked after every committed `moveTo`, for every move surface
|
|
7307
|
+
* (`move_session`, TUI `/move`, SDK/ACP `session.cwd.move`). They receive the previous
|
|
7308
|
+
* and new cwd and the previous session file, and run best-effort: a listener throw is
|
|
7309
|
+
* logged and never turns an already-durable move into a rejection.
|
|
7310
|
+
*/
|
|
7311
|
+
#afterMoveListeners = new Set<
|
|
7312
|
+
(move: { previousCwd: string; newCwd: string; previousSessionFile: string | undefined }) => void | Promise<void>
|
|
7313
|
+
>();
|
|
7314
|
+
#beforeMoveListeners = new Set<
|
|
7315
|
+
(move: {
|
|
7316
|
+
previousCwd: string;
|
|
7317
|
+
newCwd: string;
|
|
7318
|
+
previousSessionFile: string | undefined;
|
|
7319
|
+
newSessionFile: string | undefined;
|
|
7320
|
+
previousCwdIdentity: { dev: bigint; ino: bigint };
|
|
7321
|
+
newCwdIdentity: { dev: bigint; ino: bigint };
|
|
7322
|
+
}) => void | Promise<void>
|
|
7323
|
+
>();
|
|
7324
|
+
#moveAbortListeners = new Set<
|
|
7325
|
+
(move: {
|
|
7326
|
+
previousCwd: string;
|
|
7327
|
+
newCwd: string;
|
|
7328
|
+
previousSessionFile: string | undefined;
|
|
7329
|
+
newSessionFile: string | undefined;
|
|
7330
|
+
previousCwdIdentity: { dev: bigint; ino: bigint };
|
|
7331
|
+
newCwdIdentity: { dev: bigint; ino: bigint };
|
|
7332
|
+
preserveRecoveryJournal?: boolean;
|
|
7333
|
+
}) => void | Promise<void>
|
|
7334
|
+
>();
|
|
7335
|
+
#movePublicationListeners = new Set<
|
|
7336
|
+
(move: {
|
|
7337
|
+
previousCwd: string;
|
|
7338
|
+
newCwd: string;
|
|
7339
|
+
previousSessionFile: string | undefined;
|
|
7340
|
+
newSessionFile: string | undefined;
|
|
7341
|
+
previousCwdIdentity: { dev: bigint; ino: bigint };
|
|
7342
|
+
newCwdIdentity: { dev: bigint; ino: bigint };
|
|
7343
|
+
}) => void | Promise<void>
|
|
7344
|
+
>();
|
|
7304
7345
|
/** Number of tool executions currently holding a shared read lease on `cwd`. */
|
|
7305
7346
|
#cwdReaderCount = 0;
|
|
7306
7347
|
/** Resolved when the last outstanding read lease is released. */
|
|
@@ -8385,13 +8426,13 @@ export class SessionManager {
|
|
|
8385
8426
|
this.#usageStatistics = commit.usageStatistics;
|
|
8386
8427
|
this.#flushed = true;
|
|
8387
8428
|
this.#ensuredOnDisk = true;
|
|
8388
|
-
this.#managedPersistExpectedIdentity =
|
|
8429
|
+
this.#managedPersistExpectedIdentity =
|
|
8430
|
+
this.destination.kind === "managed" ? this.#captureManagedPersistIdentity(sessionFile) : undefined;
|
|
8389
8431
|
this.#lazyReopenSucceeded = true;
|
|
8390
8432
|
this.#lazyReopenFallbackReason = undefined;
|
|
8391
8433
|
initialized = true;
|
|
8392
8434
|
return true;
|
|
8393
|
-
} catch
|
|
8394
|
-
SessionManagerTestHooks.lastSidecarInitError = toError(error).message;
|
|
8435
|
+
} catch {
|
|
8395
8436
|
return false;
|
|
8396
8437
|
} finally {
|
|
8397
8438
|
if (!initialized) {
|
|
@@ -8572,7 +8613,8 @@ export class SessionManager {
|
|
|
8572
8613
|
this.#lazyReopenFallbackReason = "bounded_first_open_descriptor_changed";
|
|
8573
8614
|
return false;
|
|
8574
8615
|
}
|
|
8575
|
-
this.#managedPersistExpectedIdentity =
|
|
8616
|
+
this.#managedPersistExpectedIdentity =
|
|
8617
|
+
this.destination.kind === "managed" ? this.#captureManagedPersistIdentity(sessionFile) : undefined;
|
|
8576
8618
|
this.#sessionId = discovery.header.id;
|
|
8577
8619
|
this.#sessionName = discovery.header.title;
|
|
8578
8620
|
this.#titleSource = discovery.header.titleSource;
|
|
@@ -10727,10 +10769,139 @@ export class SessionManager {
|
|
|
10727
10769
|
async joinCwdTransition(): Promise<void> {
|
|
10728
10770
|
await this.#cwdTransitionTail;
|
|
10729
10771
|
}
|
|
10772
|
+
|
|
10773
|
+
async closeCwdMoveAdmission(): Promise<void> {
|
|
10774
|
+
await this.runExclusiveCwdTransition(async () => {
|
|
10775
|
+
this.#cwdMoveAdmissionClosed = true;
|
|
10776
|
+
});
|
|
10777
|
+
}
|
|
10730
10778
|
getCwdGeneration(): number {
|
|
10731
10779
|
return this.#cwdGeneration;
|
|
10732
10780
|
}
|
|
10733
10781
|
|
|
10782
|
+
/**
|
|
10783
|
+
* Register a listener invoked after every committed `moveTo`. Returns an idempotent
|
|
10784
|
+
* unregister handle. Every move surface funnels through `moveTo`, so a single
|
|
10785
|
+
* registration covers `move_session`, `/move`, and SDK/ACP `session.cwd.move`.
|
|
10786
|
+
*/
|
|
10787
|
+
registerAfterMoveListener(
|
|
10788
|
+
listener: (move: {
|
|
10789
|
+
previousCwd: string;
|
|
10790
|
+
newCwd: string;
|
|
10791
|
+
previousSessionFile: string | undefined;
|
|
10792
|
+
}) => void | Promise<void>,
|
|
10793
|
+
): () => void {
|
|
10794
|
+
this.#afterMoveListeners.add(listener);
|
|
10795
|
+
return () => {
|
|
10796
|
+
this.#afterMoveListeners.delete(listener);
|
|
10797
|
+
};
|
|
10798
|
+
}
|
|
10799
|
+
|
|
10800
|
+
registerBeforeMoveListener(
|
|
10801
|
+
listener: (move: {
|
|
10802
|
+
previousCwd: string;
|
|
10803
|
+
newCwd: string;
|
|
10804
|
+
previousSessionFile: string | undefined;
|
|
10805
|
+
newSessionFile: string | undefined;
|
|
10806
|
+
previousCwdIdentity: { dev: bigint; ino: bigint };
|
|
10807
|
+
newCwdIdentity: { dev: bigint; ino: bigint };
|
|
10808
|
+
preserveRecoveryJournal?: boolean;
|
|
10809
|
+
}) => void | Promise<void>,
|
|
10810
|
+
): () => void {
|
|
10811
|
+
this.#beforeMoveListeners.add(listener);
|
|
10812
|
+
return () => {
|
|
10813
|
+
this.#beforeMoveListeners.delete(listener);
|
|
10814
|
+
};
|
|
10815
|
+
}
|
|
10816
|
+
|
|
10817
|
+
async #runBeforeMoveListeners(move: {
|
|
10818
|
+
previousCwd: string;
|
|
10819
|
+
newCwd: string;
|
|
10820
|
+
previousSessionFile: string | undefined;
|
|
10821
|
+
newSessionFile: string | undefined;
|
|
10822
|
+
previousCwdIdentity: { dev: bigint; ino: bigint };
|
|
10823
|
+
newCwdIdentity: { dev: bigint; ino: bigint };
|
|
10824
|
+
}): Promise<void> {
|
|
10825
|
+
for (const listener of [...this.#beforeMoveListeners]) await listener(move);
|
|
10826
|
+
}
|
|
10827
|
+
|
|
10828
|
+
registerMovePublicationListener(
|
|
10829
|
+
listener: (move: {
|
|
10830
|
+
previousCwd: string;
|
|
10831
|
+
newCwd: string;
|
|
10832
|
+
previousSessionFile: string | undefined;
|
|
10833
|
+
newSessionFile: string | undefined;
|
|
10834
|
+
previousCwdIdentity: { dev: bigint; ino: bigint };
|
|
10835
|
+
newCwdIdentity: { dev: bigint; ino: bigint };
|
|
10836
|
+
preserveRecoveryJournal?: boolean;
|
|
10837
|
+
}) => void | Promise<void>,
|
|
10838
|
+
): () => void {
|
|
10839
|
+
this.#movePublicationListeners.add(listener);
|
|
10840
|
+
return () => {
|
|
10841
|
+
this.#movePublicationListeners.delete(listener);
|
|
10842
|
+
};
|
|
10843
|
+
}
|
|
10844
|
+
|
|
10845
|
+
async #runMovePublicationListeners(move: {
|
|
10846
|
+
previousCwd: string;
|
|
10847
|
+
newCwd: string;
|
|
10848
|
+
previousSessionFile: string | undefined;
|
|
10849
|
+
newSessionFile: string | undefined;
|
|
10850
|
+
previousCwdIdentity: { dev: bigint; ino: bigint };
|
|
10851
|
+
newCwdIdentity: { dev: bigint; ino: bigint };
|
|
10852
|
+
}): Promise<void> {
|
|
10853
|
+
for (const listener of [...this.#movePublicationListeners]) await listener(move);
|
|
10854
|
+
}
|
|
10855
|
+
|
|
10856
|
+
registerMoveAbortListener(
|
|
10857
|
+
listener: (move: {
|
|
10858
|
+
previousCwd: string;
|
|
10859
|
+
newCwd: string;
|
|
10860
|
+
previousSessionFile: string | undefined;
|
|
10861
|
+
newSessionFile: string | undefined;
|
|
10862
|
+
previousCwdIdentity: { dev: bigint; ino: bigint };
|
|
10863
|
+
newCwdIdentity: { dev: bigint; ino: bigint };
|
|
10864
|
+
preserveRecoveryJournal?: boolean;
|
|
10865
|
+
}) => void | Promise<void>,
|
|
10866
|
+
): () => void {
|
|
10867
|
+
this.#moveAbortListeners.add(listener);
|
|
10868
|
+
return () => {
|
|
10869
|
+
this.#moveAbortListeners.delete(listener);
|
|
10870
|
+
};
|
|
10871
|
+
}
|
|
10872
|
+
|
|
10873
|
+
async #runMoveAbortListeners(move: {
|
|
10874
|
+
previousCwd: string;
|
|
10875
|
+
newCwd: string;
|
|
10876
|
+
previousSessionFile: string | undefined;
|
|
10877
|
+
newSessionFile: string | undefined;
|
|
10878
|
+
previousCwdIdentity: { dev: bigint; ino: bigint };
|
|
10879
|
+
newCwdIdentity: { dev: bigint; ino: bigint };
|
|
10880
|
+
preserveRecoveryJournal?: boolean;
|
|
10881
|
+
}): Promise<void> {
|
|
10882
|
+
for (const listener of [...this.#moveAbortListeners]) {
|
|
10883
|
+
try {
|
|
10884
|
+
await listener(move);
|
|
10885
|
+
} catch (error) {
|
|
10886
|
+
logger.warn("move-abort listener failed", { error: String(error), cwd: move.newCwd });
|
|
10887
|
+
}
|
|
10888
|
+
}
|
|
10889
|
+
}
|
|
10890
|
+
|
|
10891
|
+
async #runAfterMoveListeners(move: {
|
|
10892
|
+
previousCwd: string;
|
|
10893
|
+
newCwd: string;
|
|
10894
|
+
previousSessionFile: string | undefined;
|
|
10895
|
+
}): Promise<void> {
|
|
10896
|
+
for (const listener of [...this.#afterMoveListeners]) {
|
|
10897
|
+
try {
|
|
10898
|
+
await listener(move);
|
|
10899
|
+
} catch (error) {
|
|
10900
|
+
logger.warn("after-move listener failed", { error: String(error), cwd: move.newCwd });
|
|
10901
|
+
}
|
|
10902
|
+
}
|
|
10903
|
+
}
|
|
10904
|
+
|
|
10734
10905
|
#ownsCwdTransition(): boolean {
|
|
10735
10906
|
const owner = this.#cwdTransitionOwner;
|
|
10736
10907
|
return owner !== undefined && cwdTransitionAls.getStore() === owner;
|
|
@@ -10801,15 +10972,73 @@ export class SessionManager {
|
|
|
10801
10972
|
options?: {
|
|
10802
10973
|
expectedIdentity?: { dev: bigint; ino: bigint };
|
|
10803
10974
|
targetHandle?: { stat: (opts: { bigint: true }) => Promise<fs.BigIntStats> };
|
|
10975
|
+
expectedSourceIdentity?: { dev: bigint; ino: bigint };
|
|
10976
|
+
sourceHandle?: { stat: (opts: { bigint: true }) => Promise<fs.BigIntStats> };
|
|
10804
10977
|
},
|
|
10805
10978
|
): Promise<void> {
|
|
10806
10979
|
if (!this.#ownsCwdTransition()) {
|
|
10807
10980
|
return this.runExclusiveCwdTransition(() => this.moveTo(newCwd, options));
|
|
10808
10981
|
}
|
|
10809
|
-
|
|
10982
|
+
if (this.#cwdMoveAdmissionClosed) throw new Error("Session cwd move admission is closed.");
|
|
10983
|
+
if (!options?.sourceHandle) {
|
|
10984
|
+
const sourceHandle = await SessionManager.openNoFollowDirectory(this.cwd);
|
|
10985
|
+
try {
|
|
10986
|
+
const sourceIdentity = await sourceHandle.stat({ bigint: true });
|
|
10987
|
+
if (!sourceIdentity.isDirectory()) throw new Error(`Current cwd is not a directory: ${this.cwd}`);
|
|
10988
|
+
return await this.moveTo(newCwd, {
|
|
10989
|
+
...options,
|
|
10990
|
+
expectedSourceIdentity: options?.expectedSourceIdentity ?? {
|
|
10991
|
+
dev: sourceIdentity.dev,
|
|
10992
|
+
ino: sourceIdentity.ino,
|
|
10993
|
+
},
|
|
10994
|
+
sourceHandle,
|
|
10995
|
+
});
|
|
10996
|
+
} finally {
|
|
10997
|
+
await sourceHandle.close();
|
|
10998
|
+
}
|
|
10999
|
+
}
|
|
11000
|
+
const resolvedCwd = await fs.promises.realpath(path.resolve(newCwd));
|
|
11001
|
+
const resolvedIdentity = await fs.promises.lstat(resolvedCwd, { bigint: true });
|
|
11002
|
+
if (!resolvedIdentity.isDirectory() || resolvedIdentity.isSymbolicLink()) {
|
|
11003
|
+
throw new Error(`Refusing to rescope: target is not a directory: ${resolvedCwd}`);
|
|
11004
|
+
}
|
|
11005
|
+
if (!options?.targetHandle) {
|
|
11006
|
+
const targetHandle = await SessionManager.openNoFollowDirectory(resolvedCwd);
|
|
11007
|
+
try {
|
|
11008
|
+
const targetIdentity = await targetHandle.stat({ bigint: true });
|
|
11009
|
+
const revalidatedCwd = await fs.promises.realpath(path.resolve(newCwd));
|
|
11010
|
+
const namedIdentity = await fs.promises.lstat(revalidatedCwd, { bigint: true });
|
|
11011
|
+
if (
|
|
11012
|
+
revalidatedCwd !== resolvedCwd ||
|
|
11013
|
+
!namedIdentity.isDirectory() ||
|
|
11014
|
+
namedIdentity.isSymbolicLink() ||
|
|
11015
|
+
namedIdentity.dev !== resolvedIdentity.dev ||
|
|
11016
|
+
namedIdentity.ino !== resolvedIdentity.ino ||
|
|
11017
|
+
targetIdentity.dev !== resolvedIdentity.dev ||
|
|
11018
|
+
targetIdentity.ino !== resolvedIdentity.ino
|
|
11019
|
+
)
|
|
11020
|
+
throw new Error(`Refusing to rescope: target identity changed: ${resolvedCwd}`);
|
|
11021
|
+
return await this.moveTo(resolvedCwd, {
|
|
11022
|
+
...options,
|
|
11023
|
+
expectedIdentity: options?.expectedIdentity ?? {
|
|
11024
|
+
dev: targetIdentity.dev,
|
|
11025
|
+
ino: targetIdentity.ino,
|
|
11026
|
+
},
|
|
11027
|
+
targetHandle,
|
|
11028
|
+
});
|
|
11029
|
+
} finally {
|
|
11030
|
+
await targetHandle.close();
|
|
11031
|
+
}
|
|
11032
|
+
}
|
|
10810
11033
|
if (options?.expectedIdentity || options?.targetHandle) {
|
|
10811
11034
|
await this.#assertCwdTargetIdentity(resolvedCwd, options);
|
|
10812
11035
|
}
|
|
11036
|
+
if (options?.expectedSourceIdentity || options?.sourceHandle) {
|
|
11037
|
+
await this.#assertCwdTargetIdentity(this.cwd, {
|
|
11038
|
+
expectedIdentity: options.expectedSourceIdentity,
|
|
11039
|
+
targetHandle: options.sourceHandle,
|
|
11040
|
+
});
|
|
11041
|
+
}
|
|
10813
11042
|
if (resolvedCwd === this.cwd) return;
|
|
10814
11043
|
const previousCwd = this.cwd;
|
|
10815
11044
|
const previousSessionDir = this.sessionDir;
|
|
@@ -10825,6 +11054,19 @@ export class SessionManager {
|
|
|
10825
11054
|
)
|
|
10826
11055
|
: this.destination;
|
|
10827
11056
|
const newSessionDir = nextDestination.directory;
|
|
11057
|
+
const nextSessionFile = this.#sessionFile
|
|
11058
|
+
? path.join(newSessionDir, path.basename(this.#sessionFile))
|
|
11059
|
+
: undefined;
|
|
11060
|
+
const pinnedSourceIdentity = await options.sourceHandle.stat({ bigint: true });
|
|
11061
|
+
const pinnedTargetIdentity = await options.targetHandle.stat({ bigint: true });
|
|
11062
|
+
const moveDetails = {
|
|
11063
|
+
previousCwd,
|
|
11064
|
+
newCwd: resolvedCwd,
|
|
11065
|
+
previousSessionFile,
|
|
11066
|
+
newSessionFile: nextSessionFile,
|
|
11067
|
+
previousCwdIdentity: { dev: pinnedSourceIdentity.dev, ino: pinnedSourceIdentity.ino },
|
|
11068
|
+
newCwdIdentity: { dev: pinnedTargetIdentity.dev, ino: pinnedTargetIdentity.ino },
|
|
11069
|
+
};
|
|
10828
11070
|
let hadSessionFile = false;
|
|
10829
11071
|
const managedMove = this.destination.kind === "managed" && nextDestination.kind === "managed";
|
|
10830
11072
|
let managedSourceStore: ManagedSessionDescendantStore | undefined;
|
|
@@ -10832,16 +11074,29 @@ export class SessionManager {
|
|
|
10832
11074
|
let rollbackManagedMove: (() => Promise<void>) | undefined;
|
|
10833
11075
|
let residentTransition: PreparedResidentStoreTransition | undefined;
|
|
10834
11076
|
const hadPersistedSession = this.#ensuredOnDisk;
|
|
11077
|
+
try {
|
|
11078
|
+
await this.#runBeforeMoveListeners(moveDetails);
|
|
11079
|
+
} catch (error) {
|
|
11080
|
+
await this.#runMoveAbortListeners(moveDetails);
|
|
11081
|
+
throw error;
|
|
11082
|
+
}
|
|
10835
11083
|
|
|
11084
|
+
let targetIdentityRevalidated = false;
|
|
11085
|
+
let publicationStarted = false;
|
|
10836
11086
|
if (this.persist && this.#sessionFile) {
|
|
10837
11087
|
// Close the persist writer before moving files
|
|
10838
|
-
|
|
11088
|
+
try {
|
|
11089
|
+
await this.#closePersistWriter();
|
|
11090
|
+
} catch (error) {
|
|
11091
|
+
await this.#runMoveAbortListeners(moveDetails);
|
|
11092
|
+
throw error;
|
|
11093
|
+
}
|
|
10839
11094
|
this.#persistChain = Promise.resolve();
|
|
10840
11095
|
this.#persistError = undefined;
|
|
10841
11096
|
this.#persistErrorReported = false;
|
|
10842
11097
|
|
|
10843
11098
|
const oldSessionFile = this.#sessionFile;
|
|
10844
|
-
const newSessionFile =
|
|
11099
|
+
const newSessionFile = nextSessionFile!;
|
|
10845
11100
|
const oldArtifactDir = oldSessionFile.slice(0, -6); // strip .jsonl
|
|
10846
11101
|
const newArtifactDir = newSessionFile.slice(0, -6);
|
|
10847
11102
|
let managedTranscript!: ReturnType<ManagedSessionDescendantStore["readExpected"]>;
|
|
@@ -10851,24 +11106,30 @@ export class SessionManager {
|
|
|
10851
11106
|
hadSessionFile = managedMove ? false : this.#storage.existsSync(oldSessionFile);
|
|
10852
11107
|
let movedSessionFile = false;
|
|
10853
11108
|
let movedArtifactDir = false;
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
11109
|
+
try {
|
|
11110
|
+
const materializedEntries = materializeResidentEntriesForReadSync(
|
|
11111
|
+
this.#fileEntries,
|
|
11112
|
+
this.#residentBlobStores(),
|
|
11113
|
+
);
|
|
11114
|
+
const transitionEntries: FileEntry[] = materializedEntries.map(entry =>
|
|
11115
|
+
entry.type === "session" ? { ...entry, cwd: resolvedCwd } : entry,
|
|
11116
|
+
);
|
|
11117
|
+
residentTransition = this.#prepareResidentTextStoreTransition(
|
|
11118
|
+
{
|
|
11119
|
+
target: { sessionId: this.#sessionId, sessionFile: newSessionFile },
|
|
11120
|
+
primary: {
|
|
11121
|
+
mode: "materialize",
|
|
11122
|
+
sourceEntries: transitionEntries,
|
|
11123
|
+
sourceStores: { textStore: null, imageStore: this.#residentImageBlobStore },
|
|
11124
|
+
},
|
|
10868
11125
|
},
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
)
|
|
11126
|
+
"retain-and-throw",
|
|
11127
|
+
);
|
|
11128
|
+
} catch (error) {
|
|
11129
|
+
residentTransition?.dispose();
|
|
11130
|
+
await this.#runMoveAbortListeners(moveDetails);
|
|
11131
|
+
throw error;
|
|
11132
|
+
}
|
|
10872
11133
|
const discardResidentTransitionAndThrow = (error: unknown): never => {
|
|
10873
11134
|
residentTransition?.dispose();
|
|
10874
11135
|
throw error;
|
|
@@ -10911,6 +11172,14 @@ export class SessionManager {
|
|
|
10911
11172
|
throw new Error("artifact_destination_mismatch");
|
|
10912
11173
|
destination.fsyncTree();
|
|
10913
11174
|
};
|
|
11175
|
+
try {
|
|
11176
|
+
await this.#runMovePublicationListeners(moveDetails);
|
|
11177
|
+
publicationStarted = true;
|
|
11178
|
+
} catch (error) {
|
|
11179
|
+
residentTransition?.dispose();
|
|
11180
|
+
await this.#runMoveAbortListeners(moveDetails);
|
|
11181
|
+
throw error;
|
|
11182
|
+
}
|
|
10914
11183
|
try {
|
|
10915
11184
|
if (managedMove) {
|
|
10916
11185
|
if (previousDestination.kind !== "managed" || nextDestination.kind !== "managed")
|
|
@@ -10996,6 +11265,16 @@ export class SessionManager {
|
|
|
10996
11265
|
if (!isEnoent(err)) throw err;
|
|
10997
11266
|
}
|
|
10998
11267
|
}
|
|
11268
|
+
if (options?.expectedIdentity || options?.targetHandle) {
|
|
11269
|
+
if (options.expectedSourceIdentity || options.sourceHandle) {
|
|
11270
|
+
await this.#assertCwdTargetIdentity(previousCwd, {
|
|
11271
|
+
expectedIdentity: options.expectedSourceIdentity,
|
|
11272
|
+
targetHandle: options.sourceHandle,
|
|
11273
|
+
});
|
|
11274
|
+
}
|
|
11275
|
+
await this.#assertCwdTargetIdentity(resolvedCwd, options);
|
|
11276
|
+
targetIdentityRevalidated = true;
|
|
11277
|
+
}
|
|
10999
11278
|
} catch (err) {
|
|
11000
11279
|
if (managedMove && managedDestinationStore) {
|
|
11001
11280
|
try {
|
|
@@ -11037,6 +11316,7 @@ export class SessionManager {
|
|
|
11037
11316
|
)
|
|
11038
11317
|
await managedSourceStore.publishNoReplace(path.basename(oldSessionFile), managedTranscript.bytes);
|
|
11039
11318
|
} catch (rollbackErr) {
|
|
11319
|
+
await this.#runMoveAbortListeners({ ...moveDetails, preserveRecoveryJournal: true });
|
|
11040
11320
|
discardResidentTransitionAndThrow(
|
|
11041
11321
|
new Error(`Failed to rollback managed move: ${toError(rollbackErr).message}`, {
|
|
11042
11322
|
cause: toError(err),
|
|
@@ -11044,18 +11324,46 @@ export class SessionManager {
|
|
|
11044
11324
|
);
|
|
11045
11325
|
}
|
|
11046
11326
|
} else {
|
|
11047
|
-
|
|
11048
|
-
|
|
11327
|
+
try {
|
|
11328
|
+
if (movedArtifactDir) await movePathAcrossDevicesSafe(newArtifactDir, oldArtifactDir);
|
|
11329
|
+
if (movedSessionFile) await movePathAcrossDevicesSafe(newSessionFile, oldSessionFile);
|
|
11330
|
+
} catch (rollbackError) {
|
|
11331
|
+
await this.#runMoveAbortListeners({ ...moveDetails, preserveRecoveryJournal: true });
|
|
11332
|
+
discardResidentTransitionAndThrow(
|
|
11333
|
+
new Error(`Failed to rollback move: ${toError(rollbackError).message}`, { cause: toError(err) }),
|
|
11334
|
+
);
|
|
11335
|
+
}
|
|
11049
11336
|
}
|
|
11337
|
+
await this.#runMoveAbortListeners(moveDetails);
|
|
11050
11338
|
discardResidentTransitionAndThrow(err);
|
|
11051
11339
|
}
|
|
11052
11340
|
this.#sessionFile = newSessionFile;
|
|
11053
11341
|
}
|
|
11342
|
+
if (!publicationStarted) {
|
|
11343
|
+
try {
|
|
11344
|
+
await this.#runMovePublicationListeners(moveDetails);
|
|
11345
|
+
publicationStarted = true;
|
|
11346
|
+
} catch (error) {
|
|
11347
|
+
await this.#runMoveAbortListeners(moveDetails);
|
|
11348
|
+
throw error;
|
|
11349
|
+
}
|
|
11350
|
+
}
|
|
11054
11351
|
|
|
11055
11352
|
// Update cwd and sessionDir after physical publication succeeds. Metadata failures restore the source
|
|
11056
11353
|
// authority but deliberately retain any destination publication evidence rather than deleting it.
|
|
11057
|
-
if (options?.expectedIdentity || options?.targetHandle) {
|
|
11058
|
-
|
|
11354
|
+
if (!targetIdentityRevalidated && (options?.expectedIdentity || options?.targetHandle)) {
|
|
11355
|
+
try {
|
|
11356
|
+
if (options.expectedSourceIdentity || options.sourceHandle) {
|
|
11357
|
+
await this.#assertCwdTargetIdentity(previousCwd, {
|
|
11358
|
+
expectedIdentity: options.expectedSourceIdentity,
|
|
11359
|
+
targetHandle: options.sourceHandle,
|
|
11360
|
+
});
|
|
11361
|
+
}
|
|
11362
|
+
await this.#assertCwdTargetIdentity(resolvedCwd, options);
|
|
11363
|
+
} catch (error) {
|
|
11364
|
+
await this.#runMoveAbortListeners(moveDetails);
|
|
11365
|
+
throw error;
|
|
11366
|
+
}
|
|
11059
11367
|
}
|
|
11060
11368
|
this.#cwdGeneration += 1;
|
|
11061
11369
|
this.cwd = resolvedCwd;
|
|
@@ -11095,6 +11403,7 @@ export class SessionManager {
|
|
|
11095
11403
|
await rollbackManagedMove();
|
|
11096
11404
|
} catch (rollbackError) {
|
|
11097
11405
|
if (toError(rollbackError).message !== "cleanup_pending") {
|
|
11406
|
+
await this.#runMoveAbortListeners({ ...moveDetails, preserveRecoveryJournal: true });
|
|
11098
11407
|
throw new Error(`Failed to rollback managed move: ${toError(rollbackError).message}`, {
|
|
11099
11408
|
cause: toError(error),
|
|
11100
11409
|
});
|
|
@@ -11115,12 +11424,14 @@ export class SessionManager {
|
|
|
11115
11424
|
const header = this.#fileEntries.find(entry => entry.type === "session") as SessionHeader | undefined;
|
|
11116
11425
|
if (header) applyHeaderPatch(header, { cwd: previousCwd });
|
|
11117
11426
|
this.#headerExportRevision++;
|
|
11427
|
+
await this.#runMoveAbortListeners(moveDetails);
|
|
11118
11428
|
throw error;
|
|
11119
11429
|
}
|
|
11120
11430
|
// The destination has already been published. Retain it rather than moving it
|
|
11121
11431
|
// back over the source after a metadata failure.
|
|
11122
11432
|
this.#managedTranscriptStoreCache = null;
|
|
11123
11433
|
this.#headerExportRevision++;
|
|
11434
|
+
await this.#runAfterMoveListeners(moveDetails);
|
|
11124
11435
|
throw error;
|
|
11125
11436
|
}
|
|
11126
11437
|
if (residentTransition) this.#commitResidentTextStoreTransition(residentTransition);
|
|
@@ -11129,6 +11440,9 @@ export class SessionManager {
|
|
|
11129
11440
|
if (this.#sessionFile) {
|
|
11130
11441
|
writeTerminalBreadcrumb(resolvedCwd, this.#sessionFile);
|
|
11131
11442
|
}
|
|
11443
|
+
// The move is fully committed here; notify every surface's after-move listeners
|
|
11444
|
+
// (coordinator runtime-state relocation, postmortem finalizer rebinding, ...).
|
|
11445
|
+
await this.#runAfterMoveListeners(moveDetails);
|
|
11132
11446
|
}
|
|
11133
11447
|
|
|
11134
11448
|
/** Sync version for initial creation (no existing writer to close). */
|
|
@@ -18,8 +18,9 @@ export interface YieldDispatcher<P> {
|
|
|
18
18
|
export interface YieldQueueOptions {
|
|
19
19
|
isStreaming: () => boolean;
|
|
20
20
|
injectStreaming(msg: AgentMessage): void;
|
|
21
|
-
injectIdle(messages: AgentMessage[]): Promise<void>;
|
|
22
|
-
scheduleIdleFlush(run: () => Promise<void
|
|
21
|
+
injectIdle(messages: AgentMessage[], signal?: AbortSignal): Promise<void>;
|
|
22
|
+
scheduleIdleFlush(run: (signal?: AbortSignal) => Promise<void>, onSkip: () => void): void;
|
|
23
|
+
getIdleFlushSignal?(): AbortSignal | undefined;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
type YieldFlushMode = "streaming" | "idle";
|
|
@@ -39,6 +40,7 @@ export class YieldQueue {
|
|
|
39
40
|
readonly #dispatchers = new Map<string, StoredDispatcher>();
|
|
40
41
|
readonly #entries = new Map<string, unknown[]>();
|
|
41
42
|
#idleFlushPending = false;
|
|
43
|
+
#idleFlushPendingOwner: symbol | undefined;
|
|
42
44
|
|
|
43
45
|
constructor(options: YieldQueueOptions) {
|
|
44
46
|
this.#options = options;
|
|
@@ -82,9 +84,10 @@ export class YieldQueue {
|
|
|
82
84
|
return false;
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
async flush(mode: YieldFlushMode): Promise<void> {
|
|
87
|
+
async flush(mode: YieldFlushMode, signal?: AbortSignal): Promise<void> {
|
|
86
88
|
if (mode === "idle") {
|
|
87
89
|
this.#idleFlushPending = false;
|
|
90
|
+
this.#idleFlushPendingOwner = undefined;
|
|
88
91
|
}
|
|
89
92
|
const idleMessages: AgentMessage[] = [];
|
|
90
93
|
for (const [kind, dispatcher] of this.#dispatchers) {
|
|
@@ -105,16 +108,20 @@ export class YieldQueue {
|
|
|
105
108
|
}
|
|
106
109
|
if (mode === "idle" && idleMessages.length > 0) {
|
|
107
110
|
try {
|
|
108
|
-
await this.#options.injectIdle(idleMessages);
|
|
111
|
+
await this.#options.injectIdle(idleMessages, signal ?? this.#options.getIdleFlushSignal?.());
|
|
109
112
|
} catch (error) {
|
|
110
113
|
logger.warn("Yield queue idle dispatch failed", { error: formatError(error) });
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
}
|
|
114
117
|
|
|
115
|
-
clear(): void {
|
|
118
|
+
clear(onDrop?: (kind: string, entries: readonly unknown[]) => void): void {
|
|
119
|
+
if (onDrop) {
|
|
120
|
+
for (const [kind, entries] of this.#entries) onDrop(kind, entries);
|
|
121
|
+
}
|
|
116
122
|
this.#entries.clear();
|
|
117
123
|
this.#idleFlushPending = false;
|
|
124
|
+
this.#idleFlushPendingOwner = undefined;
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
/** Drop only the queued entries of a single kind, leaving other kinds intact. */
|
|
@@ -140,14 +147,21 @@ export class YieldQueue {
|
|
|
140
147
|
#scheduleIdleFlush(): void {
|
|
141
148
|
if (this.#idleFlushPending) return;
|
|
142
149
|
this.#idleFlushPending = true;
|
|
150
|
+
const owner = Symbol("idle-flush");
|
|
151
|
+
this.#idleFlushPendingOwner = owner;
|
|
152
|
+
const releaseOwner = () => {
|
|
153
|
+
if (this.#idleFlushPendingOwner !== owner) return;
|
|
154
|
+
this.#idleFlushPendingOwner = undefined;
|
|
155
|
+
this.#idleFlushPending = false;
|
|
156
|
+
};
|
|
143
157
|
try {
|
|
144
|
-
this.#options.scheduleIdleFlush(async
|
|
145
|
-
|
|
158
|
+
this.#options.scheduleIdleFlush(async signal => {
|
|
159
|
+
releaseOwner();
|
|
146
160
|
if (this.#options.isStreaming()) return;
|
|
147
|
-
await this.flush("idle");
|
|
148
|
-
});
|
|
161
|
+
await this.flush("idle", signal);
|
|
162
|
+
}, releaseOwner);
|
|
149
163
|
} catch (error) {
|
|
150
|
-
|
|
164
|
+
releaseOwner();
|
|
151
165
|
logger.warn("Yield queue idle flush scheduling failed", { error: formatError(error) });
|
|
152
166
|
}
|
|
153
167
|
}
|
|
@@ -748,6 +748,7 @@ export function shouldOfferOnboarding(state: FrictionlessOnboardingState): boole
|
|
|
748
748
|
|
|
749
749
|
export interface AutomaticOnboardingInput {
|
|
750
750
|
normalInteractive: boolean;
|
|
751
|
+
automation?: boolean;
|
|
751
752
|
initialMessage?: string;
|
|
752
753
|
initialMessages: readonly string[];
|
|
753
754
|
initialImages?: readonly unknown[];
|
|
@@ -757,6 +758,7 @@ export interface AutomaticOnboardingInput {
|
|
|
757
758
|
export function shouldOfferAutomaticOnboarding(input: AutomaticOnboardingInput): boolean {
|
|
758
759
|
return (
|
|
759
760
|
input.normalInteractive &&
|
|
761
|
+
!input.automation &&
|
|
760
762
|
!input.initialMessage &&
|
|
761
763
|
input.initialMessages.length === 0 &&
|
|
762
764
|
(input.initialImages?.length ?? 0) === 0 &&
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
{"id":"glm","aliases":["zai","z-ai","bigmodel"],"name":"GLM / zAI","description":"OpenAI-compatible GLM endpoint from zAI/BigModel","compatibility":"openai","api":"openai-completions","providerId":"glm-proxy","baseUrl":"https://api.z.ai/api/paas/v4","apiKeyEnv":"ZAI_API_KEY","models":["glm-4.6"],"compat":{"supportsDeveloperRole":false,"supportsReasoningEffort":false,"thinkingFormat":"zai","reasoningContentField":"reasoning_content"}},
|
|
5
5
|
{"id":"alibaba-token-plan","aliases":["alibaba","token-plan"],"name":"Alibaba Token Plan","description":"OpenAI-compatible Alibaba Token Plan endpoint","compatibility":"openai","api":"openai-completions","providerId":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","apiKeyEnv":"ALIBABA_TOKEN_PLAN_API_KEY","models":["qwen3.8-max-preview","qwen3.8-max","glm-5.2","deepseek-v4-pro"],"modelApi":{"qwen3.8-max-preview":"openai-responses","qwen3.8-max":"openai-responses","glm-5.2":"openai-completions","deepseek-v4-pro":"openai-completions"},"compat":{"supportsDeveloperRole":false}},
|
|
6
6
|
{"id":"cline-pass","aliases":["clinepass","cline"],"name":"ClinePass","description":"ClinePass subscription models discovered from Cline's upstream models.dev catalog","compatibility":"openai","api":"openai-completions","providerId":"cline-pass","baseUrl":"https://api.cline.bot/api/v1","apiKeyEnv":"CLINE_API_KEY","discovery":{"type":"models-dev","modelsDevProvider":"cline-pass"},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning"}},
|
|
7
|
-
{"id":"commandcode-goat","aliases":["commandcode","command-code","goat"],"name":"Command Code GOAT","description":"Command Code GOAT coding-plan models discovered from its OpenAI-compatible Provider API","compatibility":"openai","api":"openai-completions","providerId":"commandcode-goat","baseUrl":"https://api.commandcode.ai/provider/v1","apiKeyEnv":"CMD_API_KEY","discovery":{"type":"openai-models-list","apiByModelPrefix":{"claude-":"anthropic-messages"}},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning_content"}},
|
|
7
|
+
{"id":"commandcode-goat","aliases":["commandcode","command-code","goat"],"name":"Command Code GOAT","description":"Command Code GOAT coding-plan models discovered from its OpenAI-compatible Provider API","compatibility":"openai","api":"openai-completions","providerId":"commandcode-goat","baseUrl":"https://api.commandcode.ai/provider/v1","apiKeyEnv":"CMD_API_KEY","discovery":{"type":"openai-models-list","apiByModelPrefix":{"claude":"anthropic-messages","anthropic/claude":"anthropic-messages"}},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","reasoningContentField":"reasoning_content"}},
|
|
8
8
|
{"id":"litellm","aliases":["litellm-proxy"],"name":"LiteLLM Proxy","description":"OpenAI-compatible LiteLLM proxy endpoint (user-supplied base URL) with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"litellm","parameterized":true,"apiKeyEnv":"LITELLM_API_KEY","discovery":{"type":"openai-models-list"}},
|
|
9
9
|
{"id":"openai-compatible-proxy","aliases":["openai-proxy","compatible-proxy","custom-proxy"],"name":"OpenAI-Compatible Proxy","description":"Generic OpenAI-compatible gateway (OpenRouter, OpenCode Go, custom) with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"openai-compatible-proxy","parameterized":true,"apiKeyEnv":"OPENAI_COMPATIBLE_API_KEY","discovery":{"type":"openai-models-list"}}
|
|
10
10
|
]
|
|
@@ -131,7 +131,12 @@ export async function switchSessionCredentialCommand(runtime: SlashCommandRuntim
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
try {
|
|
134
|
-
authStorage.switchSessionCredential(
|
|
134
|
+
authStorage.switchSessionCredential(
|
|
135
|
+
provider,
|
|
136
|
+
runtime.session.credentialSessionId,
|
|
137
|
+
parsed.selector,
|
|
138
|
+
runtime.session.modelRegistry.getAuthStorageOwner?.(),
|
|
139
|
+
);
|
|
135
140
|
} catch (error) {
|
|
136
141
|
return error instanceof Error ? error.message : String(error);
|
|
137
142
|
}
|
package/src/task/executor.ts
CHANGED
|
@@ -1121,6 +1121,8 @@ export async function runSubprocessOnce(options: ExecutorOptions): Promise<Singl
|
|
|
1121
1121
|
const abortController = new AbortController();
|
|
1122
1122
|
const abortSignal = abortController.signal;
|
|
1123
1123
|
let activeSession: AgentSession | null = null;
|
|
1124
|
+
let ownedModelRegistry: ModelRegistry | undefined;
|
|
1125
|
+
let ownedAuthStorage: AuthStorage | undefined;
|
|
1124
1126
|
let unsubscribe: (() => void) | null = null;
|
|
1125
1127
|
let yieldCalled = false;
|
|
1126
1128
|
let pauseRequested = false;
|
|
@@ -1703,9 +1705,19 @@ export async function runSubprocessOnce(options: ExecutorOptions): Promise<Singl
|
|
|
1703
1705
|
checkAbort();
|
|
1704
1706
|
// Pin authStorage to modelRegistry.authStorage — mirrors the createAgentSession invariant.
|
|
1705
1707
|
const registryFromParent = options.modelRegistry !== undefined;
|
|
1706
|
-
const
|
|
1707
|
-
options.
|
|
1708
|
-
|
|
1708
|
+
const registryAuthStorage =
|
|
1709
|
+
options.authStorage ??
|
|
1710
|
+
options.modelRegistry?.authStorage ??
|
|
1711
|
+
(await awaitAbortable(discoverAuthStorage(settings.getAgentDir())));
|
|
1712
|
+
if (options.modelRegistry === undefined && options.authStorage === undefined)
|
|
1713
|
+
ownedAuthStorage = registryAuthStorage;
|
|
1714
|
+
ownedModelRegistry = options.modelRegistry
|
|
1715
|
+
? undefined
|
|
1716
|
+
: new ModelRegistry(registryAuthStorage, path.join(settings.getAgentDir(), "models.yml"), settings, {
|
|
1717
|
+
agentDir: settings.getAgentDir(),
|
|
1718
|
+
automaticRefresh: false,
|
|
1719
|
+
});
|
|
1720
|
+
const modelRegistry = options.modelRegistry ?? ownedModelRegistry!;
|
|
1709
1721
|
const authStorage = modelRegistry.authStorage;
|
|
1710
1722
|
if (options.authStorage && options.authStorage !== authStorage) {
|
|
1711
1723
|
throw new Error(
|
|
@@ -2500,6 +2512,10 @@ export async function runSubprocessOnce(options: ExecutorOptions): Promise<Singl
|
|
|
2500
2512
|
// Ignore cleanup errors
|
|
2501
2513
|
}
|
|
2502
2514
|
}
|
|
2515
|
+
ownedModelRegistry?.dispose();
|
|
2516
|
+
ownedModelRegistry = undefined;
|
|
2517
|
+
ownedAuthStorage?.close();
|
|
2518
|
+
ownedAuthStorage = undefined;
|
|
2503
2519
|
}
|
|
2504
2520
|
|
|
2505
2521
|
return {
|