@kkelly-offical/kkcode 0.1.7 → 0.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.
Files changed (163) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +452 -387
  3. package/package.json +50 -46
  4. package/src/agent/agent.mjs +228 -220
  5. package/src/agent/custom-agent-loader.mjs +6 -3
  6. package/src/agent/generator.mjs +2 -2
  7. package/src/agent/prompt/assistant.txt +12 -0
  8. package/src/agent/prompt/bug-hunter.txt +89 -89
  9. package/src/agent/prompt/frontend-designer.txt +58 -58
  10. package/src/agent/prompt/guide.txt +1 -1
  11. package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
  12. package/src/agent/prompt/longagent-coding-agent.txt +37 -37
  13. package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
  14. package/src/agent/prompt/longagent-preview-agent.txt +63 -63
  15. package/src/command/custom-commands.mjs +2 -2
  16. package/src/commands/agent.mjs +1 -1
  17. package/src/commands/background.mjs +145 -4
  18. package/src/commands/chat.mjs +117 -76
  19. package/src/commands/config.mjs +148 -1
  20. package/src/commands/doctor.mjs +30 -6
  21. package/src/commands/init.mjs +32 -6
  22. package/src/commands/longagent.mjs +117 -0
  23. package/src/commands/mcp.mjs +275 -43
  24. package/src/commands/permission.mjs +1 -1
  25. package/src/commands/session.mjs +195 -140
  26. package/src/commands/skill.mjs +63 -0
  27. package/src/commands/theme.mjs +1 -1
  28. package/src/config/defaults.mjs +280 -260
  29. package/src/config/import-config.mjs +1 -1
  30. package/src/config/load-config.mjs +61 -4
  31. package/src/config/schema.mjs +591 -574
  32. package/src/context.mjs +4 -1
  33. package/src/core/constants.mjs +97 -91
  34. package/src/core/types.mjs +1 -1
  35. package/src/github/api.mjs +78 -78
  36. package/src/github/auth.mjs +294 -286
  37. package/src/github/flow.mjs +298 -298
  38. package/src/github/workspace.mjs +225 -212
  39. package/src/index.mjs +84 -82
  40. package/src/knowledge/frontend-aesthetics.txt +38 -38
  41. package/src/mcp/client-http.mjs +139 -141
  42. package/src/mcp/client-sse.mjs +297 -288
  43. package/src/mcp/client-stdio.mjs +534 -533
  44. package/src/mcp/constants.mjs +2 -2
  45. package/src/mcp/registry.mjs +498 -479
  46. package/src/mcp/stdio-framing.mjs +135 -133
  47. package/src/mcp/tool-result.mjs +24 -24
  48. package/src/observability/edit-diagnostics.mjs +449 -0
  49. package/src/observability/index.mjs +42 -42
  50. package/src/observability/metrics.mjs +165 -137
  51. package/src/observability/tracer.mjs +137 -137
  52. package/src/onboarding.mjs +209 -0
  53. package/src/orchestration/background-manager.mjs +567 -372
  54. package/src/orchestration/background-worker.mjs +419 -305
  55. package/src/orchestration/interruption-reason.mjs +21 -0
  56. package/src/orchestration/longagent-manager.mjs +197 -171
  57. package/src/orchestration/stage-scheduler.mjs +733 -728
  58. package/src/orchestration/subagent-router.mjs +7 -1
  59. package/src/orchestration/task-scheduler.mjs +219 -7
  60. package/src/permission/engine.mjs +1 -1
  61. package/src/permission/exec-policy.mjs +370 -370
  62. package/src/permission/file-edit-policy.mjs +108 -0
  63. package/src/permission/prompt.mjs +1 -1
  64. package/src/permission/rules.mjs +116 -7
  65. package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
  66. package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
  67. package/src/plugin/hook-bus.mjs +19 -5
  68. package/src/plugin/manifest-loader.mjs +222 -0
  69. package/src/provider/anthropic.mjs +396 -390
  70. package/src/provider/ollama.mjs +7 -1
  71. package/src/provider/openai.mjs +382 -340
  72. package/src/provider/retry-policy.mjs +74 -68
  73. package/src/provider/router.mjs +242 -241
  74. package/src/provider/sse.mjs +104 -104
  75. package/src/provider/wizard.mjs +556 -0
  76. package/src/repl/capability-facade.mjs +30 -0
  77. package/src/repl/command-surface.mjs +23 -0
  78. package/src/repl/controller-entry.mjs +40 -0
  79. package/src/repl/core-shell.mjs +208 -0
  80. package/src/repl/dialog-router.mjs +87 -0
  81. package/src/repl/input-engine.mjs +76 -0
  82. package/src/repl/keymap.mjs +7 -0
  83. package/src/repl/operator-surface.mjs +15 -0
  84. package/src/repl/permission-flow.mjs +49 -0
  85. package/src/repl/runtime-facade.mjs +36 -0
  86. package/src/repl/slash-router.mjs +62 -0
  87. package/src/repl/state-store.mjs +29 -0
  88. package/src/repl/turn-controller.mjs +58 -0
  89. package/src/repl/verification.mjs +23 -0
  90. package/src/repl.mjs +3368 -2981
  91. package/src/rules/load-rules.mjs +3 -3
  92. package/src/runtime.mjs +1 -1
  93. package/src/session/agent-transaction.mjs +86 -0
  94. package/src/session/checkpoint.mjs +302 -302
  95. package/src/session/compaction.mjs +298 -298
  96. package/src/session/engine.mjs +417 -232
  97. package/src/session/longagent-4stage.mjs +467 -460
  98. package/src/session/longagent-hybrid.mjs +1344 -1097
  99. package/src/session/longagent-plan.mjs +376 -365
  100. package/src/session/longagent-project-memory.mjs +53 -53
  101. package/src/session/longagent-scaffold.mjs +291 -291
  102. package/src/session/longagent-task-bus.mjs +138 -54
  103. package/src/session/longagent-utils.mjs +828 -472
  104. package/src/session/longagent.mjs +911 -900
  105. package/src/session/loop.mjs +1005 -930
  106. package/src/session/prompt/agent.txt +25 -25
  107. package/src/session/prompt/anthropic.txt +150 -150
  108. package/src/session/prompt/beast.txt +1 -1
  109. package/src/session/prompt/plan.txt +31 -31
  110. package/src/session/prompt/qwen.txt +46 -46
  111. package/src/session/recovery.mjs +21 -0
  112. package/src/session/rollback.mjs +196 -195
  113. package/src/session/routing-observability.mjs +72 -0
  114. package/src/session/runtime-state.mjs +47 -0
  115. package/src/session/store.mjs +523 -519
  116. package/src/session/system-prompt.mjs +308 -273
  117. package/src/session/task-validator.mjs +267 -267
  118. package/src/session/usability-gates.mjs +2 -2
  119. package/src/skill/builtin/commit.mjs +64 -64
  120. package/src/skill/builtin/design.mjs +76 -76
  121. package/src/skill/generator.mjs +18 -2
  122. package/src/skill/registry.mjs +642 -390
  123. package/src/storage/audit-store.mjs +18 -11
  124. package/src/storage/event-log.mjs +7 -1
  125. package/src/storage/ghost-commit-store.mjs +243 -245
  126. package/src/storage/paths.mjs +13 -0
  127. package/src/theme/default-theme.mjs +1 -1
  128. package/src/theme/markdown.mjs +4 -0
  129. package/src/theme/schema.mjs +1 -1
  130. package/src/theme/status-bar.mjs +162 -158
  131. package/src/tool/audit-wrapper.mjs +18 -2
  132. package/src/tool/edit-transaction.mjs +23 -0
  133. package/src/tool/executor.mjs +26 -1
  134. package/src/tool/file-read-state.mjs +65 -0
  135. package/src/tool/git-auto.mjs +526 -526
  136. package/src/tool/git-full-auto.mjs +487 -478
  137. package/src/tool/mutation-guard.mjs +54 -0
  138. package/src/tool/prompt/edit.txt +3 -3
  139. package/src/tool/prompt/multiedit.txt +1 -0
  140. package/src/tool/prompt/notebookedit.txt +2 -1
  141. package/src/tool/prompt/patch.txt +25 -24
  142. package/src/tool/prompt/read.txt +3 -3
  143. package/src/tool/prompt/sysinfo.txt +29 -0
  144. package/src/tool/prompt/task.txt +66 -4
  145. package/src/tool/prompt/write.txt +2 -2
  146. package/src/tool/question-prompt.mjs +99 -93
  147. package/src/tool/registry.mjs +1701 -1343
  148. package/src/tool/task-tool.mjs +14 -6
  149. package/src/ui/activity-renderer.mjs +667 -664
  150. package/src/ui/repl-background-panel.mjs +7 -0
  151. package/src/ui/repl-capability-panel.mjs +9 -0
  152. package/src/ui/repl-dashboard.mjs +54 -4
  153. package/src/ui/repl-help.mjs +110 -0
  154. package/src/ui/repl-operator-panel.mjs +12 -0
  155. package/src/ui/repl-route-feedback.mjs +35 -0
  156. package/src/ui/repl-status-view.mjs +76 -0
  157. package/src/ui/repl-task-panel.mjs +5 -0
  158. package/src/ui/repl-transcript-panel.mjs +56 -0
  159. package/src/ui/repl-turn-summary.mjs +135 -0
  160. package/src/usage/pricing.mjs +122 -121
  161. package/src/usage/usage-meter.mjs +1 -0
  162. package/src/util/git.mjs +562 -519
  163. package/src/util/template.mjs +6 -1
@@ -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
+ }
@@ -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` at least once before editing. This tool will error if you attempt an edit without reading the file.
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
- - Use `read` with offset/limit to view the relevant section, note line numbers, then call `patch`.
6
- - Lines are 1-based and the range is inclusive (both start_line and end_line are replaced).
7
- - To delete lines without replacement, pass content as empty string "".
8
-
9
- Parameters:
10
- - path (required): file path
11
- - start_line (required): first line to replace (1-based, inclusive)
12
- - end_line (required): last line to replace (1-based, inclusive)
13
- - content (required): replacement text (replaces the entire line range)
14
-
15
- When to use `patch` vs `edit`:
16
- - You know exact line numbers from a recent `read` use `patch`
17
- - You know exact text to match but not line numbers → use `edit`
18
- - You need to replace many occurrences of a string → use `edit` with replace_all
19
- - You want to replace a large section (50+ lines) → use `patch` (only needs new content, not old)
20
-
21
- Workflow for large file modifications:
22
- 1. `read` the file (or section with offset/limit)
23
- 2. Note the line numbers of the section to change
24
- 3. Call `patch` with start_line, end_line, and new content
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
@@ -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 a file before editing it with `edit`. Edits on unread files are rejected.
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. Do not provide offset/limit unless necessary.
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.
@@ -1,6 +1,59 @@
1
1
  Launch a subagent to handle complex, multi-step tasks autonomously.
2
2
 
3
- Each task spawns a full, independent LLM session with its own context window. The subagent makes its own tool calls and returns a single result message when done.
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
- - **run_in_background** (optional): Set to true to run as a background worker. Returns a task_id immediately use `background_output` to check results later.
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's contents.
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
- return { approved: false, requestChanges: true, feedback: "" }
87
- }
88
- if (answer === "reject" || answer === "3") {
89
- return { approved: false, requestChanges: false, feedback: "" }
90
- }
91
- // Custom text input: treat as "request changes" with the text as feedback
92
- return { approved: false, requestChanges: true, feedback: answer }
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
+ }