negotium 0.1.7 → 0.1.8

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.
Files changed (256) hide show
  1. package/README.md +2 -2
  2. package/dist/chunk-0ay6cjdy.js +1331 -0
  3. package/dist/chunk-0ay6cjdy.js.map +16 -0
  4. package/dist/chunk-1k7t7bnx.js +97 -0
  5. package/dist/chunk-1k7t7bnx.js.map +10 -0
  6. package/dist/chunk-5ahkew9c.js +806 -0
  7. package/dist/chunk-5ahkew9c.js.map +17 -0
  8. package/dist/chunk-6cpn7ht2.js +287 -0
  9. package/dist/chunk-6cpn7ht2.js.map +13 -0
  10. package/dist/chunk-6kctn20e.js +3529 -0
  11. package/dist/chunk-6kctn20e.js.map +20 -0
  12. package/dist/chunk-7xqj7aex.js +77 -0
  13. package/dist/chunk-7xqj7aex.js.map +11 -0
  14. package/dist/chunk-9p0ww3tk.js +41 -0
  15. package/dist/chunk-9p0ww3tk.js.map +10 -0
  16. package/dist/chunk-a92a7nrk.js +252 -0
  17. package/dist/chunk-a92a7nrk.js.map +10 -0
  18. package/dist/chunk-c500xe9z.js +33 -0
  19. package/dist/chunk-c500xe9z.js.map +10 -0
  20. package/dist/chunk-cds1qxsf.js +1644 -0
  21. package/dist/chunk-cds1qxsf.js.map +17 -0
  22. package/dist/chunk-e4407g8x.js +274 -0
  23. package/dist/chunk-e4407g8x.js.map +10 -0
  24. package/dist/chunk-e5pbggsh.js +363 -0
  25. package/dist/chunk-e5pbggsh.js.map +11 -0
  26. package/dist/chunk-edb07sjp.js +567 -0
  27. package/dist/chunk-edb07sjp.js.map +14 -0
  28. package/dist/chunk-exd0v74a.js +3012 -0
  29. package/dist/chunk-exd0v74a.js.map +32 -0
  30. package/dist/chunk-fgrme9tr.js +4398 -0
  31. package/dist/chunk-fgrme9tr.js.map +47 -0
  32. package/dist/chunk-fka9eqe1.js +581 -0
  33. package/dist/chunk-fka9eqe1.js.map +11 -0
  34. package/dist/chunk-g82jygez.js +108 -0
  35. package/dist/chunk-g82jygez.js.map +10 -0
  36. package/dist/chunk-ghs2j1fr.js +312 -0
  37. package/dist/chunk-ghs2j1fr.js.map +10 -0
  38. package/dist/chunk-gmy11efb.js +125 -0
  39. package/dist/chunk-gmy11efb.js.map +10 -0
  40. package/dist/chunk-gxma4c70.js +110 -0
  41. package/dist/chunk-gxma4c70.js.map +10 -0
  42. package/dist/chunk-hkdqhm34.js +61 -0
  43. package/dist/chunk-hkdqhm34.js.map +10 -0
  44. package/dist/chunk-hpa0cpzh.js +134 -0
  45. package/dist/chunk-hpa0cpzh.js.map +10 -0
  46. package/dist/chunk-hwy3n31w.js +21 -0
  47. package/dist/chunk-hwy3n31w.js.map +9 -0
  48. package/dist/chunk-j2behpy7.js +214 -0
  49. package/dist/chunk-j2behpy7.js.map +10 -0
  50. package/dist/chunk-jc0at0tc.js +102 -0
  51. package/dist/chunk-jc0at0tc.js.map +10 -0
  52. package/dist/chunk-jkqq5ef6.js +243 -0
  53. package/dist/chunk-jkqq5ef6.js.map +11 -0
  54. package/dist/chunk-jzkw2f01.js +51 -0
  55. package/dist/chunk-jzkw2f01.js.map +10 -0
  56. package/dist/chunk-jzz91jwh.js +70 -0
  57. package/dist/chunk-jzz91jwh.js.map +10 -0
  58. package/dist/chunk-k9z47mab.js +247 -0
  59. package/dist/chunk-k9z47mab.js.map +12 -0
  60. package/dist/chunk-n17yxpa8.js +23 -0
  61. package/dist/chunk-n17yxpa8.js.map +10 -0
  62. package/dist/chunk-naa09ags.js +2143 -0
  63. package/dist/chunk-naa09ags.js.map +15 -0
  64. package/dist/chunk-ns3ke88n.js +142 -0
  65. package/dist/chunk-ns3ke88n.js.map +10 -0
  66. package/dist/chunk-p3cr2ejg.js +1626 -0
  67. package/dist/chunk-p3cr2ejg.js.map +15 -0
  68. package/dist/chunk-q5bcpf23.js +2925 -0
  69. package/dist/chunk-q5bcpf23.js.map +20 -0
  70. package/dist/chunk-rafc55bs.js +424 -0
  71. package/dist/chunk-rafc55bs.js.map +11 -0
  72. package/dist/chunk-rbrxjvbj.js +2830 -0
  73. package/dist/chunk-rbrxjvbj.js.map +23 -0
  74. package/dist/chunk-sa874j5c.js +9 -0
  75. package/dist/chunk-sa874j5c.js.map +10 -0
  76. package/dist/chunk-sgsz2fpq.js +289 -0
  77. package/dist/chunk-sgsz2fpq.js.map +10 -0
  78. package/dist/chunk-sm6fh5c5.js +569 -0
  79. package/dist/chunk-sm6fh5c5.js.map +14 -0
  80. package/dist/chunk-t9zcg8f1.js +499 -0
  81. package/dist/chunk-t9zcg8f1.js.map +13 -0
  82. package/dist/chunk-vp2db42d.js +121 -0
  83. package/dist/chunk-vp2db42d.js.map +10 -0
  84. package/dist/chunk-w13jjf4a.js +46 -0
  85. package/dist/chunk-w13jjf4a.js.map +10 -0
  86. package/dist/chunk-x1arr7ze.js +63 -0
  87. package/dist/chunk-x1arr7ze.js.map +10 -0
  88. package/dist/chunk-xfq68rch.js +583 -0
  89. package/dist/chunk-xfq68rch.js.map +11 -0
  90. package/dist/main.js +132 -20
  91. package/dist/main.js.map +4 -4
  92. package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
  93. package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
  94. package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
  95. package/dist/runtime/src/agents/archiver.ts +396 -0
  96. package/dist/runtime/src/agents/auth-check.ts +64 -0
  97. package/dist/runtime/src/agents/claude-provider.ts +737 -0
  98. package/dist/runtime/src/agents/claude-registry.ts +115 -0
  99. package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
  100. package/dist/runtime/src/agents/codex-provider.ts +785 -0
  101. package/dist/runtime/src/agents/codex-registry.ts +115 -0
  102. package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
  103. package/dist/runtime/src/agents/contracts.ts +69 -0
  104. package/dist/runtime/src/agents/deep-map.ts +13 -0
  105. package/dist/runtime/src/agents/execution-host.ts +91 -0
  106. package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
  107. package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
  108. package/dist/runtime/src/agents/fork.ts +65 -0
  109. package/dist/runtime/src/agents/hosted-agent.ts +48 -0
  110. package/dist/runtime/src/agents/idle-archiver.ts +128 -0
  111. package/dist/runtime/src/agents/index.ts +205 -0
  112. package/dist/runtime/src/agents/maestro-provider.ts +206 -0
  113. package/dist/runtime/src/agents/maestro-registry.ts +31 -0
  114. package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
  115. package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
  116. package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
  117. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
  118. package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
  119. package/dist/runtime/src/agents/model-catalog.ts +151 -0
  120. package/dist/runtime/src/agents/registry.ts +24 -0
  121. package/dist/runtime/src/agents/rollout/claude.ts +331 -0
  122. package/dist/runtime/src/agents/rollout/codex.ts +569 -0
  123. package/dist/runtime/src/agents/rollout/shared.ts +205 -0
  124. package/dist/runtime/src/agents/self-config-core.ts +415 -0
  125. package/dist/runtime/src/agents/task-events.ts +44 -0
  126. package/dist/runtime/src/agents/tool-format.ts +352 -0
  127. package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
  128. package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
  129. package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
  130. package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
  131. package/dist/runtime/src/application/submit-user-message.ts +47 -0
  132. package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
  133. package/dist/runtime/src/application/switch-topic-model.ts +79 -0
  134. package/dist/runtime/src/application/topic-service.ts +139 -0
  135. package/dist/runtime/src/application/vault-command.ts +170 -0
  136. package/dist/runtime/src/bus.ts +319 -0
  137. package/dist/runtime/src/index.ts +333 -0
  138. package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
  139. package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
  140. package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
  141. package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
  142. package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
  143. package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
  144. package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
  145. package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
  146. package/dist/runtime/src/mcp/runtime-spec.ts +134 -0
  147. package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
  148. package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
  149. package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
  150. package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
  151. package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
  152. package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
  153. package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
  154. package/dist/runtime/src/mcp/system-health-server.ts +130 -0
  155. package/dist/runtime/src/mcp/task-server.ts +173 -0
  156. package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
  157. package/dist/runtime/src/mcp/vault-http.ts +219 -0
  158. package/dist/runtime/src/mcp/vault-run.ts +153 -0
  159. package/dist/runtime/src/mcp/vault-server.ts +104 -0
  160. package/dist/runtime/src/mcp/wiki-server.ts +755 -0
  161. package/dist/runtime/src/media/file-events.ts +33 -0
  162. package/dist/runtime/src/media/text-extractor.ts +312 -0
  163. package/dist/runtime/src/media/video.ts +92 -0
  164. package/dist/runtime/src/outbox/file-ops.ts +210 -0
  165. package/dist/runtime/src/outbox/index.ts +8 -0
  166. package/dist/runtime/src/outbox/utils.ts +51 -0
  167. package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
  168. package/dist/runtime/src/platform/config.ts +371 -0
  169. package/dist/runtime/src/platform/constants.ts +18 -0
  170. package/dist/runtime/src/platform/debug-users.ts +14 -0
  171. package/dist/runtime/src/platform/delay.ts +1 -0
  172. package/dist/runtime/src/platform/error.ts +9 -0
  173. package/dist/runtime/src/platform/file-utils.ts +16 -0
  174. package/dist/runtime/src/platform/jsonl.ts +202 -0
  175. package/dist/runtime/src/platform/lifecycle.ts +174 -0
  176. package/dist/runtime/src/platform/logger.ts +31 -0
  177. package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
  178. package/dist/runtime/src/platform/mcp-config.ts +865 -0
  179. package/dist/runtime/src/platform/modules.ts +145 -0
  180. package/dist/runtime/src/platform/node-plugins.ts +46 -0
  181. package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
  182. package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
  183. package/dist/runtime/src/prompts/builders.ts +357 -0
  184. package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
  185. package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
  186. package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
  187. package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
  188. package/dist/runtime/src/query/active-rooms.ts +532 -0
  189. package/dist/runtime/src/query/control.ts +313 -0
  190. package/dist/runtime/src/query/index.ts +24 -0
  191. package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
  192. package/dist/runtime/src/query/session-inbox-path.ts +42 -0
  193. package/dist/runtime/src/query/state.ts +94 -0
  194. package/dist/runtime/src/query/types.ts +83 -0
  195. package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
  196. package/dist/runtime/src/runtime/attachments.ts +151 -0
  197. package/dist/runtime/src/runtime/background-sessions.ts +83 -0
  198. package/dist/runtime/src/runtime/channel-context.ts +107 -0
  199. package/dist/runtime/src/runtime/errors.ts +69 -0
  200. package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
  201. package/dist/runtime/src/runtime/file-hooks.ts +81 -0
  202. package/dist/runtime/src/runtime/footer.ts +23 -0
  203. package/dist/runtime/src/runtime/inbox.ts +946 -0
  204. package/dist/runtime/src/runtime/tasks.ts +56 -0
  205. package/dist/runtime/src/runtime/topic-config.ts +29 -0
  206. package/dist/runtime/src/runtime/turn-runner.ts +2286 -0
  207. package/dist/runtime/src/runtime/usage-alert.ts +84 -0
  208. package/dist/runtime/src/runtime/visual-html.ts +109 -0
  209. package/dist/runtime/src/runtime/visual-store.ts +430 -0
  210. package/dist/runtime/src/runtime/visuals.ts +171 -0
  211. package/dist/runtime/src/security/sanitize.ts +27 -0
  212. package/dist/runtime/src/security/sensitive-path.ts +34 -0
  213. package/dist/runtime/src/storage/activity-log.ts +152 -0
  214. package/dist/runtime/src/storage/api-messages.ts +617 -0
  215. package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
  216. package/dist/runtime/src/storage/api-topic-config.ts +192 -0
  217. package/dist/runtime/src/storage/api-topics.ts +844 -0
  218. package/dist/runtime/src/storage/app-settings.ts +44 -0
  219. package/dist/runtime/src/storage/browser-profiles.ts +133 -0
  220. package/dist/runtime/src/storage/conversations.ts +268 -0
  221. package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
  222. package/dist/runtime/src/storage/forum/index.ts +4 -0
  223. package/dist/runtime/src/storage/forum/repository.ts +283 -0
  224. package/dist/runtime/src/storage/forum/schema.ts +300 -0
  225. package/dist/runtime/src/storage/forum-db.ts +23 -0
  226. package/dist/runtime/src/storage/runtime-events.ts +130 -0
  227. package/dist/runtime/src/storage/runtime-leases.ts +189 -0
  228. package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
  229. package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
  230. package/dist/runtime/src/storage/runtime-turn-requests.ts +300 -0
  231. package/dist/runtime/src/storage/self-schedules.ts +298 -0
  232. package/dist/runtime/src/storage/session-asks.ts +347 -0
  233. package/dist/runtime/src/storage/sqlite.ts +129 -0
  234. package/dist/runtime/src/storage/tasks.ts +179 -0
  235. package/dist/runtime/src/storage/token-stats.ts +144 -0
  236. package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
  237. package/dist/runtime/src/storage/topic-archive.ts +74 -0
  238. package/dist/runtime/src/storage/topic-settings.ts +67 -0
  239. package/dist/runtime/src/storage/topic-transcript.ts +98 -0
  240. package/dist/runtime/src/storage/vault-crypto.ts +65 -0
  241. package/dist/runtime/src/storage/vault.ts +225 -0
  242. package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
  243. package/dist/runtime/src/storage/wiki.ts +85 -0
  244. package/dist/runtime/src/topics/create.ts +123 -0
  245. package/dist/runtime/src/topics/derive.ts +398 -0
  246. package/dist/runtime/src/topics/lifecycle.ts +311 -0
  247. package/dist/runtime/src/topics/links.ts +7 -0
  248. package/dist/runtime/src/topics/personal-general.ts +65 -0
  249. package/dist/runtime/src/topics/session.ts +381 -0
  250. package/dist/runtime/src/types/api.ts +497 -0
  251. package/dist/runtime/src/types.ts +255 -0
  252. package/dist/runtime/src/version.ts +1 -0
  253. package/dist/runtime/tsconfig.json +13 -0
  254. package/package.json +16 -3
  255. package/dist/main.d.ts +0 -3
  256. 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
+ }