@oh-my-pi/pi-coding-agent 17.2.9 → 17.2.11
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 +83 -0
- package/dist/{CHANGELOG-8yy8h3bc.md → CHANGELOG-d9xenpn9.md} +83 -0
- package/dist/cli.js +10208 -12596
- package/dist/types/capability/mcp.d.ts +11 -0
- package/dist/types/capability/skill.d.ts +6 -0
- package/dist/types/cli/args.d.ts +1 -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/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +13 -0
- package/dist/types/config/settings-schema.d.ts +1 -34
- package/dist/types/discovery/agent-plugin-format.d.ts +143 -0
- package/dist/types/discovery/agent-plugins.d.ts +1 -0
- package/dist/types/discovery/contained-path.d.ts +34 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -3
- package/dist/types/extensibility/extensions/loader.d.ts +9 -2
- package/dist/types/extensibility/extensions/types.d.ts +45 -8
- package/dist/types/extensibility/hooks/types.d.ts +5 -5
- package/dist/types/extensibility/shared-events.d.ts +3 -2
- package/dist/types/extensibility/skills.d.ts +5 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/main.d.ts +1 -1
- package/dist/types/mcp/transports/header-policy.d.ts +45 -0
- package/dist/types/mcp/types.d.ts +15 -0
- package/dist/types/modes/acp/acp-agent.d.ts +1 -1
- package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
- package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -1
- package/dist/types/modes/acp/acp-mode.d.ts +1 -1
- package/dist/types/modes/components/agent-hub-projection.d.ts +38 -0
- package/dist/types/modes/components/agent-hub-renderer.d.ts +41 -0
- package/dist/types/modes/components/agent-hub.d.ts +14 -5
- 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/model-browser.d.ts +9 -1
- 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/interactive-mode.d.ts +8 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
- package/dist/types/registry/agent-registry.d.ts +47 -0
- package/dist/types/registry/persisted-agents.d.ts +3 -1
- package/dist/types/secrets/index.d.ts +23 -1
- package/dist/types/session/agent-session-events.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/prewalk.d.ts +3 -1
- package/dist/types/session/session-entries.d.ts +10 -0
- package/dist/types/session/session-loader.d.ts +11 -1
- package/dist/types/session/session-manager.d.ts +9 -1
- package/dist/types/session/session-tools.d.ts +18 -1
- package/dist/types/session/turn-recovery.d.ts +7 -2
- package/dist/types/slash-commands/acp-builtins.d.ts +1 -1
- package/dist/types/slash-commands/available-commands.d.ts +1 -1
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/index.d.ts +4 -3
- package/dist/types/task/read-only-policy.d.ts +3 -0
- package/dist/types/task/structured-subagent.d.ts +2 -0
- package/dist/types/task/types.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +3 -3
- package/dist/types/tools/browser/launch.d.ts +2 -1
- package/dist/types/tools/browser/tab-worker.d.ts +2 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +1 -5
- package/dist/types/tools/run-scope.d.ts +27 -1
- package/dist/types/tools/shell-tokenize.d.ts +16 -0
- package/dist/types/tools/terminal-output.d.ts +1 -1
- package/dist/types/utils/turndown.d.ts +1 -1
- package/dist/types/vibe/runtime.d.ts +2 -3
- package/dist/types/web/scrapers/readthedocs.d.ts +0 -3
- package/dist/types/web/search/providers/browser-headers.d.ts +2 -3
- package/examples/custom-tools/README.md +1 -1
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/api-demo.ts +5 -6
- package/examples/extensions/chalk-logger.ts +1 -1
- package/examples/extensions/hello.ts +2 -2
- package/examples/extensions/reload-runtime.ts +1 -1
- package/examples/extensions/tools.ts +2 -2
- package/examples/extensions/with-deps/index.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/package.json +15 -31
- package/scripts/bundle-dist.ts +1 -11
- package/scripts/legacy-pi-virtual-module.ts +4 -1
- package/src/advisor/runtime.ts +54 -0
- package/src/capability/mcp.ts +11 -0
- package/src/capability/skill.ts +6 -0
- package/src/cli/agents-cli.ts +1 -1
- package/src/cli/args.ts +24 -2
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/bench-cli.ts +1 -1
- package/src/cli/claude-trace-cli.ts +1 -1
- package/src/cli/command-help.ts +4 -0
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/flag-tables.ts +4 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/grep-cli.ts +1 -1
- package/src/cli/grievances-cli.ts +1 -1
- package/src/cli/help-extra.ts +1 -1
- package/src/cli/models-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/read-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/shell-cli.ts +1 -1
- package/src/cli/ssh-cli.ts +1 -1
- package/src/cli/stats-cli.ts +1 -1
- package/src/cli/tiny-models-cli.ts +1 -1
- package/src/cli/ttsr-cli.ts +1 -1
- package/src/cli/update-cli.ts +1 -1
- package/src/cli/usage-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli/worktree-cli.ts +1 -1
- package/src/cli-commands.ts +5 -0
- package/src/commands/commit.ts +16 -3
- package/src/commands/say.ts +1 -1
- package/src/commands/share.ts +71 -0
- package/src/commands/token.ts +1 -1
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/agentic/index.ts +39 -21
- package/src/commit/cli.ts +1 -1
- package/src/commit/execute.ts +56 -0
- package/src/commit/index.ts +2 -1
- package/src/commit/pipeline.ts +20 -7
- package/src/config/model-discovery.ts +1 -1
- package/src/config/model-registry.ts +21 -1
- package/src/config/model-resolver.ts +54 -7
- package/src/config/settings-schema.ts +2 -33
- package/src/config/settings.ts +46 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/agents.ts +4 -2
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +5 -4
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -1
- package/src/discovery/claude-plugins.ts +21 -5
- package/src/discovery/contained-path.ts +78 -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/eval/py/runner.py +38 -1
- package/src/exec/non-interactive-env.ts +1 -0
- package/src/extensibility/custom-commands/loader.ts +4 -4
- package/src/extensibility/custom-commands/types.ts +5 -5
- package/src/extensibility/custom-tools/loader.ts +4 -4
- package/src/extensibility/custom-tools/types.ts +3 -3
- package/src/extensibility/extensions/loader.ts +85 -20
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +51 -8
- package/src/extensibility/hooks/loader.ts +4 -5
- package/src/extensibility/hooks/types.ts +5 -5
- package/src/extensibility/plugins/legacy-pi-compat.ts +53 -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/index.ts +2 -2
- package/src/internal-urls/memory-protocol.ts +5 -1
- 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/launch/terminal-output.ts +1 -1
- package/src/main.ts +88 -23
- package/src/markit/converters/docx.ts +1 -1
- package/src/markit/converters/epub.ts +1 -1
- package/src/markit/converters/pptx.ts +1 -1
- package/src/markit/converters/xlsx.ts +1 -1
- 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 +20 -9
- package/src/modes/acp/acp-client-bridge.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/acp/acp-mode.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +2 -0
- package/src/modes/components/agent-hub-projection.ts +248 -0
- package/src/modes/components/agent-hub-renderer.ts +194 -0
- package/src/modes/components/agent-hub.ts +674 -207
- package/src/modes/components/agent-transcript-viewer.ts +3 -0
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/chat-transcript-builder.ts +3 -0
- package/src/modes/components/custom-editor.ts +0 -9
- package/src/modes/components/extensions/inspector-panel.ts +11 -6
- package/src/modes/components/footer.ts +1 -3
- package/src/modes/components/model-browser.ts +11 -3
- package/src/modes/components/model-hub.ts +4 -1
- package/src/modes/components/status-line/component.ts +1 -0
- package/src/modes/components/status-line/segments.ts +10 -7
- 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 +4 -1
- package/src/modes/controllers/event-controller.ts +44 -20
- package/src/modes/controllers/extension-ui-controller.ts +2 -2
- package/src/modes/controllers/input-controller.ts +20 -2
- package/src/modes/controllers/mcp-command-controller.ts +81 -20
- package/src/modes/controllers/selector-controller.ts +41 -49
- package/src/modes/controllers/streaming-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +13 -2
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/utils/transcript-render-helpers.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/computer.md +1 -1
- package/src/registry/agent-lifecycle.ts +14 -7
- package/src/registry/agent-registry.ts +65 -2
- package/src/registry/persisted-agents.ts +341 -16
- package/src/sdk.ts +6 -49
- package/src/secrets/index.ts +52 -1
- package/src/session/agent-session-events.ts +2 -2
- package/src/session/agent-session.ts +54 -16
- package/src/session/messages.ts +1 -0
- package/src/session/prewalk.ts +57 -17
- package/src/session/session-context.ts +3 -5
- package/src/session/session-entries.ts +10 -0
- package/src/session/session-handoff.ts +5 -1
- package/src/session/session-listing.ts +1 -1
- package/src/session/session-loader.ts +74 -8
- package/src/session/session-manager.ts +22 -2
- package/src/session/session-paths.ts +57 -9
- package/src/session/session-tools.ts +65 -4
- package/src/session/todo-tracker.ts +11 -1
- package/src/session/turn-recovery.ts +96 -74
- package/src/slash-commands/acp-builtins.ts +1 -1
- package/src/slash-commands/available-commands.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +12 -8
- package/src/task/executor.ts +70 -20
- package/src/task/index.ts +30 -34
- package/src/task/isolation-runner.ts +24 -11
- package/src/task/persisted-revive.ts +12 -5
- package/src/task/read-only-policy.ts +27 -0
- package/src/task/structured-subagent.ts +14 -4
- package/src/task/types.ts +4 -0
- package/src/tools/auto-generated-guard.ts +1 -1
- package/src/tools/bash-interactive.ts +4 -4
- package/src/tools/bash-skill-urls.ts +15 -0
- package/src/tools/bash.ts +16 -17
- package/src/tools/browser/cmux/cmux-tab.ts +66 -18
- package/src/tools/browser/launch.ts +41 -12
- package/src/tools/browser/readable.ts +9 -9
- package/src/tools/browser/tab-supervisor.ts +12 -3
- package/src/tools/browser/tab-worker-entry.ts +1 -1
- package/src/tools/browser/tab-worker.ts +101 -12
- package/src/tools/debug.ts +1 -1
- package/src/tools/fetch.ts +1 -1
- package/src/tools/jtd-to-json-schema.ts +123 -21
- package/src/tools/path-utils.ts +9 -2
- package/src/tools/read.ts +21 -9
- package/src/tools/run-scope.ts +290 -4
- package/src/tools/shell-tokenize.ts +98 -0
- package/src/tools/terminal-output.ts +1 -1
- package/src/tools/todo.ts +1 -1
- package/src/utils/clipboard.ts +10 -2
- package/src/utils/external-editor.ts +4 -2
- package/src/utils/jj.ts +1 -1
- package/src/utils/turndown.ts +1 -2
- package/src/vibe/runtime.ts +5 -5
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +2 -1
- package/src/web/scrapers/twitter.ts +2 -2
- package/src/web/scrapers/types.ts +1 -1
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/search/providers/browser-headers.ts +12 -39
- package/src/web/search/providers/codex.ts +3 -26
- package/src/web/search/providers/ecosia.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/google.ts +1 -1
- package/src/web/search/providers/mojeek.ts +1 -1
- package/src/web/search/providers/startpage.ts +1 -1
- package/src/markit/converters/mammoth.d.ts +0 -24
|
@@ -42,7 +42,28 @@ function encodeRelativeSessionDirName(prefix: string, relative: string): string
|
|
|
42
42
|
return encoded ? (prefix.endsWith("-") ? `${prefix}${encoded}` : `${prefix}-${encoded}`) : prefix;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Reconstruct the short-lived hashed session dir name used by 17.2.5-17.2.8
|
|
47
|
+
* (reverted PR #7397): `<scope>-<readable>-<sha256hex>` keyed by the canonical
|
|
48
|
+
* cwd. Kept only so {@link migrateHashedSessionDir} can recover sessions
|
|
49
|
+
* stranded when 17.2.9 restored the legacy names without a reverse migration.
|
|
50
|
+
*/
|
|
51
|
+
function encodeHashedSessionDirName(canonicalCwd: string, scope: "home" | "tmp" | "abs"): string {
|
|
52
|
+
const normalized = canonicalCwd.replaceAll("\\", "/");
|
|
53
|
+
const readable = path
|
|
54
|
+
.basename(canonicalCwd)
|
|
55
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
56
|
+
.replace(/^-+|-+$/g, "")
|
|
57
|
+
.slice(-80);
|
|
58
|
+
const digest = Bun.SHA256.hash(normalized, "hex");
|
|
59
|
+
return `${scope}-${readable || "project"}-${digest}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getDefaultSessionDirName(cwd: string): {
|
|
63
|
+
encodedDirName: string;
|
|
64
|
+
hashedDirName: string;
|
|
65
|
+
resolvedCwd: string;
|
|
66
|
+
} {
|
|
46
67
|
const resolvedCwd = path.resolve(cwd);
|
|
47
68
|
const canonicalCwd = resolveEquivalentPath(resolvedCwd);
|
|
48
69
|
const home = os.homedir();
|
|
@@ -51,13 +72,19 @@ function getDefaultSessionDirName(cwd: string): { encodedDirName: string; resolv
|
|
|
51
72
|
const canonicalTempRoot = resolveEquivalentPath(tempRoot);
|
|
52
73
|
const homeRelative = path.relative(canonicalHome, canonicalCwd);
|
|
53
74
|
const tempRelative = path.relative(canonicalTempRoot, canonicalCwd);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
let encodedDirName: string;
|
|
76
|
+
let scope: "home" | "tmp" | "abs";
|
|
77
|
+
if (homeRelative === "" || (!homeRelative.startsWith("..") && !path.isAbsolute(homeRelative))) {
|
|
78
|
+
encodedDirName = encodeRelativeSessionDirName("-", homeRelative);
|
|
79
|
+
scope = "home";
|
|
80
|
+
} else if (tempRelative === "" || (!tempRelative.startsWith("..") && !path.isAbsolute(tempRelative))) {
|
|
81
|
+
encodedDirName = encodeRelativeSessionDirName("-tmp", tempRelative);
|
|
82
|
+
scope = "tmp";
|
|
83
|
+
} else {
|
|
84
|
+
encodedDirName = encodeLegacyAbsoluteSessionDirName(canonicalCwd);
|
|
85
|
+
scope = "abs";
|
|
86
|
+
}
|
|
87
|
+
return { encodedDirName, hashedDirName: encodeHashedSessionDirName(canonicalCwd, scope), resolvedCwd };
|
|
61
88
|
}
|
|
62
89
|
|
|
63
90
|
/**
|
|
@@ -121,6 +148,26 @@ function migrateLegacyAbsoluteSessionDir(cwd: string, sessionDir: string, sessio
|
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Migrate a 17.2.5-17.2.8 hashed session dir back into its legacy path-based
|
|
153
|
+
* directory. The 17.2.9 revert restored the legacy names but dropped migration,
|
|
154
|
+
* stranding sessions written under the hashed scheme (issue #7677). Best-effort.
|
|
155
|
+
*/
|
|
156
|
+
function migrateHashedSessionDir(hashedDirName: string, sessionDir: string, sessionsRoot: string): void {
|
|
157
|
+
const hashedDir = path.join(sessionsRoot, hashedDirName);
|
|
158
|
+
if (hashedDir === sessionDir || !fs.existsSync(hashedDir)) return;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
migrateSessionDirPath(hashedDir, sessionDir);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logger.warn("Failed to migrate hashed session directory", {
|
|
164
|
+
oldPath: hashedDir,
|
|
165
|
+
newPath: sessionDir,
|
|
166
|
+
error: String(error),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
124
171
|
export function resolveManagedSessionRoot(sessionDir: string, cwd: string): string | undefined {
|
|
125
172
|
const currentDirName = path.basename(sessionDir);
|
|
126
173
|
const { encodedDirName } = getDefaultSessionDirName(cwd);
|
|
@@ -140,10 +187,11 @@ export function computeDefaultSessionDir(
|
|
|
140
187
|
storage: SessionStorage,
|
|
141
188
|
sessionsRoot: string = getSessionsDir(),
|
|
142
189
|
): string {
|
|
143
|
-
const { encodedDirName, resolvedCwd } = getDefaultSessionDirName(cwd);
|
|
190
|
+
const { encodedDirName, hashedDirName, resolvedCwd } = getDefaultSessionDirName(cwd);
|
|
144
191
|
migrateHomeSessionDirs(sessionsRoot);
|
|
145
192
|
const sessionDir = path.join(sessionsRoot, encodedDirName);
|
|
146
193
|
migrateLegacyAbsoluteSessionDir(resolvedCwd, sessionDir, sessionsRoot);
|
|
194
|
+
migrateHashedSessionDir(hashedDirName, sessionDir, sessionsRoot);
|
|
147
195
|
storage.ensureDirSync(sessionDir);
|
|
148
196
|
return sessionDir;
|
|
149
197
|
}
|
|
@@ -7,7 +7,7 @@ import { formatModelString } from "../config/model-resolver";
|
|
|
7
7
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
8
8
|
import type { CustomTool, CustomToolContext } from "../extensibility/custom-tools/types";
|
|
9
9
|
import { CustomToolAdapter } from "../extensibility/custom-tools/wrapper";
|
|
10
|
-
import type { ExtensionRunner } from "../extensibility/extensions";
|
|
10
|
+
import type { ExtensionRunner, SourceInfo, ToolInfo } from "../extensibility/extensions";
|
|
11
11
|
import { ExtensionToolWrapper } from "../extensibility/extensions/wrapper";
|
|
12
12
|
import { loadSkills, type Skill, type SkillWarning, setActiveSkills } from "../extensibility/skills";
|
|
13
13
|
import { type LocalProtocolOptions, XD_URL_PREFIX } from "../internal-urls";
|
|
@@ -198,6 +198,15 @@ export class SessionTools {
|
|
|
198
198
|
#presentationPinnedToolNames: ReadonlySet<string> | undefined;
|
|
199
199
|
#runtimeSelectedToolNames: ReadonlySet<string> | undefined;
|
|
200
200
|
#baseSystemPrompt: string[];
|
|
201
|
+
/**
|
|
202
|
+
* Per-turn system prompt returned by a `before_agent_start` extension hook
|
|
203
|
+
* ("replace the system prompt for this turn"). While set, base-prompt
|
|
204
|
+
* rebuilds keep this override on the agent instead of the rebuilt base, so a
|
|
205
|
+
* rebuild landing in the prompt window (compaction/promotion, memory
|
|
206
|
+
* promotion, MCP/RPC tool refresh, hindsight MM-TTL refresh) cannot silently
|
|
207
|
+
* drop it before the request. Cleared when the turn ends.
|
|
208
|
+
*/
|
|
209
|
+
#turnSystemPromptOverride: string[] | undefined;
|
|
201
210
|
#lastAppliedToolSignature: string | undefined;
|
|
202
211
|
/**
|
|
203
212
|
* `xd://` device names the current base system prompt renders in its catalog
|
|
@@ -262,6 +271,31 @@ export class SessionTools {
|
|
|
262
271
|
this.#baseSystemPrompt = prompt;
|
|
263
272
|
}
|
|
264
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Pushes `base` to the agent as the effective system prompt, unless an active
|
|
276
|
+
* per-turn {@link #turnSystemPromptOverride} takes precedence. Every base
|
|
277
|
+
* rebuild applies its result through here so a mid-turn rebuild preserves the
|
|
278
|
+
* override.
|
|
279
|
+
*/
|
|
280
|
+
#applyAgentSystemPrompt(base: string[]): void {
|
|
281
|
+
this.#host.agent.setSystemPrompt(this.#turnSystemPromptOverride ?? base);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Registers the per-turn `before_agent_start` system-prompt override and
|
|
286
|
+
* applies it to the agent. Base rebuilds during the turn preserve it until
|
|
287
|
+
* {@link clearTurnSystemPromptOverride}.
|
|
288
|
+
*/
|
|
289
|
+
setTurnSystemPromptOverride(prompt: string[]): void {
|
|
290
|
+
this.#turnSystemPromptOverride = prompt;
|
|
291
|
+
this.#host.agent.setSystemPrompt(prompt);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Drops the active per-turn override; later rebuilds fall back to the base prompt. */
|
|
295
|
+
clearTurnSystemPromptOverride(): void {
|
|
296
|
+
this.#turnSystemPromptOverride = undefined;
|
|
297
|
+
}
|
|
298
|
+
|
|
265
299
|
/** Skills currently rendered into the system prompt. */
|
|
266
300
|
get skills(): Skill[] {
|
|
267
301
|
return this.#skills;
|
|
@@ -333,6 +367,33 @@ export class SessionTools {
|
|
|
333
367
|
return Array.from(this.#toolRegistry.keys());
|
|
334
368
|
}
|
|
335
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Full metadata for every registered tool, including source provenance.
|
|
372
|
+
*
|
|
373
|
+
* Backs the `getAllTools()` ExtensionAPI method. Returns {@link ToolInfo}
|
|
374
|
+
* objects (not bare names) so extensions authored against upstream
|
|
375
|
+
* `@earendil-works/pi-coding-agent` — which promises `ToolInfo[]` — can read
|
|
376
|
+
* `sourceInfo.source` unchanged.
|
|
377
|
+
*/
|
|
378
|
+
getAllToolInfos(): ToolInfo[] {
|
|
379
|
+
return Array.from(this.#toolRegistry, ([name, tool]) => {
|
|
380
|
+
const source = this.#builtInToolNames.has(name)
|
|
381
|
+
? "builtin"
|
|
382
|
+
: isMCPToolName(name)
|
|
383
|
+
? "mcp"
|
|
384
|
+
: this.#rpcHostToolNames.has(name)
|
|
385
|
+
? "sdk"
|
|
386
|
+
: "extension";
|
|
387
|
+
const sourceInfo: SourceInfo = {
|
|
388
|
+
path: `<${source}:${name}>`,
|
|
389
|
+
source,
|
|
390
|
+
scope: "temporary",
|
|
391
|
+
origin: "top-level",
|
|
392
|
+
};
|
|
393
|
+
return { name, description: tool.description, parameters: tool.parameters, sourceInfo };
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
336
397
|
#wrapRuntimeTool(tool: AgentTool): AgentTool {
|
|
337
398
|
const wrapped = wrapToolWithMetaNotice(tool);
|
|
338
399
|
const extensionRunner = this.#host.extensionRunner();
|
|
@@ -663,7 +724,7 @@ export class SessionTools {
|
|
|
663
724
|
if (this.#lastAppliedToolSignature !== undefined) this.#host.clearInheritedProviderPromptCacheKey();
|
|
664
725
|
this.#baseSystemPrompt = rebuiltSystemPrompt;
|
|
665
726
|
this.#host.clearMemoryPromotionSnapshot();
|
|
666
|
-
this.#
|
|
727
|
+
this.#applyAgentSystemPrompt(this.#baseSystemPrompt);
|
|
667
728
|
this.#lastAppliedToolSignature = rebuiltSignature;
|
|
668
729
|
this.#promptModelKey = this.#currentPromptModelKey();
|
|
669
730
|
this.#basePromptXdevNames = new Set(rebuiltXdevCatalogNames);
|
|
@@ -1103,7 +1164,7 @@ export class SessionTools {
|
|
|
1103
1164
|
) {
|
|
1104
1165
|
this.#host.clearInheritedProviderPromptCacheKey();
|
|
1105
1166
|
}
|
|
1106
|
-
this.#
|
|
1167
|
+
this.#applyAgentSystemPrompt(this.#baseSystemPrompt);
|
|
1107
1168
|
this.#promptModelKey = this.#currentPromptModelKey();
|
|
1108
1169
|
// Refresh the cached signature so a subsequent `applyActiveToolsByName` with
|
|
1109
1170
|
// the same tool set does not re-rebuild on top of the explicit refresh we
|
|
@@ -1143,7 +1204,7 @@ export class SessionTools {
|
|
|
1143
1204
|
this.#host.captureMemoryPromotionSnapshot(previousBaseSystemPrompt);
|
|
1144
1205
|
const stablePrompt = [...previousBaseSystemPrompt, injected];
|
|
1145
1206
|
this.#baseSystemPrompt = stablePrompt;
|
|
1146
|
-
this.#
|
|
1207
|
+
this.#applyAgentSystemPrompt(stablePrompt);
|
|
1147
1208
|
return stablePrompt;
|
|
1148
1209
|
} catch (err) {
|
|
1149
1210
|
logger.debug("Memory backend beforeAgentStartPrompt failed", {
|
|
@@ -27,6 +27,15 @@ const QUESTION_PROMPT_RE =
|
|
|
27
27
|
const USER_DIRECTED_PROMPT_RE = /\b(?:you|your|we|our)\b/i;
|
|
28
28
|
const USER_RESPONSE_CUE_RE =
|
|
29
29
|
/^(?: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;
|
|
30
|
+
/**
|
|
31
|
+
* A trailing question mark is the universal signal that a line is a question, but
|
|
32
|
+
* the English word/pronoun gates above exist to filter incidental "?" out of prose
|
|
33
|
+
* (e.g. a TypeScript `foo?: string` tail). Non-English text has no cheap word list,
|
|
34
|
+
* yet any non-ASCII character in a "?"/"?"-terminated line reliably marks it as
|
|
35
|
+
* genuine prose — CJK/Japanese/Korean, Spanish `¿…?`, accented Latin — so treat it
|
|
36
|
+
* as a real user-directed question. Fixes non-Latin prompts going undetected (#7803).
|
|
37
|
+
*/
|
|
38
|
+
const NON_ASCII_TEXT_RE = /[^\x00-\x7F]/;
|
|
30
39
|
|
|
31
40
|
interface PromptLine {
|
|
32
41
|
text: string;
|
|
@@ -361,7 +370,8 @@ function isQuestionPromptLine(line: string): boolean {
|
|
|
361
370
|
return (
|
|
362
371
|
candidate.hadPromptLabel ||
|
|
363
372
|
QUESTION_PROMPT_RE.test(candidate.text) ||
|
|
364
|
-
USER_DIRECTED_PROMPT_RE.test(candidate.text)
|
|
373
|
+
USER_DIRECTED_PROMPT_RE.test(candidate.text) ||
|
|
374
|
+
NON_ASCII_TEXT_RE.test(candidate.text)
|
|
365
375
|
);
|
|
366
376
|
}
|
|
367
377
|
|
|
@@ -28,7 +28,7 @@ import type { ModelRegistry } from "../config/model-registry";
|
|
|
28
28
|
import { formatModelStringWithRouting, resolveModelOverride } from "../config/model-resolver";
|
|
29
29
|
|
|
30
30
|
import type { Settings } from "../config/settings";
|
|
31
|
-
import type {
|
|
31
|
+
import type { RetryErrorUpdate } from "../extensibility/shared-events";
|
|
32
32
|
import emptyStopRetryTemplate from "../prompts/system/empty-stop-retry.md" with { type: "text" };
|
|
33
33
|
import thinkingLoopRedirectTemplate from "../prompts/system/thinking-loop-redirect.md" with { type: "text" };
|
|
34
34
|
import unexpectedStopRetryTemplate from "../prompts/system/unexpected-stop-retry.md" with { type: "text" };
|
|
@@ -161,7 +161,7 @@ export interface TurnRecoveryOptions {
|
|
|
161
161
|
initialRetryFallback?: InitialRetryFallbackState;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
type
|
|
164
|
+
type PendingRetryError = {
|
|
165
165
|
entryId: string;
|
|
166
166
|
persistenceKey: string;
|
|
167
167
|
recovery: AssistantRetryRecoveryKind;
|
|
@@ -184,7 +184,7 @@ export class TurnRecovery {
|
|
|
184
184
|
#retryResolve: (() => void) | undefined;
|
|
185
185
|
#activeRetryFallback: ActiveRetryFallbackState | undefined;
|
|
186
186
|
#usageReserveApprovedSelector: string | undefined;
|
|
187
|
-
#
|
|
187
|
+
#pendingRetryErrors: PendingRetryError[] = [];
|
|
188
188
|
#usageLimitOutcomes = new WeakMap<AssistantMessage, Promise<UsageLimitOutcome>>();
|
|
189
189
|
#emptyStopRetryCount = 0;
|
|
190
190
|
#unexpectedStopRetryCount = 0;
|
|
@@ -250,14 +250,17 @@ export class TurnRecovery {
|
|
|
250
250
|
role: this.#activeRetryFallback.role,
|
|
251
251
|
});
|
|
252
252
|
}
|
|
253
|
-
const
|
|
253
|
+
const retryErrors = await this.#markPendingRetryErrors({
|
|
254
|
+
status: "recovered",
|
|
255
|
+
supersedingMessage: message,
|
|
256
|
+
});
|
|
254
257
|
await this.#host.emitSessionEvent({
|
|
255
258
|
type: "auto_retry_end",
|
|
256
259
|
success: true,
|
|
257
260
|
attempt: this.#retryAttempt,
|
|
258
|
-
|
|
261
|
+
retryErrors,
|
|
259
262
|
});
|
|
260
|
-
this.#
|
|
263
|
+
this.#clearPendingRetryErrors();
|
|
261
264
|
this.#retryAttempt = 0;
|
|
262
265
|
}
|
|
263
266
|
|
|
@@ -272,7 +275,7 @@ export class TurnRecovery {
|
|
|
272
275
|
attempt,
|
|
273
276
|
finalError: message.errorMessage,
|
|
274
277
|
});
|
|
275
|
-
this.#
|
|
278
|
+
this.#clearPendingRetryErrors();
|
|
276
279
|
}
|
|
277
280
|
|
|
278
281
|
/** Persists an otherwise skipped terminal empty error turn. */
|
|
@@ -305,8 +308,13 @@ export class TurnRecovery {
|
|
|
305
308
|
return this.#runRecoveryCompactionWithRollback(reason, message, allowDefer, options);
|
|
306
309
|
}
|
|
307
310
|
|
|
308
|
-
/**
|
|
309
|
-
|
|
311
|
+
/**
|
|
312
|
+
* Restores the configured primary after fallback cooldown expiry.
|
|
313
|
+
* @returns true when the active model was actually switched back to the
|
|
314
|
+
* primary, so callers can re-run the pre-send context-fit check against the
|
|
315
|
+
* reverted (possibly smaller) window before issuing the next request.
|
|
316
|
+
*/
|
|
317
|
+
maybeRestoreRetryFallbackPrimary(): Promise<boolean> {
|
|
310
318
|
return this.#maybeRestoreRetryFallbackPrimary();
|
|
311
319
|
}
|
|
312
320
|
|
|
@@ -381,8 +389,8 @@ export class TurnRecovery {
|
|
|
381
389
|
}
|
|
382
390
|
}
|
|
383
391
|
|
|
384
|
-
#
|
|
385
|
-
this.#
|
|
392
|
+
#clearPendingRetryErrors(): void {
|
|
393
|
+
this.#pendingRetryErrors = [];
|
|
386
394
|
}
|
|
387
395
|
|
|
388
396
|
/**
|
|
@@ -432,7 +440,7 @@ export class TurnRecovery {
|
|
|
432
440
|
return parts.join("; ");
|
|
433
441
|
}
|
|
434
442
|
|
|
435
|
-
async #
|
|
443
|
+
async #recordPendingRetryError(
|
|
436
444
|
message: AssistantMessage,
|
|
437
445
|
id: number,
|
|
438
446
|
options: { switchedCredential: boolean; switchedModel: boolean; delayMs: number },
|
|
@@ -451,11 +459,11 @@ export class TurnRecovery {
|
|
|
451
459
|
break;
|
|
452
460
|
}
|
|
453
461
|
if (!branchEntry) return;
|
|
454
|
-
if (this.#
|
|
462
|
+
if (this.#pendingRetryErrors.some(error => error.entryId === branchEntry.id)) return;
|
|
455
463
|
const rateLimited = AIError.is(id, AIError.Flag.UsageLimit);
|
|
456
464
|
const recovery = this.#retryRecoveryKind(id, options.switchedCredential, options.switchedModel, options.delayMs);
|
|
457
465
|
const note = this.#retryRecoveryNote(recovery, rateLimited);
|
|
458
|
-
this.#
|
|
466
|
+
this.#pendingRetryErrors.push({
|
|
459
467
|
entryId: branchEntry.id,
|
|
460
468
|
persistenceKey,
|
|
461
469
|
recovery,
|
|
@@ -464,24 +472,17 @@ export class TurnRecovery {
|
|
|
464
472
|
});
|
|
465
473
|
}
|
|
466
474
|
|
|
467
|
-
async #
|
|
468
|
-
|
|
475
|
+
async #markPendingRetryErrors(
|
|
476
|
+
completion: { status: "recovered"; supersedingMessage: AssistantMessage } | { status: "superseded" },
|
|
477
|
+
): Promise<RetryErrorUpdate[]> {
|
|
478
|
+
if (this.#pendingRetryErrors.length === 0) return [];
|
|
469
479
|
const branch = this.#host.sessionManager.getBranch();
|
|
470
480
|
const branchById = new Map<string, SessionEntry>();
|
|
471
481
|
for (const entry of branch) {
|
|
472
482
|
branchById.set(entry.id, entry);
|
|
473
483
|
}
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
timestamp: supersedingMessage.timestamp,
|
|
477
|
-
provider: supersedingMessage.provider,
|
|
478
|
-
model: supersedingMessage.model,
|
|
479
|
-
};
|
|
480
|
-
if (supersedingMessage.responseId) {
|
|
481
|
-
supersededBy.responseId = supersedingMessage.responseId;
|
|
482
|
-
}
|
|
483
|
-
const recoveredErrors: RecoveredRetryError[] = [];
|
|
484
|
-
for (const pending of this.#pendingRecoveredRetryErrors) {
|
|
484
|
+
const retryErrors: RetryErrorUpdate[] = [];
|
|
485
|
+
for (const pending of this.#pendingRetryErrors) {
|
|
485
486
|
let entry = branchById.get(pending.entryId);
|
|
486
487
|
if (entry?.type !== "message" || entry.message.role !== "assistant") {
|
|
487
488
|
entry = branch
|
|
@@ -495,27 +496,45 @@ export class TurnRecovery {
|
|
|
495
496
|
);
|
|
496
497
|
}
|
|
497
498
|
if (entry?.type !== "message" || entry.message.role !== "assistant") continue;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
499
|
+
let retryRecovery: AssistantRetryRecovery;
|
|
500
|
+
if (completion.status === "recovered") {
|
|
501
|
+
retryRecovery = {
|
|
502
|
+
kind: "auto-retry",
|
|
503
|
+
status: "recovered",
|
|
504
|
+
attempt: pending.attempt,
|
|
505
|
+
recoveredAt: new Date().toISOString(),
|
|
506
|
+
recovery: pending.recovery,
|
|
507
|
+
note: pending.note,
|
|
508
|
+
supersededBy: {
|
|
509
|
+
timestamp: completion.supersedingMessage.timestamp,
|
|
510
|
+
...(completion.supersedingMessage.responseId === undefined
|
|
511
|
+
? {}
|
|
512
|
+
: { responseId: completion.supersedingMessage.responseId }),
|
|
513
|
+
provider: completion.supersedingMessage.provider,
|
|
514
|
+
model: completion.supersedingMessage.model,
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
} else {
|
|
518
|
+
retryRecovery = {
|
|
519
|
+
kind: "auto-retry",
|
|
520
|
+
status: "superseded",
|
|
521
|
+
attempt: pending.attempt,
|
|
522
|
+
recovery: pending.recovery,
|
|
523
|
+
note: pending.note,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
507
526
|
entry.message.retryRecovery = retryRecovery;
|
|
508
|
-
|
|
527
|
+
retryErrors.push({
|
|
509
528
|
entryId: entry.id,
|
|
510
529
|
persistenceKey: pending.persistenceKey,
|
|
511
530
|
note: retryRecovery.note,
|
|
512
531
|
retryRecovery,
|
|
513
532
|
});
|
|
514
533
|
}
|
|
515
|
-
if (
|
|
534
|
+
if (retryErrors.length > 0) {
|
|
516
535
|
await this.#host.sessionManager.rewriteEntries();
|
|
517
536
|
}
|
|
518
|
-
return
|
|
537
|
+
return retryErrors;
|
|
519
538
|
}
|
|
520
539
|
|
|
521
540
|
async #handleEmptyAssistantStop(assistantMessage: AssistantMessage): Promise<boolean> {
|
|
@@ -547,7 +566,7 @@ export class TurnRecovery {
|
|
|
547
566
|
attempt: this.#retryAttempt > 0 ? this.#retryAttempt : attempts,
|
|
548
567
|
finalError,
|
|
549
568
|
});
|
|
550
|
-
this.#
|
|
569
|
+
this.#clearPendingRetryErrors();
|
|
551
570
|
this.#retryAttempt = 0;
|
|
552
571
|
this.resolveRetry();
|
|
553
572
|
// A zero-content turn carries no transcript value, while its provider usage
|
|
@@ -1302,7 +1321,7 @@ export class TurnRecovery {
|
|
|
1302
1321
|
return false;
|
|
1303
1322
|
}
|
|
1304
1323
|
if (this.#host.model() !== candidate) return false;
|
|
1305
|
-
this.#host.sessionManager.appendModelChange(candidateSelector, EPHEMERAL_MODEL_CHANGE_ROLE);
|
|
1324
|
+
this.#host.sessionManager.appendModelChange(candidateSelector, EPHEMERAL_MODEL_CHANGE_ROLE, true);
|
|
1306
1325
|
this.#host.settings.getStorage()?.recordModelUsage(candidateSelector);
|
|
1307
1326
|
this.#host.setThinkingLevel(nextThinkingLevel);
|
|
1308
1327
|
if (!this.#activeRetryFallback) {
|
|
@@ -1422,7 +1441,7 @@ export class TurnRecovery {
|
|
|
1422
1441
|
if (!apiKey) return false;
|
|
1423
1442
|
const baseSelector = formatModelStringWithRouting(baseModel);
|
|
1424
1443
|
await this.#host.setModelWithProviderSessionReset(baseModel);
|
|
1425
|
-
this.#host.sessionManager.appendModelChange(baseSelector, EPHEMERAL_MODEL_CHANGE_ROLE);
|
|
1444
|
+
this.#host.sessionManager.appendModelChange(baseSelector, EPHEMERAL_MODEL_CHANGE_ROLE, true);
|
|
1426
1445
|
this.#host.settings.getStorage()?.recordModelUsage(baseSelector);
|
|
1427
1446
|
await this.#host.emitSessionEvent({
|
|
1428
1447
|
type: "retry_fallback_applied",
|
|
@@ -1433,10 +1452,10 @@ export class TurnRecovery {
|
|
|
1433
1452
|
return true;
|
|
1434
1453
|
}
|
|
1435
1454
|
|
|
1436
|
-
async #maybeRestoreRetryFallbackPrimary(): Promise<
|
|
1437
|
-
if (!this.#activeRetryFallback) return;
|
|
1438
|
-
if (this.#activeRetryFallback.pinned) return;
|
|
1439
|
-
if (this.#getRetryFallbackRevertPolicy() !== "cooldown-expiry") return;
|
|
1455
|
+
async #maybeRestoreRetryFallbackPrimary(): Promise<boolean> {
|
|
1456
|
+
if (!this.#activeRetryFallback) return false;
|
|
1457
|
+
if (this.#activeRetryFallback.pinned) return false;
|
|
1458
|
+
if (this.#getRetryFallbackRevertPolicy() !== "cooldown-expiry") return false;
|
|
1440
1459
|
|
|
1441
1460
|
const {
|
|
1442
1461
|
originalSelector: originalSelectorRaw,
|
|
@@ -1446,19 +1465,19 @@ export class TurnRecovery {
|
|
|
1446
1465
|
const originalSelector = parseRetryFallbackSelector(originalSelectorRaw, this.#host.modelRegistry);
|
|
1447
1466
|
if (!originalSelector) {
|
|
1448
1467
|
this.clearActiveRetryFallback();
|
|
1449
|
-
return;
|
|
1468
|
+
return false;
|
|
1450
1469
|
}
|
|
1451
1470
|
|
|
1452
1471
|
const currentModel = this.#host.model();
|
|
1453
|
-
if (!currentModel) return;
|
|
1472
|
+
if (!currentModel) return false;
|
|
1454
1473
|
const currentSelector = formatRetryFallbackSelector(currentModel, this.#host.thinkingLevel());
|
|
1455
1474
|
if (currentSelector === originalSelector.raw) {
|
|
1456
1475
|
if (!this.isRetryFallbackSelectorSuppressed(originalSelector)) {
|
|
1457
1476
|
this.clearActiveRetryFallback();
|
|
1458
1477
|
}
|
|
1459
|
-
return;
|
|
1478
|
+
return false;
|
|
1460
1479
|
}
|
|
1461
|
-
if (this.isRetryFallbackSelectorSuppressed(originalSelector)) return;
|
|
1480
|
+
if (this.isRetryFallbackSelectorSuppressed(originalSelector)) return false;
|
|
1462
1481
|
|
|
1463
1482
|
const resolvedPrimary = resolveModelOverride(
|
|
1464
1483
|
[originalSelector.raw],
|
|
@@ -1467,9 +1486,9 @@ export class TurnRecovery {
|
|
|
1467
1486
|
);
|
|
1468
1487
|
const primaryModel =
|
|
1469
1488
|
resolvedPrimary.model ?? this.#host.modelRegistry.find(originalSelector.provider, originalSelector.id);
|
|
1470
|
-
if (!primaryModel) return;
|
|
1489
|
+
if (!primaryModel) return false;
|
|
1471
1490
|
const apiKey = await this.#host.modelRegistry.getApiKey(primaryModel, this.#host.sessionId());
|
|
1472
|
-
if (!apiKey) return;
|
|
1491
|
+
if (!apiKey) return false;
|
|
1473
1492
|
|
|
1474
1493
|
const currentThinkingLevel = this.#host.configuredThinkingLevel();
|
|
1475
1494
|
const thinkingToApply =
|
|
@@ -1480,6 +1499,7 @@ export class TurnRecovery {
|
|
|
1480
1499
|
this.#host.settings.getStorage()?.recordModelUsage(primarySelector);
|
|
1481
1500
|
this.#host.setThinkingLevel(thinkingToApply);
|
|
1482
1501
|
this.clearActiveRetryFallback();
|
|
1502
|
+
return true;
|
|
1483
1503
|
}
|
|
1484
1504
|
|
|
1485
1505
|
#parseRetryAfterMsFromError(errorMessage: string): number | undefined {
|
|
@@ -1581,27 +1601,27 @@ export class TurnRecovery {
|
|
|
1581
1601
|
|
|
1582
1602
|
const errorMessage = message.errorMessage || "Unknown error";
|
|
1583
1603
|
const id = this.#classifyRetryMessage(message);
|
|
1604
|
+
const rateLimitReason = parseRateLimitReason(errorMessage);
|
|
1584
1605
|
const staleOpenAIResponsesReplayError = AIError.is(id, AIError.Flag.StaleResponsesItem);
|
|
1585
1606
|
const recordedUsageLimitOutcome = await this.#usageLimitOutcomes.get(message);
|
|
1586
1607
|
const parsedRetryAfterMs = this.#parseRetryAfterMsFromError(errorMessage);
|
|
1587
1608
|
let delayMs = staleOpenAIResponsesReplayError
|
|
1588
1609
|
? 0
|
|
1589
1610
|
: calculateRetryBackoffDelayMs(retrySettings.baseDelayMs, this.#retryAttempt);
|
|
1590
|
-
//
|
|
1591
|
-
//
|
|
1592
|
-
//
|
|
1593
|
-
//
|
|
1594
|
-
//
|
|
1595
|
-
//
|
|
1596
|
-
// body merely mentions concurrency is still a usage limit (handled below),
|
|
1597
|
-
// so gate on the flag matching the rotation decision.
|
|
1611
|
+
// Transient rate/concurrency caps stay on the same credential, but must
|
|
1612
|
+
// honor their reason-specific windows. The default exponential base
|
|
1613
|
+
// (≈500ms, capped at 8s) otherwise re-hits the cap and burns the retry
|
|
1614
|
+
// budget before either window can clear. An explicit provider
|
|
1615
|
+
// retry-after is authoritative in both directions, so the heuristic
|
|
1616
|
+
// window only applies when the error carries no parsed timing.
|
|
1598
1617
|
if (
|
|
1599
1618
|
!staleOpenAIResponsesReplayError &&
|
|
1600
1619
|
!AIError.is(id, AIError.Flag.UsageLimit) &&
|
|
1601
|
-
|
|
1620
|
+
parsedRetryAfterMs === undefined &&
|
|
1621
|
+
(rateLimitReason === "CONCURRENT_LIMIT" || rateLimitReason === "RATE_LIMIT_EXCEEDED")
|
|
1602
1622
|
) {
|
|
1603
|
-
const
|
|
1604
|
-
if (
|
|
1623
|
+
const reasonBackoffMs = calculateRateLimitBackoffMs(rateLimitReason);
|
|
1624
|
+
if (reasonBackoffMs > delayMs) delayMs = reasonBackoffMs;
|
|
1605
1625
|
}
|
|
1606
1626
|
let switchedCredential = false;
|
|
1607
1627
|
let switchedModel = false;
|
|
@@ -1677,16 +1697,18 @@ export class TurnRecovery {
|
|
|
1677
1697
|
|
|
1678
1698
|
if (retryBudgetExhausted) {
|
|
1679
1699
|
if (!switchedModel) {
|
|
1700
|
+
const attempt = this.#retryAttempt - 1;
|
|
1701
|
+
message.errorMessage = `Retry budget exhausted after ${attempt} ${attempt === 1 ? "retry" : "retries"}: ${errorMessage}`;
|
|
1680
1702
|
await this.persistTerminalEmptyErrorTurn(message);
|
|
1681
|
-
|
|
1682
|
-
// final failure and reset.
|
|
1703
|
+
const retryErrors = await this.#markPendingRetryErrors({ status: "superseded" });
|
|
1683
1704
|
await this.#host.emitSessionEvent({
|
|
1684
1705
|
type: "auto_retry_end",
|
|
1685
1706
|
success: false,
|
|
1686
|
-
attempt
|
|
1687
|
-
finalError:
|
|
1707
|
+
attempt,
|
|
1708
|
+
finalError: errorMessage,
|
|
1709
|
+
retryErrors,
|
|
1688
1710
|
});
|
|
1689
|
-
this.#
|
|
1711
|
+
this.#clearPendingRetryErrors();
|
|
1690
1712
|
this.#retryAttempt = 0;
|
|
1691
1713
|
this.resolveRetry(); // Resolve so waitForRetry() completes
|
|
1692
1714
|
return false;
|
|
@@ -1711,7 +1733,7 @@ export class TurnRecovery {
|
|
|
1711
1733
|
attempt: this.#retryAttempt - 1,
|
|
1712
1734
|
finalError: errorMessage,
|
|
1713
1735
|
});
|
|
1714
|
-
this.#
|
|
1736
|
+
this.#clearPendingRetryErrors();
|
|
1715
1737
|
}
|
|
1716
1738
|
this.#retryAttempt = 0;
|
|
1717
1739
|
this.resolveRetry();
|
|
@@ -1736,7 +1758,7 @@ export class TurnRecovery {
|
|
|
1736
1758
|
attempt: this.#retryAttempt - 1,
|
|
1737
1759
|
finalError: errorMessage,
|
|
1738
1760
|
});
|
|
1739
|
-
this.#
|
|
1761
|
+
this.#clearPendingRetryErrors();
|
|
1740
1762
|
}
|
|
1741
1763
|
this.#retryAttempt = 0;
|
|
1742
1764
|
this.resolveRetry();
|
|
@@ -1761,12 +1783,12 @@ export class TurnRecovery {
|
|
|
1761
1783
|
attempt,
|
|
1762
1784
|
finalError: `Provider requested ${delayMs}ms wait, exceeds retry.maxDelayMs (${maxDelayMs}ms). Original error: ${errorMessage}`,
|
|
1763
1785
|
});
|
|
1764
|
-
this.#
|
|
1786
|
+
this.#clearPendingRetryErrors();
|
|
1765
1787
|
this.resolveRetry();
|
|
1766
1788
|
return false;
|
|
1767
1789
|
}
|
|
1768
1790
|
|
|
1769
|
-
await this.#
|
|
1791
|
+
await this.#recordPendingRetryError(message, id, { switchedCredential, switchedModel, delayMs });
|
|
1770
1792
|
|
|
1771
1793
|
await this.#host.emitSessionEvent({
|
|
1772
1794
|
type: "auto_retry_start",
|
|
@@ -1808,7 +1830,7 @@ export class TurnRecovery {
|
|
|
1808
1830
|
attempt,
|
|
1809
1831
|
finalError: "Retry cancelled",
|
|
1810
1832
|
});
|
|
1811
|
-
this.#
|
|
1833
|
+
this.#clearPendingRetryErrors();
|
|
1812
1834
|
this.resolveRetry();
|
|
1813
1835
|
return false;
|
|
1814
1836
|
}
|
|
@@ -1881,7 +1903,7 @@ export class TurnRecovery {
|
|
|
1881
1903
|
attempt,
|
|
1882
1904
|
finalError: `Retry continuation failed locally: ${localError}. Original error: ${message.errorMessage ?? "Unknown error"}`,
|
|
1883
1905
|
});
|
|
1884
|
-
this.#
|
|
1906
|
+
this.#clearPendingRetryErrors();
|
|
1885
1907
|
this.resolveRetry();
|
|
1886
1908
|
}
|
|
1887
1909
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AvailableCommand } from "@
|
|
1
|
+
import type { AvailableCommand } from "@oh-my-pi/pi-utils/acp";
|
|
2
2
|
import { BUILTIN_SLASH_COMMANDS_INTERNAL, lookupBuiltinSlashCommand } from "./builtin-registry";
|
|
3
3
|
import { parseSlashCommand } from "./helpers/parse";
|
|
4
4
|
import type { AcpBuiltinSlashCommandResult, SlashCommandRuntime } from "./types";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AvailableCommand } from "@
|
|
1
|
+
import type { AvailableCommand } from "@oh-my-pi/pi-utils/acp";
|
|
2
2
|
import type { SkillsSettings } from "../config/settings";
|
|
3
3
|
import type { LoadedCustomCommand } from "../extensibility/custom-commands";
|
|
4
4
|
import type { ExtensionRunner } from "../extensibility/extensions";
|