@oh-my-pi/pi-coding-agent 14.1.2 → 14.2.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 +47 -2
- package/package.json +8 -8
- package/scripts/build-binary.ts +61 -0
- package/src/autoresearch/helpers.ts +10 -0
- package/src/autoresearch/index.ts +1 -11
- package/src/autoresearch/tools/init-experiment.ts +1 -10
- package/src/autoresearch/tools/log-experiment.ts +1 -11
- package/src/autoresearch/tools/run-experiment.ts +1 -10
- package/src/bun-imports.d.ts +6 -0
- package/src/cli/plugin-cli.ts +23 -45
- package/src/commit/agentic/tools/propose-commit.ts +1 -14
- package/src/commit/agentic/tools/split-commit.ts +1 -15
- package/src/commit/utils.ts +15 -1
- package/src/config/model-registry.ts +3 -3
- package/src/config/prompt-templates.ts +4 -12
- package/src/config/settings-schema.ts +27 -2
- package/src/config/settings.ts +1 -1
- package/src/dap/session.ts +8 -2
- package/src/discovery/claude-plugins.ts +61 -6
- package/src/discovery/codex.ts +2 -15
- package/src/discovery/gemini.ts +2 -15
- package/src/discovery/helpers.ts +40 -1
- package/src/discovery/opencode.ts +2 -15
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +3 -14
- package/src/edit/index.ts +67 -3
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +63 -0
- package/src/edit/modes/chunk.ts +6 -2
- package/src/edit/modes/hashline.ts +3 -3
- package/src/edit/modes/replace.ts +2 -13
- package/src/edit/read-file.ts +18 -0
- package/src/edit/renderer.ts +61 -33
- package/src/extensibility/extensions/compact-handler.ts +40 -0
- package/src/extensibility/extensions/runner.ts +11 -29
- package/src/extensibility/utils.ts +7 -1
- package/src/internal-urls/docs-index.generated.ts +9 -2
- package/src/lsp/client.ts +14 -5
- package/src/lsp/index.ts +53 -10
- package/src/lsp/render.ts +14 -2
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +1 -0
- package/src/mcp/manager.ts +29 -48
- package/src/memories/index.ts +7 -1
- package/src/modes/acp/acp-agent.ts +3 -16
- package/src/modes/components/model-selector.ts +15 -24
- package/src/modes/components/plugin-settings.ts +16 -5
- package/src/modes/components/read-tool-group.ts +92 -9
- package/src/modes/components/settings-defs.ts +18 -0
- package/src/modes/components/settings-selector.ts +2 -6
- package/src/modes/components/tool-execution.ts +61 -28
- package/src/modes/controllers/event-controller.ts +3 -1
- package/src/modes/controllers/extension-ui-controller.ts +99 -150
- package/src/modes/controllers/selector-controller.ts +3 -12
- package/src/modes/interactive-mode.ts +4 -2
- package/src/modes/print-mode.ts +4 -22
- package/src/modes/rpc/rpc-mode.ts +18 -38
- package/src/modes/shared.ts +10 -1
- package/src/modes/utils/ui-helpers.ts +6 -2
- package/src/plan-mode/approved-plan.ts +5 -4
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/subagent-user-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +208 -243
- package/src/prompts/tools/apply-patch.md +67 -0
- package/src/prompts/tools/ast-edit.md +18 -23
- package/src/prompts/tools/ast-grep.md +25 -32
- package/src/prompts/tools/bash.md +11 -23
- package/src/prompts/tools/debug.md +8 -22
- package/src/prompts/tools/find.md +0 -4
- package/src/prompts/tools/grep.md +3 -5
- package/src/prompts/tools/hashline.md +16 -10
- package/src/prompts/tools/python.md +10 -14
- package/src/prompts/tools/read.md +17 -24
- package/src/prompts/tools/task.md +57 -21
- package/src/prompts/tools/todo-write.md +45 -67
- package/src/session/agent-session.ts +4 -4
- package/src/session/session-manager.ts +15 -7
- package/src/session/streaming-output.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +3 -14
- package/src/task/executor.ts +13 -34
- package/src/task/index.ts +82 -18
- package/src/task/simple-mode.ts +27 -0
- package/src/task/template.ts +17 -3
- package/src/task/types.ts +77 -30
- package/src/tools/ask.ts +2 -4
- package/src/tools/ast-edit.ts +41 -17
- package/src/tools/ast-grep.ts +8 -27
- package/src/tools/bash-skill-urls.ts +9 -7
- package/src/tools/bash.ts +66 -24
- package/src/tools/browser.ts +1 -1
- package/src/tools/fetch.ts +1 -14
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/find.ts +25 -29
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +1 -8
- package/src/tools/gh.ts +6 -13
- package/src/tools/grep.ts +103 -59
- package/src/tools/jtd-to-json-schema.ts +16 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/path-utils.ts +61 -5
- package/src/tools/plan-mode-guard.ts +6 -5
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +1 -1
- package/src/tools/render-utils.ts +38 -6
- package/src/tools/renderers.ts +1 -0
- package/src/tools/resolve.ts +12 -3
- package/src/tools/ssh.ts +3 -11
- package/src/tools/submit-result.ts +1 -13
- package/src/tools/todo-write.ts +137 -103
- package/src/tools/vim.ts +1 -1
- package/src/tools/write.ts +2 -23
- package/src/tui/code-cell.ts +12 -7
- package/src/utils/edit-mode.ts +3 -2
- package/src/utils/git.ts +1 -1
- package/src/vim/engine.ts +41 -58
- package/src/web/scrapers/crates-io.ts +1 -14
- package/src/web/scrapers/types.ts +13 -0
- package/src/web/search/providers/base.ts +13 -0
- package/src/web/search/providers/brave.ts +2 -5
- package/src/web/search/providers/codex.ts +20 -24
- package/src/web/search/providers/gemini.ts +39 -1
- package/src/web/search/providers/jina.ts +2 -5
- package/src/web/search/providers/kagi.ts +3 -8
- package/src/web/search/providers/kimi.ts +3 -7
- package/src/web/search/providers/parallel.ts +3 -8
- package/src/web/search/providers/synthetic.ts +3 -7
- package/src/web/search/providers/tavily.ts +15 -11
- package/src/web/search/providers/utils.ts +36 -0
- package/src/web/search/providers/zai.ts +3 -7
|
@@ -5,28 +5,52 @@ Launches subagents to parallelize workflows.
|
|
|
5
5
|
- Use the `poll` tool to wait until completion. You **MUST NOT** poll `read jobs://` in a loop.
|
|
6
6
|
{{/if}}
|
|
7
7
|
|
|
8
|
+
{{#if defaultMode}}
|
|
9
|
+
Current input mode: `default`. Shared `context` and custom task-call `schema` are available.
|
|
10
|
+
{{/if}}
|
|
11
|
+
{{#if schemaFreeMode}}
|
|
12
|
+
Current input mode: `schema-free`. Shared `context` is available; custom task-call `schema` is disabled. For structured output, rely on the agent definition or inherited session schema.
|
|
13
|
+
{{/if}}
|
|
14
|
+
{{#if independentMode}}
|
|
15
|
+
Current input mode: `independent`. Shared `context` and custom `schema` are both disabled. Every assignment must stand on its own.
|
|
16
|
+
{{/if}}
|
|
17
|
+
|
|
18
|
+
{{#if contextEnabled}}
|
|
8
19
|
Subagents lack your conversation history. Every decision, file content, and user requirement they need **MUST** be explicit in `context` or `assignment`.
|
|
20
|
+
{{else}}
|
|
21
|
+
Subagents lack your conversation history. Every decision, file content, and user requirement they need **MUST** be explicit in each task `assignment`.
|
|
22
|
+
{{/if}}
|
|
9
23
|
|
|
10
24
|
<parameters>
|
|
11
25
|
- `agent`: Agent type for all tasks.
|
|
12
26
|
- `.id`: CamelCase, max 32 chars
|
|
13
27
|
- `.description`: UI display only — subagent never sees it
|
|
14
28
|
- `.assignment`: Complete self-contained instructions. One-liners PROHIBITED; missing acceptance criteria = too vague.
|
|
29
|
+
{{#if contextEnabled}}
|
|
15
30
|
- `context`: Shared background prepended to every assignment. Session-specific info only.
|
|
31
|
+
{{/if}}
|
|
32
|
+
{{#if customSchemaEnabled}}
|
|
16
33
|
- `schema`: JSON-encoded JTD schema for expected output. Format lives here — **MUST NOT** be duplicated in assignments.
|
|
34
|
+
{{/if}}
|
|
17
35
|
- `tasks`: Tasks to execute in parallel.
|
|
36
|
+
{{#if isolationEnabled}}
|
|
18
37
|
- `isolated`: Run in isolated environment; returns patches. Use when tasks edit overlapping files.
|
|
38
|
+
{{/if}}
|
|
19
39
|
</parameters>
|
|
20
40
|
|
|
21
41
|
<critical>
|
|
42
|
+
{{#if contextEnabled}}
|
|
22
43
|
- **MUST NOT** duplicate shared constraints across assignments — put them in `context` once.
|
|
44
|
+
{{else}}
|
|
45
|
+
- Every `assignment` must repeat any constraints, reference paths, and acceptance criteria it needs — there is no shared `context` field.
|
|
46
|
+
{{/if}}
|
|
23
47
|
- **MUST NOT** tell tasks to run project-wide build/test/lint. Parallel agents share the working tree; each task edits, stops. Caller verifies after all complete.
|
|
24
|
-
- For large payloads (traces, JSON blobs), write to `local://<path>` and pass the path in context.
|
|
25
|
-
- Prefer `task` agents that investigate **and** edit in one pass.
|
|
48
|
+
- For large payloads (traces, JSON blobs), write to `local://<path>` and pass the path in {{#if contextEnabled}}`context`{{else}}the relevant `assignment`{{/if}}.
|
|
49
|
+
- Prefer `task` agents that investigate **and** edit in one pass. Launch a dedicated read-only discovery step only when affected files are genuinely unknown.
|
|
26
50
|
</critical>
|
|
27
51
|
|
|
28
52
|
<scope>
|
|
29
|
-
Each task: **at most 3–5 files**. Globs
|
|
53
|
+
Each task: **at most 3–5 files**. Globs, "update all", or package-wide scope = too broad. Enumerate files explicitly and fan out to a cluster of agents.
|
|
30
54
|
</scope>
|
|
31
55
|
|
|
32
56
|
<parallelization>
|
|
@@ -38,10 +62,12 @@ Each task: **at most 3–5 files**. Globs in file paths, "update all", or packag
|
|
|
38
62
|
|API exports|Callers|Need signatures|
|
|
39
63
|
|Core module|Dependents|Import dependency|
|
|
40
64
|
|Schema/migration|App logic|Schema dependency|
|
|
65
|
+
|
|
41
66
|
**Safe to parallelize:** independent modules, isolated file-scoped refactors, tests for existing code.
|
|
42
67
|
</parallelization>
|
|
43
68
|
|
|
44
69
|
<templates>
|
|
70
|
+
{{#if contextEnabled}}
|
|
45
71
|
**context:**
|
|
46
72
|
```
|
|
47
73
|
## Goal ← one sentence: what the batch accomplishes
|
|
@@ -50,6 +76,9 @@ Each task: **at most 3–5 files**. Globs in file paths, "update all", or packag
|
|
|
50
76
|
## API Contract ← exact types/signatures if tasks share an interface (omit if N/A)
|
|
51
77
|
## Acceptance ← definition of done; build/lint runs AFTER all tasks complete
|
|
52
78
|
```
|
|
79
|
+
{{else}}
|
|
80
|
+
No shared `context` field exists in this mode. Fold goal, non-goals, constraints, and acceptance criteria into each `assignment`.
|
|
81
|
+
{{/if}}
|
|
53
82
|
**assignment:**
|
|
54
83
|
```
|
|
55
84
|
## Target ← exact file paths; named symbols; explicit non-goals
|
|
@@ -61,68 +90,75 @@ Each task: **at most 3–5 files**. Globs in file paths, "update all", or packag
|
|
|
61
90
|
|
|
62
91
|
<checklist>
|
|
63
92
|
Before invoking:
|
|
93
|
+
{{#if contextEnabled}}
|
|
64
94
|
- `context` contains only session-specific info
|
|
95
|
+
{{else}}
|
|
96
|
+
- Every `assignment` includes its own goal, constraints, and acceptance criteria (no shared context)
|
|
97
|
+
{{/if}}
|
|
65
98
|
- Every `assignment` follows the template; no one-liners; edge cases covered
|
|
66
99
|
- Tasks are truly parallel — you can articulate why none depends on another's output
|
|
67
100
|
- File paths are explicit; no globs
|
|
101
|
+
{{#if customSchemaEnabled}}
|
|
68
102
|
- `schema` is set if you expect structured output
|
|
103
|
+
{{else}}
|
|
104
|
+
- Do not pass a custom task-call `schema` in this mode
|
|
105
|
+
{{/if}}
|
|
69
106
|
</checklist>
|
|
70
107
|
|
|
108
|
+
{{#if contextEnabled}}
|
|
71
109
|
<example label="Rename exported symbol + update all call sites">
|
|
72
|
-
Two tasks with non-overlapping file sets
|
|
110
|
+
Two tasks with non-overlapping file sets — demonstrates scope partitioning.
|
|
73
111
|
|
|
74
112
|
<context>
|
|
75
113
|
## Goal
|
|
76
114
|
Rename `parseConfig` → `loadConfig` in `src/config/parser.ts` and all callers.
|
|
77
115
|
## Non-goals
|
|
78
|
-
|
|
116
|
+
No behavior or signature changes; rename only.
|
|
79
117
|
## Acceptance (global)
|
|
80
118
|
Caller runs `bun check:ts` after both tasks complete. Tasks must NOT run it.
|
|
81
119
|
</context>
|
|
82
120
|
<tasks>
|
|
83
121
|
<task name="RenameExport">
|
|
84
|
-
<description>Rename the export in parser.ts</description>
|
|
85
122
|
<assignment>
|
|
86
123
|
## Target
|
|
87
|
-
-
|
|
88
|
-
-
|
|
124
|
+
- `src/config/parser.ts`: function `parseConfig`
|
|
125
|
+
- If `src/config/index.ts` re-exports it, update the re-export
|
|
89
126
|
- Non-goals: do not touch callers or tests
|
|
90
127
|
|
|
91
128
|
## Change
|
|
92
|
-
- Rename `parseConfig` → `loadConfig` (declaration + any JSDoc
|
|
93
|
-
- If `src/config/index.ts` re-exports `parseConfig`, update that re-export too
|
|
129
|
+
- Rename `parseConfig` → `loadConfig` (declaration + any JSDoc references)
|
|
94
130
|
|
|
95
131
|
## Edge Cases
|
|
96
|
-
-
|
|
97
|
-
- Internal helpers
|
|
132
|
+
- Rename all overload signatures if overloaded
|
|
133
|
+
- Internal helpers like `_parseConfigValue` are different symbols — leave untouched
|
|
98
134
|
- Do not add a backwards-compat alias
|
|
99
135
|
|
|
100
136
|
## Acceptance
|
|
101
|
-
- `
|
|
137
|
+
- `parseConfig` no longer appears as a top-level export in `parser.ts`
|
|
102
138
|
</assignment>
|
|
103
139
|
</task>
|
|
104
140
|
<task name="UpdateCallers">
|
|
105
|
-
<description>Update import and call sites in consuming modules</description>
|
|
106
141
|
<assignment>
|
|
107
142
|
## Target
|
|
108
|
-
-
|
|
109
|
-
- Non-goals: do not touch `src/config/parser.ts` or `src/config/index.ts`
|
|
143
|
+
- `src/cli/init.ts`, `src/server/bootstrap.ts`, `src/worker/index.ts`
|
|
144
|
+
- Non-goals: do not touch `src/config/parser.ts` or `src/config/index.ts`
|
|
110
145
|
|
|
111
146
|
## Change
|
|
112
|
-
-
|
|
147
|
+
- Replace `import { parseConfig }` → `import { loadConfig }`
|
|
113
148
|
- Replace every call site `parseConfig(` → `loadConfig(`
|
|
149
|
+
- For `import * as cfg` users, update `cfg.parseConfig` property access
|
|
114
150
|
|
|
115
151
|
## Edge Cases
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
- If any file re-exports `parseConfig` to an external package boundary, keep the old name via `export { loadConfig as parseConfig }` and add a `// TODO: remove after next major` comment
|
|
152
|
+
- String literals containing "parseConfig" (logs, comments) are documentation — leave them
|
|
153
|
+
- If a file re-exports to an external package boundary, keep the old name via `export { loadConfig as parseConfig }` with a `// TODO: remove after next major` comment
|
|
119
154
|
|
|
120
155
|
## Acceptance
|
|
121
|
-
- No bare
|
|
156
|
+
- No bare `parseConfig` identifier remains in the three target files
|
|
122
157
|
</assignment>
|
|
123
158
|
</task>
|
|
124
159
|
</tasks>
|
|
125
160
|
</example>
|
|
161
|
+
{{/if}}
|
|
126
162
|
|
|
127
163
|
{{#list agents join="\n"}}
|
|
128
164
|
### Agent: {{name}}
|
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
Manages a phased task list.
|
|
2
|
-
|
|
1
|
+
Manages a phased task list. Each field is a verb — set the ones you need in a single call.
|
|
2
|
+
The next pending task is auto-promoted to `in_progress` after completing the current one.
|
|
3
3
|
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
1. Before beginning — `{op: "update", id: "task-N", status: "in_progress"}`
|
|
7
|
-
2. Immediately after finishing — `{op: "update", id: "task-N", status: "completed"}`
|
|
4
|
+
<protocol>
|
|
5
|
+
## Fields
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
|Field|Type|When to use|
|
|
8
|
+
|---|---|---|
|
|
9
|
+
|`phases`|Phase[]|Initial setup, or full restructure when the plan changes significantly|
|
|
10
|
+
|`complete`|string[]|Mark tasks done|
|
|
11
|
+
|`start`|string|Jump to a specific task out of order|
|
|
12
|
+
|`abandon`|string[]|Drop tasks intentionally|
|
|
13
|
+
|`remove`|string[]|Remove tasks that are no longer relevant|
|
|
14
|
+
|`add_notes`|{id, notes}[]|Append runtime observations to tasks|
|
|
15
|
+
|`add_tasks`|{phase, content, details?}[]|Add tasks to a phase (by name or ID)|
|
|
16
|
+
|`add_phase`|{name, tasks?}|Add a new phase of work discovered mid-task|
|
|
17
|
+
|
|
18
|
+
## Task Anatomy
|
|
19
|
+
- `content`: Short label (5-10 words). What is being done, not how.
|
|
20
|
+
- `details`: File paths, implementation steps, edge cases. Shown only when the task is active.
|
|
21
|
+
|
|
22
|
+
## Rules
|
|
23
|
+
- Mark tasks completed immediately after finishing — never defer
|
|
24
|
+
- Complete phases in order — do not skip ahead while earlier ones are pending
|
|
25
|
+
- On blockers: add a new task describing the blocker
|
|
26
|
+
</protocol>
|
|
11
27
|
|
|
12
28
|
<conditions>
|
|
13
29
|
Create a todo list when:
|
|
@@ -17,73 +33,35 @@ Create a todo list when:
|
|
|
17
33
|
4. New instructions arrive mid-task — capture before proceeding
|
|
18
34
|
</conditions>
|
|
19
35
|
|
|
20
|
-
<
|
|
21
|
-
|
|
36
|
+
<example name="initial-setup">
|
|
37
|
+
{phases: [
|
|
38
|
+
{name: "Investigation", tasks: [{content: "Read source"}, {content: "Map callsites"}]},
|
|
39
|
+
{name: "Implementation", tasks: [{content: "Apply fix", details: "Update parser.ts to handle edge case in line 42"}, {content: "Run tests"}]}
|
|
40
|
+
]}
|
|
41
|
+
</example>
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|`replace`|Initial setup, or full restructure when the plan changes significantly|
|
|
27
|
-
|`add_phase`|Add a new phase of work discovered mid-task|
|
|
28
|
-
|`add_task`|Add a task to an existing phase|
|
|
29
|
-
|`remove_task`|Remove a task that is no longer relevant|
|
|
43
|
+
<example name="complete">
|
|
44
|
+
{complete: ["task-2", "task-3"]}
|
|
45
|
+
</example>
|
|
30
46
|
|
|
31
|
-
|
|
47
|
+
<example name="add-notes">
|
|
48
|
+
{add_notes: [{id: "task-3", notes: "Found edge case in parser — needs null check"}]}
|
|
49
|
+
</example>
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|`in_progress`|Currently working — exactly one at a time|
|
|
37
|
-
|`completed`|Fully done|
|
|
38
|
-
|`abandoned`|Dropped intentionally|
|
|
51
|
+
<example name="add-task">
|
|
52
|
+
{add_tasks: [{phase: "Implementation", content: "Handle retries", details: "Cap exponential backoff in retry.ts"}]}
|
|
53
|
+
</example>
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- You **MUST** keep exactly **one** task `in_progress`
|
|
44
|
-
- You **MUST** complete phases in order — do not mark later tasks `completed` while earlier ones are `pending`
|
|
45
|
-
- On blockers: keep `in_progress`, add a new task describing the blocker
|
|
46
|
-
- Multiple ops can be batched in one call (e.g., complete current + start next)
|
|
47
|
-
</protocol>
|
|
55
|
+
<example name="add-phase">
|
|
56
|
+
{add_phase: {name: "Cleanup", tasks: [{content: "Remove dead code"}]}}
|
|
57
|
+
</example>
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
- `notes`: Runtime observations added during execution.
|
|
59
|
+
<example name="combined">
|
|
60
|
+
{complete: ["task-2"], add_notes: [{id: "task-3", notes: "Needs extra validation"}]}
|
|
61
|
+
</example>
|
|
53
62
|
|
|
54
63
|
<avoid>
|
|
55
64
|
- Single-step tasks — act directly
|
|
56
65
|
- Conversational or informational requests
|
|
57
66
|
- Tasks completable in under 3 trivial steps
|
|
58
67
|
</avoid>
|
|
59
|
-
|
|
60
|
-
<example name="start-task">
|
|
61
|
-
Mark task-2 in_progress before beginning work:
|
|
62
|
-
ops: [{op: "update", id: "task-2", status: "in_progress"}]
|
|
63
|
-
</example>
|
|
64
|
-
|
|
65
|
-
<example name="complete-and-advance">
|
|
66
|
-
Finish task-2 and start task-3 in one call:
|
|
67
|
-
ops: [
|
|
68
|
-
{op: "update", id: "task-2", status: "completed"},
|
|
69
|
-
{op: "update", id: "task-3", status: "in_progress"}
|
|
70
|
-
]
|
|
71
|
-
</example>
|
|
72
|
-
|
|
73
|
-
<example name="add_task">
|
|
74
|
-
Add a follow-up task with implementation specifics in `details`:
|
|
75
|
-
ops: [{op: "add_task", phase: "Implementation", after: "task-2", task: {content: "Handle retries", details: "Update retry.ts to cap exponential backoff and preserve AbortSignal handling", status: "pending"}}]
|
|
76
|
-
</example>
|
|
77
|
-
|
|
78
|
-
<example name="initial-setup">
|
|
79
|
-
Replace is for setup only. Prefer add_phase / add_task for incremental additions.
|
|
80
|
-
ops: [{op: "replace", phases: [
|
|
81
|
-
{name: "Investigation", tasks: [{content: "Read source"}, {content: "Map callsites"}]},
|
|
82
|
-
{name: "Implementation", tasks: [{content: "Apply fix", details: "Update parser.ts to handle edge case in line 42"}, {content: "Run tests"}]}
|
|
83
|
-
]}]
|
|
84
|
-
</example>
|
|
85
|
-
|
|
86
|
-
<example name="skip">
|
|
87
|
-
User: "What does this function do?" / "Add a comment" / "Run npm install"
|
|
88
|
-
→ Do it directly. No list needed.
|
|
89
|
-
</example>
|
|
@@ -132,7 +132,7 @@ import { resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
|
|
|
132
132
|
import { assertEditableFile } from "../tools/auto-generated-guard";
|
|
133
133
|
import type { CheckpointState } from "../tools/checkpoint";
|
|
134
134
|
import { outputMeta } from "../tools/output-meta";
|
|
135
|
-
import { resolveToCwd } from "../tools/path-utils";
|
|
135
|
+
import { normalizeLocalScheme, resolveToCwd } from "../tools/path-utils";
|
|
136
136
|
import { isAutoQaEnabled } from "../tools/report-tool-issue";
|
|
137
137
|
import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo-write";
|
|
138
138
|
import { ToolError } from "../tools/tool-errors";
|
|
@@ -2445,8 +2445,8 @@ export class AgentSession {
|
|
|
2445
2445
|
const state = this.#planModeState;
|
|
2446
2446
|
if (!state?.enabled) return null;
|
|
2447
2447
|
const sessionPlanUrl = "local://PLAN.md";
|
|
2448
|
-
const resolvedPlanPath = state.planFilePath.startsWith("local
|
|
2449
|
-
? resolveLocalUrlToPath(state.planFilePath, {
|
|
2448
|
+
const resolvedPlanPath = state.planFilePath.startsWith("local:")
|
|
2449
|
+
? resolveLocalUrlToPath(normalizeLocalScheme(state.planFilePath), {
|
|
2450
2450
|
getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
|
|
2451
2451
|
getSessionId: () => this.sessionManager.getSessionId(),
|
|
2452
2452
|
})
|
|
@@ -2456,7 +2456,7 @@ export class AgentSession {
|
|
|
2456
2456
|
getSessionId: () => this.sessionManager.getSessionId(),
|
|
2457
2457
|
});
|
|
2458
2458
|
const displayPlanPath =
|
|
2459
|
-
state.planFilePath.startsWith("local
|
|
2459
|
+
state.planFilePath.startsWith("local:") || resolvedPlanPath !== resolvedSessionPlan
|
|
2460
2460
|
? state.planFilePath
|
|
2461
2461
|
: sessionPlanUrl;
|
|
2462
2462
|
|
|
@@ -286,6 +286,10 @@ export type ReadonlySessionManager = Pick<
|
|
|
286
286
|
| "putBlob"
|
|
287
287
|
>;
|
|
288
288
|
|
|
289
|
+
function createSessionId(): string {
|
|
290
|
+
return Bun.randomUUIDv7();
|
|
291
|
+
}
|
|
292
|
+
|
|
289
293
|
/** Generate a unique short ID (8 hex chars, collision-checked) */
|
|
290
294
|
function generateId(byId: { has(id: string): boolean }): string {
|
|
291
295
|
for (let i = 0; i < 100; i++) {
|
|
@@ -1500,7 +1504,7 @@ export class SessionManager {
|
|
|
1500
1504
|
this.#fileEntries = await loadEntriesFromFile(this.#sessionFile, this.storage);
|
|
1501
1505
|
if (this.#fileEntries.length > 0) {
|
|
1502
1506
|
const header = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
|
|
1503
|
-
this.#sessionId = header?.id ??
|
|
1507
|
+
this.#sessionId = header?.id ?? createSessionId();
|
|
1504
1508
|
this.#sessionName = header?.title;
|
|
1505
1509
|
this.#titleSource = header?.titleSource;
|
|
1506
1510
|
|
|
@@ -1549,7 +1553,7 @@ export class SessionManager {
|
|
|
1549
1553
|
this.#persistErrorReported = false;
|
|
1550
1554
|
|
|
1551
1555
|
// Create new session ID and header
|
|
1552
|
-
this.#sessionId =
|
|
1556
|
+
this.#sessionId = createSessionId();
|
|
1553
1557
|
const timestamp = new Date().toISOString();
|
|
1554
1558
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
1555
1559
|
this.#sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.#sessionId}.jsonl`);
|
|
@@ -1680,7 +1684,7 @@ export class SessionManager {
|
|
|
1680
1684
|
this.#persistChain = Promise.resolve();
|
|
1681
1685
|
this.#persistError = undefined;
|
|
1682
1686
|
this.#persistErrorReported = false;
|
|
1683
|
-
this.#sessionId =
|
|
1687
|
+
this.#sessionId = createSessionId();
|
|
1684
1688
|
this.#sessionName = undefined;
|
|
1685
1689
|
this.#titleSource = undefined;
|
|
1686
1690
|
const timestamp = new Date().toISOString();
|
|
@@ -2036,14 +2040,18 @@ export class SessionManager {
|
|
|
2036
2040
|
if (this.#needsFullRewriteOnNextPersist || !this.#flushed) {
|
|
2037
2041
|
// Full flush: rewrite the entire file atomically to avoid
|
|
2038
2042
|
// duplicating entries if the file already exists (e.g. from ensureOnDisk).
|
|
2039
|
-
|
|
2043
|
+
// Errors are already surfaced through #persistChain/#persistError; the
|
|
2044
|
+
// caller intentionally fires-and-forgets, so swallow the awaited rejection
|
|
2045
|
+
// here to avoid an unhandled rejection when the persist dir races with
|
|
2046
|
+
// test-level tempDir cleanup.
|
|
2047
|
+
this.#rewriteFile().catch(() => {});
|
|
2040
2048
|
} else {
|
|
2041
|
-
|
|
2049
|
+
this.#queuePersistTask(async () => {
|
|
2042
2050
|
const writer = this.#ensurePersistWriter();
|
|
2043
2051
|
if (!writer) return;
|
|
2044
2052
|
const persistedEntry = await prepareEntryForPersistence(entry, this.#blobStore);
|
|
2045
2053
|
await writer.write(persistedEntry);
|
|
2046
|
-
});
|
|
2054
|
+
}).catch(() => {});
|
|
2047
2055
|
}
|
|
2048
2056
|
}
|
|
2049
2057
|
|
|
@@ -2554,7 +2562,7 @@ export class SessionManager {
|
|
|
2554
2562
|
// Filter out LabelEntry from path - we'll recreate them from the resolved map
|
|
2555
2563
|
const pathWithoutLabels = branchPath.filter(e => e.type !== "label");
|
|
2556
2564
|
|
|
2557
|
-
const newSessionId =
|
|
2565
|
+
const newSessionId = createSessionId();
|
|
2558
2566
|
const timestamp = new Date().toISOString();
|
|
2559
2567
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
2560
2568
|
const newSessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${newSessionId}.jsonl`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
2
3
|
import { formatBytes } from "../tools/render-utils";
|
|
3
4
|
import { sanitizeWithOptionalSixelPassthrough } from "../utils/sixel";
|
|
@@ -750,3 +751,26 @@ export function formatHeadTruncationNotice(
|
|
|
750
751
|
const notice = `[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use sel=L${nextOffset} to continue]`;
|
|
751
752
|
return `\n\n${notice}`;
|
|
752
753
|
}
|
|
754
|
+
|
|
755
|
+
// =============================================================================
|
|
756
|
+
// Streaming tail update helper (shared by bash/ssh tools)
|
|
757
|
+
// =============================================================================
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Build an onChunk handler that appends to a TailBuffer and emits a streaming
|
|
761
|
+
* update (when `onUpdate` is defined) with the buffer's current text.
|
|
762
|
+
*/
|
|
763
|
+
export function streamTailUpdates<TDetails, TInput = unknown>(
|
|
764
|
+
tailBuffer: TailBuffer,
|
|
765
|
+
onUpdate: AgentToolUpdateCallback<TDetails, TInput> | undefined,
|
|
766
|
+
): (chunk: string) => void {
|
|
767
|
+
return chunk => {
|
|
768
|
+
tailBuffer.append(chunk);
|
|
769
|
+
if (onUpdate) {
|
|
770
|
+
onUpdate({
|
|
771
|
+
content: [{ type: "text", text: tailBuffer.text() }],
|
|
772
|
+
details: {} as TDetails,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
@@ -8,6 +8,7 @@ import type { SettingPath, SettingValue } from "../config/settings";
|
|
|
8
8
|
import { settings } from "../config/settings";
|
|
9
9
|
import {
|
|
10
10
|
clearClaudePluginRootsCache,
|
|
11
|
+
clearPluginRootsAndCaches,
|
|
11
12
|
resolveActiveProjectRegistryPath,
|
|
12
13
|
resolveOrDefaultProjectRegistryPath,
|
|
13
14
|
} from "../discovery/helpers.js";
|
|
@@ -643,13 +644,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
643
644
|
),
|
|
644
645
|
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
645
646
|
pluginsCacheDir: getPluginsCacheDir(),
|
|
646
|
-
clearPluginRootsCache:
|
|
647
|
-
const home = os.homedir();
|
|
648
|
-
invalidateFsCache(path.join(home, ".claude", "plugins", "installed_plugins.json"));
|
|
649
|
-
invalidateFsCache(path.join(home, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
650
|
-
for (const p of extraPaths ?? []) invalidateFsCache(p);
|
|
651
|
-
clearClaudePluginRootsCache();
|
|
652
|
-
},
|
|
647
|
+
clearPluginRootsCache: clearPluginRootsAndCaches,
|
|
653
648
|
});
|
|
654
649
|
|
|
655
650
|
try {
|
|
@@ -837,13 +832,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
837
832
|
),
|
|
838
833
|
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
839
834
|
pluginsCacheDir: getPluginsCacheDir(),
|
|
840
|
-
clearPluginRootsCache:
|
|
841
|
-
const home = os.homedir();
|
|
842
|
-
invalidateFsCache(path.join(home, ".claude", "plugins", "installed_plugins.json"));
|
|
843
|
-
invalidateFsCache(path.join(home, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
844
|
-
for (const p of extraPaths ?? []) invalidateFsCache(p);
|
|
845
|
-
clearClaudePluginRootsCache();
|
|
846
|
-
},
|
|
835
|
+
clearPluginRootsCache: clearPluginRootsAndCaches,
|
|
847
836
|
});
|
|
848
837
|
|
|
849
838
|
switch (sub) {
|
package/src/task/executor.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { PromptTemplate } from "../config/prompt-templates";
|
|
|
14
14
|
import { Settings } from "../config/settings";
|
|
15
15
|
import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
|
|
16
16
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
17
|
+
import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
|
|
17
18
|
import type { Skill } from "../extensibility/skills";
|
|
18
19
|
import { callTool } from "../mcp/client";
|
|
19
20
|
import type { MCPManager } from "../mcp/manager";
|
|
@@ -24,7 +25,7 @@ import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
|
24
25
|
import type { AuthStorage } from "../session/auth-storage";
|
|
25
26
|
import { SessionManager } from "../session/session-manager";
|
|
26
27
|
import { type ContextFileEntry, truncateTail } from "../tools";
|
|
27
|
-
import { jtdToJsonSchema } from "../tools/jtd-to-json-schema";
|
|
28
|
+
import { jtdToJsonSchema, normalizeSchema } from "../tools/jtd-to-json-schema";
|
|
28
29
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
29
30
|
import type { EventBus } from "../utils/event-bus";
|
|
30
31
|
import { buildNamedToolChoice } from "../utils/tool-choice";
|
|
@@ -163,20 +164,8 @@ function parseStringifiedJson(value: unknown): unknown {
|
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
function normalizeOutputSchema(schema: unknown): { normalized?: unknown; error?: string } {
|
|
167
|
-
if (schema === undefined || schema === null) return {};
|
|
168
|
-
if (typeof schema === "string") {
|
|
169
|
-
try {
|
|
170
|
-
return { normalized: JSON.parse(schema) };
|
|
171
|
-
} catch (err) {
|
|
172
|
-
return { error: err instanceof Error ? err.message : String(err) };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return { normalized: schema };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
167
|
function buildOutputValidator(schema: unknown): { validate?: ValidateFunction; error?: string } {
|
|
179
|
-
const { normalized, error } =
|
|
168
|
+
const { normalized, error } = normalizeSchema(schema);
|
|
180
169
|
if (error) return { error };
|
|
181
170
|
if (normalized === undefined) return {};
|
|
182
171
|
const jsonSchema = jtdToJsonSchema(normalized);
|
|
@@ -300,7 +289,7 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
|
|
|
300
289
|
}
|
|
301
290
|
} else {
|
|
302
291
|
const allowFallback = exitCode === 0 && !doneAborted && !signalAborted;
|
|
303
|
-
const { normalized: normalizedSchema, error: schemaError } =
|
|
292
|
+
const { normalized: normalizedSchema, error: schemaError } = normalizeSchema(outputSchema);
|
|
304
293
|
const hasOutputSchema = normalizedSchema !== undefined && !schemaError;
|
|
305
294
|
const fallback = allowFallback ? resolveFallbackCompletion(rawOutput, outputSchema) : null;
|
|
306
295
|
if (fallback) {
|
|
@@ -317,9 +306,14 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
|
|
|
317
306
|
exitCode = 0;
|
|
318
307
|
stderr = "";
|
|
319
308
|
} else if (exitCode === 0) {
|
|
309
|
+
const hasRawOutput = rawOutput.trim().length > 0;
|
|
320
310
|
rawOutput = rawOutput
|
|
321
311
|
? `${SUBAGENT_WARNING_MISSING_SUBMIT_RESULT}\n\n${rawOutput}`
|
|
322
312
|
: SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
|
|
313
|
+
if (hasOutputSchema || !hasRawOutput) {
|
|
314
|
+
exitCode = 1;
|
|
315
|
+
stderr = SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
|
|
316
|
+
}
|
|
323
317
|
}
|
|
324
318
|
}
|
|
325
319
|
|
|
@@ -950,7 +944,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
950
944
|
const mcpProxyTools = options.mcpManager ? createMCPProxyTools(options.mcpManager) : [];
|
|
951
945
|
const enableMCP = !options.mcpManager;
|
|
952
946
|
|
|
953
|
-
const { normalized: normalizedOutputSchema } =
|
|
947
|
+
const { normalized: normalizedOutputSchema } = normalizeSchema(outputSchema);
|
|
954
948
|
|
|
955
949
|
const { session } = await createAgentSession({
|
|
956
950
|
cwd: worktree ?? cwd,
|
|
@@ -1050,12 +1044,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1050
1044
|
setActiveTools: (toolNames: string[]) =>
|
|
1051
1045
|
session.setActiveToolsByName(toolNames.filter(name => !parentOwnedToolNames.has(name))),
|
|
1052
1046
|
getCommands: () => [],
|
|
1053
|
-
setModel:
|
|
1054
|
-
const key = await session.modelRegistry.getApiKey(model);
|
|
1055
|
-
if (!key) return false;
|
|
1056
|
-
await session.setModel(model);
|
|
1057
|
-
return true;
|
|
1058
|
-
},
|
|
1047
|
+
setModel: model => runExtensionSetModel(session, model),
|
|
1059
1048
|
getThinkingLevel: () => session.thinkingLevel,
|
|
1060
1049
|
setThinkingLevel: level => session.setThinkingLevel(level),
|
|
1061
1050
|
getSessionName: () => session.sessionManager.getSessionName(),
|
|
@@ -1071,14 +1060,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1071
1060
|
shutdown: () => {},
|
|
1072
1061
|
getContextUsage: () => session.getContextUsage(),
|
|
1073
1062
|
getSystemPrompt: () => session.systemPrompt,
|
|
1074
|
-
compact:
|
|
1075
|
-
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
1076
|
-
const options =
|
|
1077
|
-
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
1078
|
-
? instructionsOrOptions
|
|
1079
|
-
: undefined;
|
|
1080
|
-
await session.compact(instructions, options);
|
|
1081
|
-
},
|
|
1063
|
+
compact: instructionsOrOptions => runExtensionCompact(session, instructionsOrOptions),
|
|
1082
1064
|
},
|
|
1083
1065
|
);
|
|
1084
1066
|
extensionRunner.onError(err => {
|
|
@@ -1129,10 +1111,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1129
1111
|
|
|
1130
1112
|
await session.waitForIdle();
|
|
1131
1113
|
if (!submitResultCalled && !abortSignal.aborted) {
|
|
1132
|
-
|
|
1133
|
-
exitCode = 1;
|
|
1134
|
-
abortReasonText ??= SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
|
|
1135
|
-
error ??= SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
|
|
1114
|
+
exitCode = 0;
|
|
1136
1115
|
}
|
|
1137
1116
|
|
|
1138
1117
|
const lastAssistant = session.getLastAssistantMessage();
|