@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.
- package/LICENSE +102 -0
- package/README.md +31 -2
- package/SECURITY.md +24 -0
- package/bin/causyne.js +13 -0
- package/docs/ARCHITECTURE.md +63 -0
- package/docs/PROVENANCE.md +29 -0
- package/docs/SECURITY_REVIEW.md +89 -0
- package/docs/THIRD_PARTY_NOTICES.md +49 -0
- package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
- package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
- package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
- package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
- package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
- package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
- package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
- package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
- package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
- package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
- package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
- package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
- package/native-skills/android/play-store-release/SKILL.md +43 -0
- package/native-skills/android/room-and-persistence/SKILL.md +40 -0
- package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
- package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
- package/native-skills/backend/caching-strategy/SKILL.md +44 -0
- package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
- package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
- package/native-skills/backend/rest-api-design/SKILL.md +40 -0
- package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
- package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
- package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
- package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
- package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
- package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
- package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
- package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
- package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
- package/native-skills/data/metric-definition/SKILL.md +42 -0
- package/native-skills/data/notebook-to-production/SKILL.md +43 -0
- package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
- package/native-skills/database/connection-pooling/SKILL.md +42 -0
- package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
- package/native-skills/database/query-optimisation/SKILL.md +38 -0
- package/native-skills/database/safe-migrations/SKILL.md +47 -0
- package/native-skills/database/schema-design/SKILL.md +38 -0
- package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
- package/native-skills/design/colour-system-construction/SKILL.md +38 -0
- package/native-skills/design/design-direction-brief/SKILL.md +41 -0
- package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
- package/native-skills/design/responsive-strategy/SKILL.md +39 -0
- package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
- package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
- package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
- package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
- package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
- package/native-skills/devops/incident-triage/SKILL.md +38 -0
- package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
- package/native-skills/devops/observability-baseline/SKILL.md +38 -0
- package/native-skills/devops/secret-management/SKILL.md +40 -0
- package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
- package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
- package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
- package/native-skills/git/branch-strategy/SKILL.md +39 -0
- package/native-skills/git/commit-hygiene/SKILL.md +41 -0
- package/native-skills/git/history-recovery/SKILL.md +44 -0
- package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
- package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
- package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
- package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
- package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
- package/native-skills/mobile/deep-linking/SKILL.md +39 -0
- package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
- package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
- package/native-skills/mobile/push-notifications/SKILL.md +38 -0
- package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
- package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
- package/native-skills/performance/load-testing/SKILL.md +42 -0
- package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
- package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
- package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
- package/native-skills/python/async-correctness/SKILL.md +40 -0
- package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
- package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
- package/native-skills/python/environment-isolation/SKILL.md +41 -0
- package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
- package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
- package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
- package/native-skills/security/authorization-model/SKILL.md +42 -0
- package/native-skills/security/crypto-usage/SKILL.md +48 -0
- package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
- package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
- package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
- package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
- package/native-skills/security/threat-modelling/SKILL.md +46 -0
- package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
- package/native-skills/testing/property-based-testing/SKILL.md +46 -0
- package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
- package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
- package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
- package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
- package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
- package/native-skills/web/accessible-components/SKILL.md +45 -0
- package/native-skills/web/api-client-resilience/SKILL.md +39 -0
- package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
- package/native-skills/web/core-web-vitals/SKILL.md +40 -0
- package/native-skills/web/css-layout-systems/SKILL.md +41 -0
- package/native-skills/web/form-validation-ux/SKILL.md +39 -0
- package/native-skills/web/react-state-architecture/SKILL.md +41 -0
- package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
- package/package.json +68 -4
- package/src/agentLoop.js +2458 -0
- package/src/appChannel.js +109 -0
- package/src/approvalGrants.js +66 -0
- package/src/auditLog.js +52 -0
- package/src/autoUpdate.js +12 -0
- package/src/capabilities.js +36 -0
- package/src/checkpoints.js +1199 -0
- package/src/cliMode.js +638 -0
- package/src/commands.js +805 -0
- package/src/compiledSkills.js +540 -0
- package/src/config.js +130 -0
- package/src/contextManager.js +498 -0
- package/src/cortex/autoCondenser.js +192 -0
- package/src/cortex/provisioner.js +27 -0
- package/src/cortex/vectorDb.js +447 -0
- package/src/designPalettes.js +99 -0
- package/src/devMode.js +6 -0
- package/src/embodimentSession.js +180 -0
- package/src/entrypoint.js +194 -0
- package/src/execSafe.js +21 -0
- package/src/gitSnapshot.js +378 -0
- package/src/mcp/jsonRpc.js +165 -0
- package/src/mcp/mcpClient.js +136 -0
- package/src/mcp/mcpRegistry.js +207 -0
- package/src/memory.js +62 -0
- package/src/memoryManager.js +124 -0
- package/src/modelCatalogue.js +132 -0
- package/src/modelIds.js +42 -0
- package/src/modelRouter.js +145 -0
- package/src/outputStore.js +246 -0
- package/src/perception.js +124 -0
- package/src/planCache.js +437 -0
- package/src/procSignal.js +32 -0
- package/src/providers/anthropic.js +408 -0
- package/src/providers/base.js +328 -0
- package/src/providers/dashscope-byok.js +86 -0
- package/src/providers/deepseek.js +80 -0
- package/src/providers/factory.js +37 -0
- package/src/providers/gemini.js +346 -0
- package/src/providers/moonshot.js +73 -0
- package/src/providers/muse.js +169 -0
- package/src/providers/ollama.js +104 -0
- package/src/providers/openai-compatible.js +84 -0
- package/src/providers/openai.js +20 -0
- package/src/providers/qwen-byok.js +76 -0
- package/src/providers/xai.js +73 -0
- package/src/provisioning/capabilityConsent.js +24 -0
- package/src/provisioning/capabilityProvisioner.js +271 -0
- package/src/provisioning/capabilityTools.js +29 -0
- package/src/reporting.js +373 -0
- package/src/sandbox.js +529 -0
- package/src/sessionStore.js +279 -0
- package/src/skillsManager.js +288 -0
- package/src/slopDetector.js +307 -0
- package/src/taskBuffer.js +45 -0
- package/src/taskShape.js +241 -0
- package/src/textDiff.js +150 -0
- package/src/tokenTracker.js +560 -0
- package/src/tool-scripts/ax_snapshot.applescript +39 -0
- package/src/tool-scripts/ax_snapshot.ps1 +10 -0
- package/src/tool-scripts/desktop_act.applescript +58 -0
- package/src/tool-scripts/resolve_automator.py +119 -0
- package/src/toolPacks.js +524 -0
- package/src/tools/archive.js +63 -0
- package/src/tools/astryxOracle.js +78 -0
- package/src/tools/axSnapshot.js +165 -0
- package/src/tools/blenderAutomator.js +349 -0
- package/src/tools/browserAutomator.js +252 -0
- package/src/tools/checkSystemSpecs.js +64 -0
- package/src/tools/clipboard.js +36 -0
- package/src/tools/codeSearchAst.js +101 -0
- package/src/tools/codeSearcher.js +230 -0
- package/src/tools/codeTools.js +262 -0
- package/src/tools/codebaseSearch.js +75 -0
- package/src/tools/createSkill.js +129 -0
- package/src/tools/deferredMcpSearch.js +246 -0
- package/src/tools/desktopAct.js +208 -0
- package/src/tools/desktopOpen.js +34 -0
- package/src/tools/dynamicLoader.js +44 -0
- package/src/tools/executeProceduralMedia.js +93 -0
- package/src/tools/fsDelete.js +51 -0
- package/src/tools/fsEdit.js +105 -0
- package/src/tools/fsFind.js +128 -0
- package/src/tools/fsList.js +77 -0
- package/src/tools/fsMove.js +57 -0
- package/src/tools/fsRead.js +87 -0
- package/src/tools/fsSearch.js +198 -0
- package/src/tools/fsWrite.js +41 -0
- package/src/tools/gameAiBehaviorTree.js +208 -0
- package/src/tools/gameAnimationRig.js +194 -0
- package/src/tools/gameAssetFetcher.js +372 -0
- package/src/tools/gameAssetHarvester.js +210 -0
- package/src/tools/gameAudioSynth.js +267 -0
- package/src/tools/gameBuilder.js +886 -0
- package/src/tools/gameCanvasAudit.js +101 -0
- package/src/tools/gameCinematicSequencer.js +151 -0
- package/src/tools/gameDataForge.js +124 -0
- package/src/tools/gameDialogueTreeGen.js +166 -0
- package/src/tools/gameEconomyBalancer.js +161 -0
- package/src/tools/gameEntitySpawner.js +207 -0
- package/src/tools/gameLoreForge.js +153 -0
- package/src/tools/gameQuestArcBuilder.js +152 -0
- package/src/tools/gameSceneBuilder.js +158 -0
- package/src/tools/gameShaderForge.js +181 -0
- package/src/tools/gameTilemapGenerator.js +164 -0
- package/src/tools/gameUiHudBuilder.js +292 -0
- package/src/tools/gameVfxEmitterGen.js +108 -0
- package/src/tools/gameWebRuntimeInspector.js +91 -0
- package/src/tools/generateMediaViaApi.js +358 -0
- package/src/tools/gitExec.js +54 -0
- package/src/tools/godotAutomator.js +437 -0
- package/src/tools/manageLocalAiDependencies.js +220 -0
- package/src/tools/memoryWrite.js +99 -0
- package/src/tools/multiEdit.js +107 -0
- package/src/tools/palmierAutomator.js +104 -0
- package/src/tools/pdfReader.js +112 -0
- package/src/tools/persistentShell.js +148 -0
- package/src/tools/printer.js +316 -0
- package/src/tools/procTerm.js +91 -0
- package/src/tools/processList.js +52 -0
- package/src/tools/readNativeSkill.js +39 -0
- package/src/tools/registry.js +277 -0
- package/src/tools/resolveAutomator.js +102 -0
- package/src/tools/screenshot.js +75 -0
- package/src/tools/subagentOrchestrator.js +305 -0
- package/src/tools/sysRun.js +125 -0
- package/src/tools/todoWrite.js +75 -0
- package/src/tools/toolDispatch.js +127 -0
- package/src/tools/toolForge.js +124 -0
- package/src/tools/updatePlan.js +88 -0
- package/src/tools/viewImage.js +82 -0
- package/src/tools/visionInspector.js +154 -0
- package/src/tools/webFetch.js +74 -0
- package/src/tools/webSearch.js +82 -0
- package/src/tui/ask.js +164 -0
- package/src/tui/banner.js +158 -0
- package/src/tui/block.js +152 -0
- package/src/tui/chatRenderer.js +953 -0
- package/src/tui/completion.js +424 -0
- package/src/tui/dialogs.js +606 -0
- package/src/tui/diff.js +296 -0
- package/src/tui/history.js +135 -0
- package/src/tui/home.js +217 -0
- package/src/tui/invocation.js +105 -0
- package/src/tui/keys.js +443 -0
- package/src/tui/lineEditor.js +256 -0
- package/src/tui/overlay.js +256 -0
- package/src/tui/prompt.js +1258 -0
- package/src/tui/selectList.js +335 -0
- package/src/tui/stage.js +330 -0
- package/src/tui/stageSurface.js +609 -0
- package/src/tui/statusLine.js +254 -0
- package/src/tui/text.js +345 -0
- package/src/tui/theme.js +393 -0
- package/src/tui/toolDisplay.js +176 -0
- package/src/tui/transcript.js +235 -0
- package/src/tui/viewport.js +337 -0
- package/src/uncertainty.js +271 -0
- package/src/userSkills.js +265 -0
- package/src/utils/cli_ui.js +0 -0
- package/src/utils/inquirer.js +12 -0
- package/src/utils/paths.js +37 -0
- package/src/version.js +38 -0
- 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
|
+
}
|
package/src/execSafe.js
ADDED
|
@@ -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
|
+
}
|