negotium 0.1.7 → 0.1.9
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 +26 -2
- package/dist/canonical-mcp-bridge.js +14 -0
- package/dist/canonical-mcp-bridge.js.map +9 -0
- package/dist/chunk-10ct8814.js +429 -0
- package/dist/chunk-10ct8814.js.map +11 -0
- package/dist/chunk-18fzftjf.js +214 -0
- package/dist/chunk-18fzftjf.js.map +10 -0
- package/dist/chunk-1k7t7bnx.js +97 -0
- package/dist/chunk-1k7t7bnx.js.map +10 -0
- package/dist/chunk-1msje2zf.js +23 -0
- package/dist/chunk-1msje2zf.js.map +10 -0
- package/dist/chunk-2dgkah1z.js +706 -0
- package/dist/chunk-2dgkah1z.js.map +14 -0
- package/dist/chunk-3t4dtm26.js +61 -0
- package/dist/chunk-3t4dtm26.js.map +10 -0
- package/dist/chunk-3vn2vnj1.js +51 -0
- package/dist/chunk-3vn2vnj1.js.map +10 -0
- package/dist/chunk-6hknphtc.js +2054 -0
- package/dist/chunk-6hknphtc.js.map +29 -0
- package/dist/chunk-6tfzpcvk.js +2149 -0
- package/dist/chunk-6tfzpcvk.js.map +15 -0
- package/dist/chunk-8g0c4as0.js +23 -0
- package/dist/chunk-8g0c4as0.js.map +10 -0
- package/dist/chunk-9spx3h9n.js +279 -0
- package/dist/chunk-9spx3h9n.js.map +10 -0
- package/dist/chunk-c500xe9z.js +33 -0
- package/dist/chunk-c500xe9z.js.map +10 -0
- package/dist/chunk-e9b2xpff.js +147 -0
- package/dist/chunk-e9b2xpff.js.map +10 -0
- package/dist/chunk-edb07sjp.js +567 -0
- package/dist/chunk-edb07sjp.js.map +14 -0
- package/dist/chunk-eexqsee7.js +368 -0
- package/dist/chunk-eexqsee7.js.map +11 -0
- package/dist/chunk-efyxk97v.js +500 -0
- package/dist/chunk-efyxk97v.js.map +13 -0
- package/dist/chunk-epqxwkvh.js +3534 -0
- package/dist/chunk-epqxwkvh.js.map +20 -0
- package/dist/chunk-f1pzh3xh.js +115 -0
- package/dist/chunk-f1pzh3xh.js.map +10 -0
- package/dist/chunk-f5xgprg1.js +59 -0
- package/dist/chunk-f5xgprg1.js.map +10 -0
- package/dist/chunk-f97qybwk.js +584 -0
- package/dist/chunk-f97qybwk.js.map +11 -0
- package/dist/chunk-fvc7ncxd.js +252 -0
- package/dist/chunk-fvc7ncxd.js.map +10 -0
- package/dist/chunk-g7pvfj5e.js +574 -0
- package/dist/chunk-g7pvfj5e.js.map +14 -0
- package/dist/chunk-h8vmb9gf.js +315 -0
- package/dist/chunk-h8vmb9gf.js.map +10 -0
- package/dist/chunk-hgw7n8cm.js +43 -0
- package/dist/chunk-hgw7n8cm.js.map +10 -0
- package/dist/chunk-hsq7j9jk.js +126 -0
- package/dist/chunk-hsq7j9jk.js.map +10 -0
- package/dist/chunk-hwy3n31w.js +21 -0
- package/dist/chunk-hwy3n31w.js.map +9 -0
- package/dist/chunk-j0kjscb7.js +53 -0
- package/dist/chunk-j0kjscb7.js.map +10 -0
- package/dist/chunk-jzz91jwh.js +70 -0
- package/dist/chunk-jzz91jwh.js.map +10 -0
- package/dist/chunk-k9z47mab.js +247 -0
- package/dist/chunk-k9z47mab.js.map +12 -0
- package/dist/chunk-kbm35t7w.js +1331 -0
- package/dist/chunk-kbm35t7w.js.map +16 -0
- package/dist/chunk-m4veth0c.js +1649 -0
- package/dist/chunk-m4veth0c.js.map +17 -0
- package/dist/chunk-mhty3nna.js +150 -0
- package/dist/chunk-mhty3nna.js.map +11 -0
- package/dist/chunk-mmgh670x.js +248 -0
- package/dist/chunk-mmgh670x.js.map +11 -0
- package/dist/chunk-n0kk5xbq.js +2358 -0
- package/dist/chunk-n0kk5xbq.js.map +26 -0
- package/dist/chunk-nmv41qx9.js +2847 -0
- package/dist/chunk-nmv41qx9.js.map +23 -0
- package/dist/chunk-nqdp52aj.js +113 -0
- package/dist/chunk-nqdp52aj.js.map +12 -0
- package/dist/chunk-ns2m25dz.js +139 -0
- package/dist/chunk-ns2m25dz.js.map +10 -0
- package/dist/chunk-pkdmypdd.js +147 -0
- package/dist/chunk-pkdmypdd.js.map +11 -0
- package/dist/chunk-rmyfmrrr.js +1634 -0
- package/dist/chunk-rmyfmrrr.js.map +15 -0
- package/dist/chunk-sa874j5c.js +9 -0
- package/dist/chunk-sa874j5c.js.map +10 -0
- package/dist/chunk-sgsz2fpq.js +289 -0
- package/dist/chunk-sgsz2fpq.js.map +10 -0
- package/dist/chunk-sn51wnf1.js +3020 -0
- package/dist/chunk-sn51wnf1.js.map +32 -0
- package/dist/chunk-sqhr1a7e.js +587 -0
- package/dist/chunk-sqhr1a7e.js.map +11 -0
- package/dist/chunk-tmj5r2zv.js +2933 -0
- package/dist/chunk-tmj5r2zv.js.map +20 -0
- package/dist/chunk-vefj3880.js +56 -0
- package/dist/chunk-vefj3880.js.map +10 -0
- package/dist/chunk-vp2db42d.js +121 -0
- package/dist/chunk-vp2db42d.js.map +10 -0
- package/dist/chunk-wry79q55.js +108 -0
- package/dist/chunk-wry79q55.js.map +10 -0
- package/dist/chunk-x1arr7ze.js +63 -0
- package/dist/chunk-x1arr7ze.js.map +10 -0
- package/dist/chunk-z2xy5n2q.js +103 -0
- package/dist/chunk-z2xy5n2q.js.map +10 -0
- package/dist/hosted-agent.js +53 -0
- package/dist/hosted-agent.js.map +10 -0
- package/dist/main.js +132 -20
- package/dist/main.js.map +4 -4
- package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
- package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
- package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
- package/dist/runtime/src/agents/archiver.ts +396 -0
- package/dist/runtime/src/agents/auth-check.ts +64 -0
- package/dist/runtime/src/agents/claude-provider.ts +737 -0
- package/dist/runtime/src/agents/claude-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
- package/dist/runtime/src/agents/codex-provider.ts +785 -0
- package/dist/runtime/src/agents/codex-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
- package/dist/runtime/src/agents/contracts.ts +69 -0
- package/dist/runtime/src/agents/deep-map.ts +13 -0
- package/dist/runtime/src/agents/execution-host.ts +91 -0
- package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
- package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
- package/dist/runtime/src/agents/fork.ts +65 -0
- package/dist/runtime/src/agents/hosted-agent.ts +49 -0
- package/dist/runtime/src/agents/idle-archiver.ts +128 -0
- package/dist/runtime/src/agents/index.ts +205 -0
- package/dist/runtime/src/agents/maestro-provider.ts +206 -0
- package/dist/runtime/src/agents/maestro-registry.ts +31 -0
- package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
- package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
- package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
- package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
- package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
- package/dist/runtime/src/agents/model-catalog.ts +151 -0
- package/dist/runtime/src/agents/registry.ts +24 -0
- package/dist/runtime/src/agents/rollout/claude.ts +331 -0
- package/dist/runtime/src/agents/rollout/codex.ts +569 -0
- package/dist/runtime/src/agents/rollout/shared.ts +205 -0
- package/dist/runtime/src/agents/self-config-core.ts +415 -0
- package/dist/runtime/src/agents/task-events.ts +44 -0
- package/dist/runtime/src/agents/tool-format.ts +352 -0
- package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
- package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
- package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
- package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
- package/dist/runtime/src/application/submit-user-message.ts +50 -0
- package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
- package/dist/runtime/src/application/switch-topic-model.ts +79 -0
- package/dist/runtime/src/application/topic-service.ts +139 -0
- package/dist/runtime/src/application/vault-command.ts +170 -0
- package/dist/runtime/src/bus.ts +319 -0
- package/dist/runtime/src/index.ts +333 -0
- package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
- package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
- package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
- package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
- package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
- package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
- package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +137 -0
- package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
- package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
- package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
- package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
- package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
- package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
- package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
- package/dist/runtime/src/mcp/system-health-server.ts +130 -0
- package/dist/runtime/src/mcp/task-server.ts +173 -0
- package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
- package/dist/runtime/src/mcp/vault-http.ts +219 -0
- package/dist/runtime/src/mcp/vault-run.ts +153 -0
- package/dist/runtime/src/mcp/vault-server.ts +104 -0
- package/dist/runtime/src/mcp/wiki-server.ts +755 -0
- package/dist/runtime/src/media/file-events.ts +33 -0
- package/dist/runtime/src/media/text-extractor.ts +312 -0
- package/dist/runtime/src/media/video.ts +92 -0
- package/dist/runtime/src/outbox/file-ops.ts +210 -0
- package/dist/runtime/src/outbox/index.ts +8 -0
- package/dist/runtime/src/outbox/utils.ts +51 -0
- package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
- package/dist/runtime/src/platform/config.ts +371 -0
- package/dist/runtime/src/platform/constants.ts +18 -0
- package/dist/runtime/src/platform/debug-users.ts +14 -0
- package/dist/runtime/src/platform/delay.ts +1 -0
- package/dist/runtime/src/platform/error.ts +9 -0
- package/dist/runtime/src/platform/file-utils.ts +16 -0
- package/dist/runtime/src/platform/jsonl.ts +202 -0
- package/dist/runtime/src/platform/lifecycle.ts +174 -0
- package/dist/runtime/src/platform/logger.ts +31 -0
- package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
- package/dist/runtime/src/platform/mcp-config.ts +878 -0
- package/dist/runtime/src/platform/modules.ts +145 -0
- package/dist/runtime/src/platform/node-plugins.ts +46 -0
- package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
- package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
- package/dist/runtime/src/prompts/builders.ts +368 -0
- package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
- package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
- package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
- package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
- package/dist/runtime/src/query/active-rooms.ts +534 -0
- package/dist/runtime/src/query/control.ts +313 -0
- package/dist/runtime/src/query/index.ts +24 -0
- package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
- package/dist/runtime/src/query/session-inbox-path.ts +42 -0
- package/dist/runtime/src/query/state.ts +94 -0
- package/dist/runtime/src/query/types.ts +83 -0
- package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
- package/dist/runtime/src/runtime/attachments.ts +151 -0
- package/dist/runtime/src/runtime/background-sessions.ts +83 -0
- package/dist/runtime/src/runtime/channel-context.ts +107 -0
- package/dist/runtime/src/runtime/errors.ts +69 -0
- package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
- package/dist/runtime/src/runtime/file-hooks.ts +81 -0
- package/dist/runtime/src/runtime/footer.ts +23 -0
- package/dist/runtime/src/runtime/inbox.ts +946 -0
- package/dist/runtime/src/runtime/tasks.ts +56 -0
- package/dist/runtime/src/runtime/topic-config.ts +29 -0
- package/dist/runtime/src/runtime/turn-runner.ts +2298 -0
- package/dist/runtime/src/runtime/usage-alert.ts +84 -0
- package/dist/runtime/src/runtime/visual-html.ts +109 -0
- package/dist/runtime/src/runtime/visual-store.ts +430 -0
- package/dist/runtime/src/runtime/visuals.ts +171 -0
- package/dist/runtime/src/security/sanitize.ts +27 -0
- package/dist/runtime/src/security/sensitive-path.ts +34 -0
- package/dist/runtime/src/storage/activity-log.ts +152 -0
- package/dist/runtime/src/storage/api-messages.ts +617 -0
- package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
- package/dist/runtime/src/storage/api-topic-config.ts +192 -0
- package/dist/runtime/src/storage/api-topics.ts +844 -0
- package/dist/runtime/src/storage/app-settings.ts +44 -0
- package/dist/runtime/src/storage/browser-profiles.ts +133 -0
- package/dist/runtime/src/storage/conversations.ts +268 -0
- package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
- package/dist/runtime/src/storage/forum/index.ts +4 -0
- package/dist/runtime/src/storage/forum/repository.ts +283 -0
- package/dist/runtime/src/storage/forum/schema.ts +300 -0
- package/dist/runtime/src/storage/forum-db.ts +23 -0
- package/dist/runtime/src/storage/runtime-events.ts +130 -0
- package/dist/runtime/src/storage/runtime-leases.ts +189 -0
- package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
- package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
- package/dist/runtime/src/storage/runtime-turn-requests.ts +301 -0
- package/dist/runtime/src/storage/self-schedules.ts +298 -0
- package/dist/runtime/src/storage/session-asks.ts +347 -0
- package/dist/runtime/src/storage/sqlite.ts +129 -0
- package/dist/runtime/src/storage/tasks.ts +179 -0
- package/dist/runtime/src/storage/token-stats.ts +144 -0
- package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
- package/dist/runtime/src/storage/topic-archive.ts +74 -0
- package/dist/runtime/src/storage/topic-settings.ts +67 -0
- package/dist/runtime/src/storage/topic-transcript.ts +98 -0
- package/dist/runtime/src/storage/vault-crypto.ts +65 -0
- package/dist/runtime/src/storage/vault.ts +225 -0
- package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
- package/dist/runtime/src/storage/wiki.ts +85 -0
- package/dist/runtime/src/topics/create.ts +123 -0
- package/dist/runtime/src/topics/derive.ts +398 -0
- package/dist/runtime/src/topics/lifecycle.ts +311 -0
- package/dist/runtime/src/topics/links.ts +7 -0
- package/dist/runtime/src/topics/personal-general.ts +65 -0
- package/dist/runtime/src/topics/session.ts +381 -0
- package/dist/runtime/src/types/api.ts +497 -0
- package/dist/runtime/src/types.ts +257 -0
- package/dist/runtime/src/version.ts +1 -0
- package/dist/runtime/tsconfig.json +13 -0
- package/package.json +28 -3
- package/dist/main.d.ts +0 -3
- package/dist/main.d.ts.map +0 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maestro agent provider — thin local adapter over `maestro-agent-sdk`.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the pattern of claude-provider.ts / codex-provider.ts so that
|
|
5
|
+
* agents/index.ts can import all three providers from sibling `@/agents/*`
|
|
6
|
+
* paths rather than mixing package and local imports. The wrapper layer is
|
|
7
|
+
* the natural extension point for Otium-specific overrides — anything
|
|
8
|
+
* we want every maestro call to inherit gets stamped here before reaching
|
|
9
|
+
* the SDK.
|
|
10
|
+
*
|
|
11
|
+
* v0.1.19 host overrides:
|
|
12
|
+
* - `enableBackgroundBash: false` — the SDK's Claude-Code-style triad
|
|
13
|
+
* (`Bash(run_in_background:true)` + `BashOutput` + `KillBash`) is OFF.
|
|
14
|
+
* It was briefly always-on, but in this headless setup a background
|
|
15
|
+
* bash's completion never triggers a new model turn — the process runs
|
|
16
|
+
* in the bot but nobody polls it once the turn ends. Topics use
|
|
17
|
+
* `mcp__background-bash__background_bash_run` instead, which injects a
|
|
18
|
+
* session-inbox tell entry on exit so the loop actually closes.
|
|
19
|
+
*
|
|
20
|
+
* v0.1.21 host overrides:
|
|
21
|
+
* - `maxTokens: 32_768` — global default for the per-API-call output
|
|
22
|
+
* ceiling. Pre-v0.1.21 the SDK silently fell back to 4096, which
|
|
23
|
+
* truncated long-form generation mid-string and broke Write/Edit
|
|
24
|
+
* tool input JSON for multi-K file bodies (see the v0.1.21
|
|
25
|
+
* changelog for the bug write-up). 32K is the Otium-wide
|
|
26
|
+
* default because:
|
|
27
|
+
* - long-form legal reports (슈퍼로이어, 기일보고) routinely
|
|
28
|
+
* exceed 4K and have been the load-bearing failure surface;
|
|
29
|
+
* - 32K is generous enough that ~99% of realistic single-turn
|
|
30
|
+
* outputs fit without truncation;
|
|
31
|
+
* - keeps cost / latency bounded relative to the SDK's own
|
|
32
|
+
* model-catalog defaults (deepseek-pro 64K, deepseek-flash 32K)
|
|
33
|
+
* which can be expensive for accidental long outputs.
|
|
34
|
+
* Callers that need more (or less) override per-call via
|
|
35
|
+
* `AgentQueryOptions.maxTokens` — the caller-supplied value wins
|
|
36
|
+
* because `...opts` spreads AFTER the defaults below.
|
|
37
|
+
*
|
|
38
|
+
* v0.1.22 host overrides:
|
|
39
|
+
* - `enableToolSearch: true` — deferred-tool / ToolSearch pattern is ON.
|
|
40
|
+
* The pattern adds a round-trip every time a new tool is first reached
|
|
41
|
+
* for (turn N: model calls `ToolSearch` → turn N+1: model calls the
|
|
42
|
+
* actual tool), but that extra round-trip is now cheap relative to the
|
|
43
|
+
* reminder-token savings across Otium's MCP server count, so the
|
|
44
|
+
* tradeoff flipped in favor of deferral. Stamped as a literal `true`
|
|
45
|
+
* (not omitted) so a code-reader sees the decision instead of guessing
|
|
46
|
+
* intent. Caller-supplied `enableToolSearch: false` still wins because
|
|
47
|
+
* `...opts` spreads AFTER this default; a future dispatcher / topic
|
|
48
|
+
* config can flip it per topic if a specific topic's MCP server count
|
|
49
|
+
* ever makes deferral not worth it.
|
|
50
|
+
*
|
|
51
|
+
* v0.1.42 host overrides:
|
|
52
|
+
* - `disallowedTools` removes provider-native AskUserQuestion, Agent, and
|
|
53
|
+
* Task* tools from the schema/catalog. Otium owns those surfaces via
|
|
54
|
+
* runtime ask_user_question/spawn_subagent and the shared task MCP server
|
|
55
|
+
* so state survives agent switches.
|
|
56
|
+
*
|
|
57
|
+
* If a future override becomes per-call (cwd-aware, topic-aware, etc.)
|
|
58
|
+
* we lift the static spread into a per-call wrapper. For now the
|
|
59
|
+
* overrides are uniform across every maestro turn, so a single
|
|
60
|
+
* Object.assign keeps the surface trivial.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
import "#platform/maestro-bootstrap-env";
|
|
64
|
+
import type { HookRegistration, McpResolver } from "maestro-agent-sdk";
|
|
65
|
+
import { maestroProvider as sdkMaestroProvider, setMcpResolver } from "maestro-agent-sdk";
|
|
66
|
+
import {
|
|
67
|
+
hostedMcpServers,
|
|
68
|
+
redactHostedSecrets,
|
|
69
|
+
referencesHostedSecretStorage,
|
|
70
|
+
shouldRedirectHostedVaultTool,
|
|
71
|
+
} from "#agents/execution-host";
|
|
72
|
+
import { VAULT_BROKER_REDIRECT_ERROR } from "#agents/vault-tool-policy";
|
|
73
|
+
import type { AgentQueryOptions, UnifiedEvent } from "#types";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Default per-API-call max output tokens for every Otium maestro turn.
|
|
77
|
+
* See the file-level docstring for the rationale; callers override per call
|
|
78
|
+
* via `AgentQueryOptions.maxTokens`.
|
|
79
|
+
*/
|
|
80
|
+
const MAESTRO_DEFAULT_MAX_TOKENS = 32_768;
|
|
81
|
+
const PROVIDER_ASK_USER_TOOL = "AskUserQuestion";
|
|
82
|
+
const PROVIDER_SUBAGENT_TOOL = "Agent";
|
|
83
|
+
const MAESTRO_NATIVE_TASK_TOOLS = [
|
|
84
|
+
"TaskCreate",
|
|
85
|
+
"TaskUpdate",
|
|
86
|
+
"TaskList",
|
|
87
|
+
"TaskGet",
|
|
88
|
+
"TaskOutput",
|
|
89
|
+
"TaskStop",
|
|
90
|
+
] as const;
|
|
91
|
+
const MAESTRO_PROVIDER_OWNED_TOOL_SET = new Set<string>([
|
|
92
|
+
PROVIDER_ASK_USER_TOOL,
|
|
93
|
+
PROVIDER_SUBAGENT_TOOL,
|
|
94
|
+
...MAESTRO_NATIVE_TASK_TOOLS,
|
|
95
|
+
]);
|
|
96
|
+
const DEFAULT_MAESTRO_DISALLOWED_TOOLS = [
|
|
97
|
+
PROVIDER_ASK_USER_TOOL,
|
|
98
|
+
PROVIDER_SUBAGENT_TOOL,
|
|
99
|
+
...MAESTRO_NATIVE_TASK_TOOLS,
|
|
100
|
+
] as const;
|
|
101
|
+
|
|
102
|
+
export function buildMaestroDisallowedTools(
|
|
103
|
+
callerDisallowedTools: readonly string[] = [],
|
|
104
|
+
): readonly string[] {
|
|
105
|
+
return [...new Set([...DEFAULT_MAESTRO_DISALLOWED_TOOLS, ...callerDisallowedTools])];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Build per-call tool hooks:
|
|
110
|
+
*
|
|
111
|
+
* Pre hook — two responsibilities in one pass:
|
|
112
|
+
* 1. Block direct vault.db filesystem access (security guard).
|
|
113
|
+
* 2. Block direct placeholder substitution and redirect the model to the
|
|
114
|
+
* Vault MCP credential broker, where expanded input stays outside the
|
|
115
|
+
* provider process.
|
|
116
|
+
*
|
|
117
|
+
* Post hook — scrub raw/common encoded secret forms before tool output is sent
|
|
118
|
+
* back to the model. HTTP credentials should still prefer vault_http_request,
|
|
119
|
+
* whose broker never exposes the expanded request to the provider at all.
|
|
120
|
+
*/
|
|
121
|
+
function buildVaultHook(userId: string): HookRegistration {
|
|
122
|
+
return {
|
|
123
|
+
name: "vault-guard",
|
|
124
|
+
pre({ toolName, input }) {
|
|
125
|
+
if (referencesHostedSecretStorage(input)) {
|
|
126
|
+
return { decision: "block", error: "Runtime secret storage access is not permitted" };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Detect key references from metadata only. No Vault value is decrypted
|
|
130
|
+
// or injected into the provider/tool registry on this path.
|
|
131
|
+
if (shouldRedirectHostedVaultTool(userId, toolName, input)) {
|
|
132
|
+
return { decision: "block", error: VAULT_BROKER_REDIRECT_ERROR };
|
|
133
|
+
}
|
|
134
|
+
return { decision: "allow" };
|
|
135
|
+
},
|
|
136
|
+
post({ output }) {
|
|
137
|
+
const redacted = redactHostedSecrets(userId, output);
|
|
138
|
+
return redacted === output ? {} : { output: redacted };
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function buildMaestroToolHooks(userId: string): HookRegistration[] {
|
|
144
|
+
return [buildVaultHook(userId), buildProviderOwnedToolBlockHook()];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function providerOwnedToolRedirect(toolName: string): string {
|
|
148
|
+
if (toolName === PROVIDER_ASK_USER_TOOL) {
|
|
149
|
+
return "Use the runtime ask_user_question MCP tool instead.";
|
|
150
|
+
}
|
|
151
|
+
if (toolName === PROVIDER_SUBAGENT_TOOL) {
|
|
152
|
+
return "Use the runtime spawn_subagent MCP tool instead.";
|
|
153
|
+
}
|
|
154
|
+
return (
|
|
155
|
+
"Use the shared task MCP tools instead " +
|
|
156
|
+
"(mcp__task__task_create / task_update / task_list / task_get / task_delete)."
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildProviderOwnedToolBlockHook(): HookRegistration {
|
|
161
|
+
return {
|
|
162
|
+
name: "provider-owned-tool-redirect",
|
|
163
|
+
pre({ toolName }) {
|
|
164
|
+
if (!MAESTRO_PROVIDER_OWNED_TOOL_SET.has(toolName)) return { decision: "allow" };
|
|
165
|
+
return {
|
|
166
|
+
decision: "block",
|
|
167
|
+
error: `${toolName} is disabled in this environment. ${providerOwnedToolRedirect(toolName)}`,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function maestroProvider(opts: AgentQueryOptions): AsyncGenerator<UnifiedEvent> {
|
|
174
|
+
// Dispatcher guarantees agent === "maestro" before routing here; throw early
|
|
175
|
+
// so routing bugs surface at the call site rather than silently overwriting.
|
|
176
|
+
if (opts.agent !== undefined && opts.agent !== "maestro") {
|
|
177
|
+
throw new Error(`maestroProvider: unexpected agent "${opts.agent}", expected "maestro"`);
|
|
178
|
+
}
|
|
179
|
+
const userId = opts.userId ?? "";
|
|
180
|
+
// The SDK deliberately ships with an empty MCP resolver. Keep this wiring in
|
|
181
|
+
// the provider adapter rather than every host bootstrap so a new Negotium
|
|
182
|
+
// channel cannot accidentally run Maestro with zero runtime/wiki/task MCPs.
|
|
183
|
+
setMcpResolver(hostedMcpServers as McpResolver);
|
|
184
|
+
const callerDisallowedTools = opts.disallowedTools ?? [];
|
|
185
|
+
const callerToolHooks = (opts as { toolHooks?: HookRegistration[] }).toolHooks ?? [];
|
|
186
|
+
// maestro-agent-sdk's type does not yet include Otium's "cron" sessionType;
|
|
187
|
+
// keep the runtime value intact so the host MCP resolver can choose cron scope.
|
|
188
|
+
const sdkOpts = {
|
|
189
|
+
// Built-in background bash (Bash/BashOutput/KillBash triad) is disabled
|
|
190
|
+
// because its completion never triggers a new model turn in this headless
|
|
191
|
+
// setup — the bash runs in the bot process but when the turn ends nobody
|
|
192
|
+
// polls it. Use mcp__background-bash__background_bash_run instead, which
|
|
193
|
+
// injects a session-inbox tell entry on exit so the loop closes properly.
|
|
194
|
+
enableBackgroundBash: false,
|
|
195
|
+
maxTokens: MAESTRO_DEFAULT_MAX_TOKENS,
|
|
196
|
+
enableToolSearch: true,
|
|
197
|
+
...opts,
|
|
198
|
+
// v0.1.39: SDK AgentQueryOptions.agent is narrowed to "maestro" only.
|
|
199
|
+
// Otium's AgentKind is wider ("claude"|"codex"|"maestro"), so stamp
|
|
200
|
+
// the literal to satisfy the SDK type.
|
|
201
|
+
agent: "maestro",
|
|
202
|
+
disallowedTools: buildMaestroDisallowedTools(callerDisallowedTools),
|
|
203
|
+
toolHooks: [...buildMaestroToolHooks(userId), ...callerToolHooks],
|
|
204
|
+
} as unknown as Parameters<typeof sdkMaestroProvider>[0];
|
|
205
|
+
return sdkMaestroProvider(sdkOpts);
|
|
206
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maestro agent registry — local re-export of `maestro-agent-sdk` with
|
|
3
|
+
* a project-specific default-model override.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the pattern of claude-registry.ts / codex-registry.ts so that
|
|
6
|
+
* agents/registry.ts can resolve all three AgentRegistry instances from
|
|
7
|
+
* sibling `@/agents/*` paths, keeping the import topology consistent.
|
|
8
|
+
*
|
|
9
|
+
* maestro-agent-sdk v0.1.39 is DeepSeek-only — the Anthropic and Codex
|
|
10
|
+
* providers were removed. The SDK's own default is `deepseek-pro`, which
|
|
11
|
+
* matches this override. The override is kept explicit so the intent is
|
|
12
|
+
* clear if the upstream default ever changes; users can still switch to
|
|
13
|
+
* `deepseek-flash` per-topic via `/model deepseek-flash`.
|
|
14
|
+
*/
|
|
15
|
+
import "#platform/maestro-bootstrap-env";
|
|
16
|
+
import {
|
|
17
|
+
setConversationReader,
|
|
18
|
+
maestroRegistry as upstreamMaestroRegistry,
|
|
19
|
+
} from "maestro-agent-sdk";
|
|
20
|
+
import type { AgentRegistry } from "#agents/contracts";
|
|
21
|
+
import { readConversation } from "#storage/conversations";
|
|
22
|
+
|
|
23
|
+
// The SDK intentionally defaults its conversation reader to `() => []` so it
|
|
24
|
+
// can stay storage-agnostic. Wire Negotium's unified log into it once at module
|
|
25
|
+
// load; otherwise Maestro forkSession() creates a valid but empty rollout.
|
|
26
|
+
setConversationReader(readConversation as Parameters<typeof setConversationReader>[0]);
|
|
27
|
+
|
|
28
|
+
export const maestroRegistry: AgentRegistry = {
|
|
29
|
+
...(upstreamMaestroRegistry as AgentRegistry),
|
|
30
|
+
defaultModel: "deepseek-pro",
|
|
31
|
+
};
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { errorResult, type SharedMcpTool, textResult } from "#agents/mcp-tools/common";
|
|
4
|
+
import { WsHub } from "#bus";
|
|
5
|
+
import {
|
|
6
|
+
appendApiMessage,
|
|
7
|
+
getApiMessage,
|
|
8
|
+
updateApiMessageAskUserQuestion,
|
|
9
|
+
} from "#storage/api-messages";
|
|
10
|
+
import { getApiTopicConfig } from "#storage/api-topic-config";
|
|
11
|
+
import { getTopic } from "#storage/api-topics";
|
|
12
|
+
import type { AgentKind } from "#types";
|
|
13
|
+
import type { MessageDto } from "#types/api";
|
|
14
|
+
|
|
15
|
+
const MAX_QUESTION_CHARS = 2000;
|
|
16
|
+
const MAX_CHOICE_LABEL_CHARS = 128;
|
|
17
|
+
const MAX_CHOICE_DESCRIPTION_CHARS = 500;
|
|
18
|
+
const MAX_CHOICES = 12;
|
|
19
|
+
|
|
20
|
+
export type AskUserChoice = { label: string; description?: string };
|
|
21
|
+
|
|
22
|
+
export interface AskUserToolContext {
|
|
23
|
+
userId: string;
|
|
24
|
+
topicId: string;
|
|
25
|
+
queryId?: string;
|
|
26
|
+
agent: AgentKind;
|
|
27
|
+
model?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ClaimedAskUserAnswer = {
|
|
31
|
+
choice: AskUserChoice;
|
|
32
|
+
queryId?: string;
|
|
33
|
+
resolve: (userId: string) => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type AnswerAskUserQuestionResult =
|
|
37
|
+
| { ok: true; queryId?: string; answerMessage: MessageDto }
|
|
38
|
+
| { ok: false; error: string };
|
|
39
|
+
|
|
40
|
+
type PendingAsk = {
|
|
41
|
+
topicId: string;
|
|
42
|
+
queryId?: string;
|
|
43
|
+
messageId: string;
|
|
44
|
+
question: string;
|
|
45
|
+
choices: AskUserChoice[];
|
|
46
|
+
resolve: (answer: AskUserChoice & { userId: string }) => void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const pendingAsks = new Map<string, PendingAsk>();
|
|
50
|
+
|
|
51
|
+
function normalizeAskText(value: unknown, maxChars: number): string | undefined {
|
|
52
|
+
if (typeof value !== "string") return undefined;
|
|
53
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
54
|
+
if (!normalized) return undefined;
|
|
55
|
+
return normalized.length > maxChars
|
|
56
|
+
? `${normalized.slice(0, maxChars).trimEnd()}...`
|
|
57
|
+
: normalized;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalizeAskUserChoices(value: unknown): AskUserChoice[] {
|
|
61
|
+
if (!Array.isArray(value)) return [];
|
|
62
|
+
return value
|
|
63
|
+
.map((choice) => {
|
|
64
|
+
if (!choice || typeof choice !== "object") return null;
|
|
65
|
+
const record = choice as Record<string, unknown>;
|
|
66
|
+
const label = normalizeAskText(record.label, MAX_CHOICE_LABEL_CHARS);
|
|
67
|
+
if (!label) return null;
|
|
68
|
+
const description = normalizeAskText(record.description, MAX_CHOICE_DESCRIPTION_CHARS);
|
|
69
|
+
return {
|
|
70
|
+
label,
|
|
71
|
+
...(description ? { description } : {}),
|
|
72
|
+
};
|
|
73
|
+
})
|
|
74
|
+
.filter((choice): choice is AskUserChoice => choice !== null)
|
|
75
|
+
.slice(0, MAX_CHOICES);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function normalizeAskUserQuestionInput(input: {
|
|
79
|
+
question?: unknown;
|
|
80
|
+
choices?: unknown;
|
|
81
|
+
}): { question: string; choices: AskUserChoice[] } | { error: string } {
|
|
82
|
+
const question = normalizeAskText(input.question, MAX_QUESTION_CHARS);
|
|
83
|
+
if (!question) return { error: "question is required" };
|
|
84
|
+
const choices = normalizeAskUserChoices(input.choices);
|
|
85
|
+
if (choices.length === 0) return { error: "at least one choice is required" };
|
|
86
|
+
return { question, choices };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function hasPendingAskUserQuestion(topicId: string, messageId: string): boolean {
|
|
90
|
+
const pending = pendingAsks.get(messageId);
|
|
91
|
+
return Boolean(pending && pending.topicId === topicId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function claimPendingAskUserQuestion(
|
|
95
|
+
topicId: string,
|
|
96
|
+
messageId: string,
|
|
97
|
+
label: string,
|
|
98
|
+
): ClaimedAskUserAnswer | null {
|
|
99
|
+
const pending = pendingAsks.get(messageId);
|
|
100
|
+
if (!pending || pending.topicId !== topicId) return null;
|
|
101
|
+
const choice = pending.choices.find((item) => item.label === label);
|
|
102
|
+
if (!choice) return null;
|
|
103
|
+
pendingAsks.delete(messageId);
|
|
104
|
+
return {
|
|
105
|
+
choice,
|
|
106
|
+
queryId: pending.queryId,
|
|
107
|
+
resolve: (userId: string) => pending.resolve({ ...choice, userId }),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Host-neutral answer path for runtime ask cards. Web, terminal, Telegram, and
|
|
113
|
+
* future adapters should call this rather than reimplementing pending-map and
|
|
114
|
+
* persistence ordering. A successful call resumes the blocked MCP tool in the
|
|
115
|
+
* same provider turn.
|
|
116
|
+
*/
|
|
117
|
+
export function answerPendingAskUserQuestion(
|
|
118
|
+
topicId: string,
|
|
119
|
+
messageId: string,
|
|
120
|
+
label: string,
|
|
121
|
+
userId: string,
|
|
122
|
+
): AnswerAskUserQuestionResult {
|
|
123
|
+
const topic = getTopic(topicId);
|
|
124
|
+
if (!topic?.participants.some((participant) => participant.userId === userId)) {
|
|
125
|
+
return { ok: false, error: "User is not a member of this topic." };
|
|
126
|
+
}
|
|
127
|
+
const existing = getApiMessage(topicId, messageId);
|
|
128
|
+
const ask = existing?.kind === "ask_user_question" ? existing.askUserQuestion : undefined;
|
|
129
|
+
if (!existing || existing.deleted || !ask) {
|
|
130
|
+
return { ok: false, error: "Ask card not found." };
|
|
131
|
+
}
|
|
132
|
+
if (ask.expired || ask.selectedLabel) {
|
|
133
|
+
return { ok: false, error: "This question is no longer awaiting an answer." };
|
|
134
|
+
}
|
|
135
|
+
if (!ask.choices.some((choice) => choice.label === label)) {
|
|
136
|
+
return { ok: false, error: "Invalid choice." };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const claimed = claimPendingAskUserQuestion(topicId, messageId, label);
|
|
140
|
+
if (!claimed) return { ok: false, error: "This question is no longer awaiting an answer." };
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const editedAt = new Date().toISOString();
|
|
144
|
+
const nextAsk = { ...ask, selectedLabel: label };
|
|
145
|
+
const updated = updateApiMessageAskUserQuestion(topicId, messageId, nextAsk, editedAt);
|
|
146
|
+
if (!updated) {
|
|
147
|
+
claimed.resolve("");
|
|
148
|
+
return { ok: false, error: "Ask card not found." };
|
|
149
|
+
}
|
|
150
|
+
WsHub.get().broadcastMessageUpdated(topicId, messageId, {
|
|
151
|
+
askUserQuestion: nextAsk,
|
|
152
|
+
editedAt,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const answerMessage: MessageDto = {
|
|
156
|
+
id: randomUUID(),
|
|
157
|
+
topicId,
|
|
158
|
+
authorId: userId,
|
|
159
|
+
text: label,
|
|
160
|
+
parentId: messageId,
|
|
161
|
+
createdAt: new Date().toISOString(),
|
|
162
|
+
};
|
|
163
|
+
appendApiMessage(answerMessage);
|
|
164
|
+
WsHub.get().broadcastMessage(topicId, answerMessage);
|
|
165
|
+
claimed.resolve(userId);
|
|
166
|
+
return { ok: true, queryId: claimed.queryId, answerMessage };
|
|
167
|
+
} catch (error) {
|
|
168
|
+
claimed.resolve("");
|
|
169
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function cancelPendingAskUserQuestions(topicId: string, queryId: string): void {
|
|
174
|
+
for (const [messageId, pending] of pendingAsks) {
|
|
175
|
+
if (pending.topicId === topicId && pending.queryId === queryId) {
|
|
176
|
+
pendingAsks.delete(messageId);
|
|
177
|
+
const editedAt = new Date().toISOString();
|
|
178
|
+
const expiredAsk = {
|
|
179
|
+
question: pending.question,
|
|
180
|
+
choices: pending.choices,
|
|
181
|
+
expired: true,
|
|
182
|
+
} satisfies NonNullable<MessageDto["askUserQuestion"]>;
|
|
183
|
+
const updated = updateApiMessageAskUserQuestion(topicId, messageId, expiredAsk, editedAt);
|
|
184
|
+
if (updated) {
|
|
185
|
+
WsHub.get().broadcastMessageUpdated(topicId, messageId, {
|
|
186
|
+
askUserQuestion: expiredAsk,
|
|
187
|
+
editedAt,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
pending.resolve({
|
|
191
|
+
label: "No answer",
|
|
192
|
+
description: "The AI turn ended before the user answered.",
|
|
193
|
+
userId: "",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function appendAskMessage(
|
|
200
|
+
ctx: AskUserToolContext,
|
|
201
|
+
question: string,
|
|
202
|
+
choices: AskUserChoice[],
|
|
203
|
+
): MessageDto {
|
|
204
|
+
const cfg = getApiTopicConfig(ctx.topicId);
|
|
205
|
+
const msg: MessageDto = {
|
|
206
|
+
id: `ask-${ctx.queryId ?? "runtime"}-${randomUUID()}`,
|
|
207
|
+
topicId: ctx.topicId,
|
|
208
|
+
authorId: "ai",
|
|
209
|
+
text: question,
|
|
210
|
+
queryId: ctx.queryId,
|
|
211
|
+
agentType: ctx.agent,
|
|
212
|
+
model: ctx.model ?? cfg?.model ?? "unknown",
|
|
213
|
+
kind: "ask_user_question",
|
|
214
|
+
askUserQuestion: { question, choices },
|
|
215
|
+
createdAt: new Date().toISOString(),
|
|
216
|
+
};
|
|
217
|
+
appendApiMessage(msg);
|
|
218
|
+
return msg;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function askUserQuestion(
|
|
222
|
+
ctx: AskUserToolContext,
|
|
223
|
+
input: { question?: unknown; choices?: unknown },
|
|
224
|
+
) {
|
|
225
|
+
const topic = getTopic(ctx.topicId);
|
|
226
|
+
if (!topic) return errorResult(`Error: topic '${ctx.topicId}' not found.`);
|
|
227
|
+
if (!topic.participants.some((p: { userId: string }) => p.userId === ctx.userId)) {
|
|
228
|
+
return errorResult("Error: user is not a member of this topic.");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const normalized = normalizeAskUserQuestionInput(input);
|
|
232
|
+
if ("error" in normalized) return errorResult(`Error: ${normalized.error}.`);
|
|
233
|
+
|
|
234
|
+
const { question, choices } = normalized;
|
|
235
|
+
const msg = appendAskMessage(ctx, question, choices);
|
|
236
|
+
|
|
237
|
+
const answer = await new Promise<AskUserChoice & { userId: string }>((resolve) => {
|
|
238
|
+
pendingAsks.set(msg.id, {
|
|
239
|
+
topicId: ctx.topicId,
|
|
240
|
+
queryId: ctx.queryId,
|
|
241
|
+
messageId: msg.id,
|
|
242
|
+
question,
|
|
243
|
+
choices,
|
|
244
|
+
resolve,
|
|
245
|
+
});
|
|
246
|
+
WsHub.get().broadcastMessage(ctx.topicId, msg);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (!answer.userId) {
|
|
250
|
+
return errorResult("The AI turn ended before the user answered.");
|
|
251
|
+
}
|
|
252
|
+
const description = answer.description ? `\nDescription: ${answer.description}` : "";
|
|
253
|
+
return textResult(
|
|
254
|
+
`User selected: ${answer.label}${description}\nContinue from this selection and finish the current turn.`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function createAskUserToolDefinition(ctx: AskUserToolContext): SharedMcpTool {
|
|
259
|
+
return {
|
|
260
|
+
name: "ask_user_question",
|
|
261
|
+
description:
|
|
262
|
+
"Ask the user a blocking multiple-choice question in the chat and wait for their selection. Use this instead of provider built-in AskUserQuestion. Use only when you cannot proceed safely without the user's choice.",
|
|
263
|
+
schema: {
|
|
264
|
+
question: z.string().describe("The concise question to show to the user."),
|
|
265
|
+
choices: z
|
|
266
|
+
.array(
|
|
267
|
+
z.object({
|
|
268
|
+
label: z.string().describe("Choice label shown on the button."),
|
|
269
|
+
description: z.string().optional().describe("Optional one-sentence consequence."),
|
|
270
|
+
}),
|
|
271
|
+
)
|
|
272
|
+
.min(1)
|
|
273
|
+
.max(MAX_CHOICES)
|
|
274
|
+
.describe("Choices the user can select."),
|
|
275
|
+
},
|
|
276
|
+
async handler(input) {
|
|
277
|
+
return askUserQuestion(ctx, input);
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type McpTextContent = { type: "text"; text: string };
|
|
2
|
+
|
|
3
|
+
export type McpToolResult = {
|
|
4
|
+
content: McpTextContent[];
|
|
5
|
+
isError?: true;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type SharedMcpTool = {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
schema: Record<string, unknown>;
|
|
12
|
+
handler: (input: any) => McpToolResult | Promise<McpToolResult>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function textResult(text: string): McpToolResult {
|
|
16
|
+
return { content: [{ type: "text", text }] };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function errorResult(text: string): McpToolResult {
|
|
20
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
21
|
+
}
|