@gajae-code/coding-agent 0.16.1 → 0.16.4
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 +70 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/cli/rows.d.ts +7 -0
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/session-runtime.d.ts +23 -0
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/setup/paseo/announce.d.ts +111 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -1
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/index.d.ts +8 -2
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/dist/types/utils/external-editor.d.ts +5 -0
- package/package.json +7 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/root-flags.ts +5 -3
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock.ts +181 -17
- package/src/config/model-preset-registry.ts +5 -3
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +57 -11
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/discovery/builtin.ts +13 -4
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +10 -2
- package/src/extensibility/gjc-plugins/compiler.ts +208 -58
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/runtime-skill-discovery.ts +77 -151
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +44 -16
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +94 -6
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/main.ts +72 -34
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +29 -14
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +393 -21
- package/src/modes/controllers/command-controller.ts +2 -0
- package/src/modes/controllers/event-controller.ts +148 -5
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +47 -1
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/search.md +2 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +171 -14
- package/src/runtime-mcp/pool.test.ts +209 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/acp/adapter.ts +1 -1
- package/src/sdk/broker/broker.ts +5 -4
- package/src/sdk/bus/chat-daemon-control.ts +1 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +44 -1
- package/src/sdk/bus/slack-daemon.ts +92 -2
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/cli/rows.ts +47 -2
- package/src/sdk/cli/session-cli.ts +47 -23
- package/src/sdk/host/events.ts +25 -2
- package/src/sdk/host/host.ts +4 -1
- package/src/sdk/host/session-runtime.test.ts +36 -25
- package/src/sdk/host/session-runtime.ts +279 -43
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +60 -37
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +73 -30
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +123 -38
- package/src/session/contribution-prep.ts +214 -21
- package/src/setup/paseo/announce.ts +450 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +4 -0
- package/src/task/executor.ts +2 -1
- package/src/task/index.ts +17 -4
- package/src/tools/bash.ts +50 -21
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/index.ts +8 -2
- package/src/tools/job.ts +8 -2
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/search.ts +116 -58
- package/src/tools/skill.ts +22 -2
- package/src/tools/subagent.ts +3 -1
- package/src/tools/tool-catalog.generated.ts +9 -2
- package/src/utils/external-editor.ts +16 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
|
@@ -1220,6 +1220,7 @@ describe("SessionSdkSessionRuntime", () => {
|
|
|
1220
1220
|
const transport = memoryTransport();
|
|
1221
1221
|
const runtime = new SessionSdkSessionRuntime({
|
|
1222
1222
|
transport,
|
|
1223
|
+
eventRevision: () => 7,
|
|
1223
1224
|
control: async (_connectionId, frame) => ({ id: frame.id, ok: true, result: { operation: frame.operation } }),
|
|
1224
1225
|
query: async (_connectionId, frame) => ({ id: frame.id, ok: true, result: { query: frame.query } }),
|
|
1225
1226
|
});
|
|
@@ -1239,7 +1240,7 @@ describe("SessionSdkSessionRuntime", () => {
|
|
|
1239
1240
|
});
|
|
1240
1241
|
transport.feed("client", { type: "query_request", id: "query", query: "Q18", input: {} });
|
|
1241
1242
|
await Bun.sleep(0);
|
|
1242
|
-
expect(transport.broadcasts.
|
|
1243
|
+
expect(transport.broadcasts).toContainEqual(expect.objectContaining({ kind: "session_ready", revision: 7 }));
|
|
1243
1244
|
expect(transport.sent).toEqual(
|
|
1244
1245
|
expect.arrayContaining([
|
|
1245
1246
|
expect.objectContaining({ type: "event_replay_result", id: "replay", ok: true }),
|
|
@@ -2124,7 +2125,7 @@ describe("SessionSdkSessionRuntime", () => {
|
|
|
2124
2125
|
while (!transport.sent.some(frame => frame.id === "predecessor-prompt")) await Bun.sleep(10);
|
|
2125
2126
|
await handlers.get("agent_start")?.({}, ctx); // lifecycle epoch 1
|
|
2126
2127
|
await handlers.get("agent_end")?.(
|
|
2127
|
-
{ type: "agent_end", stopReason: "maintenance", maintenanceOutcome: "
|
|
2128
|
+
{ type: "agent_end", stopReason: "maintenance", maintenanceOutcome: "compacted" },
|
|
2128
2129
|
ctx,
|
|
2129
2130
|
);
|
|
2130
2131
|
transport.feed("client", {
|
|
@@ -2147,7 +2148,7 @@ describe("SessionSdkSessionRuntime", () => {
|
|
|
2147
2148
|
while (abortCalls < 1) await Bun.sleep(10);
|
|
2148
2149
|
await handlers.get("agent_end")?.({ type: "agent_end" }, ctx); // delayed predecessor
|
|
2149
2150
|
await handlers.get("agent_end")?.(
|
|
2150
|
-
{ type: "agent_end", stopReason: "maintenance", maintenanceOutcome: "
|
|
2151
|
+
{ type: "agent_end", stopReason: "maintenance", maintenanceOutcome: "compacted" },
|
|
2151
2152
|
ctx,
|
|
2152
2153
|
);
|
|
2153
2154
|
abortRelease.resolve();
|
|
@@ -2833,26 +2834,32 @@ describe("SessionSdkSessionRuntime", () => {
|
|
|
2833
2834
|
|
|
2834
2835
|
// B's accepted receipt is redacted to {} once its submission rejects,
|
|
2835
2836
|
// so select its record by the stable clientRef instead.
|
|
2836
|
-
// Let the async rejection settle and retire B's pending entry.
|
|
2837
|
-
await Bun.sleep(50);
|
|
2838
2837
|
// The rejected submission must TERMINALIZE as failed (#4668 review
|
|
2839
2838
|
// P1): agent_failed alone is diagnostic-only, so the settlement also
|
|
2840
2839
|
// writes agent_end. Before that fix B stayed accepted forever.
|
|
2841
2840
|
{
|
|
2842
2841
|
const ids = { clientRef: "fail-b-ref" };
|
|
2843
|
-
transport.feed("conn-b", {
|
|
2844
|
-
type: "query_request",
|
|
2845
|
-
id: "fail-b-status",
|
|
2846
|
-
query: "turn.prompt_status",
|
|
2847
|
-
input: { kind: "prompt", ...ids },
|
|
2848
|
-
} as SdkFrame);
|
|
2849
|
-
// waitResponse polls control_response only; queries get query_response.
|
|
2850
2842
|
const statusDeadline = Date.now() + 15_000;
|
|
2851
|
-
|
|
2843
|
+
let status: Record<string, unknown> | undefined;
|
|
2844
|
+
for (let attempt = 0; status === undefined; attempt += 1) {
|
|
2845
|
+
const id = `fail-b-status-${attempt}`;
|
|
2846
|
+
transport.feed("conn-b", {
|
|
2847
|
+
type: "query_request",
|
|
2848
|
+
id,
|
|
2849
|
+
query: "turn.prompt_status",
|
|
2850
|
+
input: { kind: "prompt", ...ids },
|
|
2851
|
+
} as SdkFrame);
|
|
2852
|
+
while (!transport.sent.some(frame => frame.id === id)) {
|
|
2853
|
+
if (Date.now() > statusDeadline) throw new Error("Timed out waiting for fail-b-status");
|
|
2854
|
+
await Bun.sleep(20);
|
|
2855
|
+
}
|
|
2856
|
+
const response = transport.sent.find(frame => frame.id === id);
|
|
2857
|
+
const result = response?.result as Record<string, unknown> | undefined;
|
|
2858
|
+
if (result?.status === "failed") status = response;
|
|
2859
|
+
else await Bun.sleep(20);
|
|
2852
2860
|
if (Date.now() > statusDeadline) throw new Error("Timed out waiting for fail-b-status");
|
|
2853
|
-
await Bun.sleep(20);
|
|
2854
2861
|
}
|
|
2855
|
-
expect(
|
|
2862
|
+
expect(status).toMatchObject({
|
|
2856
2863
|
ok: true,
|
|
2857
2864
|
result: expect.objectContaining({ status: "failed" }),
|
|
2858
2865
|
});
|
|
@@ -4765,7 +4772,7 @@ describe("post-acceptance invocation terminalization", () => {
|
|
|
4765
4772
|
const harness = await invocationHarness("successor-progress", cwd, {
|
|
4766
4773
|
settings: {
|
|
4767
4774
|
get: (key: string) =>
|
|
4768
|
-
key === "sdk.promptDeadlineMs" ?
|
|
4775
|
+
key === "sdk.promptDeadlineMs" ? 2_000 : key === "sdk.promptMaxRuntimeMs" ? 60_000 : undefined,
|
|
4769
4776
|
} as unknown as Settings,
|
|
4770
4777
|
sendUserMessage: async (_content, options) => {
|
|
4771
4778
|
prompts += 1;
|
|
@@ -4790,7 +4797,7 @@ describe("post-acceptance invocation terminalization", () => {
|
|
|
4790
4797
|
await successorStarted.promise;
|
|
4791
4798
|
for (let index = 0; index < 4; index += 1) {
|
|
4792
4799
|
await harness.emit("tool_execution_start");
|
|
4793
|
-
await Bun.sleep(
|
|
4800
|
+
await Bun.sleep(800);
|
|
4794
4801
|
}
|
|
4795
4802
|
expect(await harness.query("turn.prompt_status", successorIds)).toMatchObject({
|
|
4796
4803
|
result: { status: expect.stringMatching(/accepted|in_flight/) },
|
|
@@ -5553,9 +5560,13 @@ describe("accepted-control zero-execution bound (#4668)", () => {
|
|
|
5553
5560
|
// renewal wiring at the SDK boundary.
|
|
5554
5561
|
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-renew-attached-"));
|
|
5555
5562
|
try {
|
|
5563
|
+
const renewalSettings = {
|
|
5564
|
+
get: (key: string) =>
|
|
5565
|
+
key === "sdk.promptDeadlineMs" ? 2_000 : key === "sdk.promptMaxRuntimeMs" ? 60_000 : undefined,
|
|
5566
|
+
} as unknown as Settings;
|
|
5556
5567
|
let promoted: ((promotion: { startsOwnRun: boolean }) => void) | undefined;
|
|
5557
5568
|
const harness = await invocationHarness("renew-attached", cwd, {
|
|
5558
|
-
settings:
|
|
5569
|
+
settings: renewalSettings,
|
|
5559
5570
|
sendUserMessage: async (content, options) => {
|
|
5560
5571
|
await options?.onPreflightAcceptCommit?.();
|
|
5561
5572
|
if (content === "consumed") {
|
|
@@ -5574,12 +5585,12 @@ describe("accepted-control zero-execution bound (#4668)", () => {
|
|
|
5574
5585
|
expect(consumed.ok).toBe(true);
|
|
5575
5586
|
promoted?.({ startsOwnRun: false });
|
|
5576
5587
|
const ids = { commandId: consumed.result?.commandId, turnId: consumed.result?.turnId };
|
|
5577
|
-
// Past the
|
|
5578
|
-
// correlation alive
|
|
5579
|
-
//
|
|
5588
|
+
// Past the initial 2s lease, repeated tool activity keeps the
|
|
5589
|
+
// attached correlation alive. Each interval stays well within one
|
|
5590
|
+
// renewed lease while their cumulative duration exceeds the original.
|
|
5580
5591
|
for (let i = 0; i < 3; i += 1) {
|
|
5581
5592
|
await harness.emit("tool_execution_start");
|
|
5582
|
-
await Bun.sleep(
|
|
5593
|
+
await Bun.sleep(800);
|
|
5583
5594
|
}
|
|
5584
5595
|
const midRun = await harness.query("turn.prompt_status", ids);
|
|
5585
5596
|
expect(midRun.result?.status).not.toBe("failed");
|
|
@@ -5783,7 +5794,7 @@ describe("accepted-control zero-execution bound (#4668)", () => {
|
|
|
5783
5794
|
const harness = await invocationHarness("dispatch-race-lease", cwd, {
|
|
5784
5795
|
settings: {
|
|
5785
5796
|
get: (key: string) =>
|
|
5786
|
-
key === "sdk.promptDeadlineMs" ?
|
|
5797
|
+
key === "sdk.promptDeadlineMs" ? 2_000 : key === "sdk.promptMaxRuntimeMs" ? 60_000 : undefined,
|
|
5787
5798
|
} as unknown as Settings,
|
|
5788
5799
|
sendUserMessage: async (content, options) => {
|
|
5789
5800
|
await options?.onPreflightAcceptCommit?.();
|
|
@@ -5808,9 +5819,9 @@ describe("accepted-control zero-execution bound (#4668)", () => {
|
|
|
5808
5819
|
const raced = await harness.control("turn.prompt", { text: "raced" });
|
|
5809
5820
|
expect(raced.ok).toBe(true);
|
|
5810
5821
|
const idsRaced = { commandId: raced.result?.commandId, turnId: raced.result?.turnId };
|
|
5811
|
-
// Far past the
|
|
5822
|
+
// Far past the 2s lease while queued: the acceptance-anchored lease
|
|
5812
5823
|
// was dropped at the divert disposition, so no false deadline fire.
|
|
5813
|
-
await Bun.sleep(
|
|
5824
|
+
await Bun.sleep(2_200);
|
|
5814
5825
|
const queued = await harness.query("turn.prompt_status", idsRaced);
|
|
5815
5826
|
expect(queued.result?.status).not.toBe("failed");
|
|
5816
5827
|
// Real consumption re-leases and attaches to the in-flight run.
|
|
@@ -4,7 +4,7 @@ import * as crypto from "node:crypto";
|
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
-
import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
7
|
+
import { isContinuingMidRunMaintenanceOutcome, ThinkingLevel } from "@gajae-code/agent-core";
|
|
8
8
|
import type { Api, ImageContent, Model } from "@gajae-code/ai/core";
|
|
9
9
|
import { logger } from "@gajae-code/utils";
|
|
10
10
|
import { AsyncJobManager } from "../../async";
|
|
@@ -27,7 +27,10 @@ import { resolveModelChainWithAuth, splitSelectorThinkingSuffix } from "../../co
|
|
|
27
27
|
import { type ModelSelectorValue, normalizeModelSelectorValue } from "../../config/model-selector-value";
|
|
28
28
|
import { type Settings, validateSettingPatch } from "../../config/settings";
|
|
29
29
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../../extensibility/extensions";
|
|
30
|
+
import type { AgentEndEvent } from "../../extensibility/shared-events";
|
|
30
31
|
import { normalizeGoal } from "../../goals/state";
|
|
32
|
+
import { toAgentWireEventPayload } from "../../modes/shared/agent-wire/event-envelope";
|
|
33
|
+
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
31
34
|
import type { SdkRunCapability } from "../../session/sdk-run-capability";
|
|
32
35
|
import {
|
|
33
36
|
boundTerminalRetentionState,
|
|
@@ -192,6 +195,24 @@ class DiffQueryError extends Error {
|
|
|
192
195
|
}
|
|
193
196
|
}
|
|
194
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Normalize an agent-loop stop reason into the published terminal outcome.
|
|
200
|
+
*
|
|
201
|
+
* Only reached for a boundary that actually terminalizes an owned invocation,
|
|
202
|
+
* so every result is a `stopped` disposition: a failed run publishes its own
|
|
203
|
+
* correlated `agent_failed` frame with the sanitized cause instead.
|
|
204
|
+
*/
|
|
205
|
+
export function terminalStoppedOutcome(
|
|
206
|
+
stopReason: AgentEndEvent["stopReason"],
|
|
207
|
+
maintenanceOutcome: string | undefined,
|
|
208
|
+
): Extract<SdkPromptTerminalOutcome, { kind: "stopped" }> {
|
|
209
|
+
// An aborted maintenance checkpoint is the one maintenance path that really
|
|
210
|
+
// ends the run, and it ends it the same way an explicit stop does.
|
|
211
|
+
return stopReason === "cancelled" || maintenanceOutcome === "aborted"
|
|
212
|
+
? { kind: "stopped", reason: "cancelled", provenance: "client_cancel" }
|
|
213
|
+
: { kind: "stopped", reason: "end_turn", provenance: "agent" };
|
|
214
|
+
}
|
|
215
|
+
|
|
195
216
|
/** Transport-neutral endpoint contract consumed by the SDK session runtime. */
|
|
196
217
|
export interface SessionSdkTransport {
|
|
197
218
|
readonly sessionId: string;
|
|
@@ -395,6 +416,28 @@ export class SessionSdkSessionRuntime {
|
|
|
395
416
|
this.transport.broadcastFrame?.(event);
|
|
396
417
|
}
|
|
397
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Deliver a frame to specific connections without recording it.
|
|
421
|
+
*
|
|
422
|
+
* Deliberately not `emitEvent`: that assigns a ring position and broadcasts to
|
|
423
|
+
* every authenticated socket. Streamed turn content is high-frequency and
|
|
424
|
+
* unbounded in size, so a count-bounded ring would evict the lifecycle events
|
|
425
|
+
* clients depend on -- including the `agent_end` that settles their prompt --
|
|
426
|
+
* and broadcasting would hand every subscriber a stream only the submitter
|
|
427
|
+
* asked for. Delivery failure is ignored: a disconnected consumer must never
|
|
428
|
+
* disturb the turn producing the content.
|
|
429
|
+
*/
|
|
430
|
+
sendFrameTo(connectionIds: Iterable<string>, frame: SdkFrame): void {
|
|
431
|
+
for (const connectionId of connectionIds) {
|
|
432
|
+
try {
|
|
433
|
+
const result = this.transport.sendFrame(connectionId, frame);
|
|
434
|
+
if (result instanceof Promise) result.catch(() => undefined);
|
|
435
|
+
} catch {
|
|
436
|
+
// A dead connection is reaped by the transport's own close handling.
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
398
441
|
publish(frame: SdkFrame): void {
|
|
399
442
|
this.emitEvent(frame);
|
|
400
443
|
}
|
|
@@ -577,6 +620,8 @@ function canonicalTerminalOutcome(
|
|
|
577
620
|
if (candidate.kind === "stopped") {
|
|
578
621
|
if (candidate.reason === "cancelled" && candidate.provenance === "client_cancel")
|
|
579
622
|
return { kind: "stopped", reason: "cancelled", provenance: "client_cancel" };
|
|
623
|
+
if (candidate.reason === "end_turn" && candidate.provenance === "agent")
|
|
624
|
+
return { kind: "stopped", reason: "end_turn", provenance: "agent" };
|
|
580
625
|
return undefined;
|
|
581
626
|
}
|
|
582
627
|
if (candidate.kind === "failed")
|
|
@@ -2239,6 +2284,7 @@ function createControlSurface(
|
|
|
2239
2284
|
canResolveGate: () => boolean = () => true,
|
|
2240
2285
|
trackGateResolution: <T>(resolution: Promise<T>) => Promise<T> = async resolution => await resolution,
|
|
2241
2286
|
onInvocationCompletionReconciledForTests?: (kind: InvocationKind, correlation: InvocationCorrelation) => void,
|
|
2287
|
+
publishLifecycleFrame?: (frame: SdkFrame) => void,
|
|
2242
2288
|
): ControlSurface {
|
|
2243
2289
|
const normalizePromptImages = (value: unknown): ImageContent[] => {
|
|
2244
2290
|
if (!Array.isArray(value)) return [];
|
|
@@ -2376,6 +2422,24 @@ function createControlSurface(
|
|
|
2376
2422
|
const correlation = newCorrelation();
|
|
2377
2423
|
const sdkRunToken = `${correlation.commandId}:${correlation.turnId}`;
|
|
2378
2424
|
const sdkRunCapability = createSdkRunCapability(sdkRunToken);
|
|
2425
|
+
const publishTerminal = (outcome: InvocationOutcome): void => {
|
|
2426
|
+
publishLifecycleFrame?.({
|
|
2427
|
+
type: "agent_end",
|
|
2428
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
2429
|
+
...correlation,
|
|
2430
|
+
outcome,
|
|
2431
|
+
});
|
|
2432
|
+
};
|
|
2433
|
+
const publishFailure = (error: unknown): void => {
|
|
2434
|
+
const failure = sanitizePromptFailure(error);
|
|
2435
|
+
publishLifecycleFrame?.({
|
|
2436
|
+
type: "agent_failed",
|
|
2437
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
2438
|
+
...correlation,
|
|
2439
|
+
error: failure,
|
|
2440
|
+
});
|
|
2441
|
+
publishTerminal(canonicalFailedOutcome(failure));
|
|
2442
|
+
};
|
|
2379
2443
|
const preflight = Promise.withResolvers<void>();
|
|
2380
2444
|
let accepted = false;
|
|
2381
2445
|
let settled = false;
|
|
@@ -2484,11 +2548,25 @@ function createControlSurface(
|
|
|
2484
2548
|
retirePendingOwner?.(kind, correlation);
|
|
2485
2549
|
const attemptTerminalization = async (remaining: number): Promise<void> => {
|
|
2486
2550
|
try {
|
|
2551
|
+
const before = reconciliation.lookup(kind, correlation) as {
|
|
2552
|
+
status?: string;
|
|
2553
|
+
};
|
|
2554
|
+
const alreadyTerminal = before.status === "terminal_ok" || before.status === "failed";
|
|
2487
2555
|
await reconciliation.noteTransition(kind, correlation, {
|
|
2488
2556
|
type: "agent_end",
|
|
2489
2557
|
...(typeof result === "string" ? { content: sanitizeTurnResultContent(result) } : {}),
|
|
2490
2558
|
});
|
|
2491
2559
|
onInvocationCompletionReconciledForTests?.(kind, correlation);
|
|
2560
|
+
if (!alreadyTerminal) {
|
|
2561
|
+
const settledRecord = reconciliation.lookup(kind, correlation) as {
|
|
2562
|
+
status?: string;
|
|
2563
|
+
outcome?: unknown;
|
|
2564
|
+
error?: { code?: unknown; message?: unknown };
|
|
2565
|
+
};
|
|
2566
|
+
const outcome = canonicalTerminalOutcome(settledRecord.outcome, settledRecord.error);
|
|
2567
|
+
if (outcome?.kind === "failed") publishFailure(settledRecord.error ?? outcome);
|
|
2568
|
+
else if (outcome) publishTerminal(outcome);
|
|
2569
|
+
}
|
|
2492
2570
|
retirePendingOwner?.(kind, correlation, "always");
|
|
2493
2571
|
} catch (transitionError) {
|
|
2494
2572
|
if (kind === "prompt" || kind === "skill") {
|
|
@@ -2585,6 +2663,7 @@ function createControlSurface(
|
|
|
2585
2663
|
}
|
|
2586
2664
|
try {
|
|
2587
2665
|
await reconciliation.noteTransition(kind, correlation, { type: "agent_end" });
|
|
2666
|
+
publishFailure(error);
|
|
2588
2667
|
} catch (transitionError) {
|
|
2589
2668
|
if (kind === "prompt" || kind === "skill") {
|
|
2590
2669
|
retirePendingOwner?.(
|
|
@@ -3940,7 +4019,11 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
3940
4019
|
waitForGateResolutionQuiescence: () => Promise<void>;
|
|
3941
4020
|
activeInvocation?: { kind: InvocationKind; correlation: InvocationCorrelation };
|
|
3942
4021
|
drainedInvocations?: Array<{ kind: InvocationKind; correlation: InvocationCorrelation }>;
|
|
3943
|
-
attachedInvocations?: Array<{
|
|
4022
|
+
attachedInvocations?: Array<{
|
|
4023
|
+
kind: InvocationKind;
|
|
4024
|
+
correlation: InvocationCorrelation;
|
|
4025
|
+
connectionId: string | undefined;
|
|
4026
|
+
}>;
|
|
3944
4027
|
openLifecycleBatches: Array<{
|
|
3945
4028
|
epoch: number;
|
|
3946
4029
|
invocations: Array<{
|
|
@@ -3949,7 +4032,11 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
3949
4032
|
connectionId: string | undefined;
|
|
3950
4033
|
sdkRunToken: string;
|
|
3951
4034
|
}>;
|
|
3952
|
-
attachedInvocations: Array<{
|
|
4035
|
+
attachedInvocations: Array<{
|
|
4036
|
+
kind: InvocationKind;
|
|
4037
|
+
correlation: InvocationCorrelation;
|
|
4038
|
+
connectionId: string | undefined;
|
|
4039
|
+
}>;
|
|
3953
4040
|
}>;
|
|
3954
4041
|
disposeGate?: () => void;
|
|
3955
4042
|
lifecycleActive: boolean;
|
|
@@ -4142,6 +4229,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4142
4229
|
terminalContent?: TurnResultContent,
|
|
4143
4230
|
terminalHasActivity = false,
|
|
4144
4231
|
terminalOutcome?: InvocationOutcome,
|
|
4232
|
+
stopReason?: AgentEndEvent["stopReason"],
|
|
4233
|
+
startToken?: string,
|
|
4145
4234
|
): Promise<void> => {
|
|
4146
4235
|
const current = lifecycleOwner?.state ?? active;
|
|
4147
4236
|
if (!current) return;
|
|
@@ -4216,19 +4305,44 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4216
4305
|
// turn (queued-while-streaming submissions never push), so a mid-prompt
|
|
4217
4306
|
// continuation agent_start with an empty queue leaves the current owner
|
|
4218
4307
|
// untouched (review thread P1).
|
|
4219
|
-
const
|
|
4220
|
-
.
|
|
4308
|
+
const matchingPending =
|
|
4309
|
+
typeof startToken === "string" && startToken.length > 0
|
|
4310
|
+
? current.pending.filter(entry => entry.sdkRunToken === startToken)
|
|
4311
|
+
: current.pending.slice();
|
|
4312
|
+
const matchingKeys = new Set(matchingPending.map(entry => entry.sdkRunToken));
|
|
4313
|
+
const pendingSnapshot = current.pending.splice(0);
|
|
4314
|
+
const drained = pendingSnapshot
|
|
4315
|
+
.filter(entry => matchingKeys.has(entry.sdkRunToken))
|
|
4221
4316
|
.filter(entry => entry.kind !== "prompt" || !current.deadlineManager.isExpiring(entry.correlation));
|
|
4317
|
+
current.pending.push(...pendingSnapshot.filter(entry => !matchingKeys.has(entry.sdkRunToken)));
|
|
4318
|
+
const existingTokenBatch =
|
|
4319
|
+
typeof startToken === "string" && startToken.length > 0
|
|
4320
|
+
? lifecycleRunOwners.get(startToken)?.batch
|
|
4321
|
+
: undefined;
|
|
4222
4322
|
if (drained.length > 0) {
|
|
4223
|
-
|
|
4323
|
+
const batch: LifecycleBatch = {
|
|
4224
4324
|
epoch: current.lifecycleEpoch,
|
|
4225
4325
|
invocations: drained,
|
|
4226
4326
|
attachedInvocations: [],
|
|
4227
|
-
}
|
|
4327
|
+
};
|
|
4328
|
+
current.openLifecycleBatches.push(batch);
|
|
4329
|
+
if (typeof startToken === "string" && startToken.length > 0) {
|
|
4330
|
+
const entry = drained.find(item => item.sdkRunToken === startToken);
|
|
4331
|
+
lifecycleRunOwners.set(startToken, {
|
|
4332
|
+
state: current,
|
|
4333
|
+
batch,
|
|
4334
|
+
...(entry ? { correlationKey: lifecycleCorrelationKey(entry.correlation) } : {}),
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
4228
4337
|
adoptLifecycleBatch(drained);
|
|
4229
4338
|
if (current.activeInvocation?.kind === "prompt") {
|
|
4230
4339
|
current.deadlineManager.onAccepted(current.activeInvocation.correlation);
|
|
4231
4340
|
}
|
|
4341
|
+
} else if (existingTokenBatch && current.openLifecycleBatches.includes(existingTokenBatch)) {
|
|
4342
|
+
// A continuation within the same SDK-owned run carries the same token
|
|
4343
|
+
// but has no newly pending invocation. Preserve the established owner so
|
|
4344
|
+
// later chunks and the final terminal remain directed to its submitters.
|
|
4345
|
+
adoptLifecycleBatch(existingTokenBatch.invocations);
|
|
4232
4346
|
} else {
|
|
4233
4347
|
// An empty drain is an agent-initiated successor run. Clear the
|
|
4234
4348
|
// predecessor's SDK owner and active invocation so abort ownership and
|
|
@@ -4240,7 +4354,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4240
4354
|
// Pair this agent_end with the oldest unmatched start. A delayed
|
|
4241
4355
|
// aborted-turn end that lands after a successor agent_start must
|
|
4242
4356
|
// terminalize the aborted invocation, never the successor.
|
|
4243
|
-
const ended = current.openLifecycleBatches[0];
|
|
4357
|
+
const ended = failureBatch ?? current.openLifecycleBatches[0];
|
|
4244
4358
|
const baseTransitions = ended
|
|
4245
4359
|
? ended.invocations.map(({ kind, correlation }) => ({ kind, correlation }))
|
|
4246
4360
|
: current.activeInvocation
|
|
@@ -4364,7 +4478,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4364
4478
|
};
|
|
4365
4479
|
void attempt().finally(() => skillTerminalRecoveryControllers.delete(recoveryKey));
|
|
4366
4480
|
};
|
|
4367
|
-
if (type === "agent_end" && maintenanceOutcome
|
|
4481
|
+
if (type === "agent_end" && isContinuingMidRunMaintenanceOutcome(maintenanceOutcome)) {
|
|
4368
4482
|
try {
|
|
4369
4483
|
current.runtime.emitEvent({ type, sessionId: ctx.sessionManager.getSessionId() });
|
|
4370
4484
|
} catch {
|
|
@@ -4485,7 +4599,31 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4485
4599
|
observed = false;
|
|
4486
4600
|
}
|
|
4487
4601
|
}
|
|
4602
|
+
} else if (type === "agent_end" && transitions.length > 0) {
|
|
4603
|
+
// An invocation-owned terminal must carry the identity it was accepted
|
|
4604
|
+
// under and the normalized outcome. A client that submitted this turn
|
|
4605
|
+
// matches the terminal against its own acknowledged correlation and
|
|
4606
|
+
// refuses an unowned one, so the bare `{ type, sessionId }` boundary left
|
|
4607
|
+
// every externally submitted prompt reporting `working` forever even
|
|
4608
|
+
// though the turn had finished -- ACP dropped it as
|
|
4609
|
+
// `incomplete_correlation`, and the notifications runtime has always
|
|
4610
|
+
// stamped both fields for exactly this reason. One frame per invocation,
|
|
4611
|
+
// matching the agent_failed shape above, also lets a shared run attribute
|
|
4612
|
+
// the boundary to each owner instead of one signal nobody can claim. A
|
|
4613
|
+
// continuing maintenance checkpoint never reaches this branch: it returned
|
|
4614
|
+
// above with the one uncorrelated frame, so it cannot settle a live prompt.
|
|
4615
|
+
const outcome = terminalOutcome ?? terminalStoppedOutcome(stopReason, maintenanceOutcome);
|
|
4616
|
+
for (const invocation of transitions) {
|
|
4617
|
+
try {
|
|
4618
|
+
current.runtime.emitEvent({ type, sessionId, ...invocation.correlation, outcome });
|
|
4619
|
+
} catch {
|
|
4620
|
+
observed = false;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4488
4623
|
} else {
|
|
4624
|
+
// Everything that does not terminalize an owned invocation keeps the one
|
|
4625
|
+
// uncorrelated frame: `agent_start` and a host-originated run (autonomous
|
|
4626
|
+
// continuation, cron, monitor) that owns no invocation.
|
|
4489
4627
|
try {
|
|
4490
4628
|
current.runtime.emitEvent({ type, sessionId });
|
|
4491
4629
|
} catch {
|
|
@@ -4495,7 +4633,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4495
4633
|
} catch {
|
|
4496
4634
|
observed = false;
|
|
4497
4635
|
}
|
|
4498
|
-
if (type === "agent_end" && maintenanceOutcome
|
|
4636
|
+
if (type === "agent_end" && isContinuingMidRunMaintenanceOutcome(maintenanceOutcome)) return;
|
|
4499
4637
|
if (type === "agent_end") {
|
|
4500
4638
|
if (current.lifecycleEpoch !== eventLifecycleEpoch) {
|
|
4501
4639
|
// A successor agent_start won the lifecycle race while this event's
|
|
@@ -4553,29 +4691,22 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4553
4691
|
};
|
|
4554
4692
|
api.on("agent_start", (event, ctx) => {
|
|
4555
4693
|
const owner = lifecycleStateForEvent(ctx, "agent_start", event.sdkRunToken);
|
|
4556
|
-
return trackLifecycle(
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
? { correlationKey: previous.correlationKey }
|
|
4573
|
-
: entry
|
|
4574
|
-
? { correlationKey: lifecycleCorrelationKey(entry.correlation) }
|
|
4575
|
-
: {}),
|
|
4576
|
-
});
|
|
4577
|
-
}
|
|
4578
|
-
}, owner);
|
|
4694
|
+
return trackLifecycle(
|
|
4695
|
+
async () =>
|
|
4696
|
+
emitLifecycle(
|
|
4697
|
+
"agent_start",
|
|
4698
|
+
ctx,
|
|
4699
|
+
undefined,
|
|
4700
|
+
undefined,
|
|
4701
|
+
owner ? { state: owner, sessionId: owner.sessionId } : undefined,
|
|
4702
|
+
undefined,
|
|
4703
|
+
false,
|
|
4704
|
+
undefined,
|
|
4705
|
+
undefined,
|
|
4706
|
+
typeof event.sdkRunToken === "string" ? event.sdkRunToken : undefined,
|
|
4707
|
+
),
|
|
4708
|
+
owner,
|
|
4709
|
+
);
|
|
4579
4710
|
});
|
|
4580
4711
|
api.on("agent_end", (event, ctx) => {
|
|
4581
4712
|
const tokenBinding =
|
|
@@ -4608,11 +4739,20 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4608
4739
|
const failureAlreadyPublished = failure === undefined || (hasExistingFailure && genericFailureKeys.length === 0);
|
|
4609
4740
|
const terminalEvidence = promptTerminalEvidenceFromAgentEnd(event);
|
|
4610
4741
|
const terminalOutcome =
|
|
4611
|
-
event.stopReason === "cancelled"
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4742
|
+
event.stopReason === "cancelled" ||
|
|
4743
|
+
(event.stopReason === "maintenance" && event.maintenanceOutcome === "aborted")
|
|
4744
|
+
? terminalStoppedOutcome(
|
|
4745
|
+
event.stopReason,
|
|
4746
|
+
event.stopReason === "maintenance" ? event.maintenanceOutcome : undefined,
|
|
4747
|
+
)
|
|
4748
|
+
: failure !== undefined
|
|
4749
|
+
? canonicalFailedOutcome(failure)
|
|
4750
|
+
: terminalEvidence.content?.text.trim() || terminalEvidence.hasActivity
|
|
4751
|
+
? terminalStoppedOutcome(
|
|
4752
|
+
event.stopReason,
|
|
4753
|
+
event.stopReason === "maintenance" ? event.maintenanceOutcome : undefined,
|
|
4754
|
+
)
|
|
4755
|
+
: canonicalFailedOutcome(EMPTY_PROMPT_FAILURE);
|
|
4616
4756
|
return trackLifecycle(async () => {
|
|
4617
4757
|
if (failure && !failureAlreadyPublished) {
|
|
4618
4758
|
for (const key of genericFailureKeys) owner?.failureDiagnosticKeys.delete(key);
|
|
@@ -4627,6 +4767,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4627
4767
|
terminalEvidence.content,
|
|
4628
4768
|
terminalEvidence.hasActivity,
|
|
4629
4769
|
terminalOutcome,
|
|
4770
|
+
event.stopReason,
|
|
4630
4771
|
);
|
|
4631
4772
|
}, owner).finally(() => {
|
|
4632
4773
|
if (typeof event.sdkRunToken === "string" && lifecycleRunOwners.get(event.sdkRunToken)?.state === owner)
|
|
@@ -4716,11 +4857,84 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4716
4857
|
current.deadlineManager.onAttributableEvent(invocation.correlation, eventType);
|
|
4717
4858
|
}
|
|
4718
4859
|
};
|
|
4719
|
-
|
|
4860
|
+
/**
|
|
4861
|
+
* Stream one turn event to the connections that submitted the running turn.
|
|
4862
|
+
*
|
|
4863
|
+
* This is the same frame the notifications runtime sends for an ACP-launched
|
|
4864
|
+
* session (`{ type: "event", kind, payload: { event_type, event }, ...correlation }`),
|
|
4865
|
+
* built from the same shared producer, so an ACP client attached to a live
|
|
4866
|
+
* interactive session renders assistant text and tool calls exactly as it does
|
|
4867
|
+
* for a session it started itself. Without it an attached client could drive
|
|
4868
|
+
* the session and see the turn settle but never receive a single word of the
|
|
4869
|
+
* answer.
|
|
4870
|
+
*
|
|
4871
|
+
* Scoped to the owning invocations of the batch that is actually running: a
|
|
4872
|
+
* turn nobody submitted over the SDK (an ordinary terminal prompt, an
|
|
4873
|
+
* autonomous continuation, cron, monitor) has no owner connection and streams
|
|
4874
|
+
* nothing at all, so a session with no attached client pays one map lookup.
|
|
4875
|
+
*/
|
|
4876
|
+
const STREAMED_TURN_EVENT_TYPES: ReadonlySet<string> = new Set([
|
|
4877
|
+
"message_update",
|
|
4878
|
+
"message_end",
|
|
4879
|
+
"tool_execution_start",
|
|
4880
|
+
"tool_execution_update",
|
|
4881
|
+
"tool_execution_end",
|
|
4882
|
+
]);
|
|
4883
|
+
const streamTurnEvent = (event: { type?: unknown } | null | undefined, ctx: ExtensionContext): void => {
|
|
4884
|
+
const current = lifecycleStateForContext(ctx, "agent_start");
|
|
4885
|
+
const activeInvocation = current?.activeInvocation;
|
|
4886
|
+
if (!current) return;
|
|
4887
|
+
const batch = activeInvocation
|
|
4888
|
+
? current.openLifecycleBatches.find(candidate =>
|
|
4889
|
+
candidate.invocations.some(
|
|
4890
|
+
entry =>
|
|
4891
|
+
entry.correlation.commandId === activeInvocation.correlation.commandId &&
|
|
4892
|
+
entry.correlation.turnId === activeInvocation.correlation.turnId,
|
|
4893
|
+
),
|
|
4894
|
+
)
|
|
4895
|
+
: undefined;
|
|
4896
|
+
const invocations = batch
|
|
4897
|
+
? [...batch.invocations, ...batch.attachedInvocations]
|
|
4898
|
+
: current.lifecycleActive
|
|
4899
|
+
? (current.attachedInvocations ?? [])
|
|
4900
|
+
: [];
|
|
4901
|
+
if (invocations.length === 0) return;
|
|
4902
|
+
// The handlers below also renew prompt deadlines, so streaming must never
|
|
4903
|
+
// throw out of them: a frame the wire producer does not recognize is dropped.
|
|
4904
|
+
if (!event || typeof event.type !== "string" || !STREAMED_TURN_EVENT_TYPES.has(event.type)) return;
|
|
4905
|
+
try {
|
|
4906
|
+
const payload = toAgentWireEventPayload(event as AgentSessionEvent);
|
|
4907
|
+
// One frame per owning invocation, each carrying its own correlation, so a
|
|
4908
|
+
// shared run lets every submitter attribute the content to its own prompt.
|
|
4909
|
+
for (const invocation of invocations) {
|
|
4910
|
+
if (invocation.connectionId === undefined) continue;
|
|
4911
|
+
current.runtime.sendFrameTo([invocation.connectionId], {
|
|
4912
|
+
type: "event",
|
|
4913
|
+
kind: event.type,
|
|
4914
|
+
payload,
|
|
4915
|
+
...invocation.correlation,
|
|
4916
|
+
});
|
|
4917
|
+
}
|
|
4918
|
+
} catch {
|
|
4919
|
+
// Streamed content is best-effort; the turn producing it is authoritative.
|
|
4920
|
+
}
|
|
4921
|
+
};
|
|
4922
|
+
api.on("tool_execution_start", async (event, ctx) => {
|
|
4720
4923
|
renewAttributableProgress("tool_execution_start", ctx);
|
|
4924
|
+
streamTurnEvent(event, ctx);
|
|
4925
|
+
});
|
|
4926
|
+
api.on("tool_execution_update", async (event, ctx) => {
|
|
4927
|
+
streamTurnEvent(event, ctx);
|
|
4721
4928
|
});
|
|
4722
|
-
api.on("tool_execution_end", async (
|
|
4929
|
+
api.on("tool_execution_end", async (event, ctx) => {
|
|
4723
4930
|
renewAttributableProgress("tool_execution_end", ctx);
|
|
4931
|
+
streamTurnEvent(event, ctx);
|
|
4932
|
+
});
|
|
4933
|
+
api.on("message_update", async (event, ctx) => {
|
|
4934
|
+
streamTurnEvent(event, ctx);
|
|
4935
|
+
});
|
|
4936
|
+
api.on("message_end", async (event, ctx) => {
|
|
4937
|
+
streamTurnEvent(event, ctx);
|
|
4724
4938
|
});
|
|
4725
4939
|
const errorCode = (error: unknown): string | undefined =>
|
|
4726
4940
|
typeof error === "object" &&
|
|
@@ -4784,7 +4998,11 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4784
4998
|
connectionId: string | undefined;
|
|
4785
4999
|
sdkRunToken: string;
|
|
4786
5000
|
}>;
|
|
4787
|
-
attachedInvocations: Array<{
|
|
5001
|
+
attachedInvocations: Array<{
|
|
5002
|
+
kind: InvocationKind;
|
|
5003
|
+
correlation: InvocationCorrelation;
|
|
5004
|
+
connectionId: string | undefined;
|
|
5005
|
+
}>;
|
|
4788
5006
|
}> = [];
|
|
4789
5007
|
const configRevision = { current: 0 };
|
|
4790
5008
|
let acceptingGateResolutions = true;
|
|
@@ -4913,6 +5131,19 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4913
5131
|
// 2. Terminal boundary. The durable error from step 1 makes this
|
|
4914
5132
|
// classify as failed, never terminal_ok.
|
|
4915
5133
|
await reconciliation.noteTransition(kind, correlation, { type: "agent_end" });
|
|
5134
|
+
const failure = sanitizePromptFailure(Object.assign(new Error(error.message), { code: error.code }));
|
|
5135
|
+
runtime.emitEvent({
|
|
5136
|
+
type: "agent_failed",
|
|
5137
|
+
sessionId,
|
|
5138
|
+
...correlation,
|
|
5139
|
+
error: failure,
|
|
5140
|
+
});
|
|
5141
|
+
runtime.emitEvent({
|
|
5142
|
+
type: "agent_end",
|
|
5143
|
+
sessionId,
|
|
5144
|
+
...correlation,
|
|
5145
|
+
outcome: canonicalFailedOutcome(failure),
|
|
5146
|
+
});
|
|
4916
5147
|
// 3. Release recovery ownership ONLY after durable terminalization.
|
|
4917
5148
|
deadlineManager.clear(correlation);
|
|
4918
5149
|
} catch (transitionError) {
|
|
@@ -5065,7 +5296,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5065
5296
|
entry.correlation.turnId === correlation.turnId,
|
|
5066
5297
|
)
|
|
5067
5298
|
)
|
|
5068
|
-
current.attachedInvocations.push({ kind, correlation });
|
|
5299
|
+
current.attachedInvocations.push({ kind, correlation, connectionId });
|
|
5069
5300
|
if (
|
|
5070
5301
|
activeBatch &&
|
|
5071
5302
|
!activeBatch.attachedInvocations.some(
|
|
@@ -5074,7 +5305,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5074
5305
|
entry.correlation.turnId === correlation.turnId,
|
|
5075
5306
|
)
|
|
5076
5307
|
)
|
|
5077
|
-
activeBatch.attachedInvocations.push({ kind, correlation });
|
|
5308
|
+
activeBatch.attachedInvocations.push({ kind, correlation, connectionId });
|
|
5078
5309
|
bindPromotedToken(activeBatch);
|
|
5079
5310
|
return;
|
|
5080
5311
|
}
|
|
@@ -5157,6 +5388,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5157
5388
|
() => acceptingGateResolutions,
|
|
5158
5389
|
trackGateResolution,
|
|
5159
5390
|
options.onInvocationCompletionReconciledForTests,
|
|
5391
|
+
frame => runtime.emitEvent(frame),
|
|
5160
5392
|
);
|
|
5161
5393
|
const installProviderDefinitions = (capability: string, definitions: unknown): void => {
|
|
5162
5394
|
if (capability === "permission") {
|
|
@@ -5224,6 +5456,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5224
5456
|
};
|
|
5225
5457
|
runtime = new SessionSdkSessionRuntime({
|
|
5226
5458
|
transport,
|
|
5459
|
+
eventRevision: () =>
|
|
5460
|
+
typeof (ctx as Partial<ExtensionContext>).getTranscript === "function"
|
|
5461
|
+
? ctx.getTranscript().length
|
|
5462
|
+
: undefined,
|
|
5227
5463
|
masterCapabilityVerify: frame =>
|
|
5228
5464
|
verifyMasterCapabilityFrame({
|
|
5229
5465
|
frame,
|