@enderfga/claw-orchestrator 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +218 -0
  3. package/assets/banner.jpg +0 -0
  4. package/configs/council-reviewer-prompt.md +82 -0
  5. package/configs/council-system-prompt.md +141 -0
  6. package/dist/bin/cli.d.ts +13 -0
  7. package/dist/bin/cli.js +460 -0
  8. package/dist/bin/cli.js.map +1 -0
  9. package/dist/src/base-oneshot-session.d.ts +87 -0
  10. package/dist/src/base-oneshot-session.js +228 -0
  11. package/dist/src/base-oneshot-session.js.map +1 -0
  12. package/dist/src/circuit-breaker.d.ts +21 -0
  13. package/dist/src/circuit-breaker.js +49 -0
  14. package/dist/src/circuit-breaker.js.map +1 -0
  15. package/dist/src/consensus.d.ts +20 -0
  16. package/dist/src/consensus.js +52 -0
  17. package/dist/src/consensus.js.map +1 -0
  18. package/dist/src/constants.d.ts +129 -0
  19. package/dist/src/constants.js +138 -0
  20. package/dist/src/constants.js.map +1 -0
  21. package/dist/src/council.d.ts +67 -0
  22. package/dist/src/council.js +914 -0
  23. package/dist/src/council.js.map +1 -0
  24. package/dist/src/embedded-server.d.ts +25 -0
  25. package/dist/src/embedded-server.js +360 -0
  26. package/dist/src/embedded-server.js.map +1 -0
  27. package/dist/src/inbox-manager.d.ts +38 -0
  28. package/dist/src/inbox-manager.js +111 -0
  29. package/dist/src/inbox-manager.js.map +1 -0
  30. package/dist/src/index.d.ts +63 -0
  31. package/dist/src/index.js +973 -0
  32. package/dist/src/index.js.map +1 -0
  33. package/dist/src/logger.d.ts +16 -0
  34. package/dist/src/logger.js +44 -0
  35. package/dist/src/logger.js.map +1 -0
  36. package/dist/src/models.d.ts +69 -0
  37. package/dist/src/models.js +299 -0
  38. package/dist/src/models.js.map +1 -0
  39. package/dist/src/openai-compat.d.ts +224 -0
  40. package/dist/src/openai-compat.js +756 -0
  41. package/dist/src/openai-compat.js.map +1 -0
  42. package/dist/src/persistent-codex-app-session.d.ts +108 -0
  43. package/dist/src/persistent-codex-app-session.js +465 -0
  44. package/dist/src/persistent-codex-app-session.js.map +1 -0
  45. package/dist/src/persistent-codex-session.d.ts +37 -0
  46. package/dist/src/persistent-codex-session.js +208 -0
  47. package/dist/src/persistent-codex-session.js.map +1 -0
  48. package/dist/src/persistent-cursor-session.d.ts +21 -0
  49. package/dist/src/persistent-cursor-session.js +241 -0
  50. package/dist/src/persistent-cursor-session.js.map +1 -0
  51. package/dist/src/persistent-custom-session.d.ts +78 -0
  52. package/dist/src/persistent-custom-session.js +938 -0
  53. package/dist/src/persistent-custom-session.js.map +1 -0
  54. package/dist/src/persistent-gemini-session.d.ts +21 -0
  55. package/dist/src/persistent-gemini-session.js +216 -0
  56. package/dist/src/persistent-gemini-session.js.map +1 -0
  57. package/dist/src/persistent-session.d.ts +80 -0
  58. package/dist/src/persistent-session.js +745 -0
  59. package/dist/src/persistent-session.js.map +1 -0
  60. package/dist/src/proxy/anthropic-adapter.d.ts +136 -0
  61. package/dist/src/proxy/anthropic-adapter.js +392 -0
  62. package/dist/src/proxy/anthropic-adapter.js.map +1 -0
  63. package/dist/src/proxy/handler.d.ts +39 -0
  64. package/dist/src/proxy/handler.js +365 -0
  65. package/dist/src/proxy/handler.js.map +1 -0
  66. package/dist/src/proxy/schema-cleaner.d.ts +11 -0
  67. package/dist/src/proxy/schema-cleaner.js +34 -0
  68. package/dist/src/proxy/schema-cleaner.js.map +1 -0
  69. package/dist/src/proxy/thought-cache.d.ts +19 -0
  70. package/dist/src/proxy/thought-cache.js +53 -0
  71. package/dist/src/proxy/thought-cache.js.map +1 -0
  72. package/dist/src/session-manager.d.ts +317 -0
  73. package/dist/src/session-manager.js +1528 -0
  74. package/dist/src/session-manager.js.map +1 -0
  75. package/dist/src/types.d.ts +513 -0
  76. package/dist/src/types.js +8 -0
  77. package/dist/src/types.js.map +1 -0
  78. package/dist/src/validation.d.ts +31 -0
  79. package/dist/src/validation.js +104 -0
  80. package/dist/src/validation.js.map +1 -0
  81. package/openclaw.plugin.json +122 -0
  82. package/package.json +84 -0
  83. package/skills/SKILL.md +184 -0
  84. package/skills/references/claude-cli-tracking.md +25 -0
  85. package/skills/references/cli.md +187 -0
  86. package/skills/references/council.md +210 -0
  87. package/skills/references/getting-started.md +133 -0
  88. package/skills/references/inbox.md +81 -0
  89. package/skills/references/multi-engine.md +382 -0
  90. package/skills/references/openai-compat.md +203 -0
  91. package/skills/references/sessions.md +191 -0
  92. package/skills/references/tools.md +418 -0
  93. package/skills/references/ultra.md +126 -0
@@ -0,0 +1,973 @@
1
+ /**
2
+ * claw-orchestrator — Plugin entry point
3
+ *
4
+ * Registers tools, hooks, and HTTP routes with the OpenClaw Plugin SDK.
5
+ * When used standalone (no OpenClaw), exports SessionManager for direct use.
6
+ *
7
+ * Lazy initialisation: SessionManager and EmbeddedServer are created only on
8
+ * the first tool call. While the plugin is registered but never used, it
9
+ * consumes no memory beyond the tool schema definitions.
10
+ */
11
+ import { SessionManager } from './session-manager.js';
12
+ import { createProxyHandler } from './proxy/handler.js';
13
+ import { EmbeddedServer } from './embedded-server.js';
14
+ import { sanitizeCwd, validateRegex } from './validation.js';
15
+ // ─── Standalone Export ───────────────────────────────────────────────────────
16
+ export { SessionManager } from './session-manager.js';
17
+ export { PersistentClaudeSession } from './persistent-session.js';
18
+ export { BaseOneShotSession } from './base-oneshot-session.js';
19
+ export { PersistentCodexSession } from './persistent-codex-session.js';
20
+ export { PersistentGeminiSession } from './persistent-gemini-session.js';
21
+ export { PersistentCursorSession } from './persistent-cursor-session.js';
22
+ export { PersistentCustomSession } from './persistent-custom-session.js';
23
+ export { Council, getDefaultCouncilConfig } from './council.js';
24
+ export { parseConsensus, stripConsensusTags, hasConsensusMarker } from './consensus.js';
25
+ export { sanitizeCwd, validateRegex, validateName } from './validation.js';
26
+ export { createConsoleLogger, nullLogger } from './logger.js';
27
+ export { CircuitBreaker } from './circuit-breaker.js';
28
+ export { InboxManager } from './inbox-manager.js';
29
+ export * from './types.js';
30
+ /**
31
+ * OpenClaw plugin object — standard format
32
+ */
33
+ const plugin = {
34
+ id: 'claw-orchestrator',
35
+ name: 'Claw Orchestrator',
36
+ description: 'Run Claude Code, Codex, Gemini, Cursor Agent and custom coding CLIs as one unified runtime — persistent sessions, multi-agent council, worktree isolation, multi-model proxy',
37
+ register(api) {
38
+ const rawConfig = (api.pluginConfig || {});
39
+ // ─── Lazy Init ────────────────────────────────────────────────────────
40
+ //
41
+ // Neither SessionManager nor EmbeddedServer is created at plugin load
42
+ // time. They are initialised on the first tool invocation and reused
43
+ // thereafter. This keeps memory overhead at zero for users who have the
44
+ // plugin installed but do not actively use Claude Code sessions.
45
+ let manager = null;
46
+ let server = null;
47
+ function getManager() {
48
+ if (!manager) {
49
+ api.logger.info('[claw-orchestrator] First use — initialising SessionManager and embedded server');
50
+ manager = new SessionManager(rawConfig);
51
+ server = new EmbeddedServer(manager);
52
+ server.start().catch((err) => api.logger.error('[claw-orchestrator] Embedded server failed to start:', err));
53
+ }
54
+ return manager;
55
+ }
56
+ // ─── Service Lifecycle ────────────────────────────────────────────────
57
+ api.registerService({
58
+ id: 'claw-orchestrator',
59
+ start: () => api.logger.info('[claw-orchestrator] Plugin registered (lazy init — will activate on first use)'),
60
+ stop: () => {
61
+ if (server)
62
+ server.stop().catch(() => { });
63
+ if (manager)
64
+ manager.shutdown().catch(() => { });
65
+ server = null;
66
+ manager = null;
67
+ },
68
+ });
69
+ // ─── Proxy HTTP Route (multi-model support) ───────────────────────────
70
+ //
71
+ // The proxy route handler itself is lightweight (just an HTTP handler
72
+ // function); registering it eagerly is fine. The heavy proxy work only
73
+ // happens when a request actually arrives.
74
+ if (rawConfig.proxy?.enabled !== false) {
75
+ const proxyHandler = createProxyHandler(rawConfig.proxy, {
76
+ anthropicApiKey: process.env.ANTHROPIC_API_KEY,
77
+ openaiApiKey: process.env.OPENAI_API_KEY,
78
+ geminiApiKey: process.env.GEMINI_API_KEY,
79
+ gatewayUrl: process.env.GATEWAY_URL,
80
+ gatewayKey: process.env.GATEWAY_KEY,
81
+ });
82
+ api.registerHttpRoute({
83
+ path: '/v1/claude-code-proxy',
84
+ auth: 'gateway',
85
+ match: 'prefix',
86
+ handler: proxyHandler,
87
+ });
88
+ }
89
+ // ─── Tool registration helper ─────────────────────────────────────────
90
+ //
91
+ // In v3.0 we renamed the engine-coupled `claude_*` tools to engine-neutral
92
+ // names (`session_*`, `team_*`, etc.). The old names remain registered as
93
+ // deprecated aliases for one minor release and will be removed in v3.1.
94
+ //
95
+ // The alias's description is prefixed with `[DEPRECATED]` and the new
96
+ // name so any agent reading the tool list gets a clear hint to migrate.
97
+ function registerToolWithAliases(def, deprecatedAliases = []) {
98
+ api.registerTool(def);
99
+ for (const alias of deprecatedAliases) {
100
+ api.registerTool({
101
+ ...def,
102
+ name: alias,
103
+ description: `[DEPRECATED — use ${def.name}; this alias is removed in v3.1] ${def.description}`,
104
+ });
105
+ }
106
+ }
107
+ // ─── Tool: session_start ──────────────────────────────────────────────
108
+ registerToolWithAliases({
109
+ name: 'session_start',
110
+ description: 'Start a persistent coding session. Supports multiple engines: claude (default) for Claude Code CLI, codex for OpenAI Codex CLI, gemini for Google Gemini CLI, cursor for Cursor Agent CLI, or custom for any user-configured coding agent CLI.',
111
+ parameters: {
112
+ type: 'object',
113
+ properties: {
114
+ name: { type: 'string', description: 'Session name (auto-generated if omitted)' },
115
+ cwd: { type: 'string', description: 'Working directory' },
116
+ engine: {
117
+ type: 'string',
118
+ enum: ['claude', 'codex', 'codex-app', 'gemini', 'cursor', 'custom'],
119
+ description: 'Engine to use (default: claude). codex = `codex exec` per send (no /goal). codex-app = long-running `codex app-server` with /goal support. Use "custom" with customEngine config for any CLI.',
120
+ },
121
+ model: { type: 'string', description: 'Model to use (opus, sonnet, haiku, gemini-pro, o4-mini, etc.)' },
122
+ permissionMode: {
123
+ type: 'string',
124
+ enum: ['acceptEdits', 'bypassPermissions', 'default', 'delegate', 'dontAsk', 'plan', 'auto'],
125
+ },
126
+ effort: { type: 'string', enum: ['low', 'medium', 'high', 'xhigh', 'max', 'auto'] },
127
+ allowedTools: { type: 'array', items: { type: 'string' }, description: 'Tools to auto-approve' },
128
+ disallowedTools: { type: 'array', items: { type: 'string' }, description: 'Tools to deny' },
129
+ maxTurns: { type: 'number', description: 'Max agent loop turns' },
130
+ maxBudgetUsd: { type: 'number', description: 'Max API spend (USD)' },
131
+ systemPrompt: { type: 'string', description: 'Replace system prompt' },
132
+ appendSystemPrompt: { type: 'string', description: 'Append to system prompt' },
133
+ agents: { type: 'object', description: 'Custom sub-agents JSON' },
134
+ agent: { type: 'string', description: 'Default agent to use' },
135
+ bare: { type: 'boolean', description: 'Minimal mode: skip hooks, LSP, auto-memory, CLAUDE.md' },
136
+ worktree: { type: ['string', 'boolean'], description: 'Run in git worktree' },
137
+ fallbackModel: { type: 'string', description: 'Auto fallback when primary overloaded' },
138
+ jsonSchema: { type: 'string', description: 'JSON Schema for structured output' },
139
+ mcpConfig: {
140
+ type: ['string', 'array'],
141
+ items: { type: 'string' },
142
+ description: 'MCP server config file(s)',
143
+ },
144
+ settings: { type: 'string', description: 'Settings.json path or inline JSON' },
145
+ noSessionPersistence: { type: 'boolean', description: 'Do not save session to disk' },
146
+ betas: { type: ['string', 'array'], items: { type: 'string' }, description: 'Custom beta headers' },
147
+ enableAgentTeams: { type: 'boolean', description: 'Enable experimental agent teams' },
148
+ enableAutoMode: { type: 'boolean', description: 'Enable auto permission mode' },
149
+ includeHookEvents: {
150
+ type: 'boolean',
151
+ description: 'Stream hook lifecycle events (PreToolUse/PostToolUse)',
152
+ },
153
+ permissionPromptTool: {
154
+ type: 'string',
155
+ description: 'Delegate permission prompts to this MCP tool (non-interactive)',
156
+ },
157
+ excludeDynamicSystemPromptSections: {
158
+ type: 'boolean',
159
+ description: 'Move cwd/env/git from system prompt to user message for better prompt cache hits (auto-enabled with bare)',
160
+ },
161
+ debug: {
162
+ type: ['string', 'array'],
163
+ items: { type: 'string' },
164
+ description: 'Debug categories (e.g. "api", "mcp", "!statsig")',
165
+ },
166
+ debugFile: { type: 'string', description: 'Write debug output to file instead of stderr' },
167
+ fromPr: { type: 'string', description: 'Resume session linked to a GitHub PR number or URL' },
168
+ channels: {
169
+ type: ['string', 'array'],
170
+ items: { type: 'string' },
171
+ description: 'MCP channel subscriptions (research preview)',
172
+ },
173
+ dangerouslyLoadDevelopmentChannels: {
174
+ type: ['string', 'array'],
175
+ items: { type: 'string' },
176
+ description: 'Load development MCP channels',
177
+ },
178
+ enablePromptCaching1H: {
179
+ type: 'boolean',
180
+ description: 'Enable 1-hour prompt cache TTL (auto-enabled with bare)',
181
+ },
182
+ // CLI 2.1.121 features
183
+ forkSubagent: {
184
+ type: 'boolean',
185
+ description: 'Fork subagent for non-interactive sessions (sets CLAUDE_CODE_FORK_SUBAGENT=1)',
186
+ },
187
+ enableToolSearch: {
188
+ type: 'boolean',
189
+ description: 'Enable Vertex AI tool search (sets ENABLE_TOOL_SEARCH=1)',
190
+ },
191
+ otelLogUserPrompts: {
192
+ type: 'boolean',
193
+ description: 'OpenTelemetry: log user prompts (sets OTEL_LOG_USER_PROMPTS=1)',
194
+ },
195
+ otelLogRawApiBodies: {
196
+ type: 'boolean',
197
+ description: 'OpenTelemetry: log raw API request/response bodies (debug only, sets OTEL_LOG_RAW_API_BODIES=1)',
198
+ },
199
+ // CLI 2.1.122 features
200
+ bedrockServiceTier: {
201
+ type: 'string',
202
+ enum: ['default', 'flex', 'priority'],
203
+ description: 'AWS Bedrock service tier (sets ANTHROPIC_BEDROCK_SERVICE_TIER). Only effective when routing through Bedrock.',
204
+ },
205
+ customEngine: {
206
+ type: 'object',
207
+ description: 'Custom engine config (required when engine="custom"). Defines how to invoke any coding agent CLI.',
208
+ properties: {
209
+ name: { type: 'string', description: 'Engine display name' },
210
+ bin: { type: 'string', description: 'Binary path or command' },
211
+ binEnv: { type: 'string', description: 'Env var that overrides bin' },
212
+ persistent: {
213
+ type: 'boolean',
214
+ description: 'true=long-running subprocess (Claude Code style), false=spawn per send (default)',
215
+ },
216
+ args: {
217
+ type: 'object',
218
+ description: 'CLI flag mappings',
219
+ properties: {
220
+ print: { type: 'string' },
221
+ outputFormat: { type: 'string' },
222
+ outputFormatValue: { type: 'string' },
223
+ inputFormat: { type: 'string' },
224
+ inputFormatValue: { type: 'string' },
225
+ skipPermissions: { type: 'string' },
226
+ permissionMode: { type: 'string' },
227
+ model: { type: 'string' },
228
+ systemPrompt: { type: 'string' },
229
+ appendSystemPrompt: { type: 'string' },
230
+ maxTurns: { type: 'string' },
231
+ resume: { type: 'string' },
232
+ verbose: { type: 'string' },
233
+ replayUserMessages: { type: 'string' },
234
+ includePartialMessages: { type: 'string' },
235
+ effort: { type: 'string' },
236
+ workspace: { type: 'string' },
237
+ extra: { type: 'array', items: { type: 'string' } },
238
+ },
239
+ },
240
+ permissionModes: { type: 'object', description: 'Map OpenClaw permission names to CLI values' },
241
+ pricing: {
242
+ type: 'object',
243
+ properties: {
244
+ input: { type: 'number' },
245
+ output: { type: 'number' },
246
+ cached: { type: 'number' },
247
+ },
248
+ },
249
+ contextWindow: { type: 'number' },
250
+ env: { type: 'object', description: 'Extra environment variables' },
251
+ sanitizePatterns: { type: 'array', items: { type: 'string' } },
252
+ },
253
+ required: ['name', 'bin', 'args'],
254
+ },
255
+ resumeSessionId: {
256
+ type: 'string',
257
+ description: 'Resume an existing Claude Code session by its ID (e.g. from ~/.claude/sessions/). Replays conversation history via session/load instead of starting fresh.',
258
+ },
259
+ },
260
+ },
261
+ execute: async (_id, args) => {
262
+ const sanitized = { ...args };
263
+ if (sanitized.cwd)
264
+ sanitized.cwd = sanitizeCwd(sanitized.cwd);
265
+ const info = await getManager().startSession(sanitized);
266
+ return { ok: true, ...info };
267
+ },
268
+ }, ['claude_session_start']);
269
+ // ─── Tool: session_send ───────────────────────────────────────────────
270
+ registerToolWithAliases({
271
+ name: 'session_send',
272
+ description: 'Send a message to a persistent Claude Code session and get the response',
273
+ parameters: {
274
+ type: 'object',
275
+ properties: {
276
+ name: { type: 'string', description: 'Session name' },
277
+ message: { type: 'string', description: 'Message to send' },
278
+ effort: {
279
+ type: 'string',
280
+ enum: ['low', 'medium', 'high', 'xhigh', 'max'],
281
+ description: 'Effort for this message',
282
+ },
283
+ plan: { type: 'boolean', description: 'Enable plan mode' },
284
+ timeout: { type: 'number', description: 'Timeout in ms (default 300000)' },
285
+ stream: {
286
+ type: 'boolean',
287
+ description: 'Collect text chunks as they arrive and include them in result.chunks[] (default false). Note: OpenClaw plugin SDK does not yet support mid-tool streaming to the caller, so chunks are buffered and returned with the final result.',
288
+ },
289
+ },
290
+ required: ['name', 'message'],
291
+ },
292
+ execute: async (_id, args) => {
293
+ const wantChunks = args.stream;
294
+ const chunks = [];
295
+ const result = await getManager().sendMessage(args.name, args.message, {
296
+ effort: args.effort,
297
+ plan: args.plan,
298
+ timeout: args.timeout,
299
+ // When stream:true, collect chunks into array for caller.
300
+ // True mid-tool streaming requires SDK-level support (not yet available).
301
+ onChunk: wantChunks
302
+ ? (chunk) => {
303
+ chunks.push(chunk);
304
+ }
305
+ : undefined,
306
+ });
307
+ return {
308
+ ok: true,
309
+ ...result,
310
+ ...(wantChunks ? { chunks } : {}),
311
+ };
312
+ },
313
+ }, ['claude_session_send']);
314
+ // ─── Tool: session_stop ───────────────────────────────────────────────
315
+ registerToolWithAliases({
316
+ name: 'session_stop',
317
+ description: 'Stop a persistent Claude Code session',
318
+ parameters: {
319
+ type: 'object',
320
+ properties: { name: { type: 'string', description: 'Session name' } },
321
+ required: ['name'],
322
+ },
323
+ execute: async (_id, args) => {
324
+ await getManager().stopSession(args.name);
325
+ return { ok: true };
326
+ },
327
+ }, ['claude_session_stop']);
328
+ // ─── Tool: session_list ───────────────────────────────────────────────
329
+ registerToolWithAliases({
330
+ name: 'session_list',
331
+ description: 'List all active Claude Code sessions',
332
+ parameters: { type: 'object', properties: {} },
333
+ execute: async (_id) => {
334
+ if (!manager)
335
+ return { ok: true, sessions: [], persisted: [] };
336
+ return { ok: true, sessions: manager.listSessions(), persisted: manager.listPersistedSessions() };
337
+ },
338
+ }, ['claude_session_list']);
339
+ // ─── Tool: sessions_overview ──────────────────────────────────────────
340
+ registerToolWithAliases({
341
+ name: 'sessions_overview',
342
+ description: 'Get an aggregate overview of all active Claude Code sessions — readiness, busy/paused state, cost, context usage, and last activity for each. Use this for a dashboard view across all sessions. For single-session detail, use session_status instead.',
343
+ parameters: { type: 'object', properties: {} },
344
+ execute: async (_id) => {
345
+ if (!manager)
346
+ return {
347
+ ok: true,
348
+ version: 'unknown',
349
+ sessions: 0,
350
+ sessionNames: [],
351
+ uptime: process.uptime(),
352
+ details: [],
353
+ };
354
+ return manager.health();
355
+ },
356
+ }, ['claude_sessions_overview']);
357
+ // ─── Tool: session_status ─────────────────────────────────────────────
358
+ registerToolWithAliases({
359
+ name: 'session_status',
360
+ description: 'Get detailed status of a Claude Code session (context %, tokens, cost, uptime)',
361
+ parameters: {
362
+ type: 'object',
363
+ properties: { name: { type: 'string', description: 'Session name' } },
364
+ required: ['name'],
365
+ },
366
+ execute: async (_id, args) => {
367
+ const status = getManager().getStatus(args.name);
368
+ return { ok: true, ...status };
369
+ },
370
+ }, ['claude_session_status']);
371
+ // ─── Tool: session_grep ───────────────────────────────────────────────
372
+ registerToolWithAliases({
373
+ name: 'session_grep',
374
+ description: 'Search session history for events matching a regex pattern',
375
+ parameters: {
376
+ type: 'object',
377
+ properties: {
378
+ name: { type: 'string', description: 'Session name' },
379
+ pattern: { type: 'string', description: 'Regex pattern to search' },
380
+ limit: { type: 'number', description: 'Max results (default 50)' },
381
+ },
382
+ required: ['name', 'pattern'],
383
+ },
384
+ execute: async (_id, args) => {
385
+ validateRegex(args.pattern);
386
+ const matches = await getManager().grepSession(args.name, args.pattern, args.limit);
387
+ return { ok: true, count: matches.length, matches };
388
+ },
389
+ }, ['claude_session_grep']);
390
+ // ─── Tool: session_compact ────────────────────────────────────────────
391
+ registerToolWithAliases({
392
+ name: 'session_compact',
393
+ description: 'Compact a session to reclaim context window space',
394
+ parameters: {
395
+ type: 'object',
396
+ properties: {
397
+ name: { type: 'string', description: 'Session name' },
398
+ summary: { type: 'string', description: 'Optional summary for compaction' },
399
+ },
400
+ required: ['name'],
401
+ },
402
+ execute: async (_id, args) => {
403
+ await getManager().compactSession(args.name, args.summary);
404
+ return { ok: true };
405
+ },
406
+ }, ['claude_session_compact']);
407
+ // ─── Tool: agents_list ────────────────────────────────────────────────
408
+ registerToolWithAliases({
409
+ name: 'agents_list',
410
+ description: 'List agent definitions from .claude/agents/',
411
+ parameters: {
412
+ type: 'object',
413
+ properties: { cwd: { type: 'string', description: 'Project directory' } },
414
+ },
415
+ execute: async (_id, args) => {
416
+ const agents = getManager().listAgents(sanitizeCwd(args.cwd));
417
+ return { ok: true, agents };
418
+ },
419
+ }, ['claude_agents_list']);
420
+ // ─── Tool: team_list ──────────────────────────────────────────────────
421
+ registerToolWithAliases({
422
+ name: 'team_list',
423
+ description: 'List teammates in an agent team session (requires enableAgentTeams)',
424
+ parameters: {
425
+ type: 'object',
426
+ properties: { name: { type: 'string', description: 'Session name' } },
427
+ required: ['name'],
428
+ },
429
+ execute: async (_id, args) => {
430
+ const response = await getManager().teamList(args.name);
431
+ return { ok: true, response };
432
+ },
433
+ }, ['claude_team_list']);
434
+ // ─── Tool: team_send ──────────────────────────────────────────────────
435
+ registerToolWithAliases({
436
+ name: 'team_send',
437
+ description: 'Send a message to a specific teammate in an agent team session',
438
+ parameters: {
439
+ type: 'object',
440
+ properties: {
441
+ name: { type: 'string', description: 'Session name' },
442
+ teammate: { type: 'string', description: 'Teammate name' },
443
+ message: { type: 'string', description: 'Message to send' },
444
+ },
445
+ required: ['name', 'teammate', 'message'],
446
+ },
447
+ execute: async (_id, args) => {
448
+ const result = await getManager().teamSend(args.name, args.teammate, args.message);
449
+ return { ok: true, ...result };
450
+ },
451
+ }, ['claude_team_send']);
452
+ // ─── Tool: session_update_tools ───────────────────────────────────────
453
+ registerToolWithAliases({
454
+ name: 'session_update_tools',
455
+ description: 'Update allowedTools or disallowedTools for a running session. Restarts the session process with --resume to apply the new tool constraints while preserving conversation history. Rejects if the session is currently busy.',
456
+ parameters: {
457
+ type: 'object',
458
+ properties: {
459
+ name: { type: 'string', description: 'Session name' },
460
+ allowedTools: {
461
+ type: 'array',
462
+ items: { type: 'string' },
463
+ description: 'New allowedTools list (replaces existing, or merges if merge:true)',
464
+ },
465
+ disallowedTools: {
466
+ type: 'array',
467
+ items: { type: 'string' },
468
+ description: 'New disallowedTools list (replaces existing, or merges if merge:true)',
469
+ },
470
+ removeTools: {
471
+ type: 'array',
472
+ items: { type: 'string' },
473
+ description: 'Tools to remove from allowedTools/disallowedTools (applied after merge)',
474
+ },
475
+ merge: { type: 'boolean', description: 'Merge with existing lists instead of replacing (default false)' },
476
+ },
477
+ required: ['name'],
478
+ },
479
+ execute: async (_id, args) => {
480
+ const info = await getManager().updateTools(args.name, {
481
+ allowedTools: args.allowedTools,
482
+ disallowedTools: args.disallowedTools,
483
+ removeTools: args.removeTools,
484
+ merge: args.merge,
485
+ });
486
+ return { ok: true, restarted: true, ...info };
487
+ },
488
+ }, ['claude_session_update_tools']);
489
+ // ─── Tool: session_switch_model ───────────────────────────────────────
490
+ registerToolWithAliases({
491
+ name: 'session_switch_model',
492
+ description: 'Switch the model for a running session immediately. Restarts the session process with --resume so the new model takes effect on the next message while preserving conversation history.',
493
+ parameters: {
494
+ type: 'object',
495
+ properties: {
496
+ name: { type: 'string', description: 'Session name' },
497
+ model: { type: 'string', description: 'New model (opus, sonnet, haiku, gemini-pro, etc.)' },
498
+ },
499
+ required: ['name', 'model'],
500
+ },
501
+ execute: async (_id, args) => {
502
+ const info = await getManager().switchModel(args.name, args.model);
503
+ return { ok: true, restarted: true, ...info };
504
+ },
505
+ }, ['claude_session_switch_model']);
506
+ // ─── Tool: project_purge (CLI 2.1.126) ────────────────────────────────
507
+ registerToolWithAliases({
508
+ name: 'project_purge',
509
+ description: 'Delete Claude Code project state (transcripts, tasks, file history, config entry) via `claude project purge`. Defaults to dry-run for safety — pass dry_run=false to actually delete. Use all=true to purge every project.',
510
+ parameters: {
511
+ type: 'object',
512
+ properties: {
513
+ path: {
514
+ type: 'string',
515
+ description: 'Project path to purge (resolved to absolute). Ignored when all=true. Defaults to current cwd.',
516
+ },
517
+ all: {
518
+ type: 'boolean',
519
+ description: 'Purge state for every project. Mutually exclusive with path.',
520
+ },
521
+ dry_run: {
522
+ type: 'boolean',
523
+ description: 'List what would be deleted without deleting. Defaults to true for safety.',
524
+ },
525
+ },
526
+ },
527
+ execute: async (_id, args) => {
528
+ const result = await getManager().purgeProject({
529
+ path: args.path,
530
+ all: args.all,
531
+ dryRun: args.dry_run,
532
+ });
533
+ return { ok: true, ...result };
534
+ },
535
+ }, ['claude_project_purge']);
536
+ // ─── Tool: codex_resume (Codex 0.119+) ──────────────────────────────
537
+ api.registerTool({
538
+ name: 'codex_resume',
539
+ description: 'Resume a previously recorded Codex thread by UUID/name, or pick the most recent with last=true. Spawns `codex exec resume` with --json so the output is parsed into structured fields. Independent of session manager state — useful for cross-process continuity.',
540
+ parameters: {
541
+ type: 'object',
542
+ properties: {
543
+ session_id: { type: 'string', description: 'Codex thread UUID or name. Mutually exclusive with last.' },
544
+ last: { type: 'boolean', description: 'Resume the most recent recorded Codex session.' },
545
+ message: { type: 'string', description: 'Prompt to send after resuming.' },
546
+ cwd: { type: 'string', description: 'Working directory to run codex in.' },
547
+ model: { type: 'string', description: 'Override model (e.g. gpt-5.5).' },
548
+ timeout: { type: 'number', description: 'Timeout in ms (default 300000).' },
549
+ },
550
+ required: ['message'],
551
+ },
552
+ execute: async (_id, args) => {
553
+ return await getManager().codexResume({
554
+ sessionId: args.session_id,
555
+ last: args.last,
556
+ message: args.message,
557
+ cwd: args.cwd,
558
+ model: args.model,
559
+ timeout: args.timeout,
560
+ });
561
+ },
562
+ });
563
+ // ─── Tool: codex_review ─────────────────────────────────────────────
564
+ api.registerTool({
565
+ name: 'codex_review',
566
+ description: 'Run a non-interactive Codex code review (`codex review`). Pick exactly one diff scope: uncommitted (working-tree changes), base (vs branch), or commit (vs SHA). Output is plain text — Codex `review` does not emit JSON.',
567
+ parameters: {
568
+ type: 'object',
569
+ properties: {
570
+ prompt: { type: 'string', description: 'Custom review instructions (optional).' },
571
+ cwd: { type: 'string', description: 'Repository to review. Defaults to current cwd.' },
572
+ uncommitted: {
573
+ type: 'boolean',
574
+ description: 'Review staged + unstaged + untracked changes. Mutually exclusive with base/commit.',
575
+ },
576
+ base: {
577
+ type: 'string',
578
+ description: 'Review changes against this base branch. Mutually exclusive with uncommitted/commit.',
579
+ },
580
+ commit: {
581
+ type: 'string',
582
+ description: 'Review changes introduced by this commit SHA. Mutually exclusive with uncommitted/base.',
583
+ },
584
+ title: { type: 'string', description: 'Optional commit title shown in the review summary.' },
585
+ model: { type: 'string', description: 'Override model.' },
586
+ timeout: { type: 'number', description: 'Timeout in ms (default 600000).' },
587
+ },
588
+ },
589
+ execute: async (_id, args) => {
590
+ return await getManager().codexReview({
591
+ prompt: args.prompt,
592
+ cwd: args.cwd,
593
+ uncommitted: args.uncommitted,
594
+ base: args.base,
595
+ commit: args.commit,
596
+ title: args.title,
597
+ model: args.model,
598
+ timeout: args.timeout,
599
+ });
600
+ },
601
+ });
602
+ // ─── Tools: codex_goal_* (Codex 0.128 /goal slash commands) ────────
603
+ //
604
+ // Goal mutation in Codex's v2 protocol is **server-side**: clients drive
605
+ // it by sending the `/goal <args>` slash text via `turn/start`. These
606
+ // tools are convenience wrappers around that pattern, scoped to sessions
607
+ // started with engine: "codex-app". Calls against any other engine
608
+ // surface a clear error rather than silently no-op'ing.
609
+ api.registerTool({
610
+ name: 'codex_goal_set',
611
+ description: 'Set a long-horizon objective on a codex-app session. Sends `/goal <objective>` via the app-server slash command. Requires engine: "codex-app".',
612
+ parameters: {
613
+ type: 'object',
614
+ properties: {
615
+ name: { type: 'string', description: 'Session name (must be a codex-app session).' },
616
+ objective: {
617
+ type: 'string',
618
+ description: 'The objective text. Codex will pursue this across turns until achieved, paused, or cleared.',
619
+ },
620
+ timeout: { type: 'number', description: 'Timeout in ms for the resulting turn (default 120000).' },
621
+ },
622
+ required: ['name', 'objective'],
623
+ },
624
+ execute: async (_id, args) => {
625
+ return await getManager().codexGoalCommand(args.name, args.objective, args.timeout);
626
+ },
627
+ });
628
+ api.registerTool({
629
+ name: 'codex_goal_get',
630
+ description: 'Read the cached goal state on a codex-app session (objective, status, tokensUsed, timeUsedSeconds, tokenBudget). Returns null if no goal is active. Pure read — does not send any turn.',
631
+ parameters: {
632
+ type: 'object',
633
+ properties: { name: { type: 'string', description: 'Session name (must be a codex-app session).' } },
634
+ required: ['name'],
635
+ },
636
+ execute: async (_id, args) => {
637
+ return getManager().codexGoalGet(args.name);
638
+ },
639
+ });
640
+ api.registerTool({
641
+ name: 'codex_goal_pause',
642
+ description: 'Pause goal pursuit on a codex-app session. Sends `/goal pause`.',
643
+ parameters: {
644
+ type: 'object',
645
+ properties: {
646
+ name: { type: 'string', description: 'Session name.' },
647
+ timeout: { type: 'number', description: 'Timeout in ms (default 120000).' },
648
+ },
649
+ required: ['name'],
650
+ },
651
+ execute: async (_id, args) => {
652
+ return await getManager().codexGoalCommand(args.name, 'pause', args.timeout);
653
+ },
654
+ });
655
+ api.registerTool({
656
+ name: 'codex_goal_resume',
657
+ description: 'Resume a paused goal on a codex-app session. Sends `/goal resume`.',
658
+ parameters: {
659
+ type: 'object',
660
+ properties: {
661
+ name: { type: 'string', description: 'Session name.' },
662
+ timeout: { type: 'number', description: 'Timeout in ms (default 120000).' },
663
+ },
664
+ required: ['name'],
665
+ },
666
+ execute: async (_id, args) => {
667
+ return await getManager().codexGoalCommand(args.name, 'resume', args.timeout);
668
+ },
669
+ });
670
+ api.registerTool({
671
+ name: 'codex_goal_clear',
672
+ description: 'Clear the active goal on a codex-app session. Sends `/goal clear`.',
673
+ parameters: {
674
+ type: 'object',
675
+ properties: {
676
+ name: { type: 'string', description: 'Session name.' },
677
+ timeout: { type: 'number', description: 'Timeout in ms (default 120000).' },
678
+ },
679
+ required: ['name'],
680
+ },
681
+ execute: async (_id, args) => {
682
+ return await getManager().codexGoalCommand(args.name, 'clear', args.timeout);
683
+ },
684
+ });
685
+ // ─── Tool: council_start ────────────────────────────────────────────
686
+ api.registerTool({
687
+ name: 'council_start',
688
+ description: 'Start a multi-agent council that collaborates on a task using git worktree isolation, round-based execution, and consensus voting. Agents can use different engines (Claude, Codex) and models.',
689
+ parameters: {
690
+ type: 'object',
691
+ properties: {
692
+ task: { type: 'string', description: 'Task description for the council to work on' },
693
+ projectDir: { type: 'string', description: 'Working directory for the council project' },
694
+ agents: {
695
+ type: 'array',
696
+ description: 'Agent personas. Defaults to 3-agent team (Planner, Generator, Evaluator) if omitted.',
697
+ items: {
698
+ type: 'object',
699
+ properties: {
700
+ name: { type: 'string', description: 'Agent display name' },
701
+ emoji: { type: 'string', description: 'Agent emoji identifier' },
702
+ persona: { type: 'string', description: 'Agent personality/expertise description' },
703
+ engine: {
704
+ type: 'string',
705
+ enum: ['claude', 'codex', 'codex-app', 'gemini', 'cursor', 'custom'],
706
+ description: 'Engine (default: claude). Use "custom" with customEngine for any CLI.',
707
+ },
708
+ model: { type: 'string', description: 'Model to use' },
709
+ baseUrl: { type: 'string', description: 'Custom API endpoint (for proxy)' },
710
+ customEngine: { type: 'object', description: 'Custom engine config (when engine="custom")' },
711
+ },
712
+ required: ['name', 'emoji', 'persona'],
713
+ },
714
+ },
715
+ maxRounds: { type: 'number', description: 'Max collaboration rounds (default 15)' },
716
+ agentTimeoutMs: { type: 'number', description: 'Per-agent timeout in ms (default 1800000)' },
717
+ maxTurnsPerAgent: { type: 'number', description: 'Max tool turns per agent per round (default 30)' },
718
+ maxBudgetUsd: { type: 'number', description: 'Max API spend per agent (USD)' },
719
+ defaultPermissionMode: {
720
+ type: 'string',
721
+ enum: ['acceptEdits', 'bypassPermissions', 'default', 'delegate', 'dontAsk', 'plan', 'auto'],
722
+ description: 'Default permission mode for council agents (default: bypassPermissions)',
723
+ },
724
+ },
725
+ required: ['task', 'projectDir'],
726
+ },
727
+ execute: async (_id, args) => {
728
+ const { getDefaultCouncilConfig } = await import('./council.js');
729
+ const projectDir = sanitizeCwd(args.projectDir);
730
+ const defaultConfig = getDefaultCouncilConfig(projectDir);
731
+ const config = {
732
+ name: 'council',
733
+ agents: args.agents || defaultConfig.agents,
734
+ maxRounds: args.maxRounds ?? defaultConfig.maxRounds,
735
+ projectDir,
736
+ agentTimeoutMs: args.agentTimeoutMs,
737
+ maxTurnsPerAgent: args.maxTurnsPerAgent,
738
+ maxBudgetUsd: args.maxBudgetUsd,
739
+ defaultPermissionMode: args.defaultPermissionMode,
740
+ };
741
+ const session = getManager().councilStart(args.task, config);
742
+ return { ok: true, ...session, note: 'Council running in background. Poll with council_status.' };
743
+ },
744
+ });
745
+ // ─── Tool: council_status ───────────────────────────────────────────
746
+ api.registerTool({
747
+ name: 'council_status',
748
+ description: 'Get the status of a running council session',
749
+ parameters: {
750
+ type: 'object',
751
+ properties: { id: { type: 'string', description: 'Council session ID' } },
752
+ required: ['id'],
753
+ },
754
+ execute: async (_id, args) => {
755
+ const session = getManager().councilStatus(args.id);
756
+ if (!session)
757
+ return { ok: false, error: 'Council not found' };
758
+ return { ok: true, ...session };
759
+ },
760
+ });
761
+ // ─── Tool: council_abort ────────────────────────────────────────────
762
+ api.registerTool({
763
+ name: 'council_abort',
764
+ description: 'Abort a running council, stopping all agent sessions',
765
+ parameters: {
766
+ type: 'object',
767
+ properties: { id: { type: 'string', description: 'Council session ID' } },
768
+ required: ['id'],
769
+ },
770
+ execute: async (_id, args) => {
771
+ getManager().councilAbort(args.id);
772
+ return { ok: true };
773
+ },
774
+ });
775
+ // ─── Tool: council_inject ───────────────────────────────────────────
776
+ api.registerTool({
777
+ name: 'council_inject',
778
+ description: 'Inject a user message into the next round of a running council. The message will be appended to all agent prompts in the next round.',
779
+ parameters: {
780
+ type: 'object',
781
+ properties: {
782
+ id: { type: 'string', description: 'Council session ID' },
783
+ message: { type: 'string', description: 'Message to inject' },
784
+ },
785
+ required: ['id', 'message'],
786
+ },
787
+ execute: async (_id, args) => {
788
+ getManager().councilInject(args.id, args.message);
789
+ return { ok: true };
790
+ },
791
+ });
792
+ // ─── Tool: council_review ──────────────────────────────────────────
793
+ api.registerTool({
794
+ name: 'council_review',
795
+ description: 'Review a completed council session. Returns a structured report of all changed files, branches, worktrees, plan.md status, review files, and agent summaries. Does not modify any state — purely informational. Use this before deciding to accept or reject.',
796
+ parameters: {
797
+ type: 'object',
798
+ properties: { id: { type: 'string', description: 'Council session ID' } },
799
+ required: ['id'],
800
+ },
801
+ execute: async (_id, args) => {
802
+ const result = await getManager().councilReview(args.id);
803
+ return { ok: true, ...result };
804
+ },
805
+ });
806
+ // ─── Tool: council_accept ──────────────────────────────────────────
807
+ api.registerTool({
808
+ name: 'council_accept',
809
+ description: 'Accept and finalize council work. Cleans up all council scaffolding: removes worktrees, deletes council/* branches, removes plan.md and reviews/ directory. Only call after reviewing with council_review.',
810
+ parameters: {
811
+ type: 'object',
812
+ properties: { id: { type: 'string', description: 'Council session ID' } },
813
+ required: ['id'],
814
+ },
815
+ execute: async (_id, args) => {
816
+ const result = await getManager().councilAccept(args.id);
817
+ return { ok: true, ...result };
818
+ },
819
+ });
820
+ // ─── Tool: council_reject ──────────────────────────────────────────
821
+ api.registerTool({
822
+ name: 'council_reject',
823
+ description: 'Reject council work and provide feedback. Rewrites plan.md with rejection feedback and commits it. Does NOT delete any worktrees or branches — the council can be restarted to retry. Use this when the council output is incomplete or broken.',
824
+ parameters: {
825
+ type: 'object',
826
+ properties: {
827
+ id: { type: 'string', description: 'Council session ID' },
828
+ feedback: {
829
+ type: 'string',
830
+ description: 'Detailed feedback explaining why the work is rejected and what needs to be fixed',
831
+ },
832
+ },
833
+ required: ['id', 'feedback'],
834
+ },
835
+ execute: async (_id, args) => {
836
+ const result = await getManager().councilReject(args.id, args.feedback);
837
+ return { ok: true, ...result };
838
+ },
839
+ });
840
+ // ─── Tool: session_send_to ────────────────────────────────────────────
841
+ registerToolWithAliases({
842
+ name: 'session_send_to',
843
+ description: 'Send a cross-session message from one session to another. If the target is idle, the message is delivered immediately. If busy, it is queued in the inbox for later delivery. Use "*" as target to broadcast to all other sessions.',
844
+ parameters: {
845
+ type: 'object',
846
+ properties: {
847
+ from: { type: 'string', description: 'Sender session name' },
848
+ to: { type: 'string', description: 'Target session name, or "*" for broadcast' },
849
+ message: { type: 'string', description: 'Message text' },
850
+ summary: { type: 'string', description: 'Short preview (5-10 words)' },
851
+ },
852
+ required: ['from', 'to', 'message'],
853
+ },
854
+ execute: async (_id, args) => {
855
+ const result = await getManager().sessionSendTo(args.from, args.to, args.message, args.summary);
856
+ return { ok: true, ...result };
857
+ },
858
+ }, ['claude_session_send_to']);
859
+ // ─── Tool: session_inbox ──────────────────────────────────────────────
860
+ registerToolWithAliases({
861
+ name: 'session_inbox',
862
+ description: 'Read inbox messages for a session. Returns unread messages by default.',
863
+ parameters: {
864
+ type: 'object',
865
+ properties: {
866
+ name: { type: 'string', description: 'Session name' },
867
+ unreadOnly: { type: 'boolean', description: 'Only unread messages (default true)' },
868
+ },
869
+ required: ['name'],
870
+ },
871
+ execute: async (_id, args) => {
872
+ const messages = getManager().sessionInbox(args.name, args.unreadOnly ?? true);
873
+ return { ok: true, count: messages.length, messages };
874
+ },
875
+ }, ['claude_session_inbox']);
876
+ // ─── Tool: session_deliver_inbox ──────────────────────────────────────
877
+ registerToolWithAliases({
878
+ name: 'session_deliver_inbox',
879
+ description: 'Deliver all queued inbox messages to an idle session. Call this when a session finishes a task to process waiting messages.',
880
+ parameters: {
881
+ type: 'object',
882
+ properties: { name: { type: 'string', description: 'Session name' } },
883
+ required: ['name'],
884
+ },
885
+ execute: async (_id, args) => {
886
+ const count = await getManager().sessionDeliverInbox(args.name);
887
+ return { ok: true, delivered: count };
888
+ },
889
+ }, ['claude_session_deliver_inbox']);
890
+ // ─── Tool: ultraplan_start ──────────────────────────────────────
891
+ api.registerTool({
892
+ name: 'ultraplan_start',
893
+ description: 'Start an Ultraplan session: a dedicated Opus planning session that explores your project for up to 30 minutes and produces a detailed implementation plan. Runs in background.',
894
+ parameters: {
895
+ type: 'object',
896
+ properties: {
897
+ task: { type: 'string', description: 'What to plan — describe the feature, refactor, or problem' },
898
+ cwd: { type: 'string', description: 'Project directory to explore' },
899
+ model: { type: 'string', description: 'Model to use (default: opus)' },
900
+ timeout: { type: 'number', description: 'Timeout in ms (default: 1800000 = 30 min)' },
901
+ },
902
+ required: ['task'],
903
+ },
904
+ execute: async (_id, args) => {
905
+ const result = getManager().ultraplanStart(args.task, {
906
+ cwd: sanitizeCwd(args.cwd),
907
+ model: args.model,
908
+ timeout: args.timeout,
909
+ });
910
+ return { ok: true, ...result, note: 'Ultraplan running in background. Poll with ultraplan_status.' };
911
+ },
912
+ });
913
+ // ─── Tool: ultraplan_status ─────────────────────────────────────
914
+ api.registerTool({
915
+ name: 'ultraplan_status',
916
+ description: 'Get the status of an Ultraplan session. Returns the plan text when completed.',
917
+ parameters: {
918
+ type: 'object',
919
+ properties: { id: { type: 'string', description: 'Ultraplan ID' } },
920
+ required: ['id'],
921
+ },
922
+ execute: async (_id, args) => {
923
+ const result = getManager().ultraplanStatus(args.id);
924
+ if (!result)
925
+ return { ok: false, error: 'Ultraplan not found' };
926
+ return { ok: true, ...result };
927
+ },
928
+ });
929
+ // ─── Tool: ultrareview_start ────────────────────────────────────
930
+ api.registerTool({
931
+ name: 'ultrareview_start',
932
+ description: 'Start an Ultrareview: a fleet of bug-hunting agents (5-20) that review your codebase from different angles in parallel. Each agent specializes in a different area (security, performance, logic, types, etc.). Runs in background.',
933
+ parameters: {
934
+ type: 'object',
935
+ properties: {
936
+ cwd: { type: 'string', description: 'Project directory to review' },
937
+ agentCount: { type: 'number', description: 'Number of reviewer agents (1-20, default 5)' },
938
+ maxDurationMinutes: { type: 'number', description: 'Max review duration in minutes (5-25, default 10)' },
939
+ model: { type: 'string', description: 'Model for reviewers (default: session default)' },
940
+ focus: { type: 'string', description: 'Review focus area (default: bugs + security + quality)' },
941
+ },
942
+ required: ['cwd'],
943
+ },
944
+ execute: async (_id, args) => {
945
+ const result = getManager().ultrareviewStart(sanitizeCwd(args.cwd), {
946
+ agentCount: args.agentCount,
947
+ maxDurationMinutes: args.maxDurationMinutes,
948
+ model: args.model,
949
+ focus: args.focus,
950
+ });
951
+ return { ok: true, ...result, note: 'Ultrareview running in background. Poll with ultrareview_status.' };
952
+ },
953
+ });
954
+ // ─── Tool: ultrareview_status ───────────────────────────────────
955
+ api.registerTool({
956
+ name: 'ultrareview_status',
957
+ description: 'Get the status of an Ultrareview. Returns all findings when completed.',
958
+ parameters: {
959
+ type: 'object',
960
+ properties: { id: { type: 'string', description: 'Ultrareview ID' } },
961
+ required: ['id'],
962
+ },
963
+ execute: async (_id, args) => {
964
+ const result = getManager().ultrareviewStatus(args.id);
965
+ if (!result)
966
+ return { ok: false, error: 'Ultrareview not found' };
967
+ return { ok: true, ...result };
968
+ },
969
+ });
970
+ },
971
+ };
972
+ export default plugin;
973
+ //# sourceMappingURL=index.js.map