@oh-my-pi/pi-coding-agent 15.1.2 → 15.1.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 +60 -0
- package/dist/types/async/job-manager.d.ts +3 -2
- package/dist/types/cli/auth-broker-cli.d.ts +25 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +18 -0
- package/dist/types/cli/grievances-cli.d.ts +12 -0
- package/dist/types/commands/auth-broker.d.ts +54 -0
- package/dist/types/commands/auth-gateway.d.ts +32 -0
- package/dist/types/commands/grievances.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -1
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -1
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -1
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +46 -0
- package/dist/types/discovery/agents.d.ts +12 -1
- package/dist/types/edit/renderer.d.ts +3 -0
- package/dist/types/eval/index.d.ts +0 -2
- package/dist/types/goals/tools/goal-tool.d.ts +10 -2
- package/dist/types/index.d.ts +0 -1
- package/dist/types/internal-urls/index.d.ts +1 -1
- package/dist/types/internal-urls/{pi-protocol.d.ts → omp-protocol.d.ts} +3 -3
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/main.d.ts +11 -2
- package/dist/types/modes/acp/acp-agent.d.ts +2 -1
- package/dist/types/modes/acp/acp-event-mapper.d.ts +13 -1
- package/dist/types/modes/acp/acp-mode.d.ts +3 -1
- package/dist/types/modes/emoji-autocomplete.d.ts +16 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
- package/dist/types/plan-mode/approved-plan.d.ts +10 -4
- package/dist/types/sdk.d.ts +10 -3
- package/dist/types/session/agent-session.d.ts +7 -3
- package/dist/types/session/auth-broker-config.d.ts +13 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/client-bridge.d.ts +3 -0
- package/dist/types/tools/eval.d.ts +41 -7
- package/dist/types/tools/irc.d.ts +8 -2
- package/dist/types/tools/report-tool-issue.d.ts +118 -1
- package/dist/types/tools/resolve.d.ts +8 -2
- package/examples/custom-tools/README.md +3 -12
- package/examples/extensions/README.md +2 -15
- package/examples/extensions/api-demo.ts +1 -7
- package/package.json +7 -7
- package/src/async/job-manager.ts +111 -13
- package/src/autoresearch/tools/init-experiment.ts +11 -33
- package/src/autoresearch/tools/log-experiment.ts +10 -24
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +2 -9
- package/src/cli/auth-broker-cli.ts +746 -0
- package/src/cli/auth-gateway-cli.ts +342 -0
- package/src/cli/grievances-cli.ts +109 -16
- package/src/cli/update-cli.ts +1 -5
- package/src/cli.ts +4 -2
- package/src/commands/auth-broker.ts +96 -0
- package/src/commands/auth-gateway.ts +61 -0
- package/src/commands/grievances.ts +13 -8
- package/src/commands/launch.ts +1 -1
- package/src/commit/agentic/agent.ts +2 -0
- package/src/commit/agentic/tools/analyze-file.ts +2 -2
- package/src/commit/agentic/tools/git-file-diff.ts +2 -2
- package/src/commit/agentic/tools/git-hunk.ts +3 -3
- package/src/commit/agentic/tools/git-overview.ts +2 -2
- package/src/commit/agentic/tools/propose-changelog.ts +1 -3
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -9
- package/src/config/model-equivalence.ts +279 -174
- package/src/config/model-registry.ts +37 -6
- package/src/config/model-resolver.ts +13 -8
- package/src/config/models-config-schema.ts +8 -0
- package/src/config/settings-schema.ts +52 -0
- package/src/cursor.ts +1 -1
- package/src/debug/log-formatting.ts +1 -1
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/profiler.ts +4 -0
- package/src/debug/raw-sse-buffer.ts +100 -59
- package/src/debug/raw-sse.ts +1 -1
- package/src/discovery/agents.ts +15 -4
- package/src/edit/modes/apply-patch.ts +1 -5
- package/src/edit/modes/patch.ts +5 -5
- package/src/edit/modes/replace.ts +5 -5
- package/src/edit/renderer.ts +2 -1
- package/src/edit/streaming.ts +1 -1
- package/src/eval/index.ts +0 -2
- package/src/eval/js/shared/runtime.ts +107 -2
- package/src/eval/py/kernel.ts +1 -1
- package/src/exa/researcher.ts +4 -4
- package/src/exa/search.ts +10 -22
- package/src/exa/websets.ts +33 -33
- package/src/extensibility/typebox.ts +44 -17
- package/src/goals/tools/goal-tool.ts +3 -3
- package/src/index.ts +0 -3
- package/src/internal-urls/docs-index.generated.ts +21 -18
- package/src/internal-urls/index.ts +1 -1
- package/src/internal-urls/{pi-protocol.ts → omp-protocol.ts} +10 -10
- package/src/internal-urls/router.ts +3 -3
- package/src/internal-urls/types.ts +1 -1
- package/src/lsp/types.ts +8 -11
- package/src/main.ts +216 -146
- package/src/mcp/tool-bridge.ts +3 -3
- package/src/modes/acp/acp-agent.ts +203 -57
- package/src/modes/acp/acp-client-bridge.ts +2 -1
- package/src/modes/acp/acp-event-mapper.ts +208 -32
- package/src/modes/acp/acp-mode.ts +11 -3
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/diff.ts +1 -2
- package/src/modes/components/eval-execution.ts +1 -1
- package/src/modes/components/oauth-selector.ts +38 -2
- package/src/modes/components/tool-execution.ts +1 -2
- package/src/modes/components/tree-selector.ts +26 -7
- package/src/modes/controllers/command-controller.ts +95 -34
- package/src/modes/controllers/input-controller.ts +4 -3
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/interactive-mode.ts +92 -19
- package/src/modes/print-mode.ts +3 -3
- package/src/modes/prompt-action-autocomplete.ts +14 -0
- package/src/plan-mode/approved-plan.ts +30 -9
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/tools/ask.md +4 -3
- package/src/prompts/tools/eval.md +25 -26
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/resolve.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/web-search.md +1 -1
- package/src/sdk.ts +81 -8
- package/src/session/agent-session.ts +362 -131
- package/src/session/agent-storage.ts +7 -2
- package/src/session/auth-broker-config.ts +102 -0
- package/src/session/auth-storage.ts +7 -1
- package/src/session/client-bridge.ts +3 -0
- package/src/session/streaming-output.ts +1 -1
- package/src/task/types.ts +10 -35
- package/src/tools/bash-interactive.ts +4 -1
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser.ts +12 -20
- package/src/tools/eval.ts +77 -100
- package/src/tools/gh.ts +21 -45
- package/src/tools/hindsight-recall.ts +1 -1
- package/src/tools/hindsight-reflect.ts +2 -2
- package/src/tools/hindsight-retain.ts +3 -7
- package/src/tools/index.ts +8 -1
- package/src/tools/inspect-image.ts +4 -1
- package/src/tools/irc.ts +4 -12
- package/src/tools/job.ts +3 -11
- package/src/tools/report-tool-issue.ts +462 -17
- package/src/tools/resolve.ts +2 -7
- package/src/tools/todo-write.ts +8 -15
- package/src/utils/title-generator.ts +3 -0
- package/src/web/search/index.ts +6 -6
- package/dist/types/eval/parse.d.ts +0 -28
- package/dist/types/eval/sniff.d.ts +0 -11
- package/src/eval/eval.lark +0 -36
- package/src/eval/parse.ts +0 -407
- package/src/eval/sniff.ts +0 -28
|
@@ -18,12 +18,15 @@ import * as fs from "node:fs";
|
|
|
18
18
|
import * as path from "node:path";
|
|
19
19
|
import { scheduler } from "node:timers/promises";
|
|
20
20
|
import {
|
|
21
|
+
type AfterToolCallContext,
|
|
22
|
+
type AfterToolCallResult,
|
|
21
23
|
type Agent,
|
|
22
24
|
AgentBusyError,
|
|
23
25
|
type AgentEvent,
|
|
24
26
|
type AgentMessage,
|
|
25
27
|
type AgentState,
|
|
26
28
|
type AgentTool,
|
|
29
|
+
resolveTelemetry,
|
|
27
30
|
ThinkingLevel,
|
|
28
31
|
} from "@oh-my-pi/pi-agent-core";
|
|
29
32
|
import {
|
|
@@ -78,7 +81,7 @@ import {
|
|
|
78
81
|
prompt,
|
|
79
82
|
Snowflake,
|
|
80
83
|
} from "@oh-my-pi/pi-utils";
|
|
81
|
-
import { type AsyncJob, AsyncJobManager } from "../async";
|
|
84
|
+
import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async";
|
|
82
85
|
import { reset as resetCapabilities } from "../capability";
|
|
83
86
|
import type { Rule } from "../capability/rule";
|
|
84
87
|
import { MODEL_ROLE_IDS, type ModelRegistry } from "../config/model-registry";
|
|
@@ -94,7 +97,7 @@ import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-temp
|
|
|
94
97
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
95
98
|
import { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
|
|
96
99
|
import { loadCapability } from "../discovery";
|
|
97
|
-
import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../edit";
|
|
100
|
+
import { expandApplyPatchToEntries, normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../edit";
|
|
98
101
|
import {
|
|
99
102
|
disposeKernelSessionsByOwner,
|
|
100
103
|
executePython as executePythonCommand,
|
|
@@ -153,6 +156,7 @@ import planModeToolDecisionReminderPrompt from "../prompts/system/plan-mode-tool
|
|
|
153
156
|
type: "text",
|
|
154
157
|
};
|
|
155
158
|
import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
|
|
159
|
+
import ttsrToolReminderTemplate from "../prompts/system/ttsr-tool-reminder.md" with { type: "text" };
|
|
156
160
|
import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
157
161
|
import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
|
|
158
162
|
import { invalidateHostMetadata } from "../ssh/connection-manager";
|
|
@@ -232,6 +236,7 @@ export type AsyncJobSnapshotItem = Pick<AsyncJob, "id" | "type" | "status" | "la
|
|
|
232
236
|
export interface AsyncJobSnapshot {
|
|
233
237
|
running: AsyncJobSnapshotItem[];
|
|
234
238
|
recent: AsyncJobSnapshotItem[];
|
|
239
|
+
delivery: AsyncJobDeliveryState;
|
|
235
240
|
}
|
|
236
241
|
|
|
237
242
|
// ============================================================================
|
|
@@ -530,7 +535,7 @@ function createHandoffFileName(date = new Date()): string {
|
|
|
530
535
|
// ============================================================================
|
|
531
536
|
|
|
532
537
|
/** Tools that require user permission before execution when an ACP client is connected. */
|
|
533
|
-
const PERMISSION_REQUIRED_TOOLS = new Set(["bash", "edit", "
|
|
538
|
+
const PERMISSION_REQUIRED_TOOLS = new Set(["bash", "edit", "delete", "move"]);
|
|
534
539
|
|
|
535
540
|
/** Permission options presented to the client on each gated tool call. */
|
|
536
541
|
const PERMISSION_OPTIONS: ClientBridgePermissionOption[] = [
|
|
@@ -542,46 +547,106 @@ const PERMISSION_OPTIONS: ClientBridgePermissionOption[] = [
|
|
|
542
547
|
|
|
543
548
|
const PERMISSION_OPTIONS_BY_ID = new Map(PERMISSION_OPTIONS.map(option => [option.optionId, option]));
|
|
544
549
|
|
|
545
|
-
function
|
|
546
|
-
const
|
|
550
|
+
function getStringProperty(value: Record<string, unknown>, key: string): string | undefined {
|
|
551
|
+
const candidate = value[key];
|
|
552
|
+
return typeof candidate === "string" ? candidate : undefined;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function collectStringPaths(value: unknown): string[] {
|
|
556
|
+
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function getEditDestructiveIntent(args: unknown): { kind: "delete" | "move"; paths: string[] } | undefined {
|
|
560
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return undefined;
|
|
561
|
+
const a = args as Record<string, unknown>;
|
|
562
|
+
|
|
563
|
+
const edits = Array.isArray(a.edits) ? a.edits : undefined;
|
|
564
|
+
if (edits) {
|
|
565
|
+
const path = getStringProperty(a, "path");
|
|
566
|
+
if (path) {
|
|
567
|
+
for (const edit of edits) {
|
|
568
|
+
if (!edit || typeof edit !== "object" || Array.isArray(edit)) continue;
|
|
569
|
+
const op = getStringProperty(edit as Record<string, unknown>, "op");
|
|
570
|
+
if (op === "delete") return { kind: "delete", paths: [path] };
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
for (const edit of edits) {
|
|
574
|
+
if (!edit || typeof edit !== "object" || Array.isArray(edit)) continue;
|
|
575
|
+
const entry = edit as Record<string, unknown>;
|
|
576
|
+
const op = getStringProperty(entry, "op");
|
|
577
|
+
const rename = getStringProperty(entry, "rename");
|
|
578
|
+
if (op !== "create" && rename) return { kind: "move", paths: path ? [path, rename] : [rename] };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const input = getStringProperty(a, "input");
|
|
583
|
+
if (input) {
|
|
584
|
+
try {
|
|
585
|
+
const entries = expandApplyPatchToEntries({ input });
|
|
586
|
+
const deleteEntry = entries.find(entry => entry.op === "delete");
|
|
587
|
+
if (deleteEntry) return { kind: "delete", paths: [deleteEntry.path] };
|
|
588
|
+
const moveEntry = entries.find(entry => entry.rename);
|
|
589
|
+
if (moveEntry?.rename) return { kind: "move", paths: [moveEntry.path, moveEntry.rename] };
|
|
590
|
+
} catch {
|
|
591
|
+
// If the edit input is not an apply_patch envelope, it is not a delete/move operation.
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return undefined;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function getPermissionIntent(
|
|
599
|
+
toolName: string,
|
|
600
|
+
args: unknown,
|
|
601
|
+
): { toolName: string; title: string; paths?: string[]; cacheKey: string } | undefined {
|
|
602
|
+
const a = args && typeof args === "object" && !Array.isArray(args) ? (args as Record<string, unknown>) : {};
|
|
547
603
|
if (toolName === "bash") {
|
|
548
|
-
const cmd =
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return `${verb} ${p}`;
|
|
555
|
-
}
|
|
556
|
-
} else if (toolName === "move") {
|
|
557
|
-
const from =
|
|
558
|
-
typeof a.oldPath === "string"
|
|
559
|
-
? a.oldPath
|
|
560
|
-
: typeof a.path === "string"
|
|
561
|
-
? a.path
|
|
562
|
-
: typeof a.from === "string"
|
|
563
|
-
? a.from
|
|
564
|
-
: undefined;
|
|
565
|
-
const to =
|
|
566
|
-
typeof a.newPath === "string"
|
|
567
|
-
? a.newPath
|
|
568
|
-
: typeof a.to === "string"
|
|
569
|
-
? a.to
|
|
570
|
-
: typeof a.destination === "string"
|
|
571
|
-
? a.destination
|
|
572
|
-
: undefined;
|
|
573
|
-
if (from && to) return `Move ${from} to ${to}`;
|
|
574
|
-
if (from) return `Move ${from}`;
|
|
575
|
-
} else if (toolName === "ast_edit") {
|
|
576
|
-
const paths = Array.isArray(a.paths)
|
|
577
|
-
? (a.paths as unknown[]).filter(x => typeof x === "string").join(", ")
|
|
578
|
-
: undefined;
|
|
579
|
-
if (paths) return `AST edit ${paths}`;
|
|
604
|
+
const cmd = getStringProperty(a, "command")?.slice(0, 80);
|
|
605
|
+
return { toolName, title: cmd || toolName, cacheKey: toolName };
|
|
606
|
+
}
|
|
607
|
+
if (toolName === "delete") {
|
|
608
|
+
const p = getStringProperty(a, "path");
|
|
609
|
+
return { toolName, title: p ? `Delete ${p}` : toolName, paths: p ? [p] : undefined, cacheKey: toolName };
|
|
580
610
|
}
|
|
581
|
-
|
|
611
|
+
if (toolName === "move") {
|
|
612
|
+
const from = getStringProperty(a, "oldPath") ?? getStringProperty(a, "path") ?? getStringProperty(a, "from");
|
|
613
|
+
const to = getStringProperty(a, "newPath") ?? getStringProperty(a, "to") ?? getStringProperty(a, "destination");
|
|
614
|
+
if (from && to) return { toolName, title: `Move ${from} to ${to}`, paths: [from, to], cacheKey: toolName };
|
|
615
|
+
return {
|
|
616
|
+
toolName,
|
|
617
|
+
title: from ? `Move ${from}` : toolName,
|
|
618
|
+
paths: from ? [from] : undefined,
|
|
619
|
+
cacheKey: toolName,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
if (toolName === "edit") {
|
|
623
|
+
const intent = getEditDestructiveIntent(args);
|
|
624
|
+
if (!intent) return undefined;
|
|
625
|
+
if (intent.kind === "delete") {
|
|
626
|
+
return {
|
|
627
|
+
toolName,
|
|
628
|
+
title: `Delete ${intent.paths[0] ?? "edit target"}`,
|
|
629
|
+
paths: intent.paths,
|
|
630
|
+
cacheKey: "edit:delete",
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
const from = intent.paths[0];
|
|
634
|
+
const to = intent.paths[1];
|
|
635
|
+
return {
|
|
636
|
+
toolName,
|
|
637
|
+
title: from && to ? `Move ${from} to ${to}` : `Move ${from ?? to ?? "edit target"}`,
|
|
638
|
+
paths: intent.paths,
|
|
639
|
+
cacheKey: "edit:move",
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return undefined;
|
|
582
643
|
}
|
|
583
644
|
|
|
584
|
-
function extractPermissionLocations(
|
|
645
|
+
function extractPermissionLocations(
|
|
646
|
+
args: unknown,
|
|
647
|
+
cwd: string,
|
|
648
|
+
explicitPaths?: string[],
|
|
649
|
+
): { path: string; line?: number }[] {
|
|
585
650
|
if (!args || typeof args !== "object") return [];
|
|
586
651
|
const a = args as Record<string, unknown>;
|
|
587
652
|
const out: { path: string; line?: number }[] = [];
|
|
@@ -599,12 +664,16 @@ function extractPermissionLocations(args: unknown, cwd: string): { path: string;
|
|
|
599
664
|
if (out.some(location => location.path === resolved)) return;
|
|
600
665
|
out.push({ path: resolved });
|
|
601
666
|
};
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (Array.isArray(a.paths)) {
|
|
605
|
-
for (const p of a.paths) {
|
|
667
|
+
if (explicitPaths) {
|
|
668
|
+
for (const p of explicitPaths) {
|
|
606
669
|
pushPath(p);
|
|
607
670
|
}
|
|
671
|
+
return out;
|
|
672
|
+
}
|
|
673
|
+
pushPath(a.path);
|
|
674
|
+
pushPath(a.file);
|
|
675
|
+
for (const p of collectStringPaths(a.paths)) {
|
|
676
|
+
pushPath(p);
|
|
608
677
|
}
|
|
609
678
|
pushPath(a.oldPath);
|
|
610
679
|
pushPath(a.newPath);
|
|
@@ -663,6 +732,7 @@ export class AgentSession {
|
|
|
663
732
|
#planReferenceSent = false;
|
|
664
733
|
#planReferencePath = "local://PLAN.md";
|
|
665
734
|
#clientBridge: ClientBridge | undefined;
|
|
735
|
+
#allowAcpAgentInitiatedTurns = false;
|
|
666
736
|
/** Per-session memory of allow_always / reject_always decisions for gated tools. */
|
|
667
737
|
#acpPermissionDecisions: Map<string, "allow_always" | "reject_always"> = new Map();
|
|
668
738
|
|
|
@@ -767,6 +837,10 @@ export class AgentSession {
|
|
|
767
837
|
// TTSR manager for time-traveling stream rules
|
|
768
838
|
#ttsrManager: TtsrManager | undefined = undefined;
|
|
769
839
|
#pendingTtsrInjections: Rule[] = [];
|
|
840
|
+
/** Per-tool TTSR rules whose `interruptMode` opted out of aborting the stream.
|
|
841
|
+
* These are folded into the matched tool call's `toolResult` content as an
|
|
842
|
+
* in-band system reminder, instead of spawning a separate follow-up turn. */
|
|
843
|
+
#perToolTtsrInjections = new Map<string, Rule[]>();
|
|
770
844
|
#ttsrAbortPending = false;
|
|
771
845
|
#ttsrRetryToken = 0;
|
|
772
846
|
#ttsrResumePromise: Promise<void> | undefined = undefined;
|
|
@@ -796,6 +870,15 @@ export class AgentSession {
|
|
|
796
870
|
|
|
797
871
|
#streamingEditFileCache = new Map<string, string>();
|
|
798
872
|
#promptInFlightCount = 0;
|
|
873
|
+
// Wire-level agent_end emission deferred until #promptInFlightCount drops to 0.
|
|
874
|
+
// Internal extension hooks and post-emit work (auto-retry, auto-compaction, todo
|
|
875
|
+
// checks in #handleAgentEvent) still fire on the original schedule — only the
|
|
876
|
+
// `#emit(event)` that reaches external subscribers (rpc-mode stdout, ACP bridge,
|
|
877
|
+
// Cursor exec, TUI listeners) is held back. Without this, a client that resumes
|
|
878
|
+
// on `agent_end` can fire its next `prompt` before #promptWithMessage's finally
|
|
879
|
+
// has decremented #promptInFlightCount, hitting AgentBusyError. Flushed from
|
|
880
|
+
// both #endInFlight (normal) and #resetInFlight (abort).
|
|
881
|
+
#pendingAgentEndEmit: AgentSessionEvent | undefined;
|
|
799
882
|
#obfuscator: SecretObfuscator | undefined;
|
|
800
883
|
#checkpointState: CheckpointState | undefined = undefined;
|
|
801
884
|
#pendingRewindReport: string | undefined = undefined;
|
|
@@ -849,12 +932,21 @@ export class AgentSession {
|
|
|
849
932
|
this.#promptInFlightCount = Math.max(0, this.#promptInFlightCount - 1);
|
|
850
933
|
if (this.#promptInFlightCount === 0) {
|
|
851
934
|
this.#releasePowerAssertion();
|
|
935
|
+
this.#flushPendingAgentEnd();
|
|
852
936
|
}
|
|
853
937
|
}
|
|
854
938
|
|
|
855
939
|
#resetInFlight(): void {
|
|
856
940
|
this.#promptInFlightCount = 0;
|
|
857
941
|
this.#releasePowerAssertion();
|
|
942
|
+
this.#flushPendingAgentEnd();
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
#flushPendingAgentEnd(): void {
|
|
946
|
+
const pending = this.#pendingAgentEndEmit;
|
|
947
|
+
if (!pending) return;
|
|
948
|
+
this.#pendingAgentEndEmit = undefined;
|
|
949
|
+
this.#emit(pending);
|
|
858
950
|
}
|
|
859
951
|
|
|
860
952
|
constructor(config: AgentSessionConfig) {
|
|
@@ -880,16 +972,28 @@ export class AgentSession {
|
|
|
880
972
|
this.#transformContext = config.transformContext ?? (messages => messages);
|
|
881
973
|
this.#onPayload = config.onPayload;
|
|
882
974
|
this.rawSseDebugBuffer = config.rawSseDebugBuffer ?? new RawSseDebugBuffer();
|
|
975
|
+
// Avoid wrapping in an `async` closure when no user callback is configured: the
|
|
976
|
+
// outer await on `#onResponse` (provider-response.ts) tolerates a sync void return,
|
|
977
|
+
// and skipping the wrapper drops a per-event `newPromiseCapability` allocation that
|
|
978
|
+
// shows up as ~3.5% self time in streaming profiles.
|
|
883
979
|
const configuredOnResponse = config.onResponse;
|
|
884
|
-
this.#onResponse =
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
980
|
+
this.#onResponse = configuredOnResponse
|
|
981
|
+
? async (response, model) => {
|
|
982
|
+
this.rawSseDebugBuffer.recordResponse(response, model);
|
|
983
|
+
await configuredOnResponse(response, model);
|
|
984
|
+
}
|
|
985
|
+
: (response, model) => {
|
|
986
|
+
this.rawSseDebugBuffer.recordResponse(response, model);
|
|
987
|
+
};
|
|
888
988
|
const configuredOnSseEvent = config.onSseEvent;
|
|
889
|
-
this.#onSseEvent =
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
989
|
+
this.#onSseEvent = configuredOnSseEvent
|
|
990
|
+
? (event, model) => {
|
|
991
|
+
this.rawSseDebugBuffer.recordEvent(event, model);
|
|
992
|
+
configuredOnSseEvent(event, model);
|
|
993
|
+
}
|
|
994
|
+
: (event, model) => {
|
|
995
|
+
this.rawSseDebugBuffer.recordEvent(event, model);
|
|
996
|
+
};
|
|
893
997
|
this.agent.setProviderResponseInterceptor(this.#onResponse);
|
|
894
998
|
this.agent.setRawSseEventInterceptor(this.#onSseEvent);
|
|
895
999
|
this.#convertToLlm = config.convertToLlm ?? convertToLlm;
|
|
@@ -932,6 +1036,8 @@ export class AgentSession {
|
|
|
932
1036
|
this.#preCacheStreamingEditFile(event);
|
|
933
1037
|
this.#maybeAbortStreamingEdit(event);
|
|
934
1038
|
});
|
|
1039
|
+
// Per-tool TTSR reminders are folded into the matched tool's result via this hook.
|
|
1040
|
+
this.agent.afterToolCall = ctx => this.#ttsrAfterToolCall(ctx);
|
|
935
1041
|
this.agent.providerSessionState = this.#providerSessionState;
|
|
936
1042
|
this.#syncAgentSessionId();
|
|
937
1043
|
this.#syncTodoPhasesFromBranch();
|
|
@@ -1104,21 +1210,23 @@ export class AgentSession {
|
|
|
1104
1210
|
getAsyncJobSnapshot(options?: { recentLimit?: number }): AsyncJobSnapshot | null {
|
|
1105
1211
|
const manager = AsyncJobManager.instance();
|
|
1106
1212
|
if (!manager) return null;
|
|
1107
|
-
const
|
|
1213
|
+
const ownerFilter = this.#agentId ? { ownerId: this.#agentId } : undefined;
|
|
1214
|
+
const running = manager.getRunningJobs(ownerFilter).map(job => ({
|
|
1108
1215
|
id: job.id,
|
|
1109
1216
|
type: job.type,
|
|
1110
1217
|
status: job.status,
|
|
1111
1218
|
label: job.label,
|
|
1112
1219
|
startTime: job.startTime,
|
|
1113
1220
|
}));
|
|
1114
|
-
const recent = manager.getRecentJobs(options?.recentLimit ?? 5).map(job => ({
|
|
1221
|
+
const recent = manager.getRecentJobs(options?.recentLimit ?? 5, ownerFilter).map(job => ({
|
|
1115
1222
|
id: job.id,
|
|
1116
1223
|
type: job.type,
|
|
1117
1224
|
status: job.status,
|
|
1118
1225
|
label: job.label,
|
|
1119
1226
|
startTime: job.startTime,
|
|
1120
1227
|
}));
|
|
1121
|
-
|
|
1228
|
+
const delivery = manager.getDeliveryState(ownerFilter);
|
|
1229
|
+
return { running, recent, delivery };
|
|
1122
1230
|
}
|
|
1123
1231
|
|
|
1124
1232
|
/**
|
|
@@ -1176,6 +1284,18 @@ export class AgentSession {
|
|
|
1176
1284
|
return;
|
|
1177
1285
|
}
|
|
1178
1286
|
await this.#emitExtensionEvent(event);
|
|
1287
|
+
// Hold the wire-level agent_end until in-flight prompts unwind. Subscribers
|
|
1288
|
+
// (rpc-mode, ACP, Cursor) treat agent_end as the "session is idle" signal;
|
|
1289
|
+
// emitting while #promptInFlightCount > 0 lets a client fire its next
|
|
1290
|
+
// `prompt` into a session that still reports isStreaming === true. Flush
|
|
1291
|
+
// happens in #endInFlight / #resetInFlight. A later agent_end (e.g. from
|
|
1292
|
+
// an auto-compaction turn that starts before the original prompt unwinds)
|
|
1293
|
+
// supersedes the pending one, which is what subscribers want — they only
|
|
1294
|
+
// care about the final settle.
|
|
1295
|
+
if (event.type === "agent_end" && this.#promptInFlightCount > 0) {
|
|
1296
|
+
this.#pendingAgentEndEmit = event;
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1179
1299
|
this.#emit(event);
|
|
1180
1300
|
}
|
|
1181
1301
|
|
|
@@ -1325,77 +1445,87 @@ export class AgentSession {
|
|
|
1325
1445
|
if (matchContext && "delta" in assistantEvent) {
|
|
1326
1446
|
const matches = this.#ttsrManager.checkDelta(assistantEvent.delta, matchContext);
|
|
1327
1447
|
if (matches.length > 0) {
|
|
1328
|
-
//
|
|
1329
|
-
|
|
1330
|
-
this.#
|
|
1331
|
-
|
|
1332
|
-
if (
|
|
1333
|
-
|
|
1334
|
-
this.#ttsrAbortPending = true;
|
|
1335
|
-
this.#ensureTtsrResumePromise();
|
|
1336
|
-
this.agent.abort();
|
|
1337
|
-
// Notify extensions (fire-and-forget, does not block abort)
|
|
1448
|
+
// Decide first: a non-interrupting tool-source match attaches to the
|
|
1449
|
+
// specific tool call's result instead of driving a loop-wide follow-up.
|
|
1450
|
+
const shouldInterrupt = this.#shouldInterruptForTtsrMatch(matches, matchContext);
|
|
1451
|
+
const perToolId = shouldInterrupt ? undefined : this.#extractTtsrToolCallId(matchContext);
|
|
1452
|
+
if (perToolId) {
|
|
1453
|
+
this.#addPerToolTtsrInjections(perToolId, matches);
|
|
1338
1454
|
this.#emitSessionEvent({ type: "ttsr_triggered", rules: matches }).catch(() => {});
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1455
|
+
} else {
|
|
1456
|
+
// Queue rules for injection; mark as injected only after successful enqueue.
|
|
1457
|
+
this.#addPendingTtsrInjections(matches);
|
|
1458
|
+
|
|
1459
|
+
if (shouldInterrupt) {
|
|
1460
|
+
// Abort the stream immediately — do not gate on extension callbacks
|
|
1461
|
+
this.#ttsrAbortPending = true;
|
|
1462
|
+
this.#ensureTtsrResumePromise();
|
|
1463
|
+
this.agent.abort();
|
|
1464
|
+
// Notify extensions (fire-and-forget, does not block abort)
|
|
1465
|
+
this.#emitSessionEvent({ type: "ttsr_triggered", rules: matches }).catch(() => {});
|
|
1466
|
+
// Schedule retry after a short delay
|
|
1467
|
+
const retryToken = ++this.#ttsrRetryToken;
|
|
1468
|
+
const generation = this.#promptGeneration;
|
|
1469
|
+
const targetMessageTimestamp =
|
|
1470
|
+
event.message.role === "assistant" ? event.message.timestamp : undefined;
|
|
1471
|
+
this.#schedulePostPromptTask(
|
|
1472
|
+
async () => {
|
|
1473
|
+
if (this.#ttsrRetryToken !== retryToken) {
|
|
1474
|
+
this.#resolveTtsrResume();
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1350
1477
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1478
|
+
const targetAssistantIndex = this.#findTtsrAssistantIndex(targetMessageTimestamp);
|
|
1479
|
+
if (
|
|
1480
|
+
!this.#ttsrAbortPending ||
|
|
1481
|
+
this.#promptGeneration !== generation ||
|
|
1482
|
+
targetAssistantIndex === -1
|
|
1483
|
+
) {
|
|
1484
|
+
this.#ttsrAbortPending = false;
|
|
1485
|
+
this.#pendingTtsrInjections = [];
|
|
1486
|
+
this.#perToolTtsrInjections.clear();
|
|
1487
|
+
this.#resolveTtsrResume();
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1357
1490
|
this.#ttsrAbortPending = false;
|
|
1358
|
-
this.#
|
|
1359
|
-
this.#
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
{ delayMs: 50 },
|
|
1397
|
-
);
|
|
1398
|
-
return;
|
|
1491
|
+
this.#perToolTtsrInjections.clear();
|
|
1492
|
+
const ttsrSettings = this.#ttsrManager?.getSettings();
|
|
1493
|
+
if (ttsrSettings?.contextMode === "discard") {
|
|
1494
|
+
// Remove the partial/aborted assistant turn from agent state
|
|
1495
|
+
this.agent.replaceMessages(this.agent.state.messages.slice(0, targetAssistantIndex));
|
|
1496
|
+
}
|
|
1497
|
+
// Inject TTSR rules as system reminder before retry
|
|
1498
|
+
const injection = this.#getTtsrInjectionContent();
|
|
1499
|
+
if (injection) {
|
|
1500
|
+
const details = { rules: injection.rules.map(rule => rule.name) };
|
|
1501
|
+
this.agent.appendMessage({
|
|
1502
|
+
role: "custom",
|
|
1503
|
+
customType: "ttsr-injection",
|
|
1504
|
+
content: injection.content,
|
|
1505
|
+
display: false,
|
|
1506
|
+
details,
|
|
1507
|
+
attribution: "agent",
|
|
1508
|
+
timestamp: Date.now(),
|
|
1509
|
+
});
|
|
1510
|
+
this.sessionManager.appendCustomMessageEntry(
|
|
1511
|
+
"ttsr-injection",
|
|
1512
|
+
injection.content,
|
|
1513
|
+
false,
|
|
1514
|
+
details,
|
|
1515
|
+
"agent",
|
|
1516
|
+
);
|
|
1517
|
+
this.#markTtsrInjected(details.rules);
|
|
1518
|
+
}
|
|
1519
|
+
try {
|
|
1520
|
+
await this.agent.continue();
|
|
1521
|
+
} catch {
|
|
1522
|
+
this.#resolveTtsrResume();
|
|
1523
|
+
}
|
|
1524
|
+
},
|
|
1525
|
+
{ delayMs: 50 },
|
|
1526
|
+
);
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1399
1529
|
}
|
|
1400
1530
|
}
|
|
1401
1531
|
}
|
|
@@ -1804,6 +1934,61 @@ export class AgentSession {
|
|
|
1804
1934
|
}
|
|
1805
1935
|
}
|
|
1806
1936
|
|
|
1937
|
+
/** Tool-call id whose argument deltas triggered a TTSR match, when known. */
|
|
1938
|
+
#extractTtsrToolCallId(matchContext: TtsrMatchContext): string | undefined {
|
|
1939
|
+
if (matchContext.source !== "tool") return undefined;
|
|
1940
|
+
const key = matchContext.streamKey;
|
|
1941
|
+
if (typeof key !== "string" || !key.startsWith("toolcall:")) return undefined;
|
|
1942
|
+
const id = key.slice("toolcall:".length);
|
|
1943
|
+
return id.length > 0 ? id : undefined;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
#addPerToolTtsrInjections(toolCallId: string, rules: Rule[]): void {
|
|
1947
|
+
const bucket = this.#perToolTtsrInjections.get(toolCallId) ?? [];
|
|
1948
|
+
const seen = new Set(bucket.map(rule => rule.name));
|
|
1949
|
+
// Dedupe against rules already bucketed for other tool calls in this
|
|
1950
|
+
// same assistant message so one rule attaches to exactly one tool call.
|
|
1951
|
+
const claimedElsewhere = new Set<string>();
|
|
1952
|
+
for (const [otherId, otherBucket] of this.#perToolTtsrInjections) {
|
|
1953
|
+
if (otherId === toolCallId) continue;
|
|
1954
|
+
for (const rule of otherBucket) claimedElsewhere.add(rule.name);
|
|
1955
|
+
}
|
|
1956
|
+
const newlyAdded: string[] = [];
|
|
1957
|
+
for (const rule of rules) {
|
|
1958
|
+
if (seen.has(rule.name) || claimedElsewhere.has(rule.name)) continue;
|
|
1959
|
+
bucket.push(rule);
|
|
1960
|
+
seen.add(rule.name);
|
|
1961
|
+
newlyAdded.push(rule.name);
|
|
1962
|
+
}
|
|
1963
|
+
if (bucket.length === 0) return;
|
|
1964
|
+
this.#perToolTtsrInjections.set(toolCallId, bucket);
|
|
1965
|
+
// Claim the rules in the TTSR manager so subsequent deltas in this same
|
|
1966
|
+
// turn (e.g. a sibling tool call's argument stream) don't re-match them.
|
|
1967
|
+
// Persistence still happens in #ttsrAfterToolCall when the tool actually
|
|
1968
|
+
// produces a result we can fold the reminder into.
|
|
1969
|
+
if (newlyAdded.length > 0) {
|
|
1970
|
+
this.#ttsrManager?.markInjectedByNames(newlyAdded);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
/** `afterToolCall` hook: fold any per-tool TTSR reminders into the result. */
|
|
1975
|
+
#ttsrAfterToolCall(ctx: AfterToolCallContext): AfterToolCallResult | undefined {
|
|
1976
|
+
const rules = this.#perToolTtsrInjections.get(ctx.toolCall.id);
|
|
1977
|
+
if (!rules || rules.length === 0) return undefined;
|
|
1978
|
+
this.#perToolTtsrInjections.delete(ctx.toolCall.id);
|
|
1979
|
+
const reminder = rules
|
|
1980
|
+
.map(r => prompt.render(ttsrToolReminderTemplate, { name: r.name, path: r.path, content: r.content }))
|
|
1981
|
+
.join("\n\n");
|
|
1982
|
+
// The TTSR manager was already claimed at bucket time; only persistence remains.
|
|
1983
|
+
const ruleNames = rules.map(r => r.name.trim()).filter(n => n.length > 0);
|
|
1984
|
+
if (ruleNames.length > 0) {
|
|
1985
|
+
this.sessionManager.appendTtsrInjection(ruleNames);
|
|
1986
|
+
}
|
|
1987
|
+
return {
|
|
1988
|
+
content: [{ type: "text", text: reminder }, ...ctx.result.content],
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1807
1992
|
#extractTtsrRuleNames(details: unknown): string[] {
|
|
1808
1993
|
if (!details || typeof details !== "object" || Array.isArray(details)) {
|
|
1809
1994
|
return [];
|
|
@@ -1854,6 +2039,11 @@ export class AgentSession {
|
|
|
1854
2039
|
}
|
|
1855
2040
|
|
|
1856
2041
|
#queueDeferredTtsrInjectionIfNeeded(assistantMsg: AssistantMessage): void {
|
|
2042
|
+
if (assistantMsg.stopReason === "aborted" || assistantMsg.stopReason === "error") {
|
|
2043
|
+
// Tools that hadn't started by abort/error will never produce results to
|
|
2044
|
+
// fold injections into — drop their stale per-tool entries.
|
|
2045
|
+
this.#perToolTtsrInjections.clear();
|
|
2046
|
+
}
|
|
1857
2047
|
if (this.#ttsrAbortPending || this.#pendingTtsrInjections.length === 0) {
|
|
1858
2048
|
return;
|
|
1859
2049
|
}
|
|
@@ -2582,6 +2772,23 @@ export class AgentSession {
|
|
|
2582
2772
|
await this.#waitForPostPromptRecovery();
|
|
2583
2773
|
}
|
|
2584
2774
|
|
|
2775
|
+
async drainAsyncJobDeliveriesForAcp(options?: { timeoutMs?: number }): Promise<boolean> {
|
|
2776
|
+
const manager = AsyncJobManager.instance();
|
|
2777
|
+
if (!manager) return false;
|
|
2778
|
+
const ownerFilter = this.#agentId ? { ownerId: this.#agentId } : undefined;
|
|
2779
|
+
const before = manager.getDeliveryState(ownerFilter);
|
|
2780
|
+
if (before.queued === 0 && !before.delivering) return false;
|
|
2781
|
+
const previousAllowAcpAgentInitiatedTurns = this.#allowAcpAgentInitiatedTurns;
|
|
2782
|
+
this.#allowAcpAgentInitiatedTurns = true;
|
|
2783
|
+
try {
|
|
2784
|
+
const drained = await manager.drainDeliveries({ timeoutMs: options?.timeoutMs, filter: ownerFilter });
|
|
2785
|
+
const after = manager.getDeliveryState(ownerFilter);
|
|
2786
|
+
return drained && (before.queued !== after.queued || before.delivering !== after.delivering);
|
|
2787
|
+
} finally {
|
|
2788
|
+
this.#allowAcpAgentInitiatedTurns = previousAllowAcpAgentInitiatedTurns;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2585
2792
|
/** Most recent assistant message in agent state. */
|
|
2586
2793
|
getLastAssistantMessage(): AssistantMessage | undefined {
|
|
2587
2794
|
return this.#findLastAssistantMessage();
|
|
@@ -2881,8 +3088,8 @@ export class AgentSession {
|
|
|
2881
3088
|
if (!bridge?.capabilities.requestPermission || !bridge.requestPermission) return tool;
|
|
2882
3089
|
if (!PERMISSION_REQUIRED_TOOLS.has(tool.name)) return tool;
|
|
2883
3090
|
return new Proxy(tool, {
|
|
2884
|
-
get: (target, prop
|
|
2885
|
-
if (prop !== "execute") return Reflect.get(target, prop,
|
|
3091
|
+
get: (target, prop) => {
|
|
3092
|
+
if (prop !== "execute") return Reflect.get(target, prop, target);
|
|
2886
3093
|
return async (
|
|
2887
3094
|
toolCallId: string,
|
|
2888
3095
|
args: unknown,
|
|
@@ -2890,8 +3097,12 @@ export class AgentSession {
|
|
|
2890
3097
|
onUpdate: never,
|
|
2891
3098
|
ctx: never,
|
|
2892
3099
|
) => {
|
|
3100
|
+
const permissionIntent = getPermissionIntent(target.name, args);
|
|
3101
|
+
if (!permissionIntent) {
|
|
3102
|
+
return await target.execute(toolCallId, args as never, signal, onUpdate, ctx);
|
|
3103
|
+
}
|
|
2893
3104
|
// Short-circuit on persisted decisions.
|
|
2894
|
-
const persisted = this.#acpPermissionDecisions.get(
|
|
3105
|
+
const persisted = this.#acpPermissionDecisions.get(permissionIntent.cacheKey);
|
|
2895
3106
|
if (persisted === "allow_always") {
|
|
2896
3107
|
return await target.execute(toolCallId, args as never, signal, onUpdate, ctx);
|
|
2897
3108
|
}
|
|
@@ -2913,9 +3124,14 @@ export class AgentSession {
|
|
|
2913
3124
|
{
|
|
2914
3125
|
toolCallId,
|
|
2915
3126
|
toolName: target.name,
|
|
2916
|
-
title:
|
|
3127
|
+
title: permissionIntent.title,
|
|
3128
|
+
status: "pending",
|
|
2917
3129
|
rawInput: args,
|
|
2918
|
-
locations: extractPermissionLocations(
|
|
3130
|
+
locations: extractPermissionLocations(
|
|
3131
|
+
args,
|
|
3132
|
+
this.sessionManager.getCwd(),
|
|
3133
|
+
permissionIntent.paths,
|
|
3134
|
+
),
|
|
2919
3135
|
},
|
|
2920
3136
|
PERMISSION_OPTIONS,
|
|
2921
3137
|
signal,
|
|
@@ -2936,9 +3152,9 @@ export class AgentSession {
|
|
|
2936
3152
|
throw new ToolError(`Tool permission response used unknown option ID: ${outcome.optionId}`);
|
|
2937
3153
|
}
|
|
2938
3154
|
if (selectedOption.kind === "allow_always") {
|
|
2939
|
-
this.#acpPermissionDecisions.set(
|
|
3155
|
+
this.#acpPermissionDecisions.set(permissionIntent.cacheKey, "allow_always");
|
|
2940
3156
|
} else if (selectedOption.kind === "reject_always") {
|
|
2941
|
-
this.#acpPermissionDecisions.set(
|
|
3157
|
+
this.#acpPermissionDecisions.set(permissionIntent.cacheKey, "reject_always");
|
|
2942
3158
|
}
|
|
2943
3159
|
if (selectedOption.kind === "reject_once" || selectedOption.kind === "reject_always") {
|
|
2944
3160
|
throw new ToolError(`Tool call rejected by user (${target.name})`);
|
|
@@ -4178,7 +4394,7 @@ export class AgentSession {
|
|
|
4178
4394
|
*
|
|
4179
4395
|
* Handles three cases:
|
|
4180
4396
|
* - Streaming: queue as steer/follow-up or store for next turn
|
|
4181
|
-
* - Not streaming + triggerTurn: appends to state/session, starts new turn
|
|
4397
|
+
* - Not streaming + triggerTurn: appends to state/session, starts new turn unless the client cannot own it
|
|
4182
4398
|
* - Not streaming + no trigger: appends to state/session, no turn
|
|
4183
4399
|
*/
|
|
4184
4400
|
async sendCustomMessage<T = unknown>(
|
|
@@ -4210,6 +4426,10 @@ export class AgentSession {
|
|
|
4210
4426
|
|
|
4211
4427
|
if (options?.deliverAs === "nextTurn") {
|
|
4212
4428
|
if (options?.triggerTurn) {
|
|
4429
|
+
if (this.#clientBridge?.deferAgentInitiatedTurns && !this.#allowAcpAgentInitiatedTurns) {
|
|
4430
|
+
this.#queueHiddenNextTurnMessage(appMessage, false);
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4213
4433
|
await this.agent.prompt(appMessage);
|
|
4214
4434
|
return;
|
|
4215
4435
|
}
|
|
@@ -4225,6 +4445,10 @@ export class AgentSession {
|
|
|
4225
4445
|
}
|
|
4226
4446
|
|
|
4227
4447
|
if (options?.triggerTurn) {
|
|
4448
|
+
if (this.#clientBridge?.deferAgentInitiatedTurns && !this.#allowAcpAgentInitiatedTurns) {
|
|
4449
|
+
this.#queueHiddenNextTurnMessage(appMessage, false);
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4228
4452
|
await this.agent.prompt(appMessage);
|
|
4229
4453
|
return;
|
|
4230
4454
|
}
|
|
@@ -5249,6 +5473,7 @@ export class AgentSession {
|
|
|
5249
5473
|
convertToLlm,
|
|
5250
5474
|
initiatorOverride: "agent",
|
|
5251
5475
|
metadata: this.agent.metadataForProvider(model.provider),
|
|
5476
|
+
telemetry: resolveTelemetry(this.agent.telemetry, this.sessionId),
|
|
5252
5477
|
},
|
|
5253
5478
|
handoffSignal,
|
|
5254
5479
|
);
|
|
@@ -5961,6 +6186,7 @@ export class AgentSession {
|
|
|
5961
6186
|
options?: SummaryOptions,
|
|
5962
6187
|
): Promise<CompactionResult> {
|
|
5963
6188
|
const candidates = this.#getCompactionModelCandidates(this.#modelRegistry.getAvailable());
|
|
6189
|
+
const telemetry = resolveTelemetry(this.agent.telemetry, this.sessionId);
|
|
5964
6190
|
|
|
5965
6191
|
for (const candidate of candidates) {
|
|
5966
6192
|
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
|
|
@@ -5971,6 +6197,7 @@ export class AgentSession {
|
|
|
5971
6197
|
...options,
|
|
5972
6198
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
5973
6199
|
convertToLlm,
|
|
6200
|
+
telemetry,
|
|
5974
6201
|
});
|
|
5975
6202
|
} catch (error) {
|
|
5976
6203
|
if (!this.#isCompactionAuthFailure(error)) {
|
|
@@ -6207,6 +6434,7 @@ export class AgentSession {
|
|
|
6207
6434
|
} else {
|
|
6208
6435
|
const candidates = this.#getCompactionModelCandidates(availableModels);
|
|
6209
6436
|
const retrySettings = this.settings.getGroup("retry");
|
|
6437
|
+
const telemetry = resolveTelemetry(this.agent.telemetry, this.sessionId);
|
|
6210
6438
|
let compactResult: CompactionResult | undefined;
|
|
6211
6439
|
let lastError: unknown;
|
|
6212
6440
|
|
|
@@ -6224,6 +6452,7 @@ export class AgentSession {
|
|
|
6224
6452
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
6225
6453
|
initiatorOverride: "agent",
|
|
6226
6454
|
convertToLlm,
|
|
6455
|
+
telemetry,
|
|
6227
6456
|
});
|
|
6228
6457
|
break;
|
|
6229
6458
|
} catch (error) {
|
|
@@ -7828,6 +8057,7 @@ export class AgentSession {
|
|
|
7828
8057
|
reserveTokens: branchSummarySettings.reserveTokens,
|
|
7829
8058
|
metadata: this.agent.metadataForProvider(model.provider),
|
|
7830
8059
|
convertToLlm,
|
|
8060
|
+
telemetry: resolveTelemetry(this.agent.telemetry, this.sessionId),
|
|
7831
8061
|
});
|
|
7832
8062
|
this.#branchSummaryAbortController = undefined;
|
|
7833
8063
|
if (result.aborted) {
|
|
@@ -8063,11 +8293,12 @@ export class AgentSession {
|
|
|
8063
8293
|
};
|
|
8064
8294
|
}
|
|
8065
8295
|
|
|
8066
|
-
async fetchUsageReports(): Promise<UsageReport[] | null> {
|
|
8296
|
+
async fetchUsageReports(signal?: AbortSignal): Promise<UsageReport[] | null> {
|
|
8067
8297
|
const authStorage = this.#modelRegistry.authStorage;
|
|
8068
8298
|
if (!authStorage.fetchUsageReports) return null;
|
|
8069
8299
|
return authStorage.fetchUsageReports({
|
|
8070
8300
|
baseUrlResolver: provider => this.#modelRegistry.getProviderBaseUrl?.(provider),
|
|
8301
|
+
signal,
|
|
8071
8302
|
});
|
|
8072
8303
|
}
|
|
8073
8304
|
|