@oh-my-pi/pi-coding-agent 17.2.10 → 17.2.12
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 +53 -0
- package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-k9ghy5sn.md} +53 -0
- package/dist/cli.js +10775 -10762
- package/dist/types/advisor/runtime.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +11 -0
- package/dist/types/capability/skill.d.ts +6 -0
- package/dist/types/cli/command-help.d.ts +3 -0
- package/dist/types/commands/share.d.ts +25 -0
- package/dist/types/commit/agentic/index.d.ts +3 -1
- package/dist/types/commit/execute.d.ts +32 -0
- package/dist/types/commit/index.d.ts +2 -1
- package/dist/types/commit/pipeline.d.ts +7 -1
- package/dist/types/config/custom-models.d.ts +31 -0
- package/dist/types/config/model-config-values.d.ts +19 -0
- package/dist/types/config/model-patch.d.ts +93 -0
- package/dist/types/config/model-provider-discovery.d.ts +51 -0
- package/dist/types/config/model-registry.d.ts +9 -52
- package/dist/types/config/settings-schema.d.ts +1 -34
- package/dist/types/config/settings.d.ts +5 -3
- package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
- package/dist/types/discovery/agent-plugin-format.d.ts +136 -0
- package/dist/types/discovery/agent-plugins.d.ts +1 -0
- package/dist/types/discovery/contained-path.d.ts +33 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/eval/executor-base.d.ts +8 -2
- package/dist/types/eval/kernel-session-registry.d.ts +60 -0
- package/dist/types/export/share.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -2
- package/dist/types/extensibility/skills.d.ts +5 -0
- package/dist/types/lsp/diagnostics.d.ts +96 -0
- package/dist/types/lsp/index.d.ts +7 -128
- package/dist/types/lsp/servers.d.ts +72 -0
- package/dist/types/lsp/tool.d.ts +42 -0
- package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
- package/dist/types/lsp/writethrough.d.ts +33 -0
- package/dist/types/mcp/transports/header-policy.d.ts +46 -0
- package/dist/types/mcp/types.d.ts +15 -0
- package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
- package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
- package/dist/types/modes/components/agent-hub.d.ts +2 -0
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -2
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/theme/color.d.ts +19 -0
- package/dist/types/modes/theme/loader.d.ts +19 -0
- package/dist/types/modes/theme/schema.d.ts +175 -0
- package/dist/types/modes/theme/symbols.d.ts +28 -0
- package/dist/types/modes/theme/theme-class.d.ts +244 -0
- package/dist/types/modes/theme/theme.d.ts +9 -280
- package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
- package/dist/types/registry/agent-registry.d.ts +1 -3
- package/dist/types/secrets/index.d.ts +26 -2
- package/dist/types/secrets/message-transform.d.ts +40 -0
- package/dist/types/secrets/obfuscator.d.ts +0 -108
- package/dist/types/secrets/placeholder-scan.d.ts +95 -0
- package/dist/types/secrets/placeholder.d.ts +94 -0
- package/dist/types/secrets/replacement.d.ts +82 -0
- package/dist/types/session/agent-session-events.d.ts +2 -2
- package/dist/types/session/agent-session-types.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +2 -2
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/prewalk.d.ts +3 -1
- package/dist/types/session/session-handoff.d.ts +3 -3
- package/dist/types/session/session-manager.d.ts +32 -0
- package/dist/types/session/session-provider-boundary.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +8 -0
- package/dist/types/session/turn-recovery.d.ts +9 -4
- package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
- package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
- package/dist/types/slash-commands/builtin-control.d.ts +2 -0
- package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
- package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
- package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/slash-commands/builtin-session.d.ts +2 -0
- package/dist/types/task/index.d.ts +2 -0
- package/dist/types/tools/gh-common.d.ts +69 -0
- package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
- package/dist/types/tools/gh-pr-diff.d.ts +102 -0
- package/dist/types/tools/gh-run-watch.d.ts +78 -0
- package/dist/types/tools/gh-search.d.ts +61 -0
- package/dist/types/tools/gh-types.d.ts +340 -0
- package/dist/types/tools/gh-view.d.ts +88 -0
- package/dist/types/tools/gh.d.ts +4 -201
- package/dist/types/tools/read-archive.d.ts +16 -0
- package/dist/types/tools/read-format.d.ts +104 -0
- package/dist/types/tools/read-path-resolution.d.ts +19 -0
- package/dist/types/tools/read-pdf-images.d.ts +12 -0
- package/dist/types/tools/read-renderer.d.ts +24 -0
- package/dist/types/tools/read-selector.d.ts +27 -0
- package/dist/types/tools/read-sqlite.d.ts +16 -0
- package/dist/types/tools/read-summary.d.ts +19 -0
- package/dist/types/tools/read.d.ts +1 -23
- package/dist/types/tools/shell-tokenize.d.ts +18 -1
- package/dist/types/utils/changelog.d.ts +0 -5
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +4 -1
- package/src/advisor/runtime.ts +12 -7
- package/src/capability/mcp.ts +11 -0
- package/src/capability/skill.ts +6 -0
- package/src/cli/command-help.ts +4 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli-commands.ts +5 -0
- package/src/commands/commit.ts +16 -3
- package/src/commands/share.ts +71 -0
- package/src/commit/agentic/index.ts +39 -21
- package/src/commit/execute.ts +56 -0
- package/src/commit/index.ts +2 -1
- package/src/commit/pipeline.ts +20 -7
- package/src/config/custom-models.ts +188 -0
- package/src/config/model-config-values.ts +128 -0
- package/src/config/model-discovery.ts +1 -1
- package/src/config/model-patch.ts +252 -0
- package/src/config/model-provider-discovery.ts +132 -0
- package/src/config/model-registry.ts +84 -704
- package/src/config/settings-schema.ts +2 -33
- package/src/config/settings.ts +53 -3
- package/src/debug/raw-sse-buffer.ts +20 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/claude-plugins.ts +21 -5
- package/src/discovery/contained-path.ts +75 -0
- package/src/discovery/helpers.ts +23 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/omp-plugins.ts +20 -7
- package/src/edit/hashline/diff.ts +5 -1
- package/src/eval/executor-base.ts +39 -6
- package/src/eval/jl/executor.ts +82 -371
- package/src/eval/kernel-session-registry.ts +398 -0
- package/src/eval/py/executor.ts +53 -261
- package/src/eval/rb/executor.ts +36 -279
- package/src/exec/non-interactive-env.ts +1 -0
- package/src/export/share.ts +2 -1
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +78 -14
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +12 -0
- package/src/extensibility/plugins/marketplace/manager.ts +2 -1
- package/src/extensibility/shared-events.ts +3 -2
- package/src/extensibility/skills.ts +9 -0
- package/src/internal-urls/skill-protocol.ts +14 -0
- package/src/internal-urls/vault-protocol.ts +2 -2
- package/src/launch/client.ts +11 -1
- package/src/launch/protocol.ts +7 -2
- package/src/lsp/diagnostics.ts +516 -0
- package/src/lsp/index.ts +20 -2819
- package/src/lsp/servers.ts +296 -0
- package/src/lsp/tool.ts +1352 -0
- package/src/lsp/workspace-diagnostics.ts +170 -0
- package/src/lsp/writethrough.ts +561 -0
- package/src/main.ts +3 -2
- package/src/mcp/config.ts +3 -0
- package/src/mcp/manager.ts +12 -9
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +35 -52
- package/src/mcp/transports/sse.ts +20 -27
- package/src/mcp/types.ts +15 -0
- package/src/modes/acp/acp-agent.ts +15 -4
- package/src/modes/components/agent-dashboard.ts +2 -0
- package/src/modes/components/agent-hub-projection.ts +2 -2
- package/src/modes/components/agent-hub-renderer.ts +3 -7
- package/src/modes/components/agent-hub.ts +5 -0
- package/src/modes/components/agent-transcript-viewer.ts +3 -0
- package/src/modes/components/chat-transcript-builder.ts +3 -0
- package/src/modes/components/custom-editor.ts +0 -9
- package/src/modes/components/status-line/component.ts +1 -0
- package/src/modes/components/status-line/segments.ts +6 -4
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/tool-execution.ts +13 -16
- package/src/modes/components/tree-selector.ts +62 -3
- package/src/modes/controllers/command-controller.ts +8 -2
- package/src/modes/controllers/event-controller.ts +15 -15
- package/src/modes/controllers/input-controller.ts +20 -2
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/theme/color.ts +133 -0
- package/src/modes/theme/loader.ts +178 -0
- package/src/modes/theme/schema.ts +263 -0
- package/src/modes/theme/symbols.ts +1000 -0
- package/src/modes/theme/theme-class.ts +611 -0
- package/src/modes/theme/theme.ts +27 -2453
- package/src/modes/theme/tui-adapters.ts +279 -0
- package/src/modes/utils/transcript-render-helpers.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/registry/agent-registry.ts +2 -2
- package/src/sdk.ts +6 -49
- package/src/secrets/index.ts +51 -6
- package/src/secrets/message-transform.ts +287 -0
- package/src/secrets/obfuscator.ts +39 -1303
- package/src/secrets/placeholder-scan.ts +506 -0
- package/src/secrets/placeholder.ts +309 -0
- package/src/secrets/replacement.ts +216 -0
- package/src/session/agent-session-events.ts +2 -2
- package/src/session/agent-session-types.ts +6 -0
- package/src/session/agent-session.ts +146 -21
- package/src/session/indexed-session-storage.ts +7 -2
- package/src/session/messages.ts +1 -0
- package/src/session/prewalk.ts +57 -17
- package/src/session/session-advisors.ts +32 -34
- package/src/session/session-context.ts +3 -5
- package/src/session/session-handoff.ts +39 -16
- package/src/session/session-manager.ts +67 -3
- package/src/session/session-provider-boundary.ts +3 -6
- package/src/session/session-tools.ts +37 -3
- package/src/session/todo-tracker.ts +11 -1
- package/src/session/turn-recovery.ts +115 -84
- package/src/slash-commands/builtin-collaboration.ts +504 -0
- package/src/slash-commands/builtin-completions.ts +291 -0
- package/src/slash-commands/builtin-control.ts +78 -0
- package/src/slash-commands/builtin-lifecycle.ts +506 -0
- package/src/slash-commands/builtin-marketplace.ts +567 -0
- package/src/slash-commands/builtin-modes.ts +518 -0
- package/src/slash-commands/builtin-registry.ts +21 -2996
- package/src/slash-commands/builtin-session.ts +592 -0
- package/src/task/executor.ts +17 -14
- package/src/task/index.ts +21 -6
- package/src/tools/bash-skill-urls.ts +15 -0
- package/src/tools/bash.ts +10 -11
- package/src/tools/debug.ts +1 -1
- package/src/tools/gh-common.ts +288 -0
- package/src/tools/gh-pr-checkout.ts +679 -0
- package/src/tools/gh-pr-diff.ts +473 -0
- package/src/tools/gh-run-watch.ts +1015 -0
- package/src/tools/gh-search.ts +500 -0
- package/src/tools/gh-types.ts +379 -0
- package/src/tools/gh-view.ts +612 -0
- package/src/tools/gh.ts +109 -3821
- package/src/tools/jtd-to-json-schema.ts +123 -21
- package/src/tools/read-archive.ts +209 -0
- package/src/tools/read-format.ts +593 -0
- package/src/tools/read-path-resolution.ts +36 -0
- package/src/tools/read-pdf-images.ts +250 -0
- package/src/tools/read-renderer.ts +289 -0
- package/src/tools/read-selector.ts +84 -0
- package/src/tools/read-sqlite.ts +215 -0
- package/src/tools/read-summary.ts +199 -0
- package/src/tools/read.ts +82 -1824
- package/src/tools/shell-tokenize.ts +99 -1
- package/src/utils/changelog.ts +1 -1
- package/src/utils/title-generator.ts +3 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/zai.ts +12 -3
|
@@ -174,8 +174,8 @@ import {
|
|
|
174
174
|
deobfuscateSessionContext,
|
|
175
175
|
deobfuscateToolArguments,
|
|
176
176
|
obfuscateProviderContext,
|
|
177
|
-
|
|
178
|
-
} from "../secrets/obfuscator";
|
|
177
|
+
} from "../secrets/message-transform";
|
|
178
|
+
import type { SecretObfuscator } from "../secrets/obfuscator";
|
|
179
179
|
import {
|
|
180
180
|
AUTO_THINKING,
|
|
181
181
|
type ConfiguredThinkingLevel,
|
|
@@ -304,7 +304,7 @@ import {
|
|
|
304
304
|
USER_INTERRUPT_LABEL,
|
|
305
305
|
} from "./messages";
|
|
306
306
|
import { ModelControls, type ModelControlsHost } from "./model-controls";
|
|
307
|
-
import { PrewalkCoordinator, type PrewalkCoordinatorHost } from "./prewalk";
|
|
307
|
+
import { isPrewalkPlanNudge, PrewalkCoordinator, type PrewalkCoordinatorHost } from "./prewalk";
|
|
308
308
|
import {
|
|
309
309
|
isAdvisorCard,
|
|
310
310
|
isDisplayableQueuedMessage,
|
|
@@ -881,8 +881,8 @@ export class AgentSession {
|
|
|
881
881
|
/**
|
|
882
882
|
* Arm prewalk outside the normal startup path so an explicit slash command starts immediately.
|
|
883
883
|
*/
|
|
884
|
-
armPrewalk(target: Model, thinkingLevel?: ConfiguredThinkingLevel):
|
|
885
|
-
this.#prewalk.arm(target, thinkingLevel);
|
|
884
|
+
armPrewalk(target: Model, thinkingLevel?: ConfiguredThinkingLevel): boolean {
|
|
885
|
+
return this.#prewalk.arm(target, thinkingLevel);
|
|
886
886
|
}
|
|
887
887
|
|
|
888
888
|
/** Validate the active plan artifact and shape an `xd://propose` result for review-mode hosts. */
|
|
@@ -2056,6 +2056,41 @@ export class AgentSession {
|
|
|
2056
2056
|
*/
|
|
2057
2057
|
#prunedTerminalRefusal: AssistantMessage | undefined = undefined;
|
|
2058
2058
|
|
|
2059
|
+
/**
|
|
2060
|
+
* In-flight {@link #dispatchAgentEvent} promises. agent-core invokes the
|
|
2061
|
+
* event subscriber fire-and-forget, so a `message_end`/`agent_end` handler
|
|
2062
|
+
* can still be awaiting extension/subscriber/maintenance work — and thus its
|
|
2063
|
+
* `sessionManager`/`agent.state` append — after `agent.waitForIdle()`
|
|
2064
|
+
* resolves. Dispose drains this set so the late append lands *before* the
|
|
2065
|
+
* memory release, never after it.
|
|
2066
|
+
*/
|
|
2067
|
+
#inFlightEventHandlers = new Set<Promise<void>>();
|
|
2068
|
+
|
|
2069
|
+
/**
|
|
2070
|
+
* Subscriber entry point. Delegates to {@link #dispatchAgentEvent} and
|
|
2071
|
+
* records the dispatch in {@link #inFlightEventHandlers} until it settles so
|
|
2072
|
+
* {@link #drainInFlightEventHandlers} can await the session's async
|
|
2073
|
+
* event/persistence pipeline during teardown.
|
|
2074
|
+
*/
|
|
2075
|
+
#handleAgentEvent = (event: AgentEvent): Promise<void> => {
|
|
2076
|
+
const processing = this.#dispatchAgentEvent(event);
|
|
2077
|
+
this.#inFlightEventHandlers.add(processing);
|
|
2078
|
+
void processing.finally(() => this.#inFlightEventHandlers.delete(processing)).catch(() => {});
|
|
2079
|
+
return processing;
|
|
2080
|
+
};
|
|
2081
|
+
|
|
2082
|
+
/**
|
|
2083
|
+
* Await every in-flight event handler (and any it chains into) so a late
|
|
2084
|
+
* message/entry append cannot land after the caller clears session memory.
|
|
2085
|
+
* The agent must already be idle — otherwise new events keep arriving and
|
|
2086
|
+
* this never drains.
|
|
2087
|
+
*/
|
|
2088
|
+
async #drainInFlightEventHandlers(): Promise<void> {
|
|
2089
|
+
while (this.#inFlightEventHandlers.size > 0) {
|
|
2090
|
+
await Promise.allSettled([...this.#inFlightEventHandlers]);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2059
2094
|
/** Internal handler for agent events - shared by subscribe and reconnect.
|
|
2060
2095
|
*
|
|
2061
2096
|
* `agent_end` handling schedules deferred post-prompt recovery work
|
|
@@ -2070,7 +2105,7 @@ export class AgentSession {
|
|
|
2070
2105
|
* `#postPromptTasksPromise` is set the moment `#emit` invokes this handler, so
|
|
2071
2106
|
* the recovery wait always sees the in-flight handler and blocks until it — and
|
|
2072
2107
|
* everything it schedules — settles. */
|
|
2073
|
-
#
|
|
2108
|
+
#dispatchAgentEvent = async (event: AgentEvent): Promise<void> => {
|
|
2074
2109
|
if (event.type === "tool_execution_end" && this.#isTerminalYieldToolResult(event)) {
|
|
2075
2110
|
const alreadyTerminated = this.#synchronouslyTerminatedYieldToolCallIds.delete(event.toolCallId);
|
|
2076
2111
|
if (!alreadyTerminated) {
|
|
@@ -2489,14 +2524,17 @@ export class AgentSession {
|
|
|
2489
2524
|
const persistMessageEnd = () => {
|
|
2490
2525
|
// Check if this is a hook/custom message
|
|
2491
2526
|
if (event.message.role === "hookMessage" || event.message.role === "custom") {
|
|
2492
|
-
//
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2527
|
+
// Prewalk's plan nudge is a one-run steering instruction. Persisting it would
|
|
2528
|
+
// resurrect the consumed prompt on resume, fork, or any context rebuild.
|
|
2529
|
+
if (!isPrewalkPlanNudge(event.message)) {
|
|
2530
|
+
this.sessionManager.appendCustomMessageEntry(
|
|
2531
|
+
event.message.customType,
|
|
2532
|
+
event.message.content,
|
|
2533
|
+
event.message.display,
|
|
2534
|
+
event.message.details,
|
|
2535
|
+
event.message.attribution ?? "agent",
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2500
2538
|
if (event.message.role === "custom" && event.message.customType === "ttsr-injection") {
|
|
2501
2539
|
this.#ttsr.markInjectedFromDetails(event.message.details);
|
|
2502
2540
|
}
|
|
@@ -2645,7 +2683,7 @@ export class AgentSession {
|
|
|
2645
2683
|
// here after maintenance routing, tagged isTerminal so subscribers can
|
|
2646
2684
|
// tell final settles from scheduled continuations.
|
|
2647
2685
|
await this.#emitSessionEvent({ ...event, isTerminal: !options?.willContinue });
|
|
2648
|
-
void this.#emitAgentEndNotification(activeMessages, options).catch(err => {
|
|
2686
|
+
void this.#emitAgentEndNotification([...activeMessages], options).catch(err => {
|
|
2649
2687
|
logger.error("Agent end extension notification failed", { err });
|
|
2650
2688
|
});
|
|
2651
2689
|
};
|
|
@@ -3017,11 +3055,23 @@ export class AgentSession {
|
|
|
3017
3055
|
}
|
|
3018
3056
|
this.#beginInFlight();
|
|
3019
3057
|
try {
|
|
3020
|
-
await this.#recovery.maybeRestoreRetryFallbackPrimary();
|
|
3058
|
+
const reverted = await this.#recovery.maybeRestoreRetryFallbackPrimary();
|
|
3021
3059
|
if (signal.aborted || this.#isDisposed) {
|
|
3022
3060
|
this.#skipAgentContinue("post-restore-unavailable", options);
|
|
3023
3061
|
return;
|
|
3024
3062
|
}
|
|
3063
|
+
// A cooldown-expiry revert can drop the active window below the
|
|
3064
|
+
// accumulated context. The user-prompt path re-checks context after
|
|
3065
|
+
// the revert via runPrePromptCompactionIfNeeded; the auto-continue
|
|
3066
|
+
// path must do the same so agent.continue() never sends a
|
|
3067
|
+
// predictably oversized request to the reverted (smaller) model.
|
|
3068
|
+
if (reverted) {
|
|
3069
|
+
await this.#maintenance.runPrePromptCompactionIfNeeded([]);
|
|
3070
|
+
if (signal.aborted || this.#isDisposed) {
|
|
3071
|
+
this.#skipAgentContinue("post-restore-unavailable", options);
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3025
3075
|
if (this.settings.get("retry.usageAwareFallback")) {
|
|
3026
3076
|
if (!(await this.#runQueuedUsageAwarePreflight(signal))) {
|
|
3027
3077
|
this.#skipAgentContinue("session-unavailable", options);
|
|
@@ -3440,7 +3490,7 @@ export class AgentSession {
|
|
|
3440
3490
|
success: event.success,
|
|
3441
3491
|
attempt: event.attempt,
|
|
3442
3492
|
finalError: event.finalError,
|
|
3443
|
-
|
|
3493
|
+
retryErrors: event.retryErrors,
|
|
3444
3494
|
});
|
|
3445
3495
|
} else if (event.type === "ttsr_triggered") {
|
|
3446
3496
|
await this.#extensionRunner.emit({ type: "ttsr_triggered", rules: event.rules });
|
|
@@ -3820,8 +3870,6 @@ export class AgentSession {
|
|
|
3820
3870
|
this.#releasePowerAssertion();
|
|
3821
3871
|
await cleanupEmptyMoveSession(this.sessionManager, this.#movedFromEmptySessionFile);
|
|
3822
3872
|
this.#movedFromEmptySessionFile = undefined;
|
|
3823
|
-
// All teardown branches that can append session entries have settled.
|
|
3824
|
-
await this.sessionManager.close();
|
|
3825
3873
|
this.#closeAllProviderSessions("dispose");
|
|
3826
3874
|
this.setHindsightSessionState(undefined);
|
|
3827
3875
|
hindsightState?.dispose();
|
|
@@ -3836,6 +3884,77 @@ export class AgentSession {
|
|
|
3836
3884
|
}
|
|
3837
3885
|
this.#eventListeners = [];
|
|
3838
3886
|
this.#sessionChangeCallbacks.clear();
|
|
3887
|
+
|
|
3888
|
+
// A dispose triggered mid-turn (Ctrl-C / timeout / hard-killed subagent)
|
|
3889
|
+
// only *signals* the agent loop via the earlier abort(); the loop and the
|
|
3890
|
+
// session's fire-and-forget event handlers still unwind asynchronously.
|
|
3891
|
+
// Detach the response/SSE interceptors so a late frame cannot re-record
|
|
3892
|
+
// into rawSseDebugBuffer, then wait (bounded) for both the core run AND
|
|
3893
|
+
// the in-flight event/persistence handlers to settle — the latter can
|
|
3894
|
+
// still append the finished message/entries after agent.waitForIdle()
|
|
3895
|
+
// alone. Without this the release races the unwind and a disposed session
|
|
3896
|
+
// is repopulated with exactly the state we are trying to drop.
|
|
3897
|
+
this.agent.setProviderResponseInterceptor(undefined);
|
|
3898
|
+
this.agent.setRawSseEventInterceptor(undefined);
|
|
3899
|
+
let drained = false;
|
|
3900
|
+
try {
|
|
3901
|
+
await withTimeout(
|
|
3902
|
+
(async () => {
|
|
3903
|
+
await this.agent.waitForIdle();
|
|
3904
|
+
await this.#drainInFlightEventHandlers();
|
|
3905
|
+
})(),
|
|
3906
|
+
options.drainTimeoutMs ?? POST_PROMPT_DRAIN_TIMEOUT_MS,
|
|
3907
|
+
"Timed out waiting for the active agent run to settle during dispose",
|
|
3908
|
+
);
|
|
3909
|
+
drained = true;
|
|
3910
|
+
} catch (error) {
|
|
3911
|
+
logger.warn("Active agent run still settling at dispose deadline", { error: String(error) });
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
// Event handlers can reopen the append writer while they persist their
|
|
3915
|
+
// terminal message; that pipeline has drained (or hit the deadline).
|
|
3916
|
+
// Raise the write barrier BEFORE the final close: a handler that
|
|
3917
|
+
// outlived the deadline could otherwise enqueue disk work behind the
|
|
3918
|
+
// closing tail while we await it, and that work would run against the
|
|
3919
|
+
// file after a revival reopens it. The seal also bumps the disk epoch,
|
|
3920
|
+
// superseding queued tail work and fencing already-running atomic
|
|
3921
|
+
// rewrites at their commit guard; hot-path appends drained above are
|
|
3922
|
+
// already durable, and close() (scheduled post-seal) still flushes and
|
|
3923
|
+
// closes the writer.
|
|
3924
|
+
this.sessionManager.seal();
|
|
3925
|
+
await this.sessionManager.close();
|
|
3926
|
+
|
|
3927
|
+
// Release retained conversation memory. dispose() is terminal, and every
|
|
3928
|
+
// revival path reopens the transcript from disk (AgentLifecycleManager
|
|
3929
|
+
// reviver / persisted-revive / `history://`), so the in-memory copy is
|
|
3930
|
+
// dead weight from here on. Dropping it lets a parked subagent's session
|
|
3931
|
+
// graph shed its heavy payloads even while the lifecycle adoption record's
|
|
3932
|
+
// reviver closure still references the session object. Fixes #8003.
|
|
3933
|
+
this.#releaseRetainedSessionMemory();
|
|
3934
|
+
|
|
3935
|
+
// The deadline does not cancel the drain: a handler parked in a slow
|
|
3936
|
+
// extension hook resumes afterwards and would repopulate exactly the
|
|
3937
|
+
// state released above. Its disk writes are already dead — the release
|
|
3938
|
+
// SEALED the session manager (a revival may reopen the same JSONL
|
|
3939
|
+
// through a new manager the moment dispose returns, and this manager
|
|
3940
|
+
// must never race that writer) — so re-run only the in-memory reset
|
|
3941
|
+
// once the pipeline genuinely settles. The extension runner bounds hook
|
|
3942
|
+
// runtime, so this deferred pass is not unbounded.
|
|
3943
|
+
if (!drained) {
|
|
3944
|
+
void (async () => {
|
|
3945
|
+
await this.agent.waitForIdle();
|
|
3946
|
+
await this.#drainInFlightEventHandlers();
|
|
3947
|
+
this.#releaseRetainedSessionMemory();
|
|
3948
|
+
})().catch(error => logger.warn("Deferred dispose finalization failed", { error: String(error) }));
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
/** Drop the in-memory conversation state after the terminal dispose flush. */
|
|
3953
|
+
#releaseRetainedSessionMemory(): void {
|
|
3954
|
+
this.agent.reset();
|
|
3955
|
+
this.agent.setAppendOnlyContext(undefined);
|
|
3956
|
+
this.rawSseDebugBuffer.clear();
|
|
3957
|
+
this.sessionManager.releaseRetainedEntries();
|
|
3839
3958
|
}
|
|
3840
3959
|
|
|
3841
3960
|
#closeAllProviderSessions(reason: string): void {
|
|
@@ -5273,11 +5392,13 @@ export class AgentSession {
|
|
|
5273
5392
|
|
|
5274
5393
|
if (result?.systemPrompt !== undefined) {
|
|
5275
5394
|
baseXdevCatalogDelivered = false;
|
|
5276
|
-
this.
|
|
5395
|
+
this.#tools.setTurnSystemPromptOverride(result.systemPrompt);
|
|
5277
5396
|
} else {
|
|
5397
|
+
this.#tools.clearTurnSystemPromptOverride();
|
|
5278
5398
|
this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
|
|
5279
5399
|
}
|
|
5280
5400
|
} else {
|
|
5401
|
+
this.#tools.clearTurnSystemPromptOverride();
|
|
5281
5402
|
this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
|
|
5282
5403
|
}
|
|
5283
5404
|
|
|
@@ -5342,6 +5463,8 @@ export class AgentSession {
|
|
|
5342
5463
|
await this.#waitForPostPromptRecovery(generation);
|
|
5343
5464
|
}
|
|
5344
5465
|
} finally {
|
|
5466
|
+
// The per-turn before_agent_start override lives only for this turn.
|
|
5467
|
+
this.#tools.clearTurnSystemPromptOverride();
|
|
5345
5468
|
this.#usagePreflightReadyForNextModelCall = false;
|
|
5346
5469
|
this.#endInFlight();
|
|
5347
5470
|
}
|
|
@@ -6200,6 +6323,9 @@ export class AgentSession {
|
|
|
6200
6323
|
this.abortRetry();
|
|
6201
6324
|
this.#promptGeneration++;
|
|
6202
6325
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
6326
|
+
// Abort the handoff first so generic compaction cancellation cannot replace
|
|
6327
|
+
// the harness reason with an unreasoned "Handoff cancelled".
|
|
6328
|
+
this.#handoff.abortHandoff(new Error(options?.reason ?? "Handoff aborted by session"));
|
|
6203
6329
|
if (options?.preserveCompaction) {
|
|
6204
6330
|
// Manual `/compact` installed its own #compactionAbortController before
|
|
6205
6331
|
// this internal abort and must keep it alive (that marker is what makes
|
|
@@ -6211,7 +6337,6 @@ export class AgentSession {
|
|
|
6211
6337
|
} else {
|
|
6212
6338
|
this.abortCompaction();
|
|
6213
6339
|
}
|
|
6214
|
-
this.abortHandoff();
|
|
6215
6340
|
this.abortBash();
|
|
6216
6341
|
this.abortEval();
|
|
6217
6342
|
const postPromptDrain = this.#cancelPostPromptTasks();
|
|
@@ -120,8 +120,13 @@ export class IndexedSessionStorage implements SessionStorage {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
async drain(): Promise<void> {
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
// Quiesce EVERY pending backend operation, not just the drain-tracked
|
|
124
|
+
// fire-and-forget publishes: an atomic write whose commit guard passed
|
|
125
|
+
// just before a terminal seal is still on the wire with
|
|
126
|
+
// `trackDrain: false`, and a graceful shutdown (SessionManager.close)
|
|
127
|
+
// must not return while it can still publish under a reopened path.
|
|
128
|
+
while (this.#drainPending.size > 0 || this.#pathPending.size > 0) {
|
|
129
|
+
await Promise.allSettled([...this.#drainPending, ...this.#pathPending.values()]);
|
|
125
130
|
}
|
|
126
131
|
const error = this.#firstDrainError;
|
|
127
132
|
this.#firstDrainError = undefined;
|
package/src/session/messages.ts
CHANGED
|
@@ -42,6 +42,7 @@ import { formatOutputNotice } from "../tools/output-meta";
|
|
|
42
42
|
export const SKILL_PROMPT_MESSAGE_TYPE = "skill-prompt";
|
|
43
43
|
export const LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE = "lsp-late-diagnostic";
|
|
44
44
|
export const BACKGROUND_TAN_DISPATCH_MESSAGE_TYPE = "background-tan-dispatch";
|
|
45
|
+
export const PREWALK_PLAN_MESSAGE_TYPE = "prewalk-plan";
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* Logs provider-error turns so their actual cause is available outside the
|
package/src/session/prewalk.ts
CHANGED
|
@@ -14,11 +14,16 @@ import { type ConfiguredThinkingLevel, prewalkWouldBeNoop } from "../thinking";
|
|
|
14
14
|
import type { PlanProposalHandler } from "../tools/resolve";
|
|
15
15
|
import { ToolError } from "../tools/tool-errors";
|
|
16
16
|
import type { PlanYolo, Prewalk } from "./agent-session-types";
|
|
17
|
+
import { PREWALK_PLAN_MESSAGE_TYPE } from "./messages";
|
|
17
18
|
import type { SessionManager } from "./session-manager";
|
|
18
19
|
|
|
19
|
-
const PREWALK_PLAN_MESSAGE_TYPE = "prewalk-plan";
|
|
20
20
|
const PREWALK_CONTINUE_MESSAGE_TYPE = "prewalk-continue";
|
|
21
21
|
const PREWALK_CHECKLIST_MESSAGE_TYPE = "prewalk-checklist";
|
|
22
|
+
|
|
23
|
+
/** Hidden plan steering is consumed within the live run and must not reappear after a context rebuild. */
|
|
24
|
+
export function isPrewalkPlanNudge(message: AgentMessage): boolean {
|
|
25
|
+
return message.role === "custom" && message.customType === PREWALK_PLAN_MESSAGE_TYPE;
|
|
26
|
+
}
|
|
22
27
|
const PREWALK_ACTION_TOOLS: Record<string, true> = {
|
|
23
28
|
edit: true,
|
|
24
29
|
write: true,
|
|
@@ -99,10 +104,40 @@ export class PrewalkCoordinator {
|
|
|
99
104
|
return this.#prewalk;
|
|
100
105
|
}
|
|
101
106
|
|
|
107
|
+
#isNoop(prewalk: Prewalk): boolean {
|
|
108
|
+
return prewalkWouldBeNoop(
|
|
109
|
+
this.#host.model(),
|
|
110
|
+
this.#host.configuredThinkingLevel(),
|
|
111
|
+
prewalk.target,
|
|
112
|
+
prewalk.thinkingLevel,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#clearPrewalkState(): void {
|
|
117
|
+
this.#prewalk = undefined;
|
|
118
|
+
this.#planInjected = false;
|
|
119
|
+
this.#continuePending = false;
|
|
120
|
+
this.#todoSeen = false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#disarmNoop(prewalk: Prewalk): void {
|
|
124
|
+
this.#clearPrewalkState();
|
|
125
|
+
this.#host.emitNotice(
|
|
126
|
+
"info",
|
|
127
|
+
`Prewalk: target ${prewalk.target.provider}/${prewalk.target.id} already matches the active model and thinking level; nothing to switch.`,
|
|
128
|
+
"prewalk",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
102
132
|
/** Advances the one-way prewalk switch at a completed assistant-turn boundary. */
|
|
103
133
|
async advanceAtTurnEnd(liveMessages: AgentMessage[], context: AgentTurnEndContext | undefined): Promise<void> {
|
|
104
134
|
const prewalk = this.#prewalk;
|
|
105
135
|
if (!prewalk || context?.message.role !== "assistant") return;
|
|
136
|
+
if (this.#isNoop(prewalk)) {
|
|
137
|
+
this.#scrubPlanNudge(liveMessages);
|
|
138
|
+
this.#disarmNoop(prewalk);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
106
141
|
if (context.toolResults.some(result => result.toolName === "todo" && !result.isError)) this.#todoSeen = true;
|
|
107
142
|
|
|
108
143
|
const hasToolResults = context.toolResults.length > 0;
|
|
@@ -147,18 +182,12 @@ export class PrewalkCoordinator {
|
|
|
147
182
|
}
|
|
148
183
|
this.#scrubPlanNudge(liveMessages);
|
|
149
184
|
const target = prewalk.target;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.#prewalk = undefined;
|
|
153
|
-
this.#host.emitNotice(
|
|
154
|
-
"info",
|
|
155
|
-
`Prewalk: target ${target.provider}/${target.id} already matches the active model and thinking level; nothing to switch.`,
|
|
156
|
-
"prewalk",
|
|
157
|
-
);
|
|
185
|
+
if (this.#isNoop(prewalk)) {
|
|
186
|
+
this.#disarmNoop(prewalk);
|
|
158
187
|
return;
|
|
159
188
|
}
|
|
160
189
|
await this.#host.setModelTemporary(target, prewalk.thinkingLevel, { ephemeral: true });
|
|
161
|
-
this.#
|
|
190
|
+
this.#clearPrewalkState();
|
|
162
191
|
this.#host.emitNotice(
|
|
163
192
|
"info",
|
|
164
193
|
`Prewalk: switched to ${target.provider}/${target.id} after first ${action.toolName} call.`,
|
|
@@ -175,18 +204,29 @@ export class PrewalkCoordinator {
|
|
|
175
204
|
}
|
|
176
205
|
|
|
177
206
|
/** Arms a prewalk immediately for an explicit slash-command request. */
|
|
178
|
-
arm(target: Model, thinkingLevel?: ConfiguredThinkingLevel):
|
|
179
|
-
|
|
207
|
+
arm(target: Model, thinkingLevel?: ConfiguredThinkingLevel): boolean {
|
|
208
|
+
const active = this.#prewalk;
|
|
209
|
+
if (active) {
|
|
180
210
|
this.#host.emitNotice(
|
|
181
211
|
"info",
|
|
182
|
-
`Prewalk: already armed for ${
|
|
212
|
+
`Prewalk: already armed for ${active.target.provider}/${active.target.id}, waiting for the first edit/write.`,
|
|
183
213
|
"prewalk",
|
|
184
214
|
);
|
|
185
|
-
return
|
|
215
|
+
return (
|
|
216
|
+
active.target.provider === target.provider &&
|
|
217
|
+
active.target.id === target.id &&
|
|
218
|
+
active.thinkingLevel === thinkingLevel
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const candidate = { target, thinkingLevel };
|
|
222
|
+
if (this.#isNoop(candidate)) {
|
|
223
|
+
this.#disarmNoop(candidate);
|
|
224
|
+
return false;
|
|
186
225
|
}
|
|
187
|
-
this.#prewalk =
|
|
226
|
+
this.#prewalk = candidate;
|
|
188
227
|
this.#planInjected = true;
|
|
189
228
|
this.#continuePending = true;
|
|
229
|
+
this.#todoSeen = false;
|
|
190
230
|
this.#host.agent.steer({
|
|
191
231
|
role: "custom",
|
|
192
232
|
customType: PREWALK_PLAN_MESSAGE_TYPE,
|
|
@@ -200,6 +240,7 @@ export class PrewalkCoordinator {
|
|
|
200
240
|
`Prewalk: armed for ${target.provider}/${target.id} — will switch at the first edit/write once the todo list exists.`,
|
|
201
241
|
"prewalk",
|
|
202
242
|
);
|
|
243
|
+
return true;
|
|
203
244
|
}
|
|
204
245
|
|
|
205
246
|
/** Lazily enables plan-yolo's plan phase before the first prompt is built. */
|
|
@@ -220,8 +261,7 @@ export class PrewalkCoordinator {
|
|
|
220
261
|
|
|
221
262
|
#scrubPlanNudge(liveMessages: AgentMessage[]): void {
|
|
222
263
|
if (!this.#planInjected) return;
|
|
223
|
-
const isPlanNudge =
|
|
224
|
-
message.role === "custom" && message.customType === PREWALK_PLAN_MESSAGE_TYPE;
|
|
264
|
+
const isPlanNudge = isPrewalkPlanNudge;
|
|
225
265
|
for (let index = liveMessages.length - 1; index >= 0; index--) {
|
|
226
266
|
if (!isPlanNudge(liveMessages[index])) continue;
|
|
227
267
|
invalidateMessageCache(liveMessages[index]);
|
|
@@ -1167,47 +1167,42 @@ export class SessionAdvisors {
|
|
|
1167
1167
|
const failedMessage = failedMessages.findLast(
|
|
1168
1168
|
(message): message is AssistantMessage => message.role === "assistant",
|
|
1169
1169
|
);
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
// an HTTP 429 thrown from prompt()); classify the raw error so a
|
|
1173
|
-
// structural usage limit still marks the exhausted credential.
|
|
1174
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1175
|
-
if (!AIError.isUsageLimit(error) && !isUsageLimitOutcome(extractHttpStatusFromError(error), message)) {
|
|
1176
|
-
return false;
|
|
1177
|
-
}
|
|
1178
|
-
const currentModel = advisor.agent.state.model;
|
|
1179
|
-
const outcome = await this.#host.modelRegistry.authStorage.markUsageLimitReached(
|
|
1180
|
-
currentModel.provider,
|
|
1181
|
-
advisor.providerSessionId,
|
|
1182
|
-
{
|
|
1183
|
-
retryAfterMs: extractRetryHint(undefined, message),
|
|
1184
|
-
baseUrl: currentModel.baseUrl,
|
|
1185
|
-
modelId: currentModel.id,
|
|
1186
|
-
signal,
|
|
1187
|
-
},
|
|
1188
|
-
);
|
|
1189
|
-
return outcome.switched;
|
|
1190
|
-
}
|
|
1191
|
-
if (failedMessage.content.some(block => block.type === "toolCall")) return false;
|
|
1170
|
+
const assistantFailure = failedMessage?.stopReason === "error" ? failedMessage : undefined;
|
|
1171
|
+
if (assistantFailure?.content.some(block => block.type === "toolCall")) return false;
|
|
1192
1172
|
|
|
1193
1173
|
const currentModel = advisor.agent.state.model;
|
|
1194
|
-
const message =
|
|
1195
|
-
const errorId =
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1174
|
+
const message = assistantFailure?.errorMessage ?? (error instanceof Error ? error.message : String(error));
|
|
1175
|
+
const errorId = assistantFailure
|
|
1176
|
+
? AIError.classifyMessage({
|
|
1177
|
+
api: currentModel.api,
|
|
1178
|
+
errorId: assistantFailure.errorId,
|
|
1179
|
+
errorMessage: message,
|
|
1180
|
+
errorStatus: assistantFailure.errorStatus,
|
|
1181
|
+
})
|
|
1182
|
+
: AIError.classify(error, currentModel.api);
|
|
1201
1183
|
if (AIError.is(errorId, AIError.Flag.Abort) || AIError.is(errorId, AIError.Flag.UserInterrupt)) return false;
|
|
1202
|
-
if (
|
|
1184
|
+
if (
|
|
1185
|
+
AIError.is(errorId, AIError.Flag.ContextOverflow) ||
|
|
1186
|
+
(assistantFailure && AIError.isContextOverflow(assistantFailure, currentModel.contextWindow ?? 0))
|
|
1187
|
+
) {
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1203
1190
|
|
|
1204
|
-
const
|
|
1191
|
+
const accountPolicyDenial = AIError.is(errorId, AIError.Flag.AccountPolicy);
|
|
1192
|
+
if (accountPolicyDenial) {
|
|
1193
|
+
const switched = await this.#host.modelRegistry.authStorage.rotateSessionCredential(
|
|
1194
|
+
currentModel.provider,
|
|
1195
|
+
advisor.providerSessionId,
|
|
1196
|
+
{ error: message, modelId: currentModel.id, signal },
|
|
1197
|
+
);
|
|
1198
|
+
if (switched) return true;
|
|
1199
|
+
}
|
|
1205
1200
|
|
|
1206
1201
|
const retryAfterMs = extractRetryHint(undefined, message);
|
|
1207
|
-
|
|
1202
|
+
const usageLimit =
|
|
1208
1203
|
AIError.is(errorId, AIError.Flag.UsageLimit) ||
|
|
1209
|
-
isUsageLimitOutcome(extractHttpStatusFromError(error), message)
|
|
1210
|
-
) {
|
|
1204
|
+
isUsageLimitOutcome(extractHttpStatusFromError(error), message);
|
|
1205
|
+
if (usageLimit) {
|
|
1211
1206
|
const outcome = await this.#host.modelRegistry.authStorage.markUsageLimitReached(
|
|
1212
1207
|
currentModel.provider,
|
|
1213
1208
|
advisor.providerSessionId,
|
|
@@ -1220,6 +1215,9 @@ export class SessionAdvisors {
|
|
|
1220
1215
|
);
|
|
1221
1216
|
if (outcome.switched) return true;
|
|
1222
1217
|
}
|
|
1218
|
+
if (!assistantFailure && !accountPolicyDenial && !usageLimit) return false;
|
|
1219
|
+
|
|
1220
|
+
const currentSelector = formatRetryFallbackSelector(currentModel, advisor.thinkingLevel);
|
|
1223
1221
|
|
|
1224
1222
|
const retrySettings = this.#host.settings.getGroup("retry");
|
|
1225
1223
|
if (!retrySettings.enabled || !retrySettings.modelFallback) return false;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
INTERRUPTED_THINKING_MESSAGE_TYPE,
|
|
9
9
|
isCustomMessageContent,
|
|
10
10
|
normalizeCustomMessagePayload,
|
|
11
|
+
PREWALK_PLAN_MESSAGE_TYPE,
|
|
11
12
|
} from "./messages";
|
|
12
13
|
import { type CompactionEntry, EPHEMERAL_MODEL_CHANGE_ROLE, type SessionEntry } from "./session-entries";
|
|
13
14
|
|
|
@@ -332,15 +333,12 @@ export function buildSessionContext(
|
|
|
332
333
|
const appendMessage = (entry: SessionEntry) => {
|
|
333
334
|
handleEntryResetTracking(entry);
|
|
334
335
|
if (entry.type === "message") {
|
|
335
|
-
if (
|
|
336
|
-
!options?.transcript &&
|
|
337
|
-
entry.message.role === "assistant" &&
|
|
338
|
-
entry.message.retryRecovery?.status === "recovered"
|
|
339
|
-
) {
|
|
336
|
+
if (!options?.transcript && entry.message.role === "assistant" && entry.message.retryRecovery) {
|
|
340
337
|
return;
|
|
341
338
|
}
|
|
342
339
|
pushMessage(entry.message);
|
|
343
340
|
} else if (entry.type === "custom_message") {
|
|
341
|
+
if (!options?.transcript && entry.customType === PREWALK_PLAN_MESSAGE_TYPE) return;
|
|
344
342
|
if (!isCustomMessageContent(entry.content)) return;
|
|
345
343
|
const normalized = normalizeCustomMessagePayload(entry);
|
|
346
344
|
const attribution = entry.attribution === undefined ? undefined : normalized.attribution;
|
|
@@ -14,7 +14,8 @@ import { logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
|
14
14
|
import type { ModelRegistry } from "../config/model-registry";
|
|
15
15
|
import type { Settings } from "../config/settings";
|
|
16
16
|
import type { ExtensionRunner, SessionBeforeSwitchResult } from "../extensibility/extensions";
|
|
17
|
-
import { obfuscateProviderContext
|
|
17
|
+
import { obfuscateProviderContext } from "../secrets/message-transform";
|
|
18
|
+
import type { SecretObfuscator } from "../secrets/obfuscator";
|
|
18
19
|
import type { HandoffResult, SessionHandoffOptions } from "./agent-session-types";
|
|
19
20
|
import type { BashSessionTransition } from "./bash-runner";
|
|
20
21
|
import type { SessionContext } from "./session-context";
|
|
@@ -29,6 +30,17 @@ function createHandoffFileName(date = new Date()): string {
|
|
|
29
30
|
return `handoff-${fileTimestamp}.md`;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
function throwIfHandoffAborted(signal: AbortSignal): void {
|
|
34
|
+
if (!signal.aborted) return;
|
|
35
|
+
const reason = signal.reason;
|
|
36
|
+
if (reason instanceof DOMException && reason.name === "AbortError") {
|
|
37
|
+
throw new Error("Handoff cancelled");
|
|
38
|
+
}
|
|
39
|
+
if (reason instanceof Error) throw reason;
|
|
40
|
+
if (typeof reason === "string" && reason.length > 0) throw new Error(reason);
|
|
41
|
+
throw new Error("Handoff aborted by session");
|
|
42
|
+
}
|
|
43
|
+
|
|
32
44
|
/** Capabilities borrowed from the owning AgentSession. */
|
|
33
45
|
export interface SessionHandoffHost {
|
|
34
46
|
agent: Agent;
|
|
@@ -80,10 +92,10 @@ export class SessionHandoff {
|
|
|
80
92
|
this.#host = host;
|
|
81
93
|
}
|
|
82
94
|
/**
|
|
83
|
-
* Cancel in-progress handoff generation.
|
|
95
|
+
* Cancel in-progress handoff generation, preserving a harness-provided reason.
|
|
84
96
|
*/
|
|
85
|
-
abortHandoff(): void {
|
|
86
|
-
this.#handoffAbortController?.abort();
|
|
97
|
+
abortHandoff(reason?: Error): void {
|
|
98
|
+
this.#handoffAbortController?.abort(reason);
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
/**
|
|
@@ -117,7 +129,7 @@ export class SessionHandoff {
|
|
|
117
129
|
const sourceSignal = options?.signal;
|
|
118
130
|
const onSourceAbort = () => {
|
|
119
131
|
if (!handoffSignal.aborted) {
|
|
120
|
-
handoffAbortController.abort();
|
|
132
|
+
handoffAbortController.abort(sourceSignal?.reason);
|
|
121
133
|
}
|
|
122
134
|
};
|
|
123
135
|
if (sourceSignal) {
|
|
@@ -130,9 +142,7 @@ export class SessionHandoff {
|
|
|
130
142
|
let advisorRecordersDetached = false;
|
|
131
143
|
let sessionTransitioned = false;
|
|
132
144
|
try {
|
|
133
|
-
|
|
134
|
-
throw new Error("Handoff cancelled");
|
|
135
|
-
}
|
|
145
|
+
throwIfHandoffAborted(handoffSignal);
|
|
136
146
|
|
|
137
147
|
const model = this.#host.model();
|
|
138
148
|
if (!model) {
|
|
@@ -207,11 +217,23 @@ export class SessionHandoff {
|
|
|
207
217
|
);
|
|
208
218
|
const handoffText = this.#host.deobfuscateFromProvider(rawHandoffText);
|
|
209
219
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
220
|
+
throwIfHandoffAborted(handoffSignal);
|
|
221
|
+
if (!handoffText || handoffText.trim().length === 0) {
|
|
222
|
+
// Empty/whitespace-only generation is a real failure, not a user
|
|
223
|
+
// cancellation. #7904 stopped masking provider errors as "Handoff
|
|
224
|
+
// cancelled"; an empty document is the remaining path that produced the
|
|
225
|
+
// same misleading, undebuggable message (#7993).
|
|
226
|
+
logger.warn("Handoff generation produced no content", {
|
|
227
|
+
sessionId: this.#host.sessionId(),
|
|
228
|
+
autoTriggered: options?.autoTriggered ?? false,
|
|
229
|
+
});
|
|
230
|
+
// Auto-handoff is best-effort: returning undefined lets maintenance fall
|
|
231
|
+
// back to context-full compaction. A user-initiated handoff must surface
|
|
232
|
+
// the failure instead of a silent, misleading "cancelled".
|
|
233
|
+
if (options?.autoTriggered) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
throw new Error("Handoff generation produced no content");
|
|
215
237
|
}
|
|
216
238
|
|
|
217
239
|
// Start a new session
|
|
@@ -309,9 +331,10 @@ export class SessionHandoff {
|
|
|
309
331
|
|
|
310
332
|
return { document: handoffText, savedPath };
|
|
311
333
|
} catch (error) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
334
|
+
// Only a genuine cancellation (user Esc or an unreasoned source-signal
|
|
335
|
+
// abort) maps to "Handoff cancelled". A harness-provided abort reason and
|
|
336
|
+
// provider failures surface verbatim.
|
|
337
|
+
throwIfHandoffAborted(handoffSignal);
|
|
315
338
|
throw error;
|
|
316
339
|
} finally {
|
|
317
340
|
if (advisorRecordersDetached) {
|