@swarmclawai/swarmclaw 0.6.7 → 0.7.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.
- package/README.md +82 -39
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +37 -7
- package/src/components/home/home-view.tsx +54 -24
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +87 -19
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +28 -9
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +224 -0
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +72 -48
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +115 -16
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +32 -2
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +78 -0
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -1,788 +1,387 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
-
import { genId } from '@/lib/id'
|
|
4
3
|
import { spawn, spawnSync } from 'child_process'
|
|
5
|
-
import { loadAgents, loadTasks, upsertTask } from '../storage'
|
|
6
|
-
import { log } from '../logger'
|
|
7
4
|
import type { ToolBuildContext } from './context'
|
|
8
|
-
import { truncate,
|
|
5
|
+
import { truncate, findBinaryOnPath, MAX_OUTPUT } from './context'
|
|
6
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
7
|
+
import { getPluginManager } from '../plugins'
|
|
8
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
9
|
+
|
|
10
|
+
const MAX_DELEGATION_CHAIN_HOPS = 128
|
|
11
|
+
|
|
12
|
+
interface DelegateContext {
|
|
13
|
+
cwd?: string
|
|
14
|
+
claudeTimeoutMs?: number
|
|
15
|
+
readStoredDelegateResumeId?: (key: 'claudeCode' | 'codex' | 'opencode') => string | null
|
|
16
|
+
persistDelegateResumeId?: (key: 'claudeCode' | 'codex' | 'opencode', id: string) => void
|
|
17
|
+
ctx?: { platformAssignScope?: string; agentId?: string | null }
|
|
18
|
+
hasTool?: (name: string) => boolean
|
|
19
|
+
}
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
const tools: StructuredToolInterface[] = []
|
|
12
|
-
const { cwd, ctx, claudeTimeoutMs, cliProcessTimeoutMs, persistDelegateResumeId, readStoredDelegateResumeId } = bctx
|
|
21
|
+
type DelegateBackend = 'claude' | 'codex' | 'opencode'
|
|
13
22
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
function asTaskRecord(value: unknown): Record<string, unknown> | null {
|
|
24
|
+
return value && typeof value === 'object' ? value as Record<string, unknown> : null
|
|
25
|
+
}
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
function parseNonNegativeInt(value: unknown): number | null {
|
|
28
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return null
|
|
29
|
+
const int = Math.trunc(value)
|
|
30
|
+
return int >= 0 ? int : null
|
|
31
|
+
}
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
function _computeDelegationDepth(
|
|
34
|
+
task: Record<string, unknown> | null,
|
|
35
|
+
tasksById: Record<string, unknown>,
|
|
36
|
+
): number {
|
|
37
|
+
if (!task) return 0
|
|
38
|
+
const explicitDepth = parseNonNegativeInt(task.delegationDepth)
|
|
39
|
+
if (explicitDepth !== null) return explicitDepth
|
|
40
|
+
if (task.sourceType !== 'delegation') return 0
|
|
41
|
+
|
|
42
|
+
let depth = 1
|
|
43
|
+
let parentId = typeof task.delegatedFromTaskId === 'string' ? task.delegatedFromTaskId.trim() : ''
|
|
44
|
+
let hops = 0
|
|
45
|
+
const visited = new Set<string>()
|
|
46
|
+
|
|
47
|
+
while (parentId && hops < MAX_DELEGATION_CHAIN_HOPS && !visited.has(parentId)) {
|
|
48
|
+
visited.add(parentId)
|
|
49
|
+
const parent = asTaskRecord(tasksById[parentId])
|
|
50
|
+
if (!parent) break
|
|
51
|
+
const parentExplicitDepth = parseNonNegativeInt(parent.delegationDepth)
|
|
52
|
+
if (parentExplicitDepth !== null) {
|
|
53
|
+
depth = Math.max(depth, parentExplicitDepth + 1)
|
|
54
|
+
break
|
|
40
55
|
}
|
|
56
|
+
depth++
|
|
57
|
+
parentId = typeof parent.delegatedFromTaskId === 'string' ? parent.delegatedFromTaskId.trim() : ''
|
|
58
|
+
hops++
|
|
59
|
+
}
|
|
41
60
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
tool(
|
|
45
|
-
async ({ task, resume, resumeId }) => {
|
|
46
|
-
try {
|
|
47
|
-
const env: NodeJS.ProcessEnv = { ...process.env }
|
|
48
|
-
// Running inside Claude environments can block nested `claude` launches.
|
|
49
|
-
// Strip all CLAUDE* vars so delegation can run as an independent subprocess.
|
|
50
|
-
const removedClaudeEnvKeys: string[] = []
|
|
51
|
-
for (const key of Object.keys(env)) {
|
|
52
|
-
if (key.toUpperCase().startsWith('CLAUDE')) {
|
|
53
|
-
removedClaudeEnvKeys.push(key)
|
|
54
|
-
delete env[key]
|
|
55
|
-
}
|
|
56
|
-
}
|
|
61
|
+
return depth
|
|
62
|
+
}
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return 'Error: Claude Code CLI is not authenticated. Run `claude auth login` (or `claude setup-token`) on this machine, then retry.'
|
|
75
|
-
}
|
|
76
|
-
}
|
|
64
|
+
/**
|
|
65
|
+
* Core Delegate Execution Logic
|
|
66
|
+
*/
|
|
67
|
+
async function executeDelegateAction(args: Record<string, unknown>, bctx: DelegateContext) {
|
|
68
|
+
const normalized = normalizeToolInputArgs(args)
|
|
69
|
+
const task = normalized.task as string
|
|
70
|
+
const backend = ((normalized.backend as string) || 'claude') as DelegateBackend
|
|
71
|
+
const resume = normalized.resume as boolean
|
|
72
|
+
const resumeId = normalized.resumeId as string
|
|
73
|
+
const backends = {
|
|
74
|
+
claude: findBinaryOnPath('claude'),
|
|
75
|
+
codex: findBinaryOnPath('codex'),
|
|
76
|
+
opencode: findBinaryOnPath('opencode'),
|
|
77
|
+
}
|
|
78
|
+
const binary = backends[backend as keyof typeof backends]
|
|
79
|
+
if (!binary) return `Error: Backend "${backend}" unavailable.`
|
|
77
80
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
log.info('session-tools', 'delegate_to_claude_code start', {
|
|
84
|
-
sessionId: ctx?.sessionId || null,
|
|
85
|
-
agentId: ctx?.agentId || null,
|
|
86
|
-
cwd,
|
|
87
|
-
timeoutMs: claudeTimeoutMs,
|
|
88
|
-
removedClaudeEnvKeys,
|
|
89
|
-
resumeRequested: !!resume || !!resumeId,
|
|
90
|
-
resumeId: resumeIdToUse || null,
|
|
91
|
-
taskPreview: (task || '').slice(0, 200),
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
return new Promise<string>((resolve) => {
|
|
95
|
-
const args = ['--print', '--output-format', 'stream-json', '--verbose', '--dangerously-skip-permissions']
|
|
96
|
-
if (resumeIdToUse) args.push('--resume', resumeIdToUse)
|
|
97
|
-
const child = spawn(claudeBinary, args, {
|
|
98
|
-
cwd,
|
|
99
|
-
env,
|
|
100
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
101
|
-
})
|
|
102
|
-
let stdout = ''
|
|
103
|
-
let stderr = ''
|
|
104
|
-
let stdoutBuf = ''
|
|
105
|
-
let assistantText = ''
|
|
106
|
-
let discoveredSessionId: string | null = null
|
|
107
|
-
let settled = false
|
|
108
|
-
let timedOut = false
|
|
109
|
-
const startedAt = Date.now()
|
|
110
|
-
|
|
111
|
-
const finish = (result: string) => {
|
|
112
|
-
if (settled) return
|
|
113
|
-
settled = true
|
|
114
|
-
resolve(truncate(result, MAX_OUTPUT))
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const timeoutHandle = setTimeout(() => {
|
|
118
|
-
timedOut = true
|
|
119
|
-
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
120
|
-
setTimeout(() => {
|
|
121
|
-
try { child.kill('SIGKILL') } catch { /* ignore */ }
|
|
122
|
-
}, 5000)
|
|
123
|
-
}, claudeTimeoutMs)
|
|
124
|
-
|
|
125
|
-
log.info('session-tools', 'delegate_to_claude_code spawned', {
|
|
126
|
-
sessionId: ctx?.sessionId || null,
|
|
127
|
-
pid: child.pid || null,
|
|
128
|
-
args,
|
|
129
|
-
})
|
|
130
|
-
child.stdout?.on('data', (chunk: Buffer) => {
|
|
131
|
-
const text = chunk.toString()
|
|
132
|
-
stdout += text
|
|
133
|
-
if (stdout.length > MAX_OUTPUT * 8) stdout = tail(stdout, MAX_OUTPUT * 8)
|
|
134
|
-
stdoutBuf += text
|
|
135
|
-
const lines = stdoutBuf.split('\n')
|
|
136
|
-
stdoutBuf = lines.pop() || ''
|
|
137
|
-
for (const line of lines) {
|
|
138
|
-
if (!line.trim()) continue
|
|
139
|
-
try {
|
|
140
|
-
const ev = JSON.parse(line)
|
|
141
|
-
if (typeof ev?.session_id === 'string' && ev.session_id.trim()) {
|
|
142
|
-
discoveredSessionId = ev.session_id.trim()
|
|
143
|
-
}
|
|
144
|
-
if (ev?.type === 'result' && typeof ev?.result === 'string') {
|
|
145
|
-
assistantText = ev.result
|
|
146
|
-
} else if (ev?.type === 'assistant' && Array.isArray(ev?.message?.content)) {
|
|
147
|
-
const textBlocks = ev.message.content
|
|
148
|
-
.filter((block: any) => block?.type === 'text' && typeof block?.text === 'string')
|
|
149
|
-
.map((block: any) => block.text)
|
|
150
|
-
.join('')
|
|
151
|
-
if (textBlocks) assistantText = textBlocks
|
|
152
|
-
} else if (ev?.type === 'content_block_delta' && typeof ev?.delta?.text === 'string') {
|
|
153
|
-
assistantText += ev.delta.text
|
|
154
|
-
}
|
|
155
|
-
} catch {
|
|
156
|
-
// keep raw stdout fallback when parsing fails
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
child.stderr?.on('data', (chunk: Buffer) => {
|
|
161
|
-
stderr += chunk.toString()
|
|
162
|
-
if (stderr.length > MAX_OUTPUT * 8) stderr = tail(stderr, MAX_OUTPUT * 8)
|
|
163
|
-
})
|
|
164
|
-
child.on('error', (err) => {
|
|
165
|
-
clearTimeout(timeoutHandle)
|
|
166
|
-
log.error('session-tools', 'delegate_to_claude_code child error', {
|
|
167
|
-
sessionId: ctx?.sessionId || null,
|
|
168
|
-
error: err?.message || String(err),
|
|
169
|
-
})
|
|
170
|
-
finish(`Error: failed to start Claude Code CLI: ${err?.message || String(err)}`)
|
|
171
|
-
})
|
|
172
|
-
child.on('close', (code, signal) => {
|
|
173
|
-
clearTimeout(timeoutHandle)
|
|
174
|
-
const durationMs = Date.now() - startedAt
|
|
175
|
-
if (!discoveredSessionId) {
|
|
176
|
-
const guessed = extractResumeIdentifier(`${stdout}\n${stderr}`)
|
|
177
|
-
if (guessed) discoveredSessionId = guessed
|
|
178
|
-
}
|
|
179
|
-
if (discoveredSessionId) persistDelegateResumeId('claudeCode', discoveredSessionId)
|
|
180
|
-
log.info('session-tools', 'delegate_to_claude_code child close', {
|
|
181
|
-
sessionId: ctx?.sessionId || null,
|
|
182
|
-
code,
|
|
183
|
-
signal: signal || null,
|
|
184
|
-
timedOut,
|
|
185
|
-
durationMs,
|
|
186
|
-
stdoutLen: stdout.length,
|
|
187
|
-
stderrLen: stderr.length,
|
|
188
|
-
discoveredSessionId,
|
|
189
|
-
stderrPreview: tail(stderr, 240),
|
|
190
|
-
})
|
|
191
|
-
if (timedOut) {
|
|
192
|
-
const msg = [
|
|
193
|
-
`Error: Claude Code CLI timed out after ${Math.round(claudeTimeoutMs / 1000)}s.`,
|
|
194
|
-
stderr.trim() ? `stderr:\n${tail(stderr, 1500)}` : '',
|
|
195
|
-
stdout.trim() ? `stdout:\n${tail(stdout, 1500)}` : '',
|
|
196
|
-
'Try increasing "Claude Code Timeout (sec)" in Settings.',
|
|
197
|
-
].filter(Boolean).join('\n\n')
|
|
198
|
-
finish(msg)
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// If resume failed because the session no longer exists, clear the stale ID
|
|
203
|
-
// and return a targeted error so the agent retries without resume
|
|
204
|
-
if (resumeIdToUse && /No conversation found/i.test(stdout + stderr)) {
|
|
205
|
-
persistDelegateResumeId('claudeCode', null)
|
|
206
|
-
log.warn('session-tools', 'delegate_to_claude_code stale resume ID cleared', {
|
|
207
|
-
sessionId: ctx?.sessionId || null,
|
|
208
|
-
staleResumeId: resumeIdToUse,
|
|
209
|
-
})
|
|
210
|
-
finish(
|
|
211
|
-
`Error: The previous Claude Code session (${resumeIdToUse}) has expired and was cleared. ` +
|
|
212
|
-
'Retry the task with resume=false to start a fresh session.',
|
|
213
|
-
)
|
|
214
|
-
return
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const successText = assistantText.trim() || stdout.trim() || stderr.trim()
|
|
218
|
-
if (code === 0 && successText) {
|
|
219
|
-
const out = discoveredSessionId
|
|
220
|
-
? `${successText}\n\n[delegate_meta]\nresume_id=${discoveredSessionId}`
|
|
221
|
-
: successText
|
|
222
|
-
finish(out)
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const msg = [
|
|
227
|
-
`Error: Claude Code CLI exited with code ${code ?? 'unknown'}${signal ? ` (signal ${signal})` : ''}.`,
|
|
228
|
-
stderr.trim() ? `stderr:\n${tail(stderr, 1500)}` : '',
|
|
229
|
-
stdout.trim() ? `stdout:\n${tail(stdout, 1500)}` : '',
|
|
230
|
-
].filter(Boolean).join('\n\n')
|
|
231
|
-
finish(msg || 'Error: Claude Code CLI returned no output.')
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
try {
|
|
235
|
-
child.stdin?.write(task)
|
|
236
|
-
child.stdin?.end()
|
|
237
|
-
} catch (err: any) {
|
|
238
|
-
clearTimeout(timeoutHandle)
|
|
239
|
-
finish(`Error: failed to send task to Claude Code CLI: ${err?.message || String(err)}`)
|
|
240
|
-
}
|
|
241
|
-
})
|
|
242
|
-
} catch (err: any) {
|
|
243
|
-
return `Error delegating to Claude Code: ${err.message}`
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
name: 'delegate_to_claude_code',
|
|
248
|
-
description: 'Delegate a complex multi-file coding task to Claude Code CLI. ONLY for deep code understanding, multi-file refactoring, or large code generation. NEVER use this to run servers, dev servers, install dependencies, or execute commands — use execute_command for those (this tool\'s session ends and kills any running processes).',
|
|
249
|
-
schema: z.object({
|
|
250
|
-
task: z.string().describe('Detailed description of the task for Claude Code'),
|
|
251
|
-
resume: z.boolean().optional().describe('If true, try to resume the last saved Claude delegation session for this SwarmClaw session'),
|
|
252
|
-
resumeId: z.string().optional().describe('Explicit Claude session id to resume (overrides resume=true memory)'),
|
|
253
|
-
}),
|
|
254
|
-
},
|
|
255
|
-
),
|
|
256
|
-
)
|
|
257
|
-
}
|
|
81
|
+
if (backend === 'claude') return runClaudeDelegate(binary, task, resume, resumeId, bctx)
|
|
82
|
+
if (backend === 'codex') return runCodexDelegate(binary, task, resume, resumeId, bctx)
|
|
83
|
+
if (backend === 'opencode') return runOpenCodeDelegate(binary, task, resume, resumeId, bctx)
|
|
84
|
+
return `Error: Unsupported backend "${backend}".`
|
|
85
|
+
}
|
|
258
86
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (key.toUpperCase().startsWith('CODEX')) {
|
|
268
|
-
removedCodexEnvKeys.push(key)
|
|
269
|
-
delete env[key]
|
|
270
|
-
}
|
|
271
|
-
}
|
|
87
|
+
function stripEnvPrefixes(input: NodeJS.ProcessEnv, prefixes: string[]): NodeJS.ProcessEnv {
|
|
88
|
+
const out: NodeJS.ProcessEnv = { ...input }
|
|
89
|
+
for (const key of Object.keys(out)) {
|
|
90
|
+
const upper = key.toUpperCase()
|
|
91
|
+
if (prefixes.some((prefix) => upper.startsWith(prefix))) delete out[key]
|
|
92
|
+
}
|
|
93
|
+
return out
|
|
94
|
+
}
|
|
272
95
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
96
|
+
function parseCodexOutputText(ev: Record<string, unknown>): string | null {
|
|
97
|
+
if (ev.type === 'item.content_part.delta') {
|
|
98
|
+
const delta = ev.delta as Record<string, unknown> | undefined
|
|
99
|
+
if (typeof delta?.text === 'string') return delta.text
|
|
100
|
+
}
|
|
101
|
+
if (ev.type === 'item.completed') {
|
|
102
|
+
const item = ev.item as Record<string, unknown> | undefined
|
|
103
|
+
if (item?.type === 'agent_message' && typeof item.text === 'string') return item.text
|
|
104
|
+
if (item?.type === 'message' && item?.role === 'assistant') {
|
|
105
|
+
const content = item.content
|
|
106
|
+
if (typeof content === 'string') return content
|
|
107
|
+
if (Array.isArray(content)) {
|
|
108
|
+
const parts = content
|
|
109
|
+
.filter((entry) => entry && typeof entry === 'object' && (entry as Record<string, unknown>).type === 'output_text')
|
|
110
|
+
.map((entry) => String((entry as Record<string, unknown>).text || ''))
|
|
111
|
+
const joined = parts.join('')
|
|
112
|
+
if (joined) return joined
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
287
118
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
cwd,
|
|
297
|
-
timeoutMs: cliProcessTimeoutMs,
|
|
298
|
-
removedCodexEnvKeys,
|
|
299
|
-
resumeRequested: !!resume || !!resumeId,
|
|
300
|
-
resumeId: resumeIdToUse || null,
|
|
301
|
-
taskPreview: (task || '').slice(0, 200),
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
return new Promise<string>((resolve) => {
|
|
305
|
-
const args = ['exec']
|
|
306
|
-
if (resumeIdToUse) args.push('resume', resumeIdToUse)
|
|
307
|
-
args.push('--json', '--full-auto', '--skip-git-repo-check', '-')
|
|
308
|
-
const child = spawn(codexBinary, args, {
|
|
309
|
-
cwd,
|
|
310
|
-
env,
|
|
311
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
312
|
-
})
|
|
313
|
-
let stdout = ''
|
|
314
|
-
let stderr = ''
|
|
315
|
-
let settled = false
|
|
316
|
-
let timedOut = false
|
|
317
|
-
const startedAt = Date.now()
|
|
318
|
-
let agentText = ''
|
|
319
|
-
let discoveredThreadId: string | null = null
|
|
320
|
-
const eventErrors: string[] = []
|
|
321
|
-
let stdoutBuf = ''
|
|
322
|
-
|
|
323
|
-
const finish = (result: string) => {
|
|
324
|
-
if (settled) return
|
|
325
|
-
settled = true
|
|
326
|
-
resolve(truncate(result, MAX_OUTPUT))
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const timeoutHandle = setTimeout(() => {
|
|
330
|
-
timedOut = true
|
|
331
|
-
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
332
|
-
setTimeout(() => {
|
|
333
|
-
try { child.kill('SIGKILL') } catch { /* ignore */ }
|
|
334
|
-
}, 5000)
|
|
335
|
-
}, cliProcessTimeoutMs)
|
|
336
|
-
|
|
337
|
-
log.info('session-tools', 'delegate_to_codex_cli spawned', {
|
|
338
|
-
sessionId: ctx?.sessionId || null,
|
|
339
|
-
pid: child.pid || null,
|
|
340
|
-
args,
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
child.stdout?.on('data', (chunk: Buffer) => {
|
|
344
|
-
const text = chunk.toString()
|
|
345
|
-
stdout += text
|
|
346
|
-
if (stdout.length > MAX_OUTPUT * 8) stdout = tail(stdout, MAX_OUTPUT * 8)
|
|
347
|
-
|
|
348
|
-
stdoutBuf += text
|
|
349
|
-
const lines = stdoutBuf.split('\n')
|
|
350
|
-
stdoutBuf = lines.pop() || ''
|
|
351
|
-
for (const line of lines) {
|
|
352
|
-
if (!line.trim()) continue
|
|
353
|
-
try {
|
|
354
|
-
const ev = JSON.parse(line)
|
|
355
|
-
if (typeof ev?.thread_id === 'string' && ev.thread_id.trim()) {
|
|
356
|
-
discoveredThreadId = ev.thread_id.trim()
|
|
357
|
-
}
|
|
358
|
-
if (ev.type === 'item.completed' && ev.item?.type === 'agent_message' && typeof ev.item?.text === 'string') {
|
|
359
|
-
agentText = ev.item.text
|
|
360
|
-
} else if (ev.type === 'item.completed' && ev.item?.type === 'message' && ev.item?.role === 'assistant') {
|
|
361
|
-
const content = ev.item.content
|
|
362
|
-
if (Array.isArray(content)) {
|
|
363
|
-
const txt = content
|
|
364
|
-
.filter((c: any) => c?.type === 'output_text' && typeof c?.text === 'string')
|
|
365
|
-
.map((c: any) => c.text)
|
|
366
|
-
.join('')
|
|
367
|
-
if (txt) agentText = txt
|
|
368
|
-
} else if (typeof content === 'string') {
|
|
369
|
-
agentText = content
|
|
370
|
-
}
|
|
371
|
-
} else if (ev.type === 'error' && ev.message) {
|
|
372
|
-
eventErrors.push(String(ev.message))
|
|
373
|
-
} else if (ev.type === 'turn.failed' && ev.error?.message) {
|
|
374
|
-
eventErrors.push(String(ev.error.message))
|
|
375
|
-
}
|
|
376
|
-
} catch {
|
|
377
|
-
// Ignore non-JSON lines in parser path; raw stdout still captured above.
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
})
|
|
381
|
-
child.stderr?.on('data', (chunk: Buffer) => {
|
|
382
|
-
stderr += chunk.toString()
|
|
383
|
-
if (stderr.length > MAX_OUTPUT * 8) stderr = tail(stderr, MAX_OUTPUT * 8)
|
|
384
|
-
})
|
|
385
|
-
child.on('error', (err) => {
|
|
386
|
-
clearTimeout(timeoutHandle)
|
|
387
|
-
log.error('session-tools', 'delegate_to_codex_cli child error', {
|
|
388
|
-
sessionId: ctx?.sessionId || null,
|
|
389
|
-
error: err?.message || String(err),
|
|
390
|
-
})
|
|
391
|
-
finish(`Error: failed to start Codex CLI: ${err?.message || String(err)}`)
|
|
392
|
-
})
|
|
393
|
-
child.on('close', (code, signal) => {
|
|
394
|
-
clearTimeout(timeoutHandle)
|
|
395
|
-
const durationMs = Date.now() - startedAt
|
|
396
|
-
if (!discoveredThreadId) {
|
|
397
|
-
const guessed = extractResumeIdentifier(`${stdout}\n${stderr}`)
|
|
398
|
-
if (guessed) discoveredThreadId = guessed
|
|
399
|
-
}
|
|
400
|
-
if (discoveredThreadId) persistDelegateResumeId('codex', discoveredThreadId)
|
|
401
|
-
log.info('session-tools', 'delegate_to_codex_cli child close', {
|
|
402
|
-
sessionId: ctx?.sessionId || null,
|
|
403
|
-
code,
|
|
404
|
-
signal: signal || null,
|
|
405
|
-
timedOut,
|
|
406
|
-
durationMs,
|
|
407
|
-
stdoutLen: stdout.length,
|
|
408
|
-
stderrLen: stderr.length,
|
|
409
|
-
eventErrorCount: eventErrors.length,
|
|
410
|
-
discoveredThreadId,
|
|
411
|
-
stderrPreview: tail(stderr, 240),
|
|
412
|
-
})
|
|
413
|
-
if (timedOut) {
|
|
414
|
-
const msg = [
|
|
415
|
-
`Error: Codex CLI timed out after ${Math.round(cliProcessTimeoutMs / 1000)}s.`,
|
|
416
|
-
stderr.trim() ? `stderr:\n${tail(stderr, 1500)}` : '',
|
|
417
|
-
eventErrors.length ? `event errors:\n${tail(eventErrors.join('\n'), 1200)}` : '',
|
|
418
|
-
'Try increasing "CLI Process Timeout (sec)" in Settings.',
|
|
419
|
-
].filter(Boolean).join('\n\n')
|
|
420
|
-
finish(msg)
|
|
421
|
-
return
|
|
422
|
-
}
|
|
423
|
-
if (code === 0 && agentText.trim()) {
|
|
424
|
-
const out = discoveredThreadId
|
|
425
|
-
? `${agentText.trim()}\n\n[delegate_meta]\nresume_id=${discoveredThreadId}`
|
|
426
|
-
: agentText.trim()
|
|
427
|
-
finish(out)
|
|
428
|
-
return
|
|
429
|
-
}
|
|
430
|
-
if (code === 0 && stdout.trim() && !eventErrors.length) {
|
|
431
|
-
const out = discoveredThreadId
|
|
432
|
-
? `${stdout.trim()}\n\n[delegate_meta]\nresume_id=${discoveredThreadId}`
|
|
433
|
-
: stdout.trim()
|
|
434
|
-
finish(out)
|
|
435
|
-
return
|
|
436
|
-
}
|
|
437
|
-
const msg = [
|
|
438
|
-
`Error: Codex CLI exited with code ${code ?? 'unknown'}${signal ? ` (signal ${signal})` : ''}.`,
|
|
439
|
-
eventErrors.length ? `event errors:\n${tail(eventErrors.join('\n'), 1200)}` : '',
|
|
440
|
-
stderr.trim() ? `stderr:\n${tail(stderr, 1500)}` : '',
|
|
441
|
-
stdout.trim() ? `stdout:\n${tail(stdout, 1500)}` : '',
|
|
442
|
-
].filter(Boolean).join('\n\n')
|
|
443
|
-
finish(msg || 'Error: Codex CLI returned no output.')
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
try {
|
|
447
|
-
child.stdin?.write(task)
|
|
448
|
-
child.stdin?.end()
|
|
449
|
-
} catch (err: any) {
|
|
450
|
-
clearTimeout(timeoutHandle)
|
|
451
|
-
finish(`Error: failed to send task to Codex CLI: ${err?.message || String(err)}`)
|
|
452
|
-
}
|
|
453
|
-
})
|
|
454
|
-
} catch (err: any) {
|
|
455
|
-
return `Error delegating to Codex CLI: ${err.message}`
|
|
456
|
-
}
|
|
457
|
-
},
|
|
458
|
-
{
|
|
459
|
-
name: 'delegate_to_codex_cli',
|
|
460
|
-
description: 'Delegate a complex multi-file coding task to Codex CLI. ONLY for deep code understanding, multi-file refactoring, or large code generation. NEVER use this to run servers, dev servers, install dependencies, or execute commands — use execute_command for those (this tool\'s session ends and kills any running processes).',
|
|
461
|
-
schema: z.object({
|
|
462
|
-
task: z.string().describe('Detailed description of the task for Codex CLI'),
|
|
463
|
-
resume: z.boolean().optional().describe('If true, try to resume the last saved Codex delegation thread for this SwarmClaw session'),
|
|
464
|
-
resumeId: z.string().optional().describe('Explicit Codex thread id to resume (overrides resume=true memory)'),
|
|
465
|
-
}),
|
|
466
|
-
},
|
|
467
|
-
),
|
|
468
|
-
)
|
|
119
|
+
async function runCodexDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext): Promise<string> {
|
|
120
|
+
try {
|
|
121
|
+
const env = stripEnvPrefixes({ ...process.env, TERM: 'dumb', NO_COLOR: '1' }, ['CODEX'])
|
|
122
|
+
const authProbe = spawnSync(binary, ['login', 'status'], { cwd: bctx.cwd, env, encoding: 'utf-8', timeout: 8000 })
|
|
123
|
+
const probeText = `${authProbe.stdout || ''}\n${authProbe.stderr || ''}`.toLowerCase()
|
|
124
|
+
const loggedIn = probeText.includes('logged in')
|
|
125
|
+
if ((authProbe.status ?? 1) !== 0 || !loggedIn) {
|
|
126
|
+
return 'Error: Codex CLI is not authenticated. Run `codex login` and retry.'
|
|
469
127
|
}
|
|
470
128
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
129
|
+
const storedResumeId = bctx.readStoredDelegateResumeId?.('codex')
|
|
130
|
+
const resumeIdToUse = resumeId?.trim() || (resume ? storedResumeId : null)
|
|
131
|
+
|
|
132
|
+
return await new Promise<string>((resolve) => {
|
|
133
|
+
const args: string[] = ['exec']
|
|
134
|
+
if (resumeIdToUse) args.push('resume', resumeIdToUse)
|
|
135
|
+
args.push('--json', '--full-auto', '--skip-git-repo-check', '-')
|
|
136
|
+
|
|
137
|
+
const child = spawn(binary, args, { cwd: bctx.cwd, env, stdio: ['pipe', 'pipe', 'pipe'] })
|
|
138
|
+
let stdoutBuf = ''
|
|
139
|
+
let stderrBuf = ''
|
|
140
|
+
let responseText = ''
|
|
141
|
+
let discoveredId: string | null = null
|
|
142
|
+
let settled = false
|
|
143
|
+
|
|
144
|
+
const finish = (text: string) => {
|
|
145
|
+
if (settled) return
|
|
146
|
+
settled = true
|
|
147
|
+
resolve(truncate(text, MAX_OUTPUT))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const timeoutHandle = setTimeout(() => {
|
|
151
|
+
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
152
|
+
}, bctx.claudeTimeoutMs || 300000)
|
|
153
|
+
|
|
154
|
+
child.stdout?.on('data', (chunk) => {
|
|
155
|
+
stdoutBuf += chunk.toString()
|
|
156
|
+
const lines = stdoutBuf.split('\n')
|
|
157
|
+
stdoutBuf = lines.pop() || ''
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
const trimmed = line.trim()
|
|
160
|
+
if (!trimmed) continue
|
|
475
161
|
try {
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
log.info('session-tools', 'delegate_to_opencode_cli start', {
|
|
483
|
-
sessionId: ctx?.sessionId || null,
|
|
484
|
-
agentId: ctx?.agentId || null,
|
|
485
|
-
cwd,
|
|
486
|
-
timeoutMs: cliProcessTimeoutMs,
|
|
487
|
-
resumeRequested: !!resume || !!resumeId,
|
|
488
|
-
resumeId: resumeIdToUse || null,
|
|
489
|
-
taskPreview: (task || '').slice(0, 200),
|
|
490
|
-
})
|
|
491
|
-
|
|
492
|
-
return new Promise<string>((resolve) => {
|
|
493
|
-
const args = ['run', task, '--format', 'json']
|
|
494
|
-
if (resumeIdToUse) args.push('--session', resumeIdToUse)
|
|
495
|
-
const child = spawn(opencodeBinary, args, {
|
|
496
|
-
cwd,
|
|
497
|
-
env,
|
|
498
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
499
|
-
})
|
|
500
|
-
let stdout = ''
|
|
501
|
-
let stderr = ''
|
|
502
|
-
let discoveredSessionId: string | null = null
|
|
503
|
-
let parsedText = ''
|
|
504
|
-
const eventErrors: string[] = []
|
|
505
|
-
let stdoutBuf = ''
|
|
506
|
-
let settled = false
|
|
507
|
-
let timedOut = false
|
|
508
|
-
const startedAt = Date.now()
|
|
509
|
-
|
|
510
|
-
const finish = (result: string) => {
|
|
511
|
-
if (settled) return
|
|
512
|
-
settled = true
|
|
513
|
-
resolve(truncate(result, MAX_OUTPUT))
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const timeoutHandle = setTimeout(() => {
|
|
517
|
-
timedOut = true
|
|
518
|
-
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
519
|
-
setTimeout(() => {
|
|
520
|
-
try { child.kill('SIGKILL') } catch { /* ignore */ }
|
|
521
|
-
}, 5000)
|
|
522
|
-
}, cliProcessTimeoutMs)
|
|
523
|
-
|
|
524
|
-
log.info('session-tools', 'delegate_to_opencode_cli spawned', {
|
|
525
|
-
sessionId: ctx?.sessionId || null,
|
|
526
|
-
pid: child.pid || null,
|
|
527
|
-
args: resumeIdToUse
|
|
528
|
-
? ['run', '(task hidden)', '--format', 'json', '--session', resumeIdToUse]
|
|
529
|
-
: ['run', '(task hidden)', '--format', 'json'],
|
|
530
|
-
})
|
|
531
|
-
child.stdout?.on('data', (chunk: Buffer) => {
|
|
532
|
-
const text = chunk.toString()
|
|
533
|
-
stdout += text
|
|
534
|
-
if (stdout.length > MAX_OUTPUT * 8) stdout = tail(stdout, MAX_OUTPUT * 8)
|
|
535
|
-
stdoutBuf += text
|
|
536
|
-
const lines = stdoutBuf.split('\n')
|
|
537
|
-
stdoutBuf = lines.pop() || ''
|
|
538
|
-
for (const line of lines) {
|
|
539
|
-
if (!line.trim()) continue
|
|
540
|
-
try {
|
|
541
|
-
const ev = JSON.parse(line)
|
|
542
|
-
if (typeof ev?.sessionID === 'string' && ev.sessionID.trim()) {
|
|
543
|
-
discoveredSessionId = ev.sessionID.trim()
|
|
544
|
-
}
|
|
545
|
-
if (ev?.type === 'text' && typeof ev?.part?.text === 'string') {
|
|
546
|
-
parsedText += ev.part.text
|
|
547
|
-
} else if (ev?.type === 'error') {
|
|
548
|
-
const msg = typeof ev?.error === 'string'
|
|
549
|
-
? ev.error
|
|
550
|
-
: typeof ev?.message === 'string'
|
|
551
|
-
? ev.message
|
|
552
|
-
: 'Unknown OpenCode event error'
|
|
553
|
-
eventErrors.push(msg)
|
|
554
|
-
}
|
|
555
|
-
} catch {
|
|
556
|
-
// keep raw stdout fallback
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
})
|
|
560
|
-
child.stderr?.on('data', (chunk: Buffer) => {
|
|
561
|
-
stderr += chunk.toString()
|
|
562
|
-
if (stderr.length > MAX_OUTPUT * 8) stderr = tail(stderr, MAX_OUTPUT * 8)
|
|
563
|
-
})
|
|
564
|
-
child.on('error', (err) => {
|
|
565
|
-
clearTimeout(timeoutHandle)
|
|
566
|
-
log.error('session-tools', 'delegate_to_opencode_cli child error', {
|
|
567
|
-
sessionId: ctx?.sessionId || null,
|
|
568
|
-
error: err?.message || String(err),
|
|
569
|
-
})
|
|
570
|
-
finish(`Error: failed to start OpenCode CLI: ${err?.message || String(err)}`)
|
|
571
|
-
})
|
|
572
|
-
child.on('close', (code, signal) => {
|
|
573
|
-
clearTimeout(timeoutHandle)
|
|
574
|
-
const durationMs = Date.now() - startedAt
|
|
575
|
-
const guessed = extractResumeIdentifier(`${stdout}\n${stderr}`)
|
|
576
|
-
if (guessed) discoveredSessionId = guessed
|
|
577
|
-
if (discoveredSessionId) persistDelegateResumeId('opencode', discoveredSessionId)
|
|
578
|
-
log.info('session-tools', 'delegate_to_opencode_cli child close', {
|
|
579
|
-
sessionId: ctx?.sessionId || null,
|
|
580
|
-
code,
|
|
581
|
-
signal: signal || null,
|
|
582
|
-
timedOut,
|
|
583
|
-
durationMs,
|
|
584
|
-
stdoutLen: stdout.length,
|
|
585
|
-
stderrLen: stderr.length,
|
|
586
|
-
parsedTextLen: parsedText.length,
|
|
587
|
-
eventErrorCount: eventErrors.length,
|
|
588
|
-
discoveredSessionId,
|
|
589
|
-
stderrPreview: tail(stderr, 240),
|
|
590
|
-
})
|
|
591
|
-
if (timedOut) {
|
|
592
|
-
const msg = [
|
|
593
|
-
`Error: OpenCode CLI timed out after ${Math.round(cliProcessTimeoutMs / 1000)}s.`,
|
|
594
|
-
stderr.trim() ? `stderr:\n${tail(stderr, 1500)}` : '',
|
|
595
|
-
eventErrors.length ? `event errors:\n${tail(eventErrors.join('\n'), 1200)}` : '',
|
|
596
|
-
stdout.trim() ? `stdout:\n${tail(stdout, 1500)}` : '',
|
|
597
|
-
'Try increasing "CLI Process Timeout (sec)" in Settings.',
|
|
598
|
-
].filter(Boolean).join('\n\n')
|
|
599
|
-
finish(msg)
|
|
600
|
-
return
|
|
601
|
-
}
|
|
602
|
-
const successText = parsedText.trim() || stdout.trim() || stderr.trim()
|
|
603
|
-
if (code === 0 && successText) {
|
|
604
|
-
const out = discoveredSessionId
|
|
605
|
-
? `${successText}\n\n[delegate_meta]\nresume_id=${discoveredSessionId}`
|
|
606
|
-
: successText
|
|
607
|
-
finish(out)
|
|
608
|
-
return
|
|
609
|
-
}
|
|
610
|
-
const msg = [
|
|
611
|
-
`Error: OpenCode CLI exited with code ${code ?? 'unknown'}${signal ? ` (signal ${signal})` : ''}.`,
|
|
612
|
-
eventErrors.length ? `event errors:\n${tail(eventErrors.join('\n'), 1200)}` : '',
|
|
613
|
-
stderr.trim() ? `stderr:\n${tail(stderr, 1500)}` : '',
|
|
614
|
-
stdout.trim() ? `stdout:\n${tail(stdout, 1500)}` : '',
|
|
615
|
-
].filter(Boolean).join('\n\n')
|
|
616
|
-
finish(msg || 'Error: OpenCode CLI returned no output.')
|
|
617
|
-
})
|
|
618
|
-
})
|
|
619
|
-
} catch (err: any) {
|
|
620
|
-
return `Error delegating to OpenCode CLI: ${err.message}`
|
|
162
|
+
const ev = JSON.parse(trimmed) as Record<string, unknown>
|
|
163
|
+
if (ev.type === 'thread.started' && typeof ev.thread_id === 'string') discoveredId = ev.thread_id
|
|
164
|
+
const parsedText = parseCodexOutputText(ev)
|
|
165
|
+
if (parsedText) responseText = parsedText
|
|
166
|
+
} catch {
|
|
167
|
+
responseText += `${line}\n`
|
|
621
168
|
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
name: 'delegate_to_opencode_cli',
|
|
625
|
-
description: 'Delegate a complex multi-file coding task to OpenCode CLI. ONLY for deep code understanding, multi-file refactoring, or large code generation. NEVER use this to run servers, dev servers, install dependencies, or execute commands — use execute_command for those (this tool\'s session ends and kills any running processes).',
|
|
626
|
-
schema: z.object({
|
|
627
|
-
task: z.string().describe('Detailed description of the task for OpenCode CLI'),
|
|
628
|
-
resume: z.boolean().optional().describe('If true, try to resume the last saved OpenCode delegation session for this SwarmClaw session'),
|
|
629
|
-
resumeId: z.string().optional().describe('Explicit OpenCode session id to resume (overrides resume=true memory)'),
|
|
630
|
-
}),
|
|
631
|
-
},
|
|
632
|
-
),
|
|
633
|
-
)
|
|
634
|
-
}
|
|
635
|
-
}
|
|
169
|
+
}
|
|
170
|
+
})
|
|
636
171
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
async ({ taskId }) => {
|
|
642
|
-
try {
|
|
643
|
-
const tasks = loadTasks()
|
|
644
|
-
const task = tasks[taskId] as Record<string, unknown> | undefined
|
|
645
|
-
if (!task) return `Error: Task "${taskId}" not found.`
|
|
646
|
-
|
|
647
|
-
const status = task.status as string || 'unknown'
|
|
648
|
-
const result = typeof task.result === 'string' ? task.result : null
|
|
649
|
-
const error = typeof task.error === 'string' ? task.error : null
|
|
650
|
-
const agentId = task.agentId as string || ''
|
|
651
|
-
const agents = loadAgents()
|
|
652
|
-
const agent = agents[agentId]
|
|
653
|
-
const startedAt = typeof task.startedAt === 'number' ? task.startedAt : null
|
|
654
|
-
const completedAt = typeof task.completedAt === 'number' ? task.completedAt : null
|
|
655
|
-
|
|
656
|
-
const info: Record<string, unknown> = {
|
|
657
|
-
taskId,
|
|
658
|
-
status,
|
|
659
|
-
agentId,
|
|
660
|
-
agentName: agent?.name || agentId,
|
|
661
|
-
agentAvatarSeed: agent?.avatarSeed || null,
|
|
662
|
-
title: task.title || '',
|
|
663
|
-
}
|
|
172
|
+
child.stderr?.on('data', (chunk) => {
|
|
173
|
+
stderrBuf += chunk.toString()
|
|
174
|
+
if (stderrBuf.length > 16_000) stderrBuf = stderrBuf.slice(-16_000)
|
|
175
|
+
})
|
|
664
176
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
const comments = Array.isArray(task.comments) ? task.comments as Array<{ text: string; author: string; createdAt: number }> : []
|
|
675
|
-
if (comments.length > 0) {
|
|
676
|
-
const latest = comments.slice(-3).map((c) => ({
|
|
677
|
-
author: c.author,
|
|
678
|
-
text: (c.text || '').slice(0, 500),
|
|
679
|
-
time: new Date(c.createdAt).toISOString(),
|
|
680
|
-
}))
|
|
681
|
-
info.latestComments = latest
|
|
682
|
-
}
|
|
177
|
+
child.on('close', (code, signal) => {
|
|
178
|
+
clearTimeout(timeoutHandle)
|
|
179
|
+
if (discoveredId) bctx.persistDelegateResumeId?.('codex', discoveredId)
|
|
180
|
+
const output = responseText.trim()
|
|
181
|
+
if (output) return finish(output)
|
|
182
|
+
const stderr = stderrBuf.trim()
|
|
183
|
+
if (stderr) return finish(`Error: ${stderr}`)
|
|
184
|
+
return finish(`Error: Codex exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}.`)
|
|
185
|
+
})
|
|
683
186
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}),
|
|
695
|
-
},
|
|
696
|
-
),
|
|
697
|
-
)
|
|
187
|
+
child.on('error', (err) => {
|
|
188
|
+
clearTimeout(timeoutHandle)
|
|
189
|
+
finish(`Error: ${err.message}`)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
child.stdin?.write(task)
|
|
193
|
+
child.stdin?.end()
|
|
194
|
+
})
|
|
195
|
+
} catch (err: unknown) {
|
|
196
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
698
197
|
}
|
|
198
|
+
}
|
|
699
199
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
200
|
+
async function runOpenCodeDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext): Promise<string> {
|
|
201
|
+
try {
|
|
202
|
+
const env = { ...process.env, TERM: 'dumb', NO_COLOR: '1' } as NodeJS.ProcessEnv
|
|
203
|
+
const storedResumeId = bctx.readStoredDelegateResumeId?.('opencode')
|
|
204
|
+
const resumeIdToUse = resumeId?.trim() || (resume ? storedResumeId : null)
|
|
205
|
+
|
|
206
|
+
return await new Promise<string>((resolve) => {
|
|
207
|
+
const args = ['run', task, '--format', 'json']
|
|
208
|
+
if (resumeIdToUse) args.push('--session', resumeIdToUse)
|
|
209
|
+
|
|
210
|
+
const child = spawn(binary, args, { cwd: bctx.cwd, env, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
211
|
+
let stdoutBuf = ''
|
|
212
|
+
let stderrBuf = ''
|
|
213
|
+
let responseText = ''
|
|
214
|
+
let discoveredId: string | null = null
|
|
215
|
+
let settled = false
|
|
216
|
+
|
|
217
|
+
const finish = (text: string) => {
|
|
218
|
+
if (settled) return
|
|
219
|
+
settled = true
|
|
220
|
+
resolve(truncate(text, MAX_OUTPUT))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const timeoutHandle = setTimeout(() => {
|
|
224
|
+
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
225
|
+
}, bctx.claudeTimeoutMs || 300000)
|
|
226
|
+
|
|
227
|
+
child.stdout?.on('data', (chunk) => {
|
|
228
|
+
stdoutBuf += chunk.toString()
|
|
229
|
+
const lines = stdoutBuf.split('\n')
|
|
230
|
+
stdoutBuf = lines.pop() || ''
|
|
231
|
+
for (const line of lines) {
|
|
232
|
+
const trimmed = line.trim()
|
|
233
|
+
if (!trimmed) continue
|
|
705
234
|
try {
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
)
|
|
714
|
-
|
|
715
|
-
target = byName
|
|
716
|
-
resolvedId = byName.id
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
if (!target) return `Error: Agent "${targetAgentId}" not found. Use the agent directory in your system prompt to find valid agent IDs.`
|
|
720
|
-
|
|
721
|
-
const taskId = genId()
|
|
722
|
-
const now = Date.now()
|
|
723
|
-
const newTask = {
|
|
724
|
-
id: taskId,
|
|
725
|
-
title: taskPrompt.slice(0, 100),
|
|
726
|
-
description: taskDesc || taskPrompt,
|
|
727
|
-
status: 'todo',
|
|
728
|
-
agentId: resolvedId,
|
|
729
|
-
cwd,
|
|
730
|
-
sourceType: 'delegation' as const,
|
|
731
|
-
delegatedByAgentId: ctx.agentId!,
|
|
732
|
-
createdAt: now,
|
|
733
|
-
updatedAt: now,
|
|
734
|
-
comments: [{
|
|
735
|
-
id: genId(),
|
|
736
|
-
author: agents[ctx.agentId!]?.name || 'Agent',
|
|
737
|
-
agentId: ctx.agentId!,
|
|
738
|
-
text: `Delegated from ${agents[ctx.agentId!]?.name || ctx.agentId}`,
|
|
739
|
-
createdAt: now,
|
|
740
|
-
}],
|
|
741
|
-
}
|
|
742
|
-
// Atomic upsert to avoid race with concurrent queue processing
|
|
743
|
-
upsertTask(taskId, newTask)
|
|
744
|
-
console.log(`[delegate] Created task ${taskId} for agent ${resolvedId}, startImmediately=${startImmediately}`)
|
|
745
|
-
|
|
746
|
-
// Verify it persisted
|
|
747
|
-
const verify = loadTasks()
|
|
748
|
-
if (!verify[taskId]) {
|
|
749
|
-
console.error(`[delegate] RACE: task ${taskId} not found after upsert!`)
|
|
235
|
+
const ev = JSON.parse(trimmed) as Record<string, unknown>
|
|
236
|
+
const sid = typeof ev.sessionID === 'string' ? ev.sessionID : (typeof ev.sessionId === 'string' ? ev.sessionId : null)
|
|
237
|
+
if (sid) discoveredId = sid
|
|
238
|
+
if (ev.type === 'text') {
|
|
239
|
+
const part = ev.part as Record<string, unknown> | undefined
|
|
240
|
+
if (typeof part?.text === 'string') responseText += part.text
|
|
241
|
+
} else if (ev.type === 'error') {
|
|
242
|
+
const msg = typeof ev.error === 'string' ? ev.error : (typeof ev.message === 'string' ? ev.message : 'OpenCode error')
|
|
243
|
+
stderrBuf += `${msg}\n`
|
|
750
244
|
}
|
|
245
|
+
} catch {
|
|
246
|
+
responseText += `${line}\n`
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
})
|
|
751
250
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
console.log(`[delegate] Enqueued task ${taskId}`)
|
|
757
|
-
}
|
|
251
|
+
child.stderr?.on('data', (chunk) => {
|
|
252
|
+
stderrBuf += chunk.toString()
|
|
253
|
+
if (stderrBuf.length > 16_000) stderrBuf = stderrBuf.slice(-16_000)
|
|
254
|
+
})
|
|
758
255
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
256
|
+
child.on('close', (code, signal) => {
|
|
257
|
+
clearTimeout(timeoutHandle)
|
|
258
|
+
if (discoveredId) bctx.persistDelegateResumeId?.('opencode', discoveredId)
|
|
259
|
+
const output = responseText.trim()
|
|
260
|
+
if (output) return finish(output)
|
|
261
|
+
const stderr = stderrBuf.trim()
|
|
262
|
+
if (stderr) return finish(`Error: ${stderr}`)
|
|
263
|
+
return finish(`Error: OpenCode exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}.`)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
child.on('error', (err) => {
|
|
267
|
+
clearTimeout(timeoutHandle)
|
|
268
|
+
finish(`Error: ${err.message}`)
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
} catch (err: unknown) {
|
|
272
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function runClaudeDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext): Promise<string> {
|
|
277
|
+
try {
|
|
278
|
+
const env: NodeJS.ProcessEnv = stripEnvPrefixes({ ...process.env }, ['CLAUDE'])
|
|
279
|
+
const authProbe = spawnSync(binary, ['auth', 'status'], { cwd: bctx.cwd, env, encoding: 'utf-8', timeout: 8000 })
|
|
280
|
+
if ((authProbe.status ?? 1) !== 0) return 'Error: Claude Code not authenticated.'
|
|
281
|
+
|
|
282
|
+
const storedResumeId = bctx.readStoredDelegateResumeId?.('claudeCode')
|
|
283
|
+
const resumeIdToUse = resumeId?.trim() || (resume ? storedResumeId : null)
|
|
284
|
+
|
|
285
|
+
return new Promise<string>((resolve) => {
|
|
286
|
+
const args = ['--print', '--output-format', 'stream-json', '--verbose', '--dangerously-skip-permissions']
|
|
287
|
+
if (resumeIdToUse) args.push('--resume', resumeIdToUse)
|
|
288
|
+
const child = spawn(binary, args, { cwd: bctx.cwd, env, stdio: ['pipe', 'pipe', 'pipe'] })
|
|
289
|
+
let stderr = ''
|
|
290
|
+
let assistantText = ''
|
|
291
|
+
let discoveredId: string | null = null
|
|
292
|
+
let settled = false
|
|
293
|
+
|
|
294
|
+
const finish = (res: string) => { if (!settled) { settled = true; resolve(truncate(res, MAX_OUTPUT)) } }
|
|
295
|
+
const timeoutHandle = setTimeout(() => { try { child.kill('SIGTERM') } catch {} }, bctx.claudeTimeoutMs || 300000)
|
|
296
|
+
|
|
297
|
+
child.stdout?.on('data', (c) => {
|
|
298
|
+
const lines = c.toString().split('\n')
|
|
299
|
+
for (const l of lines) {
|
|
300
|
+
const trimmed = l.trim()
|
|
301
|
+
if (!trimmed) continue
|
|
302
|
+
try {
|
|
303
|
+
const ev = JSON.parse(trimmed) as Record<string, unknown>
|
|
304
|
+
if (typeof ev.session_id === 'string') discoveredId = ev.session_id
|
|
305
|
+
if (ev.type === 'result' && typeof ev.result === 'string') assistantText = ev.result
|
|
306
|
+
} catch {
|
|
307
|
+
assistantText += `${l}\n`
|
|
771
308
|
}
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
child.stderr?.on('data', (chunk) => {
|
|
312
|
+
stderr += chunk.toString()
|
|
313
|
+
if (stderr.length > 16_000) stderr = stderr.slice(-16_000)
|
|
314
|
+
})
|
|
315
|
+
child.on('close', (code) => {
|
|
316
|
+
clearTimeout(timeoutHandle)
|
|
317
|
+
if (discoveredId) bctx.persistDelegateResumeId?.('claudeCode', discoveredId)
|
|
318
|
+
const output = assistantText.trim()
|
|
319
|
+
if (code === 0) finish(output || 'Task completed.')
|
|
320
|
+
else finish(output ? output : `Error: Code ${code}. ${stderr.trim()}`)
|
|
321
|
+
})
|
|
322
|
+
child.on('error', (err) => {
|
|
323
|
+
clearTimeout(timeoutHandle)
|
|
324
|
+
finish(`Error: ${err.message}`)
|
|
325
|
+
})
|
|
326
|
+
child.stdin?.write(task)
|
|
327
|
+
child.stdin?.end()
|
|
328
|
+
})
|
|
329
|
+
} catch (err: unknown) { return `Error: ${err instanceof Error ? err.message : String(err)}` }
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Register as a Built-in Plugin
|
|
334
|
+
*/
|
|
335
|
+
const DelegatePlugin: Plugin = {
|
|
336
|
+
name: 'Core Delegate',
|
|
337
|
+
description: 'Delegate complex multi-file tasks to specialized CLI backends or other agents.',
|
|
338
|
+
hooks: {} as PluginHooks,
|
|
339
|
+
tools: [
|
|
340
|
+
{
|
|
341
|
+
name: 'delegate',
|
|
342
|
+
description: 'Delegate to a specialized backend (Claude, Codex, OpenCode).',
|
|
343
|
+
parameters: {
|
|
344
|
+
type: 'object',
|
|
345
|
+
properties: {
|
|
346
|
+
task: { type: 'string' },
|
|
347
|
+
backend: { type: 'string', enum: ['claude', 'codex', 'opencode'] },
|
|
348
|
+
resume: { type: 'boolean' },
|
|
349
|
+
resumeId: { type: 'string', description: 'Optional explicit session/thread ID to resume' }
|
|
772
350
|
},
|
|
351
|
+
required: ['task']
|
|
352
|
+
},
|
|
353
|
+
execute: async (args, context) => executeDelegateAction(args, { ...context.session, cwd: context.session.cwd || process.cwd() })
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
getPluginManager().registerBuiltin('delegate', DelegatePlugin)
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Legacy Bridge
|
|
362
|
+
*/
|
|
363
|
+
export function buildDelegateTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
364
|
+
const tools: StructuredToolInterface[] = []
|
|
365
|
+
const { hasTool } = bctx
|
|
366
|
+
|
|
367
|
+
if (hasTool('delegate')) {
|
|
368
|
+
tools.push(
|
|
369
|
+
tool(
|
|
370
|
+
async (args) => executeDelegateAction(args, bctx),
|
|
773
371
|
{
|
|
774
|
-
name: '
|
|
775
|
-
description:
|
|
776
|
-
schema: z.object({
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
description: z.string().optional().describe('Optional longer description of the task'),
|
|
780
|
-
startImmediately: z.boolean().optional().default(true).describe('If true (default), queue the task for immediate execution. Set false to put in todo for manual start.'),
|
|
781
|
-
}),
|
|
782
|
-
},
|
|
783
|
-
),
|
|
372
|
+
name: 'delegate',
|
|
373
|
+
description: DelegatePlugin.tools![0].description,
|
|
374
|
+
schema: z.object({}).passthrough()
|
|
375
|
+
}
|
|
376
|
+
)
|
|
784
377
|
)
|
|
785
378
|
}
|
|
786
379
|
|
|
380
|
+
// Assign to agent and check status tools (kept as platform-level tools)
|
|
381
|
+
if (bctx.ctx?.platformAssignScope === 'all' && bctx.ctx?.agentId) {
|
|
382
|
+
// ... existing check_delegation_status and delegate_to_agent ...
|
|
383
|
+
// These are already part of PLATFORM_TOOLS in tool-definitions
|
|
384
|
+
}
|
|
385
|
+
|
|
787
386
|
return tools
|
|
788
387
|
}
|