@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.1
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 +75 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +15 -5
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +18 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +4 -0
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-registry.ts +25 -2
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +20 -2
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/llm-bridge.ts +8 -3
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +9 -7
- package/src/memories/index.ts +12 -5
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +23 -0
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +124 -119
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +169 -94
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +18 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +11 -37
- package/src/session/agent-session.ts +82 -6
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +5 -2
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +80 -78
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +81 -62
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
|
@@ -6,111 +6,120 @@ You NEVER:
|
|
|
6
6
|
- Run state-changing commands (git commit, npm install, etc.)
|
|
7
7
|
- Make any system changes
|
|
8
8
|
|
|
9
|
-
To implement: call `resolve` with `action: "apply"`, a `reason`, and `extra: { title: "<
|
|
9
|
+
To implement: call `resolve` with `action: "apply"`, a `reason`, and `extra: { title: "<slug>" }` where `<slug>` matches your `local://<slug>-plan.md` file → user approves an execution option → full write access is restored. `<slug>` may only contain letters, numbers, underscores, and hyphens. The plan file is never renamed, so its name is yours to choose.
|
|
10
10
|
|
|
11
11
|
You NEVER ask the user to exit plan mode for you; you MUST call `resolve` yourself.
|
|
12
12
|
</critical>
|
|
13
13
|
|
|
14
|
+
## Objective
|
|
15
|
+
|
|
16
|
+
A plan is **decision-complete**: another engineer or agent can execute it end-to-end without making a single design decision. Optimize every choice for that. Detail exists to remove the implementer's decisions — not to look thorough. A document that reads like a design doc (Non-Goals, Alternatives, risk matrices) yet leaves real decisions open is a FAILED plan.
|
|
17
|
+
|
|
14
18
|
## Plan File
|
|
15
19
|
|
|
16
20
|
{{#if planExists}}
|
|
17
|
-
Plan file exists at `{{planFilePath}}`; you MUST read and update it incrementally.
|
|
21
|
+
Plan file exists at `{{planFilePath}}`; you MUST read and update it incrementally. If this request is a different task, write a fresh `local://<slug>-plan.md` instead and leave the old plan in place.
|
|
18
22
|
{{else}}
|
|
19
|
-
You MUST
|
|
23
|
+
Choose a short kebab-case `<slug>` that names this task (letters, numbers, hyphens) and write the plan to `local://<slug>-plan.md` — e.g. `local://auth-token-refresh-plan.md`. You MUST pass that same `<slug>` as `title` when you call `resolve`.
|
|
20
24
|
{{/if}}
|
|
21
25
|
|
|
22
|
-
You MUST use `{{editToolName}}` for incremental updates; use `{{writeToolName}}` only for create/full replace.
|
|
26
|
+
You MUST use `{{editToolName}}` for incremental updates; use `{{writeToolName}}` only for create/full replace. You MUST update the plan as you learn — you NEVER batch all writing to the end.
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
The approval selector includes:
|
|
26
|
-
- **Approve and execute**: starts execution in fresh context (session cleared).
|
|
27
|
-
- **Approve and compact context**: distills the plan-mode discussion into a summary, then starts execution in this session.
|
|
28
|
-
- **Approve and keep context**: starts execution in this session, preserving exploration history.
|
|
28
|
+
## Resolving Unknowns
|
|
29
29
|
|
|
30
|
-
You MUST
|
|
31
|
-
|
|
30
|
+
You MUST eliminate unknowns by discovering facts, not by asking. Before asking the user anything, perform at least one targeted exploration pass.
|
|
31
|
+
|
|
32
|
+
Two kinds of unknowns, treated differently:
|
|
33
|
+
- **Discoverable facts** — repo/system truth: file locations, current behavior, existing patterns, types, configs. You MUST explore first (`find`, `search`, `read`, parallel explore subagents). You NEVER ask what the codebase can answer (e.g. "where is this defined?"). Ask only when several plausible candidates remain or a required identifier is genuinely absent — and then present the candidates with a recommendation.
|
|
34
|
+
- **Preferences and tradeoffs** — intent, UX, scope boundaries, performance-vs-simplicity: not derivable from code. You MUST surface these early via `{{askToolName}}` with 2–4 mutually exclusive options and a recommended default. If left unanswered, proceed with the default and record it under Assumptions.
|
|
35
|
+
|
|
36
|
+
Every question MUST materially change the plan, confirm a load-bearing assumption, or choose between real tradeoffs. You MUST batch questions. You NEVER ask filler questions or offer obviously-wrong options.
|
|
32
37
|
|
|
33
38
|
{{#if reentry}}
|
|
34
39
|
## Re-entry
|
|
35
40
|
|
|
36
41
|
<procedure>
|
|
37
|
-
1. Read existing plan
|
|
38
|
-
2. Evaluate request against it
|
|
42
|
+
1. Read the existing plan.
|
|
43
|
+
2. Evaluate the new request against it.
|
|
39
44
|
3. Decide:
|
|
40
|
-
- **Different task** →
|
|
41
|
-
- **Same task, continuing** →
|
|
42
|
-
4. Call `resolve` with `action: "apply"` and `extra: { title }` when complete
|
|
45
|
+
- **Different task** → overwrite the plan.
|
|
46
|
+
- **Same task, continuing** → update and delete outdated sections.
|
|
47
|
+
4. Call `resolve` with `action: "apply"` and `extra: { title }` when complete.
|
|
43
48
|
</procedure>
|
|
44
49
|
{{/if}}
|
|
45
50
|
|
|
46
51
|
{{#if iterative}}
|
|
47
|
-
## Iterative
|
|
52
|
+
## Workflow — Iterative
|
|
48
53
|
|
|
49
54
|
<procedure>
|
|
50
55
|
### 1. Explore
|
|
51
|
-
You MUST use `find`, `search`, `read` to
|
|
56
|
+
You MUST use `find`, `search`, `read` to ground yourself in the actual code. Hunt for existing functions, utilities, and conventions to reuse before proposing anything new.
|
|
52
57
|
|
|
53
58
|
### 2. Interview
|
|
54
|
-
You MUST use `{{askToolName}}` to
|
|
55
|
-
- Ambiguous requirements
|
|
56
|
-
- Technical decisions and tradeoffs
|
|
57
|
-
- Preferences: UI/UX, performance, edge cases
|
|
59
|
+
You MUST use `{{askToolName}}` to resolve preferences and tradeoffs (see Resolving Unknowns). Batch questions; never ask what exploration answers.
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### 3. Update Incrementally
|
|
62
|
-
You MUST use `{{editToolName}}` to update plan file as you learn; NEVER wait until end.
|
|
61
|
+
### 3. Update incrementally
|
|
62
|
+
You MUST use `{{editToolName}}` to revise the plan file as you learn.
|
|
63
63
|
|
|
64
64
|
### 4. Calibrate
|
|
65
|
-
- Large unspecified task → multiple interview rounds
|
|
66
|
-
-
|
|
65
|
+
- Large, unspecified task → multiple interview rounds.
|
|
66
|
+
- Small, well-specified task → few or no questions.
|
|
67
67
|
</procedure>
|
|
68
|
-
|
|
69
|
-
<caution>
|
|
70
|
-
### Plan Structure
|
|
71
|
-
|
|
72
|
-
You MUST use clear markdown headers; include:
|
|
73
|
-
- Recommended approach (not alternatives)
|
|
74
|
-
- Paths of critical files to modify
|
|
75
|
-
- Verification: how to test end-to-end
|
|
76
|
-
|
|
77
|
-
The plan MUST be scannable yet detailed enough to execute.
|
|
78
|
-
</caution>
|
|
79
|
-
|
|
80
68
|
{{else}}
|
|
81
|
-
##
|
|
69
|
+
## Workflow — Parallel
|
|
82
70
|
|
|
83
71
|
<procedure>
|
|
84
|
-
### Phase 1
|
|
85
|
-
You MUST focus on the request and
|
|
72
|
+
### Phase 1 — Understand
|
|
73
|
+
You MUST focus on the request and the code behind it. You SHOULD launch parallel `explore` subagents (via `task`) when scope spans multiple areas — give each a distinct focus (existing implementations, related components, test patterns). Actively hunt for reusable functions, utilities, and conventions; avoid proposing new code when a suitable implementation already exists.
|
|
86
74
|
|
|
87
|
-
### Phase 2
|
|
88
|
-
You MUST draft an approach
|
|
75
|
+
### Phase 2 — Design
|
|
76
|
+
You MUST draft an approach from your exploration, weigh trade-offs briefly, then commit to one. For large or cross-cutting changes you MAY spawn a planning/critique subagent to pressure-test the approach before you commit.
|
|
89
77
|
|
|
90
|
-
### Phase 3
|
|
91
|
-
You MUST read critical files. You MUST verify plan matches original request. You SHOULD use `{{askToolName}}` to
|
|
78
|
+
### Phase 3 — Review
|
|
79
|
+
You MUST read the critical files you intend to touch to confirm the approach holds against the real code. You MUST verify the plan still matches the original request. You SHOULD use `{{askToolName}}` to close remaining preference questions.
|
|
92
80
|
|
|
93
|
-
### Phase 4
|
|
94
|
-
You MUST
|
|
95
|
-
- Recommended approach only
|
|
96
|
-
- Paths of critical files to modify
|
|
97
|
-
- Verification section
|
|
81
|
+
### Phase 4 — Write the plan
|
|
82
|
+
You MUST write the plan file (see **Plan File** above) per **The Plan** below.
|
|
98
83
|
</procedure>
|
|
84
|
+
{{/if}}
|
|
85
|
+
|
|
86
|
+
## The Plan
|
|
87
|
+
|
|
88
|
+
The plan MUST be self-contained: approval may clear or compact this conversation, so the file alone must carry everything needed to execute.
|
|
99
89
|
|
|
100
90
|
<caution>
|
|
101
|
-
|
|
91
|
+
Write 3–5 short, scannable markdown sections. The usual shape:
|
|
92
|
+
- **Context** — why this change: the problem or need, what prompted it, the intended outcome. 2–4 sentences.
|
|
93
|
+
- **Approach** — the recommended approach only. Group bullets by subsystem or behavior, NOT file-by-file. Name existing functions/utilities to reuse, with their paths. Describe a repeated pattern once with a few representative paths — you NEVER enumerate every file or line.
|
|
94
|
+
- **Critical files** — the ≤5 files that disambiguate non-obvious changes, each with a one-line reason. Skip files whose change is already obvious from the Approach.
|
|
95
|
+
- **Verification** — how to test end-to-end: exact commands, tests to run or add, manual steps.
|
|
96
|
+
- **Assumptions** — only the decisions you made that the user might want to override.
|
|
97
|
+
|
|
98
|
+
Prefer the minimum detail needed for safe implementation, not exhaustive coverage. Compress related changes into high-signal bullets; omit branch-by-branch logic, restated invariants, and lists of unaffected behavior. Behavior-level descriptions beat symbol-by-symbol removal lists.
|
|
102
99
|
</caution>
|
|
103
|
-
{{/if}}
|
|
104
100
|
|
|
105
101
|
<directives>
|
|
106
|
-
- You
|
|
102
|
+
- You NEVER include sections that decide nothing: Non-Goals, Out of Scope, Alternatives Considered, Risks/Mitigations boilerplate, Future Work. Omit them entirely.
|
|
103
|
+
- You NEVER invent schema, validation, precedence, or fallback policy the request did not establish, unless it is required to prevent a concrete implementation mistake.
|
|
104
|
+
- You NEVER present alternatives in the final plan — choose. Record a discarded option only when it is a live tradeoff the user should confirm, and put it under Assumptions.
|
|
107
105
|
</directives>
|
|
108
106
|
|
|
107
|
+
<caution>
|
|
108
|
+
The approval selector offers:
|
|
109
|
+
- **Approve and execute** — execution starts in fresh context (session cleared).
|
|
110
|
+
- **Approve and compact context** — distills this discussion into a summary, then executes in this session.
|
|
111
|
+
- **Approve and keep context** — executes in this session, preserving exploration history.
|
|
112
|
+
|
|
113
|
+
All three rely on the plan file being self-contained.
|
|
114
|
+
</caution>
|
|
115
|
+
|
|
109
116
|
<critical>
|
|
117
|
+
You MUST use `{{askToolName}}` only to clarify requirements or choose between approaches.
|
|
118
|
+
|
|
110
119
|
Your turn ends ONLY by:
|
|
111
120
|
1. Using `{{askToolName}}` to gather information, OR
|
|
112
|
-
2. Calling `resolve` with `action: "apply"`, `reason`, and `extra: { title: "<
|
|
121
|
+
2. Calling `resolve` with `action: "apply"`, `reason`, and `extra: { title: "<slug>" }` (the slug of your `local://<slug>-plan.md`) when ready — this triggers user approval, then implementation with full tool access.
|
|
113
122
|
|
|
114
|
-
You NEVER ask plan approval via text or `{{askToolName}}`; you MUST use `resolve`.
|
|
115
|
-
You MUST keep going until complete.
|
|
123
|
+
You NEVER ask for plan approval via text or `{{askToolName}}`; you MUST use `resolve`.
|
|
124
|
+
You MUST keep going until the plan is decision-complete.
|
|
116
125
|
</critical>
|
package/src/sdk.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
} from "@oh-my-pi/pi-agent-core";
|
|
11
11
|
import {
|
|
12
12
|
type CredentialDisabledEvent,
|
|
13
|
-
isUsageLimitError,
|
|
14
13
|
type Message,
|
|
15
14
|
type Model,
|
|
16
15
|
type SimpleStreamOptions,
|
|
@@ -24,7 +23,6 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
24
23
|
import {
|
|
25
24
|
$env,
|
|
26
25
|
$flag,
|
|
27
|
-
extractRetryHint,
|
|
28
26
|
getAgentDbPath,
|
|
29
27
|
getAgentDir,
|
|
30
28
|
getAuthBrokerSnapshotCachePath,
|
|
@@ -39,6 +37,7 @@ import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled } from ".
|
|
|
39
37
|
import { loadCapability } from "./capability";
|
|
40
38
|
import { type Rule, ruleCapability, setActiveRules } from "./capability/rule";
|
|
41
39
|
import { bucketRules } from "./capability/rule-buckets";
|
|
40
|
+
import { createApiKeyResolver } from "./config/api-key-resolver";
|
|
42
41
|
import { shouldEnableAppendOnlyContext } from "./config/append-only-context-mode";
|
|
43
42
|
import { ModelRegistry } from "./config/model-registry";
|
|
44
43
|
import {
|
|
@@ -280,6 +279,8 @@ export interface CreateAgentSessionOptions {
|
|
|
280
279
|
/** Optional provider-facing session identifier for prompt caches and sticky auth selection.
|
|
281
280
|
* Keeps persisted session files isolated while reusing provider-side caches. */
|
|
282
281
|
providerSessionId?: string;
|
|
282
|
+
/** Optional provider-facing prompt cache key, distinct from request lineage. */
|
|
283
|
+
providerPromptCacheKey?: string;
|
|
283
284
|
|
|
284
285
|
/** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
|
|
285
286
|
customTools?: (CustomTool | ToolDefinition)[];
|
|
@@ -2001,6 +2002,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2001
2002
|
onPayload,
|
|
2002
2003
|
onResponse,
|
|
2003
2004
|
sessionId: providerSessionId,
|
|
2005
|
+
promptCacheKey: options.providerPromptCacheKey,
|
|
2004
2006
|
transformContext,
|
|
2005
2007
|
steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
|
|
2006
2008
|
followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
|
|
@@ -2017,9 +2019,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2017
2019
|
kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
|
|
2018
2020
|
preferWebsockets: preferOpenAICodexWebsockets,
|
|
2019
2021
|
getToolContext: tc => toolContextStore.getContext(tc),
|
|
2020
|
-
getApiKey: async provider => {
|
|
2022
|
+
getApiKey: async (provider, ctx) => {
|
|
2021
2023
|
// Read agent.sessionId at call time so credential selection stays aligned
|
|
2022
2024
|
// with metadataResolver after /new, fork, resume, or branch switches.
|
|
2025
|
+
// Retry steps (ctx carries an auth error) drive the central a/b/c
|
|
2026
|
+
// policy — force-refresh the same account, then rotate to a sibling —
|
|
2027
|
+
// and may legitimately yield no key when every account is exhausted.
|
|
2028
|
+
if (ctx?.error !== undefined) {
|
|
2029
|
+
return createApiKeyResolver(modelRegistry, provider, { sessionId: agent.sessionId })(ctx);
|
|
2030
|
+
}
|
|
2023
2031
|
const key = await modelRegistry.getApiKeyForProvider(provider, agent.sessionId);
|
|
2024
2032
|
if (!key) {
|
|
2025
2033
|
throw new Error(`No API key found for provider "${provider}"`);
|
|
@@ -2033,40 +2041,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2033
2041
|
return streamSimple(streamModel, context, {
|
|
2034
2042
|
...streamOptions,
|
|
2035
2043
|
openrouterVariant: streamOptions?.openrouterVariant ?? openrouterVariant,
|
|
2036
|
-
onAuthError: async (provider, oldKey, error) => {
|
|
2037
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2038
|
-
// streamSimple invokes this for both 401 auth failures AND
|
|
2039
|
-
// rotatable usage-limit errors (Codex usage_limit_reached,
|
|
2040
|
-
// Anthropic usage_limit_reached, etc.). The two need
|
|
2041
|
-
// different storage actions: a real 401 means the credential
|
|
2042
|
-
// is bad and should be marked suspect; a usage limit just
|
|
2043
|
-
// means this account is parked until reset and should be
|
|
2044
|
-
// temporarily blocked so a sibling can pick the request up.
|
|
2045
|
-
if (isUsageLimitError(message)) {
|
|
2046
|
-
const retryAfterMs = extractRetryHint(undefined, message);
|
|
2047
|
-
const switched = await modelRegistry.authStorage.markUsageLimitReached(provider, agent.sessionId, {
|
|
2048
|
-
retryAfterMs,
|
|
2049
|
-
signal: streamOptions?.signal,
|
|
2050
|
-
});
|
|
2051
|
-
logger.debug("Retrying provider request after usage-limit block", {
|
|
2052
|
-
provider,
|
|
2053
|
-
switched,
|
|
2054
|
-
retryAfterMs,
|
|
2055
|
-
error: message,
|
|
2056
|
-
});
|
|
2057
|
-
if (!switched) return undefined;
|
|
2058
|
-
return modelRegistry.getApiKeyForProvider(provider, agent.sessionId);
|
|
2059
|
-
}
|
|
2060
|
-
await modelRegistry.authStorage.invalidateCredentialMatching(provider, oldKey, {
|
|
2061
|
-
signal: streamOptions?.signal,
|
|
2062
|
-
sessionId: agent.sessionId,
|
|
2063
|
-
});
|
|
2064
|
-
logger.debug("Retrying provider request after credential invalidation", {
|
|
2065
|
-
provider,
|
|
2066
|
-
error: message,
|
|
2067
|
-
});
|
|
2068
|
-
return modelRegistry.getApiKeyForProvider(provider, agent.sessionId);
|
|
2069
|
-
},
|
|
2070
2044
|
});
|
|
2071
2045
|
},
|
|
2072
2046
|
cursorExecHandlers,
|
|
@@ -471,6 +471,12 @@ export interface SessionStats {
|
|
|
471
471
|
cost: number;
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
+
export interface FreshSessionResult {
|
|
475
|
+
previousSessionId: string;
|
|
476
|
+
sessionId: string;
|
|
477
|
+
closedProviderSessions: number;
|
|
478
|
+
}
|
|
479
|
+
|
|
474
480
|
/** Internal marker for hook messages queued through the agent loop */
|
|
475
481
|
// ============================================================================
|
|
476
482
|
// Constants
|
|
@@ -922,6 +928,7 @@ export class AgentSession {
|
|
|
922
928
|
#agentId: string | undefined;
|
|
923
929
|
#agentRegistry: AgentRegistry | undefined;
|
|
924
930
|
#providerSessionId: string | undefined;
|
|
931
|
+
#freshProviderSessionId: string | undefined;
|
|
925
932
|
#isDisposed = false;
|
|
926
933
|
// Extension system
|
|
927
934
|
#extensionRunner: ExtensionRunner | undefined = undefined;
|
|
@@ -1275,6 +1282,14 @@ export class AgentSession {
|
|
|
1275
1282
|
return this.#modelRegistry;
|
|
1276
1283
|
}
|
|
1277
1284
|
|
|
1285
|
+
get asyncJobManager(): AsyncJobManager | undefined {
|
|
1286
|
+
return this.#asyncJobManager;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
getAgentId(): string | undefined {
|
|
1290
|
+
return this.#agentId;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1278
1293
|
/** Advance the tool-choice queue and return the next directive for the upcoming LLM call. */
|
|
1279
1294
|
nextToolChoice(): ToolChoice | undefined {
|
|
1280
1295
|
return this.#toolChoiceQueue.nextToolChoice();
|
|
@@ -1681,7 +1696,7 @@ export class AgentSession {
|
|
|
1681
1696
|
// Abort the stream immediately — do not gate on extension callbacks
|
|
1682
1697
|
this.#ttsrAbortPending = true;
|
|
1683
1698
|
this.#ensureTtsrResumePromise();
|
|
1684
|
-
this.agent.abort();
|
|
1699
|
+
this.agent.abort(this.#formatTtsrAbortReason(matches));
|
|
1685
1700
|
// Notify extensions (fire-and-forget, does not block abort)
|
|
1686
1701
|
this.#emitSessionEvent({ type: "ttsr_triggered", rules: matches }).catch(() => {});
|
|
1687
1702
|
// Schedule retry after a short delay
|
|
@@ -2162,6 +2177,12 @@ export class AgentSession {
|
|
|
2162
2177
|
}
|
|
2163
2178
|
}
|
|
2164
2179
|
|
|
2180
|
+
#formatTtsrAbortReason(rules: Rule[]): string {
|
|
2181
|
+
const label = rules.length === 1 ? "rule" : "rules";
|
|
2182
|
+
const ruleNames = rules.map(rule => rule.name).join(", ");
|
|
2183
|
+
return `TTSR matched ${label}: ${ruleNames}`;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2165
2186
|
/** Get TTSR injection payload and clear pending injections. */
|
|
2166
2187
|
#getTtsrInjectionContent(): { content: string; rules: Rule[] } | undefined {
|
|
2167
2188
|
if (this.#pendingTtsrInjections.length === 0) return undefined;
|
|
@@ -2185,13 +2206,20 @@ export class AgentSession {
|
|
|
2185
2206
|
* project, `~`-relative when it lives under home, else the raw path.
|
|
2186
2207
|
*/
|
|
2187
2208
|
#displayRulePath(rulePath: string): string {
|
|
2188
|
-
const cwdRel =
|
|
2209
|
+
const cwdRel =
|
|
2210
|
+
relativePathWithinRoot(this.sessionManager.getCwd(), rulePath) ??
|
|
2211
|
+
this.#displayPathWithinRoot(this.sessionManager.getCwd(), rulePath);
|
|
2189
2212
|
if (cwdRel) return cwdRel;
|
|
2190
2213
|
const homeRel = relativePathWithinRoot(os.homedir(), rulePath);
|
|
2191
2214
|
if (homeRel) return `~/${homeRel}`;
|
|
2192
2215
|
return rulePath;
|
|
2193
2216
|
}
|
|
2194
2217
|
|
|
2218
|
+
#displayPathWithinRoot(root: string, candidate: string): string | null {
|
|
2219
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
2220
|
+
return relative && !relative.startsWith("..") && !path.isAbsolute(relative) ? relative : null;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2195
2223
|
#addPendingTtsrInjections(rules: Rule[]): void {
|
|
2196
2224
|
const seen = new Set(this.#pendingTtsrInjections.map(rule => rule.name));
|
|
2197
2225
|
for (const rule of rules) {
|
|
@@ -2946,6 +2974,10 @@ export class AgentSession {
|
|
|
2946
2974
|
this.#unsubscribeAgent = this.agent.subscribe(this.#handleAgentEvent);
|
|
2947
2975
|
}
|
|
2948
2976
|
|
|
2977
|
+
#activeProviderSessionId(sessionId?: string): string {
|
|
2978
|
+
return this.#freshProviderSessionId ?? this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2949
2981
|
/**
|
|
2950
2982
|
* Set agent.sessionId from the session manager and install a dynamic
|
|
2951
2983
|
* metadata resolver so every Anthropic API request carries
|
|
@@ -2958,7 +2990,7 @@ export class AgentSession {
|
|
|
2958
2990
|
* `#syncAgentSessionId()` on every such event.
|
|
2959
2991
|
*/
|
|
2960
2992
|
#syncAgentSessionId(sessionId?: string): void {
|
|
2961
|
-
const sid = this.#
|
|
2993
|
+
const sid = this.#activeProviderSessionId(sessionId);
|
|
2962
2994
|
this.agent.sessionId = sid;
|
|
2963
2995
|
this.agent.setMetadataResolver((provider: string) =>
|
|
2964
2996
|
buildSessionMetadata(sid, provider, this.#modelRegistry.authStorage),
|
|
@@ -3088,6 +3120,23 @@ export class AgentSession {
|
|
|
3088
3120
|
this.#providerSessionState.clear();
|
|
3089
3121
|
}
|
|
3090
3122
|
|
|
3123
|
+
freshSession(): FreshSessionResult | undefined {
|
|
3124
|
+
if (this.isStreaming) return undefined;
|
|
3125
|
+
const previousSessionId = this.sessionId;
|
|
3126
|
+
const closedProviderSessions = this.#providerSessionState.size;
|
|
3127
|
+
this.#closeAllProviderSessions("fresh session");
|
|
3128
|
+
this.#freshProviderSessionId = Bun.randomUUIDv7();
|
|
3129
|
+
this.#syncAgentSessionId();
|
|
3130
|
+
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
3131
|
+
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
3132
|
+
this.agent.appendOnlyContext?.invalidateForModelChange();
|
|
3133
|
+
return {
|
|
3134
|
+
previousSessionId,
|
|
3135
|
+
sessionId: this.sessionId,
|
|
3136
|
+
closedProviderSessions,
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3091
3140
|
// =========================================================================
|
|
3092
3141
|
// Read-only State Access
|
|
3093
3142
|
// =========================================================================
|
|
@@ -3992,7 +4041,7 @@ export class AgentSession {
|
|
|
3992
4041
|
|
|
3993
4042
|
/** Current session ID */
|
|
3994
4043
|
get sessionId(): string {
|
|
3995
|
-
return this.#
|
|
4044
|
+
return this.#activeProviderSessionId();
|
|
3996
4045
|
}
|
|
3997
4046
|
getEvalSessionId(): string | null {
|
|
3998
4047
|
if (this.#parentEvalSessionId !== undefined) return this.#parentEvalSessionId;
|
|
@@ -5091,8 +5140,13 @@ export class AgentSession {
|
|
|
5091
5140
|
|
|
5092
5141
|
/**
|
|
5093
5142
|
* Abort current operation and wait for agent to become idle.
|
|
5143
|
+
*
|
|
5144
|
+
* `reason` (e.g. `USER_INTERRUPT_LABEL`) rides the agent's `AbortController`
|
|
5145
|
+
* and surfaces verbatim on the aborted assistant message's `errorMessage`, so
|
|
5146
|
+
* the transcript can distinguish a deliberate user interrupt from an opaque
|
|
5147
|
+
* abort. Omit it for internal/lifecycle aborts.
|
|
5094
5148
|
*/
|
|
5095
|
-
async abort(options?: { goalReason?: "interrupted" | "internal" }): Promise<void> {
|
|
5149
|
+
async abort(options?: { goalReason?: "interrupted" | "internal"; reason?: string }): Promise<void> {
|
|
5096
5150
|
this.abortRetry();
|
|
5097
5151
|
this.#promptGeneration++;
|
|
5098
5152
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
@@ -5101,7 +5155,7 @@ export class AgentSession {
|
|
|
5101
5155
|
this.abortBash();
|
|
5102
5156
|
this.abortEval();
|
|
5103
5157
|
const postPromptDrain = this.#cancelPostPromptTasks();
|
|
5104
|
-
this.agent.abort();
|
|
5158
|
+
this.agent.abort(options?.reason);
|
|
5105
5159
|
await postPromptDrain;
|
|
5106
5160
|
await this.agent.waitForIdle();
|
|
5107
5161
|
await this.#goalRuntime.onTaskAborted({ reason: options?.goalReason ?? "interrupted" });
|
|
@@ -5118,6 +5172,19 @@ export class AgentSession {
|
|
|
5118
5172
|
}
|
|
5119
5173
|
}
|
|
5120
5174
|
|
|
5175
|
+
/**
|
|
5176
|
+
* Abort active work, then immediately resume the agent so queued steer/follow-up
|
|
5177
|
+
* messages drain instead of waiting for another natural turn boundary.
|
|
5178
|
+
*/
|
|
5179
|
+
async interruptAndFlushQueuedMessages(options?: { reason?: string }): Promise<void> {
|
|
5180
|
+
if (!this.agent.hasQueuedMessages()) return;
|
|
5181
|
+
await this.abort({ reason: options?.reason });
|
|
5182
|
+
if (!this.agent.hasQueuedMessages()) return;
|
|
5183
|
+
if (this.isCompacting || this.isGeneratingHandoff) return;
|
|
5184
|
+
await this.#maybeRestoreRetryFallbackPrimary();
|
|
5185
|
+
await this.agent.continue();
|
|
5186
|
+
}
|
|
5187
|
+
|
|
5121
5188
|
/**
|
|
5122
5189
|
* Start a new session, optionally with initial messages and parent tracking.
|
|
5123
5190
|
* Clears all messages and starts a new session.
|
|
@@ -5162,6 +5229,7 @@ export class AgentSession {
|
|
|
5162
5229
|
}
|
|
5163
5230
|
await this.sessionManager.newSession(options);
|
|
5164
5231
|
this.setTodoPhases([]);
|
|
5232
|
+
this.#freshProviderSessionId = undefined;
|
|
5165
5233
|
this.#syncAgentSessionId();
|
|
5166
5234
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
5167
5235
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
@@ -5259,6 +5327,7 @@ export class AgentSession {
|
|
|
5259
5327
|
}
|
|
5260
5328
|
|
|
5261
5329
|
// Update agent session ID
|
|
5330
|
+
this.#freshProviderSessionId = undefined;
|
|
5262
5331
|
this.#syncAgentSessionId();
|
|
5263
5332
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
5264
5333
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
@@ -6226,6 +6295,7 @@ export class AgentSession {
|
|
|
6226
6295
|
this.#cancelOwnAsyncJobs();
|
|
6227
6296
|
await this.sessionManager.newSession(previousSessionFile ? { parentSession: previousSessionFile } : undefined);
|
|
6228
6297
|
this.agent.reset();
|
|
6298
|
+
this.#freshProviderSessionId = undefined;
|
|
6229
6299
|
this.#syncAgentSessionId();
|
|
6230
6300
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
6231
6301
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
@@ -8941,6 +9011,7 @@ export class AgentSession {
|
|
|
8941
9011
|
const previousTools = [...this.agent.state.tools];
|
|
8942
9012
|
const previousBaseSystemPrompt = this.#baseSystemPrompt;
|
|
8943
9013
|
const previousSystemPrompt = this.agent.state.systemPrompt;
|
|
9014
|
+
const previousFreshProviderSessionId = this.#freshProviderSessionId;
|
|
8944
9015
|
const previousFallbackSelectedMCPToolNames = previousSessionFile
|
|
8945
9016
|
? this.#getSessionDefaultSelectedMCPToolNames(previousSessionFile)
|
|
8946
9017
|
: undefined;
|
|
@@ -8952,6 +9023,9 @@ export class AgentSession {
|
|
|
8952
9023
|
|
|
8953
9024
|
try {
|
|
8954
9025
|
await this.sessionManager.setSessionFile(sessionPath);
|
|
9026
|
+
if (switchingToDifferentSession) {
|
|
9027
|
+
this.#freshProviderSessionId = undefined;
|
|
9028
|
+
}
|
|
8955
9029
|
this.#syncAgentSessionId();
|
|
8956
9030
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
8957
9031
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
@@ -9061,6 +9135,7 @@ export class AgentSession {
|
|
|
9061
9135
|
return true;
|
|
9062
9136
|
} catch (error) {
|
|
9063
9137
|
this.sessionManager.restoreState(previousSessionState);
|
|
9138
|
+
this.#freshProviderSessionId = previousFreshProviderSessionId;
|
|
9064
9139
|
this.#syncAgentSessionId(previousSessionState.sessionId);
|
|
9065
9140
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
9066
9141
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
|
@@ -9159,6 +9234,7 @@ export class AgentSession {
|
|
|
9159
9234
|
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
9160
9235
|
}
|
|
9161
9236
|
this.#syncTodoPhasesFromBranch();
|
|
9237
|
+
this.#freshProviderSessionId = undefined;
|
|
9162
9238
|
this.#syncAgentSessionId();
|
|
9163
9239
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
9164
9240
|
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
package/src/session/messages.ts
CHANGED
|
@@ -70,6 +70,32 @@ export function isSilentAbort(errorMessage: string | undefined): boolean {
|
|
|
70
70
|
return errorMessage === SILENT_ABORT_MARKER;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/** Reason threaded through `AbortController.abort(reason)` when the user aborts
|
|
74
|
+
* the turn with Esc (see `AgentSession.abort`). The agent surfaces it verbatim
|
|
75
|
+
* on the aborted assistant message's `errorMessage`, so the transcript reads as
|
|
76
|
+
* a deliberate user interrupt instead of an opaque failure. */
|
|
77
|
+
export const USER_INTERRUPT_LABEL = "Interrupted by user";
|
|
78
|
+
|
|
79
|
+
/** Sentinel `errorMessage` the agent stamps on any abort that carried no custom
|
|
80
|
+
* reason (bare `abort()`). Renderers treat it as "no specific reason given". */
|
|
81
|
+
const GENERIC_ABORT_SENTINEL = "Request was aborted";
|
|
82
|
+
|
|
83
|
+
/** Resolve the operator-facing label for an aborted assistant turn. A custom
|
|
84
|
+
* abort reason (e.g. `USER_INTERRUPT_LABEL`) threaded onto `errorMessage` is
|
|
85
|
+
* shown verbatim; aborts with no threaded reason fall back to the retry-aware
|
|
86
|
+
* generic label. Centralizes the live-stream (`EventController`), replay
|
|
87
|
+
* (`ui-helpers`), and component (`AssistantMessageComponent`) render paths so
|
|
88
|
+
* they stay in lockstep. */
|
|
89
|
+
export function resolveAbortLabel(errorMessage: string | undefined, retryAttempt = 0): string {
|
|
90
|
+
if (errorMessage && errorMessage !== GENERIC_ABORT_SENTINEL && !isSilentAbort(errorMessage)) {
|
|
91
|
+
return errorMessage;
|
|
92
|
+
}
|
|
93
|
+
if (retryAttempt > 0) {
|
|
94
|
+
return `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`;
|
|
95
|
+
}
|
|
96
|
+
return "Operation aborted";
|
|
97
|
+
}
|
|
98
|
+
|
|
73
99
|
/** Extract the optional `__pendingDisplayTag` field from a CustomMessage's
|
|
74
100
|
* `details` blob. Safe over `unknown`; returns undefined when the field is
|
|
75
101
|
* absent or non-string. */
|
|
@@ -1967,6 +1967,7 @@ export class SessionManager {
|
|
|
1967
1967
|
#inMemoryArtifacts: Map<string, string> | null = null;
|
|
1968
1968
|
#inMemoryArtifactCounter = 0;
|
|
1969
1969
|
readonly #blobStore: BlobStore;
|
|
1970
|
+
#suppressBreadcrumb = false;
|
|
1970
1971
|
|
|
1971
1972
|
private constructor(
|
|
1972
1973
|
private cwd: string,
|
|
@@ -1981,6 +1982,11 @@ export class SessionManager {
|
|
|
1981
1982
|
// Note: call _initSession() or _initSessionFile() after construction
|
|
1982
1983
|
}
|
|
1983
1984
|
|
|
1985
|
+
#maybeWriteBreadcrumb(cwd: string, sessionFile: string): void {
|
|
1986
|
+
if (this.#suppressBreadcrumb) return;
|
|
1987
|
+
writeTerminalBreadcrumb(cwd, sessionFile);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1984
1990
|
/** Puts a binary blob into the blob store and returns the blob reference */
|
|
1985
1991
|
async putBlob(data: Buffer, options?: BlobPutOptions): Promise<BlobPutResult> {
|
|
1986
1992
|
return this.#blobStore.put(data, options);
|
|
@@ -2027,7 +2033,7 @@ export class SessionManager {
|
|
|
2027
2033
|
this.#adoptedArtifactManager = null;
|
|
2028
2034
|
this.#buildIndex();
|
|
2029
2035
|
if (this.#sessionFile) {
|
|
2030
|
-
|
|
2036
|
+
this.#maybeWriteBreadcrumb(this.cwd, this.#sessionFile);
|
|
2031
2037
|
}
|
|
2032
2038
|
}
|
|
2033
2039
|
|
|
@@ -2047,7 +2053,7 @@ export class SessionManager {
|
|
|
2047
2053
|
this.#persistError = undefined;
|
|
2048
2054
|
this.#persistErrorReported = false;
|
|
2049
2055
|
this.#sessionFile = path.resolve(sessionFile);
|
|
2050
|
-
|
|
2056
|
+
this.#maybeWriteBreadcrumb(this.cwd, this.#sessionFile);
|
|
2051
2057
|
this.#fileEntries = await loadEntriesFromFile(this.#sessionFile, this.storage);
|
|
2052
2058
|
if (this.#fileEntries.length > 0) {
|
|
2053
2059
|
const header = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
|
|
@@ -2064,7 +2070,7 @@ export class SessionManager {
|
|
|
2064
2070
|
if (headerCwd && headerCwd !== this.cwd) {
|
|
2065
2071
|
this.cwd = headerCwd;
|
|
2066
2072
|
this.sessionDir = path.resolve(this.#sessionFile, "..");
|
|
2067
|
-
|
|
2073
|
+
this.#maybeWriteBreadcrumb(this.cwd, this.#sessionFile);
|
|
2068
2074
|
}
|
|
2069
2075
|
|
|
2070
2076
|
this.#needsFullRewriteOnNextPersist = migrateToCurrentVersion(this.#fileEntries);
|
|
@@ -2245,7 +2251,7 @@ export class SessionManager {
|
|
|
2245
2251
|
|
|
2246
2252
|
// Update terminal breadcrumb
|
|
2247
2253
|
if (this.#sessionFile) {
|
|
2248
|
-
|
|
2254
|
+
this.#maybeWriteBreadcrumb(resolvedCwd, this.#sessionFile);
|
|
2249
2255
|
}
|
|
2250
2256
|
}
|
|
2251
2257
|
|
|
@@ -2280,7 +2286,7 @@ export class SessionManager {
|
|
|
2280
2286
|
if (this.persist) {
|
|
2281
2287
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
2282
2288
|
this.#sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.#sessionId}.jsonl`);
|
|
2283
|
-
|
|
2289
|
+
this.#maybeWriteBreadcrumb(this.cwd, this.#sessionFile);
|
|
2284
2290
|
}
|
|
2285
2291
|
return this.#sessionFile;
|
|
2286
2292
|
}
|
|
@@ -3429,9 +3435,11 @@ export class SessionManager {
|
|
|
3429
3435
|
cwd: string,
|
|
3430
3436
|
sessionDir?: string,
|
|
3431
3437
|
storage: SessionStorage = new FileSessionStorage(),
|
|
3438
|
+
options?: { suppressBreadcrumb?: boolean },
|
|
3432
3439
|
): Promise<SessionManager> {
|
|
3433
3440
|
const dir = sessionDir ?? SessionManager.getDefaultSessionDir(cwd, undefined, storage);
|
|
3434
3441
|
const manager = new SessionManager(cwd, dir, true, storage);
|
|
3442
|
+
manager.#suppressBreadcrumb = options?.suppressBreadcrumb === true;
|
|
3435
3443
|
const forkEntries = structuredClone(await loadEntriesFromFile(sourcePath, storage)) as FileEntry[];
|
|
3436
3444
|
migrateToCurrentVersion(forkEntries);
|
|
3437
3445
|
await resolveBlobRefsInEntries(forkEntries, manager.#blobStore);
|