agent-nuvira 3.3.13 → 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/orchestrator.d.ts +31 -0
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +74 -126
- 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 +765 -33
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/cli-program.d.ts.map +1 -1
- package/dist/cli/cli-program.js +12 -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 +86 -0
- 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/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 +9 -1
- package/dist/cli/knowledge.d.ts.map +1 -1
- package/dist/cli/knowledge.js +93 -2
- package/dist/cli/knowledge.js.map +1 -1
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +47 -12
- 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/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/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 +33 -0
- package/dist/gateway/hook-contract.d.ts.map +1 -1
- package/dist/gateway/hook-contract.js +97 -0
- package/dist/gateway/hook-contract.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 +37 -0
- package/dist/inference/interface.d.ts.map +1 -1
- 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/route-resolver.d.ts +30 -0
- package/dist/inference/route-resolver.d.ts.map +1 -1
- package/dist/inference/route-resolver.js +158 -1
- package/dist/inference/route-resolver.js.map +1 -1
- package/dist/inference/tools.d.ts +16 -1
- package/dist/inference/tools.d.ts.map +1 -1
- package/dist/inference/tools.js +51 -18
- 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 +68 -24
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +301 -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 +22 -1
- package/dist/learning/error-repair.d.ts.map +1 -1
- package/dist/learning/error-repair.js +37 -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 +229 -3
- package/dist/learning/knowledge-base.d.ts.map +1 -1
- package/dist/learning/knowledge-base.js +593 -24
- package/dist/learning/knowledge-base.js.map +1 -1
- 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.map +1 -1
- package/dist/learning/model-selection.js +41 -0
- 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/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/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/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/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 +20 -2
- package/dist/tools/plan-store.d.ts.map +1 -1
- package/dist/tools/plan-store.js +51 -7
- 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 +486 -26
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/remediation-ladder.d.ts.map +1 -1
- package/dist/tools/remediation-ladder.js +8 -0
- package/dist/tools/remediation-ladder.js.map +1 -1
- 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 +265 -10
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +321 -13
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +867 -66
- package/dist/tools/tool-loop.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 +11 -0
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +362 -11
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +170 -1
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.d.ts +11 -2
- package/dist/web-dashboard/workspace-guard.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.js +24 -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-zjbdJnO-.js +210 -0
- package/src/web-dashboard/public/assets/index-zjbdJnO-.js.map +1 -0
- package/src/web-dashboard/public/index.html +1 -1
- package/src/web-dashboard/public/assets/index-DhiuR4S2.js +0 -210
- package/src/web-dashboard/public/assets/index-DhiuR4S2.js.map +0 -1
package/dist/tools/registry.js
CHANGED
|
@@ -27,7 +27,16 @@ 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';
|
|
32
41
|
import { checkStepArtifacts } from './step-artifact.js';
|
|
33
42
|
// ─── Tool definitions ───────────────────────────────────────────────────────
|
|
@@ -94,6 +103,19 @@ const askUserSchema = z.object({
|
|
|
94
103
|
.describe('2–4 answer choices the user can pick from'),
|
|
95
104
|
multi_select: z.boolean().default(false).describe('Allow multiple selections (checkbox)'),
|
|
96
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
|
+
}
|
|
97
119
|
/** E3c — publish tool args: bump type + safety flags (irreversible action). */
|
|
98
120
|
export const publishToolSchema = z.object({
|
|
99
121
|
goal: z.string().optional().describe('Optional release goal (e.g. "Release v1.2.0")'),
|
|
@@ -307,8 +329,12 @@ const editFileSchema = z.object({
|
|
|
307
329
|
});
|
|
308
330
|
/** P0.3 — write_file tool args: create/overwrite a file (confirmed). */
|
|
309
331
|
const writeFileSchema = z.object({
|
|
310
|
-
path: z.string().describe('Path to write, relative to the workspace root (parent directories are created as needed). Overwrites existing content.'),
|
|
311
|
-
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.'),
|
|
312
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.'),
|
|
313
339
|
});
|
|
314
340
|
/** P0.4 — run_terminal tool args: verify commands + gated shell execution. */
|
|
@@ -316,6 +342,7 @@ const runTerminalSchema = z.object({
|
|
|
316
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.'),
|
|
317
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).'),
|
|
318
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.'),
|
|
319
346
|
});
|
|
320
347
|
/** The C2 requirementState check as a reusable tool. */
|
|
321
348
|
const verifyRequirementSchema = z.object({
|
|
@@ -348,6 +375,11 @@ const registry = new Map();
|
|
|
348
375
|
*/
|
|
349
376
|
export function registerTool(tool) {
|
|
350
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);
|
|
351
383
|
}
|
|
352
384
|
/** Look up a tool by name. */
|
|
353
385
|
export function getTool(name) {
|
|
@@ -435,6 +467,7 @@ registerTool({
|
|
|
435
467
|
// same pipeline core + the publish workflow, so any ask auto-runs.
|
|
436
468
|
registerTool({
|
|
437
469
|
name: 'document',
|
|
470
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
438
471
|
description: 'Write documentation for the project (README, API docs, guides) through the agent pipeline',
|
|
439
472
|
category: 'pipeline',
|
|
440
473
|
inputSchema: taskGoalArgs,
|
|
@@ -443,6 +476,7 @@ registerTool({
|
|
|
443
476
|
});
|
|
444
477
|
registerTool({
|
|
445
478
|
name: 'website',
|
|
479
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
446
480
|
description: 'Create or build a website / web app end-to-end through the agent pipeline',
|
|
447
481
|
category: 'pipeline',
|
|
448
482
|
inputSchema: taskGoalArgs,
|
|
@@ -451,6 +485,7 @@ registerTool({
|
|
|
451
485
|
});
|
|
452
486
|
registerTool({
|
|
453
487
|
name: 'analyze',
|
|
488
|
+
capability: { effectClass: 'read' },
|
|
454
489
|
description: 'Analyze the project (state, architecture, comparison against another project) and produce findings',
|
|
455
490
|
category: 'pipeline',
|
|
456
491
|
inputSchema: taskGoalArgs,
|
|
@@ -467,6 +502,7 @@ registerTool({
|
|
|
467
502
|
});
|
|
468
503
|
registerTool({
|
|
469
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' }] } },
|
|
470
506
|
description: 'Publish a release to npm/GitHub (version bump, changelog, build, publish). IRREVERSIBLE — confirm bump type and target with ask_user first.',
|
|
471
507
|
category: 'workflow',
|
|
472
508
|
inputSchema: publishToolSchema,
|
|
@@ -485,6 +521,7 @@ registerTool({
|
|
|
485
521
|
});
|
|
486
522
|
registerTool({
|
|
487
523
|
name: 'ask_user',
|
|
524
|
+
capability: { effectClass: 'read' },
|
|
488
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.',
|
|
489
526
|
category: 'experience',
|
|
490
527
|
inputSchema: askUserSchema,
|
|
@@ -538,6 +575,14 @@ registerTool({
|
|
|
538
575
|
// question reaches the user once.
|
|
539
576
|
const pending = ctx.pendingConfirmation;
|
|
540
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 &&
|
|
541
586
|
(ctx.writesAuthorized?.authorized === true || Boolean(ctx.envelope)) &&
|
|
542
587
|
detectPermissionSeeking(question) &&
|
|
543
588
|
!IRREVERSIBLE_ACTION_RE.test(question)) {
|
|
@@ -565,18 +610,131 @@ registerTool({
|
|
|
565
610
|
// One confirmation refusal buys exactly ONE ask — clear it before rendering.
|
|
566
611
|
if (pending)
|
|
567
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');
|
|
568
639
|
const render = ctx.askUser || (await import('./ask-user.js')).renderAskUser;
|
|
569
|
-
const answer = await render(question,
|
|
640
|
+
const answer = await render(question, offeredChoices, multi_select);
|
|
570
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
|
+
}
|
|
571
669
|
// Recorded SHOWN and with the answer, so the trace can hand both back when
|
|
572
670
|
// the agent tries to ask it a second time (and so a self-report can say what
|
|
573
671
|
// the user actually replied, instead of "asked 4×, no answer recorded").
|
|
574
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
|
+
}
|
|
575
732
|
return `User answered: ${picked}${answer.custom ? ` (custom: ${answer.custom})` : ''}`;
|
|
576
733
|
},
|
|
577
734
|
});
|
|
578
735
|
registerTool({
|
|
579
736
|
name: 'suggest_followups',
|
|
737
|
+
capability: { effectClass: 'read' },
|
|
580
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).',
|
|
581
739
|
category: 'experience',
|
|
582
740
|
inputSchema: suggestFollowupsSchema,
|
|
@@ -595,6 +753,7 @@ registerTool({
|
|
|
595
753
|
// LLM needed — runs entirely on file system access.
|
|
596
754
|
registerTool({
|
|
597
755
|
name: 'code_search',
|
|
756
|
+
capability: { effectClass: 'read' },
|
|
598
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.',
|
|
599
758
|
category: 'workflow',
|
|
600
759
|
inputSchema: codeSearchSchema,
|
|
@@ -627,6 +786,7 @@ registerTool({
|
|
|
627
786
|
// Backed by `src/tools/coding-tools.ts` (pure fs — no new dependencies).
|
|
628
787
|
registerTool({
|
|
629
788
|
name: 'read_file',
|
|
789
|
+
capability: { effectClass: 'read' },
|
|
630
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.',
|
|
631
791
|
category: 'workflow',
|
|
632
792
|
inputSchema: readFileSchema,
|
|
@@ -635,6 +795,7 @@ registerTool({
|
|
|
635
795
|
});
|
|
636
796
|
registerTool({
|
|
637
797
|
name: 'list_dir',
|
|
798
|
+
capability: { effectClass: 'read' },
|
|
638
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.',
|
|
639
800
|
category: 'workflow',
|
|
640
801
|
inputSchema: listDirSchema,
|
|
@@ -643,6 +804,7 @@ registerTool({
|
|
|
643
804
|
});
|
|
644
805
|
registerTool({
|
|
645
806
|
name: 'glob',
|
|
807
|
+
capability: { effectClass: 'read' },
|
|
646
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.',
|
|
647
809
|
category: 'workflow',
|
|
648
810
|
inputSchema: globSchema,
|
|
@@ -651,6 +813,7 @@ registerTool({
|
|
|
651
813
|
});
|
|
652
814
|
registerTool({
|
|
653
815
|
name: 'edit_file',
|
|
816
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
654
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.',
|
|
655
818
|
category: 'workflow',
|
|
656
819
|
inputSchema: editFileSchema,
|
|
@@ -659,7 +822,8 @@ registerTool({
|
|
|
659
822
|
});
|
|
660
823
|
registerTool({
|
|
661
824
|
name: 'write_file',
|
|
662
|
-
|
|
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.',
|
|
663
827
|
category: 'workflow',
|
|
664
828
|
inputSchema: writeFileSchema,
|
|
665
829
|
endsAgentStep: false,
|
|
@@ -667,6 +831,7 @@ registerTool({
|
|
|
667
831
|
});
|
|
668
832
|
registerTool({
|
|
669
833
|
name: 'run_terminal',
|
|
834
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
670
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.',
|
|
671
836
|
category: 'workflow',
|
|
672
837
|
inputSchema: runTerminalSchema,
|
|
@@ -690,11 +855,25 @@ registerTool({
|
|
|
690
855
|
const { action, goal, steps, id, status, note } = planTodoSchema.parse(args);
|
|
691
856
|
const { defaultPlanStore } = await import('./plan-store.js');
|
|
692
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 = '';
|
|
693
863
|
if (action === 'create') {
|
|
694
864
|
if (!goal || !steps || steps.length === 0) {
|
|
695
865
|
return 'Error: plan_todo create needs goal + steps (id + description each).';
|
|
696
866
|
}
|
|
867
|
+
const before = store.snapshot?.() ?? null;
|
|
697
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
|
+
}
|
|
698
877
|
}
|
|
699
878
|
else if (id && status) {
|
|
700
879
|
// A1 — never accept an update that matched nothing. Silently returning
|
|
@@ -733,7 +912,7 @@ registerTool({
|
|
|
733
912
|
ctx.emit?.('plan:changed', snapshot);
|
|
734
913
|
}
|
|
735
914
|
}
|
|
736
|
-
return store.toText ? store.toText() : `Plan updated (${action})
|
|
915
|
+
return (store.toText ? store.toText() : `Plan updated (${action}).`) + carriedNote;
|
|
737
916
|
},
|
|
738
917
|
});
|
|
739
918
|
// ─── WS1 (#23) finding tool — a claim, and the verdict the gate computed ────
|
|
@@ -753,6 +932,7 @@ registerTool(createFindingTool());
|
|
|
753
932
|
// `src/tools/web-research.ts` (mocked-fetch tests, no network).
|
|
754
933
|
registerTool({
|
|
755
934
|
name: 'web_search',
|
|
935
|
+
capability: { effectClass: 'read' },
|
|
756
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.',
|
|
757
937
|
category: 'workflow',
|
|
758
938
|
inputSchema: webSearchSchema,
|
|
@@ -775,6 +955,7 @@ registerTool({
|
|
|
775
955
|
});
|
|
776
956
|
registerTool({
|
|
777
957
|
name: 'read_page',
|
|
958
|
+
capability: { effectClass: 'read' },
|
|
778
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.',
|
|
779
960
|
category: 'workflow',
|
|
780
961
|
inputSchema: readPageSchema,
|
|
@@ -792,6 +973,7 @@ registerTool({
|
|
|
792
973
|
// ─── I2–I5 modality packs (browser / image / voice / vision) ────────────────
|
|
793
974
|
registerTool({
|
|
794
975
|
name: 'browser',
|
|
976
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
795
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).',
|
|
796
978
|
category: 'workflow',
|
|
797
979
|
inputSchema: browserSchema,
|
|
@@ -809,6 +991,7 @@ registerTool({
|
|
|
809
991
|
});
|
|
810
992
|
registerTool({
|
|
811
993
|
name: 'generate_image',
|
|
994
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
812
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.',
|
|
813
996
|
category: 'workflow',
|
|
814
997
|
inputSchema: imageGenSchema,
|
|
@@ -845,6 +1028,7 @@ registerTool({
|
|
|
845
1028
|
});
|
|
846
1029
|
registerTool({
|
|
847
1030
|
name: 'speak',
|
|
1031
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
848
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.',
|
|
849
1033
|
category: 'workflow',
|
|
850
1034
|
inputSchema: speakSchema,
|
|
@@ -868,6 +1052,7 @@ registerTool({
|
|
|
868
1052
|
});
|
|
869
1053
|
registerTool({
|
|
870
1054
|
name: 'transcribe',
|
|
1055
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
871
1056
|
description: 'Transcribe an audio file to text (whisper.cpp or faster-whisper, local and free). Requires whisper on PATH. Returns the transcript.',
|
|
872
1057
|
category: 'workflow',
|
|
873
1058
|
inputSchema: transcribeSchema,
|
|
@@ -881,6 +1066,7 @@ registerTool({
|
|
|
881
1066
|
});
|
|
882
1067
|
registerTool({
|
|
883
1068
|
name: 'describe_image',
|
|
1069
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
884
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.',
|
|
885
1071
|
category: 'workflow',
|
|
886
1072
|
inputSchema: describeImageSchema,
|
|
@@ -897,6 +1083,7 @@ registerTool({
|
|
|
897
1083
|
});
|
|
898
1084
|
registerTool({
|
|
899
1085
|
name: 'gateway_send',
|
|
1086
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
900
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.',
|
|
901
1088
|
category: 'workflow',
|
|
902
1089
|
inputSchema: gatewaySendSchema,
|
|
@@ -905,6 +1092,7 @@ registerTool({
|
|
|
905
1092
|
});
|
|
906
1093
|
registerTool({
|
|
907
1094
|
name: 'run_cli',
|
|
1095
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
908
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.',
|
|
909
1097
|
category: 'workflow',
|
|
910
1098
|
inputSchema: runCliSchema,
|
|
@@ -913,6 +1101,7 @@ registerTool({
|
|
|
913
1101
|
});
|
|
914
1102
|
registerTool({
|
|
915
1103
|
name: 'verify_requirement',
|
|
1104
|
+
capability: { effectClass: 'read' },
|
|
916
1105
|
description: 'Verify whether a user request is complete enough to act on (requirementState: complete vs needs-clarification), returning the missing information.',
|
|
917
1106
|
category: 'experience',
|
|
918
1107
|
inputSchema: verifyRequirementSchema,
|
|
@@ -945,6 +1134,7 @@ registerTool({
|
|
|
945
1134
|
// exec (no shell) make injection structurally impossible.
|
|
946
1135
|
registerTool({
|
|
947
1136
|
name: 'clone_repo',
|
|
1137
|
+
capability: { effectClass: 'external', grantCategory: 'external', reversibleHow: 'delete the cloned directory' },
|
|
948
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.',
|
|
949
1139
|
category: 'workflow',
|
|
950
1140
|
inputSchema: cloneRepoSchema,
|
|
@@ -960,6 +1150,7 @@ registerTool({
|
|
|
960
1150
|
// while reset --hard/clean remain structurally unexpressible AND deny-guarded.
|
|
961
1151
|
registerTool({
|
|
962
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' },
|
|
963
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.',
|
|
964
1155
|
category: 'workflow',
|
|
965
1156
|
inputSchema: gitToolSchema,
|
|
@@ -1131,6 +1322,7 @@ registerTool({
|
|
|
1131
1322
|
// Browser Supervisor tool
|
|
1132
1323
|
registerTool({
|
|
1133
1324
|
name: 'browser_supervisor',
|
|
1325
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1134
1326
|
description: 'Browser CDP supervisor: monitor health, detect dialogs, track frames.',
|
|
1135
1327
|
category: 'workflow',
|
|
1136
1328
|
inputSchema: z.object({
|
|
@@ -1152,6 +1344,7 @@ registerTool({
|
|
|
1152
1344
|
// Browser Dialog tool
|
|
1153
1345
|
registerTool({
|
|
1154
1346
|
name: 'browser_dialog',
|
|
1347
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1155
1348
|
description: 'Handle native browser dialogs: accept, dismiss, or enter values.',
|
|
1156
1349
|
category: 'workflow',
|
|
1157
1350
|
inputSchema: z.object({
|
|
@@ -1179,6 +1372,7 @@ registerTool({
|
|
|
1179
1372
|
// Camofox tool
|
|
1180
1373
|
registerTool({
|
|
1181
1374
|
name: 'camofox',
|
|
1375
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1182
1376
|
description: 'Camofox anti-detection browser: open pages, screenshots, interact via a11y refs.',
|
|
1183
1377
|
category: 'workflow',
|
|
1184
1378
|
inputSchema: z.object({
|
|
@@ -1206,6 +1400,7 @@ registerTool({
|
|
|
1206
1400
|
// Docker tool
|
|
1207
1401
|
registerTool({
|
|
1208
1402
|
name: 'docker',
|
|
1403
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
1209
1404
|
description: 'Docker management: containers, images, compose, volumes, networks.',
|
|
1210
1405
|
category: 'workflow',
|
|
1211
1406
|
inputSchema: z.object({
|
|
@@ -1320,6 +1515,7 @@ registerTool({
|
|
|
1320
1515
|
// Kanban tool
|
|
1321
1516
|
registerTool({
|
|
1322
1517
|
name: 'kanban',
|
|
1518
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
1323
1519
|
description: 'Kanban board: create boards, manage cards, track priorities.',
|
|
1324
1520
|
category: 'workflow',
|
|
1325
1521
|
inputSchema: z.object({
|
|
@@ -1438,6 +1634,7 @@ registerTool({
|
|
|
1438
1634
|
// Blueprint tool
|
|
1439
1635
|
registerTool({
|
|
1440
1636
|
name: 'blueprint',
|
|
1637
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
1441
1638
|
description: 'Project blueprints: create project templates with file structure.',
|
|
1442
1639
|
category: 'workflow',
|
|
1443
1640
|
inputSchema: z.object({
|
|
@@ -1459,6 +1656,7 @@ registerTool({
|
|
|
1459
1656
|
// File ops tool
|
|
1460
1657
|
registerTool({
|
|
1461
1658
|
name: 'file_ops',
|
|
1659
|
+
capability: { effectClass: 'local-write', grantCategory: 'write', reversibleHow: "a git revert" },
|
|
1462
1660
|
description: 'Advanced file operations: state tracking, safe writes, extraction, preview.',
|
|
1463
1661
|
category: 'workflow',
|
|
1464
1662
|
inputSchema: z.object({
|
|
@@ -1669,6 +1867,7 @@ registerTool({
|
|
|
1669
1867
|
// tool-loop.ts trusts). See TOOL_TRUTHFULNESS_TRACKER.md findings #3 and #9.
|
|
1670
1868
|
registerTool({
|
|
1671
1869
|
name: 'messaging',
|
|
1870
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1672
1871
|
description: 'NOT CONNECTED — this tool has no delivery adapter and every send/react refuses with code "not_configured". '
|
|
1673
1872
|
+ 'Do NOT use it to deliver anything and never report a delivery from it. Use gateway_send instead: it sends '
|
|
1674
1873
|
+ 'through the configured gateway (WhatsApp by contact name or number, Telegram, Slack, Discord, email) and '
|
|
@@ -1700,6 +1899,7 @@ registerTool({
|
|
|
1700
1899
|
// Vision tool — comprehensive image analysis, OCR, UI element detection
|
|
1701
1900
|
registerTool({
|
|
1702
1901
|
name: 'vision',
|
|
1902
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
1703
1903
|
description: 'Analyze an image and transcribe its text (OCR) through the configured vision backend (a local Ollama '
|
|
1704
1904
|
+ 'vision model or a Gemini key); `analyze` also reports real pixel dimensions. detect-elements, '
|
|
1705
1905
|
+ 'find-element and compare have NO backend and return ok:false with code "not_configured" — an empty '
|
|
@@ -1781,6 +1981,7 @@ registerTool({
|
|
|
1781
1981
|
// Process registry tool
|
|
1782
1982
|
registerTool({
|
|
1783
1983
|
name: 'process_registry',
|
|
1984
|
+
capability: { effectClass: 'read' },
|
|
1784
1985
|
description: 'Track all running processes with metadata and cleanup.',
|
|
1785
1986
|
category: 'workflow',
|
|
1786
1987
|
inputSchema: z.object({
|
|
@@ -1806,6 +2007,7 @@ registerTool({
|
|
|
1806
2007
|
// Code execution tool
|
|
1807
2008
|
registerTool({
|
|
1808
2009
|
name: 'code_execution',
|
|
2010
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
1809
2011
|
description: 'Execute code in sandboxed environment with timeout and resource limits.',
|
|
1810
2012
|
category: 'workflow',
|
|
1811
2013
|
inputSchema: z.object({
|
|
@@ -1863,6 +2065,7 @@ registerTool({
|
|
|
1863
2065
|
// Threat patterns tool
|
|
1864
2066
|
registerTool({
|
|
1865
2067
|
name: 'threat_patterns',
|
|
2068
|
+
capability: { effectClass: 'read' },
|
|
1866
2069
|
description: 'Detect threat patterns: shell execution, external HTTP, obfuscation.',
|
|
1867
2070
|
category: 'workflow',
|
|
1868
2071
|
inputSchema: z.object({
|
|
@@ -1877,6 +2080,7 @@ registerTool({
|
|
|
1877
2080
|
// URL safety tool
|
|
1878
2081
|
registerTool({
|
|
1879
2082
|
name: 'url_safety',
|
|
2083
|
+
capability: { effectClass: 'read' },
|
|
1880
2084
|
description: 'Check URL safety: suspicious TLDs, patterns, length.',
|
|
1881
2085
|
category: 'workflow',
|
|
1882
2086
|
inputSchema: z.object({
|
|
@@ -1891,6 +2095,7 @@ registerTool({
|
|
|
1891
2095
|
// Path security tool
|
|
1892
2096
|
registerTool({
|
|
1893
2097
|
name: 'path_security',
|
|
2098
|
+
capability: { effectClass: 'read' },
|
|
1894
2099
|
description: 'Check path security: traversal, null bytes, directory escape.',
|
|
1895
2100
|
category: 'workflow',
|
|
1896
2101
|
inputSchema: z.object({
|
|
@@ -1906,6 +2111,7 @@ registerTool({
|
|
|
1906
2111
|
// Security score tool
|
|
1907
2112
|
registerTool({
|
|
1908
2113
|
name: 'security_score',
|
|
2114
|
+
capability: { effectClass: 'read' },
|
|
1909
2115
|
description: 'Calculate security score for a file: code injection, secrets, threats.',
|
|
1910
2116
|
category: 'workflow',
|
|
1911
2117
|
inputSchema: z.object({
|
|
@@ -1926,6 +2132,7 @@ registerTool({
|
|
|
1926
2132
|
// and no network, and reports masked values only — see src/security/secret-scan.ts.
|
|
1927
2133
|
registerTool({
|
|
1928
2134
|
name: 'secret_scan',
|
|
2135
|
+
capability: { effectClass: 'read' },
|
|
1929
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.',
|
|
1930
2137
|
category: 'workflow',
|
|
1931
2138
|
inputSchema: z.object({
|
|
@@ -1975,21 +2182,28 @@ registerTool({
|
|
|
1975
2182
|
// ~85% of per-turn schema tokens saved, zero capability lost.
|
|
1976
2183
|
registerTool({
|
|
1977
2184
|
name: 'tool_search',
|
|
1978
|
-
|
|
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; ' +
|
|
1979
2189
|
'"load" activates a whole toolset (media, browser, channels, docker, ' +
|
|
1980
2190
|
'productivity, publish, core-pipeline, …) for THIS turn — call it before ' +
|
|
1981
2191
|
'using any tool outside the always-available core set. "load" returns ' +
|
|
1982
|
-
'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.',
|
|
1983
2194
|
category: 'workflow',
|
|
1984
2195
|
inputSchema: z.object({
|
|
1985
|
-
action: z.enum(['search', 'load']).optional().describe('Defaults to "search".'),
|
|
1986
|
-
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)'),
|
|
1987
2198
|
limit: z.number().optional().describe('Max results (action=search)'),
|
|
1988
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)'),
|
|
1989
2203
|
}),
|
|
1990
2204
|
endsAgentStep: false,
|
|
1991
2205
|
run: async (args, ctx) => {
|
|
1992
|
-
const { action = 'search', query, limit, toolset } = args;
|
|
2206
|
+
const { action = 'search', query, limit, toolset, command, verb, note } = args;
|
|
1993
2207
|
// ── load: activate a toolset for this turn (tiered exposure) ──
|
|
1994
2208
|
if (action === 'load') {
|
|
1995
2209
|
if (!toolset) {
|
|
@@ -2032,13 +2246,181 @@ registerTool({
|
|
|
2032
2246
|
: 'Toolset recorded, but this context has no live schema loader (loop not in tiered mode) — tools remain governed by the standard enablement gate.',
|
|
2033
2247
|
});
|
|
2034
2248
|
}
|
|
2035
|
-
// ──
|
|
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.
|
|
2036
2327
|
const m = await import('./infra-tools.js');
|
|
2037
2328
|
const engine = m.getToolSearchEngine();
|
|
2038
|
-
// Index from registry
|
|
2039
2329
|
const { listTools } = await import('./registry.js');
|
|
2040
|
-
|
|
2041
|
-
|
|
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
|
+
});
|
|
2042
2424
|
},
|
|
2043
2425
|
});
|
|
2044
2426
|
// Budget config tool
|
|
@@ -2067,6 +2449,7 @@ registerTool({
|
|
|
2067
2449
|
// Fuzzy match tool
|
|
2068
2450
|
registerTool({
|
|
2069
2451
|
name: 'fuzzy_match',
|
|
2452
|
+
capability: { effectClass: 'read' },
|
|
2070
2453
|
description: 'Fuzzy string matching with Levenshtein distance.',
|
|
2071
2454
|
category: 'workflow',
|
|
2072
2455
|
inputSchema: z.object({
|
|
@@ -2100,6 +2483,7 @@ registerTool({
|
|
|
2100
2483
|
// OSV check tool
|
|
2101
2484
|
registerTool({
|
|
2102
2485
|
name: 'osv_check',
|
|
2486
|
+
capability: { effectClass: 'read' },
|
|
2103
2487
|
description: 'Check for known vulnerabilities in npm packages via OSV API.',
|
|
2104
2488
|
category: 'workflow',
|
|
2105
2489
|
inputSchema: z.object({
|
|
@@ -2129,6 +2513,7 @@ registerTool({
|
|
|
2129
2513
|
// Image source tool
|
|
2130
2514
|
registerTool({
|
|
2131
2515
|
name: 'image_source',
|
|
2516
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2132
2517
|
description: 'Detect image source: format, size, is screenshot.',
|
|
2133
2518
|
category: 'workflow',
|
|
2134
2519
|
inputSchema: z.object({
|
|
@@ -2144,6 +2529,7 @@ registerTool({
|
|
|
2144
2529
|
// Discord tool
|
|
2145
2530
|
registerTool({
|
|
2146
2531
|
name: 'discord',
|
|
2532
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2147
2533
|
description: 'Discord server management: list servers, channels, members, messages, roles. Send messages.',
|
|
2148
2534
|
category: 'workflow',
|
|
2149
2535
|
inputSchema: z.object({
|
|
@@ -2171,6 +2557,7 @@ registerTool({
|
|
|
2171
2557
|
// Home Assistant tool
|
|
2172
2558
|
registerTool({
|
|
2173
2559
|
name: 'homeassistant',
|
|
2560
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2174
2561
|
description: 'Home Assistant smart home: list entities, get state, call services (turn_on/off, set_temperature).',
|
|
2175
2562
|
category: 'workflow',
|
|
2176
2563
|
inputSchema: z.object({
|
|
@@ -2199,6 +2586,7 @@ registerTool({
|
|
|
2199
2586
|
// Microsoft Graph tool
|
|
2200
2587
|
registerTool({
|
|
2201
2588
|
name: 'microsoft_graph',
|
|
2589
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2202
2590
|
description: 'Microsoft Graph API: email, calendar, OneDrive, contacts.',
|
|
2203
2591
|
category: 'workflow',
|
|
2204
2592
|
inputSchema: z.object({
|
|
@@ -2249,6 +2637,7 @@ registerTool({
|
|
|
2249
2637
|
// Feishu document tool
|
|
2250
2638
|
registerTool({
|
|
2251
2639
|
name: 'feishu_doc',
|
|
2640
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2252
2641
|
description: 'Feishu document management: create, read, update, delete documents.',
|
|
2253
2642
|
category: 'workflow',
|
|
2254
2643
|
inputSchema: z.object({
|
|
@@ -2273,6 +2662,7 @@ registerTool({
|
|
|
2273
2662
|
// Feishu drive tool
|
|
2274
2663
|
registerTool({
|
|
2275
2664
|
name: 'feishu_drive',
|
|
2665
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2276
2666
|
description: 'Feishu drive management: list files, create folders, move, copy, search.',
|
|
2277
2667
|
category: 'workflow',
|
|
2278
2668
|
inputSchema: z.object({
|
|
@@ -2302,6 +2692,7 @@ registerTool({
|
|
|
2302
2692
|
// Video generation tool
|
|
2303
2693
|
registerTool({
|
|
2304
2694
|
name: 'video_generate',
|
|
2695
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2305
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.',
|
|
2306
2697
|
category: 'workflow',
|
|
2307
2698
|
inputSchema: z.object({
|
|
@@ -2410,6 +2801,7 @@ registerTool({
|
|
|
2410
2801
|
// NeuTTS synthesis tool
|
|
2411
2802
|
registerTool({
|
|
2412
2803
|
name: 'neutts_synth',
|
|
2804
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2413
2805
|
description: 'High-quality text-to-speech synthesis via NeuTTS.',
|
|
2414
2806
|
category: 'workflow',
|
|
2415
2807
|
inputSchema: z.object({
|
|
@@ -2433,6 +2825,7 @@ registerTool({
|
|
|
2433
2825
|
// Terminal tool — multi-environment execution
|
|
2434
2826
|
registerTool({
|
|
2435
2827
|
name: 'terminal',
|
|
2828
|
+
capability: { effectClass: 'local-state', grantCategory: 'terminal' },
|
|
2436
2829
|
description: 'Execute commands in local, Docker, Modal, SSH environments with background support.',
|
|
2437
2830
|
category: 'workflow',
|
|
2438
2831
|
inputSchema: z.object({
|
|
@@ -2487,6 +2880,7 @@ registerTool({
|
|
|
2487
2880
|
// Send message tool — cross-channel messaging
|
|
2488
2881
|
registerTool({
|
|
2489
2882
|
name: 'send_message',
|
|
2883
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2490
2884
|
description: 'Send messages to Telegram, Discord, Slack, WhatsApp, Email. Supports media attachments (images, videos, audio, documents) on WhatsApp.',
|
|
2491
2885
|
category: 'workflow',
|
|
2492
2886
|
inputSchema: z.object({
|
|
@@ -2748,6 +3142,7 @@ registerTool({
|
|
|
2748
3142
|
// MCP Tool — Dynamic MCP server connection and tool invocation
|
|
2749
3143
|
registerTool({
|
|
2750
3144
|
name: 'mcp_tool',
|
|
3145
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2751
3146
|
description: 'Connect to external MCP servers and invoke their tools — stdio, HTTP, or SSE transport.',
|
|
2752
3147
|
category: 'workflow',
|
|
2753
3148
|
inputSchema: z.object({
|
|
@@ -2775,6 +3170,7 @@ registerTool({
|
|
|
2775
3170
|
// Computer Use — Desktop automation via cua-driver
|
|
2776
3171
|
registerTool({
|
|
2777
3172
|
name: 'computer_use',
|
|
3173
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2778
3174
|
description: 'Desktop control via cua-driver — screenshots, mouse, keyboard, scroll without stealing focus.',
|
|
2779
3175
|
category: 'workflow',
|
|
2780
3176
|
inputSchema: z.object({
|
|
@@ -2863,6 +3259,7 @@ registerTool({
|
|
|
2863
3259
|
// OpenRouter Client — Multi-LLM routing
|
|
2864
3260
|
registerTool({
|
|
2865
3261
|
name: 'openrouter_client',
|
|
3262
|
+
capability: { effectClass: 'external', grantCategory: 'external' },
|
|
2866
3263
|
description: 'Route LLM calls to multiple providers via OpenRouter — cost optimization and fallback.',
|
|
2867
3264
|
category: 'workflow',
|
|
2868
3265
|
inputSchema: z.object({
|
|
@@ -2887,6 +3284,7 @@ registerTool({
|
|
|
2887
3284
|
// TTS Streaming — Provider-agnostic streaming TTS
|
|
2888
3285
|
registerTool({
|
|
2889
3286
|
name: 'tts_streaming',
|
|
3287
|
+
capability: { effectClass: 'external', reversible: false, grantCategory: 'external' },
|
|
2890
3288
|
description: 'Convert text to speech using various providers — streaming and batch modes.',
|
|
2891
3289
|
category: 'workflow',
|
|
2892
3290
|
inputSchema: z.object({
|
|
@@ -2965,6 +3363,7 @@ registerTool({
|
|
|
2965
3363
|
// that cannot, so the model can distinguish "read it" from "could not read it".
|
|
2966
3364
|
registerTool({
|
|
2967
3365
|
name: 'read_extract',
|
|
3366
|
+
capability: { effectClass: 'read' },
|
|
2968
3367
|
description: 'Extract text from a document. WORKS for PDF, DOCX, XLSX, PPTX, HTML, CSV/TSV, JSON, XML, YAML and '
|
|
2969
3368
|
+ 'plain text/Markdown; PDF and spreadsheet table rows are preserved so values stay paired with their '
|
|
2970
3369
|
+ 'columns. Returns success:false with a typed code when it cannot read the file — "no_data" means a '
|
|
@@ -2997,23 +3396,29 @@ registerTool({
|
|
|
2997
3396
|
// knowledge / web-research.
|
|
2998
3397
|
registerTool({
|
|
2999
3398
|
name: 'knowledge',
|
|
3000
|
-
description: '
|
|
3001
|
-
+ '
|
|
3002
|
-
+ '
|
|
3003
|
-
+ 'part from model knowledge or web-research.
|
|
3004
|
-
+ '
|
|
3005
|
-
+ '
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
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'),
|
|
3009
3412
|
tag: z.string().optional().describe('Tag/hashtag scoping the data (e.g. "dheeraj-health-report")'),
|
|
3010
|
-
paths: z.array(z.string()).optional().describe('Files or folders to ingest (action: add)'),
|
|
3413
|
+
paths: z.array(z.string()).optional().describe('Files or folders to ingest (action: add | sync)'),
|
|
3011
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'),
|
|
3012
3417
|
topK: z.number().optional().describe('Chunks to retrieve (action: query, default 6)'),
|
|
3013
3418
|
}),
|
|
3014
3419
|
endsAgentStep: false,
|
|
3015
3420
|
run: (args) => import('../learning/knowledge-base.js').then(async (m) => {
|
|
3016
|
-
const { action, tag, paths, question, topK } = args;
|
|
3421
|
+
const { action, tag, paths, question, doc, section, topK } = args;
|
|
3017
3422
|
switch (action) {
|
|
3018
3423
|
case 'add': {
|
|
3019
3424
|
if (!tag)
|
|
@@ -3024,7 +3429,11 @@ registerTool({
|
|
|
3024
3429
|
const skipped = result.skipped.length
|
|
3025
3430
|
? `\nSkipped: ${result.skipped.map((s) => `${s.path} (${s.reason})`).join('; ')}`
|
|
3026
3431
|
: '';
|
|
3027
|
-
|
|
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}'.`
|
|
3028
3437
|
+ `\nQuery it with action: query, tag: '${result.tag}'.${skipped}`;
|
|
3029
3438
|
}
|
|
3030
3439
|
case 'query': {
|
|
@@ -3035,6 +3444,54 @@ registerTool({
|
|
|
3035
3444
|
const hits = await m.queryKnowledge(tag, question, { topK });
|
|
3036
3445
|
return m.formatKnowledgeContext(tag, hits);
|
|
3037
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
|
+
}
|
|
3038
3495
|
case 'list': {
|
|
3039
3496
|
const tags = m.listKnowledgeTags();
|
|
3040
3497
|
if (tags.length === 0)
|
|
@@ -3112,6 +3569,7 @@ registerTool({
|
|
|
3112
3569
|
});
|
|
3113
3570
|
registerTool({
|
|
3114
3571
|
name: 'search_memory',
|
|
3572
|
+
capability: { effectClass: 'read' },
|
|
3115
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.',
|
|
3116
3574
|
category: 'workflow',
|
|
3117
3575
|
inputSchema: z.object({
|
|
@@ -3173,6 +3631,7 @@ registerTool({
|
|
|
3173
3631
|
});
|
|
3174
3632
|
registerTool({
|
|
3175
3633
|
name: 'list_memories',
|
|
3634
|
+
capability: { effectClass: 'read' },
|
|
3176
3635
|
description: 'List all memory entries with optional filters.',
|
|
3177
3636
|
category: 'workflow',
|
|
3178
3637
|
inputSchema: z.object({
|
|
@@ -3189,6 +3648,7 @@ registerTool({
|
|
|
3189
3648
|
});
|
|
3190
3649
|
registerTool({
|
|
3191
3650
|
name: 'memory_stats',
|
|
3651
|
+
capability: { effectClass: 'read' },
|
|
3192
3652
|
description: 'Get memory statistics.',
|
|
3193
3653
|
category: 'workflow',
|
|
3194
3654
|
inputSchema: z.object({}),
|