@oh-my-pi/pi-coding-agent 13.3.13 → 13.4.0
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 +97 -7
- package/examples/sdk/README.md +22 -0
- package/package.json +7 -7
- package/src/capability/index.ts +1 -11
- package/src/commit/analysis/index.ts +4 -4
- package/src/config/settings-schema.ts +18 -15
- package/src/config/settings.ts +2 -20
- package/src/discovery/index.ts +1 -11
- package/src/exa/index.ts +1 -10
- package/src/extensibility/custom-commands/index.ts +2 -15
- package/src/extensibility/custom-tools/index.ts +3 -18
- package/src/extensibility/custom-tools/loader.ts +28 -5
- package/src/extensibility/custom-tools/types.ts +18 -1
- package/src/extensibility/extensions/index.ts +9 -130
- package/src/extensibility/extensions/types.ts +2 -1
- package/src/extensibility/hooks/index.ts +3 -14
- package/src/extensibility/plugins/index.ts +6 -31
- package/src/index.ts +28 -220
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/index.ts +11 -16
- package/src/mcp/index.ts +11 -37
- package/src/mcp/tool-bridge.ts +3 -42
- package/src/mcp/transports/index.ts +2 -2
- package/src/modes/components/extensions/index.ts +3 -3
- package/src/modes/components/index.ts +35 -40
- package/src/modes/interactive-mode.ts +4 -1
- package/src/modes/rpc/rpc-mode.ts +1 -7
- package/src/modes/theme/theme.ts +11 -10
- package/src/modes/types.ts +1 -1
- package/src/patch/index.ts +4 -20
- package/src/prompts/system/system-prompt.md +18 -4
- package/src/prompts/tools/ast-edit.md +33 -0
- package/src/prompts/tools/ast-grep.md +34 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/hashline.md +1 -0
- package/src/prompts/tools/resolve.md +8 -0
- package/src/sdk.ts +27 -7
- package/src/session/agent-session.ts +25 -36
- package/src/session/session-manager.ts +0 -30
- package/src/slash-commands/builtin-registry.ts +4 -2
- package/src/stt/index.ts +3 -3
- package/src/task/types.ts +2 -2
- package/src/tools/ast-edit.ts +480 -0
- package/src/tools/ast-grep.ts +435 -0
- package/src/tools/bash.ts +3 -2
- package/src/tools/gemini-image.ts +3 -3
- package/src/tools/grep.ts +26 -8
- package/src/tools/index.ts +55 -57
- package/src/tools/pending-action.ts +33 -0
- package/src/tools/render-utils.ts +10 -0
- package/src/tools/renderers.ts +6 -4
- package/src/tools/resolve.ts +156 -0
- package/src/tools/submit-result.ts +1 -1
- package/src/web/search/index.ts +6 -4
- package/src/web/search/providers/anthropic.ts +2 -2
- package/src/web/search/providers/base.ts +3 -0
- package/src/web/search/providers/exa.ts +11 -5
- package/src/web/search/providers/gemini.ts +112 -24
- package/src/patch/normative.ts +0 -72
- package/src/prompts/tools/ast-find.md +0 -20
- package/src/prompts/tools/ast-replace.md +0 -21
- package/src/tools/ast-find.ts +0 -316
- package/src/tools/ast-replace.ts +0 -294
|
@@ -1,41 +1,36 @@
|
|
|
1
1
|
// UI Components barrel export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
16
|
-
export
|
|
17
|
-
export
|
|
18
|
-
export
|
|
19
|
-
export
|
|
20
|
-
export
|
|
21
|
-
export
|
|
22
|
-
export
|
|
23
|
-
export
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export
|
|
31
|
-
export
|
|
32
|
-
export
|
|
33
|
-
export
|
|
34
|
-
export
|
|
35
|
-
export
|
|
36
|
-
export
|
|
37
|
-
export { TtsrNotificationComponent } from "./ttsr-notification";
|
|
38
|
-
export { UserMessageComponent } from "./user-message";
|
|
39
|
-
export { UserMessageSelectorComponent } from "./user-message-selector";
|
|
40
|
-
export { truncateToVisualLines, type VisualTruncateResult } from "./visual-truncate";
|
|
41
|
-
export { type LspServerInfo, type RecentSession, WelcomeComponent } from "./welcome";
|
|
2
|
+
export * from "./assistant-message";
|
|
3
|
+
export * from "./bash-execution";
|
|
4
|
+
export * from "./bordered-loader";
|
|
5
|
+
export * from "./branch-summary-message";
|
|
6
|
+
export * from "./compaction-summary-message";
|
|
7
|
+
export * from "./countdown-timer";
|
|
8
|
+
export * from "./custom-editor";
|
|
9
|
+
export * from "./custom-message";
|
|
10
|
+
export * from "./diff";
|
|
11
|
+
export * from "./dynamic-border";
|
|
12
|
+
export * from "./footer";
|
|
13
|
+
export * from "./hook-editor";
|
|
14
|
+
export * from "./hook-input";
|
|
15
|
+
export * from "./hook-message";
|
|
16
|
+
export * from "./hook-selector";
|
|
17
|
+
export * from "./keybinding-hints";
|
|
18
|
+
export * from "./login-dialog";
|
|
19
|
+
export * from "./model-selector";
|
|
20
|
+
export * from "./oauth-selector";
|
|
21
|
+
export * from "./queue-mode-selector";
|
|
22
|
+
export * from "./read-tool-group";
|
|
23
|
+
export * from "./session-selector";
|
|
24
|
+
export * from "./settings-selector";
|
|
25
|
+
export * from "./show-images-selector";
|
|
26
|
+
export * from "./status-line";
|
|
27
|
+
export * from "./theme-selector";
|
|
28
|
+
export * from "./thinking-selector";
|
|
29
|
+
export * from "./todo-reminder";
|
|
30
|
+
export * from "./tool-execution";
|
|
31
|
+
export * from "./tree-selector";
|
|
32
|
+
export * from "./ttsr-notification";
|
|
33
|
+
export * from "./user-message";
|
|
34
|
+
export * from "./user-message-selector";
|
|
35
|
+
export * from "./visual-truncate";
|
|
36
|
+
export * from "./welcome";
|
|
@@ -702,7 +702,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
702
702
|
await this.session.prompt(prompt, { synthetic: true });
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
-
async handlePlanModeCommand(): Promise<void> {
|
|
705
|
+
async handlePlanModeCommand(initialPrompt?: string): Promise<void> {
|
|
706
706
|
if (this.planModeEnabled) {
|
|
707
707
|
const confirmed = await this.showHookConfirm(
|
|
708
708
|
"Exit plan mode?",
|
|
@@ -713,6 +713,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
713
713
|
return;
|
|
714
714
|
}
|
|
715
715
|
await this.#enterPlanMode();
|
|
716
|
+
if (initialPrompt) {
|
|
717
|
+
this.onInputCallback?.({ text: initialPrompt });
|
|
718
|
+
}
|
|
716
719
|
}
|
|
717
720
|
|
|
718
721
|
async handleExitPlanModeTool(details: ExitPlanModeDetails): Promise<void> {
|
|
@@ -23,13 +23,7 @@ import type {
|
|
|
23
23
|
} from "./rpc-types";
|
|
24
24
|
|
|
25
25
|
// Re-export types for consumers
|
|
26
|
-
export type
|
|
27
|
-
RpcCommand,
|
|
28
|
-
RpcExtensionUIRequest,
|
|
29
|
-
RpcExtensionUIResponse,
|
|
30
|
-
RpcResponse,
|
|
31
|
-
RpcSessionState,
|
|
32
|
-
} from "./rpc-types";
|
|
26
|
+
export type * from "./rpc-types";
|
|
33
27
|
|
|
34
28
|
/**
|
|
35
29
|
* Run in RPC mode.
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -1629,25 +1629,26 @@ function detectTerminalBackground(): "dark" | "light" {
|
|
|
1629
1629
|
if (terminalReportedAppearance) {
|
|
1630
1630
|
return terminalReportedAppearance;
|
|
1631
1631
|
}
|
|
1632
|
-
//
|
|
1633
|
-
//
|
|
1634
|
-
//
|
|
1635
|
-
const macAppearance = macOSReportedAppearance ?? detectMacOSAppearance();
|
|
1636
|
-
if (macAppearance) {
|
|
1637
|
-
return macAppearance;
|
|
1638
|
-
}
|
|
1639
|
-
// Fallback: COLORFGBG environment variable (static, set once at terminal launch)
|
|
1632
|
+
// COLORFGBG is set by the terminal emulator to reflect the actual profile colors.
|
|
1633
|
+
// Check it before macOS system appearance because the terminal profile may differ
|
|
1634
|
+
// from the OS-level dark/light setting (e.g. dark terminal on macOS light mode).
|
|
1640
1635
|
const colorfgbg = Bun.env.COLORFGBG || "";
|
|
1641
1636
|
if (colorfgbg) {
|
|
1642
1637
|
const parts = colorfgbg.split(";");
|
|
1643
1638
|
if (parts.length >= 2) {
|
|
1644
1639
|
const bg = parseInt(parts[1], 10);
|
|
1645
1640
|
if (!Number.isNaN(bg)) {
|
|
1646
|
-
|
|
1647
|
-
return result;
|
|
1641
|
+
return bg < 8 ? "dark" : "light";
|
|
1648
1642
|
}
|
|
1649
1643
|
}
|
|
1650
1644
|
}
|
|
1645
|
+
// macOS: query system appearance via CoreFoundation (native, no shell).
|
|
1646
|
+
// Uses cached observer value, or falls back to CFPreferencesCopyAppValue.
|
|
1647
|
+
// Works on all terminals including Warp which lacks Mode 2031 / OSC 11.
|
|
1648
|
+
const macAppearance = macOSReportedAppearance ?? detectMacOSAppearance();
|
|
1649
|
+
if (macAppearance) {
|
|
1650
|
+
return macAppearance;
|
|
1651
|
+
}
|
|
1651
1652
|
return "dark";
|
|
1652
1653
|
}
|
|
1653
1654
|
|
package/src/modes/types.ts
CHANGED
|
@@ -197,7 +197,7 @@ export interface InteractiveModeContext {
|
|
|
197
197
|
toggleThinkingBlockVisibility(): void;
|
|
198
198
|
openExternalEditor(): void;
|
|
199
199
|
registerExtensionShortcuts(): void;
|
|
200
|
-
handlePlanModeCommand(): Promise<void>;
|
|
200
|
+
handlePlanModeCommand(initialPrompt?: string): Promise<void>;
|
|
201
201
|
handleExitPlanModeTool(details: ExitPlanModeDetails): Promise<void>;
|
|
202
202
|
|
|
203
203
|
// Hook UI methods
|
package/src/patch/index.ts
CHANGED
|
@@ -48,30 +48,14 @@ import { EditMatchError } from "./types";
|
|
|
48
48
|
// Application
|
|
49
49
|
export { applyPatch, defaultFileSystem, previewPatch } from "./applicator";
|
|
50
50
|
// Diff generation
|
|
51
|
-
export
|
|
52
|
-
computeEditDiff,
|
|
53
|
-
computeHashlineDiff,
|
|
54
|
-
computePatchDiff,
|
|
55
|
-
generateDiffString,
|
|
56
|
-
generateUnifiedDiffString,
|
|
57
|
-
replaceText,
|
|
58
|
-
} from "./diff";
|
|
51
|
+
export * from "./diff";
|
|
59
52
|
|
|
60
53
|
// Fuzzy matching
|
|
61
|
-
export
|
|
54
|
+
export * from "./fuzzy";
|
|
62
55
|
// Hashline
|
|
63
|
-
export
|
|
64
|
-
applyHashlineEdits,
|
|
65
|
-
computeLineHash,
|
|
66
|
-
formatHashLines,
|
|
67
|
-
HashlineMismatchError,
|
|
68
|
-
parseTag,
|
|
69
|
-
streamHashLinesFromLines,
|
|
70
|
-
streamHashLinesFromUtf8,
|
|
71
|
-
validateLineRef,
|
|
72
|
-
} from "./hashline";
|
|
56
|
+
export * from "./hashline";
|
|
73
57
|
// Normalization
|
|
74
|
-
export
|
|
58
|
+
export * from "./normalize";
|
|
75
59
|
// Parsing
|
|
76
60
|
export { normalizeCreateContent, normalizeDiff, parseHunks as parseDiffHunks } from "./parser";
|
|
77
61
|
export type { EditRenderContext, EditToolDetails } from "./shared";
|
|
@@ -154,13 +154,28 @@ Semantic questions **MUST** be answered with semantic tools.
|
|
|
154
154
|
- Can the server propose fixes/imports/refactors? → `lsp code_actions` (list first, then apply with `apply: true` + `query`)
|
|
155
155
|
{{/has}}
|
|
156
156
|
|
|
157
|
-
{{#ifAny (includes tools "
|
|
157
|
+
{{#ifAny (includes tools "ast_grep") (includes tools "ast_edit")}}
|
|
158
158
|
### AST tools for structural code work
|
|
159
159
|
|
|
160
160
|
When AST tools are available, syntax-aware operations take priority over text hacks.
|
|
161
|
-
{{#has tools "
|
|
162
|
-
{{#has tools "
|
|
161
|
+
{{#has tools "ast_grep"}}- Use `ast_grep` for structural discovery (call shapes, declarations, syntax patterns) before text grep when code structure matters{{/has}}
|
|
162
|
+
{{#has tools "ast_edit"}}- Use `ast_edit` for structural codemods/replacements; do not use bash `sed`/`perl`/`awk` for syntax-level rewrites{{/has}}
|
|
163
163
|
- Use `grep` for plain text/regex lookup only when AST shape is irrelevant
|
|
164
|
+
|
|
165
|
+
#### Pattern syntax
|
|
166
|
+
|
|
167
|
+
Patterns match **AST structure, not text** — whitespace and formatting are irrelevant. `foo( x, y )` and `foo(x,y)` are the same pattern.
|
|
168
|
+
|
|
169
|
+
|Syntax|Name|Matches|
|
|
170
|
+
|---|---|---|
|
|
171
|
+
|`$VAR`|Capture|One AST node, bound as `$VAR`|
|
|
172
|
+
|`$_`|Wildcard|One AST node, not captured|
|
|
173
|
+
|`$$$VAR`|Variadic capture|Zero or more nodes, bound as `$VAR`|
|
|
174
|
+
|`$$$`|Variadic wildcard|Zero or more nodes, not captured|
|
|
175
|
+
|
|
176
|
+
Metavariable names **MUST** be UPPERCASE (`$A`, `$FUNC`, `$MY_VAR`). Lowercase `$var` is invalid.
|
|
177
|
+
|
|
178
|
+
When a metavariable appears multiple times in one pattern, all occurrences must match **identical** code: `$A == $A` matches `x == x` but not `x == y`.
|
|
164
179
|
{{/ifAny}}
|
|
165
180
|
{{#if eagerTasks}}
|
|
166
181
|
<eager-tasks>
|
|
@@ -244,7 +259,6 @@ Justify sequential work; default parallel. Cannot articulate why B depends on A
|
|
|
244
259
|
- You **MUST NOT** yield without proof when non-trivial work, self-assessment is deceptive: tests, linters, type checks, repro steps… exhaust all external verification.
|
|
245
260
|
## 8. Handoff
|
|
246
261
|
Before finishing, you **MUST**:
|
|
247
|
-
- List all commands run and confirm they passed.
|
|
248
262
|
- Summarize changes with file and line references.
|
|
249
263
|
- Call out TODOs, follow-up work, or uncertainties — no surprises are **PERMITTED**.
|
|
250
264
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Performs structural AST-aware rewrites via native ast-grep.
|
|
2
|
+
|
|
3
|
+
<instruction>
|
|
4
|
+
- Use for codemods and structural rewrites where plain text replace is unsafe
|
|
5
|
+
- Narrow scope with `path` before replacing (`path` accepts files, directories, or glob patterns)
|
|
6
|
+
- Default to language-scoped rewrites in mixed repositories: set `lang` and keep `path` narrow
|
|
7
|
+
- Always returns a preview; after reviewing, call `resolve` with `action: "apply"` or `action: "discard"`
|
|
8
|
+
- Treat parse issues as a scoping signal: tighten `path`/`lang` before retrying
|
|
9
|
+
- Metavariables captured in each rewrite pattern (`$A`, `$$$ARGS`) are substituted into that entry's rewrite template
|
|
10
|
+
- Each matched rewrite is a 1:1 structural substitution; you cannot split one capture into multiple nodes or merge multiple captures into one node
|
|
11
|
+
</instruction>
|
|
12
|
+
|
|
13
|
+
<output>
|
|
14
|
+
- Returns replacement summary, per-file replacement counts, and change previews
|
|
15
|
+
- Reports whether changes were applied or only previewed
|
|
16
|
+
- Includes parse issues when files cannot be processed
|
|
17
|
+
</output>
|
|
18
|
+
|
|
19
|
+
<examples>
|
|
20
|
+
- Rename a call site across a directory, preview first:
|
|
21
|
+
`{"ops":[{"pat":"oldApi($$$ARGS)","out":"newApi($$$ARGS)"}],"lang":"typescript","path":"src/"}`
|
|
22
|
+
- Multi-op codemod preview before resolving:
|
|
23
|
+
`{"ops":[{"pat":"require($A)","out":"import $A"},{"pat":"module.exports = $E","out":"export default $E"}],"lang":"javascript","path":"src/"}`
|
|
24
|
+
- Swap two arguments using captures:
|
|
25
|
+
`{"ops":[{"pat":"assertEqual($A, $B)","out":"assertEqual($B, $A)"}],"lang":"typescript","path":"tests/"}`
|
|
26
|
+
</examples>
|
|
27
|
+
|
|
28
|
+
<critical>
|
|
29
|
+
- `ops` **MUST** contain at least one concrete `{ pat, out }` entry
|
|
30
|
+
- If the path pattern spans multiple languages, set `lang` explicitly for deterministic rewrites
|
|
31
|
+
- Review preview output, then use the `resolve` tool to apply or discard (with a reason)
|
|
32
|
+
- For one-off local text edits, prefer the Edit tool instead of AST edit
|
|
33
|
+
</critical>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Performs structural code search using AST matching via native ast-grep.
|
|
2
|
+
|
|
3
|
+
<instruction>
|
|
4
|
+
- Use this when syntax shape matters more than raw text (calls, declarations, specific language constructs)
|
|
5
|
+
- Prefer a precise `path` scope to keep results targeted and deterministic (`path` accepts files, directories, or glob patterns)
|
|
6
|
+
- Default to language-scoped search in mixed repositories: pair `path` glob + explicit `lang` to avoid parse-noise from non-source files
|
|
7
|
+
- `patterns` is required and must include at least one non-empty AST pattern; `lang` is optional (`lang` is inferred per file extension when omitted)
|
|
8
|
+
- Multiple patterns run in one native pass; results are merged and then `offset`/`limit` are applied to the combined match set
|
|
9
|
+
- Use `selector` only for contextual pattern mode; otherwise provide direct patterns
|
|
10
|
+
- For variadic arguments/fields, use `$$$NAME` (not `$$NAME`)
|
|
11
|
+
- Patterns match AST structure, not text — whitespace/formatting differences are ignored
|
|
12
|
+
- When the same metavariable appears multiple times, all occurrences must match identical code
|
|
13
|
+
</instruction>
|
|
14
|
+
|
|
15
|
+
<output>
|
|
16
|
+
- Returns grouped matches with file path, byte range, line/column ranges, and metavariable captures
|
|
17
|
+
- Includes summary counts (`totalMatches`, `filesWithMatches`, `filesSearched`) and parse issues when present
|
|
18
|
+
</output>
|
|
19
|
+
|
|
20
|
+
<examples>
|
|
21
|
+
- Find all console logging calls in one pass (multi-pattern, scoped):
|
|
22
|
+
`{"patterns":["console.log($$$)","console.error($$$)"],"lang":"typescript","path":"src/"}`
|
|
23
|
+
- Capture and inspect metavariable bindings from a pattern:
|
|
24
|
+
`{"patterns":["require($MOD)"],"lang":"javascript","path":"src/"}`
|
|
25
|
+
- Contextual pattern with selector — match only the identifier `foo`, not the whole call:
|
|
26
|
+
`{"patterns":["foo()"],"selector":"identifier","lang":"typescript","path":"src/utils.ts"}`
|
|
27
|
+
</examples>
|
|
28
|
+
|
|
29
|
+
<critical>
|
|
30
|
+
- `patterns` is required
|
|
31
|
+
- Set `lang` explicitly to constrain matching when path pattern spans mixed-language trees
|
|
32
|
+
- Avoid repo-root AST scans when the target is language-specific; narrow `path` first
|
|
33
|
+
- If exploration is broad/open-ended across subsystems, use Task tool with explore subagent first
|
|
34
|
+
</critical>
|
|
@@ -35,8 +35,8 @@ You **MUST** use specialized tools instead of bash for ALL file operations:
|
|
|
35
35
|
|`ls dir/`|`read(path="dir/")`|
|
|
36
36
|
|`cat <<'EOF' > file`|`write(path="file", content="...")`|
|
|
37
37
|
|`sed -i 's/old/new/' file`|`edit(path="file", edits=[...])`|
|
|
38
|
-
- If `
|
|
39
|
-
- Bash is for command execution, not syntax-aware code transformation; prefer `
|
|
38
|
+
- If `ast_grep` / `ast_edit` tools are available in the session, you **MUST** use them for structural code search/rewrites instead of bash `grep`/`sed`/`awk`/`perl` pipelines
|
|
39
|
+
- Bash is for command execution, not syntax-aware code transformation; prefer `ast_grep` for discovery and `ast_edit` for codemods
|
|
40
40
|
- You **MUST NOT** use Bash for these operations like read, grep, find, edit, write, where specialized tools exist.
|
|
41
41
|
- You **MUST NOT** use `2>&1` | `2>/dev/null` pattern, stdout and stderr are already merged.
|
|
42
42
|
- You **MUST NOT** use `| head -n 50` or `| tail -n 100` pattern, use `head` and `tail` parameters instead.
|
|
@@ -247,4 +247,5 @@ Good — anchors to structural line:
|
|
|
247
247
|
- Every tag **MUST** be copied exactly from fresh tool result as `N#ID`.
|
|
248
248
|
- You **MUST** re-read after each edit call before issuing another on same file.
|
|
249
249
|
- Formatting is a batch operation. You **MUST** never use this tool for formatting.
|
|
250
|
+
- `lines` entries **MUST** be literal file content with real space indentation. (`\\t` in JSON inserts a literal backslash-t into the file, not a tab.)
|
|
250
251
|
</critical>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Resolves a pending preview action by either applying or discarding it.
|
|
2
|
+
- `action` is required:
|
|
3
|
+
- `"apply"` persists the pending changes.
|
|
4
|
+
- `"discard"` rejects the pending changes.
|
|
5
|
+
- `reason` is required and must explain why you chose to apply or discard.
|
|
6
|
+
|
|
7
|
+
This tool is only valid when a pending action exists (typically after a preview step).
|
|
8
|
+
If no pending action exists, the call fails with an error.
|
package/src/sdk.ts
CHANGED
|
@@ -84,9 +84,11 @@ import {
|
|
|
84
84
|
FindTool,
|
|
85
85
|
GrepTool,
|
|
86
86
|
getSearchTools,
|
|
87
|
+
HIDDEN_TOOLS,
|
|
87
88
|
loadSshTool,
|
|
88
89
|
PythonTool,
|
|
89
90
|
ReadTool,
|
|
91
|
+
ResolveTool,
|
|
90
92
|
setPreferredImageProvider,
|
|
91
93
|
setPreferredSearchProvider,
|
|
92
94
|
type Tool,
|
|
@@ -96,6 +98,8 @@ import {
|
|
|
96
98
|
} from "./tools";
|
|
97
99
|
import { ToolContextStore } from "./tools/context";
|
|
98
100
|
import { getGeminiImageTools } from "./tools/gemini-image";
|
|
101
|
+
import { wrapToolWithMetaNotice } from "./tools/output-meta";
|
|
102
|
+
import { PendingActionStore } from "./tools/pending-action";
|
|
99
103
|
import { EventBus } from "./utils/event-bus";
|
|
100
104
|
|
|
101
105
|
// Types
|
|
@@ -205,13 +209,7 @@ export type { PromptTemplate } from "./config/prompt-templates";
|
|
|
205
209
|
export { Settings, type SkillsSettings } from "./config/settings";
|
|
206
210
|
export type { CustomCommand, CustomCommandFactory } from "./extensibility/custom-commands/types";
|
|
207
211
|
export type { CustomTool, CustomToolFactory } from "./extensibility/custom-tools/types";
|
|
208
|
-
export type
|
|
209
|
-
ExtensionAPI,
|
|
210
|
-
ExtensionCommandContext,
|
|
211
|
-
ExtensionContext,
|
|
212
|
-
ExtensionFactory,
|
|
213
|
-
ToolDefinition,
|
|
214
|
-
} from "./extensibility/extensions";
|
|
212
|
+
export type * from "./extensibility/extensions";
|
|
215
213
|
export type { Skill } from "./extensibility/skills";
|
|
216
214
|
export type { FileSlashCommand } from "./extensibility/slash-commands";
|
|
217
215
|
export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp";
|
|
@@ -222,6 +220,7 @@ export {
|
|
|
222
220
|
BashTool,
|
|
223
221
|
// Tool classes and factories
|
|
224
222
|
BUILTIN_TOOLS,
|
|
223
|
+
HIDDEN_TOOLS,
|
|
225
224
|
createTools,
|
|
226
225
|
EditTool,
|
|
227
226
|
FindTool,
|
|
@@ -229,6 +228,7 @@ export {
|
|
|
229
228
|
loadSshTool,
|
|
230
229
|
PythonTool,
|
|
231
230
|
ReadTool,
|
|
231
|
+
ResolveTool,
|
|
232
232
|
WriteTool,
|
|
233
233
|
type ToolSession,
|
|
234
234
|
};
|
|
@@ -776,6 +776,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
776
776
|
})
|
|
777
777
|
: undefined;
|
|
778
778
|
|
|
779
|
+
const pendingActionStore = new PendingActionStore();
|
|
779
780
|
const toolSession: ToolSession = {
|
|
780
781
|
cwd,
|
|
781
782
|
hasUI: options.hasUI ?? false,
|
|
@@ -816,6 +817,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
816
817
|
authStorage,
|
|
817
818
|
modelRegistry,
|
|
818
819
|
asyncJobManager,
|
|
820
|
+
pendingActionStore,
|
|
819
821
|
};
|
|
820
822
|
|
|
821
823
|
// Initialize internal URL router for internal protocols (agent://, artifact://, memory://, skill://, rule://, local://)
|
|
@@ -924,6 +926,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
924
926
|
[],
|
|
925
927
|
cwd,
|
|
926
928
|
builtInToolNames,
|
|
929
|
+
pendingActionStore,
|
|
927
930
|
);
|
|
928
931
|
for (const { path, error } of discoveredCustomTools.errors) {
|
|
929
932
|
logger.error("Custom tool load failed", { path, error });
|
|
@@ -1109,6 +1112,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1109
1112
|
toolRegistry.delete("edit");
|
|
1110
1113
|
}
|
|
1111
1114
|
|
|
1115
|
+
const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
|
|
1116
|
+
if (!hasDeferrableTools) {
|
|
1117
|
+
toolRegistry.delete("resolve");
|
|
1118
|
+
} else if (!toolRegistry.has("resolve")) {
|
|
1119
|
+
const resolveTool = await logger.timeAsync("createTools:resolve:session", HIDDEN_TOOLS.resolve, toolSession);
|
|
1120
|
+
if (resolveTool) {
|
|
1121
|
+
toolRegistry.set(resolveTool.name, wrapToolWithMetaNotice(resolveTool) as AgentTool);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1112
1125
|
let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
|
|
1113
1126
|
const cursorExecHandlers = new CursorExecHandlers({
|
|
1114
1127
|
cwd,
|
|
@@ -1307,6 +1320,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1307
1320
|
return result;
|
|
1308
1321
|
},
|
|
1309
1322
|
intentTracing: !!intentField,
|
|
1323
|
+
getToolChoice: () => {
|
|
1324
|
+
if (pendingActionStore.hasPending) {
|
|
1325
|
+
return { type: "function", name: "resolve" };
|
|
1326
|
+
}
|
|
1327
|
+
return undefined;
|
|
1328
|
+
},
|
|
1310
1329
|
});
|
|
1311
1330
|
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
1312
1331
|
|
|
@@ -1343,6 +1362,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1343
1362
|
forceCopilotAgentInitiator,
|
|
1344
1363
|
obfuscator,
|
|
1345
1364
|
asyncJobManager,
|
|
1365
|
+
pendingActionStore,
|
|
1346
1366
|
});
|
|
1347
1367
|
|
|
1348
1368
|
if (model?.api === "openai-codex-responses") {
|
|
@@ -86,6 +86,7 @@ import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { t
|
|
|
86
86
|
import type { SecretObfuscator } from "../secrets/obfuscator";
|
|
87
87
|
import { outputMeta } from "../tools/output-meta";
|
|
88
88
|
import { resolveToCwd } from "../tools/path-utils";
|
|
89
|
+
import type { PendingActionStore } from "../tools/pending-action";
|
|
89
90
|
import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo-write";
|
|
90
91
|
import { parseCommandArgs } from "../utils/command-args";
|
|
91
92
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
@@ -177,6 +178,8 @@ export interface AgentSessionConfig {
|
|
|
177
178
|
forceCopilotAgentInitiator?: boolean;
|
|
178
179
|
/** Secret obfuscator for deobfuscating streaming edit content */
|
|
179
180
|
obfuscator?: SecretObfuscator;
|
|
181
|
+
/** Pending action store for preview/apply workflows */
|
|
182
|
+
pendingActionStore?: PendingActionStore;
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
/** Options for AgentSession.prompt() */
|
|
@@ -368,6 +371,7 @@ export class AgentSession {
|
|
|
368
371
|
#streamingEditFileCache = new Map<string, string>();
|
|
369
372
|
#promptInFlight = false;
|
|
370
373
|
#obfuscator: SecretObfuscator | undefined;
|
|
374
|
+
#pendingActionStore: PendingActionStore | undefined;
|
|
371
375
|
#promptGeneration = 0;
|
|
372
376
|
#providerSessionState = new Map<string, ProviderSessionState>();
|
|
373
377
|
|
|
@@ -392,6 +396,7 @@ export class AgentSession {
|
|
|
392
396
|
this.#forceCopilotAgentInitiator = config.forceCopilotAgentInitiator ?? false;
|
|
393
397
|
this.#obfuscator = config.obfuscator;
|
|
394
398
|
this.agent.providerSessionState = this.#providerSessionState;
|
|
399
|
+
this.#pendingActionStore = config.pendingActionStore;
|
|
395
400
|
this.#syncTodoPhasesFromBranch();
|
|
396
401
|
|
|
397
402
|
// Always subscribe to agent events for internal handling
|
|
@@ -651,17 +656,12 @@ export class AgentSession {
|
|
|
651
656
|
}
|
|
652
657
|
|
|
653
658
|
if (event.message.role === "toolResult") {
|
|
654
|
-
const { toolName,
|
|
659
|
+
const { toolName, details, isError, content } = event.message as {
|
|
655
660
|
toolName?: string;
|
|
656
|
-
toolCallId?: string;
|
|
657
661
|
details?: { path?: string; phases?: TodoPhase[] };
|
|
658
|
-
$normative?: Record<string, unknown>;
|
|
659
662
|
isError?: boolean;
|
|
660
663
|
content?: Array<TextContent | ImageContent>;
|
|
661
664
|
};
|
|
662
|
-
if ($normative && toolCallId && this.settings.get("normativeRewrite")) {
|
|
663
|
-
await this.#rewriteToolCallArgs(toolCallId, $normative);
|
|
664
|
-
}
|
|
665
665
|
// Invalidate streaming edit cache when edit tool completes to prevent stale data
|
|
666
666
|
if (toolName === "edit" && details?.path) {
|
|
667
667
|
this.#invalidateFileCacheForPath(details.path);
|
|
@@ -688,6 +688,22 @@ export class AgentSession {
|
|
|
688
688
|
{ deliverAs: "nextTurn" },
|
|
689
689
|
);
|
|
690
690
|
}
|
|
691
|
+
if (!isError && this.#pendingActionStore?.hasPending) {
|
|
692
|
+
const reminderText = [
|
|
693
|
+
"<system-reminder>",
|
|
694
|
+
"This is a preview. Call the `resolve` tool to apply or discard these changes.",
|
|
695
|
+
"</system-reminder>",
|
|
696
|
+
].join("\n");
|
|
697
|
+
await this.sendCustomMessage(
|
|
698
|
+
{
|
|
699
|
+
customType: "resolve-reminder",
|
|
700
|
+
content: reminderText,
|
|
701
|
+
display: false,
|
|
702
|
+
details: { toolName },
|
|
703
|
+
},
|
|
704
|
+
{ deliverAs: "nextTurn" },
|
|
705
|
+
);
|
|
706
|
+
}
|
|
691
707
|
}
|
|
692
708
|
}
|
|
693
709
|
|
|
@@ -1259,33 +1275,6 @@ export class AgentSession {
|
|
|
1259
1275
|
}
|
|
1260
1276
|
}
|
|
1261
1277
|
|
|
1262
|
-
/** Rewrite tool call arguments in agent state and persisted session history. */
|
|
1263
|
-
async #rewriteToolCallArgs(toolCallId: string, args: Record<string, unknown>): Promise<void> {
|
|
1264
|
-
let updated = false;
|
|
1265
|
-
const messages = this.agent.state.messages;
|
|
1266
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1267
|
-
const msg = messages[i];
|
|
1268
|
-
if (msg.role !== "assistant") continue;
|
|
1269
|
-
const assistantMsg = msg as AssistantMessage;
|
|
1270
|
-
if (!Array.isArray(assistantMsg.content)) continue;
|
|
1271
|
-
for (const block of assistantMsg.content) {
|
|
1272
|
-
if (typeof block !== "object" || block === null) continue;
|
|
1273
|
-
if (!("type" in block) || (block as { type?: string }).type !== "toolCall") continue;
|
|
1274
|
-
const toolCall = block as { id?: string; arguments?: Record<string, unknown> };
|
|
1275
|
-
if (toolCall.id === toolCallId) {
|
|
1276
|
-
toolCall.arguments = args;
|
|
1277
|
-
updated = true;
|
|
1278
|
-
break;
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
if (updated) break;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
if (updated) {
|
|
1285
|
-
await this.sessionManager.rewriteAssistantToolCallArgs(toolCallId, args);
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
1278
|
/** Emit extension events based on session events */
|
|
1290
1279
|
async #emitExtensionEvent(event: AgentSessionEvent): Promise<void> {
|
|
1291
1280
|
if (!this.#extensionRunner) return;
|
|
@@ -3200,7 +3189,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3200
3189
|
this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
|
|
3201
3190
|
// This handles the case where an error was kept after compaction (in the "kept" region).
|
|
3202
3191
|
// The error shouldn't trigger another compaction since we already compacted.
|
|
3203
|
-
// Example: opus fails
|
|
3192
|
+
// Example: opus fails -> switch to codex -> compact -> switch back to opus -> opus error
|
|
3204
3193
|
// is still in context but shouldn't trigger compaction again.
|
|
3205
3194
|
const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
3206
3195
|
const errorIsFromBeforeCompaction =
|
|
@@ -3213,7 +3202,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3213
3202
|
this.agent.replaceMessages(messages.slice(0, -1));
|
|
3214
3203
|
}
|
|
3215
3204
|
|
|
3216
|
-
// Try context promotion first
|
|
3205
|
+
// Try context promotion first - switch to a larger model and retry without compacting
|
|
3217
3206
|
const promoted = await this.#tryContextPromotion(assistantMessage);
|
|
3218
3207
|
if (promoted) {
|
|
3219
3208
|
// Retry on the promoted (larger) model without compacting
|
|
@@ -3221,7 +3210,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3221
3210
|
return;
|
|
3222
3211
|
}
|
|
3223
3212
|
|
|
3224
|
-
// No promotion target available
|
|
3213
|
+
// No promotion target available fall through to compaction
|
|
3225
3214
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
3226
3215
|
if (compactionSettings.enabled) {
|
|
3227
3216
|
await this.#runAutoCompaction("overflow", true);
|
|
@@ -1819,36 +1819,6 @@ export class SessionManager {
|
|
|
1819
1819
|
await this.#rewriteFile();
|
|
1820
1820
|
}
|
|
1821
1821
|
|
|
1822
|
-
/**
|
|
1823
|
-
* Rewrite tool call arguments in the most recent assistant message containing the toolCallId.
|
|
1824
|
-
* Returns true if a tool call was updated.
|
|
1825
|
-
*/
|
|
1826
|
-
async rewriteAssistantToolCallArgs(toolCallId: string, args: Record<string, unknown>): Promise<boolean> {
|
|
1827
|
-
let updated = false;
|
|
1828
|
-
for (let i = this.#fileEntries.length - 1; i >= 0; i--) {
|
|
1829
|
-
const entry = this.#fileEntries[i];
|
|
1830
|
-
if (entry.type !== "message" || entry.message.role !== "assistant") continue;
|
|
1831
|
-
const message = entry.message as { content?: unknown };
|
|
1832
|
-
if (!Array.isArray(message.content)) continue;
|
|
1833
|
-
for (const block of message.content) {
|
|
1834
|
-
if (typeof block !== "object" || block === null) continue;
|
|
1835
|
-
if (!("type" in block) || (block as { type?: string }).type !== "toolCall") continue;
|
|
1836
|
-
const toolCall = block as { id?: string; arguments?: Record<string, unknown> };
|
|
1837
|
-
if (toolCall.id === toolCallId) {
|
|
1838
|
-
toolCall.arguments = args;
|
|
1839
|
-
updated = true;
|
|
1840
|
-
break;
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
if (updated) break;
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
if (updated && this.persist && this.#sessionFile) {
|
|
1847
|
-
await this.#rewriteFile();
|
|
1848
|
-
}
|
|
1849
|
-
return updated;
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
1822
|
/**
|
|
1853
1823
|
* Append a custom message entry (for extensions) that participates in LLM context.
|
|
1854
1824
|
* @param customType Hook identifier for filtering on reload
|
|
@@ -75,8 +75,10 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
75
75
|
{
|
|
76
76
|
name: "plan",
|
|
77
77
|
description: "Toggle plan mode (agent plans before executing)",
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
inlineHint: "[prompt]",
|
|
79
|
+
allowArgs: true,
|
|
80
|
+
handle: async (command, runtime) => {
|
|
81
|
+
await runtime.ctx.handlePlanModeCommand(command.args || undefined);
|
|
80
82
|
runtime.ctx.editor.setText("");
|
|
81
83
|
},
|
|
82
84
|
},
|
package/src/stt/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
1
|
+
export * from "./downloader";
|
|
2
|
+
export * from "./setup";
|
|
3
|
+
export * from "./stt-controller";
|
package/src/task/types.ts
CHANGED
|
@@ -34,8 +34,8 @@ export const TASK_SUBAGENT_PROGRESS_CHANNEL = "task:subagent:progress";
|
|
|
34
34
|
/** Single task item for parallel execution */
|
|
35
35
|
export const taskItemSchema = Type.Object({
|
|
36
36
|
id: Type.String({
|
|
37
|
-
description: "CamelCase identifier, max
|
|
38
|
-
maxLength:
|
|
37
|
+
description: "CamelCase identifier, max 48 chars",
|
|
38
|
+
maxLength: 48,
|
|
39
39
|
}),
|
|
40
40
|
description: Type.String({
|
|
41
41
|
description: "Short one-liner for UI display only — not seen by the subagent",
|