@gajae-code/coding-agent 0.16.3 → 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 +59 -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/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 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -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 +92 -2
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/main.ts +72 -34
- package/src/modes/DESIGN.md +54 -0
- 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/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/broker/broker.ts +5 -4
- package/src/sdk/bus/chat-daemon-control.ts +1 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +37 -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/session-cli.ts +9 -6
- package/src/sdk/host/session-runtime.test.ts +2 -2
- package/src/sdk/host/session-runtime.ts +275 -43
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +73 -29
- 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 +35 -8
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/index.ts +8 -0
- 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 +8 -1
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
|
@@ -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") {
|
|
@@ -2841,6 +2841,50 @@
|
|
|
2841
2841
|
"testIds": "not_applicable"
|
|
2842
2842
|
}
|
|
2843
2843
|
},
|
|
2844
|
+
{
|
|
2845
|
+
"sourceId": "agent_session:awaitDisposeCompletion",
|
|
2846
|
+
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
2847
|
+
"sourceKind": "agent_session",
|
|
2848
|
+
"decision": "exclude",
|
|
2849
|
+
"rationale": "test-only disposal completion seam, not a user-facing SDK control seam",
|
|
2850
|
+
"exclusionMetadata": {
|
|
2851
|
+
"adapterMappings": "not_applicable",
|
|
2852
|
+
"testIds": "not_applicable"
|
|
2853
|
+
}
|
|
2854
|
+
},
|
|
2855
|
+
{
|
|
2856
|
+
"sourceId": "agent_session:setDisposeTimeoutForTests",
|
|
2857
|
+
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
2858
|
+
"sourceKind": "agent_session",
|
|
2859
|
+
"decision": "exclude",
|
|
2860
|
+
"rationale": "test-only disposal timeout seam, not a user-facing SDK control seam",
|
|
2861
|
+
"exclusionMetadata": {
|
|
2862
|
+
"adapterMappings": "not_applicable",
|
|
2863
|
+
"testIds": "not_applicable"
|
|
2864
|
+
}
|
|
2865
|
+
},
|
|
2866
|
+
{
|
|
2867
|
+
"sourceId": "agent_session:trackPostPromptTaskForTests",
|
|
2868
|
+
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
2869
|
+
"sourceKind": "agent_session",
|
|
2870
|
+
"decision": "exclude",
|
|
2871
|
+
"rationale": "test-only post-prompt task seam, not a user-facing SDK control seam",
|
|
2872
|
+
"exclusionMetadata": {
|
|
2873
|
+
"adapterMappings": "not_applicable",
|
|
2874
|
+
"testIds": "not_applicable"
|
|
2875
|
+
}
|
|
2876
|
+
},
|
|
2877
|
+
{
|
|
2878
|
+
"sourceId": "agent_session:requestWorkerIntegrationForTests",
|
|
2879
|
+
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
2880
|
+
"sourceKind": "agent_session",
|
|
2881
|
+
"decision": "exclude",
|
|
2882
|
+
"rationale": "test-only worker integration seam, not a user-facing SDK control seam",
|
|
2883
|
+
"exclusionMetadata": {
|
|
2884
|
+
"adapterMappings": "not_applicable",
|
|
2885
|
+
"testIds": "not_applicable"
|
|
2886
|
+
}
|
|
2887
|
+
},
|
|
2844
2888
|
{
|
|
2845
2889
|
"sourceId": "agent_session:closeWriterStrict",
|
|
2846
2890
|
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
@@ -2885,6 +2929,17 @@
|
|
|
2885
2929
|
"testIds": "not_applicable"
|
|
2886
2930
|
}
|
|
2887
2931
|
},
|
|
2932
|
+
{
|
|
2933
|
+
"sourceId": "agent_session:runWithPromptAdmissionForTests",
|
|
2934
|
+
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
2935
|
+
"sourceKind": "agent_session",
|
|
2936
|
+
"decision": "exclude",
|
|
2937
|
+
"rationale": "test-only prompt admission seam, not a user-facing SDK control seam",
|
|
2938
|
+
"exclusionMetadata": {
|
|
2939
|
+
"adapterMappings": "not_applicable",
|
|
2940
|
+
"testIds": "not_applicable"
|
|
2941
|
+
}
|
|
2942
|
+
},
|
|
2888
2943
|
{
|
|
2889
2944
|
"sourceId": "agent_session:queueCoordinatorRuntimeStatePersistForTests",
|
|
2890
2945
|
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
@@ -3912,6 +3967,17 @@
|
|
|
3912
3967
|
"testIds": "not_applicable"
|
|
3913
3968
|
}
|
|
3914
3969
|
},
|
|
3970
|
+
{
|
|
3971
|
+
"sourceId": "agent_session:reloadSkills",
|
|
3972
|
+
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
3973
|
+
"sourceKind": "agent_session",
|
|
3974
|
+
"decision": "exclude",
|
|
3975
|
+
"rationale": "internal marketplace lifecycle refresh for the session skill catalog; not an independent SDK ingress operation",
|
|
3976
|
+
"exclusionMetadata": {
|
|
3977
|
+
"adapterMappings": "not_applicable",
|
|
3978
|
+
"testIds": "not_applicable"
|
|
3979
|
+
}
|
|
3980
|
+
},
|
|
3915
3981
|
{
|
|
3916
3982
|
"sourceId": "agent_session:retireWorkspaceTreeForRescope",
|
|
3917
3983
|
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|