@gajae-code/coding-agent 0.15.6 → 0.16.1
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 +99 -0
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +14 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/file-lock.d.ts +5 -0
- package/dist/types/config/model-registry.d.ts +22 -0
- package/dist/types/config/settings-schema.d.ts +27 -8
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -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/main.d.ts +1 -0
- 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/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- 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/interactive-mode.d.ts +16 -2
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +79 -2
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +40 -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/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +21 -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/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +9 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- 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 +12 -5
- package/dist/types/sdk/cli/session-cli.d.ts +20 -3
- package/dist/types/sdk/client/client.d.ts +3 -2
- 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/events.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +70 -17
- 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 +3 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +6 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +52 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +79 -2
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +16 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +7 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +13 -1
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +140 -23
- package/src/commands/launch.ts +86 -18
- package/src/commands/sdk.ts +135 -28
- package/src/config/autorouting-tier-map.ts +12 -0
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/file-lock.ts +158 -83
- package/src/config/model-preset-registry.ts +178 -23
- package/src/config/model-profiles.ts +5 -5
- package/src/config/model-registry.ts +291 -42
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +27 -8
- package/src/config/settings.ts +51 -15
- package/src/coordinator-mcp/projection-scan.ts +27 -6
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +115 -21
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +213 -14
- package/src/gjc-runtime/session-state-sidecar.ts +938 -39
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -3
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/main.ts +61 -3
- 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 +610 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- 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 +23 -9
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +30 -2
- package/src/modes/controllers/selector-controller.ts +200 -98
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +5 -0
- package/src/sdk/broker/broker.ts +1646 -27
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +81 -3
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +440 -75
- 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 +591 -0
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +43 -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 +136 -18
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +84 -7
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- 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 +41 -15
- package/src/sdk/cli/session-cli.ts +297 -26
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/host/control/dispatch.ts +101 -40
- 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/events.ts +1 -0
- package/src/sdk/host/host.ts +29 -2
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +673 -14
- package/src/sdk/host/session-runtime.ts +674 -103
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +17 -4
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +105 -3
- package/src/sdk/protocol/operation-registry.ts +14 -4
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +219 -118
- package/src/sdk/session.ts +416 -195
- package/src/sdk/startup-capability.ts +1 -0
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1628 -318
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/session-manager.ts +850 -97
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/telemetry/events.ts +1138 -12
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +412 -75
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +10 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +72 -1
|
@@ -2,17 +2,27 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import * as fsSync from "node:fs";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { replaceTabs, truncateToWidth } from "@gajae-code/tui";
|
|
5
6
|
import { getAgentDir } from "@gajae-code/utils";
|
|
6
7
|
import { repo as resolveGitRepository } from "../../utils/git";
|
|
7
8
|
import { ensureBroker } from "../broker/ensure";
|
|
9
|
+
import { resolveSessionLocator } from "../broker/session-index";
|
|
10
|
+
import {
|
|
11
|
+
resolveScopeRequest,
|
|
12
|
+
type ScopeNameV1,
|
|
13
|
+
type ScopeRequestV1,
|
|
14
|
+
type SdkSearchResultV1,
|
|
15
|
+
type SdkSearchRowV1,
|
|
16
|
+
} from "../broker/session-scope";
|
|
8
17
|
import { lifecycleRequestTimeoutMs } from "../broker/startup-budget";
|
|
9
|
-
import { SdkClientError } from "../client";
|
|
18
|
+
import { readSdkBrokerDiscovery, SdkClient, SdkClientError } from "../client";
|
|
10
19
|
import { createBrokerSessionLifecycleService } from "../lifecycle/broker-client";
|
|
11
20
|
import type {
|
|
12
21
|
SessionLifecycleMutationRequest,
|
|
13
22
|
SessionLifecycleOperation,
|
|
14
23
|
SessionLifecycleSavedSession,
|
|
15
24
|
SessionLifecycleSavedSessionIdentity,
|
|
25
|
+
SessionLifecycleService,
|
|
16
26
|
SessionReconcileUncertainTarget,
|
|
17
27
|
} from "../lifecycle/service";
|
|
18
28
|
import { PROMPT_CLIENT_REF_MAX_LENGTH } from "../prompt-status";
|
|
@@ -24,6 +34,7 @@ import {
|
|
|
24
34
|
import { adapterDispositionError, findOperation, type OperationKind } from "../protocol/operation-registry";
|
|
25
35
|
import { type SessionAttachment, SessionRouter, SessionRouterError, type SessionRouterFrame } from "../router";
|
|
26
36
|
import { SessionListTraversalError, sessionListPageFromResponse, traverseSessionList } from "../session-list";
|
|
37
|
+
import { SESSION_REQUEST_TIMEOUT_MS } from "../session-reconnect";
|
|
27
38
|
import {
|
|
28
39
|
type SdkCheckpointRecordV1,
|
|
29
40
|
type SdkRetentionGapV1,
|
|
@@ -39,6 +50,7 @@ import {
|
|
|
39
50
|
|
|
40
51
|
export type SdkSessionCliAction =
|
|
41
52
|
| "list"
|
|
53
|
+
| "search"
|
|
42
54
|
| "inspect"
|
|
43
55
|
| "send"
|
|
44
56
|
| "status"
|
|
@@ -64,7 +76,6 @@ export interface SdkSessionCliArgs {
|
|
|
64
76
|
jsonInputFile?: string;
|
|
65
77
|
jsonInputStdin?: boolean;
|
|
66
78
|
idempotencyKey?: string;
|
|
67
|
-
scope?: string;
|
|
68
79
|
confirm?: boolean;
|
|
69
80
|
cursor?: string;
|
|
70
81
|
wait?: boolean;
|
|
@@ -73,6 +84,10 @@ export interface SdkSessionCliArgs {
|
|
|
73
84
|
untilIdle?: boolean;
|
|
74
85
|
allEvents?: boolean;
|
|
75
86
|
repo?: string;
|
|
87
|
+
scope?: string;
|
|
88
|
+
limit?: number;
|
|
89
|
+
json?: boolean;
|
|
90
|
+
|
|
76
91
|
agentDir?: string;
|
|
77
92
|
}
|
|
78
93
|
|
|
@@ -95,6 +110,9 @@ const TAIL_OFFLINE_MAX_SCAN_BYTES = 4 * 1024 * 1024;
|
|
|
95
110
|
const TAIL_OFFLINE_MAX_SCANNED_LINES = 4_096;
|
|
96
111
|
const TAIL_OFFLINE_MAX_LINE_BYTES = 256 * 1024;
|
|
97
112
|
const transcriptDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
113
|
+
const SEARCH_PROBE_TIMEOUT_MS = 2_000;
|
|
114
|
+
const SEARCH_PROBE_MAX_ROWS = 100;
|
|
115
|
+
const SEARCH_TEXT_WIDTH = 80;
|
|
98
116
|
|
|
99
117
|
const TERMINAL_TURN_KINDS = new Set(["turn_end", "agent_end"]);
|
|
100
118
|
const START_TURN_KINDS = new Set(["turn_start", "agent_start"]);
|
|
@@ -214,6 +232,10 @@ function isEndpointOperation(operation: string): boolean {
|
|
|
214
232
|
return operation === "session.get_endpoint";
|
|
215
233
|
}
|
|
216
234
|
|
|
235
|
+
function isRawSpawnOperation(kind: OperationKind, operation: string): boolean {
|
|
236
|
+
return kind === "global" && operation === "session.spawn";
|
|
237
|
+
}
|
|
238
|
+
|
|
217
239
|
function isLifecycleOperation(operation: string): operation is LifecycleMutationOperation {
|
|
218
240
|
return (
|
|
219
241
|
operation === "session.create" ||
|
|
@@ -257,9 +279,11 @@ async function withRouter<T>(
|
|
|
257
279
|
agentDir: string,
|
|
258
280
|
action: (router: SessionRouter) => Promise<T>,
|
|
259
281
|
onFrame?: (attachment: SessionAttachment, frame: SessionRouterFrame) => void,
|
|
282
|
+
sessionIds?: readonly string[],
|
|
260
283
|
): Promise<T> {
|
|
261
284
|
const router = new SessionRouter({
|
|
262
285
|
agentDir,
|
|
286
|
+
...(sessionIds === undefined ? {} : { sessionIds }),
|
|
263
287
|
...(onFrame === undefined ? {} : { deps: { onFrame } }),
|
|
264
288
|
});
|
|
265
289
|
let result!: T;
|
|
@@ -367,6 +391,145 @@ async function sessionRows(agentDir: string): Promise<SessionRows> {
|
|
|
367
391
|
});
|
|
368
392
|
}
|
|
369
393
|
|
|
394
|
+
function searchScopeRequest(scope: ScopeNameV1, locator: { cwd: string; worktreeRoot: string | null }): ScopeRequestV1 {
|
|
395
|
+
return { version: 1, requested: scope, requestAnchor: { cwd: locator.cwd, worktreeRoot: locator.worktreeRoot } };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function searchProbe(row: SdkSearchRowV1, router: SessionRouter): Promise<SdkSearchRowV1> {
|
|
399
|
+
if (!row.live) return Promise.resolve({ ...row, probe: "stale" });
|
|
400
|
+
return (async () => {
|
|
401
|
+
try {
|
|
402
|
+
const attachment = router.attachment(row.id);
|
|
403
|
+
if (!attachment) return { ...row, probe: "unreachable" };
|
|
404
|
+
const response = await router.request(
|
|
405
|
+
row.id,
|
|
406
|
+
{ type: "query_request", query: "session.checkpoint", input: {} },
|
|
407
|
+
attachment.generation,
|
|
408
|
+
attachment,
|
|
409
|
+
{ timeoutMs: SEARCH_PROBE_TIMEOUT_MS },
|
|
410
|
+
);
|
|
411
|
+
return { ...row, probe: response.ok === true ? "reachable" : "unreachable" };
|
|
412
|
+
} catch {
|
|
413
|
+
return { ...row, probe: "unreachable" };
|
|
414
|
+
}
|
|
415
|
+
})();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function mergeProbedSearchRows(
|
|
419
|
+
rows: readonly SdkSearchRowV1[],
|
|
420
|
+
probedRows: readonly SdkSearchRowV1[],
|
|
421
|
+
): readonly SdkSearchRowV1[] {
|
|
422
|
+
return [...probedRows, ...rows.slice(probedRows.length)];
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function probeSearchRows(agentDir: string, result: SdkSearchResultV1): Promise<SdkSearchResultV1> {
|
|
426
|
+
if (result.status === "unavailable" || result.status === "not-in-git-worktree" || result.rows.length === 0)
|
|
427
|
+
return result;
|
|
428
|
+
const rows = result.rows.slice(0, SEARCH_PROBE_MAX_ROWS);
|
|
429
|
+
try {
|
|
430
|
+
const probes = await withRouter(
|
|
431
|
+
agentDir,
|
|
432
|
+
async router => await Promise.all(rows.map(row => searchProbe(row, router))),
|
|
433
|
+
undefined,
|
|
434
|
+
rows.map(row => row.id),
|
|
435
|
+
);
|
|
436
|
+
return { ...result, rows: mergeProbedSearchRows(result.rows, probes) };
|
|
437
|
+
} catch {
|
|
438
|
+
return {
|
|
439
|
+
...result,
|
|
440
|
+
rows: mergeProbedSearchRows(
|
|
441
|
+
result.rows,
|
|
442
|
+
rows.map(row => ({ ...row, probe: row.live ? "unreachable" : "stale" })),
|
|
443
|
+
),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Resolves, lists, and probes only the exact rows selected by the Broker-scoped search. */
|
|
449
|
+
export async function runSdkSearch(
|
|
450
|
+
args: Pick<SdkSessionCliArgs, "agentDir" | "repo" | "scope" | "limit" | "cursor">,
|
|
451
|
+
createService: (agentDir: string) => SessionLifecycleService = createBrokerSessionLifecycleService,
|
|
452
|
+
probe: (agentDir: string, result: SdkSearchResultV1) => Promise<SdkSearchResultV1> = probeSearchRows,
|
|
453
|
+
): Promise<{ result: SdkSearchResultV1; exitCode: 0 | 1 }> {
|
|
454
|
+
const agentDir = args.agentDir ?? getAgentDir();
|
|
455
|
+
const locator = await resolveSessionLocator(args.repo ?? process.cwd(), agentDir);
|
|
456
|
+
const scope: ScopeNameV1 =
|
|
457
|
+
args.scope === undefined || args.scope === "repo" || args.scope === "pwd" || args.scope === "global"
|
|
458
|
+
? ((args.scope ?? "repo") as ScopeNameV1)
|
|
459
|
+
: (() => {
|
|
460
|
+
throw new SdkSessionCliError(
|
|
461
|
+
"usage",
|
|
462
|
+
`Invalid search scope "${args.scope}". Expected repo, pwd, or global.`,
|
|
463
|
+
2,
|
|
464
|
+
);
|
|
465
|
+
})();
|
|
466
|
+
const request = searchScopeRequest(scope, locator);
|
|
467
|
+
const resolved = await resolveScopeRequest(request);
|
|
468
|
+
const outcome = await createService(agentDir).scopedList(request, args.limit, args.cursor);
|
|
469
|
+
if (outcome.ok) {
|
|
470
|
+
if ("rows" in outcome.result) {
|
|
471
|
+
if (outcome.result.status === "not-in-git-worktree") return { result: outcome.result, exitCode: 0 };
|
|
472
|
+
if (outcome.result.status === "unavailable") return { result: outcome.result, exitCode: 1 };
|
|
473
|
+
return { result: await probe(agentDir, outcome.result), exitCode: 0 };
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
result: {
|
|
477
|
+
version: 1,
|
|
478
|
+
scope: resolved,
|
|
479
|
+
status: "unavailable",
|
|
480
|
+
observedAt: new Date().toISOString(),
|
|
481
|
+
rows: [],
|
|
482
|
+
warnings: [],
|
|
483
|
+
error: { code: "malformed_response", message: "broker search returned an unscoped result" },
|
|
484
|
+
},
|
|
485
|
+
exitCode: 1,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
if (!outcome.ok && outcome.result?.status === "unavailable") return { result: outcome.result, exitCode: 1 };
|
|
489
|
+
return {
|
|
490
|
+
result: {
|
|
491
|
+
version: 1,
|
|
492
|
+
scope: resolved,
|
|
493
|
+
status: "unavailable",
|
|
494
|
+
observedAt: new Date().toISOString(),
|
|
495
|
+
rows: [],
|
|
496
|
+
warnings: [],
|
|
497
|
+
error: { code: outcome.error.code, message: outcome.error.message },
|
|
498
|
+
},
|
|
499
|
+
exitCode: 1,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function searchScopeLabel(result: SdkSearchResultV1): string {
|
|
504
|
+
const resolved = result.scope.resolved;
|
|
505
|
+
if (resolved === null) return "not-in-git-worktree";
|
|
506
|
+
if (resolved.kind === "repo") return resolved.worktreeRoot;
|
|
507
|
+
if (resolved.kind === "pwd") return resolved.cwd;
|
|
508
|
+
return resolved.visibility;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function safeSearchText(value: string): string {
|
|
512
|
+
return truncateToWidth(replaceTabs(value).replaceAll(/[\r\n]/g, " "), SEARCH_TEXT_WIDTH);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** Renders a credential-free scope/status preamble and bounded search table. */
|
|
516
|
+
export function renderSdkSearchTable(result: SdkSearchResultV1): string {
|
|
517
|
+
const lines = [
|
|
518
|
+
`Scope requested: ${result.scope.requested}`,
|
|
519
|
+
`Scope resolved: ${safeSearchText(searchScopeLabel(result))}`,
|
|
520
|
+
`Status: ${result.status}`,
|
|
521
|
+
`Observed at: ${result.observedAt}`,
|
|
522
|
+
...(result.cursor === undefined ? [] : [`Continuation cursor: ${safeSearchText(result.cursor)}`]),
|
|
523
|
+
];
|
|
524
|
+
if (result.rows.length === 0) return lines.join("\n");
|
|
525
|
+
lines.push("ID PROBE LIVE CWD");
|
|
526
|
+
for (const row of result.rows)
|
|
527
|
+
lines.push(
|
|
528
|
+
`${safeSearchText(row.id).padEnd(20)} ${(row.probe ?? "-").padEnd(11)} ${String(row.live).padEnd(4)} ${safeSearchText(row.locator.cwd)}`,
|
|
529
|
+
);
|
|
530
|
+
return lines.join("\n");
|
|
531
|
+
}
|
|
532
|
+
|
|
370
533
|
const SESSION_LIST_SCOPES: readonly SdkSessionListScope[] = ["repo", "cwd", "worktree", "all"];
|
|
371
534
|
/** Row workspaces whose locator cannot be canonicalized still get one deterministic identity. */
|
|
372
535
|
const WORKSPACE_IDENTITY_CACHE_LIMIT = 4096;
|
|
@@ -450,14 +613,15 @@ async function workspaceIdentity(target: string): Promise<WorkspaceIdentity> {
|
|
|
450
613
|
/**
|
|
451
614
|
* Resolves the list selection from `--repo` (default: process cwd) under the
|
|
452
615
|
* effective scope. Outside Git, `repo`/`worktree` fail typed and actionable —
|
|
453
|
-
* they never broaden to `all`; `cwd` remains an exact canonical match
|
|
616
|
+
* they never broaden to `all`; `cwd` remains an exact canonical match, and
|
|
617
|
+
* `all` ignores the selection entirely so it works outside any checkout.
|
|
454
618
|
*/
|
|
455
619
|
export async function resolveSessionListSelection(
|
|
456
620
|
scope: SdkSessionListScope,
|
|
457
621
|
repoArg: string | undefined,
|
|
458
622
|
): Promise<SdkSessionListSelection> {
|
|
459
623
|
const selection = await workspaceIdentity(repoArg ?? process.cwd());
|
|
460
|
-
if (scope !== "cwd" && !selection.repoRoot)
|
|
624
|
+
if (scope !== "cwd" && scope !== "all" && !selection.repoRoot)
|
|
461
625
|
throw new SdkSessionCliError(
|
|
462
626
|
"not_a_repository",
|
|
463
627
|
`--scope ${scope} requires a Git repository, but "${selection.canonicalPath}" is outside any Git checkout. ` +
|
|
@@ -487,7 +651,7 @@ export async function filterSessionRowsByScope(
|
|
|
487
651
|
const warnings: string[] = [];
|
|
488
652
|
const sessions: SdkSessionRowV1[] = [];
|
|
489
653
|
for (const row of rows) {
|
|
490
|
-
const identity = await workspaceIdentity(row.locator.
|
|
654
|
+
const identity = await workspaceIdentity(row.locator.cwd);
|
|
491
655
|
let keep: boolean;
|
|
492
656
|
if (scope === "cwd") keep = identity.canonicalPath === selection.selection.canonicalPath;
|
|
493
657
|
else if (scope === "worktree")
|
|
@@ -496,7 +660,7 @@ export async function filterSessionRowsByScope(
|
|
|
496
660
|
if (keep) sessions.push(row);
|
|
497
661
|
else if (identity.repoRoot === null && identity.commonDir === null)
|
|
498
662
|
warnings.push(
|
|
499
|
-
`Session ${row.sessionId} workspace "${row.locator.
|
|
663
|
+
`Session ${row.sessionId} workspace "${row.locator.cwd}" is outside Git; excluded by scope ${scope}.`,
|
|
500
664
|
);
|
|
501
665
|
}
|
|
502
666
|
return { sessions, warnings };
|
|
@@ -563,7 +727,7 @@ async function requestControl(
|
|
|
563
727
|
const attachment = attachmentFor(router, sessionId);
|
|
564
728
|
const response = await router.request(
|
|
565
729
|
sessionId,
|
|
566
|
-
|
|
730
|
+
controlRequestFrame(operation, input, args),
|
|
567
731
|
attachment.generation,
|
|
568
732
|
attachment,
|
|
569
733
|
args.timeoutMs === undefined ? undefined : { timeoutMs: args.timeoutMs },
|
|
@@ -572,6 +736,81 @@ async function requestControl(
|
|
|
572
736
|
return response;
|
|
573
737
|
}
|
|
574
738
|
|
|
739
|
+
/** Builds the public CLI control envelope without leaking envelope fields into operation input. */
|
|
740
|
+
export function controlRequestFrame(
|
|
741
|
+
operation: string,
|
|
742
|
+
input: JsonRecord,
|
|
743
|
+
args: Pick<SdkSessionCliArgs, "confirm" | "idempotencyKey">,
|
|
744
|
+
): JsonRecord {
|
|
745
|
+
return {
|
|
746
|
+
type: "control_request",
|
|
747
|
+
operation,
|
|
748
|
+
input,
|
|
749
|
+
confirm: args.confirm === true,
|
|
750
|
+
...(args.idempotencyKey ? { idempotencyKey: args.idempotencyKey } : {}),
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const BROKER_OPERATOR_ABORT_FIELDS = new Set(["mode", "scope", "operator"]);
|
|
755
|
+
|
|
756
|
+
export function operatorAbortBrokerRequest(
|
|
757
|
+
sessionId: string,
|
|
758
|
+
operation: string,
|
|
759
|
+
input: JsonRecord,
|
|
760
|
+
args: Pick<SdkSessionCliArgs, "confirm" | "idempotencyKey">,
|
|
761
|
+
): JsonRecord | undefined {
|
|
762
|
+
if (!Object.hasOwn(input, "operator")) return undefined;
|
|
763
|
+
if (operation !== "turn.abort")
|
|
764
|
+
throw new SdkSessionCliError("invalid_input", "operator is only available for terminal turn.abort.", 2);
|
|
765
|
+
if (input.operator !== true)
|
|
766
|
+
throw new SdkSessionCliError("invalid_input", "turn.abort operator must be true when provided.", 2);
|
|
767
|
+
if (input.mode !== "terminal")
|
|
768
|
+
throw new SdkSessionCliError("invalid_input", 'operator turn.abort requires mode:"terminal".', 2);
|
|
769
|
+
for (const key of Object.keys(input))
|
|
770
|
+
if (!BROKER_OPERATOR_ABORT_FIELDS.has(key))
|
|
771
|
+
throw new SdkSessionCliError(`invalid_input`, `Unknown turn.abort terminal field: ${key}`, 2);
|
|
772
|
+
if (input.scope !== undefined && input.scope !== "turn" && input.scope !== "owned")
|
|
773
|
+
throw new SdkSessionCliError("invalid_input", 'operator turn.abort scope must be "turn" or "owned".', 2);
|
|
774
|
+
if (args.confirm !== true)
|
|
775
|
+
throw new SdkSessionCliError("invalid_input", "operator terminal abort requires --confirm.", 2);
|
|
776
|
+
if (!args.idempotencyKey)
|
|
777
|
+
throw new SdkSessionCliError("invalid_input", "operator terminal abort requires --idempotency-key.", 2);
|
|
778
|
+
return { sessionId, operation: "turn.abort", input, confirm: true };
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
async function requestBrokerOperatorAbort(
|
|
782
|
+
agentDir: string,
|
|
783
|
+
request: JsonRecord,
|
|
784
|
+
args: SdkSessionCliArgs,
|
|
785
|
+
): Promise<JsonRecord> {
|
|
786
|
+
const idempotencyKey = args.idempotencyKey;
|
|
787
|
+
if (!idempotencyKey)
|
|
788
|
+
throw new SdkSessionCliError("invalid_input", "operator terminal abort requires --idempotency-key.", 2);
|
|
789
|
+
const discovery = await readSdkBrokerDiscovery(agentDir);
|
|
790
|
+
if (!discovery) throw new SdkSessionCliError("session_unavailable", "SDK broker discovery is unavailable.", 1);
|
|
791
|
+
const timeoutMs = args.timeoutMs ?? SESSION_REQUEST_TIMEOUT_MS;
|
|
792
|
+
const client = await SdkClient.connect(discovery.url, discovery.token, {
|
|
793
|
+
timeoutMs,
|
|
794
|
+
reconnectAttempts: 0,
|
|
795
|
+
});
|
|
796
|
+
try {
|
|
797
|
+
const response = await client.global("session.control", request, {
|
|
798
|
+
idempotencyKey,
|
|
799
|
+
timeoutMs,
|
|
800
|
+
});
|
|
801
|
+
const result = object(response);
|
|
802
|
+
if (!result) throw new SdkClientError("protocol_error", "SDK broker returned a malformed control response.");
|
|
803
|
+
throwResponseFailure(result);
|
|
804
|
+
return result;
|
|
805
|
+
} finally {
|
|
806
|
+
await client.close().catch(error => {
|
|
807
|
+
process.stderr.write(
|
|
808
|
+
`SDK broker control cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`,
|
|
809
|
+
);
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
575
814
|
async function requestQuery(
|
|
576
815
|
router: SessionRouter,
|
|
577
816
|
sessionId: string,
|
|
@@ -731,11 +970,7 @@ function extractCheckpoint(response: unknown): CheckpointExtraction {
|
|
|
731
970
|
if (gap !== undefined) return { gap };
|
|
732
971
|
const record = toCheckpointRecordV1(result.checkpoint ?? result);
|
|
733
972
|
const cursor =
|
|
734
|
-
typeof result.
|
|
735
|
-
? result.cursor
|
|
736
|
-
: typeof result.checkpointToken === "string" && result.checkpointToken
|
|
737
|
-
? result.checkpointToken
|
|
738
|
-
: undefined;
|
|
973
|
+
typeof result.checkpointToken === "string" && result.checkpointToken ? result.checkpointToken : undefined;
|
|
739
974
|
return { record, cursor };
|
|
740
975
|
}
|
|
741
976
|
|
|
@@ -912,12 +1147,6 @@ function eventGapToRetentionGap(
|
|
|
912
1147
|
return undefined;
|
|
913
1148
|
}
|
|
914
1149
|
|
|
915
|
-
function publicationSequence(publicationId: string): number | undefined {
|
|
916
|
-
const parts = publicationId.split(":");
|
|
917
|
-
const value = Number(parts.at(-1));
|
|
918
|
-
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
1150
|
function tailItemFromRouterFrame(frame: SessionRouterFrame): SdkTailItemV1 | undefined {
|
|
922
1151
|
if (frame.name === undefined) return undefined;
|
|
923
1152
|
// The replay response is an out-of-band control frame, not an event-ring
|
|
@@ -925,18 +1154,16 @@ function tailItemFromRouterFrame(frame: SessionRouterFrame): SdkTailItemV1 | und
|
|
|
925
1154
|
// intentionally has no event `seq`; projecting it as a tail item would make
|
|
926
1155
|
// the strict event-position validator reject every otherwise valid tail.
|
|
927
1156
|
if (frame.name === "event_replay_result") return undefined;
|
|
928
|
-
const seq = frame.
|
|
1157
|
+
const seq = frame.seq;
|
|
929
1158
|
return toTailItemV1(
|
|
930
1159
|
{
|
|
931
1160
|
kind: frame.name,
|
|
932
|
-
...(
|
|
933
|
-
...(seq === undefined ? {} : { seq }),
|
|
1161
|
+
...(seq === undefined ? {} : { generation: frame.generation, seq }),
|
|
934
1162
|
payload: frame.body,
|
|
935
1163
|
},
|
|
936
1164
|
{
|
|
937
1165
|
kind: frame.name,
|
|
938
|
-
...(
|
|
939
|
-
...(seq === undefined ? {} : { seq }),
|
|
1166
|
+
...(seq === undefined ? {} : { generation: frame.generation, seq }),
|
|
940
1167
|
},
|
|
941
1168
|
);
|
|
942
1169
|
}
|
|
@@ -1121,6 +1348,12 @@ async function offlineTailReplay(
|
|
|
1121
1348
|
});
|
|
1122
1349
|
if (!outcome.ok)
|
|
1123
1350
|
throw new SdkSessionCliError(outcome.error.code, outcome.error.message, 1, { certainty: outcome.certainty });
|
|
1351
|
+
if ("rows" in outcome.result)
|
|
1352
|
+
throw new SdkSessionCliError(
|
|
1353
|
+
"malformed_response",
|
|
1354
|
+
"broker returned a scoped result for retained transcript replay",
|
|
1355
|
+
1,
|
|
1356
|
+
);
|
|
1124
1357
|
const savedSession = outcome.result.savedSession;
|
|
1125
1358
|
if (!savedSession)
|
|
1126
1359
|
throw new SdkSessionCliError(
|
|
@@ -1144,7 +1377,9 @@ async function offlineTailReplay(
|
|
|
1144
1377
|
version: SESSION_ROWS_VERSION,
|
|
1145
1378
|
source: "offline",
|
|
1146
1379
|
session: row,
|
|
1147
|
-
items: entries.map((entry, index) =>
|
|
1380
|
+
items: entries.map((entry, index) =>
|
|
1381
|
+
toTailItemV1(entry, { kind: "transcript", revision: index + 1, seq: index }),
|
|
1382
|
+
),
|
|
1148
1383
|
terminal: true,
|
|
1149
1384
|
},
|
|
1150
1385
|
};
|
|
@@ -1252,6 +1487,8 @@ async function runLiveTail(
|
|
|
1252
1487
|
if (attachment.sessionId !== sessionId) return;
|
|
1253
1488
|
const item = tailItemFromRouterFrame(frame);
|
|
1254
1489
|
if (!item) return;
|
|
1490
|
+
if (item.revision === undefined && checkpoint?.revision !== undefined) item.revision = checkpoint.revision;
|
|
1491
|
+
// If checkpoint was undefined, queue for later stamping? For now leave without rev
|
|
1255
1492
|
applyLifecycle(mergeEventTailItems(eventItems, seenEvents, [item], include));
|
|
1256
1493
|
};
|
|
1257
1494
|
|
|
@@ -1293,10 +1530,18 @@ async function runLiveTail(
|
|
|
1293
1530
|
);
|
|
1294
1531
|
throwResponseFailure(response);
|
|
1295
1532
|
const page = extractTranscriptPage(response);
|
|
1533
|
+
const rev = checkpoint?.revision;
|
|
1534
|
+
let nextSeq = transcriptItems.length;
|
|
1296
1535
|
mergeTailItems(
|
|
1297
1536
|
transcriptItems,
|
|
1298
1537
|
seenTranscript,
|
|
1299
|
-
page.items.map(item =>
|
|
1538
|
+
page.items.map(item => {
|
|
1539
|
+
const it = toTailItemV1(item, { kind: "transcript" });
|
|
1540
|
+
if (it.revision === undefined && rev !== undefined) it.revision = rev;
|
|
1541
|
+
if (it.generation === undefined) it.generation = checkpoint?.generation ?? 0;
|
|
1542
|
+
if (it.seq === undefined) it.seq = nextSeq++;
|
|
1543
|
+
return it;
|
|
1544
|
+
}),
|
|
1300
1545
|
include,
|
|
1301
1546
|
);
|
|
1302
1547
|
if (page.complete || page.cursor === undefined) break;
|
|
@@ -1342,9 +1587,20 @@ async function runLiveTail(
|
|
|
1342
1587
|
raw.seq,
|
|
1343
1588
|
);
|
|
1344
1589
|
}
|
|
1345
|
-
const replayItems = rawEvents.map(event =>
|
|
1590
|
+
const replayItems = rawEvents.map(event => {
|
|
1591
|
+
const it = toTailItemV1(event, { kind: "event" });
|
|
1592
|
+
if (it.revision === undefined && checkpoint?.revision !== undefined) it.revision = checkpoint.revision;
|
|
1593
|
+
return it;
|
|
1594
|
+
});
|
|
1346
1595
|
applyLifecycle(mergeEventTailItems(eventItems, seenEvents, replayItems, include));
|
|
1347
1596
|
if (malformed !== undefined) throw malformed;
|
|
1597
|
+
if (checkpoint !== undefined) {
|
|
1598
|
+
const checkpointKey = { generation: checkpoint.generation, seq: checkpoint.seq };
|
|
1599
|
+
if (turnStateKey === undefined || compareTailSeqKeys(checkpointKey, turnStateKey) >= 0) {
|
|
1600
|
+
turnStateKey = checkpointKey;
|
|
1601
|
+
turnIdle = checkpoint.idle;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1348
1604
|
if (liveReason === undefined && args.untilIdle === true && turnIdle) liveReason = "idle";
|
|
1349
1605
|
if (liveReason === undefined) {
|
|
1350
1606
|
const completion = Promise.withResolvers<TailExitReason>();
|
|
@@ -1413,6 +1669,8 @@ async function runRawControl(
|
|
|
1413
1669
|
const invalid = validateAdapterControl(operation, input);
|
|
1414
1670
|
if (invalid) throw new SdkSessionCliError(invalid.code, invalid.message, 2);
|
|
1415
1671
|
await ensureBroker({ agentDir });
|
|
1672
|
+
const operatorRequest = operatorAbortBrokerRequest(sessionId, operation, input, args);
|
|
1673
|
+
if (operatorRequest) return await requestBrokerOperatorAbort(agentDir, operatorRequest, args);
|
|
1416
1674
|
return await withRouter(agentDir, async router => await requestControl(router, sessionId, operation, input, args));
|
|
1417
1675
|
}
|
|
1418
1676
|
|
|
@@ -1506,6 +1764,7 @@ export async function runSdkSessionCli(
|
|
|
1506
1764
|
const action = args.action;
|
|
1507
1765
|
if (
|
|
1508
1766
|
action !== "list" &&
|
|
1767
|
+
action !== "search" &&
|
|
1509
1768
|
action !== "inspect" &&
|
|
1510
1769
|
action !== "send" &&
|
|
1511
1770
|
action !== "status" &&
|
|
@@ -1526,6 +1785,12 @@ export async function runSdkSessionCli(
|
|
|
1526
1785
|
writeOutput(stripSecretFields(await runList(agentDir, args)));
|
|
1527
1786
|
return;
|
|
1528
1787
|
}
|
|
1788
|
+
if (action === "search") {
|
|
1789
|
+
const search = await runSdkSearch(args);
|
|
1790
|
+
writeOutput(args.json === true ? search.result : renderSdkSearchTable(search.result));
|
|
1791
|
+
if (search.exitCode !== 0) setExitCode(search.exitCode);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1529
1794
|
if (action === "inspect") {
|
|
1530
1795
|
writeOutput(stripSecretFields(await runInspect(agentDir, requireValue(args.sessionId, "<sessionId>"))));
|
|
1531
1796
|
return;
|
|
@@ -1577,6 +1842,12 @@ export async function runSdkSessionCli(
|
|
|
1577
1842
|
const kind = rawKind(requireValue(action, "<verb>"), args);
|
|
1578
1843
|
if (!kind) throw new SdkSessionCliError("usage", "raw requires one of: control, query, global.", 2);
|
|
1579
1844
|
const operation = kind === "query" ? requireValue(args.query, "--query") : requireValue(args.operation, "--op");
|
|
1845
|
+
if (isRawSpawnOperation(kind, operation))
|
|
1846
|
+
throw new SdkSessionCliError(
|
|
1847
|
+
"adapter_operation_prohibited",
|
|
1848
|
+
"session.spawn is unavailable through the SDK session CLI.",
|
|
1849
|
+
1,
|
|
1850
|
+
);
|
|
1580
1851
|
const dispositionError = cliOperationError(kind, operation);
|
|
1581
1852
|
if (dispositionError) throw new SdkSessionCliError(dispositionError.code, dispositionError.message, 1);
|
|
1582
1853
|
if (isEndpointOperation(operation))
|
package/src/sdk/client/client.ts
CHANGED
|
@@ -116,13 +116,13 @@ export interface SdkDispatchContext {
|
|
|
116
116
|
readonly generation: number;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
/** Request identity retained after an uncertain send
|
|
120
|
-
|
|
119
|
+
/** Request identity retained after an uncertain send. */
|
|
121
120
|
export interface SdkSentRecord {
|
|
122
121
|
readonly id: string;
|
|
123
122
|
readonly operation?: string;
|
|
124
123
|
readonly idempotencyKey?: string;
|
|
125
|
-
|
|
124
|
+
/** Present for lifecycle lookups; `session.spawn` replays through its idempotency key. */
|
|
125
|
+
readonly fingerprint?: string;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
export type SdkFrameHandler = (frame: SdkFrame) => void;
|
|
@@ -473,8 +473,10 @@ export class SdkClient {
|
|
|
473
473
|
const sentOperation = typeof serializedFrame.operation === "string" ? serializedFrame.operation : undefined;
|
|
474
474
|
const sentIdempotencyKey =
|
|
475
475
|
typeof serializedFrame.idempotencyKey === "string" ? serializedFrame.idempotencyKey : undefined;
|
|
476
|
-
const
|
|
477
|
-
|
|
476
|
+
const isSpawn = sentOperation === "session.spawn";
|
|
477
|
+
const sentFingerprint = isSpawn
|
|
478
|
+
? undefined
|
|
479
|
+
: typeof serializedFrame.operation === "string"
|
|
478
480
|
? lifecycleFingerprint(serializedFrame.operation, serializedFrame.input ?? {})
|
|
479
481
|
: inputFingerprint(serializedFrame.input ?? {});
|
|
480
482
|
const deferred = Promise.withResolvers<unknown>();
|
|
@@ -571,11 +573,15 @@ export class SdkClient {
|
|
|
571
573
|
// nothing reached the transport, so the request stays retryable and
|
|
572
574
|
// non-uncertain with no record retained.
|
|
573
575
|
pending.sent = true;
|
|
576
|
+
// A spawn claim replays only through its caller-provided idempotency key;
|
|
577
|
+
// it deliberately has no lifecycle fingerprint. Retain the common sent
|
|
578
|
+
// record anyway so an uncertain transport result tells the caller which
|
|
579
|
+
// key can join that durable claim.
|
|
574
580
|
this.#rememberSentRecord({
|
|
575
581
|
id,
|
|
576
|
-
operation: sentOperation,
|
|
577
|
-
idempotencyKey: sentIdempotencyKey,
|
|
578
|
-
fingerprint: sentFingerprint,
|
|
582
|
+
...(sentOperation === undefined ? {} : { operation: sentOperation }),
|
|
583
|
+
...(sentIdempotencyKey === undefined ? {} : { idempotencyKey: sentIdempotencyKey }),
|
|
584
|
+
...(sentFingerprint === undefined ? {} : { fingerprint: sentFingerprint }),
|
|
579
585
|
});
|
|
580
586
|
try {
|
|
581
587
|
incarnation.socket.send(serializedRequest);
|