@causyne/agent 0.0.0-stage → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,99 @@
1
+ // Curated design data — the alternative to slop, not just the ban on it.
2
+ //
3
+ // The anti-slop directive in toolPacks.js has always told the model what NOT to
4
+ // do. Telling a model "no purple-to-blue gradients" without handing it something
5
+ // concrete is how you get the second-most-common gradient instead. This module
6
+ // is the positive half: real palettes and font pairings anchored to actual
7
+ // design movements, so "pick a movement and commit" has something to commit to.
8
+ //
9
+ // Plain data. No shell, no dependency, no eval.
10
+
11
+ /**
12
+ * Palettes are stored as HSL triples because the playbook asks for HSL, and
13
+ * because hue distance is what the detector reasons about.
14
+ */
15
+ export const DESIGN_MOVEMENTS = [
16
+ {
17
+ id: 'swiss',
18
+ name: 'Swiss / International Style',
19
+ principles: 'Grid discipline, generous whitespace, flush-left ragged-right type, one accent only.',
20
+ palette: { background: '#FFFFFF', surface: '#F2F2F2', ink: '#111111', accent: '#D40000', muted: '#767676' },
21
+ fonts: { display: 'Helvetica Now, Inter, sans-serif', body: 'Inter, Helvetica, sans-serif' }
22
+ },
23
+ {
24
+ id: 'brutalist',
25
+ name: 'Brutalism',
26
+ principles: 'Raw structure, visible borders, system type, high contrast, no rounded corners, no shadows.',
27
+ palette: { background: '#E8E8E3', surface: '#FFFFFF', ink: '#000000', accent: '#0000EE', muted: '#5A5A55' },
28
+ fonts: { display: 'Archivo Black, Impact, sans-serif', body: 'Courier New, monospace' }
29
+ },
30
+ {
31
+ id: 'editorial',
32
+ name: 'Editorial / Magazine',
33
+ principles: 'Serif display at large sizes, multi-column rhythm, pull quotes, asymmetric layout.',
34
+ palette: { background: '#FBF9F4', surface: '#FFFFFF', ink: '#1A1614', accent: '#8C2F1E', muted: '#6B625B' },
35
+ fonts: { display: 'Playfair Display, Georgia, serif', body: 'Source Serif Pro, Georgia, serif' }
36
+ },
37
+ {
38
+ id: 'japanese-minimal',
39
+ name: 'Japanese Minimalism',
40
+ principles: 'Ma (negative space) as the subject, muted naturals, hairline rules, restrained scale.',
41
+ palette: { background: '#F7F5F0', surface: '#FFFFFF', ink: '#2B2B2B', accent: '#7B6C54', muted: '#9A9287' },
42
+ fonts: { display: 'Noto Serif JP, Georgia, serif', body: 'Noto Sans JP, Inter, sans-serif' }
43
+ },
44
+ {
45
+ id: 'bauhaus',
46
+ name: 'Bauhaus',
47
+ principles: 'Primary triad, geometric primitives, diagonal energy, function-first composition.',
48
+ palette: { background: '#F4F1EA', surface: '#FFFFFF', ink: '#1C1C1C', accent: '#D62828', muted: '#003049' },
49
+ fonts: { display: 'Futura, Poppins, sans-serif', body: 'Poppins, Inter, sans-serif' }
50
+ },
51
+ {
52
+ id: 'terminal',
53
+ name: 'Terminal / Retro-Computing',
54
+ principles: 'Monospace throughout, phosphor accent on near-black, scanline restraint, ASCII rules.',
55
+ palette: { background: '#0C0F0A', surface: '#131A11', ink: '#D6E5C8', accent: '#7FFF3F', muted: '#5C6B52' },
56
+ fonts: { display: 'JetBrains Mono, monospace', body: 'IBM Plex Mono, monospace' }
57
+ },
58
+ {
59
+ id: 'art-deco',
60
+ name: 'Art Deco',
61
+ principles: 'Symmetry, metallic accent on deep ground, stepped geometry, high-contrast display type.',
62
+ palette: { background: '#10161F', surface: '#18202C', ink: '#EDE3D0', accent: '#C9A227', muted: '#6E7885' },
63
+ fonts: { display: 'Poiret One, Didot, serif', body: 'Jost, Inter, sans-serif' }
64
+ },
65
+ {
66
+ id: 'scandinavian',
67
+ name: 'Scandinavian Minimal',
68
+ principles: 'Warm off-white ground, pale wood neutrals, single desaturated accent, soft rounded geometry.',
69
+ palette: { background: '#FAF8F5', surface: '#FFFFFF', ink: '#2E2A26', accent: '#6A8CAF', muted: '#A39C93' },
70
+ fonts: { display: 'Outfit, Inter, sans-serif', body: 'Inter, system-ui, sans-serif' }
71
+ }
72
+ ];
73
+
74
+ /** A movement by id, or null. */
75
+ export function getMovement(id) {
76
+ return DESIGN_MOVEMENTS.find(movement => movement.id === id) || null;
77
+ }
78
+
79
+ /**
80
+ * Suggest movements the agent has NOT recently used.
81
+ *
82
+ * The creativePattern memory records what the last projects looked like; the
83
+ * whole point of recording it is to deliberately diverge next time rather than
84
+ * drifting back into one house style.
85
+ */
86
+ export function suggestMovements(recentlyUsedIds = [], count = 3) {
87
+ const used = new Set(recentlyUsedIds);
88
+ const unused = DESIGN_MOVEMENTS.filter(movement => !used.has(movement.id));
89
+ const pool = unused.length >= count ? unused : DESIGN_MOVEMENTS;
90
+ return pool.slice(0, count);
91
+ }
92
+
93
+ /** A compact briefing the agent can be handed when its output scores as slop. */
94
+ export function formatMovementBrief(movements) {
95
+ return movements.map(movement => {
96
+ const palette = Object.entries(movement.palette).map(([role, hex]) => `${role} ${hex}`).join(', ');
97
+ return `- ${movement.name}: ${movement.principles}\n palette: ${palette}\n type: ${movement.fonts.display} / ${movement.fonts.body}`;
98
+ }).join('\n');
99
+ }
package/src/devMode.js ADDED
@@ -0,0 +1,6 @@
1
+ // The former Android entitlement emulator is retired. Keep this internal
2
+ // predicate while callers migrate to explicit local runtime budgets. Neither
3
+ // environment variables nor a hidden developer secret can bypass approvals.
4
+ export function isDevEmulator() {
5
+ return false;
6
+ }
@@ -0,0 +1,180 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { causyneHome } from './utils/paths.js';
5
+
6
+ // C5 — the resumable embodiment session.
7
+ //
8
+ // Astra's other headline, next to computer use, is STAYING ON THE JOB. Causyne
9
+ // already persists more session state than most agents ship (checkpoints.js,
10
+ // the V5 Cortex, the spill store). What none of them carried is the one thing a
11
+ // GUI task cannot rebuild from the transcript: WHICH APPLICATION THE AGENT WAS
12
+ // DRIVING AND WHAT IT LAST SAW ON SCREEN.
13
+ //
14
+ // Why that specifically. A file edit is recoverable from disk — the file is
15
+
16
+ // something into "Notes" only if it re-reads the screen, and re-reading costs a
17
+ // full Channel B pass on a window that may since have moved, closed, or been
18
+ // replaced. Worse, without the state it does not know whether its last action
19
+ // LANDED. Repeating an action that already succeeded is how a GUI agent sends
20
+ // the same message twice.
21
+ //
22
+ // FAIL-OPEN BY CONTRACT. Every function here swallows its own errors and
23
+ // returns a safe default. Losing session state must degrade the agent to
24
+ // "re-read the screen", never break the task — the same contract outputStore.js
25
+ // holds, and for the same reason.
26
+ //
27
+ // WHAT IS DELIBERATELY NOT STORED: no screenshots, no typed text, no element
28
+ // values. A record of what a user's screen said, sitting in a file, is a
29
+ // surveillance artefact. Only the app name, the action verbs, and a HASH of the
30
+ // last tree are kept — enough to answer "is the screen still as I left it?"
31
+ // without keeping the screen.
32
+
33
+ const SESSION_DIR = path.join(causyneHome(), 'embodiment');
34
+
35
+ /** Sessions older than this are pruned. A GUI session has no value stale. */
36
+ const RETENTION_MS = 24 * 60 * 60 * 1000;
37
+
38
+ /** Kill switch, matching CAUSYNE_OUTPUT_SPILL's shape. */
39
+ export function isSessionTrackingEnabled() {
40
+ return process.env.CAUSYNE_EMBODIMENT_SESSION !== '0';
41
+ }
42
+
43
+ function ensureSessionDir() {
44
+ if (!fs.existsSync(SESSION_DIR)) {
45
+ fs.mkdirSync(SESSION_DIR, { recursive: true, mode: 0o700 });
46
+ }
47
+ }
48
+
49
+
50
+ function safeTaskId(taskId) {
51
+ const cleaned = String(taskId || 'default').replace(/[^a-zA-Z0-9_-]/g, '');
52
+ return cleaned === '' ? 'default' : cleaned.slice(0, 64);
53
+ }
54
+
55
+ function sessionFile(taskId) {
56
+ return path.join(SESSION_DIR, `${safeTaskId(taskId)}.json`);
57
+ }
58
+
59
+ /**
60
+ * Cheap, dependency-free digest of a screen reading.
61
+ *
62
+ * Not cryptographic and does not need to be — it answers one question, "is this
63
+ * the same screen I saw before?", where a collision costs one redundant re-read.
64
+ */
65
+ function digestOf(text) {
66
+ let hash = 5381;
67
+ const source = String(text || '');
68
+ for (let i = 0; i < source.length; i++) {
69
+ hash = ((hash * 33) ^ source.charCodeAt(i)) >>> 0;
70
+ }
71
+ return `${hash.toString(16)}:${source.length}`;
72
+ }
73
+
74
+ /**
75
+ * Record that the agent observed a screen.
76
+ *
77
+ * @param {string} taskId
78
+ * @param {string} app Application observed, '' for frontmost.
79
+ * @param {string} treeText The ax_snapshot output. Hashed, never stored.
80
+ */
81
+ export function recordObservation(taskId, app, treeText) {
82
+ if (!isSessionTrackingEnabled()) return;
83
+ try {
84
+ ensureSessionDir();
85
+ const previous = readSession(taskId) || {};
86
+ const state = {
87
+ ...previous,
88
+ app: app || previous.app || '',
89
+ lastTreeDigest: digestOf(treeText),
90
+ lastTreeNodeCount: String(treeText || '').split('\n').filter(Boolean).length,
91
+ observedAt: new Date().toISOString()
92
+ };
93
+ fs.writeFileSync(sessionFile(taskId), JSON.stringify(state, null, 2), { mode: 0o600 });
94
+ } catch (e) {
95
+ // Fail open: a lost session record must never fail the task.
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Record that the agent acted on a screen.
101
+ *
102
+ * Stores the VERBS only, never their arguments. "typed 14 characters into
103
+ * Notes" is enough to resume; the 14 characters are the user's business.
104
+ */
105
+ export function recordActuation(taskId, app, verbs) {
106
+ if (!isSessionTrackingEnabled()) return;
107
+ try {
108
+ ensureSessionDir();
109
+ const previous = readSession(taskId) || {};
110
+ const state = {
111
+ ...previous,
112
+ app: app || previous.app || '',
113
+ lastActions: Array.isArray(verbs) ? verbs.map(v => String(v)).slice(0, 12) : [],
114
+ // The screen has almost certainly changed, so the stored digest is stale
115
+ // by definition. Marking it beats silently trusting it.
116
+ lastTreeDigest: null,
117
+ actedAt: new Date().toISOString()
118
+ };
119
+ fs.writeFileSync(sessionFile(taskId), JSON.stringify(state, null, 2), { mode: 0o600 });
120
+ } catch (e) {
121
+ // Fail open.
122
+ }
123
+ }
124
+
125
+ /** @returns {object|null} the stored session, or null when there is none. */
126
+ export function readSession(taskId) {
127
+ try {
128
+ const file = sessionFile(taskId);
129
+ if (!fs.existsSync(file)) return null;
130
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
131
+ } catch (e) {
132
+ return null;
133
+ }
134
+ }
135
+
136
+
137
+ export function describeSession(taskId) {
138
+ const state = readSession(taskId);
139
+ if (!state || (!state.app && !state.lastActions)) return '';
140
+
141
+ const parts = [];
142
+ parts.push(`You were driving ${state.app ? `"${state.app}"` : 'the frontmost application'} before this session resumed.`);
143
+ if (Array.isArray(state.lastActions) && state.lastActions.length > 0) {
144
+ parts.push(`Your last actions were: ${state.lastActions.join(', ')}.`);
145
+ parts.push('You do NOT know whether they landed — the screen may have changed while you were gone.');
146
+ }
147
+ parts.push('Call ax_snapshot before acting again, and compare what you see to what you expected.');
148
+ return `\n\n🖥️ EMBODIMENT SESSION\n${parts.join(' ')}`;
149
+ }
150
+
151
+ /** Drop a task's session, e.g. when the task completes. */
152
+ export function clearSession(taskId) {
153
+ try {
154
+ const file = sessionFile(taskId);
155
+ if (fs.existsSync(file)) fs.unlinkSync(file);
156
+ } catch (e) {
157
+ // Fail open.
158
+ }
159
+ }
160
+
161
+ /** Retire sessions past the retention window. Fire-and-forget, never throws. */
162
+ export function pruneSessions() {
163
+ try {
164
+ if (!fs.existsSync(SESSION_DIR)) return;
165
+ const cutoff = Date.now() - RETENTION_MS;
166
+ for (const name of fs.readdirSync(SESSION_DIR)) {
167
+ const file = path.join(SESSION_DIR, name);
168
+ try {
169
+ if (fs.statSync(file).mtimeMs < cutoff) fs.unlinkSync(file);
170
+ } catch (e) { /* skip this one */ }
171
+ }
172
+ } catch (e) {
173
+ // Fail open.
174
+ }
175
+ }
176
+
177
+ /** Where sessions live. For diagnostics and tests. */
178
+ export function getSessionDir() {
179
+ return SESSION_DIR;
180
+ }
@@ -0,0 +1,194 @@
1
+ import { program } from 'commander';
2
+
3
+ import { runTask, chatMode, execCommand, interactiveConfig } from './cliMode.js';
4
+ import { runSelfUpdate } from './autoUpdate.js';
5
+ import { printContextReport, printUsageReport, printCheckpoints, printUndo, buildStatusText, printReport } from './reporting.js';
6
+ import { runCommand } from './commands.js';
7
+ import { resolveInvocation } from './tui/invocation.js';
8
+
9
+ // The old flag remains parseable only to explain its retirement.
10
+ function announceDevEmulator(requested) {
11
+ if (requested) console.log('Developer emulator mode is retired. Configure local budgets explicitly.');
12
+ }
13
+ /**
14
+ * Ask before something irreversible, without dragging in a prompt framework.
15
+ *
16
+ * These commands run outside the chat surface, so ChatPrompt's ConfirmLayer is
17
+ * not available — it needs the live region to draw into. A bare readline
18
+ * question is the honest primitive here. A non-TTY answers NO: an unattended
19
+ * `causyne undo` in a script must not silently rewrite the working tree, and
20
+ * `--yes` is the way a script says it meant it.
21
+ */
22
+ async function confirmDestructive(question) {
23
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
24
+ const readline = (await import('readline')).default;
25
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
26
+ try {
27
+ const answer = await new Promise((resolve) => rl.question(`${question} [y/N] `, resolve));
28
+ return /^y(es)?$/i.test(String(answer).trim());
29
+ } finally {
30
+ rl.close();
31
+ }
32
+ }
33
+
34
+ import { getPackageVersion } from './version.js';
35
+ const pkgVersion = getPackageVersion();
36
+
37
+
38
+ program
39
+ // The advertised name. `causyne-agent` still resolves to the same launcher via
40
+ // the second entry in package.json "bin", but the help text has to teach ONE
41
+ // name or it teaches neither — a user who typed `causyne` and is answered
42
+ // "Usage: causyne-agent" reasonably concludes they ran the wrong thing.
43
+ .name('causyne')
44
+ .description('Causyne — Local desktop AI harness (alias: causyne-agent)')
45
+ .version(pkgVersion);
46
+
47
+ program
48
+ .command('update')
49
+ .description('Show source update guidance (no automatic installation)')
50
+ .action(async () => {
51
+ await runSelfUpdate(true);
52
+ });
53
+
54
+ program
55
+ .command('status')
56
+ .description('Show local provider, workspace and runtime status')
57
+ .action(() => printReport(buildStatusText(), { title: 'Status' }));
58
+
59
+ program
60
+ .command('config')
61
+ .description('Configure Direct API Provider (Interactive)')
62
+ .option('-p, --provider <name>', 'Set LLM provider (openai, anthropic, gemini, muse, ollama, dashscope, deepseek)')
63
+ .option('-k, --apikey <key>', 'Set API key')
64
+ .option('-m, --model <name>', 'Set model name')
65
+ .action(async (options) => {
66
+ await interactiveConfig(options);
67
+ });
68
+
69
+ program
70
+ .command('run <task>')
71
+ .description('Run a single task directly from the CLI')
72
+ .option('--dev-emulator', 'Retired compatibility flag; grants no additional permissions')
73
+ .action(async (task, options) => {
74
+ announceDevEmulator(options.devEmulator);
75
+ await runTask(task);
76
+ });
77
+
78
+ program
79
+ .command('chat')
80
+ .description('Start interactive chat mode with the agent')
81
+ .option('--dev-emulator', 'Retired compatibility flag; grants no additional permissions')
82
+ .action(async (options) => {
83
+ announceDevEmulator(options.devEmulator);
84
+ await chatMode();
85
+ });
86
+
87
+ program
88
+ .command('context')
89
+ .description('Show what fills the context window (tool schemas, window pool, spilled output)')
90
+ .action(async () => {
91
+ printContextReport();
92
+ });
93
+
94
+ program
95
+ .command('usage')
96
+ .description('Show BYOK token usage and estimated cost (today and lifetime)')
97
+ .action(async () => {
98
+ await printUsageReport();
99
+ });
100
+
101
+ program
102
+ .command('checkpoints')
103
+ .description('List recent undo checkpoints (files + conversation snapshots)')
104
+ .option('-n, --limit <count>', 'How many task groups to show', '15')
105
+ .action(async (options) => {
106
+ printCheckpoints(parseInt(options.limit, 10) || 15);
107
+ });
108
+
109
+ program
110
+ .command('undo [taskId]')
111
+ .description("Roll back the last task's file changes and rewind the conversation")
112
+ .option('--step', 'Undo only the single most recent file change')
113
+ .option('--full', 'Also roll back files changed by terminal commands (reverts your own manual edits too)')
114
+ .option('--dry-run', 'Show what would change without writing anything')
115
+ .option('-y, --yes', 'Do not ask for confirmation')
116
+ .action(async (taskId, options) => {
117
+ const opts = { step: options.step, dryRun: options.dryRun, full: options.full };
118
+
119
+ // A rollback writes to the user's files, and this command had no gate at
120
+ // all: `causyne undo` restored immediately. The preview is the same code
121
+ // path with dryRun forced, so what is shown is exactly what will happen —
122
+ // not a description of it.
123
+ if (!options.dryRun && !options.yes) {
124
+ const { buildUndoText } = await import('./reporting.js');
125
+ const preview = buildUndoText(taskId, { ...opts, dryRun: true });
126
+ printReport(preview.text, { title: 'Undo preview' });
127
+ if (!(await confirmDestructive('Apply this rollback?'))) {
128
+ console.log('Cancelled. Nothing was written.');
129
+ process.exit(0);
130
+ }
131
+ }
132
+
133
+ // Explicit now that the default is a preview: reaching here means either
134
+ // --dry-run was asked for, or the user confirmed the preview above.
135
+ const ok = printUndo(taskId, { ...opts, dryRun: options.dryRun === true ? true : false });
136
+ process.exit(ok ? 0 : 1);
137
+ });
138
+
139
+ program
140
+ .command('mcp [action] [args...]')
141
+ .description('Manage MCP servers: list (default), add, remove, grant, revoke')
142
+ // An MCP invocation is somebody else's command line — `npx -y @scope/server`
143
+ // is normal. Without this, commander claims `-y` as its own unknown option and
144
+ // the add fails before it reaches us. (passThroughOptions() would be the
145
+ // stricter tool but requires enablePositionalOptions() on the root command,
146
+ // which would change parsing for every other subcommand.)
147
+ .allowUnknownOption()
148
+ .action(async (action, args) => {
149
+ // Same registry the chat surfaces use, so `causyne-agent mcp` and `/mcp`
150
+ // can never disagree about what a server is or what enabling one means.
151
+ const argv = action ? [action, ...(args || [])] : [];
152
+ const result = await runCommand('mcp', argv, { surface: 'terminal' });
153
+ console.log('\n' + result.output + '\n');
154
+ process.exit(result.ok ? 0 : 1);
155
+ });
156
+
157
+ program
158
+ .command('exec <command>')
159
+ .description('Quick execute a command using the agent terminal')
160
+ .action(async (command) => {
161
+ await execCommand(command);
162
+ });
163
+
164
+ // Setup fallback for missing commands
165
+ program.on('command:*', function () {
166
+ console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
167
+ process.exit(1);
168
+ });
169
+
170
+ // Bare `causyne` opens the application; anything else is a subcommand.
171
+ //
172
+ // This was `process.argv.length <= 2`, which is true only for the exact string
173
+ // `causyne`. resolveInvocation() consumes declared root options first, so
174
+ // `causyne --dev-emulator` is still bare, while an unknown option or any bare
175
+ // word still goes to Commander to be dispatched or rejected. See
176
+ // src/tui/invocation.js for why each rule is conservative in that direction.
177
+ const invocation = resolveInvocation(process.argv);
178
+
179
+ if (invocation.isBareRoot) {
180
+ if (!process.stdout.isTTY) {
181
+ // Nothing can be interactive here, and printing a wordmark into a pipe is
182
+ // noise. `causyne | cat` behaves like every other CLI: it says what it does.
183
+ program.outputHelp();
184
+ process.exit(0);
185
+ }
186
+ announceDevEmulator(invocation.rootOptions.includes('--dev-emulator'));
187
+ // A missing key is a hint on the home screen, not a gate in front of it.
188
+ chatMode({ home: true }).catch((e) => {
189
+ console.error(e && e.stack ? e.stack : e);
190
+ process.exit(1);
191
+ });
192
+ } else {
193
+ program.parse(process.argv);
194
+ }
@@ -0,0 +1,21 @@
1
+ import { exec, execSync as nodeExecSync } from 'node:child_process';
2
+
3
+ // Compatibility wrapper, NOT a security boundary. These APIs invoke a shell.
4
+ // Callers must enforce approval and validate input; prefer execFile with argv.
5
+ export function execSync(command, options) {
6
+ return nodeExecSync(command, options);
7
+ }
8
+
9
+ export function execAsync(command, options = {}) {
10
+ return new Promise((resolve, reject) => {
11
+ exec(command, options, (error, stdout, stderr) => {
12
+ if (error) {
13
+ error.stdout = stdout;
14
+ error.stderr = stderr;
15
+ reject(error);
16
+ return;
17
+ }
18
+ resolve(typeof stdout === 'string' ? stdout : String(stdout ?? ''));
19
+ });
20
+ });
21
+ }