@causyne/agent 0.0.0-stage → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/LICENSE +102 -0
  2. package/README.md +31 -2
  3. package/SECURITY.md +24 -0
  4. package/bin/causyne.js +13 -0
  5. package/docs/ARCHITECTURE.md +63 -0
  6. package/docs/PROVENANCE.md +29 -0
  7. package/docs/SECURITY_REVIEW.md +89 -0
  8. package/docs/THIRD_PARTY_NOTICES.md +49 -0
  9. package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
  10. package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
  11. package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
  12. package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
  13. package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
  14. package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
  15. package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
  16. package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
  17. package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
  18. package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
  19. package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
  20. package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
  21. package/native-skills/android/play-store-release/SKILL.md +43 -0
  22. package/native-skills/android/room-and-persistence/SKILL.md +40 -0
  23. package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
  24. package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
  25. package/native-skills/backend/caching-strategy/SKILL.md +44 -0
  26. package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
  27. package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
  28. package/native-skills/backend/rest-api-design/SKILL.md +40 -0
  29. package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
  30. package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
  31. package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
  32. package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
  33. package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
  34. package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
  35. package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
  36. package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
  37. package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
  38. package/native-skills/data/metric-definition/SKILL.md +42 -0
  39. package/native-skills/data/notebook-to-production/SKILL.md +43 -0
  40. package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
  41. package/native-skills/database/connection-pooling/SKILL.md +42 -0
  42. package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
  43. package/native-skills/database/query-optimisation/SKILL.md +38 -0
  44. package/native-skills/database/safe-migrations/SKILL.md +47 -0
  45. package/native-skills/database/schema-design/SKILL.md +38 -0
  46. package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
  47. package/native-skills/design/colour-system-construction/SKILL.md +38 -0
  48. package/native-skills/design/design-direction-brief/SKILL.md +41 -0
  49. package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
  50. package/native-skills/design/responsive-strategy/SKILL.md +39 -0
  51. package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
  52. package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
  53. package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
  54. package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
  55. package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
  56. package/native-skills/devops/incident-triage/SKILL.md +38 -0
  57. package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
  58. package/native-skills/devops/observability-baseline/SKILL.md +38 -0
  59. package/native-skills/devops/secret-management/SKILL.md +40 -0
  60. package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
  61. package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
  62. package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
  63. package/native-skills/git/branch-strategy/SKILL.md +39 -0
  64. package/native-skills/git/commit-hygiene/SKILL.md +41 -0
  65. package/native-skills/git/history-recovery/SKILL.md +44 -0
  66. package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
  67. package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
  68. package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
  69. package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
  70. package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
  71. package/native-skills/mobile/deep-linking/SKILL.md +39 -0
  72. package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
  73. package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
  74. package/native-skills/mobile/push-notifications/SKILL.md +38 -0
  75. package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
  76. package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
  77. package/native-skills/performance/load-testing/SKILL.md +42 -0
  78. package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
  79. package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
  80. package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
  81. package/native-skills/python/async-correctness/SKILL.md +40 -0
  82. package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
  83. package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
  84. package/native-skills/python/environment-isolation/SKILL.md +41 -0
  85. package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
  86. package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
  87. package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
  88. package/native-skills/security/authorization-model/SKILL.md +42 -0
  89. package/native-skills/security/crypto-usage/SKILL.md +48 -0
  90. package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
  91. package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
  92. package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
  93. package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
  94. package/native-skills/security/threat-modelling/SKILL.md +46 -0
  95. package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
  96. package/native-skills/testing/property-based-testing/SKILL.md +46 -0
  97. package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
  98. package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
  99. package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
  100. package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
  101. package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
  102. package/native-skills/web/accessible-components/SKILL.md +45 -0
  103. package/native-skills/web/api-client-resilience/SKILL.md +39 -0
  104. package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
  105. package/native-skills/web/core-web-vitals/SKILL.md +40 -0
  106. package/native-skills/web/css-layout-systems/SKILL.md +41 -0
  107. package/native-skills/web/form-validation-ux/SKILL.md +39 -0
  108. package/native-skills/web/react-state-architecture/SKILL.md +41 -0
  109. package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
  110. package/package.json +68 -4
  111. package/src/agentLoop.js +2458 -0
  112. package/src/appChannel.js +109 -0
  113. package/src/approvalGrants.js +66 -0
  114. package/src/auditLog.js +52 -0
  115. package/src/autoUpdate.js +12 -0
  116. package/src/capabilities.js +36 -0
  117. package/src/checkpoints.js +1199 -0
  118. package/src/cliMode.js +638 -0
  119. package/src/commands.js +805 -0
  120. package/src/compiledSkills.js +540 -0
  121. package/src/config.js +130 -0
  122. package/src/contextManager.js +498 -0
  123. package/src/cortex/autoCondenser.js +192 -0
  124. package/src/cortex/provisioner.js +27 -0
  125. package/src/cortex/vectorDb.js +447 -0
  126. package/src/designPalettes.js +99 -0
  127. package/src/devMode.js +6 -0
  128. package/src/embodimentSession.js +180 -0
  129. package/src/entrypoint.js +194 -0
  130. package/src/execSafe.js +21 -0
  131. package/src/gitSnapshot.js +378 -0
  132. package/src/mcp/jsonRpc.js +165 -0
  133. package/src/mcp/mcpClient.js +136 -0
  134. package/src/mcp/mcpRegistry.js +207 -0
  135. package/src/memory.js +62 -0
  136. package/src/memoryManager.js +124 -0
  137. package/src/modelCatalogue.js +132 -0
  138. package/src/modelIds.js +42 -0
  139. package/src/modelRouter.js +145 -0
  140. package/src/outputStore.js +246 -0
  141. package/src/perception.js +124 -0
  142. package/src/planCache.js +437 -0
  143. package/src/procSignal.js +32 -0
  144. package/src/providers/anthropic.js +408 -0
  145. package/src/providers/base.js +328 -0
  146. package/src/providers/dashscope-byok.js +86 -0
  147. package/src/providers/deepseek.js +80 -0
  148. package/src/providers/factory.js +37 -0
  149. package/src/providers/gemini.js +346 -0
  150. package/src/providers/moonshot.js +73 -0
  151. package/src/providers/muse.js +169 -0
  152. package/src/providers/ollama.js +104 -0
  153. package/src/providers/openai-compatible.js +84 -0
  154. package/src/providers/openai.js +20 -0
  155. package/src/providers/qwen-byok.js +76 -0
  156. package/src/providers/xai.js +73 -0
  157. package/src/provisioning/capabilityConsent.js +24 -0
  158. package/src/provisioning/capabilityProvisioner.js +271 -0
  159. package/src/provisioning/capabilityTools.js +29 -0
  160. package/src/reporting.js +373 -0
  161. package/src/sandbox.js +529 -0
  162. package/src/sessionStore.js +279 -0
  163. package/src/skillsManager.js +288 -0
  164. package/src/slopDetector.js +307 -0
  165. package/src/taskBuffer.js +45 -0
  166. package/src/taskShape.js +241 -0
  167. package/src/textDiff.js +150 -0
  168. package/src/tokenTracker.js +560 -0
  169. package/src/tool-scripts/ax_snapshot.applescript +39 -0
  170. package/src/tool-scripts/ax_snapshot.ps1 +10 -0
  171. package/src/tool-scripts/desktop_act.applescript +58 -0
  172. package/src/tool-scripts/resolve_automator.py +119 -0
  173. package/src/toolPacks.js +524 -0
  174. package/src/tools/archive.js +63 -0
  175. package/src/tools/astryxOracle.js +78 -0
  176. package/src/tools/axSnapshot.js +165 -0
  177. package/src/tools/blenderAutomator.js +349 -0
  178. package/src/tools/browserAutomator.js +252 -0
  179. package/src/tools/checkSystemSpecs.js +64 -0
  180. package/src/tools/clipboard.js +36 -0
  181. package/src/tools/codeSearchAst.js +101 -0
  182. package/src/tools/codeSearcher.js +230 -0
  183. package/src/tools/codeTools.js +262 -0
  184. package/src/tools/codebaseSearch.js +75 -0
  185. package/src/tools/createSkill.js +129 -0
  186. package/src/tools/deferredMcpSearch.js +246 -0
  187. package/src/tools/desktopAct.js +208 -0
  188. package/src/tools/desktopOpen.js +34 -0
  189. package/src/tools/dynamicLoader.js +44 -0
  190. package/src/tools/executeProceduralMedia.js +93 -0
  191. package/src/tools/fsDelete.js +51 -0
  192. package/src/tools/fsEdit.js +105 -0
  193. package/src/tools/fsFind.js +128 -0
  194. package/src/tools/fsList.js +77 -0
  195. package/src/tools/fsMove.js +57 -0
  196. package/src/tools/fsRead.js +87 -0
  197. package/src/tools/fsSearch.js +198 -0
  198. package/src/tools/fsWrite.js +41 -0
  199. package/src/tools/gameAiBehaviorTree.js +208 -0
  200. package/src/tools/gameAnimationRig.js +194 -0
  201. package/src/tools/gameAssetFetcher.js +372 -0
  202. package/src/tools/gameAssetHarvester.js +210 -0
  203. package/src/tools/gameAudioSynth.js +267 -0
  204. package/src/tools/gameBuilder.js +886 -0
  205. package/src/tools/gameCanvasAudit.js +101 -0
  206. package/src/tools/gameCinematicSequencer.js +151 -0
  207. package/src/tools/gameDataForge.js +124 -0
  208. package/src/tools/gameDialogueTreeGen.js +166 -0
  209. package/src/tools/gameEconomyBalancer.js +161 -0
  210. package/src/tools/gameEntitySpawner.js +207 -0
  211. package/src/tools/gameLoreForge.js +153 -0
  212. package/src/tools/gameQuestArcBuilder.js +152 -0
  213. package/src/tools/gameSceneBuilder.js +158 -0
  214. package/src/tools/gameShaderForge.js +181 -0
  215. package/src/tools/gameTilemapGenerator.js +164 -0
  216. package/src/tools/gameUiHudBuilder.js +292 -0
  217. package/src/tools/gameVfxEmitterGen.js +108 -0
  218. package/src/tools/gameWebRuntimeInspector.js +91 -0
  219. package/src/tools/generateMediaViaApi.js +358 -0
  220. package/src/tools/gitExec.js +54 -0
  221. package/src/tools/godotAutomator.js +437 -0
  222. package/src/tools/manageLocalAiDependencies.js +220 -0
  223. package/src/tools/memoryWrite.js +99 -0
  224. package/src/tools/multiEdit.js +107 -0
  225. package/src/tools/palmierAutomator.js +104 -0
  226. package/src/tools/pdfReader.js +112 -0
  227. package/src/tools/persistentShell.js +148 -0
  228. package/src/tools/printer.js +316 -0
  229. package/src/tools/procTerm.js +91 -0
  230. package/src/tools/processList.js +52 -0
  231. package/src/tools/readNativeSkill.js +39 -0
  232. package/src/tools/registry.js +277 -0
  233. package/src/tools/resolveAutomator.js +102 -0
  234. package/src/tools/screenshot.js +75 -0
  235. package/src/tools/subagentOrchestrator.js +305 -0
  236. package/src/tools/sysRun.js +125 -0
  237. package/src/tools/todoWrite.js +75 -0
  238. package/src/tools/toolDispatch.js +127 -0
  239. package/src/tools/toolForge.js +124 -0
  240. package/src/tools/updatePlan.js +88 -0
  241. package/src/tools/viewImage.js +82 -0
  242. package/src/tools/visionInspector.js +154 -0
  243. package/src/tools/webFetch.js +74 -0
  244. package/src/tools/webSearch.js +82 -0
  245. package/src/tui/ask.js +164 -0
  246. package/src/tui/banner.js +158 -0
  247. package/src/tui/block.js +152 -0
  248. package/src/tui/chatRenderer.js +953 -0
  249. package/src/tui/completion.js +424 -0
  250. package/src/tui/dialogs.js +606 -0
  251. package/src/tui/diff.js +296 -0
  252. package/src/tui/history.js +135 -0
  253. package/src/tui/home.js +217 -0
  254. package/src/tui/invocation.js +105 -0
  255. package/src/tui/keys.js +443 -0
  256. package/src/tui/lineEditor.js +256 -0
  257. package/src/tui/overlay.js +256 -0
  258. package/src/tui/prompt.js +1258 -0
  259. package/src/tui/selectList.js +335 -0
  260. package/src/tui/stage.js +330 -0
  261. package/src/tui/stageSurface.js +609 -0
  262. package/src/tui/statusLine.js +254 -0
  263. package/src/tui/text.js +345 -0
  264. package/src/tui/theme.js +393 -0
  265. package/src/tui/toolDisplay.js +176 -0
  266. package/src/tui/transcript.js +235 -0
  267. package/src/tui/viewport.js +337 -0
  268. package/src/uncertainty.js +271 -0
  269. package/src/userSkills.js +265 -0
  270. package/src/utils/cli_ui.js +0 -0
  271. package/src/utils/inquirer.js +12 -0
  272. package/src/utils/paths.js +37 -0
  273. package/src/version.js +38 -0
  274. package/src/vfeTracker.js +220 -0
@@ -0,0 +1,305 @@
1
+ import { TokenTracker } from '../tokenTracker.js';
2
+
3
+ // Subagent delegation.
4
+ //
5
+ // WHAT THIS USED TO DO (finding D-7)
6
+ //
7
+ // The previous implementation imported `fork` and `Worker` and used neither.
8
+ // It waited on a `setTimeout`, then returned:
9
+ //
10
+ // "Subagent [X] successfully executed task: "..." within scope [...].
11
+ // Zero errors detected."
12
+ //
13
+ // for work that had never happened. It pushed a "✅ Subagent Completed" push
14
+
15
+ // the tokens it pretended to spend. Worst of all, that sentence went straight
16
+ // into the parent model's context, so the model reasoned onward from a lie —
17
+ // and the schema described the whole thing as "surpasses standard CLI
18
+ // single-loop agents".
19
+ //
20
+ // A tool that reports success it did not earn is worse than a tool that does
21
+ // not exist: a missing tool fails loudly and the agent routes around it, while
22
+ // a lying tool corrupts every conclusion drawn after it.
23
+ //
24
+ // WHAT IT DOES NOW
25
+ //
26
+ // It runs a real AgentLoop. Each subagent gets its own message history, its own
27
+ // step ceiling, its own token accounting, and a tool scope that is actually
28
+ // enforced by the loop (agentLoop.js filters the catalogue against
29
+
30
+ // transport, tagged with its id. `delegate_and_wait` returns the child's real
31
+ // final message. When a subagent fails, this tool says so.
32
+ //
33
+ // The loops run in-process on purpose. Delegation and real concurrency come
34
+ // from several loops awaiting network I/O at once; forking a child process of
35
+ // our own binary would add a process-spawner pattern to the artefact for no
36
+ // capability we do not already have.
37
+
38
+ const activeSubagents = new Map();
39
+ const MAX_CONCURRENT_SUBAGENTS = 4;
40
+
41
+ // A subagent may never delegate again. The depth argument documents the rule;
42
+ // withholding the tool from the child's scope is what enforces it.
43
+ const SUBAGENT_TOOL_NAME = 'agent.subagent_orchestrate';
44
+ const DEFAULT_TOOL_SCOPE = ['fs_read', 'fs_find', 'fs_search', 'fs_list', 'code_search_ast'];
45
+
46
+
47
+ function childTransport(parentLoop, subagentId, name) {
48
+ return {
49
+ get readyState() {
50
+ return parentLoop && parentLoop.eventSink ? parentLoop.eventSink.readyState : 3;
51
+ },
52
+ send(raw) {
53
+ if (!parentLoop || typeof parentLoop.emitEvent !== 'function') return;
54
+ let frame;
55
+ try { frame = JSON.parse(raw); } catch (e) { return; }
56
+ parentLoop.emitEvent({ ...frame, subagentId, subagentName: name });
57
+ }
58
+ };
59
+ }
60
+
61
+ async function executeSubagent(record, parentLoop) {
62
+ // Imported lazily: agentLoop.js reaches this module through the tool
63
+ // registry, so a static import would close a cycle.
64
+ const { AgentLoop } = await import('../agentLoop.js');
65
+
66
+ const transport = childTransport(parentLoop, record.subagentId, record.name);
67
+ const child = new AgentLoop(transport, record.subagentId, record.prompt);
68
+
69
+ // A delegated task is a bounded errand, not a second full session.
70
+ child.maxSteps = Math.min(child.maxSteps || 25, record.maxSteps);
71
+ child.maxCircuitBreaker = child.maxSteps;
72
+ child.messages = [];
73
+ child.toolScope = new Set(record.toolScope.filter(n => n !== SUBAGENT_TOOL_NAME));
74
+ child.currentTaskPrompt = record.prompt;
75
+ // Depth is carried so the child's own guard can refuse, even if a future
76
+ // change ever puts the delegation tool back in scope.
77
+ child.subagentDepth = record.depth;
78
+
79
+ record.loop = child;
80
+ record.startedAtMs = Date.now();
81
+
82
+ await child.run();
83
+
84
+ const finalAssistant = [...child.messages].reverse()
85
+ .find(m => m.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
86
+ const toolCalls = child.messages.filter(m => m.role === 'tool');
87
+ const failures = toolCalls.filter(m => {
88
+ try { return JSON.parse(m.content).success === false; } catch (e) { return false; }
89
+ });
90
+
91
+ return {
92
+ subagentId: record.subagentId,
93
+ name: record.name,
94
+ stepsCompleted: child.stepsCompleted,
95
+ toolCallsMade: toolCalls.length,
96
+ toolFailures: failures.length,
97
+ elapsedMs: Date.now() - record.startedAtMs,
98
+ // The child's own words, not a template. If it produced nothing, say that
99
+ // rather than inventing a summary.
100
+ summary: finalAssistant ? finalAssistant.content : null,
101
+ stoppedEarly: child.stepsCompleted >= child.maxSteps
102
+ };
103
+ }
104
+
105
+ export const subagentOrchestrator = {
106
+ schema: {
107
+ name: SUBAGENT_TOOL_NAME,
108
+ description: 'Delegate a bounded sub-task to an independent agent loop with its own context, step budget and restricted tool scope. Use it to parallelise genuinely independent work (research several files, audit separate modules). The subagent runs a real agent loop and returns its actual result; it cannot delegate further.',
109
+ parameters: {
110
+ type: 'object',
111
+ properties: {
112
+ action: {
113
+ type: 'string',
114
+ enum: ['spawn', 'list', 'kill', 'delegate_and_wait'],
115
+ description: 'spawn (start in the background and return immediately), list (show active subagents), kill (stop one), delegate_and_wait (run it and return the result).'
116
+ },
117
+ name: { type: 'string', description: 'A short name for the subagent, e.g. Audit_Worker.' },
118
+ prompt: { type: 'string', description: 'The complete instruction for the subagent. It has no memory of this conversation, so it must be self-contained.' },
119
+ subagentId: { type: 'string', description: 'Target subagent for the kill action.' },
120
+ toolScope: { type: 'array', items: { type: 'string' }, description: 'Catalogue tool names the subagent may use. Defaults to read-only inspection tools. It can never receive the delegation tool itself.' },
121
+ maxSteps: { type: 'integer', description: 'Step ceiling for the subagent. Defaults to 25.' },
122
+ maxTokens: { type: 'integer', description: 'Advisory token budget for this subagent. Defaults to 50000.' },
123
+ depth: { type: 'integer', description: 'Hierarchy depth. 0 = master, 1 = worker. Subagents cannot spawn subagents.' }
124
+ },
125
+ required: ['action']
126
+ }
127
+ },
128
+
129
+ async execute(args, context = {}) {
130
+ try {
131
+ const action = args.action;
132
+ const parentLoop = context.parentLoop || null;
133
+
134
+ if (action === 'list') {
135
+ const list = [...activeSubagents.values()].map(worker => ({
136
+ subagentId: worker.subagentId,
137
+ name: worker.name,
138
+ status: worker.status,
139
+ depth: worker.depth,
140
+ toolScope: worker.toolScope,
141
+ startTime: worker.startTime,
142
+ stepsCompleted: worker.loop ? worker.loop.stepsCompleted : 0,
143
+ summary: worker.summary,
144
+ error: worker.error
145
+ }));
146
+ return {
147
+ success: true,
148
+ output: JSON.stringify({
149
+ active_workers: list.filter(w => w.status === 'running').length,
150
+ max_concurrent_allowed: MAX_CONCURRENT_SUBAGENTS,
151
+ workers: list
152
+ }, null, 2)
153
+ };
154
+ }
155
+
156
+ if (action === 'kill') {
157
+ const id = args.subagentId || args.name;
158
+ if (!id) return { success: false, error: 'subagentId or name is required to stop a subagent.' };
159
+ for (const [key, worker] of activeSubagents.entries()) {
160
+ if (key === id || worker.name === id) {
161
+ // Safe now that run() rearms the controller (Phase 2): aborting one
162
+ // loop no longer poisons any other.
163
+ try { if (worker.loop) worker.loop.abort(); } catch (e) {}
164
+ worker.status = 'killed';
165
+ activeSubagents.delete(key);
166
+ return { success: true, output: `Subagent ${key} (${worker.name}) stopped.` };
167
+ }
168
+ }
169
+ return { success: false, error: `Subagent ${id} not found.` };
170
+ }
171
+
172
+ if (action !== 'spawn' && action !== 'delegate_and_wait') {
173
+ return { success: false, error: `Unknown action: ${action}` };
174
+ }
175
+
176
+ const prompt = args.prompt;
177
+ if (!prompt) return { success: false, error: 'A prompt is required to delegate a task.' };
178
+
179
+ const depth = args.depth !== undefined ? args.depth : 1;
180
+ if (depth > 1) {
181
+ return {
182
+ success: false,
183
+ error: `Subagent hierarchy depth violation: depth ${depth} exceeds the 2-tier boundary (master -> worker). Subagents cannot delegate further.`
184
+ };
185
+ }
186
+ if (parentLoop && parentLoop.subagentDepth !== undefined) {
187
+ return { success: false, error: 'A subagent cannot delegate to another subagent.' };
188
+ }
189
+
190
+ const running = [...activeSubagents.values()].filter(w => w.status === 'running').length;
191
+ if (running >= MAX_CONCURRENT_SUBAGENTS) {
192
+ return {
193
+ success: false,
194
+ error: `Subagent concurrency limit reached (${MAX_CONCURRENT_SUBAGENTS} running). Stop one, or wait for it to finish.`
195
+ };
196
+ }
197
+
198
+ const name = args.name || `Worker_${Math.random().toString(36).slice(2, 7)}`;
199
+ const subagentId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
200
+ const requestedScope = Array.isArray(args.toolScope) && args.toolScope.length > 0
201
+ ? args.toolScope
202
+ : DEFAULT_TOOL_SCOPE;
203
+
204
+ // Reject an unknown tool name instead of silently accepting it. The old
205
+ // implementation accepted anything, which is easy to do when the scope is
206
+ // never used for anything.
207
+ const { TOOL_DEFINITIONS } = await import('./registry.js');
208
+ const catalogue = new Set(TOOL_DEFINITIONS.map(t => t.name));
209
+ const unknown = requestedScope.filter(n => !catalogue.has(n) && n !== SUBAGENT_TOOL_NAME);
210
+ if (unknown.length > 0) {
211
+ return { success: false, error: `Unknown tools in toolScope: ${unknown.join(', ')}.` };
212
+ }
213
+
214
+ const record = {
215
+ subagentId,
216
+ name,
217
+ prompt,
218
+ toolScope: requestedScope,
219
+ depth,
220
+ maxSteps: args.maxSteps || 25,
221
+ maxTokens: args.maxTokens || 50000,
222
+ status: 'running',
223
+ startTime: new Date().toISOString(),
224
+ summary: null,
225
+ error: null,
226
+ loop: null
227
+ };
228
+
229
+ // Budget pre-check against the real tracker, before any work starts.
230
+ const tracker = new TokenTracker();
231
+ const estimated = Math.ceil(prompt.length / 4) + 1500;
232
+ const check = await tracker.trackUsage(estimated, 0);
233
+ if (!check.allowed) {
234
+ return {
235
+ success: false,
236
+ error: `Token budget block: ${check.reason || 'the budget threshold would be exceeded by this subagent.'}`
237
+ };
238
+ }
239
+
240
+ activeSubagents.set(subagentId, record);
241
+
242
+ const work = executeSubagent(record, parentLoop)
243
+ .then(result => {
244
+ record.status = 'completed';
245
+ record.summary = result.summary;
246
+ return result;
247
+ })
248
+ .catch(error => {
249
+ record.status = 'failed';
250
+ record.error = error.message;
251
+ throw error;
252
+ });
253
+
254
+ if (action === 'delegate_and_wait') {
255
+ try {
256
+ const result = await work;
257
+ activeSubagents.delete(subagentId);
258
+ // No summary means the subagent produced nothing. Report that
259
+ // plainly — it is a real outcome, and pretending otherwise is the
260
+ // exact defect this rewrite exists to remove.
261
+ if (!result.summary) {
262
+ return {
263
+ success: false,
264
+ error: `Subagent [${name}] finished without producing a result after ${result.stepsCompleted} steps.`,
265
+ output: JSON.stringify(result, null, 2)
266
+ };
267
+ }
268
+ return { success: true, output: JSON.stringify(result, null, 2) };
269
+ } catch (error) {
270
+ activeSubagents.delete(subagentId);
271
+ return { success: false, error: `Subagent [${name}] failed: ${error.message}` };
272
+ }
273
+ }
274
+
275
+ // Completion stays on the parent's local event stream.
276
+ work
277
+ .then(result => {
278
+ parentLoop.emitEvent({
279
+ type: 'notice', subagentId, subagentName: name,
280
+ message: `Subagent [${name}] finished in ${result.stepsCompleted} steps.`
281
+ });
282
+ })
283
+ .catch(error => {
284
+ parentLoop.emitEvent({
285
+ type: 'notice', tone: 'error', subagentId, subagentName: name,
286
+ message: `Subagent [${name}] failed: ${error.message}`
287
+ });
288
+ });
289
+
290
+ return {
291
+ success: true,
292
+ output: JSON.stringify({
293
+ subagentId,
294
+ name,
295
+ status: 'running',
296
+ toolScope: requestedScope,
297
+ maxSteps: record.maxSteps,
298
+ message: `Subagent [${name}] is running. Poll it with action "list"; the result appears there when it finishes.`
299
+ }, null, 2)
300
+ };
301
+ } catch (e) {
302
+ return { success: false, error: e.message };
303
+ }
304
+ }
305
+ };
@@ -0,0 +1,125 @@
1
+ import { spawn } from 'child_process';
2
+ import os from 'os';
3
+
4
+ // Phase 1 AGI Upgrade: ANSI Stripper
5
+ function stripAnsi(string) {
6
+ const pattern = [
7
+ '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
8
+ '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))'
9
+ ].join('|');
10
+ return string.replace(new RegExp(pattern, 'g'), '');
11
+ }
12
+
13
+ export const sysRun = {
14
+ schema: {
15
+ name: 'sysRun',
16
+ description: 'Execute a shell command. CRITICAL CONSTRAINTS: 1. NEVER use interactive flags (like git rebase -i). 2. Always use non-interactive flags (--yes, -y). 3. If running git commit, always run git status, git diff, and git log first.',
17
+ parameters: {
18
+ type: 'object',
19
+ properties: {
20
+ command: { type: 'string', description: 'The shell command to run' },
21
+ cwd: { type: 'string', description: 'Working directory' },
22
+ timeout: { type: 'number', description: 'Timeout in ms (default 60000)' }
23
+ },
24
+ required: ['command'],
25
+ additionalProperties: false
26
+ }
27
+ },
28
+ async execute(args, context = {}) {
29
+ const cwd = args.cwd ? args.cwd.replace(/^~\//, os.homedir() + '/') : process.cwd();
30
+ const timeoutMs = args.timeout || 60000;
31
+ const signal = context.signal;
32
+
33
+ return new Promise((resolve) => {
34
+ // Phase 1 AGI Upgrade: Spawn instead of Exec
35
+ const shell = os.platform() === 'win32' ? 'cmd.exe' : '/bin/bash';
36
+ const shellArgs = os.platform() === 'win32' ? ['/c', args.command] : ['-c', args.command];
37
+
38
+ const child = spawn(shell, shellArgs, { cwd, signal });
39
+
40
+ // Phase 1 AGI Upgrade: 50KB Ring Buffer (FIFO)
41
+ const MAX_LENGTH = 50000;
42
+ let outputBuffer = '';
43
+
44
+ const appendToBuffer = (data) => {
45
+ outputBuffer += data.toString();
46
+ if (outputBuffer.length > MAX_LENGTH) {
47
+ // Truncate from the start (keep the most recent 50KB)
48
+ outputBuffer = '...[OUTPUT TRUNCATED]...\n' + outputBuffer.slice(outputBuffer.length - MAX_LENGTH);
49
+ }
50
+ };
51
+
52
+ child.stdout.on('data', appendToBuffer);
53
+ child.stderr.on('data', appendToBuffer);
54
+
55
+ let timeoutId;
56
+ if (timeoutMs > 0) {
57
+ timeoutId = setTimeout(() => {
58
+ child.kill('SIGKILL');
59
+ resolve({ success: false, output: stripAnsi(outputBuffer), error: `Process timed out after ${timeoutMs}ms`, exitCode: 124 });
60
+ }, timeoutMs);
61
+ }
62
+
63
+ child.on('error', (err) => {
64
+ if (timeoutId) clearTimeout(timeoutId);
65
+ resolve({ success: false, output: stripAnsi(outputBuffer), error: err.message, exitCode: 1 });
66
+ });
67
+
68
+ child.on('close', (code) => {
69
+ if (timeoutId) clearTimeout(timeoutId);
70
+ const cleanOutput = stripAnsi(outputBuffer);
71
+
72
+ if (code === 0) {
73
+ resolve({ success: true, output: cleanOutput, exitCode: 0 });
74
+ } else {
75
+ if (signal && signal.aborted) {
76
+ resolve({ success: false, output: cleanOutput, error: 'Process killed by user.', exitCode: 1 });
77
+ } else {
78
+ resolve({ success: false, output: cleanOutput, error: `Command exited with code ${code}`, exitCode: code });
79
+ }
80
+ }
81
+ });
82
+ });
83
+ }
84
+ };
85
+
86
+ export const sysRunBg = {
87
+ schema: {
88
+ name: 'sysRunBg',
89
+ description: 'Start a long-running process in background (dev servers, watchers)',
90
+ parameters: {
91
+ type: 'object',
92
+ properties: {
93
+ command: { type: 'string' },
94
+ cwd: { type: 'string' },
95
+ label: { type: 'string' },
96
+ maxDuration: { type: 'number' }
97
+ },
98
+ required: ['command', 'label']
99
+ }
100
+ },
101
+ async execute(args) {
102
+ // Phase 1 AGI Upgrade: Real Detached Process Implementation
103
+ try {
104
+ const cwd = args.cwd ? args.cwd.replace(/^~\//, os.homedir() + '/') : process.cwd();
105
+ const shell = os.platform() === 'win32' ? 'cmd.exe' : '/bin/bash';
106
+ const shellArgs = os.platform() === 'win32' ? ['/c', args.command] : ['-c', args.command];
107
+
108
+ const child = spawn(shell, shellArgs, {
109
+ cwd,
110
+ detached: true,
111
+ stdio: 'ignore' // We don't track output for background tasks currently
112
+ });
113
+
114
+ child.unref(); // Allow Node to exit even if this child is running
115
+
116
+ return {
117
+ success: true,
118
+ output: `Successfully started background task [${args.label}]: ${args.command}`,
119
+ pid: child.pid
120
+ };
121
+ } catch (e) {
122
+ return { success: false, error: e.message };
123
+ }
124
+ }
125
+ };
@@ -0,0 +1,75 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ export const todoWrite = {
5
+ schema: {
6
+ name: 'TodoWrite',
7
+ description: 'Dynamic Context Injector: Create and manage structured tasks. Forces atomic, granular steps instead of vague goals.',
8
+ parameters: {
9
+ type: 'object',
10
+ properties: {
11
+ todos: {
12
+ type: 'array',
13
+ description: 'The updated todo list (JSON state)',
14
+ items: {
15
+ type: 'object',
16
+ properties: {
17
+ id: { type: 'string' },
18
+ content: { type: 'string', minLength: 5, description: 'Must be an atomic, actionable step.' },
19
+ status: {
20
+ type: 'string',
21
+ enum: ['pending', 'in_progress', 'completed']
22
+ }
23
+ },
24
+ required: ['id', 'content', 'status'],
25
+ additionalProperties: false
26
+ }
27
+ }
28
+ },
29
+ required: ['todos'],
30
+ additionalProperties: false
31
+ }
32
+ },
33
+ async execute(args) {
34
+ try {
35
+ const todoPath = path.join(process.cwd(), '.causyne_todos.json');
36
+ const mdPath = path.join(process.cwd(), 'AGI_PLAN.md');
37
+
38
+ // Phase 2 AGI Upgrade: Atomic Validation
39
+ for (const task of args.todos) {
40
+ const words = task.content.split(' ').length;
41
+ if (words < 2) {
42
+ return { success: false, error: `Task [${task.id}] is too vague ("${task.content}"). Rewrite it into an atomic, actionable step (e.g., "Scaffold React Navbar").` };
43
+ }
44
+ }
45
+
46
+ // Save JSON state
47
+ await fs.writeFile(todoPath, JSON.stringify(args.todos, null, 2), 'utf8');
48
+
49
+ // Render to MD for user visibility
50
+ let mdContent = '# AGI Execution Plan\n\n';
51
+ for (const t of args.todos) {
52
+ let box = '[ ]';
53
+ if (t.status === 'in_progress') box = '[/]';
54
+ if (t.status === 'completed') box = '[x]';
55
+ mdContent += `- ${box} ${t.id}: ${t.content}\n`;
56
+ }
57
+ await fs.writeFile(mdPath, mdContent, 'utf8');
58
+
59
+ // Phase 2 AGI Upgrade: Memory Hook (Signaling back to AgentLoop context window)
60
+ const inProgress = args.todos.filter(t => t.status === 'in_progress');
61
+ const completed = args.todos.filter(t => t.status === 'completed');
62
+
63
+ if (inProgress.length > 1) {
64
+ return { success: true, output: `[Memory Hook] Saved. WARNING: You have ${inProgress.length} tasks in_progress. Only ONE task may be in progress at a time.` };
65
+ }
66
+
67
+ return {
68
+ success: true,
69
+ output: `[Memory Hook] Tasks synchronized. ${completed.length} completed, ${inProgress.length} active. Focus locked on: ${inProgress[0]?.content || 'None'}`
70
+ };
71
+ } catch (e) {
72
+ return { success: false, error: e.message };
73
+ }
74
+ }
75
+ };
@@ -0,0 +1,127 @@
1
+ // The tool-dispatch discipline: permission → pre-hook → execute → post-hook → result.
2
+ //
3
+ // Ported from the reference agent loop (claw-code rust/crates/runtime/src/
4
+ // conversation.rs:201-254). The single rule that matters: a denial or a failure
5
+ // is NEVER thrown out of the loop — it comes back as a tool result with an error
6
+ // flag, so the model sees it and self-corrects. Only a fatal/auth/transport error
7
+ // (handled by the caller) ends the turn.
8
+ //
9
+ // This is pure logic with no I/O of its own: the caller injects `authorize`,
10
+ // `preHook`, `postHook` and `execute`. That is what makes the agent loop's tool
11
+ // handling testable for the first time — a scripted set of these four functions
12
+ // exercises deny → result, execute → result, throw → recovered-not-fatal without
13
+ // a live model or a real filesystem.
14
+ //
15
+ // Pure Node. No dependency, no shell.
16
+
17
+ /**
18
+ * A tool-result message in the shape the providers expect, matching what
19
+ * agentLoop.js already pushes: { role:'tool', tool_call_id, name, content }
20
+ * where content is a JSON string carrying at least { success }.
21
+ */
22
+ export function toolResultMessage(toolCallId, name, payload) {
23
+ return {
24
+ role: 'tool',
25
+ tool_call_id: toolCallId,
26
+ name,
27
+ content: JSON.stringify(payload)
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Fold hook feedback lines into a result payload.
33
+ *
34
+ * Hook stdout is model-visible feedback, not a log line (the reference appends it
35
+ * to the tool output). `denied` marks it as the reason a call was refused.
36
+ */
37
+ export function mergeHookFeedback(payload, messages, denied = false) {
38
+ if (!messages || messages.length === 0) return payload;
39
+ const label = denied ? 'Hook feedback (denied)' : 'Hook feedback';
40
+ const note = `${label}:\n${messages.join('\n')}`;
41
+ const existing = typeof payload.hookFeedback === 'string' ? payload.hookFeedback + '\n' : '';
42
+ return { ...payload, hookFeedback: existing + note };
43
+ }
44
+
45
+ const ALLOW = { denied: false, messages: [] };
46
+
47
+ /**
48
+ * Run one tool call through the discipline and return a result message.
49
+ *
50
+ * Injected functions (all optional except execute):
51
+ * authorize(name, args) -> { allow:true } | { allow:false, reason }
52
+ * preHook(name, args) -> { denied, messages } (Stage 3 fills this in)
53
+ * execute(name, args) -> any payload, or throws
54
+ * postHook(name, args, payload, isError) -> { denied, messages }
55
+ *
56
+ * Returns { message, denied, isError }. Never throws for a tool-level failure —
57
+ * a thrown execute() becomes an is_error result. The caller decides, from the
58
+ * result, whether to break (it never should for a tool error) or continue.
59
+ */
60
+ export async function dispatchToolCall({
61
+ toolCallId,
62
+ name,
63
+ args = {},
64
+ authorize = null,
65
+ preHook = null,
66
+ postHook = null,
67
+ execute
68
+ }) {
69
+ // 1. Permission. A denial is a result, not an exception.
70
+ if (authorize) {
71
+ const decision = authorize(name, args) || {};
72
+ if (decision.allow === false) {
73
+ return {
74
+ denied: true,
75
+ isError: true,
76
+ message: toolResultMessage(toolCallId, name, {
77
+ success: false,
78
+ error: decision.reason || `Tool "${name}" was not permitted.`
79
+ })
80
+ };
81
+ }
82
+ }
83
+
84
+ // 2. PreToolUse hook. Denial short-circuits before execute.
85
+ const pre = (preHook ? await preHook(name, args) : ALLOW) || ALLOW;
86
+ if (pre.denied) {
87
+ return {
88
+ denied: true,
89
+ isError: true,
90
+ message: toolResultMessage(toolCallId, name, mergeHookFeedback(
91
+ { success: false, error: `PreToolUse hook denied tool "${name}".` },
92
+ pre.messages,
93
+ true
94
+ ))
95
+ };
96
+ }
97
+
98
+ // 3. Execute. A throw becomes an is_error result — never propagated.
99
+ let payload;
100
+ let isError = false;
101
+ try {
102
+ payload = await execute(name, args);
103
+ if (payload == null) payload = { success: true };
104
+ if (typeof payload !== 'object') payload = { success: true, output: payload };
105
+ if (payload.success === undefined) payload.success = true;
106
+ } catch (error) {
107
+ isError = true;
108
+ payload = { success: false, error: error && error.message ? error.message : String(error) };
109
+ }
110
+
111
+ // Pre-hook feedback rides along even on a successful call.
112
+ payload = mergeHookFeedback(payload, pre.messages, false);
113
+
114
+ // 4. PostToolUse hook. Can flip a success to an error and add feedback.
115
+ const post = (postHook ? await postHook(name, args, payload, isError) : ALLOW) || ALLOW;
116
+ if (post.denied) {
117
+ isError = true;
118
+ payload = { ...payload, success: false };
119
+ }
120
+ payload = mergeHookFeedback(payload, post.messages, post.denied);
121
+
122
+ return {
123
+ denied: false,
124
+ isError,
125
+ message: toolResultMessage(toolCallId, name, payload)
126
+ };
127
+ }