@kkelly-offical/kkcode 0.1.7 → 0.2.3-preview.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/LICENSE +674 -674
- package/README.md +474 -387
- package/package.json +50 -46
- package/src/agent/agent.mjs +228 -220
- package/src/agent/custom-agent-loader.mjs +6 -3
- package/src/agent/generator.mjs +2 -2
- package/src/agent/prompt/assistant.txt +12 -0
- package/src/agent/prompt/bug-hunter.txt +89 -89
- package/src/agent/prompt/frontend-designer.txt +58 -58
- package/src/agent/prompt/guide.txt +1 -1
- package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
- package/src/agent/prompt/longagent-coding-agent.txt +37 -37
- package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
- package/src/agent/prompt/longagent-preview-agent.txt +63 -63
- package/src/command/custom-commands.mjs +2 -2
- package/src/commands/agent.mjs +1 -1
- package/src/commands/background.mjs +145 -4
- package/src/commands/chat.mjs +117 -76
- package/src/commands/config.mjs +148 -1
- package/src/commands/doctor.mjs +30 -6
- package/src/commands/init.mjs +32 -6
- package/src/commands/longagent.mjs +117 -0
- package/src/commands/mcp.mjs +275 -43
- package/src/commands/permission.mjs +1 -1
- package/src/commands/session.mjs +195 -140
- package/src/commands/skill.mjs +63 -0
- package/src/commands/theme.mjs +1 -1
- package/src/commands/update.mjs +32 -0
- package/src/config/defaults.mjs +289 -260
- package/src/config/import-config.mjs +1 -1
- package/src/config/load-config.mjs +61 -4
- package/src/config/schema.mjs +604 -574
- package/src/context.mjs +4 -1
- package/src/core/constants.mjs +97 -91
- package/src/core/types.mjs +1 -1
- package/src/github/api.mjs +78 -78
- package/src/github/auth.mjs +294 -286
- package/src/github/flow.mjs +298 -298
- package/src/github/workspace.mjs +225 -212
- package/src/index.mjs +87 -82
- package/src/knowledge/frontend-aesthetics.txt +38 -38
- package/src/mcp/client-http.mjs +139 -141
- package/src/mcp/client-sse.mjs +297 -288
- package/src/mcp/client-stdio.mjs +534 -533
- package/src/mcp/constants.mjs +4 -2
- package/src/mcp/registry.mjs +498 -479
- package/src/mcp/stdio-framing.mjs +135 -133
- package/src/mcp/tool-result.mjs +24 -24
- package/src/observability/edit-diagnostics.mjs +449 -0
- package/src/observability/index.mjs +42 -42
- package/src/observability/metrics.mjs +165 -137
- package/src/observability/tracer.mjs +137 -137
- package/src/onboarding.mjs +209 -0
- package/src/orchestration/background-manager.mjs +567 -372
- package/src/orchestration/background-worker.mjs +419 -305
- package/src/orchestration/interruption-reason.mjs +21 -0
- package/src/orchestration/longagent-manager.mjs +197 -171
- package/src/orchestration/stage-scheduler.mjs +733 -728
- package/src/orchestration/subagent-router.mjs +7 -1
- package/src/orchestration/task-scheduler.mjs +219 -7
- package/src/permission/engine.mjs +1 -1
- package/src/permission/exec-policy.mjs +370 -370
- package/src/permission/file-edit-policy.mjs +108 -0
- package/src/permission/prompt.mjs +1 -1
- package/src/permission/rules.mjs +116 -7
- package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
- package/src/plugin/hook-bus.mjs +19 -5
- package/src/plugin/manifest-loader.mjs +222 -0
- package/src/provider/anthropic.mjs +396 -390
- package/src/provider/ollama.mjs +7 -1
- package/src/provider/openai.mjs +382 -340
- package/src/provider/retry-policy.mjs +74 -68
- package/src/provider/router.mjs +242 -241
- package/src/provider/sse.mjs +104 -104
- package/src/provider/wizard.mjs +556 -0
- package/src/repl/capability-facade.mjs +30 -0
- package/src/repl/command-surface.mjs +23 -0
- package/src/repl/controller-entry.mjs +40 -0
- package/src/repl/core-shell.mjs +208 -0
- package/src/repl/dialog-router.mjs +87 -0
- package/src/repl/input-engine.mjs +76 -0
- package/src/repl/keymap.mjs +7 -0
- package/src/repl/operator-surface.mjs +15 -0
- package/src/repl/permission-flow.mjs +49 -0
- package/src/repl/runtime-facade.mjs +36 -0
- package/src/repl/slash-router.mjs +62 -0
- package/src/repl/state-store.mjs +29 -0
- package/src/repl/turn-controller.mjs +58 -0
- package/src/repl/verification.mjs +23 -0
- package/src/repl.mjs +3371 -2981
- package/src/rules/load-rules.mjs +3 -3
- package/src/runtime.mjs +1 -1
- package/src/session/agent-transaction.mjs +86 -0
- package/src/session/checkpoint.mjs +302 -302
- package/src/session/compaction.mjs +298 -298
- package/src/session/engine.mjs +417 -232
- package/src/session/longagent-4stage.mjs +467 -460
- package/src/session/longagent-hybrid.mjs +1344 -1097
- package/src/session/longagent-plan.mjs +376 -365
- package/src/session/longagent-project-memory.mjs +53 -53
- package/src/session/longagent-scaffold.mjs +291 -291
- package/src/session/longagent-task-bus.mjs +138 -54
- package/src/session/longagent-utils.mjs +828 -472
- package/src/session/longagent.mjs +911 -900
- package/src/session/loop.mjs +1005 -930
- package/src/session/prompt/agent.txt +25 -25
- package/src/session/prompt/anthropic.txt +150 -150
- package/src/session/prompt/beast.txt +1 -1
- package/src/session/prompt/plan.txt +31 -31
- package/src/session/prompt/qwen.txt +46 -46
- package/src/session/recovery.mjs +21 -0
- package/src/session/rollback.mjs +196 -195
- package/src/session/routing-observability.mjs +72 -0
- package/src/session/runtime-state.mjs +47 -0
- package/src/session/store.mjs +523 -519
- package/src/session/system-prompt.mjs +308 -273
- package/src/session/task-validator.mjs +267 -267
- package/src/session/usability-gates.mjs +2 -2
- package/src/skill/builtin/commit.mjs +64 -64
- package/src/skill/builtin/design.mjs +76 -76
- package/src/skill/generator.mjs +18 -2
- package/src/skill/registry.mjs +642 -390
- package/src/storage/audit-store.mjs +18 -11
- package/src/storage/event-log.mjs +7 -1
- package/src/storage/ghost-commit-store.mjs +243 -245
- package/src/storage/paths.mjs +17 -0
- package/src/theme/default-theme.mjs +1 -1
- package/src/theme/markdown.mjs +4 -0
- package/src/theme/schema.mjs +1 -1
- package/src/theme/status-bar.mjs +162 -158
- package/src/tool/audit-wrapper.mjs +18 -2
- package/src/tool/edit-transaction.mjs +23 -0
- package/src/tool/executor.mjs +26 -1
- package/src/tool/file-read-state.mjs +65 -0
- package/src/tool/git-auto.mjs +526 -526
- package/src/tool/git-full-auto.mjs +487 -478
- package/src/tool/mutation-guard.mjs +54 -0
- package/src/tool/prompt/edit.txt +3 -3
- package/src/tool/prompt/multiedit.txt +1 -0
- package/src/tool/prompt/notebookedit.txt +2 -1
- package/src/tool/prompt/patch.txt +25 -24
- package/src/tool/prompt/read.txt +3 -3
- package/src/tool/prompt/sysinfo.txt +29 -0
- package/src/tool/prompt/task.txt +66 -4
- package/src/tool/prompt/write.txt +2 -2
- package/src/tool/question-prompt.mjs +99 -93
- package/src/tool/registry.mjs +1701 -1343
- package/src/tool/task-tool.mjs +14 -6
- package/src/ui/activity-renderer.mjs +667 -664
- package/src/ui/repl-background-panel.mjs +7 -0
- package/src/ui/repl-capability-panel.mjs +9 -0
- package/src/ui/repl-dashboard.mjs +54 -4
- package/src/ui/repl-help.mjs +110 -0
- package/src/ui/repl-operator-panel.mjs +12 -0
- package/src/ui/repl-route-feedback.mjs +35 -0
- package/src/ui/repl-status-view.mjs +76 -0
- package/src/ui/repl-task-panel.mjs +5 -0
- package/src/ui/repl-transcript-panel.mjs +56 -0
- package/src/ui/repl-turn-summary.mjs +135 -0
- package/src/update/checker.mjs +184 -0
- package/src/usage/pricing.mjs +122 -121
- package/src/usage/usage-meter.mjs +1 -0
- package/src/util/git.mjs +562 -519
- package/src/util/template.mjs +6 -1
- package/src/version.mjs +3 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises"
|
|
2
|
+
import { getFileReadState, extractTrackedView } from "./file-read-state.mjs"
|
|
3
|
+
|
|
4
|
+
function missingReadMessage(displayPath, operation) {
|
|
5
|
+
return `error: "${displayPath}" has not been read yet. Read it first before ${operation}.`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function partialReadMessage(displayPath, operation) {
|
|
9
|
+
return `error: "${displayPath}" was only partially read. Read the full file before ${operation}.`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function staleReadMessage(displayPath, operation) {
|
|
13
|
+
return `error: "${displayPath}" has changed since it was last read. Read it again before ${operation}.`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function missingFileMessage(displayPath, operation) {
|
|
17
|
+
return `error: "${displayPath}" no longer exists. Re-read the latest workspace state before ${operation}.`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function validateExistingFileMutation({
|
|
21
|
+
targetPath,
|
|
22
|
+
displayPath,
|
|
23
|
+
operation,
|
|
24
|
+
requireFullRead = false
|
|
25
|
+
}) {
|
|
26
|
+
const readState = getFileReadState(targetPath)
|
|
27
|
+
const label = String(displayPath || targetPath)
|
|
28
|
+
const action = String(operation || "modifying it")
|
|
29
|
+
|
|
30
|
+
if (!readState) {
|
|
31
|
+
return { ok: false, reason: "unread", message: missingReadMessage(label, action) }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (requireFullRead && readState.isPartialView) {
|
|
35
|
+
return { ok: false, reason: "partial_read", message: partialReadMessage(label, action) }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const fileStat = await stat(targetPath)
|
|
40
|
+
const currentTimestamp = Math.floor(fileStat.mtimeMs)
|
|
41
|
+
const currentContent = await readFile(targetPath, "utf8")
|
|
42
|
+
const currentTrackedView = extractTrackedView(currentContent, readState)
|
|
43
|
+
if (currentTrackedView === readState.content) {
|
|
44
|
+
return { ok: true, readState, currentTimestamp, currentContent }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { ok: false, reason: "stale", message: staleReadMessage(label, action) }
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error?.code === "ENOENT") {
|
|
50
|
+
return { ok: false, reason: "missing", message: missingFileMessage(label, action) }
|
|
51
|
+
}
|
|
52
|
+
throw error
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/tool/prompt/edit.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Performs exact string replacements in files. Transactional with automatic rollback on failure.
|
|
2
2
|
|
|
3
3
|
Usage:
|
|
4
|
-
- You must use `read`
|
|
4
|
+
- You must use `read` before editing. This tool will reject edits on unread or stale files.
|
|
5
5
|
- When editing text from `read` output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + arrow (→). Everything after that arrow is the actual file content to match. Never include any part of the line number prefix in the `before` or `after` strings.
|
|
6
6
|
- ALWAYS prefer editing existing files. NEVER write new files unless explicitly required.
|
|
7
7
|
- Only use emojis if the user explicitly requests it.
|
|
@@ -22,6 +22,6 @@ When to use replace_all:
|
|
|
22
22
|
Common mistakes and how to avoid them:
|
|
23
23
|
- Editing without reading first → always `read` before `edit`
|
|
24
24
|
- `before` too short, matches multiple locations → include surrounding lines for uniqueness
|
|
25
|
-
- Stale content after failed edit → re-read the file, it may have changed
|
|
25
|
+
- Stale content after failed edit → re-read the file, it may have changed; stale edits are rejected
|
|
26
26
|
- Including line number prefixes in `before` → only use actual file content, not the " 1→" prefix
|
|
27
|
-
- Using `edit` for whole-file replacement → use `write` instead for complete rewrites
|
|
27
|
+
- Using `edit` for whole-file replacement → use `write` instead for complete rewrites
|
|
@@ -16,5 +16,6 @@ Parameters:
|
|
|
16
16
|
|
|
17
17
|
Rules:
|
|
18
18
|
- You MUST `read` each file before editing it (same as `edit` tool).
|
|
19
|
+
- Existing files in the batch must be freshly read. If any file is unread or stale, the whole batch is rejected.
|
|
19
20
|
- If any change fails validation (no match, ambiguous match), the entire batch is rejected before any writes.
|
|
20
21
|
- On write failure mid-batch, all previously applied changes are rolled back.
|
|
@@ -14,8 +14,9 @@ Operations:
|
|
|
14
14
|
|
|
15
15
|
IMPORTANT:
|
|
16
16
|
- The notebook must be a valid .ipynb JSON file with a "cells" array.
|
|
17
|
+
- You MUST read the notebook before editing it. Unread or stale notebook edits are rejected.
|
|
17
18
|
- cell_number is 0-indexed. The first cell is 0, second is 1, etc.
|
|
18
19
|
- For insert mode, cell_type must be specified ("code" or "markdown").
|
|
19
20
|
- Cell outputs are preserved during replace; cleared only if cell_type changes to "markdown".
|
|
20
21
|
- When inserting code cells, outputs default to an empty array.
|
|
21
|
-
- This tool reads the notebook, modifies the cells array, and writes it back atomically.
|
|
22
|
+
- This tool reads the notebook, modifies the cells array, and writes it back atomically.
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
Replace a range of lines in a file by line number. Transactional with automatic rollback.
|
|
2
|
-
|
|
3
|
-
Usage:
|
|
4
|
-
- You MUST `read` the file first — patches on unread files are rejected.
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- You know exact
|
|
18
|
-
- You
|
|
19
|
-
- You
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
Replace a range of lines in a file by line number. Transactional with automatic rollback.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
- You MUST `read` the file first — patches on unread files are rejected.
|
|
5
|
+
- If the file changed after your last read, `patch` will reject until you re-read it.
|
|
6
|
+
- Use `read` with offset/limit to view the relevant section, note line numbers, then call `patch`.
|
|
7
|
+
- Lines are 1-based and the range is inclusive (both start_line and end_line are replaced).
|
|
8
|
+
- To delete lines without replacement, pass content as empty string "".
|
|
9
|
+
|
|
10
|
+
Parameters:
|
|
11
|
+
- path (required): file path
|
|
12
|
+
- start_line (required): first line to replace (1-based, inclusive)
|
|
13
|
+
- end_line (required): last line to replace (1-based, inclusive)
|
|
14
|
+
- content (required): replacement text (replaces the entire line range)
|
|
15
|
+
|
|
16
|
+
When to use `patch` vs `edit`:
|
|
17
|
+
- You know exact line numbers from a recent `read` → use `patch`
|
|
18
|
+
- You know exact text to match but not line numbers → use `edit`
|
|
19
|
+
- You need to replace many occurrences of a string → use `edit` with replace_all
|
|
20
|
+
- You want to replace a large section (50+ lines) → use `patch` (only needs new content, not old)
|
|
21
|
+
|
|
22
|
+
Workflow for large file modifications:
|
|
23
|
+
1. `read` the file (or section with offset/limit)
|
|
24
|
+
2. Note the line numbers of the section to change
|
|
25
|
+
3. Call `patch` with start_line, end_line, and new content
|
package/src/tool/prompt/read.txt
CHANGED
|
@@ -18,7 +18,7 @@ Assume this tool is able to read all files on the machine. If the user provides
|
|
|
18
18
|
## Critical Rules
|
|
19
19
|
|
|
20
20
|
- ALWAYS use `read` to view files. NEVER use `bash` with cat/head/tail/type/Get-Content.
|
|
21
|
-
- You MUST read
|
|
21
|
+
- You MUST read an existing file before using `write`, `edit`, `patch`, or `notebookedit` on it. Unread or stale edits are rejected.
|
|
22
22
|
- You can call multiple `read` tools in a single response. It is always better to speculatively read multiple potentially useful files in parallel.
|
|
23
23
|
- If the user provides a path to a screenshot, ALWAYS use this tool to view the file at that path.
|
|
24
24
|
|
|
@@ -32,9 +32,9 @@ Assume this tool is able to read all files on the machine. If the user provides
|
|
|
32
32
|
|
|
33
33
|
## Tips
|
|
34
34
|
|
|
35
|
-
- Read the full file first to understand context before editing.
|
|
35
|
+
- Read the full file first to understand context before editing. Offset/limit reads are treated as partial views and do not authorize whole-file overwrites.
|
|
36
36
|
- For large files (1000+ lines), use offset+limit to read specific sections.
|
|
37
37
|
- After a failed edit, re-read the file — it may have changed since your last read.
|
|
38
38
|
- Line numbers in output help you locate exact positions for edits.
|
|
39
39
|
- When reading multiple related files, read them all in parallel for efficiency.
|
|
40
|
-
- When editing text from read output, preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + →. Everything after that → is the actual file content to match. Never include any part of the line number prefix in edit operations.
|
|
40
|
+
- When editing text from read output, preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + →. Everything after that → is the actual file content to match. Never include any part of the line number prefix in edit operations.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Return structured, read-only system and runtime information for the current machine/workspace.
|
|
2
|
+
|
|
3
|
+
Use `sysinfo` when the user asks for:
|
|
4
|
+
- system information
|
|
5
|
+
- OS / runtime / shell summary
|
|
6
|
+
- CPU / memory / disk summary
|
|
7
|
+
- current workspace / package-manager / git-repo context
|
|
8
|
+
|
|
9
|
+
Prefer `sysinfo` over raw `bash` when the goal is a concise environment summary.
|
|
10
|
+
|
|
11
|
+
Parameters:
|
|
12
|
+
- `sections` (optional): array of section names to return. Supported:
|
|
13
|
+
- `os`
|
|
14
|
+
- `runtime`
|
|
15
|
+
- `workspace`
|
|
16
|
+
- `cpu`
|
|
17
|
+
- `memory`
|
|
18
|
+
- `disk`
|
|
19
|
+
- `path` (optional): workspace path to inspect for `workspace` / `disk` sections
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
- `sysinfo()`
|
|
23
|
+
- `sysinfo(sections=["os","runtime","workspace"])`
|
|
24
|
+
- `sysinfo(sections=["disk"], path=".")`
|
|
25
|
+
|
|
26
|
+
Safety:
|
|
27
|
+
- This tool is read-only.
|
|
28
|
+
- It does not dump full environment variables or process lists.
|
|
29
|
+
- Use `bash` only when the user truly needs deeper platform-specific diagnostics.
|
package/src/tool/prompt/task.txt
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
Launch a subagent to handle complex, multi-step tasks autonomously.
|
|
2
2
|
|
|
3
|
-
Each task spawns a
|
|
3
|
+
Each task spawns a delegated LLM session that makes its own tool calls and returns a single result message when done.
|
|
4
|
+
|
|
5
|
+
# Delegation Decision Rules
|
|
6
|
+
|
|
7
|
+
Stay local when:
|
|
8
|
+
- The next step is a simple read/edit/run action you can do directly
|
|
9
|
+
- Your immediate next action is blocked on the result, and delegation would only add latency
|
|
10
|
+
- The work is tightly coupled to nearby edits you are already making
|
|
11
|
+
- Routing is merely uncertain — do not delegate just to compensate for that uncertainty
|
|
12
|
+
|
|
13
|
+
Delegate when:
|
|
14
|
+
- The work needs multiple tool calls plus autonomous reasoning
|
|
15
|
+
- The work is self-contained enough to brief clearly
|
|
16
|
+
- The work can proceed in parallel without racing your current critical path
|
|
17
|
+
|
|
18
|
+
# Fresh Session vs Forked Context vs Continued Session
|
|
19
|
+
|
|
20
|
+
- New `task` calls start with fresh context by default. The subagent does NOT see your parent conversation unless you summarize it in the prompt.
|
|
21
|
+
- Set `execution_mode="fork_context"` when the delegated task should start from a fork of the current parent session transcript. Use this for sidecar research, audits, or follow-up verification that materially benefits from current context.
|
|
22
|
+
- `fork_context` is reserved for **read-only sidecar work**. If the delegated slice needs implementation or file mutation, prefer `fresh_agent`.
|
|
23
|
+
- Use `session_id` only to continue an existing delegated session that already has the needed context.
|
|
24
|
+
- `fork_context` inherits transcript context, but it does NOT change the parent/child result contract: the parent agent still owns synthesis and must wait for the child result instead of assuming it succeeded.
|
|
25
|
+
- For implementation-heavy work, prefer a fresh task brief with explicit file scope. For follow-up questions or incremental continuation on the same delegated slice, continue the same `session_id`.
|
|
26
|
+
- `isolation="worktree"` creates a **local detached git worktree** for isolated sidecar execution. This MVP is local-only and currently intended for background delegated runs.
|
|
27
|
+
|
|
28
|
+
# Brief Writing Rules
|
|
29
|
+
|
|
30
|
+
Every delegated prompt should include:
|
|
31
|
+
- Objective: the concrete outcome to produce
|
|
32
|
+
- Why: the context or decision pressure behind the task
|
|
33
|
+
- Write scope: whether to mutate files or stay read-only
|
|
34
|
+
- Starting points: relevant paths, symbols, tests, or commands
|
|
35
|
+
- Constraints: architectural boundaries, forbidden edits, or safety rules
|
|
36
|
+
- Deliverable: what the subagent should return (summary, patch, review findings, etc.)
|
|
37
|
+
|
|
38
|
+
Never delegate understanding:
|
|
39
|
+
- Do not hand off the core synthesis step with prompts like “based on your findings, fix it”
|
|
40
|
+
- Parent agent owns synthesis, judgment, and final claims
|
|
41
|
+
- Forked/background delegates report results; they do not authorize the parent to guess
|
|
42
|
+
|
|
43
|
+
kkcode also accepts structured brief fields for these sections (`objective`, `why`, `write_scope`, `starting_points`, `constraints`, `deliverable`) and will synthesize the directive brief when `prompt` is omitted.
|
|
44
|
+
|
|
45
|
+
## Execution contract
|
|
46
|
+
|
|
47
|
+
Whether the brief is handwritten or synthesized, delegated work must follow this contract:
|
|
48
|
+
- Stay local if a direct read/edit/run action would finish the next step faster
|
|
49
|
+
- Do not delegate to compensate for routing uncertainty on a bounded local task
|
|
50
|
+
- Avoid suggesting LongAgent-style escalation unless heavy multi-file evidence is actually present
|
|
51
|
+
- Do not fabricate completion or present unfinished work as done
|
|
52
|
+
- Do not peek at unfinished sibling work and convert guesses into facts
|
|
53
|
+
- Background delegates cannot ask interactive questions; keep them self-contained
|
|
54
|
+
- `isolation="worktree"` is local-only and must not be treated as a remote execution surface
|
|
55
|
+
|
|
56
|
+
Good delegation briefs are directive and self-contained. The subagent should not need to infer hidden context.
|
|
4
57
|
|
|
5
58
|
# Available subagent types
|
|
6
59
|
|
|
@@ -21,6 +74,7 @@ Use `task` when the work:
|
|
|
21
74
|
- Benefits from an independent context window (protects main conversation from result noise)
|
|
22
75
|
- Involves code review, deep research, or broad codebase exploration
|
|
23
76
|
- Can be parallelized — launch multiple independent tasks simultaneously
|
|
77
|
+
- Is safe to hand off without racing another agent on the same files
|
|
24
78
|
|
|
25
79
|
<example>
|
|
26
80
|
User: "Please investigate how authentication works in this project"
|
|
@@ -63,7 +117,7 @@ GOOD: glob(pattern="**/*.test.js")
|
|
|
63
117
|
|
|
64
118
|
# Parameters
|
|
65
119
|
|
|
66
|
-
- **prompt** (required): Detailed task description with ALL necessary context. The subagent starts fresh — it cannot see your conversation history. Include:
|
|
120
|
+
- **prompt** (required unless `objective` is provided for a new run): Detailed task description with ALL necessary context. The subagent starts fresh — it cannot see your conversation history. Include:
|
|
67
121
|
- What to do (specific instructions)
|
|
68
122
|
- Why (context for decision-making)
|
|
69
123
|
- Whether to write code or just research
|
|
@@ -71,7 +125,11 @@ GOOD: glob(pattern="**/*.test.js")
|
|
|
71
125
|
|
|
72
126
|
- **subagent_type** (optional): One of "explore", "architect", "reviewer", "researcher", or omit for default build agent.
|
|
73
127
|
|
|
74
|
-
- **
|
|
128
|
+
- **session_id** (optional): Continue an existing delegated session when you need follow-up work on the same slice. Do NOT use this as a substitute for briefing a new task.
|
|
129
|
+
- **execution_mode** (optional): `fresh_agent` (default) or `fork_context`. Use `fork_context` only when inheriting the parent transcript is genuinely useful and the delegated slice stays read-only.
|
|
130
|
+
- **isolation** (optional): `default` (normal workspace) or `worktree` (local detached git worktree). `worktree` is currently meant for isolated background sidecar execution only.
|
|
131
|
+
|
|
132
|
+
- **run_in_background** (optional): Set to true only for sidecar work that can continue while you do other things. Returns a task_id immediately — use `background_output` to check results later. Background delegates must be self-contained and cannot use `question`.
|
|
75
133
|
|
|
76
134
|
# Important Behaviors
|
|
77
135
|
|
|
@@ -80,4 +138,8 @@ GOOD: glob(pattern="**/*.test.js")
|
|
|
80
138
|
- You can launch multiple tasks in parallel for independent work — this is a key advantage.
|
|
81
139
|
- Avoid duplicating work that subagents are already doing. If you delegated research, don't also search yourself.
|
|
82
140
|
- Provide clear, self-contained prompts. The subagent has no access to your conversation context.
|
|
83
|
-
- Clearly state whether you expect the agent to write code or just do research, since it cannot infer your intent.
|
|
141
|
+
- Clearly state whether you expect the agent to write code or just do research, since it cannot infer your intent.
|
|
142
|
+
- Do NOT launch overlapping delegates on the same file set unless coordination is explicit.
|
|
143
|
+
- Do NOT "peek" at unfinished delegated work and present it as done. Wait for the result, then summarize it accurately.
|
|
144
|
+
- Do NOT fabricate completion. If a background delegate is still running or blocked, report that truthfully.
|
|
145
|
+
- If you launch background work, keep moving on non-overlapping tasks and report progress when the delegate finishes or blocks.
|
|
@@ -2,7 +2,7 @@ Writes a file to the local filesystem. Creates parent directories automatically.
|
|
|
2
2
|
|
|
3
3
|
Usage:
|
|
4
4
|
- This tool will overwrite the existing file if there is one at the provided path.
|
|
5
|
-
- If this is an existing file, you MUST use the `read` tool first to read the file
|
|
5
|
+
- If this is an existing file, you MUST use the `read` tool first to read the full file. Partial reads do not authorize whole-file writes.
|
|
6
6
|
- ALWAYS prefer editing existing files with `edit`. NEVER write new files unless explicitly required.
|
|
7
7
|
- NEVER proactively create documentation files (*.md) or README files. Only create documentation if explicitly requested by the user.
|
|
8
8
|
- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.
|
|
@@ -35,4 +35,4 @@ When to use `write` vs `edit` vs `patch`:
|
|
|
35
35
|
- Adding content to end of file → `write` with mode="append"
|
|
36
36
|
- Changing a few lines in an existing file → `edit`
|
|
37
37
|
- Renaming a variable across a file → `edit` with replace_all
|
|
38
|
-
- Replacing a large section by line numbers → `patch`
|
|
38
|
+
- Replacing a large section by line numbers → `patch`
|
|
@@ -1,93 +1,99 @@
|
|
|
1
|
-
import { stdin as input, stdout as output } from "node:process"
|
|
2
|
-
import { createInterface } from "node:readline/promises"
|
|
3
|
-
|
|
4
|
-
let customPromptHandler = null
|
|
5
|
-
|
|
6
|
-
export function setQuestionPromptHandler(handler) {
|
|
7
|
-
customPromptHandler = typeof handler === "function" ? handler : null
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function askQuestionInteractive({ questions }) {
|
|
11
|
-
if (!Array.isArray(questions) || questions.length === 0) {
|
|
12
|
-
return {}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// 1. TUI handler (registered by repl.mjs)
|
|
16
|
-
if (customPromptHandler) {
|
|
17
|
-
const answers = await customPromptHandler({ questions })
|
|
18
|
-
if (answers && typeof answers === "object") return answers
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// 2. Non-TTY: return empty answers
|
|
22
|
-
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
23
|
-
return Object.fromEntries(questions.map((q) => [q.id, ""]))
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// 3. TTY fallback: readline sequential Q&A
|
|
27
|
-
const rl = createInterface({ input, output })
|
|
28
|
-
const answers = {}
|
|
29
|
-
try {
|
|
30
|
-
for (const q of questions) {
|
|
31
|
-
console.log("")
|
|
32
|
-
console.log(` ${q.text}`)
|
|
33
|
-
if (q.description) console.log(` ${q.description}`)
|
|
34
|
-
const options = Array.isArray(q.options) ? q.options : []
|
|
35
|
-
if (options.length) {
|
|
36
|
-
for (let i = 0; i < options.length; i++) {
|
|
37
|
-
const opt = options[i]
|
|
38
|
-
console.log(` ${i + 1}. ${opt.label}`)
|
|
39
|
-
if (opt.description) console.log(` ${opt.description}`)
|
|
40
|
-
}
|
|
41
|
-
if (q.allowCustom !== false) {
|
|
42
|
-
console.log(` ${options.length + 1}. Custom...`)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const raw = (await rl.question(" > ")).trim()
|
|
46
|
-
if (options.length) {
|
|
47
|
-
const idx = parseInt(raw, 10)
|
|
48
|
-
if (idx >= 1 && idx <= options.length) {
|
|
49
|
-
const chosen = options[idx - 1]
|
|
50
|
-
answers[q.id] = chosen.value || chosen.label
|
|
51
|
-
} else {
|
|
52
|
-
answers[q.id] = raw
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
answers[q.id] = raw
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} finally {
|
|
59
|
-
rl.close()
|
|
60
|
-
}
|
|
61
|
-
return answers
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export async function askPlanApproval({ plan, files = [] }) {
|
|
65
|
-
const fileList = files.length ? `\nFiles to modify:\n${files.map(f => ` - ${f}`).join("\n")}` : ""
|
|
66
|
-
const questions = [
|
|
67
|
-
{
|
|
68
|
-
id: "plan_approval",
|
|
69
|
-
text: `Plan Review`,
|
|
70
|
-
description: `${plan}${fileList}`,
|
|
71
|
-
options: [
|
|
72
|
-
{ label: "Approve", value: "approve", description: "Proceed with this plan" },
|
|
73
|
-
{ label: "Request Changes", value: "changes", description: "Revise and resubmit with feedback" },
|
|
74
|
-
{ label: "Reject", value: "reject", description: "Cancel this plan entirely" }
|
|
75
|
-
],
|
|
76
|
-
multi: false,
|
|
77
|
-
allowCustom: true
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
const answers = await askQuestionInteractive({ questions })
|
|
81
|
-
const answer = String(answers.plan_approval || "").trim().toLowerCase()
|
|
82
|
-
if (answer === "approve" || answer === "1") {
|
|
83
|
-
return { approved: true, requestChanges: false, feedback: "" }
|
|
84
|
-
}
|
|
85
|
-
if (answer === "changes" || answer === "2") {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return { approved: false, requestChanges:
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
1
|
+
import { stdin as input, stdout as output } from "node:process"
|
|
2
|
+
import { createInterface } from "node:readline/promises"
|
|
3
|
+
|
|
4
|
+
let customPromptHandler = null
|
|
5
|
+
|
|
6
|
+
export function setQuestionPromptHandler(handler) {
|
|
7
|
+
customPromptHandler = typeof handler === "function" ? handler : null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function askQuestionInteractive({ questions }) {
|
|
11
|
+
if (!Array.isArray(questions) || questions.length === 0) {
|
|
12
|
+
return {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// 1. TUI handler (registered by repl.mjs)
|
|
16
|
+
if (customPromptHandler) {
|
|
17
|
+
const answers = await customPromptHandler({ questions })
|
|
18
|
+
if (answers && typeof answers === "object") return answers
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 2. Non-TTY: return empty answers
|
|
22
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
23
|
+
return Object.fromEntries(questions.map((q) => [q.id, ""]))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 3. TTY fallback: readline sequential Q&A
|
|
27
|
+
const rl = createInterface({ input, output })
|
|
28
|
+
const answers = {}
|
|
29
|
+
try {
|
|
30
|
+
for (const q of questions) {
|
|
31
|
+
console.log("")
|
|
32
|
+
console.log(` ${q.text}`)
|
|
33
|
+
if (q.description) console.log(` ${q.description}`)
|
|
34
|
+
const options = Array.isArray(q.options) ? q.options : []
|
|
35
|
+
if (options.length) {
|
|
36
|
+
for (let i = 0; i < options.length; i++) {
|
|
37
|
+
const opt = options[i]
|
|
38
|
+
console.log(` ${i + 1}. ${opt.label}`)
|
|
39
|
+
if (opt.description) console.log(` ${opt.description}`)
|
|
40
|
+
}
|
|
41
|
+
if (q.allowCustom !== false) {
|
|
42
|
+
console.log(` ${options.length + 1}. Custom...`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const raw = (await rl.question(" > ")).trim()
|
|
46
|
+
if (options.length) {
|
|
47
|
+
const idx = parseInt(raw, 10)
|
|
48
|
+
if (idx >= 1 && idx <= options.length) {
|
|
49
|
+
const chosen = options[idx - 1]
|
|
50
|
+
answers[q.id] = chosen.value || chosen.label
|
|
51
|
+
} else {
|
|
52
|
+
answers[q.id] = raw
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
answers[q.id] = raw
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} finally {
|
|
59
|
+
rl.close()
|
|
60
|
+
}
|
|
61
|
+
return answers
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function askPlanApproval({ plan, files = [] }) {
|
|
65
|
+
const fileList = files.length ? `\nFiles to modify:\n${files.map(f => ` - ${f}`).join("\n")}` : ""
|
|
66
|
+
const questions = [
|
|
67
|
+
{
|
|
68
|
+
id: "plan_approval",
|
|
69
|
+
text: `Plan Review`,
|
|
70
|
+
description: `${plan}${fileList}`,
|
|
71
|
+
options: [
|
|
72
|
+
{ label: "Approve", value: "approve", description: "Proceed with this plan" },
|
|
73
|
+
{ label: "Request Changes", value: "changes", description: "Revise and resubmit with feedback" },
|
|
74
|
+
{ label: "Reject", value: "reject", description: "Cancel this plan entirely" }
|
|
75
|
+
],
|
|
76
|
+
multi: false,
|
|
77
|
+
allowCustom: true
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
const answers = await askQuestionInteractive({ questions })
|
|
81
|
+
const answer = String(answers.plan_approval || "").trim().toLowerCase()
|
|
82
|
+
if (answer === "approve" || answer === "1") {
|
|
83
|
+
return { approved: true, requestChanges: false, feedback: "" }
|
|
84
|
+
}
|
|
85
|
+
if (answer === "changes" || answer === "2") {
|
|
86
|
+
const rl2 = createInterface({ input, output })
|
|
87
|
+
let changeFeedback = ""
|
|
88
|
+
try { changeFeedback = (await rl2.question(" Feedback> ")).trim() } catch {} finally { rl2.close() }
|
|
89
|
+
return { approved: false, requestChanges: true, feedback: changeFeedback }
|
|
90
|
+
}
|
|
91
|
+
if (answer === "reject" || answer === "3") {
|
|
92
|
+
const rl3 = createInterface({ input, output })
|
|
93
|
+
let rejectFeedback = ""
|
|
94
|
+
try { rejectFeedback = (await rl3.question(" Reason> ")).trim() } catch {} finally { rl3.close() }
|
|
95
|
+
return { approved: false, requestChanges: false, feedback: rejectFeedback }
|
|
96
|
+
}
|
|
97
|
+
// Custom text input: treat as "request changes" with the text as feedback
|
|
98
|
+
return { approved: false, requestChanges: true, feedback: answer }
|
|
99
|
+
}
|