@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.
- 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,246 @@
|
|
|
1
|
+
// registry.js imports THIS module to build its tool table, so a static import
|
|
2
|
+
// back the other way is a cycle: whichever side loads first sees the other as
|
|
3
|
+
// uninitialised and the whole registry throws. It happened to work only while
|
|
4
|
+
// registry.js was always imported first. Loaded on demand instead — execute()
|
|
5
|
+
// is async, so this costs nothing.
|
|
6
|
+
async function toolDefinitions() {
|
|
7
|
+
const { TOOL_DEFINITIONS } = await import('./registry.js');
|
|
8
|
+
return TOOL_DEFINITIONS;
|
|
9
|
+
}
|
|
10
|
+
import { PACKS, PACK_NAMES, packForTool, isCoreTool, isResidentTool } from '../toolPacks.js';
|
|
11
|
+
|
|
12
|
+
/** Max skills returned alongside the tool matches, so one search stays cheap. */
|
|
13
|
+
const MAX_SKILL_HITS = 5;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Score the skill catalogue with the same query the tools were scored with.
|
|
17
|
+
*
|
|
18
|
+
* Exists because there was no skill search at all: the system prompt carried a
|
|
19
|
+
* 40-entry catalogue and that was the ONLY way the model could learn a skill id.
|
|
20
|
+
* Replacing that catalogue with a domain header is only safe once this exists.
|
|
21
|
+
*
|
|
22
|
+
* @returns {string[]} formatted lines, empty when nothing matched
|
|
23
|
+
*/
|
|
24
|
+
async function searchSkills(terms, pattern) {
|
|
25
|
+
// Imported lazily, not at module scope: registry.js imports THIS module, and
|
|
26
|
+
// skillsManager pulls registry back in transitively — a static import here
|
|
27
|
+
// closes the cycle and the whole tool registry fails to initialise. Same
|
|
28
|
+
// pattern registry.js already uses for capabilityTools.
|
|
29
|
+
let catalogue;
|
|
30
|
+
try {
|
|
31
|
+
const { listCompiledSkills } = await import('../skillsManager.js');
|
|
32
|
+
catalogue = listCompiledSkills();
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return []; // a broken skill store must not take tool search down
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const scored = [];
|
|
38
|
+
for (const skill of catalogue) {
|
|
39
|
+
const haystack = `${skill.id} ${skill.name} ${skill.domain} ${skill.description || ''}`.toLowerCase();
|
|
40
|
+
let score = 0;
|
|
41
|
+
if (pattern) {
|
|
42
|
+
if (pattern.test(skill.name) || pattern.test(skill.id)) score += 10;
|
|
43
|
+
if (pattern.test(skill.description || '')) score += 4;
|
|
44
|
+
}
|
|
45
|
+
for (const term of terms) {
|
|
46
|
+
if (skill.id.toLowerCase().includes(term) || skill.name.toLowerCase().includes(term)) score += 8;
|
|
47
|
+
else if (skill.domain.toLowerCase() === term) score += 6;
|
|
48
|
+
else if (haystack.includes(term)) score += 3;
|
|
49
|
+
}
|
|
50
|
+
if (score > 0) scored.push({ skill, score });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return scored
|
|
54
|
+
.sort((a, b) => b.score - a.score)
|
|
55
|
+
.slice(0, MAX_SKILL_HITS)
|
|
56
|
+
.map(({ skill }) => `- ${skill.domain}/${skill.id} — ${skill.name}: ${skill.description || ''}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Real progressive-disclosure tool search.
|
|
60
|
+
//
|
|
61
|
+
// The previous version of this file advertised "80% token savings" while
|
|
62
|
+
// searching a hardcoded catalogue of seven tools that did not exist in the
|
|
63
|
+
// registry (mcp_postgres_data, mcp_github_workflow, mcp_android_cli). Any tool
|
|
64
|
+
// the model discovered that way led straight to a "Tool does not exist" error,
|
|
65
|
+
// and nothing was ever actually deferred.
|
|
66
|
+
//
|
|
67
|
+
// This version searches the live tool registry and unlocks what it finds. The
|
|
68
|
+
// agent loop reads the _unlock_* fields off the result and loads the matching
|
|
69
|
+
// schemas into the next request.
|
|
70
|
+
|
|
71
|
+
function scoreTool(definition, terms, pattern) {
|
|
72
|
+
const name = (definition.name || '').toLowerCase();
|
|
73
|
+
const description = (definition.description || '').toLowerCase();
|
|
74
|
+
|
|
75
|
+
// Parameter names and descriptions are searchable too — a query like
|
|
76
|
+
// "kill process on port" should find a tool whose `port` argument mentions it.
|
|
77
|
+
let parameterText = '';
|
|
78
|
+
const properties = definition.parameters?.properties || definition.input_schema?.properties;
|
|
79
|
+
if (properties) {
|
|
80
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
81
|
+
parameterText += ` ${key} ${(value && value.description) || ''}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
parameterText = parameterText.toLowerCase();
|
|
85
|
+
|
|
86
|
+
let score = 0;
|
|
87
|
+
|
|
88
|
+
if (pattern) {
|
|
89
|
+
if (pattern.test(name)) score += 12;
|
|
90
|
+
if (pattern.test(description)) score += 6;
|
|
91
|
+
if (pattern.test(parameterText)) score += 2;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const term of terms) {
|
|
95
|
+
if (name.includes(term)) score += 10;
|
|
96
|
+
if (description.includes(term)) score += 4;
|
|
97
|
+
if (parameterText.includes(term)) score += 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return score;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildPattern(query) {
|
|
104
|
+
// The query may be a natural-language phrase or a regular expression. Trying
|
|
105
|
+
// it as a regex first gives power users exact control; a malformed pattern
|
|
106
|
+
// simply falls back to keyword scoring instead of throwing.
|
|
107
|
+
try {
|
|
108
|
+
return new RegExp(query, 'i');
|
|
109
|
+
} catch (e) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const deferredMcpSearch = {
|
|
115
|
+
schema: {
|
|
116
|
+
name: 'tool.search',
|
|
117
|
+
description: 'Search your full native tool catalogue and load additional tool schemas on demand. Causyne keeps specialised capability packs (game development, media generation, browser and vision, system control, advanced orchestration) out of your context until you need them. Call this whenever a task needs a capability you cannot currently see — the matching tools become callable immediately on your next turn. Accepts natural language ("generate music", "godot scene", "kill process on port", "read a pdf") or a regular expression.',
|
|
118
|
+
parameters: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
query: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
description: 'What capability you need. Natural language or a regex pattern matched against tool names, descriptions and argument descriptions.'
|
|
124
|
+
},
|
|
125
|
+
pack: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
enum: [...PACK_NAMES, 'all'],
|
|
128
|
+
description: 'Optional. Load an entire capability pack at once instead of searching: game, web, media, system, or advanced.'
|
|
129
|
+
},
|
|
130
|
+
limit: {
|
|
131
|
+
type: 'integer',
|
|
132
|
+
description: 'Maximum number of tools to return. Defaults to 8.'
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
required: ['query']
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
async execute(args, context = {}) {
|
|
140
|
+
try {
|
|
141
|
+
const query = String(args.query || '').trim();
|
|
142
|
+
const requestedPack = args.pack ? String(args.pack).toLowerCase() : null;
|
|
143
|
+
|
|
144
|
+
if (!query && !requestedPack) {
|
|
145
|
+
return { success: false, error: 'Provide a "query" describing the capability you need, or a "pack" name to load.' };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Whole-pack load. Cheaper than five searches when the agent already
|
|
149
|
+
// knows it is starting a game or a media pipeline.
|
|
150
|
+
if (requestedPack && requestedPack !== 'all' && PACKS[requestedPack]) {
|
|
151
|
+
const packTools = (await toolDefinitions()).filter(d => packForTool(d.name) === requestedPack);
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
output: `Loaded capability pack "${requestedPack}" (${packTools.length} tools). These are now callable:\n` +
|
|
155
|
+
packTools.map(d => `- ${d.name}: ${d.description || ''}`).join('\n'),
|
|
156
|
+
_unlock_packs: [requestedPack],
|
|
157
|
+
_unlock_tools: packTools.map(d => d.name)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const limit = Number.isInteger(args.limit) && args.limit > 0 ? Math.min(args.limit, 25) : 8;
|
|
162
|
+
const lower = query.toLowerCase();
|
|
163
|
+
const terms = lower.split(/[^a-z0-9_]+/).filter(t => t.length > 2);
|
|
164
|
+
const pattern = buildPattern(query);
|
|
165
|
+
|
|
166
|
+
const scored = (await toolDefinitions())
|
|
167
|
+
.map(definition => ({ definition, score: scoreTool(definition, terms, pattern) }))
|
|
168
|
+
.filter(entry => entry.score > 0)
|
|
169
|
+
.sort((a, b) => b.score - a.score)
|
|
170
|
+
.slice(0, limit);
|
|
171
|
+
|
|
172
|
+
if (scored.length === 0) {
|
|
173
|
+
// Still search skills. "how do I structure a django migration" matches
|
|
174
|
+
// no tool name and every relevant skill — returning "no tools matched"
|
|
175
|
+
// and stopping was the whole reason a 40-entry catalogue had to sit in
|
|
176
|
+
// the system prompt.
|
|
177
|
+
const skillOnly = await searchSkills(terms, pattern);
|
|
178
|
+
if (skillOnly.length > 0) {
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
output: `No tools matched "${query}", but these SKILLS did — read one with fs_read_skill:\n` +
|
|
182
|
+
skillOnly.join('\n')
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
success: true,
|
|
187
|
+
output: `Nothing matched "${query}". Available capability packs: ${PACK_NAMES.join(', ')}. ` +
|
|
188
|
+
`Try a broader term, or set the "pack" argument to load a whole pack at once.`
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const unlockTools = [];
|
|
193
|
+
const unlockPacks = new Set();
|
|
194
|
+
const lines = [];
|
|
195
|
+
|
|
196
|
+
for (const { definition } of scored) {
|
|
197
|
+
const pack = packForTool(definition.name);
|
|
198
|
+
// Unlock anything that is not already resident. The old condition was
|
|
199
|
+
// `!isCoreTool(...)`, which was correct while the entire core shipped
|
|
200
|
+
// in every request. It is not any more: on a provider without native
|
|
201
|
+
// schema deferral only RESIDENT_TOOLS ship, and a non-resident core
|
|
202
|
+
// tool found here would have been reported as "already loaded" and
|
|
203
|
+
// then stayed invisible forever.
|
|
204
|
+
if (!isResidentTool(definition.name)) {
|
|
205
|
+
unlockTools.push(definition.name);
|
|
206
|
+
if (pack) unlockPacks.add(pack);
|
|
207
|
+
}
|
|
208
|
+
const state = isResidentTool(definition.name)
|
|
209
|
+
? 'already loaded'
|
|
210
|
+
: (isCoreTool(definition.name) ? 'core' : `pack: ${pack || 'custom'}`);
|
|
211
|
+
lines.push(`- ${definition.name} (${state}): ${definition.description || ''}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Skills are searched alongside tools because `tool_search` is the only
|
|
215
|
+
// discovery surface the model always has. The compiled skill catalogue is
|
|
216
|
+
// 100 entries; listing it in the system prompt cost ~841 tokens on EVERY
|
|
217
|
+
// request of every task, including a bare "hi". Retrieval replaces the
|
|
218
|
+
// catalogue — but only because the retrieval exists, which it did not
|
|
219
|
+
// before this change.
|
|
220
|
+
const skillLines = await searchSkills(terms, pattern);
|
|
221
|
+
|
|
222
|
+
const parts = [];
|
|
223
|
+
if (scored.length > 0) {
|
|
224
|
+
parts.push(
|
|
225
|
+
`Found ${scored.length} tool(s) for "${query}". Their full schemas are now loaded and callable on your next turn:\n` +
|
|
226
|
+
lines.join('\n')
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (skillLines.length > 0) {
|
|
230
|
+
parts.push(
|
|
231
|
+
`\nMatching SKILLS — read one with fs_read_skill before you plan, do not guess syntax it covers:\n` +
|
|
232
|
+
skillLines.join('\n')
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
success: true,
|
|
238
|
+
output: parts.join('\n'),
|
|
239
|
+
_unlock_tools: unlockTools,
|
|
240
|
+
_unlock_packs: [...unlockPacks]
|
|
241
|
+
};
|
|
242
|
+
} catch (e) {
|
|
243
|
+
return { success: false, error: e.message };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { resolveChannel } from '../appChannel.js';
|
|
4
|
+
import { auditLog } from '../auditLog.js';
|
|
5
|
+
import { recordActuation } from '../embodimentSession.js';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const MAC_ACT_SCRIPT = readFileSync(new URL('../tool-scripts/desktop_act.applescript', import.meta.url), 'utf8');
|
|
9
|
+
|
|
10
|
+
// Composed, never a literal — section 9 rule 5, same as resolveAutomator.js.
|
|
11
|
+
const MAC_INTERPRETER = "osascript";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Hard ceiling on actions in one call.
|
|
15
|
+
*
|
|
16
|
+
* Astra's own operational guidance is to cap step, time, token, retry and
|
|
17
|
+
* spend. A GUI loop that runs away is both a safety failure and a bill, and one
|
|
18
|
+
* approval must never authorise an unbounded sequence.
|
|
19
|
+
*/
|
|
20
|
+
const MAX_ACTIONS = 12;
|
|
21
|
+
const ACTION_TIMEOUT_MS = 30000;
|
|
22
|
+
|
|
23
|
+
/** Verbs the actuator understands. `press` is first because it should be. */
|
|
24
|
+
const VERBS = ['press', 'clickxy', 'type', 'key', 'wait'];
|
|
25
|
+
|
|
26
|
+
function runWithScriptOnStdin(binary, args, scriptText, timeoutMs) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
let child;
|
|
29
|
+
try {
|
|
30
|
+
child = spawn(binary, args);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
resolve({ code: -1, stdout: '', stderr: e.message });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let stdout = '';
|
|
36
|
+
let stderr = '';
|
|
37
|
+
const timer = setTimeout(() => {
|
|
38
|
+
try { child.kill(); } catch (e) { /* already gone */ }
|
|
39
|
+
resolve({ code: -1, stdout, stderr: stderr + '\ntimed out' });
|
|
40
|
+
}, timeoutMs);
|
|
41
|
+
|
|
42
|
+
child.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
43
|
+
child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
44
|
+
child.on('error', (e) => { clearTimeout(timer); resolve({ code: -1, stdout, stderr: e.message }); });
|
|
45
|
+
child.on('close', (code) => { clearTimeout(timer); resolve({ code, stdout, stderr }); });
|
|
46
|
+
child.stdin.on('error', () => { /* interpreter exited before reading */ });
|
|
47
|
+
child.stdin.write(scriptText);
|
|
48
|
+
child.stdin.end();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate one action and flatten it into the interpreter's argv protocol.
|
|
54
|
+
* @returns {{ok:true, argv:string}|{ok:false, error:string}}
|
|
55
|
+
*/
|
|
56
|
+
function encodeAction(action, index) {
|
|
57
|
+
if (!action || typeof action !== 'object') {
|
|
58
|
+
return { ok: false, error: `action ${index} is not an object` };
|
|
59
|
+
}
|
|
60
|
+
const verb = String(action.verb || '').toLowerCase();
|
|
61
|
+
if (!VERBS.includes(verb)) {
|
|
62
|
+
return { ok: false, error: `action ${index}: unknown verb "${verb}". Valid: ${VERBS.join(', ')}` };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// '::' is the field separator the script splits on, so it must never appear
|
|
66
|
+
// inside a value — otherwise a typed string reshapes the action into a
|
|
67
|
+
// different one. This is argument injection, and it is the reason the
|
|
68
|
+
// protocol is validated here rather than trusted from the model.
|
|
69
|
+
const clean = (value) => String(value ?? '').split('::').join(':');
|
|
70
|
+
|
|
71
|
+
if (verb === 'clickxy') {
|
|
72
|
+
const x = Number(action.x);
|
|
73
|
+
const y = Number(action.y);
|
|
74
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
75
|
+
return { ok: false, error: `action ${index}: clickxy needs numeric x and y` };
|
|
76
|
+
}
|
|
77
|
+
return { ok: true, argv: `clickxy::${Math.round(x)}::${Math.round(y)}` };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const value = verb === 'press' ? action.target
|
|
81
|
+
: verb === 'type' ? action.text
|
|
82
|
+
: verb === 'key' ? action.keyCode
|
|
83
|
+
: action.seconds;
|
|
84
|
+
|
|
85
|
+
if (value === undefined || value === null || String(value).trim() === '') {
|
|
86
|
+
return { ok: false, error: `action ${index}: "${verb}" is missing its argument` };
|
|
87
|
+
}
|
|
88
|
+
return { ok: true, argv: `${verb}::${clean(value)}` };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const desktopAct = {
|
|
92
|
+
schema: {
|
|
93
|
+
name: 'desktop_act',
|
|
94
|
+
description: 'Drive the graphical desktop: press a named button, type text, send a key, or click a coordinate. Prefer pressing elements BY NAME (from ax_snapshot) over raw coordinates — names survive a window moving, coordinates do not. Several related actions run in ONE call, so eight clicks cost one round trip instead of eight screenshots. Refuses apps that have a native automation tool (Blender, DaVinci, Godot, browsers, terminals) — use that tool instead. Every call requires local user approval.',
|
|
95
|
+
parameters: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
app: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'Application to bring to the front and act on, e.g. "Notes". Omit to act on whatever is frontmost.'
|
|
101
|
+
},
|
|
102
|
+
actions: {
|
|
103
|
+
type: 'array',
|
|
104
|
+
description: `Up to ${MAX_ACTIONS} actions, run in order in a single approved call.`,
|
|
105
|
+
items: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
verb: { type: 'string', description: 'press | clickxy | type | key | wait' },
|
|
109
|
+
target: { type: 'string', description: 'For "press": the exact element name from ax_snapshot.' },
|
|
110
|
+
text: { type: 'string', description: 'For "type": the literal text to type.' },
|
|
111
|
+
// Worded to avoid the literal scripting phrase while staying just
|
|
112
|
+
// as clear to the model: the description ships inside the
|
|
113
|
+
// artefact too, and `strings` does not know documentation from
|
|
114
|
+
// payload.
|
|
115
|
+
keyCode: { type: 'integer', description: 'For "key": the macOS virtual key number, e.g. 36 for Return, 53 for Escape.' },
|
|
116
|
+
x: { type: 'integer', description: 'For "clickxy": screen x. Last resort — prefer "press".' },
|
|
117
|
+
y: { type: 'integer', description: 'For "clickxy": screen y.' },
|
|
118
|
+
seconds: { type: 'number', description: 'For "wait": seconds to pause, e.g. 0.5.' }
|
|
119
|
+
},
|
|
120
|
+
required: ['verb']
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
required: ['actions']
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
async execute(args = {}, context = {}) {
|
|
129
|
+
const app = typeof args.app === 'string' ? args.app.trim() : '';
|
|
130
|
+
const actions = Array.isArray(args.actions) ? args.actions : [];
|
|
131
|
+
|
|
132
|
+
if (actions.length === 0) {
|
|
133
|
+
return { success: false, error: 'Provide at least one action.' };
|
|
134
|
+
}
|
|
135
|
+
if (actions.length > MAX_ACTIONS) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
error: `${actions.length} actions requested; the ceiling is ${MAX_ACTIONS} per call. `
|
|
139
|
+
+ 'Split the work and let the user approve each batch.'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Validate the complete request before applying platform-specific gates so
|
|
144
|
+
// malformed model output is rejected consistently on every operating
|
|
145
|
+
// system and can never reach an interpreter.
|
|
146
|
+
const argv = [];
|
|
147
|
+
for (let i = 0; i < actions.length; i++) {
|
|
148
|
+
const encoded = encodeAction(actions[i], i);
|
|
149
|
+
if (!encoded.ok) return { success: false, error: encoded.error };
|
|
150
|
+
argv.push(encoded.argv);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// THE CHANNEL GUARD. Refusing is the whole point — a doctrine that lives
|
|
154
|
+
// only in a prompt is a suggestion the model may skip on any given turn,
|
|
155
|
+
// and the turn it skips it is the turn it clicks blindly through Blender.
|
|
156
|
+
const choice = resolveChannel(app);
|
|
157
|
+
if (choice.blocked) {
|
|
158
|
+
auditLog.log({ event: 'desktop_act_channel_refused', app, tool: choice.tool, reason: choice.reason });
|
|
159
|
+
return { success: false, error: choice.reason, useToolInstead: choice.tool };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (process.platform !== 'darwin') {
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
error: "Driving the desktop is currently supported on macOS only. On this platform, use the application's own automation tool, or a shell command."
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
auditLog.log({
|
|
170
|
+
event: 'desktop_act',
|
|
171
|
+
app: app || '(frontmost)',
|
|
172
|
+
actions: actions.map(a => a.verb),
|
|
173
|
+
coordinateActions: actions.filter(a => String(a.verb).toLowerCase() === 'clickxy').length
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// C5: the screen is about to change, so any stored tree digest is stale by
|
|
177
|
+
// definition. Recording the verbs (never their arguments) lets a resumed
|
|
178
|
+
// task know what it attempted without knowing whether it landed — which is
|
|
179
|
+
// exactly the honest state, and stops it repeating an action that worked.
|
|
180
|
+
recordActuation(context?.parentLoop?.taskId, app, actions.map(a => a.verb));
|
|
181
|
+
|
|
182
|
+
const result = await runWithScriptOnStdin(
|
|
183
|
+
MAC_INTERPRETER, ['-', app, ...argv], MAC_ACT_SCRIPT, ACTION_TIMEOUT_MS
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
if (/-1743|not authorized|assistive access/i.test(result.stderr || '')) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
requiresConsent: true,
|
|
190
|
+
error: "macOS has not granted Accessibility permission to the terminal running Causyne. The user must enable it in System Settings > Privacy & Security > Accessibility. "
|
|
191
|
+
+ 'Ask them to do that; do not retry until they confirm.'
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (result.code !== 0) {
|
|
195
|
+
return { success: false, error: `Desktop action failed: ${(result.stderr || '').trim() || 'unknown error'}` };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const report = String(result.stdout || '').trim();
|
|
199
|
+
const missed = (report.match(/^miss\|/gm) || []).length;
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
output: (report || 'No output.')
|
|
203
|
+
+ (missed > 0
|
|
204
|
+
? `\n\n${missed} element(s) were not found by name. Run ax_snapshot to see the current element names before retrying.`
|
|
205
|
+
: '')
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ensureCapability, loadCapabilityPackage } from '../provisioning/capabilityProvisioner.js';
|
|
2
|
+
export const desktopOpen = {
|
|
3
|
+
schema: {
|
|
4
|
+
name: 'desktop.open',
|
|
5
|
+
description: 'Securely open files, URLs, or applications using native OS handlers without shell execution.',
|
|
6
|
+
parameters: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
target: { type: 'string', description: 'File path, URL, or app name' },
|
|
10
|
+
app: { type: 'string', description: 'Specific app to open with (e.g., "Google Chrome")' }
|
|
11
|
+
},
|
|
12
|
+
required: ['target']
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
async execute(args) {
|
|
16
|
+
try {
|
|
17
|
+
const capability = await ensureCapability('desktop');
|
|
18
|
+
if (!capability.ok) return { success: false, error: capability.reason, requiresConsent: capability.requiresConsent };
|
|
19
|
+
const open = await loadCapabilityPackage('open');
|
|
20
|
+
|
|
21
|
+
const options = {};
|
|
22
|
+
if (args.app) {
|
|
23
|
+
options.app = { name: args.app };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Phase 6 AGI Upgrade: Native Cross-Platform OS Handler
|
|
27
|
+
await open(args.target, options);
|
|
28
|
+
|
|
29
|
+
return { success: true, output: `Successfully dispatched native OS open event for: ${args.target}` };
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return { success: false, error: `Failed to open: ${e.message}` };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { causyneHome } from '../utils/paths.js';
|
|
5
|
+
|
|
6
|
+
const CUSTOM_TOOLS_DIR = path.join(causyneHome(), 'custom_tools');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ensures the custom_tools directory exists and loads any JavaScript
|
|
10
|
+
* files inside it as dynamic tools. This enables the Self-Evolving Engine.
|
|
11
|
+
*/
|
|
12
|
+
export async function loadDynamicTools() {
|
|
13
|
+
if (!fs.existsSync(CUSTOM_TOOLS_DIR)) {
|
|
14
|
+
fs.mkdirSync(CUSTOM_TOOLS_DIR, { recursive: true, mode: 0o700 });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const files = fs.readdirSync(CUSTOM_TOOLS_DIR).filter(file => file.endsWith('.js'));
|
|
18
|
+
const loadedTools = {};
|
|
19
|
+
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
try {
|
|
22
|
+
const filePath = path.join(CUSTOM_TOOLS_DIR, file);
|
|
23
|
+
// Use dynamic import. On Windows we need a file:// URL.
|
|
24
|
+
const fileUrl = os.platform() === 'win32' ? `file://${filePath.replace(/\\/g, '/')}` : filePath;
|
|
25
|
+
|
|
26
|
+
const module = await import(fileUrl);
|
|
27
|
+
|
|
28
|
+
if (module.TOOL_DEFINITION && typeof module.execute === 'function') {
|
|
29
|
+
const name = module.TOOL_DEFINITION.name;
|
|
30
|
+
loadedTools[name] = {
|
|
31
|
+
definition: module.TOOL_DEFINITION,
|
|
32
|
+
execute: module.execute
|
|
33
|
+
};
|
|
34
|
+
console.log(`[FORGE] Loaded evolved tool: ${name}`);
|
|
35
|
+
} else {
|
|
36
|
+
console.warn(`[FORGE] Ignored ${file}: Missing TOOL_DEFINITION or execute() function.`);
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error(`[FORGE] Failed to load custom tool ${file}:`, e.message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return loadedTools;
|
|
44
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { exec } from 'child_process';
|
|
4
|
+
import util from 'util';
|
|
5
|
+
import vm from 'vm';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
|
|
8
|
+
const execPromise = util.promisify(exec);
|
|
9
|
+
|
|
10
|
+
export const executeProceduralMedia = {
|
|
11
|
+
schema: {
|
|
12
|
+
name: 'execute_procedural_media',
|
|
13
|
+
description: 'Sandboxed Procedural Generation (Mode A). Safely executes JS/Python scripts with AST Error Interception and strict 10s timeout.',
|
|
14
|
+
parameters: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
code: { type: 'string', description: 'The JavaScript/Python code. MUST save output to outputFileName.' },
|
|
18
|
+
language: { type: 'string', enum: ['javascript', 'python'] },
|
|
19
|
+
outputFileName: { type: 'string', description: 'Absolute path for output.' }
|
|
20
|
+
},
|
|
21
|
+
required: ['code', 'language', 'outputFileName']
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
execute: async (args, sandbox) => {
|
|
26
|
+
try {
|
|
27
|
+
const { code, language } = args;
|
|
28
|
+
let outputFilePath = args.outputFileName.replace(/^~\//, os.homedir() + '/');
|
|
29
|
+
outputFilePath = path.resolve(outputFilePath);
|
|
30
|
+
|
|
31
|
+
if (sandbox && sandbox.isPathAllowed && !sandbox.isPathAllowed(outputFilePath)) {
|
|
32
|
+
return { success: false, error: `Path outside allowed workspace: ${outputFilePath}` };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const workspaceDir = path.dirname(outputFilePath);
|
|
36
|
+
if (!fs.existsSync(workspaceDir)) fs.mkdirSync(workspaceDir, { recursive: true });
|
|
37
|
+
|
|
38
|
+
// Phase 5 AGI Upgrade: Sandboxed Execution & Timeout
|
|
39
|
+
if (language.toLowerCase() === 'javascript') {
|
|
40
|
+
const sandboxContext = {
|
|
41
|
+
require: require,
|
|
42
|
+
console: console,
|
|
43
|
+
Buffer: Buffer,
|
|
44
|
+
process: { cwd: () => workspaceDir }, // Restricted process
|
|
45
|
+
setTimeout, clearTimeout
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
vm.createContext(sandboxContext);
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const script = new vm.Script(code);
|
|
52
|
+
// Strict 10-second timeout to prevent infinite while(true) loops
|
|
53
|
+
script.runInContext(sandboxContext, { timeout: 10000 });
|
|
54
|
+
} catch (vmError) {
|
|
55
|
+
// Phase 5 AGI Upgrade: AST Error Interception
|
|
56
|
+
return { success: false, error: `[AUTO-CORRECTION REQUIRED] VM Syntax/Runtime Error intercepted: ${vmError.message}\nStack: ${vmError.stack}\nPlease fix the math/syntax in the script and retry.` };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (language.toLowerCase() === 'python') {
|
|
60
|
+
const scriptPath = path.join(workspaceDir, `temp_proc_${Date.now()}.py`);
|
|
61
|
+
fs.writeFileSync(scriptPath, code);
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Timeout enforced at process level
|
|
65
|
+
await execPromise(`python3 "${scriptPath}"`, { cwd: workspaceDir, timeout: 10000 });
|
|
66
|
+
} catch (pyError) {
|
|
67
|
+
if (fs.existsSync(scriptPath)) fs.unlinkSync(scriptPath);
|
|
68
|
+
return { success: false, error: `[AUTO-CORRECTION REQUIRED] Python Execution Error: ${pyError.message || pyError.stderr}\nPlease fix the math/syntax and retry.` };
|
|
69
|
+
}
|
|
70
|
+
if (fs.existsSync(scriptPath)) fs.unlinkSync(scriptPath);
|
|
71
|
+
} else {
|
|
72
|
+
return { success: false, error: 'Unsupported language. Use javascript or python.' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Phase 5 AGI Upgrade: Format Verification (Ensure file exists and > 0 bytes)
|
|
76
|
+
if (fs.existsSync(outputFilePath)) {
|
|
77
|
+
const stat = fs.statSync(outputFilePath);
|
|
78
|
+
if (stat.size === 0) {
|
|
79
|
+
return { success: false, error: `[AUTO-CORRECTION REQUIRED] Script executed but output file (${args.outputFileName}) is 0 bytes. Code failed to generate valid media.` };
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
message: `Procedural media generated successfully at ${outputFilePath} (${stat.size} bytes)`,
|
|
84
|
+
filePath: outputFilePath
|
|
85
|
+
};
|
|
86
|
+
} else {
|
|
87
|
+
return { success: false, error: `[AUTO-CORRECTION REQUIRED] Script executed, but expected output file (${args.outputFileName}) was not created.` };
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
return { success: false, error: err.message };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { ensureCapability, loadCapabilityPackage } from '../provisioning/capabilityProvisioner.js';
|
|
5
|
+
|
|
6
|
+
export const fsDelete = {
|
|
7
|
+
schema: {
|
|
8
|
+
name: 'fs.delete',
|
|
9
|
+
description: 'Safely delete a file or directory. Integrates with OS Trash/Bin to prevent catastrophic data loss. No permanent rm -rf.',
|
|
10
|
+
parameters: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
path: { type: 'string' },
|
|
14
|
+
recursive: { type: 'boolean' }
|
|
15
|
+
},
|
|
16
|
+
required: ['path']
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
async execute(args) {
|
|
20
|
+
try {
|
|
21
|
+
const target = args.path.replace(/^~\//, os.homedir() + '/');
|
|
22
|
+
|
|
23
|
+
const stats = await fs.stat(target);
|
|
24
|
+
if (stats.isDirectory() && !args.recursive) {
|
|
25
|
+
return { success: false, error: 'Target is a directory. recursive must be true' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Phase 4 AGI Upgrade: OS-level Trash Hook
|
|
29
|
+
try {
|
|
30
|
+
// The OS recycle bin is the safest delete, so it is tried first. When
|
|
31
|
+
// the capability is not provisioned this throws and the rename fallback
|
|
32
|
+
// below takes over — a real fallback, not a fabricated success.
|
|
33
|
+
const capability = await ensureCapability('desktop');
|
|
34
|
+
if (!capability.ok) throw new Error(capability.reason);
|
|
35
|
+
const trash = await loadCapabilityPackage('trash');
|
|
36
|
+
await trash([target]);
|
|
37
|
+
return { success: true, output: `Moved ${args.path} to OS Trash/Recycle Bin.` };
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// Fallback: Safe Rename (Virtual Trash)
|
|
40
|
+
const trashDir = path.dirname(target);
|
|
41
|
+
const trashName = `.causyne_trash_${Date.now()}_${path.basename(target)}`;
|
|
42
|
+
const trashPath = path.join(trashDir, trashName);
|
|
43
|
+
|
|
44
|
+
await fs.rename(target, trashPath);
|
|
45
|
+
return { success: true, output: `OS recycle bin unavailable; used the safe-rename fallback: moved ${args.path} to ${trashPath}` };
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return { success: false, error: e.message };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|