agent-nuvira 3.3.12 → 3.3.14
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/README.md +67 -1
- package/dist/agents/agents/planner.d.ts.map +1 -1
- package/dist/agents/agents/planner.js +9 -0
- package/dist/agents/agents/planner.js.map +1 -1
- package/dist/agents/agents/reasoner.d.ts.map +1 -1
- package/dist/agents/agents/reasoner.js +13 -0
- package/dist/agents/agents/reasoner.js.map +1 -1
- package/dist/agents/orchestrator.d.ts +31 -0
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +103 -135
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/cli/chat.d.ts +89 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +798 -39
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/cli-program.d.ts.map +1 -1
- package/dist/cli/cli-program.js +20 -0
- package/dist/cli/cli-program.js.map +1 -1
- package/dist/cli/config.d.ts +12 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +107 -3
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/decisions.d.ts +28 -0
- package/dist/cli/decisions.d.ts.map +1 -0
- package/dist/cli/decisions.js +140 -0
- package/dist/cli/decisions.js.map +1 -0
- package/dist/cli/doctor.d.ts +6 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +46 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +6 -0
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/failover-runner.d.ts.map +1 -1
- package/dist/cli/failover-runner.js +19 -1
- package/dist/cli/failover-runner.js.map +1 -1
- package/dist/cli/feedback.d.ts.map +1 -1
- package/dist/cli/feedback.js +5 -1
- package/dist/cli/feedback.js.map +1 -1
- package/dist/cli/hooks.d.ts +30 -0
- package/dist/cli/hooks.d.ts.map +1 -0
- package/dist/cli/hooks.js +224 -0
- package/dist/cli/hooks.js.map +1 -0
- package/dist/cli/knowledge.d.ts +31 -0
- package/dist/cli/knowledge.d.ts.map +1 -0
- package/dist/cli/knowledge.js +223 -0
- package/dist/cli/knowledge.js.map +1 -0
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +92 -18
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/model.d.ts +20 -0
- package/dist/cli/model.d.ts.map +1 -1
- package/dist/cli/model.js +294 -14
- package/dist/cli/model.js.map +1 -1
- package/dist/cli/omniroute-control.d.ts +88 -0
- package/dist/cli/omniroute-control.d.ts.map +1 -0
- package/dist/cli/omniroute-control.js +179 -0
- package/dist/cli/omniroute-control.js.map +1 -0
- package/dist/cli/omniroute.d.ts +21 -0
- package/dist/cli/omniroute.d.ts.map +1 -0
- package/dist/cli/omniroute.js +67 -0
- package/dist/cli/omniroute.js.map +1 -0
- package/dist/cli/rate.d.ts +78 -0
- package/dist/cli/rate.d.ts.map +1 -0
- package/dist/cli/rate.js +294 -0
- package/dist/cli/rate.js.map +1 -0
- package/dist/config/process-env.d.ts.map +1 -1
- package/dist/config/process-env.js +10 -0
- package/dist/config/process-env.js.map +1 -1
- package/dist/config/types.d.ts +8 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/history.d.ts +21 -0
- package/dist/context/history.d.ts.map +1 -1
- package/dist/context/history.js +50 -0
- package/dist/context/history.js.map +1 -1
- package/dist/enterprise/vault.d.ts.map +1 -1
- package/dist/enterprise/vault.js +5 -21
- package/dist/enterprise/vault.js.map +1 -1
- package/dist/gateway/hook-contract.d.ts +206 -0
- package/dist/gateway/hook-contract.d.ts.map +1 -0
- package/dist/gateway/hook-contract.js +449 -0
- package/dist/gateway/hook-contract.js.map +1 -0
- package/dist/gateway/hooks.d.ts.map +1 -1
- package/dist/gateway/hooks.js +14 -0
- package/dist/gateway/hooks.js.map +1 -1
- package/dist/inference/gemini-adapter.d.ts.map +1 -1
- package/dist/inference/gemini-adapter.js +4 -0
- package/dist/inference/gemini-adapter.js.map +1 -1
- package/dist/inference/interface.d.ts +54 -0
- package/dist/inference/interface.d.ts.map +1 -1
- package/dist/inference/model-id-validation.d.ts +57 -0
- package/dist/inference/model-id-validation.d.ts.map +1 -0
- package/dist/inference/model-id-validation.js +127 -0
- package/dist/inference/model-id-validation.js.map +1 -0
- package/dist/inference/native-tools.d.ts +16 -1
- package/dist/inference/native-tools.d.ts.map +1 -1
- package/dist/inference/native-tools.js +34 -10
- package/dist/inference/native-tools.js.map +1 -1
- package/dist/inference/openai-compat-adapter.d.ts.map +1 -1
- package/dist/inference/openai-compat-adapter.js +12 -1
- package/dist/inference/openai-compat-adapter.js.map +1 -1
- package/dist/inference/provider-catalog.d.ts +11 -0
- package/dist/inference/provider-catalog.d.ts.map +1 -1
- package/dist/inference/provider-catalog.js +80 -0
- package/dist/inference/provider-catalog.js.map +1 -1
- package/dist/inference/route-resolver.d.ts +32 -8
- package/dist/inference/route-resolver.d.ts.map +1 -1
- package/dist/inference/route-resolver.js +175 -1
- package/dist/inference/route-resolver.js.map +1 -1
- package/dist/inference/tool-call-utils.d.ts.map +1 -1
- package/dist/inference/tool-call-utils.js +10 -8
- package/dist/inference/tool-call-utils.js.map +1 -1
- package/dist/inference/tools.d.ts +17 -1
- package/dist/inference/tools.d.ts.map +1 -1
- package/dist/inference/tools.js +69 -19
- package/dist/inference/tools.js.map +1 -1
- package/dist/learning/acceptance-model.d.ts +247 -0
- package/dist/learning/acceptance-model.d.ts.map +1 -0
- package/dist/learning/acceptance-model.js +708 -0
- package/dist/learning/acceptance-model.js.map +1 -0
- package/dist/learning/auto-router.d.ts +121 -24
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +374 -96
- package/dist/learning/auto-router.js.map +1 -1
- package/dist/learning/autonomy-policy.d.ts +12 -16
- package/dist/learning/autonomy-policy.d.ts.map +1 -1
- package/dist/learning/autonomy-policy.js +50 -48
- package/dist/learning/autonomy-policy.js.map +1 -1
- package/dist/learning/behavioural-labels.d.ts +86 -0
- package/dist/learning/behavioural-labels.d.ts.map +1 -0
- package/dist/learning/behavioural-labels.js +166 -0
- package/dist/learning/behavioural-labels.js.map +1 -0
- package/dist/learning/capability-evidence.d.ts +167 -0
- package/dist/learning/capability-evidence.d.ts.map +1 -0
- package/dist/learning/capability-evidence.js +210 -0
- package/dist/learning/capability-evidence.js.map +1 -0
- package/dist/learning/capability-readiness.d.ts +64 -0
- package/dist/learning/capability-readiness.d.ts.map +1 -0
- package/dist/learning/capability-readiness.js +74 -0
- package/dist/learning/capability-readiness.js.map +1 -0
- package/dist/learning/capability-types.d.ts +169 -0
- package/dist/learning/capability-types.d.ts.map +1 -0
- package/dist/learning/capability-types.js +20 -0
- package/dist/learning/capability-types.js.map +1 -0
- package/dist/learning/catalog-feed.d.ts +89 -0
- package/dist/learning/catalog-feed.d.ts.map +1 -0
- package/dist/learning/catalog-feed.js +211 -0
- package/dist/learning/catalog-feed.js.map +1 -0
- package/dist/learning/cli-intent-effects.d.ts +60 -0
- package/dist/learning/cli-intent-effects.d.ts.map +1 -0
- package/dist/learning/cli-intent-effects.js +153 -0
- package/dist/learning/cli-intent-effects.js.map +1 -0
- package/dist/learning/command-adaptation.d.ts +41 -0
- package/dist/learning/command-adaptation.d.ts.map +1 -0
- package/dist/learning/command-adaptation.js +122 -0
- package/dist/learning/command-adaptation.js.map +1 -0
- package/dist/learning/consent-picture.d.ts +42 -0
- package/dist/learning/consent-picture.d.ts.map +1 -0
- package/dist/learning/consent-picture.js +53 -0
- package/dist/learning/consent-picture.js.map +1 -0
- package/dist/learning/context-budget.d.ts +37 -6
- package/dist/learning/context-budget.d.ts.map +1 -1
- package/dist/learning/context-budget.js +38 -9
- package/dist/learning/context-budget.js.map +1 -1
- package/dist/learning/decision-log.d.ts +114 -0
- package/dist/learning/decision-log.d.ts.map +1 -0
- package/dist/learning/decision-log.js +296 -0
- package/dist/learning/decision-log.js.map +1 -0
- package/dist/learning/deliverable-class.d.ts.map +1 -1
- package/dist/learning/deliverable-class.js +19 -0
- package/dist/learning/deliverable-class.js.map +1 -1
- package/dist/learning/deliverable-corpus.d.ts +117 -0
- package/dist/learning/deliverable-corpus.d.ts.map +1 -0
- package/dist/learning/deliverable-corpus.js +176 -0
- package/dist/learning/deliverable-corpus.js.map +1 -0
- package/dist/learning/error-repair.d.ts +45 -2
- package/dist/learning/error-repair.d.ts.map +1 -1
- package/dist/learning/error-repair.js +95 -0
- package/dist/learning/error-repair.js.map +1 -1
- package/dist/learning/exhaustion-report.d.ts +136 -0
- package/dist/learning/exhaustion-report.d.ts.map +1 -0
- package/dist/learning/exhaustion-report.js +239 -0
- package/dist/learning/exhaustion-report.js.map +1 -0
- package/dist/learning/failure-bookkeeping.d.ts +16 -0
- package/dist/learning/failure-bookkeeping.d.ts.map +1 -1
- package/dist/learning/failure-bookkeeping.js +51 -5
- package/dist/learning/failure-bookkeeping.js.map +1 -1
- package/dist/learning/gate-friction.d.ts +39 -0
- package/dist/learning/gate-friction.d.ts.map +1 -0
- package/dist/learning/gate-friction.js +91 -0
- package/dist/learning/gate-friction.js.map +1 -0
- package/dist/learning/harness-narration.d.ts +34 -0
- package/dist/learning/harness-narration.d.ts.map +1 -0
- package/dist/learning/harness-narration.js +58 -0
- package/dist/learning/harness-narration.js.map +1 -0
- package/dist/learning/hub-skill-catalog.d.ts.map +1 -1
- package/dist/learning/hub-skill-catalog.js +1 -2
- package/dist/learning/hub-skill-catalog.js.map +1 -1
- package/dist/learning/hybrid-router.d.ts +65 -0
- package/dist/learning/hybrid-router.d.ts.map +1 -1
- package/dist/learning/hybrid-router.js +186 -0
- package/dist/learning/hybrid-router.js.map +1 -1
- package/dist/learning/knowledge-base.d.ts +350 -0
- package/dist/learning/knowledge-base.d.ts.map +1 -0
- package/dist/learning/knowledge-base.js +906 -0
- package/dist/learning/knowledge-base.js.map +1 -0
- package/dist/learning/knowledge-eval.d.ts +171 -0
- package/dist/learning/knowledge-eval.d.ts.map +1 -0
- package/dist/learning/knowledge-eval.js +196 -0
- package/dist/learning/knowledge-eval.js.map +1 -0
- package/dist/learning/knowledge-turn.d.ts +92 -0
- package/dist/learning/knowledge-turn.d.ts.map +1 -0
- package/dist/learning/knowledge-turn.js +190 -0
- package/dist/learning/knowledge-turn.js.map +1 -0
- package/dist/learning/learned-commands.d.ts +64 -0
- package/dist/learning/learned-commands.d.ts.map +1 -0
- package/dist/learning/learned-commands.js +108 -0
- package/dist/learning/learned-commands.js.map +1 -0
- package/dist/learning/lexical-search.d.ts +50 -0
- package/dist/learning/lexical-search.d.ts.map +1 -0
- package/dist/learning/lexical-search.js +111 -0
- package/dist/learning/lexical-search.js.map +1 -0
- package/dist/learning/machine-facts.d.ts +71 -0
- package/dist/learning/machine-facts.d.ts.map +1 -0
- package/dist/learning/machine-facts.js +164 -0
- package/dist/learning/machine-facts.js.map +1 -0
- package/dist/learning/markdown-outline.d.ts +73 -0
- package/dist/learning/markdown-outline.d.ts.map +1 -0
- package/dist/learning/markdown-outline.js +136 -0
- package/dist/learning/markdown-outline.js.map +1 -0
- package/dist/learning/model-first-router.d.ts +33 -1
- package/dist/learning/model-first-router.d.ts.map +1 -1
- package/dist/learning/model-first-router.js +123 -9
- package/dist/learning/model-first-router.js.map +1 -1
- package/dist/learning/model-identity.d.ts +80 -0
- package/dist/learning/model-identity.d.ts.map +1 -0
- package/dist/learning/model-identity.js +115 -0
- package/dist/learning/model-identity.js.map +1 -0
- package/dist/learning/model-registry.d.ts +141 -2
- package/dist/learning/model-registry.d.ts.map +1 -1
- package/dist/learning/model-registry.js +324 -38
- package/dist/learning/model-registry.js.map +1 -1
- package/dist/learning/model-scoring.d.ts.map +1 -1
- package/dist/learning/model-scoring.js +8 -1
- package/dist/learning/model-scoring.js.map +1 -1
- package/dist/learning/model-selection.d.ts +2 -1
- package/dist/learning/model-selection.d.ts.map +1 -1
- package/dist/learning/model-selection.js +76 -2
- package/dist/learning/model-selection.js.map +1 -1
- package/dist/learning/outcome-observation.d.ts +49 -0
- package/dist/learning/outcome-observation.d.ts.map +1 -0
- package/dist/learning/outcome-observation.js +95 -0
- package/dist/learning/outcome-observation.js.map +1 -0
- package/dist/learning/pair-entitlement.d.ts +184 -0
- package/dist/learning/pair-entitlement.d.ts.map +1 -0
- package/dist/learning/pair-entitlement.js +214 -0
- package/dist/learning/pair-entitlement.js.map +1 -0
- package/dist/learning/project-docs.d.ts +83 -0
- package/dist/learning/project-docs.d.ts.map +1 -0
- package/dist/learning/project-docs.js +132 -0
- package/dist/learning/project-docs.js.map +1 -0
- package/dist/learning/provider-fallback.d.ts +23 -1
- package/dist/learning/provider-fallback.d.ts.map +1 -1
- package/dist/learning/provider-fallback.js +24 -0
- package/dist/learning/provider-fallback.js.map +1 -1
- package/dist/learning/quota-ledger.d.ts +13 -0
- package/dist/learning/quota-ledger.d.ts.map +1 -1
- package/dist/learning/quota-ledger.js +28 -0
- package/dist/learning/quota-ledger.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +65 -1
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +123 -1
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/requirement-probe.d.ts +55 -0
- package/dist/learning/requirement-probe.d.ts.map +1 -0
- package/dist/learning/requirement-probe.js +77 -0
- package/dist/learning/requirement-probe.js.map +1 -0
- package/dist/learning/resilient-call.d.ts +20 -1
- package/dist/learning/resilient-call.d.ts.map +1 -1
- package/dist/learning/resilient-call.js +42 -4
- package/dist/learning/resilient-call.js.map +1 -1
- package/dist/learning/response-usability.d.ts +85 -0
- package/dist/learning/response-usability.d.ts.map +1 -0
- package/dist/learning/response-usability.js +135 -0
- package/dist/learning/response-usability.js.map +1 -0
- package/dist/learning/router-bandit.d.ts +132 -0
- package/dist/learning/router-bandit.d.ts.map +1 -1
- package/dist/learning/router-bandit.js +253 -0
- package/dist/learning/router-bandit.js.map +1 -1
- package/dist/learning/routing-history.d.ts +41 -2
- package/dist/learning/routing-history.d.ts.map +1 -1
- package/dist/learning/routing-history.js.map +1 -1
- package/dist/learning/run-trace.d.ts +21 -0
- package/dist/learning/run-trace.d.ts.map +1 -1
- package/dist/learning/run-trace.js +38 -0
- package/dist/learning/run-trace.js.map +1 -1
- package/dist/learning/seeded-benchmark.d.ts +14 -0
- package/dist/learning/seeded-benchmark.d.ts.map +1 -1
- package/dist/learning/seeded-benchmark.js +43 -4
- package/dist/learning/seeded-benchmark.js.map +1 -1
- package/dist/learning/session-grant.d.ts +75 -0
- package/dist/learning/session-grant.d.ts.map +1 -0
- package/dist/learning/session-grant.js +112 -0
- package/dist/learning/session-grant.js.map +1 -0
- package/dist/learning/task-model-continuity.d.ts +73 -0
- package/dist/learning/task-model-continuity.d.ts.map +1 -0
- package/dist/learning/task-model-continuity.js +87 -0
- package/dist/learning/task-model-continuity.js.map +1 -0
- package/dist/learning/turn-feedback.d.ts +93 -0
- package/dist/learning/turn-feedback.d.ts.map +1 -0
- package/dist/learning/turn-feedback.js +127 -0
- package/dist/learning/turn-feedback.js.map +1 -0
- package/dist/learning/turn-report.d.ts +104 -2
- package/dist/learning/turn-report.d.ts.map +1 -1
- package/dist/learning/turn-report.js +161 -6
- package/dist/learning/turn-report.js.map +1 -1
- package/dist/learning/verdict-routing.d.ts +51 -0
- package/dist/learning/verdict-routing.d.ts.map +1 -0
- package/dist/learning/verdict-routing.js +122 -0
- package/dist/learning/verdict-routing.js.map +1 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +5 -0
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/manager.d.ts +10 -0
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +20 -0
- package/dist/mcp/manager.js.map +1 -1
- package/dist/mcp/mcp-discovery.d.ts +34 -0
- package/dist/mcp/mcp-discovery.d.ts.map +1 -0
- package/dist/mcp/mcp-discovery.js +71 -0
- package/dist/mcp/mcp-discovery.js.map +1 -0
- package/dist/mcp/mcp-schema-cache.d.ts +17 -0
- package/dist/mcp/mcp-schema-cache.d.ts.map +1 -1
- package/dist/mcp/mcp-schema-cache.js +47 -3
- package/dist/mcp/mcp-schema-cache.js.map +1 -1
- package/dist/mcp/types.d.ts +28 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/dist/security/secret-scan.d.ts +156 -0
- package/dist/security/secret-scan.d.ts.map +1 -0
- package/dist/security/secret-scan.js +525 -0
- package/dist/security/secret-scan.js.map +1 -0
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +4 -0
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/capability-registry.d.ts +150 -0
- package/dist/tools/capability-registry.d.ts.map +1 -0
- package/dist/tools/capability-registry.js +400 -0
- package/dist/tools/capability-registry.js.map +1 -0
- package/dist/tools/child-agent-runtime.d.ts.map +1 -1
- package/dist/tools/child-agent-runtime.js +19 -5
- package/dist/tools/child-agent-runtime.js.map +1 -1
- package/dist/tools/coding-tools.d.ts +17 -5
- package/dist/tools/coding-tools.d.ts.map +1 -1
- package/dist/tools/coding-tools.js +80 -11
- package/dist/tools/coding-tools.js.map +1 -1
- package/dist/tools/command-memo.d.ts +116 -0
- package/dist/tools/command-memo.d.ts.map +1 -0
- package/dist/tools/command-memo.js +202 -0
- package/dist/tools/command-memo.js.map +1 -0
- package/dist/tools/git-tool.d.ts.map +1 -1
- package/dist/tools/git-tool.js +13 -3
- package/dist/tools/git-tool.js.map +1 -1
- package/dist/tools/harness-directive.d.ts +42 -0
- package/dist/tools/harness-directive.d.ts.map +1 -0
- package/dist/tools/harness-directive.js +45 -0
- package/dist/tools/harness-directive.js.map +1 -0
- package/dist/tools/loop-skill-hint.d.ts +74 -174
- package/dist/tools/loop-skill-hint.d.ts.map +1 -1
- package/dist/tools/loop-skill-hint.js +110 -536
- package/dist/tools/loop-skill-hint.js.map +1 -1
- package/dist/tools/mcp-client-tool.d.ts +2 -0
- package/dist/tools/mcp-client-tool.d.ts.map +1 -1
- package/dist/tools/mcp-client-tool.js +3 -0
- package/dist/tools/mcp-client-tool.js.map +1 -1
- package/dist/tools/modality/shared.d.ts +1 -4
- package/dist/tools/modality/shared.d.ts.map +1 -1
- package/dist/tools/modality/shared.js +7 -22
- package/dist/tools/modality/shared.js.map +1 -1
- package/dist/tools/plan-store.d.ts +44 -4
- package/dist/tools/plan-store.d.ts.map +1 -1
- package/dist/tools/plan-store.js +93 -9
- package/dist/tools/plan-store.js.map +1 -1
- package/dist/tools/registry.d.ts +76 -3
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +623 -15
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/remediation-ladder.d.ts +91 -0
- package/dist/tools/remediation-ladder.d.ts.map +1 -0
- package/dist/tools/remediation-ladder.js +316 -0
- package/dist/tools/remediation-ladder.js.map +1 -0
- package/dist/tools/run-cli.d.ts.map +1 -1
- package/dist/tools/run-cli.js +29 -4
- package/dist/tools/run-cli.js.map +1 -1
- package/dist/tools/run-terminal.d.ts +11 -0
- package/dist/tools/run-terminal.d.ts.map +1 -1
- package/dist/tools/run-terminal.js +289 -10
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/skills-hub.d.ts +7 -0
- package/dist/tools/skills-hub.d.ts.map +1 -1
- package/dist/tools/skills-hub.js +11 -2
- package/dist/tools/skills-hub.js.map +1 -1
- package/dist/tools/step-artifact.d.ts +35 -0
- package/dist/tools/step-artifact.d.ts.map +1 -0
- package/dist/tools/step-artifact.js +154 -0
- package/dist/tools/step-artifact.js.map +1 -0
- package/dist/tools/tool-loop.d.ts +327 -13
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +891 -66
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/toolsets.js +4 -4
- package/dist/tools/toolsets.js.map +1 -1
- package/dist/tools/worktree.d.ts +11 -2
- package/dist/tools/worktree.d.ts.map +1 -1
- package/dist/tools/worktree.js +11 -2
- package/dist/tools/worktree.js.map +1 -1
- package/dist/utils/atomic-store.d.ts +53 -0
- package/dist/utils/atomic-store.d.ts.map +1 -0
- package/dist/utils/atomic-store.js +178 -0
- package/dist/utils/atomic-store.js.map +1 -0
- package/dist/utils/binary-probe.d.ts +25 -0
- package/dist/utils/binary-probe.d.ts.map +1 -0
- package/dist/utils/binary-probe.js +48 -0
- package/dist/utils/binary-probe.js.map +1 -0
- package/dist/utils/workspace-path.d.ts +50 -0
- package/dist/utils/workspace-path.d.ts.map +1 -0
- package/dist/utils/workspace-path.js +86 -0
- package/dist/utils/workspace-path.js.map +1 -0
- package/dist/web-dashboard/chat-console.d.ts +24 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +38 -0
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/project-context.d.ts +27 -2
- package/dist/web-dashboard/project-context.d.ts.map +1 -1
- package/dist/web-dashboard/project-context.js +32 -2
- package/dist/web-dashboard/project-context.js.map +1 -1
- package/dist/web-dashboard/server.d.ts +40 -0
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +733 -15
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +236 -1
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.d.ts +18 -2
- package/dist/web-dashboard/workspace-guard.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.js +94 -12
- package/dist/web-dashboard/workspace-guard.js.map +1 -1
- package/dist/web-dashboard/workspace-resolution.d.ts +87 -0
- package/dist/web-dashboard/workspace-resolution.d.ts.map +1 -0
- package/dist/web-dashboard/workspace-resolution.js +105 -0
- package/dist/web-dashboard/workspace-resolution.js.map +1 -0
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/index-tK8Y2qHB.css +1 -0
- package/src/web-dashboard/public/assets/index-zjbdJnO-.js +210 -0
- package/src/web-dashboard/public/assets/index-zjbdJnO-.js.map +1 -0
- package/src/web-dashboard/public/index.html +2 -2
- package/src/web-dashboard/public/assets/index-BrZcYhy6.js +0 -207
- package/src/web-dashboard/public/assets/index-BrZcYhy6.js.map +0 -1
- package/src/web-dashboard/public/assets/index-DsWczTa6.css +0 -1
package/dist/tools/registry.js
CHANGED
|
@@ -27,8 +27,18 @@ import { envBuff } from '../config/paths.js';
|
|
|
27
27
|
import { z, toJSONSchema } from 'zod';
|
|
28
28
|
import { ACTION_BY_INTENT } from '../nlu/actions.js';
|
|
29
29
|
import { detectPermissionSeeking, IRREVERSIBLE_ACTION_RE } from '../learning/autonomy-policy.js';
|
|
30
|
+
// The explicit, session-scoped "allow all <category> for this session" grant —
|
|
31
|
+
// offered at the confirmation a state-changing tool demanded (see the
|
|
32
|
+
// `ask_user` runner) and consulted by the write/terminal gates.
|
|
33
|
+
import { grantSession, sessionGrantLabel, sessionGrantNotice, } from '../learning/session-grant.js';
|
|
34
|
+
// Cluster G — a folder the user names in an ask_user reply IS the workspace
|
|
35
|
+
// (see the adoption block in the `ask_user` runner). The rule is the same,
|
|
36
|
+
// conservative one the dashboard server applies to a message.
|
|
37
|
+
import { directoryFromMessage } from '../utils/workspace-path.js';
|
|
30
38
|
import { createFindingTool } from './finding-tool.js';
|
|
39
|
+
import { declareToolCapability } from './capability-registry.js';
|
|
31
40
|
import { recordArtifact } from './artifact-append.js';
|
|
41
|
+
import { checkStepArtifacts } from './step-artifact.js';
|
|
32
42
|
// ─── Tool definitions ───────────────────────────────────────────────────────
|
|
33
43
|
// ─── Shared auto-delivery helper ─────────────────────────────────────────
|
|
34
44
|
// Tools that produce artifact files (generate_image, speak, video_generate)
|
|
@@ -93,6 +103,19 @@ const askUserSchema = z.object({
|
|
|
93
103
|
.describe('2–4 answer choices the user can pick from'),
|
|
94
104
|
multi_select: z.boolean().default(false).describe('Allow multiple selections (checkbox)'),
|
|
95
105
|
});
|
|
106
|
+
/**
|
|
107
|
+
* The extra choice a TOOL-DEMANDED confirmation ask offers: allow this whole
|
|
108
|
+
* category of state-changing action for the rest of the session. Deliberately
|
|
109
|
+
* one fixed label, so the grant is a choice the user picks — never a phrase
|
|
110
|
+
* the harness tries to recognise in free text.
|
|
111
|
+
*/
|
|
112
|
+
const SESSION_GRANT_CHOICE_LABEL = 'Allow this for the whole session';
|
|
113
|
+
/** Did the user's answer (single-select or multi-select) name the grant choice? */
|
|
114
|
+
function answerIsSessionGrant(answer) {
|
|
115
|
+
return Array.isArray(answer)
|
|
116
|
+
? answer.includes(SESSION_GRANT_CHOICE_LABEL)
|
|
117
|
+
: answer === SESSION_GRANT_CHOICE_LABEL;
|
|
118
|
+
}
|
|
96
119
|
/** E3c — publish tool args: bump type + safety flags (irreversible action). */
|
|
97
120
|
export const publishToolSchema = z.object({
|
|
98
121
|
goal: z.string().optional().describe('Optional release goal (e.g. "Release v1.2.0")'),
|
|
@@ -306,8 +329,12 @@ const editFileSchema = z.object({
|
|
|
306
329
|
});
|
|
307
330
|
/** P0.3 — write_file tool args: create/overwrite a file (confirmed). */
|
|
308
331
|
const writeFileSchema = z.object({
|
|
309
|
-
path: z.string().describe('Path to write, relative to the workspace root (parent directories are created as needed). Overwrites existing content.'),
|
|
310
|
-
content: z.string().describe('The
|
|
332
|
+
path: z.string().describe('Path to write, relative to the workspace root (parent directories are created as needed). Overwrites existing content unless mode is "append".'),
|
|
333
|
+
content: z.string().describe('The new file content. With mode "overwrite" (default) this REPLACES the file; with mode "append" it is added to the end. Keep any SINGLE call small enough to emit (well under ~150 lines): a payload too large for one model output arrives empty and cannot be sent at all.'),
|
|
334
|
+
mode: z
|
|
335
|
+
.enum(['overwrite', 'append'])
|
|
336
|
+
.default('overwrite')
|
|
337
|
+
.describe('Use "append" to build a large document or file in SECTIONS across several calls: the first call creates it (mode "overwrite" or "append" — same thing when the file does not exist yet), then every further section appends. This is the supported way to deliver something bigger than one model output; a single oversized call cannot be delivered.'),
|
|
311
338
|
confirm: z.boolean().default(false).describe('Set true ONLY after the user explicitly confirmed this write via ask_user. CREATING a new file the request asked for needs no confirmation — it is applied directly. Refused without it only when it would REPLACE content that already exists.'),
|
|
312
339
|
});
|
|
313
340
|
/** P0.4 — run_terminal tool args: verify commands + gated shell execution. */
|
|
@@ -315,6 +342,7 @@ const runTerminalSchema = z.object({
|
|
|
315
342
|
command: z.string().min(1).describe('The shell command to run in the workspace — e.g. "npx vitest run tests/foo.test.ts", "npx tsc --noEmit", "npm run build", "git diff --stat". Read-only verify commands run directly. Recoverable workspace commands (dependency installs, mkdir/touch/cp/mv, git add, npm init) also run directly when the request authorized the work; every other state-changing command needs confirm:true after the user approves via ask_user. Destructive/system commands are denied outright.'),
|
|
316
343
|
confirm: z.boolean().default(false).describe('Set true ONLY after the user explicitly confirmed a command you initiated. Required for state-changing commands outside the recoverable workspace set (network fetches, arbitrary code, global installs, anything that leaves the machine).'),
|
|
317
344
|
timeout_ms: z.number().int().min(1000).max(300000).optional().describe('Timeout in ms (default 120000).'),
|
|
345
|
+
verb: z.string().optional().describe('What this command is FOR (e.g. "install java"). When it SUCCEEDS, it is remembered for this verb on this machine, so the next tool_search resolve can offer it instead of re-deriving it. Optional.'),
|
|
318
346
|
});
|
|
319
347
|
/** The C2 requirementState check as a reusable tool. */
|
|
320
348
|
const verifyRequirementSchema = z.object({
|
|
@@ -347,6 +375,11 @@ const registry = new Map();
|
|
|
347
375
|
*/
|
|
348
376
|
export function registerTool(tool) {
|
|
349
377
|
registry.set(tool.name, tool);
|
|
378
|
+
// Record the tool's OWN capability declaration so the name-only lookups
|
|
379
|
+
// (`effectClassOfTool`, `grantCategoryOfTool`) and the descriptor answer from
|
|
380
|
+
// it instead of a hand-maintained map. A tool that declares nothing is left
|
|
381
|
+
// out — absence IS the conservative default.
|
|
382
|
+
declareToolCapability(tool.name, tool.capability);
|
|
350
383
|
}
|
|
351
384
|
/** Look up a tool by name. */
|
|
352
385
|
export function getTool(name) {
|
|
@@ -434,6 +467,7 @@ registerTool({
|
|
|
434
467
|
// same pipeline core + the publish workflow, so any ask auto-runs.
|
|
435
468
|
registerTool({
|
|
436
469
|
name: 'document',
|
|
470
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
437
471
|
description: 'Write documentation for the project (README, API docs, guides) through the agent pipeline',
|
|
438
472
|
category: 'pipeline',
|
|
439
473
|
inputSchema: taskGoalArgs,
|
|
@@ -442,6 +476,7 @@ registerTool({
|
|
|
442
476
|
});
|
|
443
477
|
registerTool({
|
|
444
478
|
name: 'website',
|
|
479
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
445
480
|
description: 'Create or build a website / web app end-to-end through the agent pipeline',
|
|
446
481
|
category: 'pipeline',
|
|
447
482
|
inputSchema: taskGoalArgs,
|
|
@@ -450,6 +485,7 @@ registerTool({
|
|
|
450
485
|
});
|
|
451
486
|
registerTool({
|
|
452
487
|
name: 'analyze',
|
|
488
|
+
capability: { effectClass: 'read' },
|
|
453
489
|
description: 'Analyze the project (state, architecture, comparison against another project) and produce findings',
|
|
454
490
|
category: 'pipeline',
|
|
455
491
|
inputSchema: taskGoalArgs,
|
|
@@ -466,6 +502,7 @@ registerTool({
|
|
|
466
502
|
});
|
|
467
503
|
registerTool({
|
|
468
504
|
name: 'publish',
|
|
505
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external', requires: { credentials: [{ anyOf: ['NPM_TOKEN', 'GITHUB_TOKEN'], note: 'an npm automation token, or a GitHub token for a GitHub release' }] } },
|
|
469
506
|
description: 'Publish a release to npm/GitHub (version bump, changelog, build, publish). IRREVERSIBLE — confirm bump type and target with ask_user first.',
|
|
470
507
|
category: 'workflow',
|
|
471
508
|
inputSchema: publishToolSchema,
|
|
@@ -484,6 +521,7 @@ registerTool({
|
|
|
484
521
|
});
|
|
485
522
|
registerTool({
|
|
486
523
|
name: 'ask_user',
|
|
524
|
+
capability: { effectClass: 'read' },
|
|
487
525
|
description: 'Ask the user a clarifying question with 2–4 choices (multi-select optional). Every surface also offers the user a free-text "Other" answer, so a reply may be their own words instead of one of your choices. Use when a request is ambiguous or missing information.',
|
|
488
526
|
category: 'experience',
|
|
489
527
|
inputSchema: askUserSchema,
|
|
@@ -537,6 +575,14 @@ registerTool({
|
|
|
537
575
|
// question reaches the user once.
|
|
538
576
|
const pending = ctx.pendingConfirmation;
|
|
539
577
|
if (!pending &&
|
|
578
|
+
// Cluster G — an UNSCOPED turn is asking for a required INPUT, not for
|
|
579
|
+
// permission, and the two can look identical to this heuristic ("which
|
|
580
|
+
// folder should I create the app in?" reads as permission-seeking).
|
|
581
|
+
// Suppressing it deadlocks the turn: the user is never asked, so no
|
|
582
|
+
// folder is ever named, so every retry fails for the same reason — the
|
|
583
|
+
// "keeps refusing" loop. A missing workspace is not a decision the model
|
|
584
|
+
// may take on the user's behalf.
|
|
585
|
+
!ctx.workspaceUnscoped &&
|
|
540
586
|
(ctx.writesAuthorized?.authorized === true || Boolean(ctx.envelope)) &&
|
|
541
587
|
detectPermissionSeeking(question) &&
|
|
542
588
|
!IRREVERSIBLE_ACTION_RE.test(question)) {
|
|
@@ -564,18 +610,131 @@ registerTool({
|
|
|
564
610
|
// One confirmation refusal buys exactly ONE ask — clear it before rendering.
|
|
565
611
|
if (pending)
|
|
566
612
|
ctx.pendingConfirmation = undefined;
|
|
613
|
+
// ── Session-grant offer (Copilot-style "allow all for this session") ─────
|
|
614
|
+
// Only when the ask is the CONFIRMATION a state-changing tool demanded
|
|
615
|
+
// (`pending` is set) is there a category to offer. It is ONE extra choice,
|
|
616
|
+
// so the user's explicit go-ahead is RECEIVED, never inferred — an answer
|
|
617
|
+
// that does not name it leaves the ask-every-time default untouched, and an
|
|
618
|
+
// unattended default (returned below) is an assumption, not a grant.
|
|
619
|
+
const grantCategory = pending
|
|
620
|
+
? (pending.category ??
|
|
621
|
+
(pending.tool === 'run_terminal'
|
|
622
|
+
? 'terminal'
|
|
623
|
+
: pending.tool === 'run_cli'
|
|
624
|
+
? null // run_cli states its own category; absent = a grant cannot help
|
|
625
|
+
: 'write'))
|
|
626
|
+
: null;
|
|
627
|
+
const offeredChoices = grantCategory && choices.length < 4
|
|
628
|
+
? [
|
|
629
|
+
...choices,
|
|
630
|
+
{
|
|
631
|
+
label: SESSION_GRANT_CHOICE_LABEL,
|
|
632
|
+
description: `Allow all ${sessionGrantLabel(grantCategory)} for this session — no more asks until the conversation ends.`,
|
|
633
|
+
},
|
|
634
|
+
]
|
|
635
|
+
: choices;
|
|
636
|
+
// Deferred so the registry stays import-light — the writing half of the
|
|
637
|
+
// decision log is only needed when an ask actually reaches a person.
|
|
638
|
+
const { recordDecision, decisionsRecordingEnabled } = await import('../learning/decision-log.js');
|
|
567
639
|
const render = ctx.askUser || (await import('./ask-user.js')).renderAskUser;
|
|
568
|
-
const answer = await render(question,
|
|
640
|
+
const answer = await render(question, offeredChoices, multi_select);
|
|
569
641
|
const picked = Array.isArray(answer.answer) ? answer.answer.join(', ') : answer.answer;
|
|
642
|
+
// E1 — an UNATTENDED answer is an ASSUMPTION, not the user's word. Recorded
|
|
643
|
+
// as one (never as a shown ask with an answer, which is what made a silent
|
|
644
|
+
// default indistinguishable from a real reply), emitted so the turn can
|
|
645
|
+
// disclose it, and worded to the model as an assumption so its answer cannot
|
|
646
|
+
// claim the user chose it.
|
|
647
|
+
if (answer.unattended) {
|
|
648
|
+
ctx.runTrace?.recordAssumption(question, picked);
|
|
649
|
+
ctx.emit?.('autonomy:assumed-default', { question, assumed: picked }, 'tool-loop');
|
|
650
|
+
return (`No user was reachable to answer, so "${picked}" was ASSUMED — it is NOT their answer. ` +
|
|
651
|
+
(answer.custom ??
|
|
652
|
+
`In your written answer, tell the user the question you would have asked and the assumption ` +
|
|
653
|
+
`you made (e.g. "I assumed ${picked} — tell me if you would prefer something else"), then continue with it.`) +
|
|
654
|
+
' Do not record it as a user decision or re-ask the same question.');
|
|
655
|
+
}
|
|
656
|
+
// ── Grant it, if the user picked the session choice ──────────────────────
|
|
657
|
+
// The grant is stored against the session (the same key the intent envelope
|
|
658
|
+
// uses), so it outlives this turn and dies with the conversation. The write
|
|
659
|
+
// happens HERE; the RETURN that tells the model to finish the refused call
|
|
660
|
+
// is issued after the decision is recorded, so the grant itself is a
|
|
661
|
+
// recorded, revisable decision like any other must-ask.
|
|
662
|
+
const granted = Boolean(grantCategory) && answerIsSessionGrant(answer.answer);
|
|
663
|
+
const grant = granted && grantCategory
|
|
664
|
+
? grantSession(ctx.planStore, [grantCategory], 'the user allowed it for the session')
|
|
665
|
+
: null;
|
|
666
|
+
if (granted && grantCategory) {
|
|
667
|
+
ctx.emit?.('autonomy:session-grant', { category: grantCategory, question }, 'tool-loop');
|
|
668
|
+
}
|
|
570
669
|
// Recorded SHOWN and with the answer, so the trace can hand both back when
|
|
571
670
|
// the agent tries to ask it a second time (and so a self-report can say what
|
|
572
671
|
// the user actually replied, instead of "asked 4×, no answer recorded").
|
|
573
672
|
ctx.runTrace?.recordAsk(question, true, picked);
|
|
673
|
+
// Bundle 36 — a MUST-ASK decision is also a PROJECT artifact: recorded so the
|
|
674
|
+
// user can go back through it later and revise it (see
|
|
675
|
+
// `learning/decision-log.ts`). Only a genuine, SHOWN ask reaches here (an
|
|
676
|
+
// unattended default is an assumption and returned above), and the write is
|
|
677
|
+
// best-effort — recording a decision must never break the turn. Deferred
|
|
678
|
+
// import to keep the registry import-light.
|
|
679
|
+
if (decisionsRecordingEnabled() && ctx.cwd) {
|
|
680
|
+
try {
|
|
681
|
+
const answerText = answer.custom
|
|
682
|
+
? picked
|
|
683
|
+
? `${picked} (${answer.custom})`
|
|
684
|
+
: answer.custom
|
|
685
|
+
: picked;
|
|
686
|
+
recordDecision({
|
|
687
|
+
question,
|
|
688
|
+
answer: answerText,
|
|
689
|
+
choices: offeredChoices.map((c) => c.label),
|
|
690
|
+
source: 'ask_user',
|
|
691
|
+
dir: ctx.cwd,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
// Best-effort — never break a turn on a decision-log write.
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// The grant was a real decision — recorded above — so now hand the model the
|
|
699
|
+
// instruction that turns it into work: finish the ONE refused call, then stop
|
|
700
|
+
// asking about this category.
|
|
701
|
+
if (granted && grantCategory) {
|
|
702
|
+
return (`User allowed all ${sessionGrantLabel(grantCategory)} for this session. ` +
|
|
703
|
+
`Retry the refused ${grantCategory === 'terminal' ? 'run_terminal command' : 'write'} ONCE with confirm:true to complete it. ` +
|
|
704
|
+
'Every later action in this category proceeds without asking — do not ask about it again. ' +
|
|
705
|
+
(grant ? sessionGrantNotice(grant) : '') +
|
|
706
|
+
'Irreversible and off-machine actions still ask, and the permission ends with the conversation.');
|
|
707
|
+
}
|
|
708
|
+
// ── Cluster G: the reply may be the FOLDER, and then it is the workspace ──
|
|
709
|
+
// An unscoped turn asks "which folder should I use?". The user answers by
|
|
710
|
+
// typing one (the free-text "Other" field, or a label they edited) — and
|
|
711
|
+
// until now that answer went no further than the model's context: the write
|
|
712
|
+
// tool would refuse again for the same reason and the user would be asked
|
|
713
|
+
// the same question a second time. That repeat is the reported "agent keep
|
|
714
|
+
// refusing even after i attach the folder".
|
|
715
|
+
//
|
|
716
|
+
// So a directory in the answer IS adopted: `ctx.cwd` moves to it and the
|
|
717
|
+
// unscoped flag clears, exactly as `clone_repo` moves `ctx.cwd` after it
|
|
718
|
+
// clones. `directoryFromMessage` is the same conservative rule the server
|
|
719
|
+
// applies to a message (absolute, existing, a DIRECTORY), so a reply that
|
|
720
|
+
// merely mentions a file path cannot relocate the turn.
|
|
721
|
+
const namedFolder = ctx.workspaceUnscoped
|
|
722
|
+
? directoryFromMessage(`${picked}\n${answer.custom ?? ''}`)
|
|
723
|
+
: undefined;
|
|
724
|
+
if (namedFolder) {
|
|
725
|
+
ctx.cwd = namedFolder;
|
|
726
|
+
ctx.workspaceUnscoped = false;
|
|
727
|
+
ctx.emit?.('workspace:adopted', { path: namedFolder, source: 'ask_user' }, 'tool-loop');
|
|
728
|
+
return (`User answered: ${picked}${answer.custom ? ` (custom: ${answer.custom})` : ''}\n` +
|
|
729
|
+
`Workspace adopted: '${namedFolder}'. This chat now works INSIDE that folder — retry the ` +
|
|
730
|
+
'write with a path inside it (the previous refusal no longer applies).');
|
|
731
|
+
}
|
|
574
732
|
return `User answered: ${picked}${answer.custom ? ` (custom: ${answer.custom})` : ''}`;
|
|
575
733
|
},
|
|
576
734
|
});
|
|
577
735
|
registerTool({
|
|
578
736
|
name: 'suggest_followups',
|
|
737
|
+
capability: { effectClass: 'read' },
|
|
579
738
|
description: 'Suggest clickable followup prompts the user can click to send. Use this tool AFTER completing the task — call it last, after your written answer (never before or instead of it). Aim for ~3 suggestions; skip only when there is no sensible next step (e.g. the user said goodbye).',
|
|
580
739
|
category: 'experience',
|
|
581
740
|
inputSchema: suggestFollowupsSchema,
|
|
@@ -594,6 +753,7 @@ registerTool({
|
|
|
594
753
|
// LLM needed — runs entirely on file system access.
|
|
595
754
|
registerTool({
|
|
596
755
|
name: 'code_search',
|
|
756
|
+
capability: { effectClass: 'read' },
|
|
597
757
|
description: 'Search the project for a regex/literal pattern (ripgrep-fast), returning matching file:line:column entries. Use for context gathering: finding definitions, usages, and where things live.',
|
|
598
758
|
category: 'workflow',
|
|
599
759
|
inputSchema: codeSearchSchema,
|
|
@@ -626,6 +786,7 @@ registerTool({
|
|
|
626
786
|
// Backed by `src/tools/coding-tools.ts` (pure fs — no new dependencies).
|
|
627
787
|
registerTool({
|
|
628
788
|
name: 'read_file',
|
|
789
|
+
capability: { effectClass: 'read' },
|
|
629
790
|
description: 'Read files with line numbers. Pass `path` for one file (offset/limit for large files) OR `paths` to read several in one call — batching is one step instead of many. Each file gets its own line count and a continuation offset when truncated. Use to open the actual contents of files code_search or glob located — never guess what a file contains.',
|
|
630
791
|
category: 'workflow',
|
|
631
792
|
inputSchema: readFileSchema,
|
|
@@ -634,6 +795,7 @@ registerTool({
|
|
|
634
795
|
});
|
|
635
796
|
registerTool({
|
|
636
797
|
name: 'list_dir',
|
|
798
|
+
capability: { effectClass: 'read' },
|
|
637
799
|
description: 'List a directory in the workspace (subdirectories first, sorted). Use to explore project structure — what is in this folder, where does this component live.',
|
|
638
800
|
category: 'workflow',
|
|
639
801
|
inputSchema: listDirSchema,
|
|
@@ -642,6 +804,7 @@ registerTool({
|
|
|
642
804
|
});
|
|
643
805
|
registerTool({
|
|
644
806
|
name: 'glob',
|
|
807
|
+
capability: { effectClass: 'read' },
|
|
645
808
|
description: 'Find files by glob pattern relative to the workspace (e.g. "src/**\/*.ts", "tests/*.test.ts"). Use to locate files by shape when code_search content search does not fit.',
|
|
646
809
|
category: 'workflow',
|
|
647
810
|
inputSchema: globSchema,
|
|
@@ -650,6 +813,7 @@ registerTool({
|
|
|
650
813
|
});
|
|
651
814
|
registerTool({
|
|
652
815
|
name: 'edit_file',
|
|
816
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
653
817
|
description: 'Surgical exact-text edit to a file (find old_string, replace with new_string — like str_replace). Make ONE call for ALL edits to a file via `replacements[]` (they apply atomically — all-or-nothing, never a partial edit). Optionally `dry_run` to preview a unified diff without writing. State-changing: confirm with the user via ask_user first, then retry with confirm:true. Use after read_file so the match is exact.',
|
|
654
818
|
category: 'workflow',
|
|
655
819
|
inputSchema: editFileSchema,
|
|
@@ -658,7 +822,8 @@ registerTool({
|
|
|
658
822
|
});
|
|
659
823
|
registerTool({
|
|
660
824
|
name: 'write_file',
|
|
661
|
-
|
|
825
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a create is undone by deleting the file; an overwrite is a git revert" },
|
|
826
|
+
description: 'Create a new file, overwrite one, or APPEND a section to one (parent directories are created). Use mode "append" to deliver a large document or file in sections across several calls — a payload too large for one model output cannot be sent at all, so split it. State-changing: confirm with the user via ask_user first, then retry with confirm:true.',
|
|
662
827
|
category: 'workflow',
|
|
663
828
|
inputSchema: writeFileSchema,
|
|
664
829
|
endsAgentStep: false,
|
|
@@ -666,6 +831,7 @@ registerTool({
|
|
|
666
831
|
});
|
|
667
832
|
registerTool({
|
|
668
833
|
name: 'run_terminal',
|
|
834
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
669
835
|
description: 'Run a shell command in the workspace and see its REAL output — typecheck, a single test file, a build, git diff/status. Use to verify code by actual invocation: run, read the failure, edit, re-run. Verify-class commands (tests/typecheck/build/git-readonly) run directly; state-changing commands need confirm:true after the user approves via ask_user. Destructive/system commands (sudo, git push, rm -rf at dangerous targets, ...) are denied outright.',
|
|
670
836
|
category: 'workflow',
|
|
671
837
|
inputSchema: runTerminalSchema,
|
|
@@ -689,14 +855,51 @@ registerTool({
|
|
|
689
855
|
const { action, goal, steps, id, status, note } = planTodoSchema.parse(args);
|
|
690
856
|
const { defaultPlanStore } = await import('./plan-store.js');
|
|
691
857
|
const store = ctx.planStore ?? defaultPlanStore();
|
|
858
|
+
// C4 — a re-declaration of the SAME goal carries the progress of the steps it
|
|
859
|
+
// re-declares (see PlanStore.create). Disclosed rather than silent: a model
|
|
860
|
+
// that re-declared the plan to RESET it must be told that the harness kept a
|
|
861
|
+
// step's status, or it will keep working around a step it believes is pending.
|
|
862
|
+
let carriedNote = '';
|
|
692
863
|
if (action === 'create') {
|
|
693
864
|
if (!goal || !steps || steps.length === 0) {
|
|
694
865
|
return 'Error: plan_todo create needs goal + steps (id + description each).';
|
|
695
866
|
}
|
|
867
|
+
const before = store.snapshot?.() ?? null;
|
|
696
868
|
store.create(goal, steps);
|
|
869
|
+
const after = store.snapshot?.() ?? null;
|
|
870
|
+
const carried = (after?.steps ?? []).filter((s) => s.status !== 'pending');
|
|
871
|
+
if (before?.goal === after?.goal && carried.length > 0) {
|
|
872
|
+
carriedNote =
|
|
873
|
+
`\n♻️ Carried ${carried.length} step(s) already progressed from this goal's previous plan: ` +
|
|
874
|
+
`${carried.map((s) => `${s.id} (${s.status})`).join(', ')}. ` +
|
|
875
|
+
`Leave them as they are, or call update with status "pending" if you really mean to redo them.`;
|
|
876
|
+
}
|
|
697
877
|
}
|
|
698
878
|
else if (id && status) {
|
|
699
|
-
|
|
879
|
+
// A1 — never accept an update that matched nothing. Silently returning
|
|
880
|
+
// the unchanged plan reads as success to the model (and as tracking to
|
|
881
|
+
// the user) while the counter stays frozen; observed live on the `cal`
|
|
882
|
+
// Android run, where the checklist sat at `1/7` across four successful
|
|
883
|
+
// steps. Resolve the reference first and, when it names no step, say so
|
|
884
|
+
// with the ids that DO exist so the model can re-send it and move on.
|
|
885
|
+
const resolved = store.resolveStepId ? store.resolveStepId(id) : id;
|
|
886
|
+
if (!resolved) {
|
|
887
|
+
const valid = (store.snapshot?.()?.steps ?? []).map((s) => s.id);
|
|
888
|
+
return `Error: plan_todo found no step "${id}" — valid step ids: ${valid.length > 0 ? valid.join(', ') : '(none — declare the plan with action "create" first)'}. Re-send this update with one of those ids.`;
|
|
889
|
+
}
|
|
890
|
+
// A6 — a step that NAMES an artifact is done only once that artifact
|
|
891
|
+
// exists. The plan can otherwise advance past undelivered work (the `cal`
|
|
892
|
+
// run kept going after its APK step never produced an APK). Checked
|
|
893
|
+
// against the workspace; a step with no file-like token is unaffected.
|
|
894
|
+
if (status === 'done' && ctx.cwd) {
|
|
895
|
+
const step = store.snapshot?.()?.steps.find((s) => s.id === resolved);
|
|
896
|
+
const { missing } = checkStepArtifacts(step?.description ?? '', ctx.cwd);
|
|
897
|
+
if (missing.length > 0) {
|
|
898
|
+
return (`Error: step "${resolved}" names artifact(s) that do not exist yet: ${missing.join(', ')}. ` +
|
|
899
|
+
`Create them in ${ctx.cwd}, then mark the step done — a step is not done while its artifact is missing.`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
store.update(resolved, status, note);
|
|
700
903
|
}
|
|
701
904
|
else {
|
|
702
905
|
return 'Error: plan_todo update needs id + status (pending|running|done|blocked).';
|
|
@@ -709,7 +912,7 @@ registerTool({
|
|
|
709
912
|
ctx.emit?.('plan:changed', snapshot);
|
|
710
913
|
}
|
|
711
914
|
}
|
|
712
|
-
return store.toText ? store.toText() : `Plan updated (${action})
|
|
915
|
+
return (store.toText ? store.toText() : `Plan updated (${action}).`) + carriedNote;
|
|
713
916
|
},
|
|
714
917
|
});
|
|
715
918
|
// ─── WS1 (#23) finding tool — a claim, and the verdict the gate computed ────
|
|
@@ -729,6 +932,7 @@ registerTool(createFindingTool());
|
|
|
729
932
|
// `src/tools/web-research.ts` (mocked-fetch tests, no network).
|
|
730
933
|
registerTool({
|
|
731
934
|
name: 'web_search',
|
|
935
|
+
capability: { effectClass: 'read' },
|
|
732
936
|
description: 'Search the web for a query, returning title/url/snippet hits. Uses the first available backend: a configured Brave/Serper/Tavily/Google-CSE key, a self-hosted SearXNG, or the keyless DuckDuckGo fallback. Set the backend with `tools.toolsets.web.provider` or an API-key env var. Use to ground answers in current information.',
|
|
733
937
|
category: 'workflow',
|
|
734
938
|
inputSchema: webSearchSchema,
|
|
@@ -751,6 +955,7 @@ registerTool({
|
|
|
751
955
|
});
|
|
752
956
|
registerTool({
|
|
753
957
|
name: 'read_page',
|
|
958
|
+
capability: { effectClass: 'read' },
|
|
754
959
|
description: 'Fetch a URL and return its readable text (Jina Reader free tier when JINA_API_KEY is set, else a plain fetch). Use to read a full page found by web_search.',
|
|
755
960
|
category: 'workflow',
|
|
756
961
|
inputSchema: readPageSchema,
|
|
@@ -768,6 +973,7 @@ registerTool({
|
|
|
768
973
|
// ─── I2–I5 modality packs (browser / image / voice / vision) ────────────────
|
|
769
974
|
registerTool({
|
|
770
975
|
name: 'browser',
|
|
976
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
771
977
|
description: 'Automate a real browser (open/click/type/extract/screenshot). Requires the optional playwright package (npm i playwright && npx playwright install chromium). Use for tasks that need a live page (login flows, scraping dynamic sites).',
|
|
772
978
|
category: 'workflow',
|
|
773
979
|
inputSchema: browserSchema,
|
|
@@ -785,6 +991,7 @@ registerTool({
|
|
|
785
991
|
});
|
|
786
992
|
registerTool({
|
|
787
993
|
name: 'generate_image',
|
|
994
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
788
995
|
description: 'Generate an image from a text prompt using a bring-your-own-key backend: Google Gemini/Imagen (GEMINI_API_KEY), OpenAI DALL-E/gpt-image (OPENAI_API_KEY), Stability AI (STABILITY_API_KEY), a local ComfyUI/Stable Diffusion (BUFF_IMAGE_API_URL), or the free keyless Pollinations.ai fallback. The first backend whose key is configured wins (override with `modality.image.provider`), and a failing backend falls back to Pollinations. The image is saved to the sandbox images/ dir and the path is returned. When running in a gateway context (WhatsApp/Telegram/etc.), the image is automatically sent back to the originating channel.',
|
|
789
996
|
category: 'workflow',
|
|
790
997
|
inputSchema: imageGenSchema,
|
|
@@ -821,6 +1028,7 @@ registerTool({
|
|
|
821
1028
|
});
|
|
822
1029
|
registerTool({
|
|
823
1030
|
name: 'speak',
|
|
1031
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
824
1032
|
description: 'Synthesize speech for text to an audio file (edge-tts or Piper, both free and local). Requires edge-tts or piper on PATH. Returns the audio file path. When running in a gateway context, the audio is automatically sent back to the originating channel.',
|
|
825
1033
|
category: 'workflow',
|
|
826
1034
|
inputSchema: speakSchema,
|
|
@@ -844,6 +1052,7 @@ registerTool({
|
|
|
844
1052
|
});
|
|
845
1053
|
registerTool({
|
|
846
1054
|
name: 'transcribe',
|
|
1055
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
847
1056
|
description: 'Transcribe an audio file to text (whisper.cpp or faster-whisper, local and free). Requires whisper on PATH. Returns the transcript.',
|
|
848
1057
|
category: 'workflow',
|
|
849
1058
|
inputSchema: transcribeSchema,
|
|
@@ -857,6 +1066,7 @@ registerTool({
|
|
|
857
1066
|
});
|
|
858
1067
|
registerTool({
|
|
859
1068
|
name: 'describe_image',
|
|
1069
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
860
1070
|
description: 'Describe an image (local Ollama llava/llama3.2-vision, or free Gemini vision when BUFF_GEMINI_API_KEY is set). Use for screenshots, diagrams, or any image the user references.',
|
|
861
1071
|
category: 'workflow',
|
|
862
1072
|
inputSchema: describeImageSchema,
|
|
@@ -873,6 +1083,7 @@ registerTool({
|
|
|
873
1083
|
});
|
|
874
1084
|
registerTool({
|
|
875
1085
|
name: 'gateway_send',
|
|
1086
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
876
1087
|
description: 'Send a message (text or image) to a DIFFERENT channel or contact through the gateway (WhatsApp by contact name or number, Telegram, Slack, Discord, email, or any registered alias). Do NOT use this tool to reply to the CURRENT conversation — your text response is automatically sent back to the originating channel. Only call this when the user asks you to deliver a result to SOMEONE ELSE — e.g. "send the poem to Alex on whatsapp" (while you are chatting with Divya). For images, set image_path to the file path (e.g. from generate_image output) — the image is sent with text as caption. AUTHORIZATION: on a gateway turn the sender must hold outbound send authority for that platform (dashboard → Agent Hub → Permissions → Send authority); the result string tells you plainly when a send was refused and why — report that to the user honestly and NEVER claim a message was delivered unless this tool returned its \'✅ sent\' result.',
|
|
877
1088
|
category: 'workflow',
|
|
878
1089
|
inputSchema: gatewaySendSchema,
|
|
@@ -881,6 +1092,7 @@ registerTool({
|
|
|
881
1092
|
});
|
|
882
1093
|
registerTool({
|
|
883
1094
|
name: 'run_cli',
|
|
1095
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
884
1096
|
description: 'Resolve a plain-English request into the exact nuvira CLI command and execute it (start/stop the dashboard or gateway, check status, add a verified sender or send-by-name contact, configure a platform like telegram/whatsapp, run evals, show stats, manage memory/cache, etc.). Use when the user describes a system/tooling task in plain English instead of typing the command — e.g. "stop the dashboard", "add Rahul to whatsapp", "enable telegram support", "run the eval suite". Ambiguous asks and destructive actions are gated: the tool returns what to confirm, then call ask_user and retry with confirm:true when the user agreed.',
|
|
885
1097
|
category: 'workflow',
|
|
886
1098
|
inputSchema: runCliSchema,
|
|
@@ -889,6 +1101,7 @@ registerTool({
|
|
|
889
1101
|
});
|
|
890
1102
|
registerTool({
|
|
891
1103
|
name: 'verify_requirement',
|
|
1104
|
+
capability: { effectClass: 'read' },
|
|
892
1105
|
description: 'Verify whether a user request is complete enough to act on (requirementState: complete vs needs-clarification), returning the missing information.',
|
|
893
1106
|
category: 'experience',
|
|
894
1107
|
inputSchema: verifyRequirementSchema,
|
|
@@ -921,6 +1134,7 @@ registerTool({
|
|
|
921
1134
|
// exec (no shell) make injection structurally impossible.
|
|
922
1135
|
registerTool({
|
|
923
1136
|
name: 'clone_repo',
|
|
1137
|
+
capability: { effectClass: 'external', grantCategory: 'external', reversibleHow: 'delete the cloned directory' },
|
|
924
1138
|
description: 'Clone a git repository (depth-1 shallow) into an ephemeral cache and scope the conversation to it — read_file / list_dir / glob / code_search / run_terminal then operate on the CLONE, so you can assess another project without touching the user\'s workspace. Use when the ask is about a repo that is not the attached project.',
|
|
925
1139
|
category: 'workflow',
|
|
926
1140
|
inputSchema: cloneRepoSchema,
|
|
@@ -936,6 +1150,7 @@ registerTool({
|
|
|
936
1150
|
// while reset --hard/clean remain structurally unexpressible AND deny-guarded.
|
|
937
1151
|
registerTool({
|
|
938
1152
|
name: 'git',
|
|
1153
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external', reversibleHow: 'a local commit is reversible; a push is only as reversible as the remote' },
|
|
939
1154
|
description: 'Structured git operations: status/log (read-only), diff (unified output + a diff card in the GUI), commit (GATED — confirm:true only after the user approved via ask_user; optional files = the accepted subset to stage+commit), and push (GATED — sends a branch to a remote; runs when the user’s own request named the push, otherwise ask via ask_user first). Use for the whole flow: diff → ask_user (accept/reject files) → commit → push.',
|
|
940
1155
|
category: 'workflow',
|
|
941
1156
|
inputSchema: gitToolSchema,
|
|
@@ -1107,6 +1322,7 @@ registerTool({
|
|
|
1107
1322
|
// Browser Supervisor tool
|
|
1108
1323
|
registerTool({
|
|
1109
1324
|
name: 'browser_supervisor',
|
|
1325
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1110
1326
|
description: 'Browser CDP supervisor: monitor health, detect dialogs, track frames.',
|
|
1111
1327
|
category: 'workflow',
|
|
1112
1328
|
inputSchema: z.object({
|
|
@@ -1128,6 +1344,7 @@ registerTool({
|
|
|
1128
1344
|
// Browser Dialog tool
|
|
1129
1345
|
registerTool({
|
|
1130
1346
|
name: 'browser_dialog',
|
|
1347
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1131
1348
|
description: 'Handle native browser dialogs: accept, dismiss, or enter values.',
|
|
1132
1349
|
category: 'workflow',
|
|
1133
1350
|
inputSchema: z.object({
|
|
@@ -1155,6 +1372,7 @@ registerTool({
|
|
|
1155
1372
|
// Camofox tool
|
|
1156
1373
|
registerTool({
|
|
1157
1374
|
name: 'camofox',
|
|
1375
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1158
1376
|
description: 'Camofox anti-detection browser: open pages, screenshots, interact via a11y refs.',
|
|
1159
1377
|
category: 'workflow',
|
|
1160
1378
|
inputSchema: z.object({
|
|
@@ -1182,6 +1400,7 @@ registerTool({
|
|
|
1182
1400
|
// Docker tool
|
|
1183
1401
|
registerTool({
|
|
1184
1402
|
name: 'docker',
|
|
1403
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
1185
1404
|
description: 'Docker management: containers, images, compose, volumes, networks.',
|
|
1186
1405
|
category: 'workflow',
|
|
1187
1406
|
inputSchema: z.object({
|
|
@@ -1296,6 +1515,7 @@ registerTool({
|
|
|
1296
1515
|
// Kanban tool
|
|
1297
1516
|
registerTool({
|
|
1298
1517
|
name: 'kanban',
|
|
1518
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
1299
1519
|
description: 'Kanban board: create boards, manage cards, track priorities.',
|
|
1300
1520
|
category: 'workflow',
|
|
1301
1521
|
inputSchema: z.object({
|
|
@@ -1414,6 +1634,7 @@ registerTool({
|
|
|
1414
1634
|
// Blueprint tool
|
|
1415
1635
|
registerTool({
|
|
1416
1636
|
name: 'blueprint',
|
|
1637
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
1417
1638
|
description: 'Project blueprints: create project templates with file structure.',
|
|
1418
1639
|
category: 'workflow',
|
|
1419
1640
|
inputSchema: z.object({
|
|
@@ -1435,6 +1656,7 @@ registerTool({
|
|
|
1435
1656
|
// File ops tool
|
|
1436
1657
|
registerTool({
|
|
1437
1658
|
name: 'file_ops',
|
|
1659
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
1438
1660
|
description: 'Advanced file operations: state tracking, safe writes, extraction, preview.',
|
|
1439
1661
|
category: 'workflow',
|
|
1440
1662
|
inputSchema: z.object({
|
|
@@ -1645,6 +1867,7 @@ registerTool({
|
|
|
1645
1867
|
// tool-loop.ts trusts). See TOOL_TRUTHFULNESS_TRACKER.md findings #3 and #9.
|
|
1646
1868
|
registerTool({
|
|
1647
1869
|
name: 'messaging',
|
|
1870
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1648
1871
|
description: 'NOT CONNECTED — this tool has no delivery adapter and every send/react refuses with code "not_configured". '
|
|
1649
1872
|
+ 'Do NOT use it to deliver anything and never report a delivery from it. Use gateway_send instead: it sends '
|
|
1650
1873
|
+ 'through the configured gateway (WhatsApp by contact name or number, Telegram, Slack, Discord, email) and '
|
|
@@ -1676,6 +1899,7 @@ registerTool({
|
|
|
1676
1899
|
// Vision tool — comprehensive image analysis, OCR, UI element detection
|
|
1677
1900
|
registerTool({
|
|
1678
1901
|
name: 'vision',
|
|
1902
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1679
1903
|
description: 'Analyze an image and transcribe its text (OCR) through the configured vision backend (a local Ollama '
|
|
1680
1904
|
+ 'vision model or a Gemini key); `analyze` also reports real pixel dimensions. detect-elements, '
|
|
1681
1905
|
+ 'find-element and compare have NO backend and return ok:false with code "not_configured" — an empty '
|
|
@@ -1757,6 +1981,7 @@ registerTool({
|
|
|
1757
1981
|
// Process registry tool
|
|
1758
1982
|
registerTool({
|
|
1759
1983
|
name: 'process_registry',
|
|
1984
|
+
capability: { effectClass: 'read' },
|
|
1760
1985
|
description: 'Track all running processes with metadata and cleanup.',
|
|
1761
1986
|
category: 'workflow',
|
|
1762
1987
|
inputSchema: z.object({
|
|
@@ -1782,6 +2007,7 @@ registerTool({
|
|
|
1782
2007
|
// Code execution tool
|
|
1783
2008
|
registerTool({
|
|
1784
2009
|
name: 'code_execution',
|
|
2010
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
1785
2011
|
description: 'Execute code in sandboxed environment with timeout and resource limits.',
|
|
1786
2012
|
category: 'workflow',
|
|
1787
2013
|
inputSchema: z.object({
|
|
@@ -1839,6 +2065,7 @@ registerTool({
|
|
|
1839
2065
|
// Threat patterns tool
|
|
1840
2066
|
registerTool({
|
|
1841
2067
|
name: 'threat_patterns',
|
|
2068
|
+
capability: { effectClass: 'read' },
|
|
1842
2069
|
description: 'Detect threat patterns: shell execution, external HTTP, obfuscation.',
|
|
1843
2070
|
category: 'workflow',
|
|
1844
2071
|
inputSchema: z.object({
|
|
@@ -1853,6 +2080,7 @@ registerTool({
|
|
|
1853
2080
|
// URL safety tool
|
|
1854
2081
|
registerTool({
|
|
1855
2082
|
name: 'url_safety',
|
|
2083
|
+
capability: { effectClass: 'read' },
|
|
1856
2084
|
description: 'Check URL safety: suspicious TLDs, patterns, length.',
|
|
1857
2085
|
category: 'workflow',
|
|
1858
2086
|
inputSchema: z.object({
|
|
@@ -1867,6 +2095,7 @@ registerTool({
|
|
|
1867
2095
|
// Path security tool
|
|
1868
2096
|
registerTool({
|
|
1869
2097
|
name: 'path_security',
|
|
2098
|
+
capability: { effectClass: 'read' },
|
|
1870
2099
|
description: 'Check path security: traversal, null bytes, directory escape.',
|
|
1871
2100
|
category: 'workflow',
|
|
1872
2101
|
inputSchema: z.object({
|
|
@@ -1882,6 +2111,7 @@ registerTool({
|
|
|
1882
2111
|
// Security score tool
|
|
1883
2112
|
registerTool({
|
|
1884
2113
|
name: 'security_score',
|
|
2114
|
+
capability: { effectClass: 'read' },
|
|
1885
2115
|
description: 'Calculate security score for a file: code injection, secrets, threats.',
|
|
1886
2116
|
category: 'workflow',
|
|
1887
2117
|
inputSchema: z.object({
|
|
@@ -1893,6 +2123,54 @@ registerTool({
|
|
|
1893
2123
|
return JSON.stringify(m.getSecurityScorer().score(filePath));
|
|
1894
2124
|
}),
|
|
1895
2125
|
});
|
|
2126
|
+
// Secret scan tool — LOCAL, dependency-free, on-demand.
|
|
2127
|
+
//
|
|
2128
|
+
// `security_score`/`ast_audit` scan ONE named file and report a score. This is
|
|
2129
|
+
// the other question an agent that writes and commits code actually has to
|
|
2130
|
+
// answer before it commits: "is anything secret-shaped anywhere in here?". It
|
|
2131
|
+
// walks the tree with bounded coverage, needs no `gitleaks`/`ggshield` binary
|
|
2132
|
+
// and no network, and reports masked values only — see src/security/secret-scan.ts.
|
|
2133
|
+
registerTool({
|
|
2134
|
+
name: 'secret_scan',
|
|
2135
|
+
capability: { effectClass: 'read' },
|
|
2136
|
+
description: 'Scan the workspace for secret-shaped strings (API keys, tokens, private keys, credentials in URLs) before committing or publishing. Fully local — no network and no external scanner binary — and every reported value is masked. Returns findings with path:line and severity, plus a summary. Set history:true to scan git HISTORY as well (every committed version of every file) — a key deleted in a later commit is STILL committed and must be rotated. A clean result means "nothing matched a known shape", NOT "no secrets": report it as a lint, never as a guarantee.',
|
|
2137
|
+
category: 'workflow',
|
|
2138
|
+
inputSchema: z.object({
|
|
2139
|
+
path: z.string().optional().describe('Directory to scan (relative to the workspace, or absolute). Default: the workspace root.'),
|
|
2140
|
+
subdir: z.string().optional().describe('Narrow the scan to one subtree, e.g. "src" or "docs".'),
|
|
2141
|
+
maxFiles: z.number().optional().describe('Cap on files visited (default 5000) — a huge repo does not hang the turn.'),
|
|
2142
|
+
history: z.boolean().optional().describe('Also scan git history (reachable blobs) for secrets ever committed. Default false. Use before publishing or when auditing an existing repo.'),
|
|
2143
|
+
maxBlobs: z.number().optional().describe('With history:true, cap on distinct historical blobs read (default 2000).'),
|
|
2144
|
+
}),
|
|
2145
|
+
endsAgentStep: false,
|
|
2146
|
+
run: async (args, ctx) => {
|
|
2147
|
+
const { join } = await import('node:path');
|
|
2148
|
+
const m = await import('../security/secret-scan.js');
|
|
2149
|
+
const { path: target, subdir, maxFiles, history, maxBlobs } = args;
|
|
2150
|
+
const cwd = ctx?.cwd ?? process.cwd();
|
|
2151
|
+
const root = target
|
|
2152
|
+
? (target.startsWith('/') || /^[A-Za-z]:[\\/]/.test(target) ? target : join(cwd, target))
|
|
2153
|
+
: cwd;
|
|
2154
|
+
if (history) {
|
|
2155
|
+
// History only: the tree scan runs first (fast, no git), then history is
|
|
2156
|
+
// appended as its own section so a reader can tell the two apart.
|
|
2157
|
+
const tree = m.scanDirectory(root, {
|
|
2158
|
+
...(subdir ? { subdir } : {}),
|
|
2159
|
+
...(maxFiles ? { maxFiles } : {}),
|
|
2160
|
+
});
|
|
2161
|
+
const hist = m.scanGitHistory(root, {
|
|
2162
|
+
...(subdir ? { subdir } : {}),
|
|
2163
|
+
...(maxBlobs ? { maxBlobs } : {}),
|
|
2164
|
+
});
|
|
2165
|
+
return [m.formatSecretScan(tree), '', '── git history ──', m.formatSecretHistoryScan(hist)].join('\n');
|
|
2166
|
+
}
|
|
2167
|
+
const result = m.scanDirectory(root, {
|
|
2168
|
+
...(subdir ? { subdir } : {}),
|
|
2169
|
+
...(maxFiles ? { maxFiles } : {}),
|
|
2170
|
+
});
|
|
2171
|
+
return m.formatSecretScan(result);
|
|
2172
|
+
},
|
|
2173
|
+
});
|
|
1896
2174
|
// ─── Batch 5: Infrastructure ──────────────────────────────────────────────
|
|
1897
2175
|
// Tool search tool — the tiered-exposure discovery surface. Two actions:
|
|
1898
2176
|
// - search (default): fuzzy-find tools by query (the pre-existing behavior).
|
|
@@ -1904,21 +2182,28 @@ registerTool({
|
|
|
1904
2182
|
// ~85% of per-turn schema tokens saved, zero capability lost.
|
|
1905
2183
|
registerTool({
|
|
1906
2184
|
name: 'tool_search',
|
|
1907
|
-
|
|
2185
|
+
capability: { effectClass: 'read' },
|
|
2186
|
+
description: 'Discover and load capabilities. Actions: "search" finds what the agent can DO by query and returns each hit as a CAPABILITY — its effect (read / local-write / local-state / external / destructive), whether it is reversible and how, any credentials or binaries it needs, whether a session grant can cover it, and a `check` saying whether those needs are actually MET on this machine (run this FIRST for an install / publish / deploy / push task, so a missing executable is known before the work starts rather than halfway through); ' +
|
|
2187
|
+
'"readiness" is the same probe without a search — call it with a query to pre-flight a described task, or with no query to see what the curated install / publish / deploy / push verbs are missing here; ' +
|
|
2188
|
+
'Tools on any MCP server connected this session are included as capabilities too (kind "mcp", with an `invoke` saying how to call them). Installed SKILLS are capabilities too (kind "skill") — call this to find the skill that fits a task, then load it with the skill tool; ' +
|
|
1908
2189
|
'"load" activates a whole toolset (media, browser, channels, docker, ' +
|
|
1909
2190
|
'productivity, publish, core-pipeline, …) for THIS turn — call it before ' +
|
|
1910
2191
|
'using any tool outside the always-available core set. "load" returns ' +
|
|
1911
|
-
'the toolset\'s tool names, which become callable immediately
|
|
2192
|
+
'the toolset\'s tool names, which become callable immediately; ' +
|
|
2193
|
+
'"resolve" checks a command or a verb against THIS machine (is the executable present, which package managers exist here, and any command already learned for it) — run it BEFORE an OS-specific command instead of guessing the platform; "record" stores a command you derived so it is known next time (the table grows by use); "list-commands" and "forget" manage that store.',
|
|
1912
2194
|
category: 'workflow',
|
|
1913
2195
|
inputSchema: z.object({
|
|
1914
|
-
action: z.enum(['search', 'load']).optional().describe('Defaults to "search".'),
|
|
1915
|
-
query: z.string().optional().describe('Search query (action=search)'),
|
|
2196
|
+
action: z.enum(['search', 'load', 'readiness', 'resolve', 'record', 'forget', 'list-commands']).optional().describe('Defaults to "search".'),
|
|
2197
|
+
query: z.string().optional().describe('Search query (action=search|readiness; omit for readiness to check the install/publish/deploy/push verbs)'),
|
|
1916
2198
|
limit: z.number().optional().describe('Max results (action=search)'),
|
|
1917
2199
|
toolset: z.string().optional().describe('Toolset name to load (action=load) — e.g. "media", "browser", "channels"'),
|
|
2200
|
+
command: z.string().optional().describe('A command to check against this machine (action=resolve) or to store (action=record)'),
|
|
2201
|
+
verb: z.string().optional().describe('What the command is FOR (action=resolve|record|forget) — e.g. "install java", or a capability ref'),
|
|
2202
|
+
note: z.string().optional().describe('Optional caveat to store beside a learned command (action=record)'),
|
|
1918
2203
|
}),
|
|
1919
2204
|
endsAgentStep: false,
|
|
1920
2205
|
run: async (args, ctx) => {
|
|
1921
|
-
const { action = 'search', query, limit, toolset } = args;
|
|
2206
|
+
const { action = 'search', query, limit, toolset, command, verb, note } = args;
|
|
1922
2207
|
// ── load: activate a toolset for this turn (tiered exposure) ──
|
|
1923
2208
|
if (action === 'load') {
|
|
1924
2209
|
if (!toolset) {
|
|
@@ -1961,13 +2246,181 @@ registerTool({
|
|
|
1961
2246
|
: 'Toolset recorded, but this context has no live schema loader (loop not in tiered mode) — tools remain governed by the standard enablement gate.',
|
|
1962
2247
|
});
|
|
1963
2248
|
}
|
|
1964
|
-
// ──
|
|
2249
|
+
// ── resolve / record / forget / list-commands: the long-tail OS command
|
|
2250
|
+
// loop. The MODEL derives a command; the harness checks it against the
|
|
2251
|
+
// machine and remembers it. No enumeration, no phrase list — presence is
|
|
2252
|
+
// probed (binaryOnPath) and the learned store is keyed by (verb, os).
|
|
2253
|
+
if (action === 'resolve' || action === 'record' || action === 'forget' || action === 'list-commands') {
|
|
2254
|
+
const { detectMachineFacts } = await import('../learning/machine-facts.js');
|
|
2255
|
+
const facts = detectMachineFacts();
|
|
2256
|
+
const learnedMod = await import('../learning/learned-commands.js');
|
|
2257
|
+
const { leadingBinary, suggestInstallCommand } = await import('../learning/command-adaptation.js');
|
|
2258
|
+
const { binaryOnPath } = await import('../utils/binary-probe.js');
|
|
2259
|
+
if (action === 'list-commands') {
|
|
2260
|
+
return JSON.stringify({ os: facts.os, commands: learnedMod.listLearnedCommands() });
|
|
2261
|
+
}
|
|
2262
|
+
if (action === 'forget') {
|
|
2263
|
+
return JSON.stringify({ os: facts.os, verb: verb ?? '', forgotten: learnedMod.forgetLearnedCommand(verb ?? '') });
|
|
2264
|
+
}
|
|
2265
|
+
if (action === 'record') {
|
|
2266
|
+
const bin = command ? leadingBinary(command) : null;
|
|
2267
|
+
const entry = learnedMod.recordLearnedCommand({
|
|
2268
|
+
verb: verb ?? '',
|
|
2269
|
+
command: command ?? '',
|
|
2270
|
+
...(bin ? { binary: bin } : {}),
|
|
2271
|
+
...(note ? { note } : {}),
|
|
2272
|
+
});
|
|
2273
|
+
return JSON.stringify({
|
|
2274
|
+
os: facts.os,
|
|
2275
|
+
recorded: entry,
|
|
2276
|
+
...(entry
|
|
2277
|
+
? { note: 'Recorded. It will be offered the next time this verb is resolved on this OS.' }
|
|
2278
|
+
: { error: 'record needs a verb and a command.' }),
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
// resolve
|
|
2282
|
+
const bin = command ? leadingBinary(command) : null;
|
|
2283
|
+
const binPresent = bin ? binaryOnPath(bin) : null;
|
|
2284
|
+
const learned = verb ? learnedMod.learnedCommandFor(verb) : null;
|
|
2285
|
+
// When the executable is missing, prefer a manager that is ACTUALLY PRESENT
|
|
2286
|
+
// here — the suggestion is buildable, not a manager the machine lacks.
|
|
2287
|
+
const suggestedInstall = bin && binPresent === false ? suggestInstallCommand(facts, bin) : null;
|
|
2288
|
+
return JSON.stringify({
|
|
2289
|
+
os: facts.os,
|
|
2290
|
+
osName: facts.osName,
|
|
2291
|
+
arch: facts.arch,
|
|
2292
|
+
shell: facts.shellName,
|
|
2293
|
+
packageManagers: facts.packageManagers,
|
|
2294
|
+
...(command ? { proposed: { command, binary: bin, present: binPresent } } : {}),
|
|
2295
|
+
...(suggestedInstall
|
|
2296
|
+
? {
|
|
2297
|
+
suggestedInstall: {
|
|
2298
|
+
manager: suggestedInstall.manager,
|
|
2299
|
+
command: suggestedInstall.command,
|
|
2300
|
+
note: 'The package name can differ from the executable name — adjust if the install fails.',
|
|
2301
|
+
},
|
|
2302
|
+
}
|
|
2303
|
+
: {}),
|
|
2304
|
+
...(learned
|
|
2305
|
+
? { learned: { command: learned.command, binary: learned.binary, source: learned.source, learnedAt: learned.learnedAt } }
|
|
2306
|
+
: {}),
|
|
2307
|
+
advice: command
|
|
2308
|
+
? binPresent
|
|
2309
|
+
? `\`${bin}\` is on this machine — the command can run.`
|
|
2310
|
+
: bin
|
|
2311
|
+
? `\`${bin}\` is NOT on this machine.` +
|
|
2312
|
+
(suggestedInstall
|
|
2313
|
+
? ` Install it with \`${suggestedInstall.command}\` (${suggestedInstall.manager} is present here), or choose another present manager: ${facts.packageManagers.join(', ')}.`
|
|
2314
|
+
: ` No package manager that can install it is present; use what is on the machine.`)
|
|
2315
|
+
: 'Could not identify an executable in this command; check it with a small probe run.'
|
|
2316
|
+
: learned
|
|
2317
|
+
? `A command for "${verb}" on this OS was already derived — use it, or record a better one.`
|
|
2318
|
+
: `No command is known for "${verb}" on this OS yet. Derive one from the machine facts, run it, then record it.`,
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
// ── search: fuzzy find, ENRICHED with capability descriptors ──
|
|
2322
|
+
// The tools returned are the same as before; each is also returned as a
|
|
2323
|
+
// CAPABILITY (effect class + reversibility + requirements + grant), and the
|
|
2324
|
+
// curated high-level ACTIONS (install / publish / deploy / push …) ride
|
|
2325
|
+
// alongside, so the model can discover what it can do — not just which tool
|
|
2326
|
+
// names exist. Discovery only: nothing here decides what runs.
|
|
1965
2327
|
const m = await import('./infra-tools.js');
|
|
1966
2328
|
const engine = m.getToolSearchEngine();
|
|
1967
|
-
// Index from registry
|
|
1968
2329
|
const { listTools } = await import('./registry.js');
|
|
1969
|
-
|
|
1970
|
-
|
|
2330
|
+
const tools = listTools();
|
|
2331
|
+
engine.index(tools.map((t) => ({ name: t.name, description: t.description })));
|
|
2332
|
+
const { capabilityIndex, searchCapabilities } = await import('./capability-registry.js');
|
|
2333
|
+
const index = await capabilityIndex(tools.map((t) => ({ name: t.name, description: t.description, category: t.category })));
|
|
2334
|
+
const { describeGap, probeRequirements } = await import('../learning/requirement-probe.js');
|
|
2335
|
+
// A command the MODEL derived for THIS OS (learned) rides on the hit, so the
|
|
2336
|
+
// per-OS table grows by use rather than by hand.
|
|
2337
|
+
const { detectMachineFacts: detectFacts } = await import('../learning/machine-facts.js');
|
|
2338
|
+
const { learnedCommandFor } = await import('../learning/learned-commands.js');
|
|
2339
|
+
const currentOs = detectFacts().os;
|
|
2340
|
+
const hits = searchCapabilities(index, query || '', limit || 10);
|
|
2341
|
+
// PRE-FLIGHT. Each hit's declared requirements are probed HERE, at discovery
|
|
2342
|
+
// time, so a missing executable is known BEFORE the run starts rather than
|
|
2343
|
+
// after it has written half the work. A binary gap is a hard blocker; a
|
|
2344
|
+
// credential gap is advisory and never blocks — see requirement-probe.ts.
|
|
2345
|
+
const toCapability = (h) => {
|
|
2346
|
+
const requires = h.capability.requires;
|
|
2347
|
+
const hasRequires = Object.keys(requires).length > 0;
|
|
2348
|
+
const readiness = hasRequires ? probeRequirements(requires) : undefined;
|
|
2349
|
+
const learnedOnThisOs = learnedCommandFor(h.capability.ref, currentOs);
|
|
2350
|
+
return {
|
|
2351
|
+
id: h.capability.id,
|
|
2352
|
+
kind: h.capability.kind,
|
|
2353
|
+
ref: h.capability.ref,
|
|
2354
|
+
name: h.capability.name,
|
|
2355
|
+
does: h.capability.oneLiner,
|
|
2356
|
+
effect: h.capability.effectClass,
|
|
2357
|
+
reversible: h.capability.reversible,
|
|
2358
|
+
...(h.capability.reversibleHow ? { undo: h.capability.reversibleHow } : {}),
|
|
2359
|
+
...(h.capability.grantCategory ? { grantable: h.capability.grantCategory } : {}),
|
|
2360
|
+
...(hasRequires ? { requires } : {}),
|
|
2361
|
+
// A foreign (MCP) hit is not called by its own name — say how to reach it.
|
|
2362
|
+
...(h.capability.invoke ? { invoke: h.capability.invoke } : {}),
|
|
2363
|
+
// HOW TO DO IT HERE vs. EVERYWHERE. `onThisMachine` is the entry for the
|
|
2364
|
+
// OS the agent is actually running on, resolved from the same per-OS map the
|
|
2365
|
+
// rest is declared in, so the model never has to infer which package manager
|
|
2366
|
+
// exists from the platform name. `platforms` carries the others, present only
|
|
2367
|
+
// where the OS genuinely determines the command.
|
|
2368
|
+
...(h.capability.platforms
|
|
2369
|
+
? {
|
|
2370
|
+
onThisMachine: h.capability.platforms[process.platform],
|
|
2371
|
+
platforms: h.capability.platforms,
|
|
2372
|
+
}
|
|
2373
|
+
: {}),
|
|
2374
|
+
// A command the MODEL derived for this OS rides ALONGSIDE the declared
|
|
2375
|
+
// one: the table grows by use, and the model can see both and choose.
|
|
2376
|
+
...(learnedOnThisOs
|
|
2377
|
+
? {
|
|
2378
|
+
learnedOnThisMachine: {
|
|
2379
|
+
command: learnedOnThisOs.command,
|
|
2380
|
+
...(learnedOnThisOs.binary ? { binary: learnedOnThisOs.binary } : {}),
|
|
2381
|
+
source: learnedOnThisOs.source,
|
|
2382
|
+
},
|
|
2383
|
+
}
|
|
2384
|
+
: {}),
|
|
2385
|
+
...(readiness
|
|
2386
|
+
? {
|
|
2387
|
+
check: {
|
|
2388
|
+
ready: readiness.ready,
|
|
2389
|
+
...(readiness.gaps.length > 0 ? { gaps: readiness.gaps.map(describeGap) } : {}),
|
|
2390
|
+
...(readiness.ask.length > 0 ? { ask: readiness.ask } : {}),
|
|
2391
|
+
},
|
|
2392
|
+
}
|
|
2393
|
+
: {}),
|
|
2394
|
+
matched: h.matched,
|
|
2395
|
+
};
|
|
2396
|
+
};
|
|
2397
|
+
// ── readiness: the same hits, reported as a PRE-FLIGHT rather than a find ──
|
|
2398
|
+
if (action === 'readiness') {
|
|
2399
|
+
// With no query, check the curated ACTION verbs — the capabilities that
|
|
2400
|
+
// declare requirements (install / publish / deploy / push / credentials).
|
|
2401
|
+
// With no query, check the curated verbs that declare needs. A read-only
|
|
2402
|
+
// action with no requirements (this-machine facts) is not a pre-flight
|
|
2403
|
+
// subject, so it is excluded rather than reported with an empty check.
|
|
2404
|
+
const selected = query
|
|
2405
|
+
? hits
|
|
2406
|
+
: index
|
|
2407
|
+
.filter((c) => c.kind === 'action' && Object.keys(c.requires).length > 0)
|
|
2408
|
+
.map((c) => ({ capability: c, matched: [] }));
|
|
2409
|
+
const caps = selected.map(toCapability);
|
|
2410
|
+
const blocked = caps.filter((c) => c.check && !c.check.ready);
|
|
2411
|
+
return JSON.stringify({
|
|
2412
|
+
checked: caps.length,
|
|
2413
|
+
blocked: blocked.length,
|
|
2414
|
+
summary: blocked.length === 0
|
|
2415
|
+
? 'No missing executables among these capabilities. Any credential notes are advisory — a credential may live in the vault rather than the environment.'
|
|
2416
|
+
: `${blocked.length} of ${caps.length} need an executable that is not on PATH; see each check.gaps.`,
|
|
2417
|
+
capabilities: caps,
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
return JSON.stringify({
|
|
2421
|
+
tools: engine.search(query || '', limit || 10),
|
|
2422
|
+
capabilities: hits.map(toCapability),
|
|
2423
|
+
});
|
|
1971
2424
|
},
|
|
1972
2425
|
});
|
|
1973
2426
|
// Budget config tool
|
|
@@ -1996,6 +2449,7 @@ registerTool({
|
|
|
1996
2449
|
// Fuzzy match tool
|
|
1997
2450
|
registerTool({
|
|
1998
2451
|
name: 'fuzzy_match',
|
|
2452
|
+
capability: { effectClass: 'read' },
|
|
1999
2453
|
description: 'Fuzzy string matching with Levenshtein distance.',
|
|
2000
2454
|
category: 'workflow',
|
|
2001
2455
|
inputSchema: z.object({
|
|
@@ -2029,6 +2483,7 @@ registerTool({
|
|
|
2029
2483
|
// OSV check tool
|
|
2030
2484
|
registerTool({
|
|
2031
2485
|
name: 'osv_check',
|
|
2486
|
+
capability: { effectClass: 'read' },
|
|
2032
2487
|
description: 'Check for known vulnerabilities in npm packages via OSV API.',
|
|
2033
2488
|
category: 'workflow',
|
|
2034
2489
|
inputSchema: z.object({
|
|
@@ -2058,6 +2513,7 @@ registerTool({
|
|
|
2058
2513
|
// Image source tool
|
|
2059
2514
|
registerTool({
|
|
2060
2515
|
name: 'image_source',
|
|
2516
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2061
2517
|
description: 'Detect image source: format, size, is screenshot.',
|
|
2062
2518
|
category: 'workflow',
|
|
2063
2519
|
inputSchema: z.object({
|
|
@@ -2073,6 +2529,7 @@ registerTool({
|
|
|
2073
2529
|
// Discord tool
|
|
2074
2530
|
registerTool({
|
|
2075
2531
|
name: 'discord',
|
|
2532
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2076
2533
|
description: 'Discord server management: list servers, channels, members, messages, roles. Send messages.',
|
|
2077
2534
|
category: 'workflow',
|
|
2078
2535
|
inputSchema: z.object({
|
|
@@ -2100,6 +2557,7 @@ registerTool({
|
|
|
2100
2557
|
// Home Assistant tool
|
|
2101
2558
|
registerTool({
|
|
2102
2559
|
name: 'homeassistant',
|
|
2560
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2103
2561
|
description: 'Home Assistant smart home: list entities, get state, call services (turn_on/off, set_temperature).',
|
|
2104
2562
|
category: 'workflow',
|
|
2105
2563
|
inputSchema: z.object({
|
|
@@ -2128,6 +2586,7 @@ registerTool({
|
|
|
2128
2586
|
// Microsoft Graph tool
|
|
2129
2587
|
registerTool({
|
|
2130
2588
|
name: 'microsoft_graph',
|
|
2589
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2131
2590
|
description: 'Microsoft Graph API: email, calendar, OneDrive, contacts.',
|
|
2132
2591
|
category: 'workflow',
|
|
2133
2592
|
inputSchema: z.object({
|
|
@@ -2178,6 +2637,7 @@ registerTool({
|
|
|
2178
2637
|
// Feishu document tool
|
|
2179
2638
|
registerTool({
|
|
2180
2639
|
name: 'feishu_doc',
|
|
2640
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2181
2641
|
description: 'Feishu document management: create, read, update, delete documents.',
|
|
2182
2642
|
category: 'workflow',
|
|
2183
2643
|
inputSchema: z.object({
|
|
@@ -2202,6 +2662,7 @@ registerTool({
|
|
|
2202
2662
|
// Feishu drive tool
|
|
2203
2663
|
registerTool({
|
|
2204
2664
|
name: 'feishu_drive',
|
|
2665
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2205
2666
|
description: 'Feishu drive management: list files, create folders, move, copy, search.',
|
|
2206
2667
|
category: 'workflow',
|
|
2207
2668
|
inputSchema: z.object({
|
|
@@ -2231,6 +2692,7 @@ registerTool({
|
|
|
2231
2692
|
// Video generation tool
|
|
2232
2693
|
registerTool({
|
|
2233
2694
|
name: 'video_generate',
|
|
2695
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2234
2696
|
description: 'Generate videos from text prompts or images. Supports FAL/BFL providers. When running in a gateway context and a video URL is returned, the video is automatically downloaded and sent back to the originating channel.',
|
|
2235
2697
|
category: 'workflow',
|
|
2236
2698
|
inputSchema: z.object({
|
|
@@ -2339,6 +2801,7 @@ registerTool({
|
|
|
2339
2801
|
// NeuTTS synthesis tool
|
|
2340
2802
|
registerTool({
|
|
2341
2803
|
name: 'neutts_synth',
|
|
2804
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2342
2805
|
description: 'High-quality text-to-speech synthesis via NeuTTS.',
|
|
2343
2806
|
category: 'workflow',
|
|
2344
2807
|
inputSchema: z.object({
|
|
@@ -2362,6 +2825,7 @@ registerTool({
|
|
|
2362
2825
|
// Terminal tool — multi-environment execution
|
|
2363
2826
|
registerTool({
|
|
2364
2827
|
name: 'terminal',
|
|
2828
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
2365
2829
|
description: 'Execute commands in local, Docker, Modal, SSH environments with background support.',
|
|
2366
2830
|
category: 'workflow',
|
|
2367
2831
|
inputSchema: z.object({
|
|
@@ -2416,6 +2880,7 @@ registerTool({
|
|
|
2416
2880
|
// Send message tool — cross-channel messaging
|
|
2417
2881
|
registerTool({
|
|
2418
2882
|
name: 'send_message',
|
|
2883
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2419
2884
|
description: 'Send messages to Telegram, Discord, Slack, WhatsApp, Email. Supports media attachments (images, videos, audio, documents) on WhatsApp.',
|
|
2420
2885
|
category: 'workflow',
|
|
2421
2886
|
inputSchema: z.object({
|
|
@@ -2677,6 +3142,7 @@ registerTool({
|
|
|
2677
3142
|
// MCP Tool — Dynamic MCP server connection and tool invocation
|
|
2678
3143
|
registerTool({
|
|
2679
3144
|
name: 'mcp_tool',
|
|
3145
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2680
3146
|
description: 'Connect to external MCP servers and invoke their tools — stdio, HTTP, or SSE transport.',
|
|
2681
3147
|
category: 'workflow',
|
|
2682
3148
|
inputSchema: z.object({
|
|
@@ -2704,6 +3170,7 @@ registerTool({
|
|
|
2704
3170
|
// Computer Use — Desktop automation via cua-driver
|
|
2705
3171
|
registerTool({
|
|
2706
3172
|
name: 'computer_use',
|
|
3173
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2707
3174
|
description: 'Desktop control via cua-driver — screenshots, mouse, keyboard, scroll without stealing focus.',
|
|
2708
3175
|
category: 'workflow',
|
|
2709
3176
|
inputSchema: z.object({
|
|
@@ -2792,6 +3259,7 @@ registerTool({
|
|
|
2792
3259
|
// OpenRouter Client — Multi-LLM routing
|
|
2793
3260
|
registerTool({
|
|
2794
3261
|
name: 'openrouter_client',
|
|
3262
|
+
capability: { effectClass: 'external', grantCategory: 'external' },
|
|
2795
3263
|
description: 'Route LLM calls to multiple providers via OpenRouter — cost optimization and fallback.',
|
|
2796
3264
|
category: 'workflow',
|
|
2797
3265
|
inputSchema: z.object({
|
|
@@ -2816,6 +3284,7 @@ registerTool({
|
|
|
2816
3284
|
// TTS Streaming — Provider-agnostic streaming TTS
|
|
2817
3285
|
registerTool({
|
|
2818
3286
|
name: 'tts_streaming',
|
|
3287
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2819
3288
|
description: 'Convert text to speech using various providers — streaming and batch modes.',
|
|
2820
3289
|
category: 'workflow',
|
|
2821
3290
|
inputSchema: z.object({
|
|
@@ -2894,6 +3363,7 @@ registerTool({
|
|
|
2894
3363
|
// that cannot, so the model can distinguish "read it" from "could not read it".
|
|
2895
3364
|
registerTool({
|
|
2896
3365
|
name: 'read_extract',
|
|
3366
|
+
capability: { effectClass: 'read' },
|
|
2897
3367
|
description: 'Extract text from a document. WORKS for PDF, DOCX, XLSX, PPTX, HTML, CSV/TSV, JSON, XML, YAML and '
|
|
2898
3368
|
+ 'plain text/Markdown; PDF and spreadsheet table rows are preserved so values stay paired with their '
|
|
2899
3369
|
+ 'columns. Returns success:false with a typed code when it cannot read the file — "no_data" means a '
|
|
@@ -2914,6 +3384,141 @@ registerTool({
|
|
|
2914
3384
|
return mgr.extract(filePath, { ocr: Boolean(ocr) }).then((r) => JSON.stringify(r));
|
|
2915
3385
|
}),
|
|
2916
3386
|
});
|
|
3387
|
+
// Knowledge — tag-scoped, vectorized retrieval over the user's own documents
|
|
3388
|
+
//
|
|
3389
|
+
// WHY A TOOL. The user's flow is "bring documents, tag them, ask questions scoped
|
|
3390
|
+
// to that tag." Retrieval must be something the AGENT can drive mid-task without
|
|
3391
|
+
// a CLI round-trip, and it must pay the read+embed cost ONCE per document rather
|
|
3392
|
+
// than re-reading the file into context every turn. This tool is that surface:
|
|
3393
|
+
// `add` ingests under a tag (documents are extracted, chunked, embedded once),
|
|
3394
|
+
// `query` returns the tag's top-k chunks with their source files so the model can
|
|
3395
|
+
// answer the data half from the user's data and the generic half from its own
|
|
3396
|
+
// knowledge / web-research.
|
|
3397
|
+
registerTool({
|
|
3398
|
+
name: 'knowledge',
|
|
3399
|
+
description: 'Work with the USER\'S OWN tagged documents. `add` ingests files/folders under a tag (extract → '
|
|
3400
|
+
+ 'chunk → embed, done once); `query` retrieves the tag\'s most relevant passages, labeled with their '
|
|
3401
|
+
+ 'source file and section, so the data part of an answer comes from the user\'s data and the general '
|
|
3402
|
+
+ 'part from model knowledge or web-research. `toc` lists a tag\'s documents and their headings; `read` '
|
|
3403
|
+
+ 'returns a whole document or ONE named section VERBATIM — use it when you have to implement or quote '
|
|
3404
|
+
+ 'what a document says, rather than summarizing it from similar passages. `sync` brings a tag up to '
|
|
3405
|
+
+ 'date with its folders (unchanged files are not re-embedded; files that vanished stop being served). '
|
|
3406
|
+
+ 'Actions: add | query | toc | read | sync | list | forget | stats. Tags are normalized '
|
|
3407
|
+
+ '(spaces/capitals/underscores become lowercase-hyphen), e.g. "Dheeraj health report" → '
|
|
3408
|
+
+ '"dheeraj-health-report".',
|
|
3409
|
+
category: 'workflow',
|
|
3410
|
+
inputSchema: z.object({
|
|
3411
|
+
action: z.enum(['add', 'query', 'toc', 'read', 'sync', 'list', 'forget', 'stats']).describe('Action'),
|
|
3412
|
+
tag: z.string().optional().describe('Tag/hashtag scoping the data (e.g. "dheeraj-health-report")'),
|
|
3413
|
+
paths: z.array(z.string()).optional().describe('Files or folders to ingest (action: add | sync)'),
|
|
3414
|
+
question: z.string().optional().describe('Question to answer from the tagged data (action: query)'),
|
|
3415
|
+
doc: z.string().optional().describe('Document to read or list: doc id, full path, or basename (action: read | toc)'),
|
|
3416
|
+
section: z.string().optional().describe('Section heading to read verbatim (action: read) — omit for the whole document'),
|
|
3417
|
+
topK: z.number().optional().describe('Chunks to retrieve (action: query, default 6)'),
|
|
3418
|
+
}),
|
|
3419
|
+
endsAgentStep: false,
|
|
3420
|
+
run: (args) => import('../learning/knowledge-base.js').then(async (m) => {
|
|
3421
|
+
const { action, tag, paths, question, doc, section, topK } = args;
|
|
3422
|
+
switch (action) {
|
|
3423
|
+
case 'add': {
|
|
3424
|
+
if (!tag)
|
|
3425
|
+
return 'tag is required for action: add';
|
|
3426
|
+
if (!paths || paths.length === 0)
|
|
3427
|
+
return 'paths is required for action: add';
|
|
3428
|
+
const result = await m.ingestKnowledge(tag, paths);
|
|
3429
|
+
const skipped = result.skipped.length
|
|
3430
|
+
? `\nSkipped: ${result.skipped.map((s) => `${s.path} (${s.reason})`).join('; ')}`
|
|
3431
|
+
: '';
|
|
3432
|
+
// Unchanged documents are named rather than silently folded in: "0 chunks"
|
|
3433
|
+
// and "nothing needed re-embedding" are different answers, and only one
|
|
3434
|
+
// of them means the ingest did nothing.
|
|
3435
|
+
const unchanged = result.unchanged > 0 ? `, ${result.unchanged} unchanged (not re-embedded)` : '';
|
|
3436
|
+
return `✅ Ingested ${result.files} document(s) / ${result.chunks} chunk(s)${unchanged} under tag '${result.tag}'.`
|
|
3437
|
+
+ `\nQuery it with action: query, tag: '${result.tag}'.${skipped}`;
|
|
3438
|
+
}
|
|
3439
|
+
case 'query': {
|
|
3440
|
+
if (!tag)
|
|
3441
|
+
return 'tag is required for action: query';
|
|
3442
|
+
if (!question)
|
|
3443
|
+
return 'question is required for action: query';
|
|
3444
|
+
const hits = await m.queryKnowledge(tag, question, { topK });
|
|
3445
|
+
return m.formatKnowledgeContext(tag, hits);
|
|
3446
|
+
}
|
|
3447
|
+
case 'toc': {
|
|
3448
|
+
if (!tag)
|
|
3449
|
+
return 'tag is required for action: toc';
|
|
3450
|
+
const docs = m.listKnowledgeSections(tag, doc);
|
|
3451
|
+
if (docs.length === 0) {
|
|
3452
|
+
return doc
|
|
3453
|
+
? `No document '${doc}' under tag '${tag}'.`
|
|
3454
|
+
: `No documents under tag '${tag}' — ingest some with action: add.`;
|
|
3455
|
+
}
|
|
3456
|
+
return docs
|
|
3457
|
+
.map((d) => {
|
|
3458
|
+
const head = `📄 ${d.title} (${d.chunks} chunk(s)) — ${d.path}`;
|
|
3459
|
+
const sections = d.sections.length
|
|
3460
|
+
? d.sections
|
|
3461
|
+
.map((s) => `\n ${' '.repeat(Math.max(0, s.level - 1))}§ ${s.headingPath ? `${s.headingPath} > ` : ''}${s.title}`)
|
|
3462
|
+
.join('')
|
|
3463
|
+
: '\n (no headings — read the whole document)';
|
|
3464
|
+
return head + sections;
|
|
3465
|
+
})
|
|
3466
|
+
.join('\n');
|
|
3467
|
+
}
|
|
3468
|
+
case 'read': {
|
|
3469
|
+
if (!tag)
|
|
3470
|
+
return 'tag is required for action: read';
|
|
3471
|
+
if (!doc)
|
|
3472
|
+
return 'doc is required for action: read (a doc id, full path, or basename — see action: toc)';
|
|
3473
|
+
const read = m.readKnowledgeDocument(tag, doc, section);
|
|
3474
|
+
if (!read.ok)
|
|
3475
|
+
return read.reason;
|
|
3476
|
+
const header = `📖 ${read.path}${read.headingPath ? ` — §${read.headingPath}` : ''}`;
|
|
3477
|
+
const note = read.truncated ? `\n\n[... truncated at ${m.MAX_READ_CHARS} characters ...]` : '';
|
|
3478
|
+
return `${header}\n\n${read.text}${note}`;
|
|
3479
|
+
}
|
|
3480
|
+
case 'sync': {
|
|
3481
|
+
if (!tag)
|
|
3482
|
+
return 'tag is required for action: sync';
|
|
3483
|
+
if (!paths || paths.length === 0)
|
|
3484
|
+
return 'paths is required for action: sync';
|
|
3485
|
+
const synced = await m.syncKnowledgeTag(tag, paths, { onChunkSkipped: () => { } });
|
|
3486
|
+
const removed = synced.removed.length
|
|
3487
|
+
? `\nRemoved (no longer present): ${synced.removed.map((p) => `${p}`).join('; ')}`
|
|
3488
|
+
: '';
|
|
3489
|
+
const skipped = synced.skipped.length
|
|
3490
|
+
? `\nSkipped: ${synced.skipped.map((s) => `${s.path} (${s.reason})`).join('; ')}`
|
|
3491
|
+
: '';
|
|
3492
|
+
return `🔄 Synced '${synced.tag}': ${synced.added} added, ${synced.changed} changed, `
|
|
3493
|
+
+ `${synced.unchanged} unchanged, ${synced.chunks} chunk(s) written.${removed}${skipped}`;
|
|
3494
|
+
}
|
|
3495
|
+
case 'list': {
|
|
3496
|
+
const tags = m.listKnowledgeTags();
|
|
3497
|
+
if (tags.length === 0)
|
|
3498
|
+
return 'No knowledge tags yet. Ingest documents with action: add.';
|
|
3499
|
+
return tags
|
|
3500
|
+
.map((t) => ` • ${t.tag} — ${t.chunkCount} chunk(s) across ${t.documents.length} document(s)`)
|
|
3501
|
+
.join('\n');
|
|
3502
|
+
}
|
|
3503
|
+
case 'forget': {
|
|
3504
|
+
if (!tag)
|
|
3505
|
+
return 'tag is required for action: forget';
|
|
3506
|
+
const removed = await m.forgetKnowledgeTag(tag);
|
|
3507
|
+
return removed ? `🗑️ Removed knowledge tag '${m.normalizeKnowledgeTag(tag)}'.` : `No knowledge tag '${tag}' found.`;
|
|
3508
|
+
}
|
|
3509
|
+
case 'stats': {
|
|
3510
|
+
if (!tag)
|
|
3511
|
+
return 'tag is required for action: stats';
|
|
3512
|
+
const entry = m.getKnowledgeTag(tag);
|
|
3513
|
+
if (!entry)
|
|
3514
|
+
return `No knowledge tag '${tag}' found.`;
|
|
3515
|
+
return JSON.stringify(entry);
|
|
3516
|
+
}
|
|
3517
|
+
default:
|
|
3518
|
+
return `Unknown action '${action}'`;
|
|
3519
|
+
}
|
|
3520
|
+
}),
|
|
3521
|
+
});
|
|
2917
3522
|
// Credential Files — Credential file management
|
|
2918
3523
|
registerTool({
|
|
2919
3524
|
name: 'credential_files',
|
|
@@ -2964,6 +3569,7 @@ registerTool({
|
|
|
2964
3569
|
});
|
|
2965
3570
|
registerTool({
|
|
2966
3571
|
name: 'search_memory',
|
|
3572
|
+
capability: { effectClass: 'read' },
|
|
2967
3573
|
description: 'Search memories by content, type, or tags. Searches BOTH memory stores: entries added with add_memory (keyword/tag) and facts learned from earlier sessions (semantic), so a memory recorded by another path is still found.',
|
|
2968
3574
|
category: 'workflow',
|
|
2969
3575
|
inputSchema: z.object({
|
|
@@ -3025,6 +3631,7 @@ registerTool({
|
|
|
3025
3631
|
});
|
|
3026
3632
|
registerTool({
|
|
3027
3633
|
name: 'list_memories',
|
|
3634
|
+
capability: { effectClass: 'read' },
|
|
3028
3635
|
description: 'List all memory entries with optional filters.',
|
|
3029
3636
|
category: 'workflow',
|
|
3030
3637
|
inputSchema: z.object({
|
|
@@ -3041,6 +3648,7 @@ registerTool({
|
|
|
3041
3648
|
});
|
|
3042
3649
|
registerTool({
|
|
3043
3650
|
name: 'memory_stats',
|
|
3651
|
+
capability: { effectClass: 'read' },
|
|
3044
3652
|
description: 'Get memory statistics.',
|
|
3045
3653
|
category: 'workflow',
|
|
3046
3654
|
inputSchema: z.object({}),
|