@swarmclawai/swarmclaw 0.3.1 → 0.4.5
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 +33 -13
- package/bin/server-cmd.js +14 -7
- package/bin/swarmclaw.js +3 -1
- package/bin/update-cmd.js +120 -0
- package/next.config.ts +10 -0
- package/package.json +4 -1
- package/src/app/api/agents/[id]/route.ts +20 -18
- package/src/app/api/agents/[id]/thread/route.ts +4 -3
- package/src/app/api/agents/route.ts +8 -3
- package/src/app/api/auth/route.ts +3 -1
- package/src/app/api/claude-skills/route.ts +3 -1
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +14 -3
- package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
- package/src/app/api/connectors/route.ts +12 -4
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +5 -3
- package/src/app/api/daemon/route.ts +6 -1
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/ip/route.ts +3 -1
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +5 -3
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/directory/route.ts +26 -0
- package/src/app/api/openclaw/discover/route.ts +61 -0
- package/src/app/api/openclaw/sync/route.ts +30 -0
- package/src/app/api/orchestrator/graph/route.ts +25 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/plugins/marketplace/route.ts +3 -1
- package/src/app/api/plugins/route.ts +3 -1
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -12
- package/src/app/api/providers/configs/route.ts +3 -1
- package/src/app/api/providers/route.ts +7 -3
- package/src/app/api/schedules/[id]/route.ts +16 -15
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +8 -3
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +5 -3
- package/src/app/api/sessions/[id]/chat/route.ts +5 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/messages/route.ts +2 -1
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +2 -1
- package/src/app/api/sessions/route.ts +11 -4
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/app/api/setup/openclaw-device/route.ts +3 -1
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +5 -3
- package/src/app/api/tasks/[id]/approve/route.ts +74 -0
- package/src/app/api/tasks/[id]/route.ts +9 -5
- package/src/app/api/tasks/route.ts +5 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/usage/route.ts +3 -1
- package/src/app/api/version/route.ts +3 -1
- package/src/app/api/webhooks/[id]/route.ts +31 -32
- package/src/app/api/webhooks/route.ts +5 -3
- package/src/app/icon.svg +58 -0
- package/src/app/page.tsx +11 -26
- package/src/cli/index.js +28 -9
- package/src/cli/index.ts +45 -2
- package/src/cli/spec.js +2 -8
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-list.tsx +3 -1
- package/src/components/agents/agent-sheet.tsx +166 -81
- package/src/components/chat/chat-area.tsx +71 -34
- package/src/components/chat/chat-header.tsx +141 -29
- package/src/components/chat/chat-tool-toggles.tsx +12 -53
- package/src/components/chat/message-bubble.tsx +110 -42
- package/src/components/chat/tool-call-bubble.tsx +50 -6
- package/src/components/chat/tool-request-banner.tsx +1 -9
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +9 -10
- package/src/components/connectors/connector-sheet.tsx +55 -36
- package/src/components/input/chat-input.tsx +72 -56
- package/src/components/knowledge/knowledge-list.tsx +27 -31
- package/src/components/layout/app-layout.tsx +133 -90
- package/src/components/layout/daemon-indicator.tsx +3 -5
- package/src/components/logs/log-list.tsx +5 -9
- package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
- package/src/components/memory/memory-detail.tsx +1 -1
- package/src/components/plugins/plugin-list.tsx +227 -27
- package/src/components/projects/project-list.tsx +122 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/providers/provider-list.tsx +46 -13
- package/src/components/providers/provider-sheet.tsx +0 -45
- package/src/components/runs/run-list.tsx +6 -15
- package/src/components/schedules/schedule-card.tsx +54 -4
- package/src/components/schedules/schedule-list.tsx +9 -4
- package/src/components/schedules/schedule-sheet.tsx +0 -47
- package/src/components/secrets/secrets-list.tsx +20 -2
- package/src/components/sessions/new-session-sheet.tsx +14 -15
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/shared/connector-platform-icon.tsx +26 -20
- package/src/components/shared/model-combobox.tsx +148 -0
- package/src/components/shared/settings/section-heartbeat.tsx +8 -40
- package/src/components/shared/settings/section-orchestrator.tsx +9 -11
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +73 -0
- package/src/components/skills/skill-list.tsx +262 -35
- package/src/components/skills/skill-sheet.tsx +0 -45
- package/src/components/tasks/task-board.tsx +3 -6
- package/src/components/tasks/task-card.tsx +43 -1
- package/src/components/tasks/task-list.tsx +8 -7
- package/src/components/tasks/task-sheet.tsx +0 -44
- package/src/components/usage/usage-list.tsx +12 -4
- package/src/hooks/use-continuous-speech.ts +144 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/hooks/use-ws.ts +66 -0
- package/src/instrumentation.ts +2 -0
- package/src/lib/chat.ts +14 -2
- package/src/lib/id.ts +6 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +15 -2
- package/src/lib/providers/index.ts +8 -0
- package/src/lib/providers/ollama.ts +10 -2
- package/src/lib/providers/openai.ts +42 -13
- package/src/lib/providers/openclaw.ts +11 -0
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +57 -8
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
- package/src/lib/server/connectors/bluebubbles.ts +357 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +46 -7
- package/src/lib/server/connectors/manager.ts +401 -6
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +64 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/context-manager.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -0
- package/src/lib/server/data-dir.ts +1 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +67 -3
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/langgraph-checkpoint.ts +274 -0
- package/src/lib/server/main-agent-loop.ts +67 -8
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-sync.ts +496 -0
- package/src/lib/server/orchestrator-lg.ts +422 -20
- package/src/lib/server/orchestrator.ts +29 -9
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +39 -13
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +8 -3
- package/src/lib/server/session-tools/connector.ts +51 -4
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +5 -5
- package/src/lib/server/session-tools/file.ts +176 -3
- package/src/lib/server/session-tools/index.ts +4 -0
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +197 -0
- package/src/lib/server/session-tools/search-providers.ts +270 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/web.ts +47 -66
- package/src/lib/server/storage-mcp.test.ts +25 -2
- package/src/lib/server/storage.ts +36 -7
- package/src/lib/server/stream-agent-chat.ts +106 -22
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/task-validation.test.ts +23 -0
- package/src/lib/server/task-validation.ts +5 -3
- package/src/lib/server/ws-hub.ts +85 -0
- package/src/lib/tool-definitions.ts +44 -0
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/upload.ts +7 -1
- package/src/lib/view-routes.ts +28 -0
- package/src/lib/ws-client.ts +124 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +28 -1
- package/src/stores/use-chat-store.ts +42 -14
- package/src/types/index.ts +34 -2
- package/src/app/api/agents/generate/route.ts +0 -42
- package/src/app/api/generate/info/route.ts +0 -12
- package/src/app/api/generate/route.ts +0 -106
- package/src/app/favicon.ico +0 -0
- package/src/components/shared/ai-gen-block.tsx +0 -77
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { tool } from '@langchain/core/tools'
|
|
3
|
-
import {
|
|
3
|
+
import { StateGraph, MessagesAnnotation, START, END } from '@langchain/langgraph'
|
|
4
|
+
import { ToolNode } from '@langchain/langgraph/prebuilt'
|
|
5
|
+
import { AIMessage } from '@langchain/core/messages'
|
|
4
6
|
import { loadSessions, saveSessions, loadAgents, loadCredentials, loadSettings, loadSecrets, loadTasks, saveTasks, decryptKey, loadSkills } from './storage'
|
|
7
|
+
import { WORKSPACE_DIR } from './data-dir'
|
|
5
8
|
import { loadRuntimeSettings, getOrchestratorLoopRecursionLimit } from './runtime-settings'
|
|
6
9
|
import { getMemoryDb } from './memory-db'
|
|
7
10
|
import { buildChatModel } from './build-llm'
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
import { getCheckpointSaver } from './langgraph-checkpoint'
|
|
12
|
+
import { notify } from './ws-hub'
|
|
13
|
+
import { pushMainLoopEventToMainSessions } from './main-agent-loop'
|
|
14
|
+
import { genId } from '@/lib/id'
|
|
15
|
+
import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
16
|
+
import type { Agent, TaskComment, MessageToolEvent } from '@/types'
|
|
12
17
|
|
|
13
18
|
function resolveCredential(credentialId: string | null | undefined): string | null {
|
|
14
19
|
if (!credentialId) return null
|
|
@@ -73,11 +78,11 @@ function getSecretsForOrchestrator(orchestratorId: string): { name: string; serv
|
|
|
73
78
|
return result
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
function saveMessage(sessionId: string, role: 'user' | 'assistant', text: string) {
|
|
81
|
+
function saveMessage(sessionId: string, role: 'user' | 'assistant', text: string, toolEvents?: MessageToolEvent[]) {
|
|
77
82
|
const sessions = loadSessions()
|
|
78
83
|
const session = sessions[sessionId]
|
|
79
84
|
if (!session) return
|
|
80
|
-
session.messages.push({ role, text, time: Date.now() })
|
|
85
|
+
session.messages.push({ role, text, time: Date.now(), ...(toolEvents?.length ? { toolEvents } : {}) })
|
|
81
86
|
session.lastActiveAt = Date.now()
|
|
82
87
|
saveSessions(sessions)
|
|
83
88
|
}
|
|
@@ -86,16 +91,15 @@ function saveMessage(sessionId: string, role: 'user' | 'assistant', text: string
|
|
|
86
91
|
async function executeSubTaskViaCli(agent: Agent, task: string, parentSessionId: string): Promise<string> {
|
|
87
92
|
// Dynamic import to avoid circular deps
|
|
88
93
|
const { callProvider } = await import('./orchestrator')
|
|
89
|
-
const crypto = await import('crypto')
|
|
90
94
|
const { loadSessions: ls, saveSessions: ss } = await import('./storage')
|
|
91
95
|
|
|
92
96
|
const sessions = ls()
|
|
93
97
|
const parentSession = sessions[parentSessionId]
|
|
94
|
-
const childId =
|
|
98
|
+
const childId = genId()
|
|
95
99
|
sessions[childId] = {
|
|
96
100
|
id: childId,
|
|
97
101
|
name: `[Agent] ${agent.name}: ${task.slice(0, 40)}`,
|
|
98
|
-
cwd: parentSession?.cwd ||
|
|
102
|
+
cwd: parentSession?.cwd || WORKSPACE_DIR,
|
|
99
103
|
user: 'system',
|
|
100
104
|
provider: agent.provider,
|
|
101
105
|
model: agent.model,
|
|
@@ -136,6 +140,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
136
140
|
orchestrator: Agent,
|
|
137
141
|
task: string,
|
|
138
142
|
sessionId: string,
|
|
143
|
+
taskId?: string,
|
|
139
144
|
): Promise<string> {
|
|
140
145
|
const allAgents = loadAgents()
|
|
141
146
|
|
|
@@ -165,9 +170,12 @@ export async function executeLangGraphOrchestrator(
|
|
|
165
170
|
return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
|
|
166
171
|
}
|
|
167
172
|
console.log(`[orchestrator-lg] Delegating to ${agent.name}: ${agentTask.slice(0, 80)}`)
|
|
168
|
-
saveMessage(sessionId, 'assistant', `[Delegating to ${agent.name}]: ${agentTask}`)
|
|
169
173
|
const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
|
|
170
|
-
saveMessage(sessionId, '
|
|
174
|
+
saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
|
|
175
|
+
name: 'delegate_to_agent',
|
|
176
|
+
input: JSON.stringify({ agentName: agent.name, agentId: agent.id, task: agentTask }),
|
|
177
|
+
output: result.slice(0, 2000),
|
|
178
|
+
}])
|
|
171
179
|
return result
|
|
172
180
|
},
|
|
173
181
|
{
|
|
@@ -263,7 +271,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
263
271
|
if (!t) return `Task "${taskId}" not found.`
|
|
264
272
|
if (!t.comments) t.comments = []
|
|
265
273
|
const c: TaskComment = {
|
|
266
|
-
id:
|
|
274
|
+
id: genId(),
|
|
267
275
|
author: orchestrator.name,
|
|
268
276
|
agentId: orchestrator.id,
|
|
269
277
|
text: comment,
|
|
@@ -288,7 +296,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
288
296
|
const createTaskTool = tool(
|
|
289
297
|
async ({ title, description: desc }) => {
|
|
290
298
|
const tasks = loadTasks()
|
|
291
|
-
const id =
|
|
299
|
+
const id = genId()
|
|
292
300
|
tasks[id] = {
|
|
293
301
|
id,
|
|
294
302
|
title,
|
|
@@ -369,12 +377,102 @@ export async function executeLangGraphOrchestrator(
|
|
|
369
377
|
taskContext,
|
|
370
378
|
].join('\n')
|
|
371
379
|
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
380
|
+
const checkpointSaver = getCheckpointSaver()
|
|
381
|
+
const isStrictMode = settings.capabilityPolicyMode === 'strict'
|
|
382
|
+
const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
|
|
383
|
+
const llmWithTools = llm.bindTools(allTools)
|
|
384
|
+
const toolNode = new ToolNode(allTools)
|
|
385
|
+
|
|
386
|
+
// Track fallback attempts for delegate_to_agent failures
|
|
387
|
+
let fallbackAttempts = 0
|
|
388
|
+
const MAX_FALLBACK_ATTEMPTS = 2
|
|
389
|
+
|
|
390
|
+
// Agent node: calls LLM with tools
|
|
391
|
+
async function agentNode(state: typeof MessagesAnnotation.State) {
|
|
392
|
+
const response = await llmWithTools.invoke([
|
|
393
|
+
{ role: 'system' as const, content: systemMessage },
|
|
394
|
+
...state.messages,
|
|
395
|
+
])
|
|
396
|
+
return { messages: [response] }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Router node: inspects tool results, decides next step
|
|
400
|
+
function routerNode(state: typeof MessagesAnnotation.State) {
|
|
401
|
+
const messages = state.messages
|
|
402
|
+
const lastMsg = messages[messages.length - 1]
|
|
403
|
+
|
|
404
|
+
// Check if the last tool message contains an error from delegate_to_agent
|
|
405
|
+
if (lastMsg && typeof (lastMsg as any).content === 'string') {
|
|
406
|
+
const content = (lastMsg as any).content as string
|
|
407
|
+
const isError = content.startsWith('Error:') || content.startsWith('Agent "') && content.includes('not found')
|
|
408
|
+
|
|
409
|
+
if (isError && fallbackAttempts < MAX_FALLBACK_ATTEMPTS) {
|
|
410
|
+
fallbackAttempts++
|
|
411
|
+
// Look for a delegate tool call in recent messages and try to find alternative agent
|
|
412
|
+
const failedToolCall = [...messages].reverse().find(
|
|
413
|
+
(m) => (m as any).tool_calls?.some((tc: any) => tc.name === 'delegate_to_agent')
|
|
414
|
+
)
|
|
415
|
+
if (failedToolCall) {
|
|
416
|
+
const tc = (failedToolCall as any).tool_calls?.find((tc: any) => tc.name === 'delegate_to_agent')
|
|
417
|
+
const failedAgentName = tc?.args?.agentName || 'unknown'
|
|
418
|
+
const fallbackHint = `The agent "${failedAgentName}" failed. Try delegating to a different agent with matching capabilities, or re-plan your approach. Fallback attempt ${fallbackAttempts}/${MAX_FALLBACK_ATTEMPTS}.`
|
|
419
|
+
return { messages: [new AIMessage({ content: fallbackHint })] }
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// No fallback needed — pass through
|
|
425
|
+
return { messages: [] }
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Routing function: after agent node, check if there are tool calls
|
|
429
|
+
function shouldContinue(state: typeof MessagesAnnotation.State) {
|
|
430
|
+
const lastMsg = state.messages[state.messages.length - 1]
|
|
431
|
+
const toolCalls = (lastMsg as any)?.tool_calls
|
|
432
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
433
|
+
return 'tools'
|
|
434
|
+
}
|
|
435
|
+
return END
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// After router, decide whether to go back to agent or end
|
|
439
|
+
function afterRouter(state: typeof MessagesAnnotation.State) {
|
|
440
|
+
const messages = state.messages
|
|
441
|
+
// If router added a fallback hint, route back to agent
|
|
442
|
+
const lastMsg = messages[messages.length - 1]
|
|
443
|
+
if (lastMsg && typeof (lastMsg as any).content === 'string' && (lastMsg as any).content.includes('Fallback attempt')) {
|
|
444
|
+
return 'agent'
|
|
445
|
+
}
|
|
446
|
+
return 'agent'
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Build the StateGraph
|
|
450
|
+
const graph = new StateGraph(MessagesAnnotation)
|
|
451
|
+
.addNode('agent', agentNode)
|
|
452
|
+
.addNode('tools', toolNode)
|
|
453
|
+
.addNode('router', routerNode)
|
|
454
|
+
.addEdge(START, 'agent')
|
|
455
|
+
.addConditionalEdges('agent', shouldContinue, { tools: 'tools', [END]: END })
|
|
456
|
+
.addEdge('tools', 'router')
|
|
457
|
+
.addConditionalEdges('router', afterRouter, { agent: 'agent' })
|
|
458
|
+
|
|
459
|
+
const compiledGraph = graph.compile({
|
|
460
|
+
checkpointer: checkpointSaver,
|
|
461
|
+
...(isStrictMode ? { interruptBefore: ['tools'] } : {}),
|
|
376
462
|
})
|
|
377
463
|
|
|
464
|
+
// Export graph structure for introspection
|
|
465
|
+
;(compiledGraph as any).__graphStructure = {
|
|
466
|
+
nodes: ['agent', 'tools', 'router'],
|
|
467
|
+
edges: [
|
|
468
|
+
{ from: START, to: 'agent' },
|
|
469
|
+
{ from: 'agent', to: 'tools', condition: 'has_tool_calls' },
|
|
470
|
+
{ from: 'agent', to: END, condition: 'no_tool_calls' },
|
|
471
|
+
{ from: 'tools', to: 'router' },
|
|
472
|
+
{ from: 'router', to: 'agent', condition: 'fallback_or_continue' },
|
|
473
|
+
],
|
|
474
|
+
}
|
|
475
|
+
|
|
378
476
|
// Save initial user message
|
|
379
477
|
saveMessage(sessionId, 'user', task)
|
|
380
478
|
|
|
@@ -391,9 +489,15 @@ export async function executeLangGraphOrchestrator(
|
|
|
391
489
|
: null
|
|
392
490
|
|
|
393
491
|
try {
|
|
394
|
-
const
|
|
492
|
+
const threadId = taskId || sessionId
|
|
493
|
+
const streamConfig = {
|
|
494
|
+
recursionLimit,
|
|
495
|
+
signal: abortController.signal,
|
|
496
|
+
configurable: { thread_id: threadId },
|
|
497
|
+
}
|
|
498
|
+
const stream = await compiledGraph.stream(
|
|
395
499
|
{ messages: [{ role: 'user' as const, content: task }] },
|
|
396
|
-
|
|
500
|
+
streamConfig,
|
|
397
501
|
)
|
|
398
502
|
|
|
399
503
|
for await (const chunk of stream) {
|
|
@@ -414,6 +518,65 @@ export async function executeLangGraphOrchestrator(
|
|
|
414
518
|
}
|
|
415
519
|
}
|
|
416
520
|
}
|
|
521
|
+
|
|
522
|
+
// Check for interrupt (paused before tool execution in strict mode)
|
|
523
|
+
if (isStrictMode && taskId) {
|
|
524
|
+
const state = await compiledGraph.getState({ configurable: { thread_id: threadId } })
|
|
525
|
+
const nextNodes = state?.next || []
|
|
526
|
+
if (nextNodes.includes('tools')) {
|
|
527
|
+
// Graph is paused before tool execution — extract pending tool call
|
|
528
|
+
const messages = state.values?.messages || []
|
|
529
|
+
const lastMsg = messages[messages.length - 1]
|
|
530
|
+
const toolCalls = lastMsg?.tool_calls || lastMsg?.additional_kwargs?.tool_calls || []
|
|
531
|
+
const pendingCall = toolCalls[0]
|
|
532
|
+
if (pendingCall) {
|
|
533
|
+
// Try OpenClaw approval bridge first when agent uses openclaw provider
|
|
534
|
+
try {
|
|
535
|
+
if (orchestrator.provider === 'openclaw') {
|
|
536
|
+
const { forwardApprovalToOpenClaw } = await import('./openclaw-approvals')
|
|
537
|
+
const toolName = pendingCall.name || pendingCall.function?.name || 'unknown'
|
|
538
|
+
const toolArgs = pendingCall.args || (pendingCall.function?.arguments ? JSON.parse(pendingCall.function.arguments) : {})
|
|
539
|
+
const decision = await forwardApprovalToOpenClaw({ toolName, args: toolArgs })
|
|
540
|
+
if (decision) {
|
|
541
|
+
if (decision.approved) {
|
|
542
|
+
// OpenClaw approved — resume the graph instead of pausing
|
|
543
|
+
console.log(`[orchestrator-lg] OpenClaw approved tool "${toolName}" — resuming graph`)
|
|
544
|
+
// Don't set pendingApproval, let the loop continue
|
|
545
|
+
} else {
|
|
546
|
+
console.log(`[orchestrator-lg] OpenClaw rejected tool "${toolName}": ${decision.reason || 'no reason'}`)
|
|
547
|
+
// Fall through to SwarmClaw's pendingApproval UI
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// If decision is null (socket unavailable), fall through to SwarmClaw UI
|
|
551
|
+
}
|
|
552
|
+
} catch {
|
|
553
|
+
// OpenClaw approval bridge not available — fall through
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const tasks = loadTasks()
|
|
557
|
+
const t = tasks[taskId]
|
|
558
|
+
if (t) {
|
|
559
|
+
t.pendingApproval = {
|
|
560
|
+
toolName: pendingCall.name || pendingCall.function?.name || 'unknown',
|
|
561
|
+
args: pendingCall.args || (pendingCall.function?.arguments ? JSON.parse(pendingCall.function.arguments) : {}),
|
|
562
|
+
threadId,
|
|
563
|
+
}
|
|
564
|
+
t.updatedAt = Date.now()
|
|
565
|
+
saveTasks(tasks)
|
|
566
|
+
notify('tasks')
|
|
567
|
+
const approvalMsg = `[Awaiting approval] Tool: ${t.pendingApproval.toolName}\nArgs: ${JSON.stringify(t.pendingApproval.args).slice(0, 300)}\n\nApprove or reject in the task board.`
|
|
568
|
+
saveMessage(sessionId, 'assistant', approvalMsg)
|
|
569
|
+
notify(`messages:${sessionId}`)
|
|
570
|
+
pushMainLoopEventToMainSessions({
|
|
571
|
+
type: 'pending_approval',
|
|
572
|
+
text: `Task "${t.title}" needs approval: ${t.pendingApproval.toolName}(${JSON.stringify(t.pendingApproval.args).slice(0, 100)})`,
|
|
573
|
+
})
|
|
574
|
+
console.log(`[orchestrator-lg] Interrupt: waiting for approval of tool "${t.pendingApproval.toolName}" on task ${taskId}`)
|
|
575
|
+
return approvalMsg
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
417
580
|
} catch (err: any) {
|
|
418
581
|
const errMsg = timedOut
|
|
419
582
|
? 'Ongoing loop stopped after reaching the configured runtime limit.'
|
|
@@ -429,3 +592,242 @@ export async function executeLangGraphOrchestrator(
|
|
|
429
592
|
const completeMatch = finalResult.match(/ORCHESTRATION_COMPLETE:\s*([\s\S]+)/)
|
|
430
593
|
return completeMatch ? completeMatch[1].trim() : finalResult
|
|
431
594
|
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Resume a paused orchestrator run after human approval.
|
|
598
|
+
* Re-creates the same agent graph and streams from the saved checkpoint.
|
|
599
|
+
*/
|
|
600
|
+
export async function resumeLangGraphOrchestrator(
|
|
601
|
+
orchestrator: Agent,
|
|
602
|
+
sessionId: string,
|
|
603
|
+
threadId: string,
|
|
604
|
+
): Promise<string> {
|
|
605
|
+
const allAgents = loadAgents()
|
|
606
|
+
const agentIds = orchestrator.subAgentIds || []
|
|
607
|
+
const agents = agentIds.map((id) => allAgents[id]).filter(Boolean) as Agent[]
|
|
608
|
+
|
|
609
|
+
// Recreate the same tools
|
|
610
|
+
const delegateTool = tool(
|
|
611
|
+
async ({ agentName, task: agentTask }) => {
|
|
612
|
+
const agent = agents.find((a) => a.name.toLowerCase() === agentName.toLowerCase())
|
|
613
|
+
if (!agent) return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
|
|
614
|
+
const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
|
|
615
|
+
saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
|
|
616
|
+
name: 'delegate_to_agent',
|
|
617
|
+
input: JSON.stringify({ agentName: agent.name, agentId: agent.id, task: agentTask }),
|
|
618
|
+
output: result.slice(0, 2000),
|
|
619
|
+
}])
|
|
620
|
+
return result
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
name: 'delegate_to_agent',
|
|
624
|
+
description: 'Delegate a task to one of the available agents.',
|
|
625
|
+
schema: z.object({
|
|
626
|
+
agentName: z.string().describe('Name of the agent to delegate to'),
|
|
627
|
+
task: z.string().describe('The task description for the agent'),
|
|
628
|
+
}),
|
|
629
|
+
},
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
const db = getMemoryDb()
|
|
633
|
+
const storeMemoryTool = tool(
|
|
634
|
+
async ({ category, title, content }) => {
|
|
635
|
+
db.add({ agentId: orchestrator.id, sessionId, category, title, content })
|
|
636
|
+
return 'Memory stored successfully.'
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: 'store_memory',
|
|
640
|
+
description: 'Store information in long-term memory.',
|
|
641
|
+
schema: z.object({
|
|
642
|
+
category: z.string(), title: z.string(), content: z.string(),
|
|
643
|
+
}),
|
|
644
|
+
},
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
const searchMemoryTool = tool(
|
|
648
|
+
async ({ query }) => {
|
|
649
|
+
const results = db.search(query, orchestrator.id)
|
|
650
|
+
if (!results.length) return 'No matching memories found.'
|
|
651
|
+
return results.map((m) => `[${m.category}] ${m.title}: ${m.content.slice(0, 300)}`).join('\n')
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: 'search_memory',
|
|
655
|
+
description: 'Search long-term memory.',
|
|
656
|
+
schema: z.object({ query: z.string() }),
|
|
657
|
+
},
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
const markCompleteTool = tool(
|
|
661
|
+
async ({ summary }) => `ORCHESTRATION_COMPLETE: ${summary}`,
|
|
662
|
+
{
|
|
663
|
+
name: 'mark_complete',
|
|
664
|
+
description: 'Signal orchestration is done.',
|
|
665
|
+
schema: z.object({ summary: z.string() }),
|
|
666
|
+
},
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
const availableSecrets = getSecretsForOrchestrator(orchestrator.id)
|
|
670
|
+
const getSecretTool = tool(
|
|
671
|
+
async ({ serviceName }) => {
|
|
672
|
+
const match = availableSecrets.find(
|
|
673
|
+
(s) => s.service.toLowerCase() === serviceName.toLowerCase() || s.name.toLowerCase() === serviceName.toLowerCase(),
|
|
674
|
+
)
|
|
675
|
+
if (!match) return `No secret found for "${serviceName}".`
|
|
676
|
+
return JSON.stringify({ name: match.name, service: match.service, value: match.value })
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: 'get_secret',
|
|
680
|
+
description: 'Retrieve a stored credential/secret by service name.',
|
|
681
|
+
schema: z.object({ serviceName: z.string() }),
|
|
682
|
+
},
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
const commentOnTaskTool = tool(
|
|
686
|
+
async ({ taskId, comment }) => {
|
|
687
|
+
const tasks = loadTasks()
|
|
688
|
+
const t = tasks[taskId]
|
|
689
|
+
if (!t) return `Task "${taskId}" not found.`
|
|
690
|
+
if (!t.comments) t.comments = []
|
|
691
|
+
t.comments.push({
|
|
692
|
+
id: genId(),
|
|
693
|
+
author: orchestrator.name,
|
|
694
|
+
agentId: orchestrator.id,
|
|
695
|
+
text: comment,
|
|
696
|
+
createdAt: Date.now(),
|
|
697
|
+
})
|
|
698
|
+
t.updatedAt = Date.now()
|
|
699
|
+
saveTasks(tasks)
|
|
700
|
+
return `Comment added to task "${t.title}".`
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
name: 'comment_on_task',
|
|
704
|
+
description: 'Add a comment to a task.',
|
|
705
|
+
schema: z.object({ taskId: z.string(), comment: z.string() }),
|
|
706
|
+
},
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
const createTaskTool = tool(
|
|
710
|
+
async ({ title, description: desc }) => {
|
|
711
|
+
const tasks = loadTasks()
|
|
712
|
+
const id = genId()
|
|
713
|
+
tasks[id] = {
|
|
714
|
+
id, title, description: desc, status: 'backlog',
|
|
715
|
+
agentId: orchestrator.id, sessionId: null, result: null, error: null,
|
|
716
|
+
comments: [], createdAt: Date.now(), updatedAt: Date.now(),
|
|
717
|
+
queuedAt: null, startedAt: null, completedAt: null,
|
|
718
|
+
}
|
|
719
|
+
saveTasks(tasks)
|
|
720
|
+
return `Task "${title}" created in backlog (id: ${id}).`
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: 'create_task',
|
|
724
|
+
description: 'Create a new task in the backlog.',
|
|
725
|
+
schema: z.object({ title: z.string(), description: z.string() }),
|
|
726
|
+
},
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
const engine = getOrchestrationEngineConfig(orchestrator)
|
|
730
|
+
const llm = buildChatModel({
|
|
731
|
+
provider: engine.provider, model: engine.model,
|
|
732
|
+
apiKey: engine.apiKey, apiEndpoint: engine.apiEndpoint,
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
const checkpointSaver = getCheckpointSaver()
|
|
736
|
+
const settings = loadSettings()
|
|
737
|
+
const isStrictMode = settings.capabilityPolicyMode === 'strict'
|
|
738
|
+
|
|
739
|
+
const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
|
|
740
|
+
const llmWithTools = llm.bindTools(allTools)
|
|
741
|
+
const toolNode = new ToolNode(allTools)
|
|
742
|
+
|
|
743
|
+
async function agentNode(state: typeof MessagesAnnotation.State) {
|
|
744
|
+
const response = await llmWithTools.invoke(state.messages)
|
|
745
|
+
return { messages: [response] }
|
|
746
|
+
}
|
|
747
|
+
function routerNode(state: typeof MessagesAnnotation.State) {
|
|
748
|
+
return { messages: [] }
|
|
749
|
+
}
|
|
750
|
+
function shouldContinue(state: typeof MessagesAnnotation.State) {
|
|
751
|
+
const lastMsg = state.messages[state.messages.length - 1]
|
|
752
|
+
const toolCalls = (lastMsg as any)?.tool_calls
|
|
753
|
+
if (toolCalls && toolCalls.length > 0) return 'tools'
|
|
754
|
+
return END
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const graphAgent = new StateGraph(MessagesAnnotation)
|
|
758
|
+
.addNode('agent', agentNode)
|
|
759
|
+
.addNode('tools', toolNode)
|
|
760
|
+
.addNode('router', routerNode)
|
|
761
|
+
.addEdge(START, 'agent')
|
|
762
|
+
.addConditionalEdges('agent', shouldContinue, { tools: 'tools', [END]: END })
|
|
763
|
+
.addEdge('tools', 'router')
|
|
764
|
+
.addEdge('router', 'agent')
|
|
765
|
+
.compile({
|
|
766
|
+
checkpointer: checkpointSaver,
|
|
767
|
+
...(isStrictMode ? { interruptBefore: ['tools'] } : {}),
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
let finalResult = ''
|
|
771
|
+
const runtime = loadRuntimeSettings()
|
|
772
|
+
const recursionLimit = getOrchestratorLoopRecursionLimit(runtime)
|
|
773
|
+
|
|
774
|
+
try {
|
|
775
|
+
// Resume from checkpoint with null input — the checkpoint has the full state
|
|
776
|
+
const stream = await graphAgent.stream(null, {
|
|
777
|
+
recursionLimit,
|
|
778
|
+
configurable: { thread_id: threadId },
|
|
779
|
+
})
|
|
780
|
+
|
|
781
|
+
for await (const chunk of stream) {
|
|
782
|
+
const agentChunk = (chunk as any).agent
|
|
783
|
+
if (agentChunk?.messages) {
|
|
784
|
+
const msgs = Array.isArray(agentChunk.messages) ? agentChunk.messages : [agentChunk.messages]
|
|
785
|
+
for (const msg of msgs) {
|
|
786
|
+
const text = typeof msg.content === 'string'
|
|
787
|
+
? msg.content
|
|
788
|
+
: Array.isArray(msg.content)
|
|
789
|
+
? msg.content.map((c: any) => c.text || '').join('')
|
|
790
|
+
: ''
|
|
791
|
+
if (text) {
|
|
792
|
+
finalResult = text
|
|
793
|
+
saveMessage(sessionId, 'assistant', text)
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Check for another interrupt
|
|
800
|
+
const state = await graphAgent.getState({ configurable: { thread_id: threadId } })
|
|
801
|
+
const nextNodes = state?.next || []
|
|
802
|
+
if (nextNodes.includes('tools')) {
|
|
803
|
+
const messages = state.values?.messages || []
|
|
804
|
+
const lastMsg = messages[messages.length - 1]
|
|
805
|
+
const toolCalls = lastMsg?.tool_calls || lastMsg?.additional_kwargs?.tool_calls || []
|
|
806
|
+
const pendingCall = toolCalls[0]
|
|
807
|
+
if (pendingCall) {
|
|
808
|
+
// Find the task by threadId
|
|
809
|
+
const tasks = loadTasks()
|
|
810
|
+
for (const t of Object.values(tasks)) {
|
|
811
|
+
if (t.pendingApproval?.threadId === threadId || t.id === threadId) {
|
|
812
|
+
t.pendingApproval = {
|
|
813
|
+
toolName: pendingCall.name || pendingCall.function?.name || 'unknown',
|
|
814
|
+
args: pendingCall.args || {},
|
|
815
|
+
threadId,
|
|
816
|
+
}
|
|
817
|
+
t.updatedAt = Date.now()
|
|
818
|
+
saveTasks(tasks)
|
|
819
|
+
notify('tasks')
|
|
820
|
+
return `Waiting for approval: ${t.pendingApproval.toolName}`
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
} catch (err: any) {
|
|
826
|
+
console.error(`[orchestrator-lg] Resume error:`, err.message || String(err))
|
|
827
|
+
saveMessage(sessionId, 'assistant', `[Error] ${err.message || String(err)}`)
|
|
828
|
+
throw err
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const completeMatch = finalResult.match(/ORCHESTRATION_COMPLETE:\s*([\s\S]+)/)
|
|
832
|
+
return completeMatch ? completeMatch[1].trim() : finalResult
|
|
833
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
2
2
|
import {
|
|
3
3
|
loadSessions, saveSessions, loadAgents,
|
|
4
4
|
loadCredentials, decryptKey, loadSettings, loadSkills,
|
|
5
5
|
} from './storage'
|
|
6
|
+
import { WORKSPACE_DIR } from './data-dir'
|
|
6
7
|
import { loadRuntimeSettings, getLegacyOrchestratorMaxTurns } from './runtime-settings'
|
|
7
8
|
import { getMemoryDb } from './memory-db'
|
|
8
9
|
import { getProvider } from '../providers'
|
|
@@ -19,11 +20,11 @@ export function createOrchestratorSession(
|
|
|
19
20
|
cwd?: string,
|
|
20
21
|
): string {
|
|
21
22
|
const sessions = loadSessions()
|
|
22
|
-
const sessionId =
|
|
23
|
+
const sessionId = genId()
|
|
23
24
|
sessions[sessionId] = {
|
|
24
25
|
id: sessionId,
|
|
25
26
|
name: `[Orch] ${orchestrator.name}: ${task.slice(0, 40)}`,
|
|
26
|
-
cwd: cwd ||
|
|
27
|
+
cwd: cwd || WORKSPACE_DIR,
|
|
27
28
|
user: 'system',
|
|
28
29
|
provider: orchestrator.provider,
|
|
29
30
|
model: orchestrator.model,
|
|
@@ -63,13 +64,14 @@ export async function executeOrchestrator(
|
|
|
63
64
|
orchestrator: Agent,
|
|
64
65
|
task: string,
|
|
65
66
|
sessionId: string,
|
|
67
|
+
taskId?: string,
|
|
66
68
|
): Promise<string> {
|
|
67
69
|
// Use LangGraph for all non-CLI providers (including OpenAI-compatible custom providers)
|
|
68
70
|
const isCliProvider = orchestrator.provider === 'claude-cli' || orchestrator.provider === 'codex-cli' || orchestrator.provider === 'opencode-cli'
|
|
69
71
|
if (!isCliProvider) {
|
|
70
72
|
console.log(`[orchestrator] Using LangGraph engine for ${orchestrator.name} (${orchestrator.provider})`)
|
|
71
73
|
const { executeLangGraphOrchestrator } = await import('./orchestrator-lg')
|
|
72
|
-
return executeLangGraphOrchestrator(orchestrator, task, sessionId)
|
|
74
|
+
return executeLangGraphOrchestrator(orchestrator, task, sessionId, taskId)
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
// claude-cli fallback (no structured tool calling)
|
|
@@ -156,7 +158,10 @@ async function executeOrchestratorLegacy(
|
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
const
|
|
161
|
+
const windowedHistory = conversationHistory.length > 10
|
|
162
|
+
? [conversationHistory[0], ...conversationHistory.slice(-9)]
|
|
163
|
+
: conversationHistory
|
|
164
|
+
const fullText = await callProvider(orchestrator, systemPrompt, windowedHistory)
|
|
160
165
|
conversationHistory.push({ role: 'assistant', text: fullText })
|
|
161
166
|
|
|
162
167
|
// Save to session
|
|
@@ -189,6 +194,21 @@ async function executeOrchestratorLegacy(
|
|
|
189
194
|
role: 'user',
|
|
190
195
|
text: `[Agent ${agent.name} result]:\n${subResult}`,
|
|
191
196
|
})
|
|
197
|
+
// Save structured delegation message for rich card rendering
|
|
198
|
+
session.messages.push({
|
|
199
|
+
role: 'assistant' as const,
|
|
200
|
+
text: `Delegated to ${agent.name}: ${cmd.delegate.task.slice(0, 100)}`,
|
|
201
|
+
time: Date.now(),
|
|
202
|
+
toolEvents: [{
|
|
203
|
+
name: 'delegate_to_agent',
|
|
204
|
+
input: JSON.stringify({ agentName: agent.name, agentId: agent.id, task: cmd.delegate.task }),
|
|
205
|
+
output: subResult.slice(0, 2000),
|
|
206
|
+
}],
|
|
207
|
+
})
|
|
208
|
+
session.lastActiveAt = Date.now()
|
|
209
|
+
const ds = loadSessions()
|
|
210
|
+
ds[sessionId] = session
|
|
211
|
+
saveSessions(ds)
|
|
192
212
|
}
|
|
193
213
|
|
|
194
214
|
if (cmd.memory_store) {
|
|
@@ -244,11 +264,11 @@ async function executeSubTask(
|
|
|
244
264
|
// Look up parent session cwd to inherit
|
|
245
265
|
const sessions = loadSessions()
|
|
246
266
|
const parentSession = sessions[parentSessionId]
|
|
247
|
-
const childId =
|
|
267
|
+
const childId = genId()
|
|
248
268
|
const childSession = {
|
|
249
269
|
id: childId,
|
|
250
270
|
name: `[Agent] ${agent.name}: ${task.slice(0, 40)}`,
|
|
251
|
-
cwd: parentSession?.cwd ||
|
|
271
|
+
cwd: parentSession?.cwd || WORKSPACE_DIR,
|
|
252
272
|
user: 'system',
|
|
253
273
|
provider: agent.provider,
|
|
254
274
|
model: agent.model,
|
|
@@ -305,12 +325,12 @@ export async function callProvider(
|
|
|
305
325
|
|
|
306
326
|
// Build a mock session for the provider
|
|
307
327
|
const mockSession = {
|
|
308
|
-
id: 'orch-' +
|
|
328
|
+
id: 'orch-' + genId(2),
|
|
309
329
|
provider: agent.provider,
|
|
310
330
|
model: agent.model,
|
|
311
331
|
credentialId: agent.credentialId,
|
|
312
332
|
apiEndpoint: agent.apiEndpoint,
|
|
313
|
-
cwd:
|
|
333
|
+
cwd: WORKSPACE_DIR,
|
|
314
334
|
tools: agent.tools || [],
|
|
315
335
|
messages: history.map((h) => ({
|
|
316
336
|
role: h.role as 'user' | 'assistant',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
2
2
|
import { spawn, type ChildProcessWithoutNullStreams } from 'child_process'
|
|
3
3
|
|
|
4
4
|
const MAX_LOG_CHARS = 200_000
|
|
@@ -99,7 +99,7 @@ function getShellCommand(command: string): { shell: string; args: string[] } {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export async function startManagedProcess(opts: StartProcessOptions): Promise<StartProcessResult> {
|
|
102
|
-
const id =
|
|
102
|
+
const id = genId(8)
|
|
103
103
|
const timeoutMs = Math.max(1000, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS)
|
|
104
104
|
const yieldMs = Math.max(250, opts.yieldMs ?? DEFAULT_BACKGROUND_YIELD_MS)
|
|
105
105
|
const startedAt = now()
|