skimpyclaw 0.3.14 → 0.4.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 +47 -37
- package/dist/__tests__/adapter-types.test.d.ts +4 -0
- package/dist/__tests__/adapter-types.test.js +63 -0
- package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
- package/dist/__tests__/anthropic-adapter.test.js +264 -0
- package/dist/__tests__/api.test.js +0 -1
- package/dist/__tests__/cli.integration.test.js +2 -4
- package/dist/__tests__/cli.test.js +0 -1
- package/dist/__tests__/code-agents-notifications.test.js +137 -0
- package/dist/__tests__/code-agents-parser.test.js +19 -1
- package/dist/__tests__/code-agents-preflight.test.js +3 -28
- package/dist/__tests__/code-agents-utils.test.js +34 -9
- package/dist/__tests__/code-agents-worktrees.test.js +116 -0
- package/dist/__tests__/codex-adapter.test.js +184 -0
- package/dist/__tests__/codex-auth.test.js +66 -0
- package/dist/__tests__/codex-provider-gating.test.js +35 -0
- package/dist/__tests__/codex-unified-loop.test.js +111 -0
- package/dist/__tests__/config-security.test.js +127 -0
- package/dist/__tests__/config.test.js +23 -0
- package/dist/__tests__/context-manager.test.js +243 -164
- package/dist/__tests__/cron-run.test.js +250 -0
- package/dist/__tests__/cron.test.js +12 -38
- package/dist/__tests__/digests.test.js +67 -0
- package/dist/__tests__/discord-attachments.test.js +211 -0
- package/dist/__tests__/discord-docs.test.d.ts +1 -0
- package/dist/__tests__/discord-docs.test.js +27 -0
- package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-agents.test.js +115 -0
- package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-context.test.js +42 -0
- package/dist/__tests__/doctor.formatters.test.js +4 -4
- package/dist/__tests__/doctor.index.test.js +1 -1
- package/dist/__tests__/doctor.runner.test.js +3 -15
- package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
- package/dist/__tests__/env-sanitizer.test.js +45 -0
- package/dist/__tests__/exec-approval.test.js +61 -0
- package/dist/__tests__/fetch-tool.test.d.ts +1 -0
- package/dist/__tests__/fetch-tool.test.js +85 -0
- package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
- package/dist/__tests__/gateway-status-auth.test.js +72 -0
- package/dist/__tests__/heartbeat.test.js +3 -3
- package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
- package/dist/__tests__/interactive-sessions.test.js +96 -0
- package/dist/__tests__/langfuse.test.js +6 -18
- package/dist/__tests__/model-selection.test.js +3 -4
- package/dist/__tests__/providers-init.test.js +2 -8
- package/dist/__tests__/providers-routing.test.js +1 -1
- package/dist/__tests__/providers-utils.test.js +13 -3
- package/dist/__tests__/sessions.test.js +14 -10
- package/dist/__tests__/setup.test.js +12 -29
- package/dist/__tests__/skills.test.js +10 -7
- package/dist/__tests__/stream-formatter.test.d.ts +1 -0
- package/dist/__tests__/stream-formatter.test.js +114 -0
- package/dist/__tests__/token-efficiency.test.js +131 -15
- package/dist/__tests__/tool-loop.test.d.ts +4 -0
- package/dist/__tests__/tool-loop.test.js +505 -0
- package/dist/__tests__/tools.test.js +101 -276
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +14 -0
- package/dist/__tests__/voice.test.js +21 -0
- package/dist/agent.js +35 -4
- package/dist/api.js +113 -37
- package/dist/channels/discord/attachments.d.ts +50 -0
- package/dist/channels/discord/attachments.js +137 -0
- package/dist/channels/discord/delegation.d.ts +5 -0
- package/dist/channels/discord/delegation.js +136 -0
- package/dist/channels/discord/handlers.js +694 -7
- package/dist/channels/discord/index.d.ts +16 -1
- package/dist/channels/discord/index.js +64 -1
- package/dist/channels/discord/thread-agents.d.ts +54 -0
- package/dist/channels/discord/thread-agents.js +323 -0
- package/dist/channels/discord/threads.d.ts +58 -0
- package/dist/channels/discord/threads.js +192 -0
- package/dist/channels/discord/types.js +4 -2
- package/dist/channels/discord/utils.d.ts +16 -0
- package/dist/channels/discord/utils.js +86 -6
- package/dist/channels/telegram/index.d.ts +1 -1
- package/dist/channels/telegram/types.js +1 -1
- package/dist/channels/telegram/utils.js +9 -3
- package/dist/channels.d.ts +1 -1
- package/dist/cli.js +20 -400
- package/dist/code-agents/executor.d.ts +1 -1
- package/dist/code-agents/executor.js +101 -45
- package/dist/code-agents/index.d.ts +2 -7
- package/dist/code-agents/index.js +111 -80
- package/dist/code-agents/interactive-resume.d.ts +6 -0
- package/dist/code-agents/interactive-resume.js +98 -0
- package/dist/code-agents/interactive-sessions.d.ts +20 -0
- package/dist/code-agents/interactive-sessions.js +132 -0
- package/dist/code-agents/parser.js +5 -1
- package/dist/code-agents/registry.d.ts +7 -1
- package/dist/code-agents/registry.js +11 -23
- package/dist/code-agents/stream-formatter.d.ts +8 -0
- package/dist/code-agents/stream-formatter.js +92 -0
- package/dist/code-agents/types.d.ts +16 -24
- package/dist/code-agents/utils.d.ts +35 -11
- package/dist/code-agents/utils.js +349 -95
- package/dist/code-agents/worktrees.d.ts +37 -0
- package/dist/code-agents/worktrees.js +116 -0
- package/dist/config.d.ts +2 -4
- package/dist/config.js +123 -23
- package/dist/cron.d.ts +1 -6
- package/dist/cron.js +175 -82
- package/dist/dashboard/assets/index-B345aOO-.js +65 -0
- package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/digests.d.ts +1 -0
- package/dist/digests.js +132 -42
- package/dist/doctor/checks.d.ts +0 -3
- package/dist/doctor/checks.js +1 -108
- package/dist/doctor/runner.js +1 -4
- package/dist/env-sanitizer.d.ts +2 -0
- package/dist/env-sanitizer.js +61 -0
- package/dist/exec-approval.d.ts +11 -1
- package/dist/exec-approval.js +17 -4
- package/dist/gateway.d.ts +3 -1
- package/dist/gateway.js +17 -7
- package/dist/heartbeat.js +1 -6
- package/dist/langfuse.js +3 -29
- package/dist/model-selection.js +3 -1
- package/dist/providers/adapter.d.ts +118 -0
- package/dist/providers/adapter.js +6 -0
- package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
- package/dist/providers/adapters/anthropic-adapter.js +204 -0
- package/dist/providers/adapters/codex-adapter.d.ts +26 -0
- package/dist/providers/adapters/codex-adapter.js +203 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +10 -272
- package/dist/providers/codex.d.ts +21 -0
- package/dist/providers/codex.js +149 -330
- package/dist/providers/content.d.ts +1 -1
- package/dist/providers/content.js +2 -2
- package/dist/providers/context-manager.d.ts +18 -6
- package/dist/providers/context-manager.js +199 -223
- package/dist/providers/index.d.ts +9 -1
- package/dist/providers/index.js +73 -64
- package/dist/providers/loop-utils.d.ts +20 -0
- package/dist/providers/loop-utils.js +30 -0
- package/dist/providers/tool-loop.d.ts +12 -0
- package/dist/providers/tool-loop.js +251 -0
- package/dist/providers/utils.d.ts +19 -3
- package/dist/providers/utils.js +100 -29
- package/dist/secure-store.d.ts +8 -0
- package/dist/secure-store.js +80 -0
- package/dist/service.js +3 -28
- package/dist/sessions.d.ts +3 -0
- package/dist/sessions.js +147 -18
- package/dist/setup-templates.js +13 -25
- package/dist/setup.d.ts +10 -6
- package/dist/setup.js +84 -292
- package/dist/skills.js +3 -11
- package/dist/tools/agent-delegation.d.ts +19 -0
- package/dist/tools/agent-delegation.js +49 -0
- package/dist/tools/bash-tool.js +89 -34
- package/dist/tools/definitions.d.ts +199 -302
- package/dist/tools/definitions.js +70 -123
- package/dist/tools/execute-context.d.ts +13 -4
- package/dist/tools/fetch-tool.js +109 -13
- package/dist/tools/file-tools.js +7 -1
- package/dist/tools.d.ts +7 -7
- package/dist/tools.js +133 -151
- package/dist/types.d.ts +37 -30
- package/dist/utils.js +4 -6
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +17 -4
- package/package.json +33 -23
- package/templates/TOOLS.md +0 -27
- package/dist/__tests__/audit.test.js +0 -122
- package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
- package/dist/__tests__/code-agents-sandbox.test.js +0 -163
- package/dist/__tests__/orchestrator.test.js +0 -425
- package/dist/__tests__/sandbox-bridge.test.js +0 -116
- package/dist/__tests__/sandbox-manager.test.js +0 -144
- package/dist/__tests__/sandbox-mount-security.test.js +0 -139
- package/dist/__tests__/sandbox-runtime.test.js +0 -176
- package/dist/__tests__/subagent.test.js +0 -240
- package/dist/__tests__/telegram.test.js +0 -42
- package/dist/code-agents/orchestrator.d.ts +0 -29
- package/dist/code-agents/orchestrator.js +0 -694
- package/dist/code-agents/worktree.d.ts +0 -40
- package/dist/code-agents/worktree.js +0 -215
- package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
- package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
- package/dist/dashboard.d.ts +0 -8
- package/dist/dashboard.js +0 -4071
- package/dist/discord.d.ts +0 -8
- package/dist/discord.js +0 -792
- package/dist/mcp-context-a8c.d.ts +0 -13
- package/dist/mcp-context-a8c.js +0 -34
- package/dist/orchestrator.d.ts +0 -15
- package/dist/orchestrator.js +0 -676
- package/dist/providers/openai.d.ts +0 -10
- package/dist/providers/openai.js +0 -355
- package/dist/sandbox/bridge.d.ts +0 -5
- package/dist/sandbox/bridge.js +0 -63
- package/dist/sandbox/index.d.ts +0 -5
- package/dist/sandbox/index.js +0 -4
- package/dist/sandbox/manager.d.ts +0 -7
- package/dist/sandbox/manager.js +0 -100
- package/dist/sandbox/mount-security.d.ts +0 -12
- package/dist/sandbox/mount-security.js +0 -122
- package/dist/sandbox/runtime.d.ts +0 -39
- package/dist/sandbox/runtime.js +0 -192
- package/dist/sandbox-utils.d.ts +0 -6
- package/dist/sandbox-utils.js +0 -36
- package/dist/subagent.d.ts +0 -19
- package/dist/subagent.js +0 -407
- package/dist/telegram.d.ts +0 -2
- package/dist/telegram.js +0 -11
- package/dist/tools/browser-tool.d.ts +0 -3
- package/dist/tools/browser-tool.js +0 -266
- package/sandbox/Dockerfile +0 -40
- /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
- /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
- /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
- /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
package/dist/orchestrator.js
DELETED
|
@@ -1,676 +0,0 @@
|
|
|
1
|
-
// Orchestrator: multi-phase task decomposition and parallel execution engine
|
|
2
|
-
import { homedir } from 'os';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import { existsSync, mkdirSync, appendFileSync, readFileSync } from 'fs';
|
|
5
|
-
import { runAgentTurn, buildSystemPrompt } from './agent.js';
|
|
6
|
-
import { getToolDefinitions, ORCHESTRATOR_TOOLS } from './tools.js';
|
|
7
|
-
import { getCurrentModel } from './gateway.js';
|
|
8
|
-
import { ensureAgentSetup } from './subagent.js';
|
|
9
|
-
// --- Constants ---
|
|
10
|
-
const ORCH_DIR = join(homedir(), '.skimpyclaw', 'orchestrations');
|
|
11
|
-
const MAX_WORKERS = 3;
|
|
12
|
-
const POLL_INTERVAL_MS = 2000;
|
|
13
|
-
// --- Module-level state ---
|
|
14
|
-
const activeSessions = new Map();
|
|
15
|
-
let currentPlanningSession = null;
|
|
16
|
-
let deliverMessage = null;
|
|
17
|
-
let orchTaskCounter = 0;
|
|
18
|
-
let orchWorkerCounter = 0;
|
|
19
|
-
let planningComplete = false;
|
|
20
|
-
function ensureOrchDir() {
|
|
21
|
-
if (!existsSync(ORCH_DIR)) {
|
|
22
|
-
mkdirSync(ORCH_DIR, { recursive: true });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function getSessionPath(sessionId) {
|
|
26
|
-
return join(ORCH_DIR, `${sessionId}.jsonl`);
|
|
27
|
-
}
|
|
28
|
-
function appendEvent(sessionId, event) {
|
|
29
|
-
ensureOrchDir();
|
|
30
|
-
const full = {
|
|
31
|
-
...event,
|
|
32
|
-
timestamp: new Date().toISOString(),
|
|
33
|
-
sessionId,
|
|
34
|
-
};
|
|
35
|
-
appendFileSync(getSessionPath(sessionId), JSON.stringify(full) + '\n', 'utf-8');
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Reconstruct a session from its JSONL event log.
|
|
39
|
-
* Returns null if the session file doesn't exist.
|
|
40
|
-
*/
|
|
41
|
-
export function loadSession(sessionId) {
|
|
42
|
-
const path = getSessionPath(sessionId);
|
|
43
|
-
if (!existsSync(path))
|
|
44
|
-
return null;
|
|
45
|
-
const lines = readFileSync(path, 'utf-8').trim().split('\n').filter(Boolean);
|
|
46
|
-
let session = null;
|
|
47
|
-
for (const line of lines) {
|
|
48
|
-
const event = JSON.parse(line);
|
|
49
|
-
switch (event.type) {
|
|
50
|
-
case 'session_created':
|
|
51
|
-
session = event.data.session;
|
|
52
|
-
break;
|
|
53
|
-
case 'task_created':
|
|
54
|
-
if (session) {
|
|
55
|
-
session.tasks.push(event.data.task);
|
|
56
|
-
}
|
|
57
|
-
break;
|
|
58
|
-
case 'task_started':
|
|
59
|
-
if (session) {
|
|
60
|
-
const task = session.tasks.find(t => t.id === event.data.taskId);
|
|
61
|
-
if (task) {
|
|
62
|
-
task.status = 'running';
|
|
63
|
-
task.workerId = event.data.workerId;
|
|
64
|
-
task.startedAt = event.timestamp;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
break;
|
|
68
|
-
case 'task_completed':
|
|
69
|
-
if (session) {
|
|
70
|
-
const task = session.tasks.find(t => t.id === event.data.taskId);
|
|
71
|
-
if (task) {
|
|
72
|
-
task.status = 'completed';
|
|
73
|
-
task.result = event.data.result;
|
|
74
|
-
task.completedAt = event.timestamp;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
break;
|
|
78
|
-
case 'task_failed':
|
|
79
|
-
if (session) {
|
|
80
|
-
const task = session.tasks.find(t => t.id === event.data.taskId);
|
|
81
|
-
if (task) {
|
|
82
|
-
task.status = 'failed';
|
|
83
|
-
task.error = event.data.error;
|
|
84
|
-
task.completedAt = event.timestamp;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
break;
|
|
88
|
-
case 'phase_changed':
|
|
89
|
-
if (session) {
|
|
90
|
-
session.status = event.data.phase;
|
|
91
|
-
}
|
|
92
|
-
break;
|
|
93
|
-
case 'message_sent':
|
|
94
|
-
if (session) {
|
|
95
|
-
session.messages.push(event.data.message);
|
|
96
|
-
}
|
|
97
|
-
break;
|
|
98
|
-
case 'session_completed':
|
|
99
|
-
if (session) {
|
|
100
|
-
session.status = 'completed';
|
|
101
|
-
session.finalResult = event.data.finalResult;
|
|
102
|
-
session.completedAt = event.timestamp;
|
|
103
|
-
}
|
|
104
|
-
break;
|
|
105
|
-
case 'session_failed':
|
|
106
|
-
if (session) {
|
|
107
|
-
session.status = 'failed';
|
|
108
|
-
session.error = event.data.error;
|
|
109
|
-
session.completedAt = event.timestamp;
|
|
110
|
-
}
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return session;
|
|
115
|
-
}
|
|
116
|
-
// ORCHESTRATOR_TOOLS is imported from tools.ts to avoid circular deps
|
|
117
|
-
// (tools.ts defines them, orchestrator.ts uses them in planning phase)
|
|
118
|
-
// --- Initialization ---
|
|
119
|
-
export function initOrchestrationSystem(deliverFn) {
|
|
120
|
-
deliverMessage = deliverFn;
|
|
121
|
-
console.log('[orchestrator] System initialized');
|
|
122
|
-
}
|
|
123
|
-
// --- Session Management ---
|
|
124
|
-
export function getActiveOrchestrations() {
|
|
125
|
-
return [...activeSessions.values()].filter(s => s.status !== 'completed' && s.status !== 'failed');
|
|
126
|
-
}
|
|
127
|
-
export function getOrchestration(id) {
|
|
128
|
-
return activeSessions.get(id) || loadSession(id);
|
|
129
|
-
}
|
|
130
|
-
export function listRecentOrchestrations(limit = 10) {
|
|
131
|
-
return [...activeSessions.values()]
|
|
132
|
-
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
|
133
|
-
.slice(0, limit);
|
|
134
|
-
}
|
|
135
|
-
export function setCurrentPlanningSession(session) {
|
|
136
|
-
currentPlanningSession = session;
|
|
137
|
-
}
|
|
138
|
-
export function getCurrentPlanningSession() {
|
|
139
|
-
return currentPlanningSession;
|
|
140
|
-
}
|
|
141
|
-
// --- Orchestrator Tool Execution ---
|
|
142
|
-
export function executeOrchestratorTool(name, input) {
|
|
143
|
-
switch (name) {
|
|
144
|
-
case 'CreateTask': {
|
|
145
|
-
if (!currentPlanningSession) {
|
|
146
|
-
return 'Error: No active planning session.';
|
|
147
|
-
}
|
|
148
|
-
const { title, description, agentType, dependsOn } = input;
|
|
149
|
-
if (!title || !description || !agentType) {
|
|
150
|
-
return 'Error: title, description, and agentType are required.';
|
|
151
|
-
}
|
|
152
|
-
if (!['coding', 'research', 'general'].includes(agentType)) {
|
|
153
|
-
return `Error: Invalid agentType "${agentType}". Must be coding, research, or general.`;
|
|
154
|
-
}
|
|
155
|
-
orchTaskCounter++;
|
|
156
|
-
const taskId = `ot${orchTaskCounter}`;
|
|
157
|
-
// Resolve dependsOn — accept any reasonable reference format:
|
|
158
|
-
// "ot1", "1", "task-1", "task_1", "#1" all resolve to the first created task
|
|
159
|
-
const deps = (dependsOn || []).map((ref) => {
|
|
160
|
-
// Already a valid task ID?
|
|
161
|
-
if (currentPlanningSession.tasks.some(t => t.id === ref))
|
|
162
|
-
return ref;
|
|
163
|
-
// Extract number from reference and map to task by creation order
|
|
164
|
-
const numMatch = ref.match(/(\d+)/);
|
|
165
|
-
if (numMatch) {
|
|
166
|
-
const idx = parseInt(numMatch[1], 10) - 1; // 1-based to 0-based
|
|
167
|
-
if (idx >= 0 && idx < currentPlanningSession.tasks.length) {
|
|
168
|
-
return currentPlanningSession.tasks[idx].id;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return ref; // Pass through — will be caught by validation below
|
|
172
|
-
});
|
|
173
|
-
// Validate resolved deps
|
|
174
|
-
const existingIds = new Set(currentPlanningSession.tasks.map(t => t.id));
|
|
175
|
-
const invalidDeps = deps.filter(id => !existingIds.has(id));
|
|
176
|
-
if (invalidDeps.length > 0) {
|
|
177
|
-
orchTaskCounter--; // Roll back counter
|
|
178
|
-
const validIds = currentPlanningSession.tasks.map((t, i) => `${t.id} (task ${i + 1}: "${t.title}")`).join(', ') || '(none yet)';
|
|
179
|
-
return `Error: Could not resolve dependsOn references: ${invalidDeps.join(', ')}. Existing tasks: ${validIds}`;
|
|
180
|
-
}
|
|
181
|
-
const task = {
|
|
182
|
-
id: taskId,
|
|
183
|
-
sessionId: currentPlanningSession.id,
|
|
184
|
-
title,
|
|
185
|
-
description,
|
|
186
|
-
status: 'pending',
|
|
187
|
-
dependsOn: deps,
|
|
188
|
-
createdAt: new Date().toISOString(),
|
|
189
|
-
};
|
|
190
|
-
currentPlanningSession.tasks.push(task);
|
|
191
|
-
appendEvent(currentPlanningSession.id, {
|
|
192
|
-
type: 'task_created',
|
|
193
|
-
data: { task },
|
|
194
|
-
});
|
|
195
|
-
const taskNum = currentPlanningSession.tasks.length;
|
|
196
|
-
return `Task ${taskNum} created (id: ${taskId}): "${title}" [${agentType}], depends on: [${deps.join(', ') || 'none'}]`;
|
|
197
|
-
}
|
|
198
|
-
case 'FinishPlanning': {
|
|
199
|
-
if (!currentPlanningSession) {
|
|
200
|
-
return 'Error: No active planning session.';
|
|
201
|
-
}
|
|
202
|
-
planningComplete = true;
|
|
203
|
-
const taskCount = currentPlanningSession.tasks.length;
|
|
204
|
-
return `Planning complete. ${taskCount} task(s) created. Execution will begin.`;
|
|
205
|
-
}
|
|
206
|
-
case 'GetTaskStatus': {
|
|
207
|
-
if (!currentPlanningSession) {
|
|
208
|
-
return 'Error: No active planning session.';
|
|
209
|
-
}
|
|
210
|
-
const tasks = currentPlanningSession.tasks;
|
|
211
|
-
if (tasks.length === 0)
|
|
212
|
-
return 'No tasks created yet.';
|
|
213
|
-
const summary = tasks.map(t => {
|
|
214
|
-
const deps = t.dependsOn.length > 0 ? ` (depends on: ${t.dependsOn.join(', ')})` : '';
|
|
215
|
-
return `- ${t.id}: [${t.status}] ${t.title}${deps}`;
|
|
216
|
-
});
|
|
217
|
-
return summary.join('\n');
|
|
218
|
-
}
|
|
219
|
-
default:
|
|
220
|
-
return `Error: Unknown orchestrator tool "${name}"`;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// --- Main Entry Point ---
|
|
224
|
-
export function startOrchestration(prompt, chatId, config, modelOverride) {
|
|
225
|
-
const sessionId = `orch_${Date.now()}`;
|
|
226
|
-
const model = modelOverride || getCurrentModel();
|
|
227
|
-
const session = {
|
|
228
|
-
id: sessionId,
|
|
229
|
-
prompt,
|
|
230
|
-
status: 'planning',
|
|
231
|
-
model,
|
|
232
|
-
chatId,
|
|
233
|
-
tasks: [],
|
|
234
|
-
workers: [],
|
|
235
|
-
messages: [],
|
|
236
|
-
createdAt: new Date().toISOString(),
|
|
237
|
-
startedAt: new Date().toISOString(),
|
|
238
|
-
};
|
|
239
|
-
activeSessions.set(sessionId, session);
|
|
240
|
-
appendEvent(sessionId, {
|
|
241
|
-
type: 'session_created',
|
|
242
|
-
data: { session },
|
|
243
|
-
});
|
|
244
|
-
// Fire and forget — run all phases async
|
|
245
|
-
runOrchestration(session, config).catch(err => {
|
|
246
|
-
console.error(`[orchestrator] Unhandled error in ${sessionId}:`, err);
|
|
247
|
-
session.status = 'failed';
|
|
248
|
-
session.error = err instanceof Error ? err.message : String(err);
|
|
249
|
-
session.completedAt = new Date().toISOString();
|
|
250
|
-
appendEvent(sessionId, {
|
|
251
|
-
type: 'session_failed',
|
|
252
|
-
data: { error: session.error },
|
|
253
|
-
});
|
|
254
|
-
if (deliverMessage) {
|
|
255
|
-
deliverMessage(chatId, `❌ Orchestration ${sessionId} failed:\n\n${session.error}`).catch(() => { });
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
return session;
|
|
259
|
-
}
|
|
260
|
-
// --- Phase Runner ---
|
|
261
|
-
async function runOrchestration(session, config) {
|
|
262
|
-
const { chatId } = session;
|
|
263
|
-
try {
|
|
264
|
-
// Phase 1: Planning
|
|
265
|
-
if (deliverMessage) {
|
|
266
|
-
await deliverMessage(chatId, `🎯 Orchestration started: planning phase...\n\nPrompt: ${session.prompt}`);
|
|
267
|
-
}
|
|
268
|
-
await planPhase(session, config);
|
|
269
|
-
if (session.tasks.length === 0) {
|
|
270
|
-
throw new Error('Planning phase produced no tasks');
|
|
271
|
-
}
|
|
272
|
-
if (deliverMessage) {
|
|
273
|
-
const taskList = session.tasks
|
|
274
|
-
.map(t => ` ${t.id}: ${t.title}`)
|
|
275
|
-
.join('\n');
|
|
276
|
-
await deliverMessage(chatId, `📋 Plan created (${session.tasks.length} tasks):\n${taskList}\n\nStarting execution...`);
|
|
277
|
-
}
|
|
278
|
-
// Phase 2: Execution
|
|
279
|
-
session.status = 'executing';
|
|
280
|
-
appendEvent(session.id, { type: 'phase_changed', data: { phase: 'executing' } });
|
|
281
|
-
await executePhase(session, config);
|
|
282
|
-
// Check for failures
|
|
283
|
-
const failedTasks = session.tasks.filter(t => t.status === 'failed');
|
|
284
|
-
if (failedTasks.length > 0 && session.tasks.every(t => t.status === 'failed' || t.status === 'completed')) {
|
|
285
|
-
// Some failed but execution is over — continue to synthesis with partial results
|
|
286
|
-
if (deliverMessage) {
|
|
287
|
-
await deliverMessage(chatId, `⚠️ Execution complete with ${failedTasks.length} failure(s). Synthesizing results...`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
else if (deliverMessage) {
|
|
291
|
-
await deliverMessage(chatId, `✅ All tasks complete. Synthesizing results...`);
|
|
292
|
-
}
|
|
293
|
-
// Phase 3: Synthesis
|
|
294
|
-
session.status = 'synthesizing';
|
|
295
|
-
appendEvent(session.id, { type: 'phase_changed', data: { phase: 'synthesizing' } });
|
|
296
|
-
await synthesizePhase(session, config);
|
|
297
|
-
// Done
|
|
298
|
-
session.status = 'completed';
|
|
299
|
-
session.completedAt = new Date().toISOString();
|
|
300
|
-
appendEvent(session.id, {
|
|
301
|
-
type: 'session_completed',
|
|
302
|
-
data: { finalResult: session.finalResult },
|
|
303
|
-
});
|
|
304
|
-
if (deliverMessage) {
|
|
305
|
-
await deliverMessage(chatId, `🏁 Orchestration complete!\n\n${session.finalResult || '(no result)'}`);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
catch (err) {
|
|
309
|
-
session.status = 'failed';
|
|
310
|
-
session.error = err instanceof Error ? err.message : String(err);
|
|
311
|
-
session.completedAt = new Date().toISOString();
|
|
312
|
-
appendEvent(session.id, {
|
|
313
|
-
type: 'session_failed',
|
|
314
|
-
data: { error: session.error },
|
|
315
|
-
});
|
|
316
|
-
if (deliverMessage) {
|
|
317
|
-
await deliverMessage(chatId, `❌ Orchestration failed:\n\n${session.error}`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
// --- Phase 1: Planning ---
|
|
322
|
-
async function planPhase(session, config) {
|
|
323
|
-
console.log(`[orchestrator] Planning phase for ${session.id}`);
|
|
324
|
-
// Set module-level state so orchestrator tools can access the session
|
|
325
|
-
currentPlanningSession = session;
|
|
326
|
-
planningComplete = false;
|
|
327
|
-
try {
|
|
328
|
-
// Ensure coordinator agent exists
|
|
329
|
-
ensureAgentSetup('general', config);
|
|
330
|
-
const systemPrompt = buildSystemPrompt('general');
|
|
331
|
-
const planningPrompt = `You are an orchestration coordinator. Your job is to break down a complex task into subtasks that can be executed by specialized agents.
|
|
332
|
-
|
|
333
|
-
Available agent types:
|
|
334
|
-
- coding: For code writing, file manipulation, bash commands, and technical tasks
|
|
335
|
-
- research: For investigation, reading files, and information gathering
|
|
336
|
-
- general: For miscellaneous tasks
|
|
337
|
-
|
|
338
|
-
You have these tools:
|
|
339
|
-
- CreateTask: Create a subtask with title, description, agentType, and optional dependsOn (array of task IDs)
|
|
340
|
-
- FinishPlanning: Call when all tasks are created
|
|
341
|
-
- GetTaskStatus: Check current task list
|
|
342
|
-
|
|
343
|
-
Rules:
|
|
344
|
-
1. Break the task into 2-8 focused subtasks
|
|
345
|
-
2. Each task description should be self-contained (the worker won't see the original prompt)
|
|
346
|
-
3. Use dependsOn to specify ordering when one task needs results from another
|
|
347
|
-
4. Minimize dependencies to maximize parallel execution
|
|
348
|
-
5. Call FinishPlanning when done
|
|
349
|
-
|
|
350
|
-
Task to decompose:
|
|
351
|
-
${session.prompt}`;
|
|
352
|
-
const messages = [
|
|
353
|
-
{ role: 'system', content: systemPrompt },
|
|
354
|
-
{ role: 'user', content: planningPrompt },
|
|
355
|
-
];
|
|
356
|
-
const toolConfig = {
|
|
357
|
-
enabled: true,
|
|
358
|
-
allowedPaths: [join(homedir(), '.skimpyclaw')],
|
|
359
|
-
maxIterations: 30,
|
|
360
|
-
bashTimeout: 15000,
|
|
361
|
-
};
|
|
362
|
-
// Get base tools and append orchestrator tools
|
|
363
|
-
const baseDefs = await getToolDefinitions(toolConfig);
|
|
364
|
-
const allTools = [...baseDefs, ...ORCHESTRATOR_TOOLS];
|
|
365
|
-
const chatOptions = { model: session.model, thinking: 'medium' };
|
|
366
|
-
// Use chatWithTools directly, but we need to handle orchestrator tools ourselves.
|
|
367
|
-
// Instead, we call the Anthropic API loop manually via chatWithTools pattern.
|
|
368
|
-
// Since chatWithTools resolves tools internally via getToolDefinitions, we need
|
|
369
|
-
// a different approach: call chatWithTools but intercept orchestrator tool calls
|
|
370
|
-
// through the executeTool function in tools.ts.
|
|
371
|
-
//
|
|
372
|
-
// Simplest approach: replicate a minimal tool loop here for planning only.
|
|
373
|
-
await planningToolLoop(messages, chatOptions, config, toolConfig, allTools);
|
|
374
|
-
}
|
|
375
|
-
finally {
|
|
376
|
-
currentPlanningSession = null;
|
|
377
|
-
planningComplete = false;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Minimal Anthropic tool loop for the planning phase.
|
|
382
|
-
* Routes orchestrator tools to executeOrchestratorTool, others to executeTool.
|
|
383
|
-
*/
|
|
384
|
-
async function planningToolLoop(messages, chatOptions, config, toolConfig, toolDefs) {
|
|
385
|
-
// Dynamic import to avoid circular deps at module level
|
|
386
|
-
const Anthropic = (await import('@anthropic-ai/sdk')).default;
|
|
387
|
-
const { resolveModel, buildSystemParam } = await import('./agent.js');
|
|
388
|
-
const { executeTool } = await import('./tools.js');
|
|
389
|
-
let resolvedModel = resolveModel(chatOptions.model, config);
|
|
390
|
-
// planningToolLoop uses the Anthropic SDK directly, so it can only handle Claude models.
|
|
391
|
-
// If the current model is non-Anthropic (e.g. gpt-*, codex), fall back to Sonnet.
|
|
392
|
-
if (!resolvedModel.includes('claude')) {
|
|
393
|
-
const fallback = config.models?.aliases?.['claude-think'] || 'claude-sonnet-4-5-20250929';
|
|
394
|
-
console.log(`[orchestrator:plan] Non-Anthropic model "${chatOptions.model}" detected, falling back to "${fallback}" for planning`);
|
|
395
|
-
resolvedModel = resolveModel(fallback, config);
|
|
396
|
-
}
|
|
397
|
-
// Strip provider prefix
|
|
398
|
-
const slashIdx = resolvedModel.indexOf('/');
|
|
399
|
-
const modelId = slashIdx > 0 ? resolvedModel.slice(slashIdx + 1) : resolvedModel;
|
|
400
|
-
const maxIterations = toolConfig.maxIterations || 30;
|
|
401
|
-
const systemMessage = messages.find(m => m.role === 'system');
|
|
402
|
-
const systemParam = buildSystemParam(systemMessage?.content);
|
|
403
|
-
const apiMessages = messages
|
|
404
|
-
.filter(m => m.role !== 'system')
|
|
405
|
-
.map(m => ({ role: m.role, content: m.content }));
|
|
406
|
-
// We need the anthropic client — import from agent.ts won't expose it.
|
|
407
|
-
// Use the same approach as chatWithTools: instantiate via config.
|
|
408
|
-
// Actually, chatWithTools uses the module-level anthropicClient. We can't access it.
|
|
409
|
-
// Alternative: use chat() with tool_use by calling the Anthropic SDK directly.
|
|
410
|
-
// But we don't have the client. Let's use a workaround: create a temporary client
|
|
411
|
-
// using the same config.
|
|
412
|
-
const anthropicConfig = config.models.providers.anthropic;
|
|
413
|
-
if (!anthropicConfig?.apiKey && !anthropicConfig?.authToken) {
|
|
414
|
-
throw new Error('Anthropic provider not configured');
|
|
415
|
-
}
|
|
416
|
-
const opts = {};
|
|
417
|
-
if (anthropicConfig.authToken) {
|
|
418
|
-
opts.apiKey = null;
|
|
419
|
-
opts.authToken = anthropicConfig.authToken;
|
|
420
|
-
opts.defaultHeaders = {
|
|
421
|
-
'accept': 'application/json',
|
|
422
|
-
'anthropic-dangerous-direct-browser-access': 'true',
|
|
423
|
-
'anthropic-beta': 'claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14',
|
|
424
|
-
'user-agent': 'claude-cli/2.1.2 (external, cli)',
|
|
425
|
-
'x-app': 'cli',
|
|
426
|
-
};
|
|
427
|
-
opts.dangerouslyAllowBrowser = true;
|
|
428
|
-
}
|
|
429
|
-
else if (anthropicConfig.apiKey) {
|
|
430
|
-
opts.apiKey = anthropicConfig.apiKey;
|
|
431
|
-
}
|
|
432
|
-
const client = new Anthropic(opts);
|
|
433
|
-
const orchToolNames = new Set(ORCHESTRATOR_TOOLS.map(t => t.name));
|
|
434
|
-
for (let i = 0; i < maxIterations; i++) {
|
|
435
|
-
if (planningComplete) {
|
|
436
|
-
console.log('[orchestrator] Planning complete signal received');
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
const params = {
|
|
440
|
-
model: modelId,
|
|
441
|
-
max_tokens: 8192,
|
|
442
|
-
messages: apiMessages,
|
|
443
|
-
tools: toolDefs,
|
|
444
|
-
};
|
|
445
|
-
if (systemParam)
|
|
446
|
-
params.system = systemParam;
|
|
447
|
-
if (chatOptions.thinking && chatOptions.thinking !== 'none') {
|
|
448
|
-
const budgetTokens = { low: 2048, medium: 8192, high: 16384 };
|
|
449
|
-
const budget = budgetTokens[chatOptions.thinking] || 2048;
|
|
450
|
-
params.thinking = { type: 'enabled', budget_tokens: budget };
|
|
451
|
-
params.max_tokens = Math.max(params.max_tokens, budget + 4096);
|
|
452
|
-
}
|
|
453
|
-
console.log(`[orchestrator:plan] Iteration ${i + 1}/${maxIterations}`);
|
|
454
|
-
const response = await client.messages.create(params);
|
|
455
|
-
// If no tool use, planning is done
|
|
456
|
-
if (response.stop_reason !== 'tool_use') {
|
|
457
|
-
const textBlocks = response.content.filter((c) => c.type === 'text');
|
|
458
|
-
return textBlocks.map((b) => b.text).join('\n');
|
|
459
|
-
}
|
|
460
|
-
// Add assistant response
|
|
461
|
-
apiMessages.push({ role: 'assistant', content: response.content });
|
|
462
|
-
// Execute tools
|
|
463
|
-
const toolResults = [];
|
|
464
|
-
for (const block of response.content) {
|
|
465
|
-
if (block.type !== 'tool_use')
|
|
466
|
-
continue;
|
|
467
|
-
const inputStr = JSON.stringify(block.input).slice(0, 200);
|
|
468
|
-
console.log(`[orchestrator:plan] -> ${block.name}(${inputStr})`);
|
|
469
|
-
let result;
|
|
470
|
-
if (orchToolNames.has(block.name)) {
|
|
471
|
-
result = executeOrchestratorTool(block.name, block.input);
|
|
472
|
-
}
|
|
473
|
-
else {
|
|
474
|
-
result = await executeTool(block.name, block.input, toolConfig);
|
|
475
|
-
}
|
|
476
|
-
console.log(`[orchestrator:plan] <- ${result.slice(0, 200)}`);
|
|
477
|
-
toolResults.push({
|
|
478
|
-
type: 'tool_result',
|
|
479
|
-
tool_use_id: block.id,
|
|
480
|
-
content: result,
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
apiMessages.push({ role: 'user', content: toolResults });
|
|
484
|
-
}
|
|
485
|
-
return '';
|
|
486
|
-
}
|
|
487
|
-
// --- Phase 2: Execution ---
|
|
488
|
-
async function executePhase(session, config) {
|
|
489
|
-
console.log(`[orchestrator] Execution phase for ${session.id} (${session.tasks.length} tasks)`);
|
|
490
|
-
const activeWorkers = new Map();
|
|
491
|
-
while (true) {
|
|
492
|
-
// Check if all tasks are terminal
|
|
493
|
-
const allDone = session.tasks.every(t => t.status === 'completed' || t.status === 'failed');
|
|
494
|
-
if (allDone)
|
|
495
|
-
break;
|
|
496
|
-
// Check for deadlock: remaining tasks all have unresolvable dependencies
|
|
497
|
-
const pendingTasks = session.tasks.filter(t => t.status === 'pending');
|
|
498
|
-
const runningTasks = session.tasks.filter(t => t.status === 'running');
|
|
499
|
-
if (pendingTasks.length > 0 && runningTasks.length === 0 && activeWorkers.size === 0) {
|
|
500
|
-
// Check if any pending task can ever be unblocked
|
|
501
|
-
const canUnblock = pendingTasks.some(t => {
|
|
502
|
-
return t.dependsOn.every(depId => {
|
|
503
|
-
const dep = session.tasks.find(d => d.id === depId);
|
|
504
|
-
return dep && dep.status === 'completed';
|
|
505
|
-
});
|
|
506
|
-
});
|
|
507
|
-
if (!canUnblock) {
|
|
508
|
-
throw new Error(`Deadlock: ${pendingTasks.length} tasks blocked with no running workers. Failed dependencies may be preventing progress.`);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
// Find unblocked pending tasks
|
|
512
|
-
const unblocked = pendingTasks.filter(t => {
|
|
513
|
-
return t.dependsOn.every(depId => {
|
|
514
|
-
const dep = session.tasks.find(d => d.id === depId);
|
|
515
|
-
return dep && dep.status === 'completed';
|
|
516
|
-
});
|
|
517
|
-
});
|
|
518
|
-
// Assign unblocked tasks to workers (up to MAX_WORKERS)
|
|
519
|
-
for (const task of unblocked) {
|
|
520
|
-
if (activeWorkers.size >= MAX_WORKERS)
|
|
521
|
-
break;
|
|
522
|
-
// Create worker
|
|
523
|
-
orchWorkerCounter++;
|
|
524
|
-
const workerId = `w${orchWorkerCounter}`;
|
|
525
|
-
// Determine agent type from the task — stored during planning via CreateTask
|
|
526
|
-
// We need to figure out the agentType. The orchestrator tool stores it...
|
|
527
|
-
// Actually, OrchTask doesn't have an agentType field. We'll default to 'general'
|
|
528
|
-
// and look for hints in the task title/description.
|
|
529
|
-
const agentType = inferAgentType(task);
|
|
530
|
-
const worker = {
|
|
531
|
-
id: workerId,
|
|
532
|
-
sessionId: session.id,
|
|
533
|
-
agentType,
|
|
534
|
-
status: 'busy',
|
|
535
|
-
currentTaskId: task.id,
|
|
536
|
-
createdAt: new Date().toISOString(),
|
|
537
|
-
};
|
|
538
|
-
session.workers.push(worker);
|
|
539
|
-
task.status = 'running';
|
|
540
|
-
task.workerId = workerId;
|
|
541
|
-
task.startedAt = new Date().toISOString();
|
|
542
|
-
appendEvent(session.id, {
|
|
543
|
-
type: 'task_started',
|
|
544
|
-
data: { taskId: task.id, workerId },
|
|
545
|
-
});
|
|
546
|
-
console.log(`[orchestrator] Assigned ${task.id} ("${task.title}") to worker ${workerId} (${agentType})`);
|
|
547
|
-
// Launch worker
|
|
548
|
-
const workerPromise = runWorkerTask(session, task, worker, config)
|
|
549
|
-
.then(() => {
|
|
550
|
-
task.status = 'completed';
|
|
551
|
-
task.completedAt = new Date().toISOString();
|
|
552
|
-
worker.status = 'stopped';
|
|
553
|
-
appendEvent(session.id, {
|
|
554
|
-
type: 'task_completed',
|
|
555
|
-
data: { taskId: task.id, result: task.result },
|
|
556
|
-
});
|
|
557
|
-
console.log(`[orchestrator] Task ${task.id} completed by ${workerId}`);
|
|
558
|
-
if (deliverMessage) {
|
|
559
|
-
deliverMessage(session.chatId, `✅ Task ${task.id} ("${task.title}") completed`).catch(() => { });
|
|
560
|
-
}
|
|
561
|
-
})
|
|
562
|
-
.catch(err => {
|
|
563
|
-
task.status = 'failed';
|
|
564
|
-
task.error = err instanceof Error ? err.message : String(err);
|
|
565
|
-
task.completedAt = new Date().toISOString();
|
|
566
|
-
worker.status = 'stopped';
|
|
567
|
-
appendEvent(session.id, {
|
|
568
|
-
type: 'task_failed',
|
|
569
|
-
data: { taskId: task.id, error: task.error },
|
|
570
|
-
});
|
|
571
|
-
console.error(`[orchestrator] Task ${task.id} failed:`, task.error);
|
|
572
|
-
if (deliverMessage) {
|
|
573
|
-
deliverMessage(session.chatId, `❌ Task ${task.id} ("${task.title}") failed: ${task.error}`).catch(() => { });
|
|
574
|
-
}
|
|
575
|
-
})
|
|
576
|
-
.finally(() => {
|
|
577
|
-
activeWorkers.delete(workerId);
|
|
578
|
-
});
|
|
579
|
-
activeWorkers.set(workerId, workerPromise);
|
|
580
|
-
}
|
|
581
|
-
// Wait for at least one worker to finish, or poll timeout
|
|
582
|
-
if (activeWorkers.size > 0) {
|
|
583
|
-
await Promise.race([
|
|
584
|
-
...activeWorkers.values(),
|
|
585
|
-
new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS)),
|
|
586
|
-
]);
|
|
587
|
-
}
|
|
588
|
-
else {
|
|
589
|
-
// No workers running, nothing to assign — wait briefly
|
|
590
|
-
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Infer agent type from task metadata.
|
|
596
|
-
* The planning phase CreateTask stores agentType in the task description as a hint.
|
|
597
|
-
* We parse it or default to 'general'.
|
|
598
|
-
*/
|
|
599
|
-
function inferAgentType(task) {
|
|
600
|
-
const desc = (task.title + ' ' + task.description).toLowerCase();
|
|
601
|
-
if (desc.includes('[coding]') || desc.includes('write code') || desc.includes('implement') || desc.includes('create file')) {
|
|
602
|
-
return 'coding';
|
|
603
|
-
}
|
|
604
|
-
if (desc.includes('[research]') || desc.includes('research') || desc.includes('investigate') || desc.includes('find out')) {
|
|
605
|
-
return 'research';
|
|
606
|
-
}
|
|
607
|
-
return 'general';
|
|
608
|
-
}
|
|
609
|
-
// --- Worker Execution ---
|
|
610
|
-
async function runWorkerTask(session, task, worker, config) {
|
|
611
|
-
// Build context from completed dependency tasks
|
|
612
|
-
const depContext = task.dependsOn
|
|
613
|
-
.map(id => session.tasks.find(t => t.id === id))
|
|
614
|
-
.filter((t) => !!t && !!t.result)
|
|
615
|
-
.map(t => `## Output from "${t.title}":\n${t.result}`)
|
|
616
|
-
.join('\n\n');
|
|
617
|
-
const prompt = depContext
|
|
618
|
-
? `${task.description}\n\n---\nContext from completed prerequisite tasks:\n${depContext}`
|
|
619
|
-
: task.description;
|
|
620
|
-
const toolConfig = {
|
|
621
|
-
enabled: true,
|
|
622
|
-
allowedPaths: [join(homedir(), '.skimpyclaw')],
|
|
623
|
-
maxIterations: 100,
|
|
624
|
-
bashTimeout: 30000,
|
|
625
|
-
};
|
|
626
|
-
// Ensure agent directory exists
|
|
627
|
-
ensureAgentSetup(worker.agentType, config);
|
|
628
|
-
const result = await runAgentTurn(worker.agentType, prompt, config, session.model, toolConfig, undefined, {
|
|
629
|
-
channel: 'orchestrator',
|
|
630
|
-
sessionId: session.id,
|
|
631
|
-
metadata: { taskId: task.id, workerId: worker.id },
|
|
632
|
-
});
|
|
633
|
-
task.result = result;
|
|
634
|
-
}
|
|
635
|
-
// --- Phase 3: Synthesis ---
|
|
636
|
-
async function synthesizePhase(session, config) {
|
|
637
|
-
console.log(`[orchestrator] Synthesis phase for ${session.id}`);
|
|
638
|
-
// Build a summary of all task results
|
|
639
|
-
const taskSummaries = session.tasks.map(t => {
|
|
640
|
-
const status = t.status === 'completed' ? '✅' : '❌';
|
|
641
|
-
const output = t.status === 'completed' ? t.result : `Error: ${t.error}`;
|
|
642
|
-
return `## ${status} Task ${t.id}: ${t.title}\n\n${output || '(no output)'}`;
|
|
643
|
-
});
|
|
644
|
-
const synthesisPrompt = `You are synthesizing the results of a multi-task orchestration.
|
|
645
|
-
|
|
646
|
-
Original request:
|
|
647
|
-
${session.prompt}
|
|
648
|
-
|
|
649
|
-
---
|
|
650
|
-
|
|
651
|
-
Task results:
|
|
652
|
-
|
|
653
|
-
${taskSummaries.join('\n\n---\n\n')}
|
|
654
|
-
|
|
655
|
-
---
|
|
656
|
-
|
|
657
|
-
Provide a clear, comprehensive response to the original request based on all task results above. If any tasks failed, note what couldn't be completed and why.`;
|
|
658
|
-
// Ensure agent exists
|
|
659
|
-
ensureAgentSetup('general', config);
|
|
660
|
-
const result = await runAgentTurn('general', synthesisPrompt, config, session.model, undefined, // No tools needed for synthesis
|
|
661
|
-
undefined, {
|
|
662
|
-
channel: 'orchestrator',
|
|
663
|
-
sessionId: session.id,
|
|
664
|
-
metadata: { phase: 'synthesis' },
|
|
665
|
-
});
|
|
666
|
-
session.finalResult = result;
|
|
667
|
-
}
|
|
668
|
-
// --- Testing ---
|
|
669
|
-
export function resetForTesting() {
|
|
670
|
-
activeSessions.clear();
|
|
671
|
-
currentPlanningSession = null;
|
|
672
|
-
deliverMessage = null;
|
|
673
|
-
orchTaskCounter = 0;
|
|
674
|
-
orchWorkerCounter = 0;
|
|
675
|
-
planningComplete = false;
|
|
676
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
|
-
import type { ProviderChatParams, ProviderToolChatParams, ToolChatResult } from './types.js';
|
|
3
|
-
export declare function addOpenAIClient(name: string, client: OpenAI): void;
|
|
4
|
-
export declare function getOpenAIClient(name: string): OpenAI | undefined;
|
|
5
|
-
export declare function hasOpenAIClient(name: string): boolean;
|
|
6
|
-
export declare function clearOpenAIClients(): void;
|
|
7
|
-
export declare function resetOpenAIProviderState(): void;
|
|
8
|
-
export declare function isOpenAIAvailable(provider: string): boolean;
|
|
9
|
-
export declare function chatOpenAI(params: ProviderChatParams, provider: string): Promise<string>;
|
|
10
|
-
export declare function chatWithToolsOpenAI(params: ProviderToolChatParams, provider: string): Promise<ToolChatResult>;
|