@oh-my-pi/pi-coding-agent 16.5.0 → 16.5.2
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 +91 -0
- package/dist/cli.js +3556 -3504
- package/dist/types/advisor/advise-tool.d.ts +12 -1
- package/dist/types/advisor/runtime.d.ts +41 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +4 -1
- package/dist/types/cli/usage-cli.d.ts +3 -0
- package/dist/types/cli/usage-error.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +2 -2
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +8 -1
- package/dist/types/config/models-config.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/discovery/substitute-plugin-root.d.ts +22 -0
- package/dist/types/eval/__tests__/process-entry-import.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +3 -3
- package/dist/types/eval/bridge-timeout.d.ts +9 -1
- package/dist/types/eval/js/context-manager.d.ts +5 -3
- package/dist/types/eval/js/process-entry.d.ts +6 -0
- package/dist/types/eval/js/worker-core.d.ts +15 -1
- package/dist/types/eval/py/spawn-options.d.ts +10 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -6
- package/dist/types/extensibility/plugins/bun-git-cache.d.ts +3 -0
- package/dist/types/goals/guided-setup.d.ts +12 -0
- package/dist/types/internal-urls/history-protocol.d.ts +3 -2
- package/dist/types/internal-urls/memory-protocol.d.ts +6 -7
- package/dist/types/internal-urls/registry-helpers.d.ts +19 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +2 -0
- package/dist/types/modes/components/__tests__/dynamic-border.test.d.ts +1 -0
- package/dist/types/modes/components/agent-hub.d.ts +10 -0
- package/dist/types/modes/components/dynamic-border.d.ts +5 -3
- package/dist/types/modes/components/login-dialog.d.ts +2 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +0 -2
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/modes/orchestrate.d.ts +1 -1
- package/dist/types/modes/rpc/host-tools.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +26 -6
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/ultrathink.d.ts +1 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +12 -0
- package/dist/types/modes/workflow.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/exit-diagnostics.d.ts +11 -0
- package/dist/types/session/messages.d.ts +15 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +11 -0
- package/dist/types/subprocess/worker-client.d.ts +6 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -0
- package/dist/types/tools/grep.d.ts +0 -2
- package/dist/types/tools/read.d.ts +0 -4
- package/dist/types/web/search/provider.d.ts +10 -3
- package/dist/types/web/search/providers/codex.d.ts +5 -4
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +917 -42
- package/src/advisor/advise-tool.ts +17 -1
- package/src/advisor/runtime.ts +270 -67
- package/src/autolearn/controller.ts +15 -3
- package/src/autoresearch/dashboard.ts +2 -2
- package/src/cli/args.ts +12 -0
- package/src/cli/auth-broker-cli.ts +30 -11
- package/src/cli/auth-gateway-cli.ts +5 -1
- package/src/cli/config-cli.ts +15 -3
- package/src/cli/dry-balance-cli.ts +14 -4
- package/src/cli/flag-tables.ts +21 -7
- package/src/cli/update-cli.ts +62 -11
- package/src/cli/usage-cli.ts +58 -5
- package/src/cli/usage-error.ts +7 -0
- package/src/cli.ts +23 -1
- package/src/commands/acp.ts +11 -2
- package/src/commands/launch.ts +12 -3
- package/src/commands/token.ts +3 -1
- package/src/config/api-key-resolver.ts +12 -3
- package/src/config/config-file.ts +30 -12
- package/src/config/model-registry.ts +7 -7
- package/src/config/model-resolver.ts +21 -7
- package/src/config/models-config.ts +1 -1
- package/src/config/settings-schema.ts +10 -0
- package/src/cursor.ts +2 -0
- package/src/discovery/claude-plugins.ts +9 -3
- package/src/discovery/omp-plugins.ts +6 -2
- package/src/discovery/substitute-plugin-root.ts +32 -0
- package/src/eval/__tests__/agent-bridge.test.ts +19 -14
- package/src/eval/__tests__/bridge-timeout.test.ts +106 -0
- package/src/eval/__tests__/js-context-manager.test.ts +158 -1
- package/src/eval/__tests__/kernel-spawn.test.ts +12 -0
- package/src/eval/__tests__/prelude-agent.test.ts +20 -0
- package/src/eval/__tests__/process-entry-import.test.ts +27 -0
- package/src/eval/agent-bridge.ts +121 -116
- package/src/eval/backend.ts +3 -3
- package/src/eval/bridge-timeout.ts +20 -2
- package/src/eval/executor-base.ts +85 -7
- package/src/eval/jl/kernel.ts +2 -1
- package/src/eval/js/context-manager.ts +109 -32
- package/src/eval/js/process-entry.ts +27 -0
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/js/worker-core.ts +70 -9
- package/src/eval/js/worker-entry.ts +1 -1
- package/src/eval/py/__tests__/prelude.test.ts +72 -0
- package/src/eval/py/kernel.ts +2 -1
- package/src/eval/py/prelude.py +28 -1
- package/src/eval/py/spawn-options.ts +13 -0
- package/src/eval/py/tool-bridge.ts +13 -14
- package/src/eval/rb/kernel.ts +2 -1
- package/src/exec/bash-executor.ts +30 -43
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/extensions/wrapper.ts +18 -18
- package/src/extensibility/plugins/bun-git-cache.ts +91 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +32 -16
- package/src/extensibility/plugins/manager.ts +28 -7
- package/src/goals/guided-setup.ts +29 -1
- package/src/internal-urls/history-protocol.ts +95 -15
- package/src/internal-urls/memory-protocol.ts +13 -9
- package/src/internal-urls/registry-helpers.ts +50 -1
- package/src/launch/broker.ts +38 -25
- package/src/lsp/client.ts +7 -1
- package/src/main.ts +29 -0
- package/src/mcp/oauth-discovery.ts +20 -1
- package/src/mcp/oauth-flow.ts +3 -1
- package/src/mcp/tool-bridge.ts +57 -6
- package/src/modes/components/__tests__/dynamic-border.test.ts +55 -0
- package/src/modes/components/agent-dashboard.ts +2 -2
- package/src/modes/components/agent-hub.ts +15 -2
- package/src/modes/components/agent-transcript-viewer.ts +2 -2
- package/src/modes/components/chat-transcript-builder.ts +26 -4
- package/src/modes/components/dynamic-border.ts +9 -6
- package/src/modes/components/extensions/extension-list.ts +2 -2
- package/src/modes/components/hook-selector.ts +10 -4
- package/src/modes/components/login-dialog.ts +5 -0
- package/src/modes/components/mcp-add-wizard.ts +5 -0
- package/src/modes/components/plan-review-overlay.ts +11 -11
- package/src/modes/components/read-tool-group.ts +1 -8
- package/src/modes/components/status-line/component.ts +10 -1
- package/src/modes/components/transcript-container.ts +110 -7
- package/src/modes/controllers/command-controller.ts +12 -4
- package/src/modes/controllers/event-controller.ts +80 -15
- package/src/modes/controllers/input-controller.ts +4 -2
- package/src/modes/controllers/mcp-command-controller.ts +6 -7
- package/src/modes/controllers/selector-controller.ts +24 -5
- package/src/modes/controllers/todo-command-controller.ts +18 -14
- package/src/modes/interactive-mode.ts +7 -3
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/orchestrate.ts +6 -5
- package/src/modes/print-mode.ts +9 -0
- package/src/modes/prompt-action-autocomplete.ts +6 -1
- package/src/modes/rpc/host-tools.ts +15 -0
- package/src/modes/rpc/rpc-mode.ts +123 -48
- package/src/modes/types.ts +1 -1
- package/src/modes/ultrathink.ts +6 -5
- package/src/modes/utils/transcript-render-helpers.ts +54 -0
- package/src/modes/utils/ui-helpers.ts +27 -1
- package/src/modes/workflow.ts +6 -5
- package/src/prompts/advisor/system.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/grep.md +1 -2
- package/src/prompts/tools/read.md +2 -4
- package/src/sdk.ts +61 -34
- package/src/session/agent-session.ts +283 -39
- package/src/session/exit-diagnostics.ts +108 -0
- package/src/session/messages.test.ts +66 -0
- package/src/session/messages.ts +37 -0
- package/src/session/streaming-output.ts +40 -12
- package/src/slash-commands/helpers/active-oauth-account.ts +22 -2
- package/src/slash-commands/helpers/logout.ts +23 -3
- package/src/slash-commands/helpers/usage-report.ts +14 -2
- package/src/subprocess/worker-client.ts +9 -2
- package/src/system-prompt.test.ts +36 -0
- package/src/system-prompt.ts +1 -1
- package/src/task/executor.ts +8 -0
- package/src/task/render.test.ts +36 -0
- package/src/task/render.ts +55 -43
- package/src/tools/bash-skill-urls.ts +4 -1
- package/src/tools/bash.ts +1 -0
- package/src/tools/browser/registry.ts +17 -3
- package/src/tools/eval.ts +14 -9
- package/src/tools/gh.ts +3 -1
- package/src/tools/grep.ts +5 -45
- package/src/tools/path-utils.ts +7 -1
- package/src/tools/read.ts +23 -74
- package/src/tools/write.ts +82 -9
- package/src/tools/yield.ts +29 -1
- package/src/utils/title-generator.ts +10 -6
- package/src/web/search/index.ts +39 -22
- package/src/web/search/provider.ts +33 -16
- package/src/web/search/providers/codex.ts +68 -21
- package/src/web/search/providers/perplexity-auth.ts +20 -11
- package/src/web/search/providers/perplexity.ts +14 -2
|
@@ -153,14 +153,18 @@ import {
|
|
|
153
153
|
AdvisorEmissionGuard,
|
|
154
154
|
type AdvisorMessageDetails,
|
|
155
155
|
type AdvisorNote,
|
|
156
|
+
AdvisorOutputQuarantinedError,
|
|
156
157
|
AdvisorRuntime,
|
|
157
158
|
type AdvisorSeverity,
|
|
158
159
|
AdvisorTranscriptRecorder,
|
|
159
160
|
advisorTranscriptFilename,
|
|
161
|
+
annotateForStaleness,
|
|
162
|
+
buildAdvisorQuarantineSourceText,
|
|
160
163
|
formatAdvisorBatchContent,
|
|
161
164
|
getOrCreateAdvisorProviderSessionId,
|
|
162
165
|
isAdvisorInterruptImmuneTurnActive,
|
|
163
166
|
isInterruptingSeverity,
|
|
167
|
+
quarantineAdvisorUnsafeOutput,
|
|
164
168
|
resolveAdvisorDeliveryChannel,
|
|
165
169
|
slugifyAdvisorName,
|
|
166
170
|
} from "../advisor";
|
|
@@ -183,7 +187,7 @@ import {
|
|
|
183
187
|
resolveModelOverride,
|
|
184
188
|
resolveModelRoleValue,
|
|
185
189
|
} from "../config/model-resolver";
|
|
186
|
-
import { MODEL_ROLE_IDS, MODEL_ROLES } from "../config/model-roles";
|
|
190
|
+
import { getKnownRoleIds, MODEL_ROLE_IDS, MODEL_ROLES } from "../config/model-roles";
|
|
187
191
|
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
|
|
188
192
|
import { buildServiceTierByFamily, serviceTierForAllFamilies, serviceTierSettingToTier } from "../config/service-tier";
|
|
189
193
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
@@ -350,6 +354,7 @@ import {
|
|
|
350
354
|
import { findCompactMode } from "./compact-modes";
|
|
351
355
|
import {
|
|
352
356
|
collectPendingToolCalls,
|
|
357
|
+
createInterruptedTurnAbortMessage,
|
|
353
358
|
SESSION_EXIT_CUSTOM_TYPE,
|
|
354
359
|
type SessionExitData,
|
|
355
360
|
summarizeToolArguments,
|
|
@@ -416,6 +421,59 @@ const MID_RUN_TODO_NUDGE_MUTATING_TOOLS: Record<string, true> = {
|
|
|
416
421
|
write: true,
|
|
417
422
|
ast_edit: true,
|
|
418
423
|
};
|
|
424
|
+
const MARKDOWN_PROMPT_PREFIX_RE = /^(?:>\s*)?(?:(?:[-*+]|\d+[.)])\s+)*/;
|
|
425
|
+
const PROMPT_LABEL_RE = /^(?:q(?:uestion)?|ask)\s*\d*\s*[:.)-]\s*/i;
|
|
426
|
+
const QUESTION_PROMPT_RE =
|
|
427
|
+
/^(?:what|which|when|where|why|how|who|whom|whose|do|does|did|can|could|would|will|should|is|are|am|may|shall)\b/i;
|
|
428
|
+
const USER_DIRECTED_PROMPT_RE = /\b(?:you|your|we|our)\b/i;
|
|
429
|
+
const USER_RESPONSE_CUE_RE =
|
|
430
|
+
/^(?:please\s+)?(?:confirm|reply|choose|pick|decide|advise)\b|^(?:please\s+)?answer\b|^(?:please\s+)?(?:let\s+me\s+know|tell\s+me)\b/i;
|
|
431
|
+
|
|
432
|
+
function assistantText(message: AssistantMessage): string {
|
|
433
|
+
return message.content
|
|
434
|
+
.filter((content): content is TextContent => content.type === "text")
|
|
435
|
+
.map(content => content.text)
|
|
436
|
+
.join("\n")
|
|
437
|
+
.trim();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
interface PromptLine {
|
|
441
|
+
text: string;
|
|
442
|
+
hadPromptLabel: boolean;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function promptLine(line: string): PromptLine {
|
|
446
|
+
const withoutMarkdownPrefix = line.trim().replace(MARKDOWN_PROMPT_PREFIX_RE, "").trim();
|
|
447
|
+
const withoutPromptLabel = withoutMarkdownPrefix.replace(PROMPT_LABEL_RE, "").trim();
|
|
448
|
+
return {
|
|
449
|
+
text: withoutPromptLabel,
|
|
450
|
+
hadPromptLabel: withoutPromptLabel !== withoutMarkdownPrefix,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function isQuestionPromptLine(line: string): boolean {
|
|
455
|
+
const candidate = promptLine(line);
|
|
456
|
+
if (!/[??]\s*$/.test(candidate.text)) return false;
|
|
457
|
+
return (
|
|
458
|
+
candidate.hadPromptLabel ||
|
|
459
|
+
QUESTION_PROMPT_RE.test(candidate.text) ||
|
|
460
|
+
USER_DIRECTED_PROMPT_RE.test(candidate.text)
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function isResponseCueLine(line: string): boolean {
|
|
465
|
+
const candidate = promptLine(line)
|
|
466
|
+
.text.replace(/[.!?。!?]+$/, "")
|
|
467
|
+
.trim();
|
|
468
|
+
return USER_RESPONSE_CUE_RE.test(candidate);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function isAwaitingUserAnswer(message: AssistantMessage): boolean {
|
|
472
|
+
const text = assistantText(message);
|
|
473
|
+
if (!text) return false;
|
|
474
|
+
const lastLine = text.split(/\r?\n/).at(-1)?.trim();
|
|
475
|
+
return lastLine !== undefined && (isQuestionPromptLine(lastLine) || isResponseCueLine(lastLine));
|
|
476
|
+
}
|
|
419
477
|
/** `customType` for the hidden mid-run todo nudge; `display: false`, so it reaches
|
|
420
478
|
* the model but never renders in the TUI or transcript. */
|
|
421
479
|
const MID_RUN_TODO_NUDGE_MESSAGE_TYPE = "mid-run-todo-nudge";
|
|
@@ -1507,6 +1565,21 @@ function isAdvisorCard(message: AgentMessage): message is CustomMessage {
|
|
|
1507
1565
|
return message.role === "custom" && message.customType === "advisor";
|
|
1508
1566
|
}
|
|
1509
1567
|
|
|
1568
|
+
function isTerminalTextAssistantAnswer(message: AgentMessage | undefined): message is AssistantMessage {
|
|
1569
|
+
if (message?.role !== "assistant" || message.stopReason !== "stop") return false;
|
|
1570
|
+
let hasText = false;
|
|
1571
|
+
for (const part of message.content) {
|
|
1572
|
+
if (part.type === "toolCall") return false;
|
|
1573
|
+
if (part.type === "text") {
|
|
1574
|
+
if (part.text.trim().length > 0) hasText = true;
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
if (part.type === "thinking" || part.type === "redactedThinking" || part.type === "fallback") continue;
|
|
1578
|
+
return false;
|
|
1579
|
+
}
|
|
1580
|
+
return hasText;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1510
1583
|
/**
|
|
1511
1584
|
* A queued message the user can restore to the editor / pull back as a draft.
|
|
1512
1585
|
* Only genuinely user-authored messages qualify: plain user turns, or custom
|
|
@@ -1942,6 +2015,7 @@ export class AgentSession {
|
|
|
1942
2015
|
// on `agent_end` can fire its next `prompt` before #promptWithMessage's finally
|
|
1943
2016
|
#emptyStopRetryCount = 0;
|
|
1944
2017
|
#unexpectedStopRetryCount = 0;
|
|
2018
|
+
#acceptTerminalEmptyStopForPrompt = false;
|
|
1945
2019
|
#promptGeneration = 0;
|
|
1946
2020
|
#pendingAgentEndEmit: AgentSessionEvent | undefined;
|
|
1947
2021
|
#pendingContextSnapshot:
|
|
@@ -1982,6 +2056,7 @@ export class AgentSession {
|
|
|
1982
2056
|
this.#emptyStopRetryCount = 0;
|
|
1983
2057
|
this.#unexpectedStopRetryCount = 0;
|
|
1984
2058
|
this.#yieldTerminationPending = false;
|
|
2059
|
+
this.#acceptTerminalEmptyStopForPrompt = false;
|
|
1985
2060
|
}
|
|
1986
2061
|
|
|
1987
2062
|
#acquirePowerAssertion(): void {
|
|
@@ -2518,7 +2593,7 @@ export class AgentSession {
|
|
|
2518
2593
|
this.#advisorPrimaryTurnsCompleted++;
|
|
2519
2594
|
if (this.#advisors.length > 0) {
|
|
2520
2595
|
for (const a of this.#advisors) {
|
|
2521
|
-
if (!a.runtime.disposed) a.runtime.onTurnEnd(messages);
|
|
2596
|
+
if (!a.runtime.disposed) a.runtime.onTurnEnd(messages, { willContinue: context?.willContinue });
|
|
2522
2597
|
}
|
|
2523
2598
|
const syncBacklog = this.settings.get("advisor.syncBacklog");
|
|
2524
2599
|
if (syncBacklog !== "off") {
|
|
@@ -2862,6 +2937,14 @@ export class AgentSession {
|
|
|
2862
2937
|
|
|
2863
2938
|
const names = config.tools === undefined ? ADVISOR_DEFAULT_TOOL_NAMES : new Set(config.tools);
|
|
2864
2939
|
const tools = (this.#advisorTools ?? []).filter(t => names.has(t.name));
|
|
2940
|
+
const availableAdvisorToolNames = new Set<string>();
|
|
2941
|
+
availableAdvisorToolNames.add(adviseTool.name);
|
|
2942
|
+
for (const tool of tools) {
|
|
2943
|
+
availableAdvisorToolNames.add(tool.name);
|
|
2944
|
+
if (tool.customWireName !== undefined) availableAdvisorToolNames.add(tool.customWireName);
|
|
2945
|
+
}
|
|
2946
|
+
let quarantinedAdvisorOutput: string | undefined;
|
|
2947
|
+
let currentAdvisorInput = "";
|
|
2865
2948
|
|
|
2866
2949
|
const primaryProviderSessionId = this.sessionId;
|
|
2867
2950
|
const advisorSessionLabel = slug
|
|
@@ -2917,6 +3000,13 @@ export class AgentSession {
|
|
|
2917
3000
|
onSseEvent: this.#onSseEvent,
|
|
2918
3001
|
transformProviderContext: this.#transformProviderContext,
|
|
2919
3002
|
intentTracing: false,
|
|
3003
|
+
transformAssistantMessage: message => {
|
|
3004
|
+
quarantinedAdvisorOutput = quarantineAdvisorUnsafeOutput(
|
|
3005
|
+
message,
|
|
3006
|
+
availableAdvisorToolNames,
|
|
3007
|
+
buildAdvisorQuarantineSourceText(currentAdvisorInput, advisorAgent.state.messages),
|
|
3008
|
+
);
|
|
3009
|
+
},
|
|
2920
3010
|
telemetry: advisorTelemetry,
|
|
2921
3011
|
serviceTier: undefined,
|
|
2922
3012
|
serviceTierResolver: advisorServiceTierResolver,
|
|
@@ -2924,7 +3014,19 @@ export class AgentSession {
|
|
|
2924
3014
|
advisorAgent.setDisableReasoning(shouldDisableReasoning(advisorThinkingLevel));
|
|
2925
3015
|
|
|
2926
3016
|
const advisorAgentFacade: AdvisorAgent = {
|
|
2927
|
-
prompt: input =>
|
|
3017
|
+
prompt: async input => {
|
|
3018
|
+
let quarantined: string | undefined;
|
|
3019
|
+
try {
|
|
3020
|
+
quarantinedAdvisorOutput = undefined;
|
|
3021
|
+
currentAdvisorInput = input;
|
|
3022
|
+
await advisorAgent.prompt(input);
|
|
3023
|
+
quarantined = quarantinedAdvisorOutput;
|
|
3024
|
+
} finally {
|
|
3025
|
+
quarantinedAdvisorOutput = undefined;
|
|
3026
|
+
currentAdvisorInput = "";
|
|
3027
|
+
}
|
|
3028
|
+
if (quarantined) throw new AdvisorOutputQuarantinedError(quarantined);
|
|
3029
|
+
},
|
|
2928
3030
|
abort: reason => advisorAgent.abort(reason),
|
|
2929
3031
|
reset: () => {
|
|
2930
3032
|
advisorAgent.reset();
|
|
@@ -3034,18 +3136,33 @@ export class AgentSession {
|
|
|
3034
3136
|
/**
|
|
3035
3137
|
* Route one accepted advice note from `advisor` to the primary. Concern and
|
|
3036
3138
|
* blocker interrupt the running agent through the steering channel; once the
|
|
3037
|
-
* loop has yielded, `triggerTurn` resumes it.
|
|
3038
|
-
*
|
|
3039
|
-
* card
|
|
3040
|
-
*
|
|
3041
|
-
*
|
|
3042
|
-
*
|
|
3139
|
+
* loop has yielded, `triggerTurn` resumes it. If the loop already ended with a
|
|
3140
|
+
* terminal text answer and no queued work remains, the note is preserved as an
|
|
3141
|
+
* advisor card instead of waking a duplicate completion turn. After a deliberate
|
|
3142
|
+
* user interrupt auto-resume is suppressed while idle/unwinding (the note
|
|
3143
|
+
* becomes a preserved card re-entering on resume); a live-streaming turn is
|
|
3144
|
+
* steered in directly. A plain nit always rides the non-interrupting YieldQueue
|
|
3145
|
+
* aside. Suppression by the per-advisor emission guard drops the note silently —
|
|
3146
|
+
* the model still saw `Recorded.`, so it isn't tempted to rephrase the same note
|
|
3147
|
+
* past the dedupe.
|
|
3043
3148
|
*/
|
|
3149
|
+
#hasTerminalTextAnswerWithoutQueuedWork(): boolean {
|
|
3150
|
+
if (this.agent.hasQueuedMessages() || this.#pendingNextTurnMessages.length > 0) return false;
|
|
3151
|
+
const messages = this.agent.state.messages;
|
|
3152
|
+
let tail = messages.length - 1;
|
|
3153
|
+
while (tail >= 0 && isAdvisorCard(messages[tail])) tail--;
|
|
3154
|
+
return isTerminalTextAssistantAnswer(messages[tail]);
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3044
3157
|
#routeAdvice(advisor: ActiveAdvisor, note: string, severity?: AdvisorSeverity): void {
|
|
3045
3158
|
if (!advisor.emissionGuard.accept(note)) {
|
|
3046
3159
|
logger.debug("advisor advice suppressed by emission guard", { severity, advisor: advisor.name });
|
|
3047
3160
|
return;
|
|
3048
3161
|
}
|
|
3162
|
+
// When newer primary turns already arrived while the advisor model was
|
|
3163
|
+
// processing this batch, the advice was generated without seeing them.
|
|
3164
|
+
// Append a lightweight staleness caveat so the primary can weigh recency.
|
|
3165
|
+
const deliveredNote = annotateForStaleness(note, advisor.runtime.hasFreshBacklog);
|
|
3049
3166
|
// The implicit single ("default") advisor stamps no source name, so its
|
|
3050
3167
|
// agent-facing `<advisory>` bytes stay identical to the pre-multi-advisor path.
|
|
3051
3168
|
const source = advisor.slug ? advisor.name : undefined;
|
|
@@ -3058,13 +3175,14 @@ export class AgentSession {
|
|
|
3058
3175
|
// loop consumes a steer at its next boundary.
|
|
3059
3176
|
streaming: this.agent.state.isStreaming,
|
|
3060
3177
|
aborting: this.#abortInProgress,
|
|
3178
|
+
terminalAnswerNoQueuedWork: this.#hasTerminalTextAnswerWithoutQueuedWork(),
|
|
3061
3179
|
interruptImmuneTurnActive: interrupting && this.#isAdvisorInterruptImmuneTurnActive(),
|
|
3062
3180
|
});
|
|
3063
3181
|
if (channel === "aside") {
|
|
3064
|
-
this.yieldQueue.enqueue("advisor", { note, severity, advisor: source });
|
|
3182
|
+
this.yieldQueue.enqueue("advisor", { note: deliveredNote, severity, advisor: source });
|
|
3065
3183
|
return;
|
|
3066
3184
|
}
|
|
3067
|
-
const notes: AdvisorNote[] = [{ note, severity, advisor: source }];
|
|
3185
|
+
const notes: AdvisorNote[] = [{ note: deliveredNote, severity, advisor: source }];
|
|
3068
3186
|
const content = formatAdvisorBatchContent(notes);
|
|
3069
3187
|
const details = { notes } satisfies AdvisorMessageDetails;
|
|
3070
3188
|
if (channel === "preserve") {
|
|
@@ -4527,7 +4645,7 @@ export class AgentSession {
|
|
|
4527
4645
|
await emitAgentEndNotification();
|
|
4528
4646
|
return;
|
|
4529
4647
|
}
|
|
4530
|
-
const todoContinuationScheduled = await this.#checkTodoCompletion();
|
|
4648
|
+
const todoContinuationScheduled = await this.#checkTodoCompletion(msg);
|
|
4531
4649
|
if (todoContinuationScheduled) {
|
|
4532
4650
|
await emitAgentEndNotification();
|
|
4533
4651
|
return;
|
|
@@ -7124,6 +7242,8 @@ export class AgentSession {
|
|
|
7124
7242
|
abort: () => {
|
|
7125
7243
|
this.agent.abort();
|
|
7126
7244
|
},
|
|
7245
|
+
settings: this.settings,
|
|
7246
|
+
localProtocolOptions: this.#localProtocolOptions(),
|
|
7127
7247
|
});
|
|
7128
7248
|
|
|
7129
7249
|
for (const customTool of mcpTools) {
|
|
@@ -7668,6 +7788,30 @@ export class AgentSession {
|
|
|
7668
7788
|
return this.#resolveRoleModelFull(role, this.#modelRegistry.getAvailable(), this.model);
|
|
7669
7789
|
}
|
|
7670
7790
|
|
|
7791
|
+
/**
|
|
7792
|
+
* Resolve the explicit thinking suffix that should apply when a temporary
|
|
7793
|
+
* picker selects a model already assigned to a configured role.
|
|
7794
|
+
*/
|
|
7795
|
+
resolveTemporaryModelThinkingLevel(model: Model): ConfiguredThinkingLevel | undefined {
|
|
7796
|
+
const availableModels = this.#modelRegistry.getAvailable();
|
|
7797
|
+
if (availableModels.length === 0) return undefined;
|
|
7798
|
+
|
|
7799
|
+
const matchPreferences = getModelMatchPreferences(this.settings);
|
|
7800
|
+
for (const role of getKnownRoleIds(this.settings)) {
|
|
7801
|
+
const roleValue = this.settings.getModelRole(role);
|
|
7802
|
+
if (!roleValue) continue;
|
|
7803
|
+
|
|
7804
|
+
const resolved = resolveModelRoleValue(roleValue, availableModels, {
|
|
7805
|
+
settings: this.settings,
|
|
7806
|
+
matchPreferences,
|
|
7807
|
+
});
|
|
7808
|
+
if (!resolved.explicitThinkingLevel || resolved.thinkingLevel === undefined || !resolved.model) continue;
|
|
7809
|
+
if (modelsAreEqual(resolved.model, model)) return resolved.thinkingLevel;
|
|
7810
|
+
}
|
|
7811
|
+
|
|
7812
|
+
return undefined;
|
|
7813
|
+
}
|
|
7814
|
+
|
|
7671
7815
|
get promptTemplates(): ReadonlyArray<PromptTemplate> {
|
|
7672
7816
|
return this.#promptTemplates;
|
|
7673
7817
|
}
|
|
@@ -8172,6 +8316,7 @@ export class AgentSession {
|
|
|
8172
8316
|
options?: Pick<PromptOptions, "toolChoice" | "images" | "skipCompactionCheck"> & {
|
|
8173
8317
|
prependMessages?: AgentMessage[];
|
|
8174
8318
|
skipPostPromptRecoveryWait?: boolean;
|
|
8319
|
+
acceptTerminalEmptyStop?: boolean;
|
|
8175
8320
|
},
|
|
8176
8321
|
): Promise<void> {
|
|
8177
8322
|
this.#beginInFlight();
|
|
@@ -8188,6 +8333,7 @@ export class AgentSession {
|
|
|
8188
8333
|
this.#mutationsSinceLastTodoTouch = 0;
|
|
8189
8334
|
this.#midRunNudgeCount = 0;
|
|
8190
8335
|
this.#resetPromptMaintenanceState();
|
|
8336
|
+
this.#acceptTerminalEmptyStopForPrompt = options?.acceptTerminalEmptyStop === true;
|
|
8191
8337
|
|
|
8192
8338
|
await this.#maybeRestoreRetryFallbackPrimary();
|
|
8193
8339
|
|
|
@@ -8729,12 +8875,21 @@ export class AgentSession {
|
|
|
8729
8875
|
}
|
|
8730
8876
|
}
|
|
8731
8877
|
|
|
8732
|
-
async #promptAgentInitiatedMessage(
|
|
8878
|
+
async #promptAgentInitiatedMessage(
|
|
8879
|
+
message: CustomMessage,
|
|
8880
|
+
options?: { acceptTerminalEmptyStop?: boolean },
|
|
8881
|
+
): Promise<void> {
|
|
8733
8882
|
this.#beginInFlight();
|
|
8734
8883
|
try {
|
|
8884
|
+
const acceptTerminalEmptyStop = options?.acceptTerminalEmptyStop === true;
|
|
8885
|
+
if (acceptTerminalEmptyStop) {
|
|
8886
|
+
this.#resetPromptMaintenanceState();
|
|
8887
|
+
}
|
|
8888
|
+
this.#acceptTerminalEmptyStopForPrompt = acceptTerminalEmptyStop;
|
|
8735
8889
|
await this.agent.prompt(message);
|
|
8736
8890
|
await this.#waitForPostPromptRecovery();
|
|
8737
8891
|
} finally {
|
|
8892
|
+
this.#acceptTerminalEmptyStopForPrompt = false;
|
|
8738
8893
|
this.#endInFlight();
|
|
8739
8894
|
}
|
|
8740
8895
|
}
|
|
@@ -8789,7 +8944,12 @@ export class AgentSession {
|
|
|
8789
8944
|
*/
|
|
8790
8945
|
async sendCustomMessage<T = unknown>(
|
|
8791
8946
|
message: CustomMessagePayload<T>,
|
|
8792
|
-
options?: {
|
|
8947
|
+
options?: {
|
|
8948
|
+
triggerTurn?: boolean;
|
|
8949
|
+
deliverAs?: "steer" | "followUp" | "nextTurn";
|
|
8950
|
+
queueChipText?: string;
|
|
8951
|
+
acceptTerminalEmptyStop?: boolean;
|
|
8952
|
+
},
|
|
8793
8953
|
): Promise<boolean> {
|
|
8794
8954
|
const normalizedPayload = normalizeCustomMessagePayload<T>(message);
|
|
8795
8955
|
const details =
|
|
@@ -8832,7 +8992,9 @@ export class AgentSession {
|
|
|
8832
8992
|
this.#queueHiddenNextTurnMessage(normalizedAppMessage, false);
|
|
8833
8993
|
return false;
|
|
8834
8994
|
}
|
|
8835
|
-
await this.#promptAgentInitiatedMessage(normalizedAppMessage
|
|
8995
|
+
await this.#promptAgentInitiatedMessage(normalizedAppMessage, {
|
|
8996
|
+
acceptTerminalEmptyStop: options.acceptTerminalEmptyStop === true,
|
|
8997
|
+
});
|
|
8836
8998
|
return true;
|
|
8837
8999
|
}
|
|
8838
9000
|
this.agent.appendMessage(normalizedAppMessage);
|
|
@@ -10269,24 +10431,37 @@ export class AgentSession {
|
|
|
10269
10431
|
|
|
10270
10432
|
// Strategy honored on manual /compact too. Custom instructions (public
|
|
10271
10433
|
// user focus OR internal plan-mode guidance) imply a directed LLM
|
|
10272
|
-
// summary; a text-only model cannot read snapcompact frames.
|
|
10273
|
-
// snapcompact itself was requested, fail locally instead of silently
|
|
10274
|
-
// converting the "no LLM call" path into a provider-backed summary.
|
|
10434
|
+
// summary; a text-only model cannot read snapcompact frames.
|
|
10275
10435
|
const wantsSnapcompact =
|
|
10276
10436
|
compactionPrep.kind !== "fromHook" &&
|
|
10277
10437
|
effectiveSettings.strategy === "snapcompact" &&
|
|
10278
10438
|
!customInstructions &&
|
|
10279
10439
|
!options?.internalGuidance;
|
|
10280
|
-
|
|
10440
|
+
// `/compact snapcompact` is an explicit no-LLM archive request: honor
|
|
10441
|
+
// its contract by failing locally rather than silently shipping the
|
|
10442
|
+
// transcript to a provider. The default-configured snapcompact
|
|
10443
|
+
// strategy, in contrast, falls back to LLM compaction (mirroring the
|
|
10444
|
+
// auto-compaction path) so a routine /compact still completes on a
|
|
10445
|
+
// text-only model (issue #5064).
|
|
10446
|
+
const explicitSnapcompact = compactMode?.name === "snapcompact";
|
|
10447
|
+
let snapcompactReady = wantsSnapcompact;
|
|
10281
10448
|
const snapcompactShapeSetting = this.settings.get("snapcompact.shape");
|
|
10282
10449
|
let snapcompactShape: snapcompact.Shape | undefined;
|
|
10283
10450
|
if (wantsSnapcompact && !this.model.input.includes("image")) {
|
|
10451
|
+
if (explicitSnapcompact) {
|
|
10452
|
+
this.emitNotice(
|
|
10453
|
+
"warning",
|
|
10454
|
+
`snapcompact needs a vision-capable model (${this.model.id} is text-only)`,
|
|
10455
|
+
"compaction",
|
|
10456
|
+
);
|
|
10457
|
+
throw new Error(`snapcompact cannot run locally: ${this.model.id} is text-only.`);
|
|
10458
|
+
}
|
|
10284
10459
|
this.emitNotice(
|
|
10285
10460
|
"warning",
|
|
10286
|
-
`snapcompact needs a vision-capable model (${this.model.id} is text-only)`,
|
|
10461
|
+
`snapcompact needs a vision-capable model (${this.model.id} is text-only); falling back to LLM compaction`,
|
|
10287
10462
|
"compaction",
|
|
10288
10463
|
);
|
|
10289
|
-
|
|
10464
|
+
snapcompactReady = false;
|
|
10290
10465
|
} else if (snapcompactReady) {
|
|
10291
10466
|
const text = snapcompact.serializeConversation(
|
|
10292
10467
|
convertToLlm(preparation.messagesToSummarize.concat(preparation.turnPrefixMessages)),
|
|
@@ -11344,6 +11519,13 @@ export class AgentSession {
|
|
|
11344
11519
|
return false;
|
|
11345
11520
|
}
|
|
11346
11521
|
|
|
11522
|
+
if (this.#acceptTerminalEmptyStopForPrompt && assistantMessage.stopReason === "stop") {
|
|
11523
|
+
this.#acceptTerminalEmptyStopForPrompt = false;
|
|
11524
|
+
this.#discardAcceptedTerminalEmptyStop(assistantMessage);
|
|
11525
|
+
this.#emptyStopRetryCount = 0;
|
|
11526
|
+
return false;
|
|
11527
|
+
}
|
|
11528
|
+
|
|
11347
11529
|
this.#emptyStopRetryCount++;
|
|
11348
11530
|
if (this.#emptyStopRetryCount > EMPTY_STOP_MAX_RETRIES) {
|
|
11349
11531
|
const attempts = this.#emptyStopRetryCount - 1;
|
|
@@ -11561,6 +11743,38 @@ export class AgentSession {
|
|
|
11561
11743
|
this.agent.appendMessage(assistantMessage);
|
|
11562
11744
|
}
|
|
11563
11745
|
|
|
11746
|
+
#discardAcceptedTerminalEmptyStop(assistantMessage: AssistantMessage): void {
|
|
11747
|
+
const branch = this.sessionManager.getBranch();
|
|
11748
|
+
const branchEntry = branch
|
|
11749
|
+
.slice()
|
|
11750
|
+
.reverse()
|
|
11751
|
+
.find(
|
|
11752
|
+
entry =>
|
|
11753
|
+
entry.type === "message" &&
|
|
11754
|
+
entry.message.role === "assistant" &&
|
|
11755
|
+
this.#isSameAssistantMessage(entry.message, assistantMessage),
|
|
11756
|
+
);
|
|
11757
|
+
const parentEntry =
|
|
11758
|
+
branchEntry?.parentId === null || branchEntry?.parentId === undefined
|
|
11759
|
+
? undefined
|
|
11760
|
+
: branch.find(entry => entry.id === branchEntry.parentId);
|
|
11761
|
+
const prunePrompt = parentEntry?.type === "custom_message";
|
|
11762
|
+
|
|
11763
|
+
this.#removeAssistantMessageFromActiveContext(assistantMessage, "accepted-terminal-empty-stop");
|
|
11764
|
+
if (prunePrompt && this.agent.state.messages.at(-1)?.role === "custom") {
|
|
11765
|
+
this.agent.replaceMessages(this.agent.state.messages.slice(0, -1));
|
|
11766
|
+
}
|
|
11767
|
+
|
|
11768
|
+
if (!branchEntry) return;
|
|
11769
|
+
const targetParentId = prunePrompt ? parentEntry.parentId : branchEntry.parentId;
|
|
11770
|
+
if (targetParentId === null) {
|
|
11771
|
+
this.sessionManager.resetLeaf();
|
|
11772
|
+
} else {
|
|
11773
|
+
this.sessionManager.branch(targetParentId);
|
|
11774
|
+
}
|
|
11775
|
+
this.sessionManager.appendCustomEntry("accepted-terminal-empty-stop");
|
|
11776
|
+
}
|
|
11777
|
+
|
|
11564
11778
|
/**
|
|
11565
11779
|
* Drop an assistant turn from BOTH the live agent context and the persisted
|
|
11566
11780
|
* session branch (reparenting the leaf to the turn's parent), so a discarded
|
|
@@ -11890,7 +12104,7 @@ export class AgentSession {
|
|
|
11890
12104
|
/**
|
|
11891
12105
|
* Check if agent stopped with incomplete todos and prompt to continue.
|
|
11892
12106
|
*/
|
|
11893
|
-
async #checkTodoCompletion(): Promise<boolean> {
|
|
12107
|
+
async #checkTodoCompletion(message: AssistantMessage): Promise<boolean> {
|
|
11894
12108
|
// Skip todo reminders when the most recent turn was driven by an explicit user force —
|
|
11895
12109
|
// the user wanted exactly that tool, not a follow-up nag about incomplete todos.
|
|
11896
12110
|
const lastServedLabel = this.#toolChoiceQueue.consumeLastServedLabel();
|
|
@@ -11955,6 +12169,13 @@ export class AgentSession {
|
|
|
11955
12169
|
return false;
|
|
11956
12170
|
}
|
|
11957
12171
|
|
|
12172
|
+
if (isAwaitingUserAnswer(message)) {
|
|
12173
|
+
logger.debug("Todo completion: assistant is waiting for user input; skipping reminder", {
|
|
12174
|
+
incomplete: incomplete.length,
|
|
12175
|
+
});
|
|
12176
|
+
return false;
|
|
12177
|
+
}
|
|
12178
|
+
|
|
11958
12179
|
// Background async jobs (bash/task) owned by this agent re-wake the loop
|
|
11959
12180
|
// when they complete: the result delivery enqueues an async-result
|
|
11960
12181
|
// follow-up that continues the run, and todos are re-evaluated at that
|
|
@@ -13829,19 +14050,23 @@ export class AgentSession {
|
|
|
13829
14050
|
}
|
|
13830
14051
|
|
|
13831
14052
|
/**
|
|
13832
|
-
* Retry an empty, reason-less provider abort: a turn
|
|
13833
|
-
*
|
|
13834
|
-
*
|
|
13835
|
-
*
|
|
13836
|
-
* session
|
|
13837
|
-
*
|
|
13838
|
-
*
|
|
13839
|
-
* `#
|
|
13840
|
-
*
|
|
14053
|
+
* Retry an empty, reason-less provider abort: a turn with no content that
|
|
14054
|
+
* carries the generic sentinel (bare `abort()`), whether the provider
|
|
14055
|
+
* finalized it as `stopReason: "aborted"` or leaked it as `stopReason:
|
|
14056
|
+
* "error"` (a stalled/dropped stream reported as an error rather than an
|
|
14057
|
+
* abort — issue #5375). Only fires while the session is neither aborting nor
|
|
14058
|
+
* tearing down. A user/lifecycle abort (`#abortInProgress`), a dispose-driven
|
|
14059
|
+
* abort (`#isDisposed`), or a session-induced streaming-edit guard abort
|
|
14060
|
+
* (`#streamingEditAbortTriggered` — auto-generated-file guard or failed-patch
|
|
14061
|
+
* preview) is deliberate and MUST settle the turn instead: routing it through
|
|
14062
|
+
* retry would orphan `#retryPromise` on a continuation the guard skips
|
|
14063
|
+
* (hanging the in-flight `prompt()`) or silently undo the guard's intended
|
|
14064
|
+
* abort. Deliberate user interrupts (`UserInterrupt`) and silent aborts carry
|
|
14065
|
+
* their own marker, not the generic sentinel, so they never match here.
|
|
13841
14066
|
*/
|
|
13842
14067
|
#isRetryableReasonlessAbort(message: AssistantMessage): boolean {
|
|
13843
14068
|
if (
|
|
13844
|
-
message.stopReason !== "aborted" ||
|
|
14069
|
+
(message.stopReason !== "aborted" && message.stopReason !== "error") ||
|
|
13845
14070
|
message.content.length !== 0 ||
|
|
13846
14071
|
this.#abortInProgress ||
|
|
13847
14072
|
this.#isDisposed ||
|
|
@@ -13851,7 +14076,7 @@ export class AgentSession {
|
|
|
13851
14076
|
}
|
|
13852
14077
|
|
|
13853
14078
|
const id = this.#classifyRetryMessage(message);
|
|
13854
|
-
if (AIError.is(id, AIError.Flag.Abort)) return true;
|
|
14079
|
+
if (message.stopReason === "aborted" && AIError.is(id, AIError.Flag.Abort)) return true;
|
|
13855
14080
|
if (!this.#isGenericAbortSentinel(message)) return false;
|
|
13856
14081
|
|
|
13857
14082
|
message.errorId = AIError.create(AIError.Flag.Abort);
|
|
@@ -15521,7 +15746,7 @@ export class AgentSession {
|
|
|
15521
15746
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
15522
15747
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
15523
15748
|
|
|
15524
|
-
|
|
15749
|
+
let sessionContext = this.buildDisplaySessionContext();
|
|
15525
15750
|
const didReloadConversationChange =
|
|
15526
15751
|
previousSessionContext !== undefined &&
|
|
15527
15752
|
this.#didSessionMessagesChange(previousSessionContext.messages, sessionContext.messages);
|
|
@@ -15579,6 +15804,20 @@ export class AgentSession {
|
|
|
15579
15804
|
}
|
|
15580
15805
|
}
|
|
15581
15806
|
|
|
15807
|
+
const model = this.model;
|
|
15808
|
+
if (model) {
|
|
15809
|
+
const interruptedTurnAbort = createInterruptedTurnAbortMessage(this.sessionManager.getBranch(), {
|
|
15810
|
+
api: model.api,
|
|
15811
|
+
provider: model.provider,
|
|
15812
|
+
model: model.id,
|
|
15813
|
+
});
|
|
15814
|
+
if (interruptedTurnAbort) {
|
|
15815
|
+
this.sessionManager.appendMessage(interruptedTurnAbort);
|
|
15816
|
+
sessionContext = this.buildDisplaySessionContext();
|
|
15817
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
15818
|
+
}
|
|
15819
|
+
}
|
|
15820
|
+
|
|
15582
15821
|
const hasThinkingEntry = this.sessionManager.getBranch().some(entry => entry.type === "thinking_level_change");
|
|
15583
15822
|
const hasServiceTierEntry = this.sessionManager
|
|
15584
15823
|
.getBranch()
|
|
@@ -15993,7 +16232,7 @@ export class AgentSession {
|
|
|
15993
16232
|
// User message: leaf = parent (null if root), text goes to editor
|
|
15994
16233
|
newLeafId = targetEntry.parentId;
|
|
15995
16234
|
editorText = this.#extractUserMessageText(targetEntry.message.content);
|
|
15996
|
-
} else if (targetEntry.type === "custom_message") {
|
|
16235
|
+
} else if (targetEntry.type === "custom_message" && targetEntry.customType !== SKILL_PROMPT_MESSAGE_TYPE) {
|
|
15997
16236
|
// Custom message: leaf = parent (null if root), text goes to editor
|
|
15998
16237
|
newLeafId = targetEntry.parentId;
|
|
15999
16238
|
editorText =
|
|
@@ -16004,7 +16243,10 @@ export class AgentSession {
|
|
|
16004
16243
|
.map(c => c.text)
|
|
16005
16244
|
.join("");
|
|
16006
16245
|
} else {
|
|
16007
|
-
// Non-user message
|
|
16246
|
+
// Non-user message (or a user-invoked skill-prompt injection): land the
|
|
16247
|
+
// leaf on the selected node so it stays on the active branch. Skill
|
|
16248
|
+
// prompts are custom_message entries but must not be re-editable — their
|
|
16249
|
+
// content is a large expanded body, not a user turn (issue #5374).
|
|
16008
16250
|
newLeafId = targetId;
|
|
16009
16251
|
}
|
|
16010
16252
|
|
|
@@ -16357,10 +16599,12 @@ export class AgentSession {
|
|
|
16357
16599
|
}
|
|
16358
16600
|
|
|
16359
16601
|
#ingestProviderUsageHeaders(response: ProviderResponseMetadata, model?: Model): void {
|
|
16360
|
-
|
|
16361
|
-
|
|
16602
|
+
const provider = model?.provider;
|
|
16603
|
+
if (!provider) return;
|
|
16604
|
+
// No-op for providers whose usage strategy lacks a header parser.
|
|
16605
|
+
this.#modelRegistry.authStorage.ingestUsageHeaders(provider, response.headers, {
|
|
16362
16606
|
sessionId: this.agent.sessionId,
|
|
16363
|
-
baseUrl: this.#modelRegistry.getProviderBaseUrl?.(
|
|
16607
|
+
baseUrl: this.#modelRegistry.getProviderBaseUrl?.(provider),
|
|
16364
16608
|
});
|
|
16365
16609
|
}
|
|
16366
16610
|
|