@oh-my-pi/pi-coding-agent 16.3.11 → 16.3.13
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 +65 -0
- package/dist/cli.js +3176 -3087
- package/dist/types/advisor/runtime.d.ts +11 -0
- package/dist/types/config/keybindings.d.ts +9 -4
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/settings-schema.d.ts +6 -0
- package/dist/types/config/settings.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +11 -2
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/__tests__/agent-protocol-nested.test.d.ts +1 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +7 -5
- package/dist/types/mnemopi/state.d.ts +7 -3
- package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -1
- package/dist/types/modes/components/read-tool-group.d.ts +1 -0
- package/dist/types/modes/github-ref-autocomplete.d.ts +35 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +11 -5
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +0 -12
- package/dist/types/modes/workflow.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +8 -4
- package/dist/types/system-prompt.d.ts +1 -1
- package/dist/types/tools/bash-interactive.d.ts +1 -1
- package/dist/types/tools/bash-skill-urls.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +2 -1
- package/dist/types/tools/browser/launch.d.ts +1 -0
- package/dist/types/tools/grep.d.ts +2 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/path-utils.d.ts +24 -0
- package/dist/types/tools/read.d.ts +3 -0
- package/dist/types/tools/renderers.d.ts +12 -5
- package/dist/types/tools/ssh.d.ts +4 -1
- package/dist/types/tools/write.d.ts +1 -0
- package/dist/types/utils/local-date.d.ts +2 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +145 -0
- package/src/advisor/runtime.ts +19 -0
- package/src/config/api-key-resolver.ts +7 -2
- package/src/config/keybindings.ts +62 -10
- package/src/config/model-registry.ts +94 -20
- package/src/config/settings-schema.ts +11 -1
- package/src/config/settings.ts +59 -21
- package/src/discovery/builtin.ts +2 -1
- package/src/discovery/claude-plugins.ts +167 -46
- package/src/discovery/helpers.ts +16 -1
- package/src/edit/renderer.ts +20 -6
- package/src/eval/js/worker-core.ts +163 -6
- package/src/exec/bash-executor.ts +14 -9
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/extensions/types.ts +13 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +6 -2
- package/src/extensibility/plugins/marketplace/fetcher.ts +15 -14
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +68 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/registry-helpers.ts +9 -6
- package/src/mnemopi/state.ts +19 -5
- package/src/modes/acp/acp-agent.ts +69 -8
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/components/model-selector.ts +30 -6
- package/src/modes/components/read-tool-group.ts +5 -1
- package/src/modes/components/settings-defs.ts +1 -1
- package/src/modes/components/status-line/component.ts +14 -2
- package/src/modes/components/tool-execution.ts +28 -24
- package/src/modes/controllers/command-controller.ts +13 -23
- package/src/modes/controllers/event-controller.ts +12 -12
- package/src/modes/controllers/extension-ui-controller.test.ts +16 -0
- package/src/modes/controllers/extension-ui-controller.ts +7 -35
- package/src/modes/controllers/input-controller.ts +23 -57
- package/src/modes/controllers/mcp-command-controller.ts +10 -9
- package/src/modes/controllers/selector-controller.ts +16 -5
- package/src/modes/github-ref-autocomplete.ts +75 -0
- package/src/modes/interactive-mode.ts +97 -12
- package/src/modes/prompt-action-autocomplete.ts +35 -0
- package/src/modes/rpc/rpc-client.ts +42 -13
- package/src/modes/rpc/rpc-mode.ts +21 -19
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/context-usage.ts +58 -5
- package/src/modes/utils/hotkeys-markdown.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +2 -2
- package/src/modes/workflow.ts +14 -8
- package/src/prompts/agents/plan.md +0 -1
- package/src/prompts/agents/reviewer.md +0 -1
- package/src/prompts/system/plan-mode-active.md +5 -2
- package/src/prompts/system/system-prompt.md +1 -2
- package/src/prompts/system/workflow-notice.md +69 -50
- package/src/prompts/tools/bash.md +18 -7
- package/src/prompts/tools/grep.md +2 -1
- package/src/prompts/tools/memory-edit.md +2 -0
- package/src/prompts/tools/read.md +4 -3
- package/src/sdk.ts +11 -0
- package/src/session/agent-session.ts +136 -19
- package/src/system-prompt.ts +3 -2
- package/src/tools/bash-interactive.ts +1 -1
- package/src/tools/bash-skill-urls.ts +39 -7
- package/src/tools/bash.ts +69 -39
- package/src/tools/browser/launch.ts +31 -4
- package/src/tools/grep.ts +105 -21
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +11 -0
- package/src/tools/memory-edit.ts +3 -1
- package/src/tools/path-utils.ts +46 -1
- package/src/tools/read.ts +135 -57
- package/src/tools/renderers.ts +13 -5
- package/src/tools/ssh.ts +10 -3
- package/src/tools/tts.ts +1 -1
- package/src/tools/write.ts +26 -0
- package/src/utils/local-date.ts +7 -0
- package/src/utils/open.ts +36 -10
|
@@ -6,14 +6,22 @@ The shell invokes **real binaries** with simple args. It is NOT full GNU Bash.
|
|
|
6
6
|
|
|
7
7
|
Use bash ONLY for: a single binary call, or one short pipeline that COMPUTES a fact and does not depend on shell-specific regex/quoting (`wc -l`, `sort | uniq -c`, `comm`, `diff`, a checksum, `git status`).
|
|
8
8
|
|
|
9
|
-
Anything below → `eval` cell, not bash:
|
|
9
|
+
{{#if hasEval}}Anything below → `eval` cell, not bash:
|
|
10
10
|
- Inline interpreter scripts (`-e`/`-c`/`--eval`) when an eval runtime exists for that language
|
|
11
11
|
- Heredocs (`<<EOF`), `while`/`for`/`if`/`case` shell control flow
|
|
12
12
|
- `$(…)` command substitution nested inside another command
|
|
13
13
|
- Pipelines with more than two stages, or stages that need control flow or quote/JSON escaping
|
|
14
14
|
- Multiline commands, `&&`-chains mixing control flow
|
|
15
15
|
- Quote/JSON escaping that fights the shell
|
|
16
|
-
|
|
16
|
+
{{else}}Anything below means you are writing a shell program, not invoking one. Prefer a purpose-built tool, a checked-in script, or a single repo command instead:
|
|
17
|
+
- Inline interpreter scripts (`-e`/`-c`/`--eval`)
|
|
18
|
+
- Heredocs (`<<EOF`), `while`/`for`/`if`/`case` shell control flow
|
|
19
|
+
- `$(…)` command substitution nested inside another command
|
|
20
|
+
- Pipelines with more than two stages, or stages that need control flow or quote/JSON escaping
|
|
21
|
+
- Multiline commands, `&&`-chains mixing control flow
|
|
22
|
+
- Quote/JSON escaping that fights the shell
|
|
23
|
+
{{/if}}
|
|
24
|
+
{{#if hasGrep}}- GNU grep BRE extensions are not guaranteed in the embedded shell: use `grep -E 'json|tool'` for alternation instead of `grep 'json\|tool'`; use the built-in `grep` tool with `pattern: "json|tool"` (Rust regex, so `\bword\b` works there){{#if hasEval}}, or `eval` for exact text processing{{/if}}.{{else}}- GNU grep BRE extensions are not guaranteed in the embedded shell: use `grep -E 'json|tool'` for alternation instead of `grep 'json\|tool'`{{#if hasEval}}, or use `eval` for exact text processing{{/if}}.{{/if}}
|
|
17
25
|
|
|
18
26
|
<instruction>
|
|
19
27
|
- `cwd` sets the working dir, not `cd dir && …`
|
|
@@ -23,14 +31,17 @@ Anything below → `eval` cell, not bash:
|
|
|
23
31
|
- `;` only when later commands should run despite earlier failures
|
|
24
32
|
- Multiple bash calls per message run concurrently. NEVER split order-dependent commands across parallel calls — chain with `&&` in one call.
|
|
25
33
|
- Internal URIs (`skill://`, `agent://`, …) auto-resolve to FS paths
|
|
26
|
-
- Need exact pipeline semantics (`cmd | head`, multi-stage filtering) or output truncation? Prefer `eval` and process the stream directly.
|
|
34
|
+
{{#if hasEval}}- Need exact pipeline semantics (`cmd | head`, multi-stage filtering) or output truncation? Prefer `eval` and process the stream directly.{{else}}- Need exact pipeline semantics (`cmd | head`, multi-stage filtering) or output truncation? Use a checked-in script, purpose-built tool, or single command that owns the output shape.{{/if}}
|
|
27
35
|
{{#if asyncEnabled}}
|
|
28
36
|
- `async: true` for long-running commands when you don't need immediate output: returns a background job ID; result delivered as a follow-up.
|
|
29
37
|
{{/if}}
|
|
30
38
|
</instruction>
|
|
31
39
|
|
|
32
40
|
<critical>
|
|
33
|
-
- The embedded shell invokes real binaries with simple args; it is NOT full GNU Bash. Loops, conditionals, heredocs, inline interpreter scripts (`-e`/`-c`/`--eval`) when an eval runtime exists, several piped stages, exact pipeline semantics, or quote/JSON escaping mean you're writing a program → use `eval` cells: restartable, stateful, and free of shell-quoting traps.
|
|
41
|
+
{{#if hasEval}}- The embedded shell invokes real binaries with simple args; it is NOT full GNU Bash and NOT a scripting surface. Loops, conditionals, heredocs, inline interpreter scripts (`-e`/`-c`/`--eval`) when an eval runtime exists, several piped stages, exact pipeline semantics, or quote/JSON escaping mean you're writing a program → use `eval` cells: restartable, stateful, and free of shell-quoting traps.{{else}}- The embedded shell invokes real binaries with simple args; it is NOT full GNU Bash and NOT a scripting surface. Loops, conditionals, heredocs, inline interpreter scripts, several piped stages, exact pipeline semantics, or quote/JSON escaping mean you're writing a shell program; use a purpose-built tool or checked-in script instead.{{/if}}
|
|
42
|
+
{{#if hasGrep}}- NEVER shell out to search content or files: `grep/rg` → `grep`.{{else}}- Avoid shelling out for broad content search; use an active search/read tool when one is available.{{/if}}
|
|
43
|
+
{{#if hasRead}}{{#if hasGlob}}- NEVER use `ls` or `find` to list or locate files — `ls` → `read` (a directory path lists entries), `find` → the `glob` tool (globbing). This is non-negotiable, even for a single quick listing.{{else}}- Prefer `read` for known file and directory reads. Only use shell listing when no file-listing tool is active.{{/if}}{{else}}{{#if hasGlob}}- Prefer `glob` for file discovery; avoid `find` when `glob` is active.{{else}}- If no file read/listing tool is active, keep shell inspection narrow and state that limitation.{{/if}}{{/if}}
|
|
44
|
+
- Avoid head/tail/redirections: stderr already merged; long output auto-truncated, FULL capture kept at `artifact://<id>`.
|
|
34
45
|
</critical>
|
|
35
46
|
|
|
36
47
|
<output>
|
|
@@ -41,9 +52,9 @@ Anything below → `eval` cell, not bash:
|
|
|
41
52
|
{{#if asyncEnabled}}
|
|
42
53
|
# Timeout and async
|
|
43
54
|
|
|
44
|
-
- `timeout` is seconds
|
|
45
|
-
- `async: true` defers only reporting — it does NOT extend
|
|
46
|
-
- Need >3600s?
|
|
55
|
+
- `timeout` is seconds; nonzero values are clamped to `1..3600` and the process is killed on elapse. Set `timeout: 0` only for commands that must run until completion or explicit cancellation.
|
|
56
|
+
- `async: true` defers only reporting — it does NOT extend a nonzero timeout; use `timeout: 0` when a daemon or watcher must be cancellation-owned.
|
|
57
|
+
- Need a daemon or >3600s run? Use `async: true` with `timeout: 0` when the harness should keep it alive until cancellation, or detach/manage lifecycle yourself (`cmd &`, supervisor, self-restarting script). The shell session persists across calls.
|
|
47
58
|
{{/if}}
|
|
48
59
|
{{#if autoBackgroundEnabled}}
|
|
49
60
|
|
|
@@ -2,7 +2,8 @@ Greps files using regex.
|
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- Rust regex (RE2-style): alternation is `foo|bar`, not GNU BRE-style `foo\|bar`; Rust word boundaries like `\bword\b` are supported. Use line anchors or post-filters instead of lookaround/backreferences.
|
|
5
|
-
- `path`: SHOULD scope to a known path (e.g. `src`); pass several as a delimited list (`src; tests`).
|
|
5
|
+
- `path`: SHOULD scope to a known path (e.g. `src`); pass several as a delimited list (`src; tests`). Use `selector` only for line-number filtering, never path/root selection (`"/"` belongs in `path`).
|
|
6
|
+
- Literal colon filename + line range? Use `selector` (e.g. `{"path":"test:1-2","selector":"1-2"}`), not recursive `path:"test:1-2:1-2"`.
|
|
6
7
|
- Cross-line patterns detected from literal `\n` or `\\n` in `pattern`.
|
|
7
8
|
</instruction>
|
|
8
9
|
|
|
@@ -5,6 +5,8 @@ Use only with ids returned by the `recall` tool. Operations:
|
|
|
5
5
|
- `forget`: permanently delete a working memory.
|
|
6
6
|
- `invalidate`: softly supersede a working or episodic memory, optionally pointing at `replacement_id`.
|
|
7
7
|
|
|
8
|
+
Fact ids (recall results marked `[facts]`) are read-only: inspect them with `read memory://<id>`; every edit op on a fact id returns `not_editable`.
|
|
9
|
+
|
|
8
10
|
Prefer `invalidate` when a memory became stale but its history may still be useful. Use `forget` only for content that should be hard-deleted.
|
|
9
11
|
|
|
10
12
|
**Always read the full memory before `update`.** Recall results are clipped previews (the trailing `…` marks a truncation and `full_length` reports the original size); `update` replaces content wholesale, so overwriting the preview would delete the unseen tail. Fetch the row first with `read memory://<id>`, then pass the merged content in `content`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Read files, directories, archives, SQLite, images, documents, internal resources, and web URLs via
|
|
1
|
+
Read files, directories, archives, SQLite, images, documents, internal resources, and web URLs via `path` plus optional `selector`.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- SHOULD parallelize independent reads.
|
|
@@ -7,7 +7,8 @@ Read files, directories, archives, SQLite, images, documents, internal resources
|
|
|
7
7
|
|
|
8
8
|
## Parameters
|
|
9
9
|
|
|
10
|
-
- `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`, `ssh://`), or URL.
|
|
10
|
+
- `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`, `ssh://`), or URL. Inline `:<sel>` still works for ranges/modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).
|
|
11
|
+
- `selector` — optional selector without leading `:` (e.g. `"50-200"`, `"raw"`, `"raw:50-100"`, `"conflicts"`). Use when `path` contains literal colons: `{"path":"test:1-2","selector":"1-2"}`.
|
|
11
12
|
|
|
12
13
|
## Selectors
|
|
13
14
|
|
|
@@ -72,6 +73,6 @@ All URI schemes take the same line selectors. `artifact://<id>` recovers spilled
|
|
|
72
73
|
`ssh://host/<absolute-path>` reads a remote text file (UTF-8, ≤1 MiB) or lists a directory one level deep, on a pre-configured SSH host or `~/.ssh/config` alias; `ssh://host/` lists the remote root and bare `ssh://` lists the configured hosts. Files are also writable via `write` and searchable via `search`; a directory only lists (`search` refuses a directory, `write` refuses to overwrite one). A literal `:`, `?`, or `#` in the remote path must be percent-encoded (`%3A`/`%3F`/`%23`) — a trailing `:sel` is read as a line selector, and `?`/`#` start a URL query/fragment. Requires a POSIX login shell (`sh`/`bash`/`zsh`); a Windows host or a non-POSIX shell (fish, csh/tcsh) is rejected — use the `ssh` tool there.
|
|
73
74
|
|
|
74
75
|
<critical>
|
|
75
|
-
-
|
|
76
|
+
- Literal colon filename + selector? Use `selector`, not recursive `path:"file:sel:sel"`.
|
|
76
77
|
- Summary footer names elided ranges? Re-issue ONLY those ranges. NEVER guess `..`/`…` content.
|
|
77
78
|
</critical>
|
package/src/sdk.ts
CHANGED
|
@@ -1525,10 +1525,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1525
1525
|
// entries capture it at fetch time and are dropped at injection if a newer
|
|
1526
1526
|
// mutation (any tool) bumped it in the meantime.
|
|
1527
1527
|
const fileMutationVersions = new Map<string, number>();
|
|
1528
|
+
const activeToolNames = new Set<string>();
|
|
1529
|
+
const setActiveToolNames = (names: Iterable<string>): void => {
|
|
1530
|
+
activeToolNames.clear();
|
|
1531
|
+
for (const name of names) {
|
|
1532
|
+
activeToolNames.add(name);
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1528
1535
|
const toolSession: ToolSession = {
|
|
1529
1536
|
get cwd() {
|
|
1530
1537
|
return sessionManager.getCwd();
|
|
1531
1538
|
},
|
|
1539
|
+
isToolActive: name => activeToolNames.has(name),
|
|
1540
|
+
setActiveToolNames,
|
|
1532
1541
|
hasUI: options.hasUI ?? false,
|
|
1533
1542
|
enableLsp,
|
|
1534
1543
|
get hasEditTool() {
|
|
@@ -2558,6 +2567,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2558
2567
|
});
|
|
2559
2568
|
hasRegistered = true;
|
|
2560
2569
|
|
|
2570
|
+
setActiveToolNames(initialToolNames);
|
|
2561
2571
|
const { systemPrompt } = await logger.time(
|
|
2562
2572
|
"buildSystemPrompt",
|
|
2563
2573
|
rebuildSystemPrompt,
|
|
@@ -2852,6 +2862,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2852
2862
|
rebuildSystemPrompt,
|
|
2853
2863
|
reloadSshTool,
|
|
2854
2864
|
requestedToolNames: requestedToolNameSet,
|
|
2865
|
+
setActiveToolNames,
|
|
2855
2866
|
getMcpServerInstructions: mcpManager
|
|
2856
2867
|
? () => {
|
|
2857
2868
|
const raw = mcpManager.getServerInstructions();
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type AsideMessage,
|
|
36
36
|
type CompactionSummaryMessage,
|
|
37
37
|
countTokens,
|
|
38
|
+
createToolScopedAbortReason,
|
|
38
39
|
resolveTelemetry,
|
|
39
40
|
type StreamFn,
|
|
40
41
|
ThinkingLevel,
|
|
@@ -108,6 +109,7 @@ import {
|
|
|
108
109
|
clearAnthropicFastModeFallback,
|
|
109
110
|
deriveClaudeDeviceId,
|
|
110
111
|
Effort,
|
|
112
|
+
isUsageLimitOutcome,
|
|
111
113
|
parseRateLimitReason,
|
|
112
114
|
realizesPriorityServiceTier,
|
|
113
115
|
resolveModelServiceTier,
|
|
@@ -124,6 +126,7 @@ import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
|
|
|
124
126
|
import { MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
|
|
125
127
|
import {
|
|
126
128
|
escapeXmlText,
|
|
129
|
+
extractHttpStatusFromError,
|
|
127
130
|
extractRetryHint,
|
|
128
131
|
formatDuration,
|
|
129
132
|
getAgentDbPath,
|
|
@@ -179,7 +182,12 @@ import { MODEL_ROLE_IDS, MODEL_ROLES } from "../config/model-roles";
|
|
|
179
182
|
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
|
|
180
183
|
import { buildServiceTierByFamily, serviceTierForAllFamilies, serviceTierSettingToTier } from "../config/service-tier";
|
|
181
184
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
182
|
-
import {
|
|
185
|
+
import {
|
|
186
|
+
getDefault,
|
|
187
|
+
onAppendOnlyModeChanged,
|
|
188
|
+
onModelRolesChanged,
|
|
189
|
+
validateProviderMaxInFlightRequests,
|
|
190
|
+
} from "../config/settings";
|
|
183
191
|
import { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
|
|
184
192
|
import { loadCapability } from "../discovery";
|
|
185
193
|
import { expandApplyPatchToEntries, normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../edit";
|
|
@@ -237,7 +245,7 @@ import { theme } from "../modes/theme/theme";
|
|
|
237
245
|
import { parseTurnBudget } from "../modes/turn-budget";
|
|
238
246
|
import { containsUltrathink, ULTRATHINK_NOTICE } from "../modes/ultrathink";
|
|
239
247
|
import { computeNonMessageBreakdown, computeNonMessageTokens } from "../modes/utils/context-usage";
|
|
240
|
-
import { containsWorkflow,
|
|
248
|
+
import { containsWorkflow, renderWorkflowNotice } from "../modes/workflow";
|
|
241
249
|
import { createPlanReadMatcher } from "../plan-mode/plan-protection";
|
|
242
250
|
import type { PlanModeState } from "../plan-mode/state";
|
|
243
251
|
import advisorSystemPrompt from "../prompts/advisor/system.md" with { type: "text" };
|
|
@@ -314,6 +322,7 @@ import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
|
314
322
|
import { extractFileMentions, generateFileMentionMessages } from "../utils/file-mentions";
|
|
315
323
|
import { normalizeModelContextImages } from "../utils/image-loading";
|
|
316
324
|
import { describeAttachedImagesForTextModel } from "../utils/image-vision-fallback";
|
|
325
|
+
import { formatLocalCalendarDate } from "../utils/local-date";
|
|
317
326
|
import { generateSessionTitle } from "../utils/title-generator";
|
|
318
327
|
import { buildNamedToolChoice, isToolChoiceActive } from "../utils/tool-choice";
|
|
319
328
|
import type { AuthStorage } from "./auth-storage";
|
|
@@ -686,6 +695,8 @@ export interface AgentSessionConfig {
|
|
|
686
695
|
toolRegistry?: Map<string, AgentTool>;
|
|
687
696
|
/** Tool names whose current registry entry is still the built-in implementation. */
|
|
688
697
|
builtInToolNames?: Iterable<string>;
|
|
698
|
+
/** Update tool-session predicates that render guidance from the live active tool set. */
|
|
699
|
+
setActiveToolNames?: (names: Iterable<string>) => void;
|
|
689
700
|
/** Current session pre-LLM message transform pipeline */
|
|
690
701
|
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => AgentMessage[] | Promise<AgentMessage[]>;
|
|
691
702
|
/**
|
|
@@ -724,6 +735,8 @@ export interface AgentSessionConfig {
|
|
|
724
735
|
convertToLlm?: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
|
725
736
|
/** System prompt builder that can consider tool availability. Returns ordered provider-facing blocks. */
|
|
726
737
|
rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => Promise<{ systemPrompt: string[] }>;
|
|
738
|
+
/** Local calendar date provider used by prompt-cache invalidation. Defaults to the host local date. */
|
|
739
|
+
getLocalCalendarDate?: () => string;
|
|
727
740
|
/** Rebuild the SSH tool from current capability discovery results. */
|
|
728
741
|
reloadSshTool?: () => Promise<AgentTool | null>;
|
|
729
742
|
requestedToolNames?: ReadonlySet<string>;
|
|
@@ -870,6 +883,7 @@ export interface HandoffResult {
|
|
|
870
883
|
export interface SessionHandoffOptions {
|
|
871
884
|
autoTriggered?: boolean;
|
|
872
885
|
signal?: AbortSignal;
|
|
886
|
+
onSwitchCancelled?: () => void;
|
|
873
887
|
}
|
|
874
888
|
|
|
875
889
|
/** Result from cycleModel() */
|
|
@@ -1166,6 +1180,7 @@ const noOpUIContext: ExtensionUIContext = {
|
|
|
1166
1180
|
pasteToEditor: () => {},
|
|
1167
1181
|
getEditorText: () => "",
|
|
1168
1182
|
editor: async () => undefined,
|
|
1183
|
+
addAutocompleteProvider: () => {},
|
|
1169
1184
|
get theme() {
|
|
1170
1185
|
return theme;
|
|
1171
1186
|
},
|
|
@@ -1555,6 +1570,7 @@ export class AgentSession {
|
|
|
1555
1570
|
#cancelExitRecorder?: () => void;
|
|
1556
1571
|
#exitRecorded = false;
|
|
1557
1572
|
#unsubscribeAppendOnly?: () => void;
|
|
1573
|
+
#unsubscribeModelRoles?: () => void;
|
|
1558
1574
|
/** Last (enable, providerId) tuple resolved by `#syncAppendOnlyContext` — used to skip no-op invalidations. */
|
|
1559
1575
|
#lastAppendOnlyResolution?: { enable: boolean; providerId: string | undefined };
|
|
1560
1576
|
#eventListeners: AgentSessionEventListener[] = [];
|
|
@@ -1716,8 +1732,10 @@ export class AgentSession {
|
|
|
1716
1732
|
#rebuildSystemPrompt:
|
|
1717
1733
|
| ((toolNames: string[], tools: Map<string, AgentTool>) => Promise<{ systemPrompt: string[] }>)
|
|
1718
1734
|
| undefined;
|
|
1735
|
+
#getLocalCalendarDate: () => string;
|
|
1719
1736
|
#getMcpServerInstructions: (() => Map<string, string> | undefined) | undefined;
|
|
1720
1737
|
#reloadSshTool: (() => Promise<AgentTool | null>) | undefined;
|
|
1738
|
+
#setActiveToolNames: ((names: Iterable<string>) => void) | undefined;
|
|
1721
1739
|
#disconnectOwnedMcpManager: (() => Promise<void>) | undefined;
|
|
1722
1740
|
#requestedToolNames: ReadonlySet<string> | undefined;
|
|
1723
1741
|
#baseSystemPrompt: string[];
|
|
@@ -2161,8 +2179,10 @@ export class AgentSession {
|
|
|
2161
2179
|
});
|
|
2162
2180
|
this.#convertToLlm = config.convertToLlm ?? convertToLlm;
|
|
2163
2181
|
this.#rebuildSystemPrompt = config.rebuildSystemPrompt;
|
|
2182
|
+
this.#getLocalCalendarDate = config.getLocalCalendarDate ?? formatLocalCalendarDate;
|
|
2164
2183
|
this.#getMcpServerInstructions = config.getMcpServerInstructions;
|
|
2165
2184
|
this.#reloadSshTool = config.reloadSshTool;
|
|
2185
|
+
this.#setActiveToolNames = config.setActiveToolNames;
|
|
2166
2186
|
this.#disconnectOwnedMcpManager = config.disconnectOwnedMcpManager;
|
|
2167
2187
|
this.#baseSystemPrompt = this.agent.state.systemPrompt;
|
|
2168
2188
|
this.#promptModelKey = this.#currentPromptModelKey();
|
|
@@ -2259,6 +2279,11 @@ export class AgentSession {
|
|
|
2259
2279
|
this.#unsubscribeAgent = this.agent.subscribe(this.#handleAgentEvent);
|
|
2260
2280
|
// Re-evaluate append-only context mode when the setting changes at runtime.
|
|
2261
2281
|
this.#unsubscribeAppendOnly = onAppendOnlyModeChanged(_value => this.#syncAppendOnlyContext(this.model));
|
|
2282
|
+
this.#unsubscribeModelRoles = onModelRolesChanged(() => {
|
|
2283
|
+
if (!this.#advisorEnabled || this.#isDisposed) return;
|
|
2284
|
+
if (this.#advisors.length > 0 && !this.#advisorRuntimeMatchesCurrentConfig()) this.#stopAdvisorRuntime();
|
|
2285
|
+
this.#buildAdvisorRuntime(true);
|
|
2286
|
+
});
|
|
2262
2287
|
}
|
|
2263
2288
|
// -------------------------------------------------------------------------
|
|
2264
2289
|
// Advisor runtime lifecycle
|
|
@@ -2391,7 +2416,9 @@ export class AgentSession {
|
|
|
2391
2416
|
#advisorRuntimeSignature(config: AdvisorConfig, slug: string, model: Model, thinkingLevel: ThinkingLevel): string {
|
|
2392
2417
|
const tools = config.tools?.length ? config.tools.join("\u001e") : "";
|
|
2393
2418
|
const instructions = config.instructions?.trim() ?? "";
|
|
2394
|
-
return [config.name, slug, model
|
|
2419
|
+
return [config.name, slug, formatModelStringWithRouting(model), thinkingLevel, tools, instructions].join(
|
|
2420
|
+
"\u001f",
|
|
2421
|
+
);
|
|
2395
2422
|
}
|
|
2396
2423
|
|
|
2397
2424
|
#advisorRuntimeMatchesCurrentConfig(): boolean {
|
|
@@ -2539,6 +2566,21 @@ export class AgentSession {
|
|
|
2539
2566
|
maintainContext: incomingTokens => this.#maintainAdvisorContext(advisorRef, incomingTokens),
|
|
2540
2567
|
obfuscator: this.#obfuscator,
|
|
2541
2568
|
beginAdvisorUpdate: () => advisorRef.emissionGuard.beginUpdate(),
|
|
2569
|
+
onTurnError: async error => {
|
|
2570
|
+
// Mirror the auth-gateway's usage-limit remedy: the in-stream a/b/c
|
|
2571
|
+
// auth retry rotates through siblings within one request but never
|
|
2572
|
+
// blocks the LAST failing credential, so without this the advisor
|
|
2573
|
+
// re-picks the same exhausted account every retry. Usage limits
|
|
2574
|
+
// only — other failures keep the plain retry/notify path (never
|
|
2575
|
+
// suspect-mark a credential on a transient advisor error).
|
|
2576
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2577
|
+
if (!isUsageLimitOutcome(extractHttpStatusFromError(error), message)) return;
|
|
2578
|
+
await this.#modelRegistry.authStorage.markUsageLimitReached(advisorModel.provider, advisorSessionId, {
|
|
2579
|
+
retryAfterMs: extractRetryHint(undefined, message),
|
|
2580
|
+
baseUrl: advisorModel.baseUrl,
|
|
2581
|
+
modelId: advisorModel.id,
|
|
2582
|
+
});
|
|
2583
|
+
},
|
|
2542
2584
|
notifyFailure: error => {
|
|
2543
2585
|
const message = error instanceof Error ? error.message : String(error);
|
|
2544
2586
|
this.emitNotice(
|
|
@@ -4666,7 +4708,8 @@ export class AgentSession {
|
|
|
4666
4708
|
// Decide first: a non-interrupting tool-source match attaches to the
|
|
4667
4709
|
// specific tool call's result instead of driving a loop-wide follow-up.
|
|
4668
4710
|
const shouldInterrupt = this.#shouldInterruptForTtsrMatch(matches, matchContext);
|
|
4669
|
-
const
|
|
4711
|
+
const matchedToolId = this.#extractTtsrToolCallId(matchContext);
|
|
4712
|
+
const perToolId = shouldInterrupt ? undefined : matchedToolId;
|
|
4670
4713
|
if (perToolId) {
|
|
4671
4714
|
this.#addPerToolTtsrInjections(perToolId, matches);
|
|
4672
4715
|
this.#emitSessionEvent({ type: "ttsr_triggered", rules: matches }).catch(() => {});
|
|
@@ -4682,7 +4725,16 @@ export class AgentSession {
|
|
|
4682
4725
|
// Abort the stream immediately — do not gate on extension callbacks
|
|
4683
4726
|
this.#ttsrAbortPending = true;
|
|
4684
4727
|
this.#ensureTtsrResumePromise();
|
|
4685
|
-
this
|
|
4728
|
+
const abortReason = this.#formatTtsrAbortReason(matches);
|
|
4729
|
+
this.agent.abort(
|
|
4730
|
+
matchedToolId
|
|
4731
|
+
? createToolScopedAbortReason(
|
|
4732
|
+
abortReason,
|
|
4733
|
+
{ [matchedToolId]: abortReason },
|
|
4734
|
+
"TTSR interrupt on another tool call",
|
|
4735
|
+
)
|
|
4736
|
+
: abortReason,
|
|
4737
|
+
);
|
|
4686
4738
|
// Notify extensions (fire-and-forget, does not block abort)
|
|
4687
4739
|
this.#emitSessionEvent({ type: "ttsr_triggered", rules: matches }).catch(() => {});
|
|
4688
4740
|
// Schedule retry after a short delay
|
|
@@ -5755,6 +5807,10 @@ export class AgentSession {
|
|
|
5755
5807
|
this.#unsubscribeAppendOnly();
|
|
5756
5808
|
this.#unsubscribeAppendOnly = undefined;
|
|
5757
5809
|
}
|
|
5810
|
+
if (this.#unsubscribeModelRoles) {
|
|
5811
|
+
this.#unsubscribeModelRoles();
|
|
5812
|
+
this.#unsubscribeModelRoles = undefined;
|
|
5813
|
+
}
|
|
5758
5814
|
this.#eventListeners = [];
|
|
5759
5815
|
}
|
|
5760
5816
|
|
|
@@ -6298,6 +6354,7 @@ export class AgentSession {
|
|
|
6298
6354
|
),
|
|
6299
6355
|
);
|
|
6300
6356
|
}
|
|
6357
|
+
this.#setActiveToolNames?.(validToolNames);
|
|
6301
6358
|
const activeNameSet = new Set(validToolNames);
|
|
6302
6359
|
for (const name of Array.from(this.#selectedDiscoveredToolNames)) {
|
|
6303
6360
|
if (!activeNameSet.has(name) || isMCPToolName(name) || !this.#toolRegistry.has(name)) {
|
|
@@ -6403,6 +6460,7 @@ export class AgentSession {
|
|
|
6403
6460
|
async refreshBaseSystemPrompt(): Promise<void> {
|
|
6404
6461
|
if (!this.#rebuildSystemPrompt) return;
|
|
6405
6462
|
const activeToolNames = this.getActiveToolNames();
|
|
6463
|
+
this.#setActiveToolNames?.(activeToolNames);
|
|
6406
6464
|
const built = await this.#rebuildSystemPrompt(activeToolNames, this.#toolRegistry);
|
|
6407
6465
|
this.#baseSystemPrompt = built.systemPrompt;
|
|
6408
6466
|
this.#baseSystemPromptBeforeMemoryPromotion = undefined;
|
|
@@ -6525,7 +6583,7 @@ export class AgentSession {
|
|
|
6525
6583
|
entries.sort();
|
|
6526
6584
|
instructionsSegment = entries.join("\u0006");
|
|
6527
6585
|
}
|
|
6528
|
-
const date =
|
|
6586
|
+
const date = this.#getLocalCalendarDate();
|
|
6529
6587
|
return `${nameSegment}\u0003${descriptionSegment}\u0005${registrySegment}\u0007${instructionsSegment}|${date}`;
|
|
6530
6588
|
}
|
|
6531
6589
|
|
|
@@ -7342,11 +7400,15 @@ export class AgentSession {
|
|
|
7342
7400
|
timestamp,
|
|
7343
7401
|
});
|
|
7344
7402
|
}
|
|
7345
|
-
if (
|
|
7403
|
+
if (
|
|
7404
|
+
this.#magicKeywordEnabled("workflow") &&
|
|
7405
|
+
containsWorkflow(text) &&
|
|
7406
|
+
this.getActiveToolNames().includes("task")
|
|
7407
|
+
) {
|
|
7346
7408
|
keywordNotices.push({
|
|
7347
7409
|
role: "custom",
|
|
7348
7410
|
customType: "workflow-notice",
|
|
7349
|
-
content:
|
|
7411
|
+
content: renderWorkflowNotice({ taskBatch: this.settings.get("task.batch") }),
|
|
7350
7412
|
display: false,
|
|
7351
7413
|
attribution: "user",
|
|
7352
7414
|
timestamp,
|
|
@@ -8248,11 +8310,10 @@ export class AgentSession {
|
|
|
8248
8310
|
}
|
|
8249
8311
|
|
|
8250
8312
|
/**
|
|
8251
|
-
* Send a user message
|
|
8252
|
-
* When deliverAs is set, queue the message instead of starting a new turn.
|
|
8313
|
+
* Send a user message through the prompt flow.
|
|
8253
8314
|
*
|
|
8254
|
-
*
|
|
8255
|
-
*
|
|
8315
|
+
* Omitted `deliverAs` starts a turn when idle and queues as a steer while streaming.
|
|
8316
|
+
* Explicit `deliverAs` queues without starting a turn in either state.
|
|
8256
8317
|
*/
|
|
8257
8318
|
async sendUserMessage(
|
|
8258
8319
|
content: string | (TextContent | ImageContent)[],
|
|
@@ -8287,10 +8348,13 @@ export class AgentSession {
|
|
|
8287
8348
|
return;
|
|
8288
8349
|
}
|
|
8289
8350
|
|
|
8290
|
-
// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
|
|
8351
|
+
// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion.
|
|
8352
|
+
// `streamingBehavior: "steer"` preserves prompt-flow side effects during streaming while
|
|
8353
|
+
// covering the narrow race where a stream starts before prompt() acquires the turn.
|
|
8291
8354
|
await this.prompt(text, {
|
|
8292
8355
|
expandPromptTemplates: false,
|
|
8293
8356
|
images,
|
|
8357
|
+
streamingBehavior: "steer",
|
|
8294
8358
|
});
|
|
8295
8359
|
}
|
|
8296
8360
|
|
|
@@ -8787,6 +8851,7 @@ export class AgentSession {
|
|
|
8787
8851
|
|
|
8788
8852
|
const targetModel = await this.#modelRegistry.refreshSelectedModelMetadata(model);
|
|
8789
8853
|
|
|
8854
|
+
this.#modelRegistry.clearSuppressedSelector(formatModelStringWithRouting(targetModel));
|
|
8790
8855
|
this.#clearActiveRetryFallback();
|
|
8791
8856
|
this.#setModelWithProviderSessionReset(targetModel);
|
|
8792
8857
|
this.sessionManager.appendModelChange(`${targetModel.provider}/${targetModel.id}`, role);
|
|
@@ -8824,6 +8889,7 @@ export class AgentSession {
|
|
|
8824
8889
|
|
|
8825
8890
|
const targetModel = await this.#modelRegistry.refreshSelectedModelMetadata(model);
|
|
8826
8891
|
|
|
8892
|
+
this.#modelRegistry.clearSuppressedSelector(formatModelStringWithRouting(targetModel));
|
|
8827
8893
|
this.#clearActiveRetryFallback();
|
|
8828
8894
|
this.#setModelWithProviderSessionReset(targetModel);
|
|
8829
8895
|
this.sessionManager.appendModelChange(
|
|
@@ -8983,6 +9049,7 @@ export class AgentSession {
|
|
|
8983
9049
|
const next = scopedModels[nextIndex];
|
|
8984
9050
|
|
|
8985
9051
|
// Apply model
|
|
9052
|
+
this.#modelRegistry.clearSuppressedSelector(formatModelStringWithRouting(next.model));
|
|
8986
9053
|
this.#clearActiveRetryFallback();
|
|
8987
9054
|
this.#setModelWithProviderSessionReset(next.model);
|
|
8988
9055
|
this.sessionManager.appendModelChange(`${next.model.provider}/${next.model.id}`);
|
|
@@ -9013,6 +9080,7 @@ export class AgentSession {
|
|
|
9013
9080
|
throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
|
|
9014
9081
|
}
|
|
9015
9082
|
|
|
9083
|
+
this.#modelRegistry.clearSuppressedSelector(formatModelStringWithRouting(nextModel));
|
|
9016
9084
|
this.#clearActiveRetryFallback();
|
|
9017
9085
|
this.#setModelWithProviderSessionReset(nextModel);
|
|
9018
9086
|
this.sessionManager.appendModelChange(`${nextModel.provider}/${nextModel.id}`);
|
|
@@ -10041,6 +10109,17 @@ export class AgentSession {
|
|
|
10041
10109
|
|
|
10042
10110
|
// Start a new session
|
|
10043
10111
|
const previousSessionFile = this.sessionFile;
|
|
10112
|
+
if (this.#extensionRunner?.hasHandlers("session_before_switch")) {
|
|
10113
|
+
const result = (await this.#extensionRunner.emit({
|
|
10114
|
+
type: "session_before_switch",
|
|
10115
|
+
reason: "handoff",
|
|
10116
|
+
})) as SessionBeforeSwitchResult | undefined;
|
|
10117
|
+
|
|
10118
|
+
if (result?.cancel) {
|
|
10119
|
+
options?.onSwitchCancelled?.();
|
|
10120
|
+
return undefined;
|
|
10121
|
+
}
|
|
10122
|
+
}
|
|
10044
10123
|
await this.sessionManager.flush();
|
|
10045
10124
|
this.#cancelOwnAsyncJobs();
|
|
10046
10125
|
await this.sessionManager.newSession(previousSessionFile ? { parentSession: previousSessionFile } : undefined);
|
|
@@ -10097,6 +10176,13 @@ export class AgentSession {
|
|
|
10097
10176
|
this.agent.replaceMessages(sessionContext.messages);
|
|
10098
10177
|
this.#resetAllAdvisorRuntimes();
|
|
10099
10178
|
this.#syncTodoPhasesFromBranch();
|
|
10179
|
+
if (this.#extensionRunner) {
|
|
10180
|
+
await this.#extensionRunner.emit({
|
|
10181
|
+
type: "session_switch",
|
|
10182
|
+
reason: "handoff",
|
|
10183
|
+
previousSessionFile,
|
|
10184
|
+
});
|
|
10185
|
+
}
|
|
10100
10186
|
|
|
10101
10187
|
return { document: handoffText, savedPath };
|
|
10102
10188
|
} catch (error) {
|
|
@@ -12313,13 +12399,17 @@ export class AgentSession {
|
|
|
12313
12399
|
// queue, not the core steering queue (which handoff's agent.reset() would wipe).
|
|
12314
12400
|
await this.#emitSessionEvent({ type: "auto_compaction_start", reason, action });
|
|
12315
12401
|
if (action === "handoff") {
|
|
12402
|
+
let handoffSwitchCancelled = false;
|
|
12316
12403
|
const handoffFocus = AUTO_HANDOFF_THRESHOLD_FOCUS;
|
|
12317
12404
|
const handoffResult = await this.handoff(handoffFocus, {
|
|
12318
12405
|
autoTriggered: true,
|
|
12319
12406
|
signal: autoCompactionSignal,
|
|
12407
|
+
onSwitchCancelled: () => {
|
|
12408
|
+
handoffSwitchCancelled = true;
|
|
12409
|
+
},
|
|
12320
12410
|
});
|
|
12321
12411
|
if (!handoffResult) {
|
|
12322
|
-
const aborted = autoCompactionSignal.aborted;
|
|
12412
|
+
const aborted = autoCompactionSignal.aborted || handoffSwitchCancelled;
|
|
12323
12413
|
if (aborted) {
|
|
12324
12414
|
await this.#emitSessionEvent({
|
|
12325
12415
|
type: "auto_compaction_end",
|
|
@@ -13220,6 +13310,7 @@ export class AgentSession {
|
|
|
13220
13310
|
#resolveRetryFallbackRole(currentSelector: string): string | undefined {
|
|
13221
13311
|
const parsedCurrent = parseRetryFallbackSelector(currentSelector, this.#modelRegistry);
|
|
13222
13312
|
if (!parsedCurrent) return undefined;
|
|
13313
|
+
const chains = this.#getRetryFallbackChains();
|
|
13223
13314
|
const currentBaseSelector = formatRetryFallbackBaseSelector(parsedCurrent);
|
|
13224
13315
|
const currentPlainSelector = this.model
|
|
13225
13316
|
? formatModelSelectorValue(formatModelString(this.model), parsedCurrent.thinkingLevel)
|
|
@@ -13229,11 +13320,11 @@ export class AgentSession {
|
|
|
13229
13320
|
? formatRetryFallbackBaseSelector(parseRetryFallbackSelector(currentPlainSelector) ?? parsedCurrent)
|
|
13230
13321
|
: undefined;
|
|
13231
13322
|
|
|
13232
|
-
for (const role of Object.keys(
|
|
13323
|
+
for (const role of Object.keys(chains)) {
|
|
13233
13324
|
const primarySelector = this.#getRetryFallbackPrimarySelector(role);
|
|
13234
13325
|
if (primarySelector?.raw === currentSelector) return role;
|
|
13235
13326
|
}
|
|
13236
|
-
for (const role of Object.keys(
|
|
13327
|
+
for (const role of Object.keys(chains)) {
|
|
13237
13328
|
const primarySelector = this.#getRetryFallbackPrimarySelector(role);
|
|
13238
13329
|
if (!primarySelector) continue;
|
|
13239
13330
|
if (currentPlainSelector && primarySelector.raw === currentPlainSelector) return role;
|
|
@@ -13241,6 +13332,14 @@ export class AgentSession {
|
|
|
13241
13332
|
if (primaryBaseSelector === currentBaseSelector) return role;
|
|
13242
13333
|
if (currentPlainBaseSelector && primaryBaseSelector === currentPlainBaseSelector) return role;
|
|
13243
13334
|
}
|
|
13335
|
+
const defaultChain = chains.default;
|
|
13336
|
+
if (
|
|
13337
|
+
Array.isArray(defaultChain) &&
|
|
13338
|
+
defaultChain.length > 0 &&
|
|
13339
|
+
this.#getRetryFallbackPrimarySelector("default") === undefined
|
|
13340
|
+
) {
|
|
13341
|
+
return "default";
|
|
13342
|
+
}
|
|
13244
13343
|
return undefined;
|
|
13245
13344
|
}
|
|
13246
13345
|
|
|
@@ -13259,9 +13358,27 @@ export class AgentSession {
|
|
|
13259
13358
|
}
|
|
13260
13359
|
|
|
13261
13360
|
#findRetryFallbackCandidates(role: string, currentSelector: string): RetryFallbackSelector[] {
|
|
13262
|
-
|
|
13263
|
-
if (chain.length <= 1) return [];
|
|
13361
|
+
let chain = this.#getRetryFallbackEffectiveChain(role);
|
|
13264
13362
|
const parsedCurrent = parseRetryFallbackSelector(currentSelector, this.#modelRegistry);
|
|
13363
|
+
if (chain.length === 0 && role === "default" && parsedCurrent) {
|
|
13364
|
+
const chains = this.#getRetryFallbackChains();
|
|
13365
|
+
const defaultChain = chains.default;
|
|
13366
|
+
if (
|
|
13367
|
+
Array.isArray(defaultChain) &&
|
|
13368
|
+
defaultChain.length > 0 &&
|
|
13369
|
+
this.#getRetryFallbackPrimarySelector("default") === undefined
|
|
13370
|
+
) {
|
|
13371
|
+
const seen = new Set<string>([parsedCurrent.raw]);
|
|
13372
|
+
chain = [parsedCurrent];
|
|
13373
|
+
for (const selector of defaultChain) {
|
|
13374
|
+
const parsed = parseRetryFallbackSelector(selector, this.#modelRegistry);
|
|
13375
|
+
if (!parsed || seen.has(parsed.raw)) continue;
|
|
13376
|
+
seen.add(parsed.raw);
|
|
13377
|
+
chain.push(parsed);
|
|
13378
|
+
}
|
|
13379
|
+
}
|
|
13380
|
+
}
|
|
13381
|
+
if (chain.length <= 1) return [];
|
|
13265
13382
|
const currentBaseSelector = parsedCurrent ? formatRetryFallbackBaseSelector(parsedCurrent) : undefined;
|
|
13266
13383
|
const currentPlainSelector =
|
|
13267
13384
|
this.model && parsedCurrent
|
|
@@ -15535,7 +15652,7 @@ export class AgentSession {
|
|
|
15535
15652
|
lastAttemptAtByAccount: coordinator.lastAttemptAtByAccount,
|
|
15536
15653
|
});
|
|
15537
15654
|
if (!decision.redeem) {
|
|
15538
|
-
logger.debug("codex-auto-reset: skipped", { reason: decision.reason });
|
|
15655
|
+
logger.debug("codex-auto-reset: skipped", { reason: decision.reason, account: accountKey });
|
|
15539
15656
|
return false;
|
|
15540
15657
|
}
|
|
15541
15658
|
if (shouldPromptCodexAutoRedeem(cfg.autoRedeem) && !(await this.#confirmCodexAutoRedeem(decision))) {
|
package/src/system-prompt.ts
CHANGED
|
@@ -24,6 +24,7 @@ import projectPromptTemplate from "./prompts/system/project-prompt.md" with { ty
|
|
|
24
24
|
import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
|
|
25
25
|
import { shortenPath } from "./tools/render-utils";
|
|
26
26
|
import { type ActiveRepoContext, resolveActiveRepoContext } from "./utils/active-repo-context";
|
|
27
|
+
import { formatLocalCalendarDate } from "./utils/local-date";
|
|
27
28
|
import { normalizePromptPath } from "./utils/prompt-path";
|
|
28
29
|
import { AGENTS_MD_LIMIT, buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
|
|
29
30
|
|
|
@@ -400,7 +401,7 @@ export async function loadSystemPromptFiles(options: LoadContextFilesOptions = {
|
|
|
400
401
|
return userLevel?.content ?? null;
|
|
401
402
|
}
|
|
402
403
|
|
|
403
|
-
export const DEFAULT_SYSTEM_PROMPT_TOOL_NAMES = ["read", "bash", "
|
|
404
|
+
export const DEFAULT_SYSTEM_PROMPT_TOOL_NAMES = ["read", "bash", "edit", "write"] as const;
|
|
404
405
|
|
|
405
406
|
export interface SystemPromptToolMetadata {
|
|
406
407
|
label: string;
|
|
@@ -693,7 +694,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
693
694
|
}
|
|
694
695
|
}
|
|
695
696
|
|
|
696
|
-
const date =
|
|
697
|
+
const date = formatLocalCalendarDate();
|
|
697
698
|
const dateTime = date;
|
|
698
699
|
const promptCwd = shortenPath(normalizePromptPath(resolvedCwd));
|
|
699
700
|
const activeRepoContextPrompt = renderActiveRepoContextPrompt(activeRepoContext);
|