@lightcone-ai/daemon 0.9.78 → 0.10.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/mcp-servers/mysql/index.js +13 -5
- package/mcp-servers/mysql/manifest.json +16 -0
- package/mcp-servers/official/company-fundamentals/index.js +34 -0
- package/mcp-servers/official/company-fundamentals/manifest.json +14 -0
- package/mcp-servers/official/compliance-check/index.js +49 -0
- package/mcp-servers/official/compliance-check/manifest.json +14 -0
- package/mcp-servers/official/industry-report/index.js +34 -0
- package/mcp-servers/official/industry-report/manifest.json +14 -0
- package/mcp-servers/official/market-data-query/index.js +34 -0
- package/mcp-servers/official/market-data-query/manifest.json +14 -0
- package/mcp-servers/official/portfolio-analysis/index.js +74 -0
- package/mcp-servers/official/portfolio-analysis/manifest.json +14 -0
- package/mcp-servers/official/portfolio-read/index.js +34 -0
- package/mcp-servers/official/portfolio-read/manifest.json +14 -0
- package/mcp-servers/official/research-fetch/index.js +35 -0
- package/mcp-servers/official/research-fetch/manifest.json +14 -0
- package/mcp-servers/official/risk-metrics/index.js +34 -0
- package/mcp-servers/official/risk-metrics/manifest.json +14 -0
- package/mcp-servers/official-common/fixtures.js +273 -0
- package/mcp-servers/official-common/server.js +34 -0
- package/mcp-servers/platform/manifest.json +15 -0
- package/mcp-servers/portfolio-analysis/core.js +592 -0
- package/mcp-servers/portfolio-analysis/index.js +45 -0
- package/mcp-servers/portfolio-analysis/package-lock.json +1139 -0
- package/mcp-servers/portfolio-analysis/package.json +10 -0
- package/mcp-servers/portfolio-read/core.js +330 -0
- package/mcp-servers/portfolio-read/index.js +127 -0
- package/mcp-servers/portfolio-read/package-lock.json +1243 -0
- package/mcp-servers/portfolio-read/package.json +11 -0
- package/mcp-servers/publisher/adapters/douyin.js +112 -20
- package/mcp-servers/publisher/index.js +84 -8
- package/mcp-servers/publisher/manifest.json +16 -0
- package/package.json +1 -1
- package/src/agent-manager.js +761 -187
- package/src/chat-bridge.js +567 -92
- package/src/connection.js +1 -1
- package/src/drivers/claude.js +48 -45
- package/src/drivers/codex.js +110 -7
- package/src/drivers/kimi.js +80 -34
- package/src/governance-state.js +89 -0
- package/src/index.js +34 -16
- package/src/lease-window.js +8 -0
- package/src/mcp-config.js +52 -21
package/src/agent-manager.js
CHANGED
|
@@ -1,34 +1,67 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
-
import { mkdirSync,
|
|
2
|
+
import { mkdirSync, writeFileSync, readdirSync, unlinkSync } from 'fs';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { buildKimiSpawn, buildKimiInitMessages, parseKimiLine, encodeKimiStdin } from './drivers/kimi.js';
|
|
5
|
+
import { parseCodexLine, adaptCodexSystemPrompt } from './drivers/codex.js';
|
|
6
|
+
import { parseKimiLine, encodeKimiStdin } from './drivers/kimi.js';
|
|
8
7
|
import { startSession, stopSession, stopAllSessions } from './browser-login.js';
|
|
9
|
-
import {
|
|
8
|
+
import { markInvalidatedLeases } from './governance-state.js';
|
|
9
|
+
|
|
10
|
+
const KIMI_SYSTEM_PROMPT_FILE = '.lightcone-kimi-system.md';
|
|
11
|
+
const KIMI_AGENT_FILE = '.lightcone-kimi-agent.yaml';
|
|
12
|
+
const KIMI_MCP_FILE = '.lightcone-kimi-mcp.json';
|
|
13
|
+
|
|
14
|
+
function runtimeMissingDetail(runtime) {
|
|
15
|
+
return `cli_missing:${runtime}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolveExitOfflineDetail({ code, signal, stopCause }) {
|
|
19
|
+
if (stopCause === 'manual_stop') return '';
|
|
20
|
+
if (stopCause === 'credential_revoked') return 'credential_revoked';
|
|
21
|
+
if (code === 0) return '';
|
|
22
|
+
if (signal === 'SIGTERM') return '';
|
|
23
|
+
if (signal === 'SIGKILL') return 'agent_timeout';
|
|
24
|
+
return 'spawn_session_crashed';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeObject(value) {
|
|
28
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function replacePlaceholders(text, replacements) {
|
|
32
|
+
if (typeof text !== 'string') return text;
|
|
33
|
+
let next = text;
|
|
34
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
35
|
+
next = next.split(token).join(value);
|
|
36
|
+
}
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
10
39
|
|
|
11
40
|
export class AgentManager {
|
|
12
41
|
constructor({ serverUrl, machineApiKey }) {
|
|
13
42
|
this.serverUrl = serverUrl;
|
|
14
43
|
this.machineApiKey = machineApiKey;
|
|
15
|
-
// key: `
|
|
44
|
+
// key: `workspaceId:agentId` → { config, workspaceId, agentId, sessionId, proc }
|
|
16
45
|
this.agents = new Map();
|
|
17
46
|
// key → true (spawn in progress)
|
|
18
47
|
this.starting = new Set();
|
|
48
|
+
this.directiveRefreshTimers = new Map();
|
|
49
|
+
this.directiveGovernanceCache = new Map();
|
|
19
50
|
}
|
|
20
51
|
|
|
21
|
-
_key(agentId,
|
|
22
|
-
return `${
|
|
52
|
+
_key(agentId, workspaceId) {
|
|
53
|
+
return `${workspaceId ?? ''}:${agentId}`;
|
|
23
54
|
}
|
|
24
55
|
|
|
25
56
|
handle(msg, connection) {
|
|
26
57
|
switch (msg.type) {
|
|
27
58
|
case 'agent:start': return this._startAgent(msg, connection);
|
|
28
|
-
case 'agent:stop': return this._stopAgent(msg.agentId, msg.
|
|
59
|
+
case 'agent:stop': return this._stopAgent(msg.agentId, msg.workspaceId, connection);
|
|
29
60
|
case 'agent:deliver': return this._deliverMessage(msg, connection);
|
|
30
61
|
case 'browser:start_login': return this._startBrowserLogin(msg, connection);
|
|
31
62
|
case 'browser:stop_login': return this._stopBrowserLogin(msg);
|
|
63
|
+
case 'policy_invalidate': return this._handlePolicyInvalidate(msg, connection);
|
|
64
|
+
case 'credential_revoked': return this._handleCredentialRevoked(msg, connection);
|
|
32
65
|
case 'ping': return connection.send({ type: 'pong' });
|
|
33
66
|
default:
|
|
34
67
|
console.log(`[AgentManager] Unhandled: ${msg.type}`);
|
|
@@ -36,6 +69,10 @@ export class AgentManager {
|
|
|
36
69
|
}
|
|
37
70
|
|
|
38
71
|
async stopAll() {
|
|
72
|
+
for (const timer of this.directiveRefreshTimers.values()) {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
}
|
|
75
|
+
this.directiveRefreshTimers.clear();
|
|
39
76
|
for (const [, agent] of this.agents) {
|
|
40
77
|
if (agent.proc) agent.proc.kill();
|
|
41
78
|
}
|
|
@@ -45,16 +82,16 @@ export class AgentManager {
|
|
|
45
82
|
|
|
46
83
|
// ── private ───────────────────────────────────────────────────────────────
|
|
47
84
|
|
|
48
|
-
|
|
49
|
-
const dir = path.join(homedir(), '.lightcone', 'workspace',
|
|
85
|
+
_workspaceRootDir(workspaceId) {
|
|
86
|
+
const dir = path.join(homedir(), '.lightcone', 'workspace', workspaceId ?? '_global');
|
|
50
87
|
mkdirSync(path.join(dir, 'artifacts'), { recursive: true });
|
|
51
88
|
mkdirSync(path.join(dir, 'notes'), { recursive: true });
|
|
52
89
|
mkdirSync(path.join(dir, 'tmp'), { recursive: true });
|
|
53
90
|
return dir;
|
|
54
91
|
}
|
|
55
92
|
|
|
56
|
-
_workspaceDir(agentId,
|
|
57
|
-
const dir = path.join(homedir(), '.lightcone', 'workspace',
|
|
93
|
+
_workspaceDir(agentId, workspaceId) {
|
|
94
|
+
const dir = path.join(homedir(), '.lightcone', 'workspace', workspaceId ?? '_global', agentId);
|
|
58
95
|
mkdirSync(dir, { recursive: true });
|
|
59
96
|
mkdirSync(path.join(dir, 'tmp'), { recursive: true });
|
|
60
97
|
return dir;
|
|
@@ -78,7 +115,7 @@ export class AgentManager {
|
|
|
78
115
|
}
|
|
79
116
|
|
|
80
117
|
_formatDeliveryText(message) {
|
|
81
|
-
return `New message in ${message.
|
|
118
|
+
return `New message in ${message.workspace_type === 'dm' ? 'dm from' : `#${message.workspace_name} from`} ${message.sender_name}: ${message.content}`;
|
|
82
119
|
}
|
|
83
120
|
|
|
84
121
|
_takePendingMessage(key) {
|
|
@@ -90,81 +127,597 @@ export class AgentManager {
|
|
|
90
127
|
return msg;
|
|
91
128
|
}
|
|
92
129
|
|
|
93
|
-
|
|
94
|
-
|
|
130
|
+
_prepareStartupMessage(key, runtime) {
|
|
131
|
+
if (runtime === 'codex') {
|
|
132
|
+
const startupMsg = this._takePendingMessage(key);
|
|
133
|
+
return { startupMsg, directiveMsg: startupMsg };
|
|
134
|
+
}
|
|
135
|
+
const pending = this._pendingMessages?.get(key);
|
|
136
|
+
return {
|
|
137
|
+
startupMsg: null,
|
|
138
|
+
directiveMsg: pending?.[0] ?? null,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_resolveDirectiveArgPath(arg, { chatBridgePath }) {
|
|
143
|
+
if (typeof arg !== 'string') return arg;
|
|
144
|
+
const normalized = arg.replaceAll('\\', '/');
|
|
145
|
+
if (normalized.includes('chat-bridge.js')) {
|
|
146
|
+
return chatBridgePath;
|
|
147
|
+
}
|
|
148
|
+
if (normalized.includes('/mcp-servers/publisher/index.js')) {
|
|
149
|
+
return new URL('../mcp-servers/publisher/index.js', import.meta.url).pathname;
|
|
150
|
+
}
|
|
151
|
+
if (normalized.includes('/mcp-servers/workspace-migrate/index.js')) {
|
|
152
|
+
return new URL('../../mcp-servers/workspace-migrate/index.js', import.meta.url).pathname;
|
|
153
|
+
}
|
|
154
|
+
if (normalized.includes('/mcp-servers/mysql/index.js')) {
|
|
155
|
+
return new URL('../../mcp-servers/mysql/index.js', import.meta.url).pathname;
|
|
156
|
+
}
|
|
157
|
+
if (normalized.includes('/mcp-servers/platform/index.js')) {
|
|
158
|
+
return new URL('../../mcp-servers/platform/index.js', import.meta.url).pathname;
|
|
159
|
+
}
|
|
160
|
+
return arg;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_normalizeDirectiveForLocalSpawn(directive, {
|
|
164
|
+
agentId,
|
|
165
|
+
workspaceId,
|
|
166
|
+
workspaceDir,
|
|
167
|
+
chatBridgePath,
|
|
168
|
+
config,
|
|
169
|
+
}) {
|
|
170
|
+
const authToken = config?.authToken || this.machineApiKey;
|
|
171
|
+
const userId = config?.userId ?? 'default';
|
|
172
|
+
const profileRoot = path.join(homedir(), '.lightcone', 'chrome-profiles');
|
|
173
|
+
const replacements = {
|
|
174
|
+
'${SERVER_URL}': this.serverUrl,
|
|
175
|
+
'${MACHINE_API_KEY}': authToken,
|
|
176
|
+
'${AGENT_ID}': agentId,
|
|
177
|
+
'${WORKSPACE_ID}': workspaceId ?? '',
|
|
178
|
+
'${WORKSPACE_DIR}': workspaceDir,
|
|
179
|
+
'${XHS_PROFILE_DIR}': path.join(profileRoot, `xhs-${userId}`),
|
|
180
|
+
'${DOUYIN_PROFILE_DIR}': path.join(profileRoot, `douyin-${userId}`),
|
|
181
|
+
'${KUAISHOU_PROFILE_DIR}': path.join(profileRoot, `kuaishou-${userId}`),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const normalized = this._replaceDirectiveValue(normalizeObject(directive), replacements);
|
|
185
|
+
const mcpServers = normalizeObject(normalized.mcp_servers);
|
|
186
|
+
const governanceLease = normalizeObject(normalized.policy_lease);
|
|
187
|
+
const leaseValidUntil = Date.parse(governanceLease.valid_until ?? '');
|
|
188
|
+
const leaseState = Number.isFinite(leaseValidUntil) && leaseValidUntil > Date.now() ? 'valid' : 'expired';
|
|
189
|
+
const governanceEnv = normalized.spawn_bundle_id || normalized.policy_version || normalized.directive_id
|
|
190
|
+
? {
|
|
191
|
+
LC_GOVERNANCE_DIRECTIVE_ID: normalized.directive_id ?? '',
|
|
192
|
+
LC_GOVERNANCE_SPAWN_BUNDLE_ID: normalized.spawn_bundle_id ?? '',
|
|
193
|
+
LC_GOVERNANCE_POLICY_VERSION: normalized.policy_version ?? '',
|
|
194
|
+
LC_GOVERNANCE_POLICY_LEASE_ID: governanceLease.lease_id ?? '',
|
|
195
|
+
LC_GOVERNANCE_POLICY_LEASE_STATE: leaseState,
|
|
196
|
+
LC_GOVERNANCE_POLICY_LEASE_JSON: JSON.stringify(governanceLease),
|
|
197
|
+
LC_GOVERNANCE_MCP_CLASSIFICATION_JSON: JSON.stringify(normalizeObject(normalized.mcp_classification)),
|
|
198
|
+
LC_GOVERNANCE_WORKSPACE_ID: workspaceId ?? '',
|
|
199
|
+
}
|
|
200
|
+
: {};
|
|
201
|
+
|
|
202
|
+
for (const [serverKey, rawServer] of Object.entries(mcpServers)) {
|
|
203
|
+
const server = normalizeObject(rawServer);
|
|
204
|
+
const args = Array.isArray(server.args) ? server.args.map((arg) => this._resolveDirectiveArgPath(String(arg), { chatBridgePath })) : [];
|
|
205
|
+
const env = Object.fromEntries(Object.entries(normalizeObject(server.env)).map(([k, v]) => [k, String(v ?? '')]));
|
|
206
|
+
const baseEnv = {};
|
|
207
|
+
if (serverKey === 'chat' || serverKey === 'publisher' || serverKey === 'platform') {
|
|
208
|
+
baseEnv.SERVER_URL = this.serverUrl;
|
|
209
|
+
baseEnv.MACHINE_API_KEY = authToken;
|
|
210
|
+
baseEnv.AGENT_ID = agentId;
|
|
211
|
+
baseEnv.WORKSPACE_ID = workspaceId ?? '';
|
|
212
|
+
baseEnv.WORKSPACE_DIR = workspaceDir;
|
|
213
|
+
} else if (serverKey === 'workspace-migrate') {
|
|
214
|
+
baseEnv.SERVER_URL = this.serverUrl;
|
|
215
|
+
baseEnv.MACHINE_API_KEY = authToken;
|
|
216
|
+
baseEnv.AGENT_ID = agentId;
|
|
217
|
+
}
|
|
218
|
+
mcpServers[serverKey] = {
|
|
219
|
+
...server,
|
|
220
|
+
args,
|
|
221
|
+
env: {
|
|
222
|
+
...env,
|
|
223
|
+
...baseEnv,
|
|
224
|
+
...(serverKey === 'chat' ? governanceEnv : {}),
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
normalized.mcp_servers = mcpServers;
|
|
230
|
+
return normalized;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_cacheDirectiveGovernanceContract(key, normalizedDirective, workspaceId) {
|
|
234
|
+
const lease = normalizeObject(normalizedDirective?.policy_lease);
|
|
235
|
+
const cached = {
|
|
236
|
+
directiveId: normalizedDirective?.directive_id ?? null,
|
|
237
|
+
spawnBundleId: normalizedDirective?.spawn_bundle_id ?? null,
|
|
238
|
+
policyVersion: normalizedDirective?.policy_version ?? null,
|
|
239
|
+
leaseId: lease.lease_id ?? null,
|
|
240
|
+
workspaceId: workspaceId ?? null,
|
|
241
|
+
updatedAt: new Date().toISOString(),
|
|
242
|
+
};
|
|
243
|
+
this.directiveGovernanceCache.set(key, cached);
|
|
244
|
+
return cached;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_replaceDirectiveValue(value, replacements) {
|
|
248
|
+
if (typeof value === 'string') return replacePlaceholders(value, replacements);
|
|
249
|
+
if (Array.isArray(value)) return value.map((item) => this._replaceDirectiveValue(item, replacements));
|
|
250
|
+
if (!value || typeof value !== 'object') return value;
|
|
251
|
+
const next = {};
|
|
252
|
+
for (const [key, itemValue] of Object.entries(value)) {
|
|
253
|
+
next[key] = this._replaceDirectiveValue(itemValue, replacements);
|
|
254
|
+
}
|
|
255
|
+
return next;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
_resolveDirectiveMcpServers(directive, replacements) {
|
|
259
|
+
const resolved = this._replaceDirectiveValue(normalizeObject(directive?.mcp_servers), replacements);
|
|
260
|
+
const mcpServers = {};
|
|
261
|
+
for (const [serverKey, rawServer] of Object.entries(resolved)) {
|
|
262
|
+
const server = normalizeObject(rawServer);
|
|
263
|
+
const command = typeof server.command === 'string' ? server.command.trim() : '';
|
|
264
|
+
if (!command) continue;
|
|
265
|
+
mcpServers[serverKey] = {
|
|
266
|
+
...server,
|
|
267
|
+
command,
|
|
268
|
+
args: Array.isArray(server.args) ? server.args.map((item) => String(item)) : [],
|
|
269
|
+
env: Object.fromEntries(
|
|
270
|
+
Object.entries(normalizeObject(server.env)).map(([k, v]) => [k, String(v ?? '')])
|
|
271
|
+
),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return mcpServers;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
_buildCodexMcpArgs(mcpServers) {
|
|
278
|
+
const args = [];
|
|
279
|
+
for (const [serverKey, server] of Object.entries(mcpServers)) {
|
|
280
|
+
const serverArgs = Array.isArray(server.args) ? server.args.map((item) => String(item)) : [];
|
|
281
|
+
const envVars = normalizeObject(server.env);
|
|
282
|
+
const envPairs = Object.entries(envVars).map(([k, v]) => `${k}=${String(v ?? '')}`);
|
|
283
|
+
const keyExpr = JSON.stringify(serverKey);
|
|
284
|
+
const commandExpr = JSON.stringify(envPairs.length > 0 ? 'env' : server.command);
|
|
285
|
+
const argsExpr = JSON.stringify(envPairs.length > 0 ? [...envPairs, server.command, ...serverArgs] : serverArgs);
|
|
286
|
+
args.push(
|
|
287
|
+
'-c', `mcp_servers.${keyExpr}.command=${commandExpr}`,
|
|
288
|
+
'-c', `mcp_servers.${keyExpr}.args=${argsExpr}`,
|
|
289
|
+
'-c', `mcp_servers.${keyExpr}.enabled=true`,
|
|
290
|
+
);
|
|
291
|
+
if (server.required === true) {
|
|
292
|
+
args.push('-c', `mcp_servers.${keyExpr}.required=true`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return args;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
_createKimiConfigFiles(workspaceDir, { systemPrompt, mcpServers }) {
|
|
299
|
+
const systemPromptPath = path.join(workspaceDir, KIMI_SYSTEM_PROMPT_FILE);
|
|
300
|
+
const agentFilePath = path.join(workspaceDir, KIMI_AGENT_FILE);
|
|
301
|
+
const mcpConfigPath = path.join(workspaceDir, KIMI_MCP_FILE);
|
|
302
|
+
|
|
303
|
+
writeFileSync(systemPromptPath, systemPrompt ?? '', 'utf8');
|
|
304
|
+
writeFileSync(agentFilePath, [
|
|
305
|
+
'version: 1',
|
|
306
|
+
'agent:',
|
|
307
|
+
' extend: default',
|
|
308
|
+
` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
|
|
309
|
+
'',
|
|
310
|
+
].join('\n'), 'utf8');
|
|
311
|
+
writeFileSync(mcpConfigPath, JSON.stringify({ mcpServers }), 'utf8');
|
|
312
|
+
|
|
313
|
+
return { systemPromptPath, agentFilePath, mcpConfigPath };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
_buildDirectiveSpawnPlan({
|
|
317
|
+
directive,
|
|
318
|
+
runtime,
|
|
319
|
+
workspaceDir,
|
|
320
|
+
chatBridgePath,
|
|
321
|
+
agentId,
|
|
322
|
+
workspaceId,
|
|
323
|
+
startupMsg,
|
|
324
|
+
}) {
|
|
325
|
+
const systemPrompt = typeof directive?.system_prompt === 'string'
|
|
326
|
+
? directive.system_prompt
|
|
327
|
+
: '';
|
|
328
|
+
const codexBasePrompt = adaptCodexSystemPrompt(systemPrompt);
|
|
329
|
+
const codexPrompt = startupMsg
|
|
330
|
+
? `${codexBasePrompt}\n\nNew message received:\n\n${this._formatDeliveryText(startupMsg.message)}\n\nRespond as appropriate. Complete all required work before stopping.`
|
|
331
|
+
: codexBasePrompt;
|
|
332
|
+
|
|
333
|
+
const baseReplacements = {
|
|
334
|
+
'__CHAT_BRIDGE_PATH__': chatBridgePath,
|
|
335
|
+
'__WORKSPACE_DIR__': workspaceDir,
|
|
336
|
+
'__SERVER_URL__': this.serverUrl,
|
|
337
|
+
'__MACHINE_API_KEY__': this.machineApiKey,
|
|
338
|
+
'__AGENT_ID__': agentId,
|
|
339
|
+
'__WORKSPACE_ID__': workspaceId ?? '',
|
|
340
|
+
};
|
|
341
|
+
const mcpServers = this._resolveDirectiveMcpServers(directive, baseReplacements);
|
|
342
|
+
|
|
343
|
+
let kimiFiles = null;
|
|
344
|
+
if (runtime === 'kimi') {
|
|
345
|
+
kimiFiles = this._createKimiConfigFiles(workspaceDir, {
|
|
346
|
+
systemPrompt,
|
|
347
|
+
mcpServers,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const replacements = {
|
|
352
|
+
...baseReplacements,
|
|
353
|
+
'__SYSTEM_PROMPT__': runtime === 'codex' ? codexPrompt : systemPrompt,
|
|
354
|
+
'__MCP_CONFIG_JSON__': JSON.stringify({ mcpServers }),
|
|
355
|
+
'__KIMI_AGENT_FILE__': kimiFiles?.agentFilePath ?? '',
|
|
356
|
+
'__KIMI_MCP_FILE__': kimiFiles?.mcpConfigPath ?? '',
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const rawArgs = Array.isArray(directive?.spawn_args) ? directive.spawn_args : [];
|
|
360
|
+
const args = rawArgs.map((item) => String(this._replaceDirectiveValue(item, replacements)));
|
|
361
|
+
|
|
362
|
+
if (runtime === 'claude') {
|
|
363
|
+
if (!args.includes('--mcp-config')) {
|
|
364
|
+
args.push('--mcp-config', replacements.__MCP_CONFIG_JSON__);
|
|
365
|
+
}
|
|
366
|
+
if (!args.includes('--system-prompt') && systemPrompt.trim()) {
|
|
367
|
+
args.push('--system-prompt', systemPrompt);
|
|
368
|
+
}
|
|
369
|
+
} else if (runtime === 'codex') {
|
|
370
|
+
if (!args.some((arg) => arg.includes('mcp_servers.')) && Object.keys(mcpServers).length > 0) {
|
|
371
|
+
args.push(...this._buildCodexMcpArgs(mcpServers));
|
|
372
|
+
}
|
|
373
|
+
const hasPromptPlaceholder = rawArgs.some((item) => String(item).includes('__SYSTEM_PROMPT__'));
|
|
374
|
+
if (!hasPromptPlaceholder && codexPrompt.trim()) {
|
|
375
|
+
args.push(codexPrompt);
|
|
376
|
+
}
|
|
377
|
+
} else if (runtime === 'kimi') {
|
|
378
|
+
if (!args.includes('--agent-file') && replacements.__KIMI_AGENT_FILE__) {
|
|
379
|
+
args.push('--agent-file', replacements.__KIMI_AGENT_FILE__);
|
|
380
|
+
}
|
|
381
|
+
if (!args.includes('--mcp-config-file') && replacements.__KIMI_MCP_FILE__) {
|
|
382
|
+
args.push('--mcp-config-file', replacements.__KIMI_MCP_FILE__);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const env = {
|
|
387
|
+
...process.env,
|
|
388
|
+
FORCE_COLOR: '0',
|
|
389
|
+
NO_COLOR: '1',
|
|
390
|
+
...Object.fromEntries(
|
|
391
|
+
Object.entries(normalizeObject(directive?.env_vars)).map(([k, v]) => [k, String(v ?? '')])
|
|
392
|
+
),
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
if (runtime === 'claude') {
|
|
396
|
+
delete env.CLAUDECODE;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const sessionArgIndex = args.indexOf('--session');
|
|
400
|
+
const kimiSessionId = runtime === 'kimi' && sessionArgIndex !== -1 ? args[sessionArgIndex + 1] ?? null : null;
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
command: runtime,
|
|
404
|
+
args,
|
|
405
|
+
env,
|
|
406
|
+
kimiSessionId,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async _fetchSpawnDirective({
|
|
411
|
+
agentId,
|
|
412
|
+
workspaceId,
|
|
413
|
+
runtime,
|
|
414
|
+
workspaceDir,
|
|
415
|
+
chatBridgePath,
|
|
416
|
+
config,
|
|
417
|
+
triggerType = 'resume',
|
|
418
|
+
}) {
|
|
419
|
+
const res = await fetch(`${this.serverUrl}/governance/spawn-directive`, {
|
|
420
|
+
method: 'POST',
|
|
421
|
+
headers: {
|
|
422
|
+
'Content-Type': 'application/json',
|
|
423
|
+
'Authorization': `Bearer ${this.machineApiKey}`,
|
|
424
|
+
},
|
|
425
|
+
body: JSON.stringify({
|
|
426
|
+
agent_id: agentId,
|
|
427
|
+
workspace_id: workspaceId ?? null,
|
|
428
|
+
trigger: {
|
|
429
|
+
type: triggerType,
|
|
430
|
+
context: { source: 'daemon-agent-manager' },
|
|
431
|
+
},
|
|
432
|
+
runtime_context: {
|
|
433
|
+
cli_type: runtime,
|
|
434
|
+
session_id: config?.sessionId ?? null,
|
|
435
|
+
workspace_dir: workspaceDir,
|
|
436
|
+
chat_bridge_path: chatBridgePath,
|
|
437
|
+
},
|
|
438
|
+
agent_profile: {
|
|
439
|
+
name: config?.name ?? null,
|
|
440
|
+
display_name: config?.displayName ?? null,
|
|
441
|
+
description: config?.description ?? null,
|
|
442
|
+
role_prompt: config?.rolePrompt ?? null,
|
|
443
|
+
feishu_bot_name: config?.feishuBotName ?? null,
|
|
444
|
+
},
|
|
445
|
+
}),
|
|
446
|
+
});
|
|
447
|
+
if (!res.ok) {
|
|
448
|
+
const text = await res.text();
|
|
449
|
+
throw new Error(`spawn_directive_failed:${res.status}:${text.slice(0, 300)}`);
|
|
450
|
+
}
|
|
451
|
+
const directive = await res.json();
|
|
452
|
+
if (!directive || typeof directive !== 'object') {
|
|
453
|
+
throw new Error('spawn_directive_invalid_response');
|
|
454
|
+
}
|
|
455
|
+
return directive;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
_clearDirectiveRefresh(key) {
|
|
459
|
+
const timer = this.directiveRefreshTimers.get(key);
|
|
460
|
+
if (timer) clearTimeout(timer);
|
|
461
|
+
this.directiveRefreshTimers.delete(key);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
_scheduleDirectiveRefresh(key, context, directive) {
|
|
465
|
+
this._clearDirectiveRefresh(key);
|
|
466
|
+
if (!directive || typeof directive !== 'object') return;
|
|
467
|
+
|
|
468
|
+
let ttlMs = Number(directive.lease_ttl_ms ?? NaN);
|
|
469
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
470
|
+
const validUntilMs = Date.parse(directive?.policy_lease?.valid_until ?? '');
|
|
471
|
+
if (Number.isFinite(validUntilMs)) {
|
|
472
|
+
ttlMs = validUntilMs - Date.now();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) return;
|
|
476
|
+
|
|
477
|
+
let refreshInMs = Math.max(250, ttlMs - 2000);
|
|
478
|
+
if (ttlMs <= 250) {
|
|
479
|
+
// Keep refresh strictly before expiry even for short leases.
|
|
480
|
+
refreshInMs = ttlMs > 1
|
|
481
|
+
? Math.max(1, Math.min(ttlMs - 1, Math.floor(ttlMs * 0.5)))
|
|
482
|
+
: 1;
|
|
483
|
+
}
|
|
484
|
+
const timer = setTimeout(async () => {
|
|
485
|
+
if (!this.agents.has(key)) return;
|
|
486
|
+
try {
|
|
487
|
+
const nextDirective = await this._fetchSpawnDirective({
|
|
488
|
+
...context,
|
|
489
|
+
triggerType: 'lease_refresh',
|
|
490
|
+
});
|
|
491
|
+
const agent = this.agents.get(key);
|
|
492
|
+
if (!agent) return;
|
|
493
|
+
agent.directive = nextDirective;
|
|
494
|
+
this._scheduleDirectiveRefresh(key, context, nextDirective);
|
|
495
|
+
} catch (err) {
|
|
496
|
+
console.log(`[AgentManager] Spawn directive refresh failed for ${context.agentId}: ${err.message}`);
|
|
497
|
+
// Retry later; fail-safe for transient network failures.
|
|
498
|
+
this._scheduleDirectiveRefresh(key, context, {
|
|
499
|
+
...directive,
|
|
500
|
+
lease_ttl_ms: 5000,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}, refreshInMs);
|
|
504
|
+
this.directiveRefreshTimers.set(key, timer);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async _startAgent({ agentId, workspaceId, workspaceName, config }, connection) {
|
|
508
|
+
const key = this._key(agentId, workspaceId);
|
|
95
509
|
if (this.agents.has(key) || this.starting.has(key)) {
|
|
96
|
-
console.log(`[AgentManager] Agent ${config?.displayName ?? agentId} in
|
|
510
|
+
console.log(`[AgentManager] Agent ${config?.displayName ?? agentId} in workspace ${workspaceName ?? workspaceId} already registered`);
|
|
97
511
|
return;
|
|
98
512
|
}
|
|
99
513
|
this.starting.add(key);
|
|
100
514
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
const workspaceDir = this._workspaceDir(agentId,
|
|
515
|
+
const requestedRuntime = String(config.runtime ?? 'claude').trim().toLowerCase() || 'claude';
|
|
516
|
+
this._workspaceRootDir(workspaceId);
|
|
517
|
+
const workspaceDir = this._workspaceDir(agentId, workspaceId);
|
|
104
518
|
const chatBridgePath = new URL('./chat-bridge.js', import.meta.url).pathname;
|
|
105
|
-
const
|
|
519
|
+
const failStart = (reason) => {
|
|
520
|
+
this._clearDirectiveRefresh(key);
|
|
521
|
+
this.starting.delete(key);
|
|
522
|
+
connection.send({ type: 'agent:status', agentId, workspaceId, status: 'inactive' });
|
|
523
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'offline', detail: reason ?? '', entries: [] });
|
|
524
|
+
};
|
|
106
525
|
|
|
107
|
-
|
|
526
|
+
let directive = null;
|
|
527
|
+
try {
|
|
528
|
+
directive = await this._fetchSpawnDirective({
|
|
529
|
+
agentId,
|
|
530
|
+
workspaceId,
|
|
531
|
+
runtime: requestedRuntime,
|
|
532
|
+
workspaceDir,
|
|
533
|
+
chatBridgePath,
|
|
534
|
+
config,
|
|
535
|
+
});
|
|
536
|
+
console.log(`[AgentManager] Spawn directive loaded for ${config.displayName ?? agentId}: ${directive.directive_id ?? 'n/a'} cli=${directive.cli_type ?? requestedRuntime}`);
|
|
537
|
+
this._scheduleDirectiveRefresh(key, {
|
|
538
|
+
agentId,
|
|
539
|
+
workspaceId,
|
|
540
|
+
runtime: requestedRuntime,
|
|
541
|
+
workspaceDir,
|
|
542
|
+
chatBridgePath,
|
|
543
|
+
config,
|
|
544
|
+
}, directive);
|
|
545
|
+
} catch (err) {
|
|
546
|
+
console.log(`[AgentManager] Spawn directive fetch failed for ${agentId} (fail-closed): ${err.message}`);
|
|
547
|
+
failStart('spawn_directive_failed');
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const runtime = String(directive?.cli_type ?? requestedRuntime).trim().toLowerCase() || requestedRuntime;
|
|
552
|
+
if (!['claude', 'codex', 'kimi'].includes(runtime)) {
|
|
553
|
+
console.log(`[AgentManager] Unsupported runtime in directive for ${agentId}: ${runtime}`);
|
|
554
|
+
failStart('spawn_directive_invalid_runtime');
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (!Array.isArray(directive?.spawn_args)) {
|
|
558
|
+
console.log(`[AgentManager] Invalid spawn args in directive for ${agentId}`);
|
|
559
|
+
failStart('spawn_directive_invalid_args');
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const { startupMsg } = this._prepareStartupMessage(key, runtime);
|
|
564
|
+
|
|
565
|
+
// Keep .skills materialization for runtime compatibility, but no longer use skills to build prompt/env/mcp.
|
|
108
566
|
let skills = [];
|
|
109
567
|
try {
|
|
110
568
|
const res = await fetch(`${this.serverUrl}/internal/agent/${agentId}/skills`, {
|
|
111
569
|
headers: { 'Authorization': `Bearer ${this.machineApiKey}` },
|
|
112
570
|
});
|
|
113
571
|
if (res.ok) skills = await res.json();
|
|
114
|
-
|
|
115
|
-
console.log(`[AgentManager] Skills loaded for ${config.displayName ?? agentId}: ${skills.length} total, ${mcpSkills.length} with MCP (${mcpSkills.map(s => s.name).join(', ') || 'none'})`);
|
|
572
|
+
console.log(`[AgentManager] Skills loaded for ${config.displayName ?? agentId}: ${skills.length}`);
|
|
116
573
|
} catch (err) {
|
|
117
574
|
console.log(`[AgentManager] Skills fetch failed for ${agentId} (non-fatal): ${err.message}`);
|
|
118
575
|
}
|
|
576
|
+
this._materializeSkills(workspaceDir, skills);
|
|
119
577
|
|
|
120
|
-
|
|
121
|
-
|
|
578
|
+
const requiredCredentials = Array.isArray(directive?.required_credentials)
|
|
579
|
+
? directive.required_credentials.filter(Boolean)
|
|
580
|
+
: [];
|
|
581
|
+
let credentialEnvVars = {};
|
|
122
582
|
try {
|
|
123
|
-
const res = await fetch(`${this.serverUrl}/
|
|
124
|
-
|
|
583
|
+
const res = await fetch(`${this.serverUrl}/governance/credential-broker`, {
|
|
584
|
+
method: 'POST',
|
|
585
|
+
headers: {
|
|
586
|
+
'Content-Type': 'application/json',
|
|
587
|
+
'Authorization': `Bearer ${this.machineApiKey}`,
|
|
588
|
+
},
|
|
589
|
+
body: JSON.stringify({
|
|
590
|
+
agent_id: agentId,
|
|
591
|
+
workspace_id: workspaceId ?? null,
|
|
592
|
+
required_credentials: requiredCredentials,
|
|
593
|
+
bundle_id: directive?.spawn_bundle_id ?? null,
|
|
594
|
+
}),
|
|
125
595
|
});
|
|
126
|
-
if (res.ok)
|
|
127
|
-
|
|
596
|
+
if (res.ok) {
|
|
597
|
+
const payload = await res.json();
|
|
598
|
+
credentialEnvVars = (payload?.env_vars && typeof payload.env_vars === 'object')
|
|
599
|
+
? payload.env_vars
|
|
600
|
+
: {};
|
|
601
|
+
console.log(`[AgentManager] Brokered credentials for ${config.displayName ?? agentId}: ${Object.keys(credentialEnvVars).join(', ') || 'none'}`);
|
|
602
|
+
} else {
|
|
603
|
+
const text = await res.text();
|
|
604
|
+
if (requiredCredentials.length > 0) {
|
|
605
|
+
throw new Error(`credential_broker_denied:${res.status}:${text.slice(0, 200)}`);
|
|
606
|
+
}
|
|
607
|
+
console.log(`[AgentManager] Credential broker denied for ${agentId} (non-fatal): ${res.status} ${text.slice(0, 200)}`);
|
|
608
|
+
}
|
|
128
609
|
} catch (err) {
|
|
129
|
-
|
|
610
|
+
if (requiredCredentials.length > 0) {
|
|
611
|
+
console.log(`[AgentManager] Credential broker failed for ${agentId} (fail-closed): ${err.message}`);
|
|
612
|
+
failStart('credential_broker_failed');
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
console.log(`[AgentManager] Credential broker failed for ${agentId} (non-fatal): ${err.message}`);
|
|
130
616
|
}
|
|
131
617
|
|
|
132
|
-
|
|
133
|
-
|
|
618
|
+
const directiveEnvVars = normalizeObject(directive?.env_vars);
|
|
619
|
+
directive.env_vars = {
|
|
620
|
+
...directiveEnvVars,
|
|
621
|
+
...credentialEnvVars,
|
|
622
|
+
};
|
|
134
623
|
|
|
135
|
-
|
|
624
|
+
const runtimeConfig = {
|
|
625
|
+
...config,
|
|
626
|
+
authToken: this.machineApiKey,
|
|
627
|
+
systemPrompt: typeof directive?.system_prompt === 'string' ? directive.system_prompt : '',
|
|
628
|
+
envVars: normalizeObject(directive?.env_vars),
|
|
629
|
+
};
|
|
136
630
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
631
|
+
let spawnPlan;
|
|
632
|
+
try {
|
|
633
|
+
spawnPlan = this._buildDirectiveSpawnPlan({
|
|
634
|
+
directive,
|
|
635
|
+
runtime,
|
|
636
|
+
workspaceDir,
|
|
637
|
+
chatBridgePath,
|
|
638
|
+
agentId,
|
|
639
|
+
workspaceId,
|
|
640
|
+
startupMsg,
|
|
143
641
|
});
|
|
642
|
+
} catch (err) {
|
|
643
|
+
console.log(`[AgentManager] Failed to build spawn plan for ${agentId}: ${err.message}`);
|
|
644
|
+
failStart('spawn_plan_invalid');
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
144
647
|
|
|
145
|
-
|
|
648
|
+
console.log(`[AgentManager] Spawning ${runtime} for ${config.displayName ?? agentId} workspace=${workspaceName ?? workspaceId ?? 'none'} directive=${directive.directive_id ?? 'n/a'}`);
|
|
649
|
+
const stdio = runtime === 'codex'
|
|
650
|
+
? ['ignore', 'pipe', 'pipe']
|
|
651
|
+
: ['pipe', 'pipe', 'pipe'];
|
|
652
|
+
const proc = spawn(spawnPlan.command, spawnPlan.args, {
|
|
653
|
+
cwd: workspaceDir,
|
|
654
|
+
env: spawnPlan.env,
|
|
655
|
+
stdio,
|
|
656
|
+
});
|
|
657
|
+
let spawnErrorReported = false;
|
|
146
658
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
659
|
+
const reportSpawnFailure = (detail) => {
|
|
660
|
+
if (spawnErrorReported) return;
|
|
661
|
+
spawnErrorReported = true;
|
|
662
|
+
this._clearDirectiveRefresh(key);
|
|
663
|
+
this.starting.delete(key);
|
|
664
|
+
this.agents.delete(key);
|
|
665
|
+
connection.send({ type: 'agent:status', agentId, workspaceId, status: 'inactive' });
|
|
666
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'offline', detail: detail ?? 'spawn_failed', entries: [] });
|
|
667
|
+
};
|
|
152
668
|
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
669
|
+
proc.once('error', (err) => {
|
|
670
|
+
const detail = err?.code === 'ENOENT'
|
|
671
|
+
? runtimeMissingDetail(runtime)
|
|
672
|
+
: 'spawn_failed';
|
|
673
|
+
console.log(`[AgentManager] Spawn process error for ${agentId} (${runtime}): ${err?.message ?? 'unknown error'}`);
|
|
674
|
+
reportSpawnFailure(detail);
|
|
675
|
+
});
|
|
158
676
|
|
|
159
|
-
|
|
160
|
-
const
|
|
677
|
+
if (runtime === 'kimi') {
|
|
678
|
+
const sessionId = spawnPlan.kimiSessionId ?? null;
|
|
679
|
+
const kimiState = { sessionId, sessionAnnounced: false };
|
|
161
680
|
|
|
162
681
|
this.agents.set(key, {
|
|
163
|
-
config
|
|
164
|
-
|
|
682
|
+
config: runtimeConfig,
|
|
683
|
+
workspaceId,
|
|
684
|
+
agentId,
|
|
685
|
+
sessionId,
|
|
686
|
+
proc,
|
|
687
|
+
runtime: 'kimi',
|
|
688
|
+
kimiState,
|
|
689
|
+
kimiIdle: false,
|
|
690
|
+
directive,
|
|
691
|
+
requiredCredentials,
|
|
692
|
+
stopCause: null,
|
|
165
693
|
});
|
|
166
694
|
this.starting.delete(key);
|
|
167
695
|
|
|
696
|
+
// Kimi wire protocol requires initialize before prompt/steer frames.
|
|
697
|
+
const initMessages = [
|
|
698
|
+
JSON.stringify({
|
|
699
|
+
jsonrpc: '2.0',
|
|
700
|
+
id: `${Date.now()}-init`,
|
|
701
|
+
method: 'initialize',
|
|
702
|
+
params: {
|
|
703
|
+
protocol_version: '1.3',
|
|
704
|
+
client: { name: 'lightcone-daemon', version: '1.0.0' },
|
|
705
|
+
capabilities: { supports_question: false, supports_plan_mode: false },
|
|
706
|
+
},
|
|
707
|
+
}),
|
|
708
|
+
JSON.stringify({
|
|
709
|
+
jsonrpc: '2.0',
|
|
710
|
+
id: `${Date.now()}-prompt`,
|
|
711
|
+
method: 'prompt',
|
|
712
|
+
params: {
|
|
713
|
+
user_input: 'Your system prompt contains your standing instructions. Follow it now and begin listening for messages.',
|
|
714
|
+
},
|
|
715
|
+
}),
|
|
716
|
+
];
|
|
717
|
+
for (const msg of initMessages) {
|
|
718
|
+
proc.stdin.write(msg + '\n');
|
|
719
|
+
}
|
|
720
|
+
|
|
168
721
|
let buffer = '';
|
|
169
722
|
proc.stdout.on('data', (chunk) => {
|
|
170
723
|
buffer += chunk.toString();
|
|
@@ -172,38 +725,20 @@ export class AgentManager {
|
|
|
172
725
|
buffer = lines.pop();
|
|
173
726
|
for (const line of lines) {
|
|
174
727
|
if (!line.trim()) continue;
|
|
175
|
-
this._parseKimiLine(key, agentId,
|
|
728
|
+
this._parseKimiLine(key, agentId, workspaceId, line, connection);
|
|
176
729
|
}
|
|
177
730
|
});
|
|
178
731
|
} else if (runtime === 'codex') {
|
|
179
|
-
const startupPrompt = startupMsg
|
|
180
|
-
? `${buildCodexSystemPrompt(config, agentId)}\n\nNew message received:\n\n${this._formatDeliveryText(startupMsg.message)}\n\nRespond as appropriate. Complete all required work before stopping.`
|
|
181
|
-
: buildCodexSystemPrompt(config, agentId);
|
|
182
|
-
|
|
183
|
-
const codexSpawn = buildCodexSpawn({
|
|
184
|
-
config,
|
|
185
|
-
agentId,
|
|
186
|
-
teamId,
|
|
187
|
-
workspaceDir,
|
|
188
|
-
chatBridgePath,
|
|
189
|
-
serverUrl: this.serverUrl,
|
|
190
|
-
machineApiKey: this.machineApiKey,
|
|
191
|
-
prompt: startupPrompt,
|
|
192
|
-
skills,
|
|
193
|
-
credentialGrants,
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
console.log(`[AgentManager] Spawning codex for ${config.displayName ?? agentId} team=${teamName ?? teamId ?? 'none'} (session=${config.sessionId ?? 'new'})`);
|
|
197
|
-
|
|
198
|
-
proc = spawn('codex', codexSpawn.args, {
|
|
199
|
-
cwd: workspaceDir,
|
|
200
|
-
env: codexSpawn.env,
|
|
201
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
202
|
-
});
|
|
203
|
-
|
|
204
732
|
this.agents.set(key, {
|
|
205
|
-
config
|
|
733
|
+
config: runtimeConfig,
|
|
734
|
+
workspaceId,
|
|
735
|
+
agentId,
|
|
736
|
+
sessionId: config.sessionId ?? null,
|
|
737
|
+
proc,
|
|
206
738
|
runtime: 'codex',
|
|
739
|
+
directive,
|
|
740
|
+
requiredCredentials,
|
|
741
|
+
stopCause: null,
|
|
207
742
|
});
|
|
208
743
|
this.starting.delete(key);
|
|
209
744
|
|
|
@@ -214,80 +749,23 @@ export class AgentManager {
|
|
|
214
749
|
buffer = lines.pop();
|
|
215
750
|
for (const line of lines) {
|
|
216
751
|
if (!line.trim()) continue;
|
|
217
|
-
this._parseCodexLine(key, agentId,
|
|
752
|
+
this._parseCodexLine(key, agentId, workspaceId, line, connection);
|
|
218
753
|
}
|
|
219
754
|
});
|
|
220
755
|
} else {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
command: 'node',
|
|
225
|
-
args: [chatBridgePath],
|
|
226
|
-
env: {
|
|
227
|
-
SERVER_URL: this.serverUrl,
|
|
228
|
-
MACHINE_API_KEY: config.authToken,
|
|
229
|
-
AGENT_ID: agentId,
|
|
230
|
-
TEAM_ID: teamId ?? '',
|
|
231
|
-
WORKSPACE_DIR: workspaceDir,
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
Object.assign(mcpServers, buildSkillMcpServers({
|
|
237
|
-
skills,
|
|
238
|
-
credentialGrants,
|
|
239
|
-
config,
|
|
756
|
+
this.agents.set(key, {
|
|
757
|
+
config: runtimeConfig,
|
|
758
|
+
workspaceId,
|
|
240
759
|
agentId,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
console.log(`[AgentManager] MCP servers for ${config.displayName ?? agentId}: ${Object.keys(mcpServers).join(', ')}`);
|
|
248
|
-
for (const [name, mc] of Object.entries(mcpServers)) {
|
|
249
|
-
console.log(`[AgentManager] mcp:${name} → ${mc.command} ${(mc.args ?? []).join(' ')}`);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const args = [
|
|
253
|
-
'--print',
|
|
254
|
-
'--allow-dangerously-skip-permissions',
|
|
255
|
-
'--dangerously-skip-permissions',
|
|
256
|
-
'--verbose',
|
|
257
|
-
'--output-format', 'stream-json',
|
|
258
|
-
'--input-format', 'stream-json',
|
|
259
|
-
'--mcp-config', JSON.stringify(mcpConfig),
|
|
260
|
-
'--system-prompt', buildSystemPrompt(config, agentId, skills),
|
|
261
|
-
'--disallowed-tools', 'EnterPlanMode,ExitPlanMode',
|
|
262
|
-
];
|
|
263
|
-
|
|
264
|
-
if (config.sessionId) {
|
|
265
|
-
// Only resume if the session file exists locally
|
|
266
|
-
const projectSlug = workspaceDir.replace(/[\/\.]/g, '-');
|
|
267
|
-
const sessionFile = path.join(homedir(), '.claude', 'projects', projectSlug, `${config.sessionId}.jsonl`);
|
|
268
|
-
try {
|
|
269
|
-
statSync(sessionFile);
|
|
270
|
-
args.push('--resume', config.sessionId);
|
|
271
|
-
} catch {
|
|
272
|
-
console.log(`[AgentManager] Session ${config.sessionId} not found locally, starting fresh`);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const spawnEnv = { ...process.env, FORCE_COLOR: '0', ...(config.envVars ?? {}) };
|
|
277
|
-
delete spawnEnv.CLAUDECODE;
|
|
278
|
-
|
|
279
|
-
console.log(`[AgentManager] Spawning claude for ${config.displayName ?? agentId} team=${teamName ?? teamId ?? 'none'} (session=${config.sessionId ?? 'new'})`);
|
|
280
|
-
|
|
281
|
-
proc = spawn('claude', args, {
|
|
282
|
-
cwd: workspaceDir,
|
|
283
|
-
env: spawnEnv,
|
|
284
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
760
|
+
sessionId: config.sessionId ?? null,
|
|
761
|
+
proc,
|
|
762
|
+
runtime: 'claude',
|
|
763
|
+
directive,
|
|
764
|
+
requiredCredentials,
|
|
765
|
+
stopCause: null,
|
|
285
766
|
});
|
|
286
|
-
|
|
287
|
-
this.agents.set(key, { config, teamId, agentId, sessionId: config.sessionId ?? null, proc, runtime: 'claude' });
|
|
288
767
|
this.starting.delete(key);
|
|
289
768
|
|
|
290
|
-
// Parse stdout stream for session ID and activity updates
|
|
291
769
|
let buffer = '';
|
|
292
770
|
proc.stdout.on('data', (chunk) => {
|
|
293
771
|
buffer += chunk.toString();
|
|
@@ -295,7 +773,7 @@ export class AgentManager {
|
|
|
295
773
|
buffer = lines.pop();
|
|
296
774
|
for (const line of lines) {
|
|
297
775
|
if (!line.trim()) continue;
|
|
298
|
-
this._parseLine(key, agentId,
|
|
776
|
+
this._parseLine(key, agentId, workspaceId, line, connection);
|
|
299
777
|
}
|
|
300
778
|
});
|
|
301
779
|
}
|
|
@@ -305,14 +783,16 @@ export class AgentManager {
|
|
|
305
783
|
if (text) console.error(`[AgentManager][${config.displayName ?? agentId}] stderr: ${text.slice(0, 500)}`);
|
|
306
784
|
});
|
|
307
785
|
|
|
308
|
-
proc.on('exit', (code) => {
|
|
786
|
+
proc.on('exit', (code, signal) => {
|
|
787
|
+
if (spawnErrorReported) return;
|
|
309
788
|
const agent = this.agents.get(key);
|
|
310
|
-
console.log(`[AgentManager] Agent ${config.displayName ?? agentId}
|
|
789
|
+
console.log(`[AgentManager] Agent ${config.displayName ?? agentId} workspace=${workspaceName ?? workspaceId ?? 'none'} exited (code=${code}, signal=${signal ?? 'none'})`);
|
|
790
|
+
this._clearDirectiveRefresh(key);
|
|
311
791
|
this.agents.delete(key);
|
|
312
792
|
|
|
313
793
|
if (code === 0 && runtime === 'codex' && this._pendingMessages?.get(key)?.length) {
|
|
314
794
|
const restartConfig = { ...config, sessionId: agent?.sessionId ?? config.sessionId ?? null };
|
|
315
|
-
this._startAgent({ agentId,
|
|
795
|
+
this._startAgent({ agentId, workspaceId, config: restartConfig }, connection);
|
|
316
796
|
return;
|
|
317
797
|
}
|
|
318
798
|
|
|
@@ -320,14 +800,20 @@ export class AgentManager {
|
|
|
320
800
|
if (code !== 0 && config.sessionId && !this._retried?.has(key)) {
|
|
321
801
|
if (!this._retried) this._retried = new Set();
|
|
322
802
|
this._retried.add(key);
|
|
323
|
-
console.log(`[AgentManager] Retrying ${agentId}
|
|
803
|
+
console.log(`[AgentManager] Retrying ${agentId} workspace=${workspaceId} without session (session may not exist locally)`);
|
|
324
804
|
const retryConfig = { ...config, sessionId: null };
|
|
325
|
-
this._startAgent({ agentId,
|
|
805
|
+
this._startAgent({ agentId, workspaceId, config: retryConfig }, connection);
|
|
326
806
|
return;
|
|
327
807
|
}
|
|
328
808
|
|
|
329
|
-
|
|
330
|
-
|
|
809
|
+
const offlineDetail = resolveExitOfflineDetail({
|
|
810
|
+
code,
|
|
811
|
+
signal,
|
|
812
|
+
stopCause: agent?.stopCause ?? null,
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
connection.send({ type: 'agent:status', agentId, workspaceId, status: 'inactive' });
|
|
816
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'offline', detail: offlineDetail, entries: [] });
|
|
331
817
|
});
|
|
332
818
|
|
|
333
819
|
// Send startup prompt
|
|
@@ -337,9 +823,9 @@ export class AgentManager {
|
|
|
337
823
|
this._write(key, 'You have just started. Follow your startup sequence: first call read_memory with path="MEMORY.md" to load your memory index, then call check_messages.');
|
|
338
824
|
}
|
|
339
825
|
|
|
340
|
-
console.log(`[AgentManager] Agent ${config.displayName ?? agentId} is now active (
|
|
341
|
-
connection.send({ type: 'agent:status', agentId,
|
|
342
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
826
|
+
console.log(`[AgentManager] Agent ${config.displayName ?? agentId} is now active (workspace=${workspaceName ?? workspaceId ?? 'none'})`);
|
|
827
|
+
connection.send({ type: 'agent:status', agentId, workspaceId, status: 'active' });
|
|
828
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
343
829
|
|
|
344
830
|
// Flush any messages that arrived while the agent was starting
|
|
345
831
|
this._flushPending(key, connection);
|
|
@@ -362,34 +848,122 @@ export class AgentManager {
|
|
|
362
848
|
return stopSession(platform);
|
|
363
849
|
}
|
|
364
850
|
|
|
365
|
-
|
|
366
|
-
const
|
|
851
|
+
_handlePolicyInvalidate(msg, connection) {
|
|
852
|
+
const leaseIds = Array.isArray(msg.lease_ids) ? msg.lease_ids.filter(Boolean) : [];
|
|
853
|
+
const reason = msg.reason ?? 'policy_updated';
|
|
854
|
+
const newPolicyHash = msg.new_policy_hash ?? null;
|
|
855
|
+
const invalidated = markInvalidatedLeases(leaseIds, { reason, newPolicyHash });
|
|
856
|
+
const invalidatedSet = new Set(leaseIds);
|
|
857
|
+
const normalizedReason = String(reason).trim().toLowerCase();
|
|
858
|
+
const forceFreshSession = normalizedReason === 'policy_major_changed'
|
|
859
|
+
|| normalizedReason === 'policy_major_change';
|
|
860
|
+
|
|
861
|
+
console.log(`[AgentManager] Received policy_invalidate: reason=${reason} leases=${invalidated}`);
|
|
862
|
+
connection.send({
|
|
863
|
+
type: 'policy_invalidate:ack',
|
|
864
|
+
lease_ids: leaseIds,
|
|
865
|
+
reason,
|
|
866
|
+
received_at: new Date().toISOString(),
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
let stopped = 0;
|
|
870
|
+
for (const [agentKey, agent] of this.agents.entries()) {
|
|
871
|
+
const leaseId = agent.directive?.policy_lease?.lease_id ?? null;
|
|
872
|
+
// Fail-closed: when no lease_id is present on agent state, treat it as affected.
|
|
873
|
+
const shouldStop = invalidatedSet.size === 0
|
|
874
|
+
|| !leaseId
|
|
875
|
+
|| invalidatedSet.has(leaseId);
|
|
876
|
+
if (!shouldStop) continue;
|
|
877
|
+
|
|
878
|
+
let workspaceId = agent.workspaceId ?? null;
|
|
879
|
+
let agentId = agent.agentId ?? null;
|
|
880
|
+
if (!agentId) {
|
|
881
|
+
const splitAt = agentKey.indexOf(':');
|
|
882
|
+
if (splitAt === -1) {
|
|
883
|
+
agentId = agentKey;
|
|
884
|
+
} else {
|
|
885
|
+
workspaceId = workspaceId ?? (agentKey.slice(0, splitAt) || null);
|
|
886
|
+
agentId = agentKey.slice(splitAt + 1);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (forceFreshSession && agent.sessionId && agentId) {
|
|
891
|
+
agent.sessionId = null;
|
|
892
|
+
connection.send({ type: 'agent:session', agentId, workspaceId, sessionId: null });
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
this._clearDirectiveRefresh(agentKey);
|
|
896
|
+
agent.proc?.kill();
|
|
897
|
+
stopped += 1;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (stopped > 0) {
|
|
901
|
+
console.log(`[AgentManager] policy_invalidate stopped ${stopped} active agent process(es)`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
_handleCredentialRevoked(msg, connection) {
|
|
906
|
+
const credentialIds = Array.isArray(msg.credential_ids) ? msg.credential_ids.filter(Boolean) : [];
|
|
907
|
+
const reason = msg.reason ?? 'credential_revoked';
|
|
908
|
+
console.log(`[AgentManager] Received credential_revoked: reason=${reason} credential_ids=${credentialIds.join(',') || 'none'}`);
|
|
909
|
+
connection.send({
|
|
910
|
+
type: 'credential_revoked:ack',
|
|
911
|
+
credential_ids: credentialIds,
|
|
912
|
+
reason,
|
|
913
|
+
received_at: new Date().toISOString(),
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
const revoked = new Set(credentialIds);
|
|
917
|
+
let stopped = 0;
|
|
918
|
+
for (const [agentKey, agent] of this.agents.entries()) {
|
|
919
|
+
const required = Array.isArray(agent.requiredCredentials)
|
|
920
|
+
? agent.requiredCredentials.filter(Boolean)
|
|
921
|
+
: [];
|
|
922
|
+
// Fail-closed: if directive omitted required credential IDs, assume this agent may be affected.
|
|
923
|
+
const shouldStop = revoked.size === 0
|
|
924
|
+
|| required.length === 0
|
|
925
|
+
|| required.some((id) => revoked.has(id));
|
|
926
|
+
if (!shouldStop) continue;
|
|
927
|
+
this._clearDirectiveRefresh(agentKey);
|
|
928
|
+
agent.stopCause = 'credential_revoked';
|
|
929
|
+
agent.proc?.kill();
|
|
930
|
+
stopped += 1;
|
|
931
|
+
}
|
|
932
|
+
if (stopped > 0) {
|
|
933
|
+
console.log(`[AgentManager] credential_revoked stopped ${stopped} active agent process(es)`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
_stopAgent(agentId, workspaceId, connection) {
|
|
938
|
+
const key = this._key(agentId, workspaceId);
|
|
367
939
|
const agent = this.agents.get(key);
|
|
368
940
|
if (!agent) return;
|
|
369
|
-
console.log(`[AgentManager] Stopping agent ${agentId}
|
|
941
|
+
console.log(`[AgentManager] Stopping agent ${agentId} workspace=${workspaceId ?? 'none'}`);
|
|
942
|
+
this._clearDirectiveRefresh(key);
|
|
943
|
+
agent.stopCause = 'manual_stop';
|
|
370
944
|
agent.proc?.kill();
|
|
371
945
|
// exit handler will report status
|
|
372
946
|
}
|
|
373
947
|
|
|
374
948
|
_deliverMessage(msg, connection) {
|
|
375
|
-
const { agentId,
|
|
376
|
-
const key = this._key(agentId,
|
|
377
|
-
connection.send({ type: 'agent:deliver:ack', agentId,
|
|
949
|
+
const { agentId, workspaceId, seq, message } = msg;
|
|
950
|
+
const key = this._key(agentId, workspaceId);
|
|
951
|
+
connection.send({ type: 'agent:deliver:ack', agentId, workspaceId, seq });
|
|
378
952
|
|
|
379
953
|
if (!this.agents.has(key) && !this.starting.has(key)) {
|
|
380
954
|
// Agent not running — queue the message and request config to spawn it
|
|
381
|
-
console.log(`[AgentManager] Agent ${agentId.slice(0,8)}
|
|
955
|
+
console.log(`[AgentManager] Agent ${agentId.slice(0,8)} workspace=${message.workspace_name ?? workspaceId} not running, requesting start for seq=${seq}`);
|
|
382
956
|
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
383
957
|
const pending = this._pendingMessages.get(key) ?? [];
|
|
384
958
|
pending.push(msg);
|
|
385
959
|
this._pendingMessages.set(key, pending);
|
|
386
|
-
connection.send({ type: 'agent:request_start', agentId,
|
|
960
|
+
connection.send({ type: 'agent:request_start', agentId, workspaceId });
|
|
387
961
|
return;
|
|
388
962
|
}
|
|
389
963
|
|
|
390
964
|
if (this.starting.has(key)) {
|
|
391
965
|
// Spawn in progress — queue the message for delivery after start
|
|
392
|
-
console.log(`[AgentManager] Agent ${agentId.slice(0,8)}
|
|
966
|
+
console.log(`[AgentManager] Agent ${agentId.slice(0,8)} workspace=${message.workspace_name ?? workspaceId} still starting, queuing seq=${seq}`);
|
|
393
967
|
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
394
968
|
const pending = this._pendingMessages.get(key) ?? [];
|
|
395
969
|
pending.push(msg);
|
|
@@ -399,7 +973,7 @@ export class AgentManager {
|
|
|
399
973
|
|
|
400
974
|
const text = this._formatDeliveryText(message);
|
|
401
975
|
const agent = this.agents.get(key);
|
|
402
|
-
console.log(`[AgentManager] Delivering seq=${seq} to agent ${agent?.config?.displayName ?? agentId.slice(0,8)}
|
|
976
|
+
console.log(`[AgentManager] Delivering seq=${seq} to agent ${agent?.config?.displayName ?? agentId.slice(0,8)} workspace=${message.workspace_name ?? workspaceId}`);
|
|
403
977
|
if (agent?.runtime === 'codex') {
|
|
404
978
|
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
405
979
|
const pending = this._pendingMessages.get(key) ?? [];
|
|
@@ -417,10 +991,10 @@ export class AgentManager {
|
|
|
417
991
|
if (!pending || pending.length === 0) return;
|
|
418
992
|
this._pendingMessages.delete(key);
|
|
419
993
|
for (const msg of pending) {
|
|
420
|
-
const { agentId,
|
|
994
|
+
const { agentId, workspaceId, seq, message } = msg;
|
|
421
995
|
const text = this._formatDeliveryText(message);
|
|
422
996
|
const agent = this.agents.get(key);
|
|
423
|
-
console.log(`[AgentManager] Flushing queued seq=${seq} to agent ${agent?.config?.displayName ?? agentId.slice(0,8)}
|
|
997
|
+
console.log(`[AgentManager] Flushing queued seq=${seq} to agent ${agent?.config?.displayName ?? agentId.slice(0,8)} workspace=${message.workspace_name ?? workspaceId}`);
|
|
424
998
|
this._write(key, text);
|
|
425
999
|
}
|
|
426
1000
|
}
|
|
@@ -453,7 +1027,7 @@ export class AgentManager {
|
|
|
453
1027
|
}
|
|
454
1028
|
}
|
|
455
1029
|
|
|
456
|
-
_parseKimiLine(key, agentId,
|
|
1030
|
+
_parseKimiLine(key, agentId, workspaceId, line, connection) {
|
|
457
1031
|
const agent = this.agents.get(key);
|
|
458
1032
|
if (!agent) return;
|
|
459
1033
|
const events = parseKimiLine(line, agent.kimiState);
|
|
@@ -462,18 +1036,18 @@ export class AgentManager {
|
|
|
462
1036
|
case 'session_init':
|
|
463
1037
|
if (agent.sessionId !== evt.sessionId) {
|
|
464
1038
|
agent.sessionId = evt.sessionId;
|
|
465
|
-
connection.send({ type: 'agent:session', agentId,
|
|
1039
|
+
connection.send({ type: 'agent:session', agentId, workspaceId, sessionId: evt.sessionId });
|
|
466
1040
|
}
|
|
467
1041
|
break;
|
|
468
1042
|
case 'thinking':
|
|
469
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1043
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'thinking', detail: '', entries: [] });
|
|
470
1044
|
break;
|
|
471
1045
|
case 'tool_call':
|
|
472
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1046
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'working', detail: evt.name, entries: [] });
|
|
473
1047
|
break;
|
|
474
1048
|
case 'turn_end':
|
|
475
1049
|
agent.kimiIdle = true;
|
|
476
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1050
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
477
1051
|
break;
|
|
478
1052
|
case 'error':
|
|
479
1053
|
console.error(`[AgentManager][kimi][${agentId}] Error: ${evt.message}`);
|
|
@@ -482,7 +1056,7 @@ export class AgentManager {
|
|
|
482
1056
|
}
|
|
483
1057
|
}
|
|
484
1058
|
|
|
485
|
-
_parseCodexLine(key, agentId,
|
|
1059
|
+
_parseCodexLine(key, agentId, workspaceId, line, connection) {
|
|
486
1060
|
const agent = this.agents.get(key);
|
|
487
1061
|
if (!agent) return;
|
|
488
1062
|
const events = parseCodexLine(line);
|
|
@@ -491,17 +1065,17 @@ export class AgentManager {
|
|
|
491
1065
|
case 'session_init':
|
|
492
1066
|
if (agent.sessionId !== evt.sessionId) {
|
|
493
1067
|
agent.sessionId = evt.sessionId;
|
|
494
|
-
connection.send({ type: 'agent:session', agentId,
|
|
1068
|
+
connection.send({ type: 'agent:session', agentId, workspaceId, sessionId: evt.sessionId });
|
|
495
1069
|
}
|
|
496
1070
|
break;
|
|
497
1071
|
case 'thinking':
|
|
498
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1072
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'thinking', detail: '', entries: [] });
|
|
499
1073
|
break;
|
|
500
1074
|
case 'tool_call':
|
|
501
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1075
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'working', detail: evt.name, entries: [] });
|
|
502
1076
|
break;
|
|
503
1077
|
case 'turn_end':
|
|
504
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1078
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
505
1079
|
break;
|
|
506
1080
|
case 'error':
|
|
507
1081
|
console.error(`[AgentManager][codex][${agentId}] Error: ${evt.message}`);
|
|
@@ -510,7 +1084,7 @@ export class AgentManager {
|
|
|
510
1084
|
}
|
|
511
1085
|
}
|
|
512
1086
|
|
|
513
|
-
_parseLine(key, agentId,
|
|
1087
|
+
_parseLine(key, agentId, workspaceId, line, connection) {
|
|
514
1088
|
let event;
|
|
515
1089
|
try { event = JSON.parse(line); }
|
|
516
1090
|
catch { return; }
|
|
@@ -520,7 +1094,7 @@ export class AgentManager {
|
|
|
520
1094
|
const agent = this.agents.get(key);
|
|
521
1095
|
if (agent && agent.sessionId !== event.session_id) {
|
|
522
1096
|
agent.sessionId = event.session_id;
|
|
523
|
-
connection.send({ type: 'agent:session', agentId,
|
|
1097
|
+
connection.send({ type: 'agent:session', agentId, workspaceId, sessionId: event.session_id });
|
|
524
1098
|
}
|
|
525
1099
|
}
|
|
526
1100
|
|
|
@@ -535,11 +1109,11 @@ export class AgentManager {
|
|
|
535
1109
|
console.log(`[AgentManager][${displayName}] <text> ${block.text?.slice(0, 500)}`);
|
|
536
1110
|
} else if (block.type === 'tool_use') {
|
|
537
1111
|
console.log(`[AgentManager][${displayName}] <tool_use> ${block.name} params=${JSON.stringify(block.input ?? {}).slice(0, 500)}`);
|
|
538
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1112
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'working', detail: block.name, entries: [] });
|
|
539
1113
|
}
|
|
540
1114
|
}
|
|
541
1115
|
if (!content.some(c => c.type === 'tool_use')) {
|
|
542
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1116
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'thinking', detail: '', entries: [] });
|
|
543
1117
|
}
|
|
544
1118
|
} else if (event.type === 'tool') {
|
|
545
1119
|
const content = event.content;
|
|
@@ -558,7 +1132,7 @@ export class AgentManager {
|
|
|
558
1132
|
}
|
|
559
1133
|
} else if (event.type === 'result') {
|
|
560
1134
|
console.log(`[AgentManager][${displayName}] turn done (stop_reason=${event.stop_reason ?? '?'})`);
|
|
561
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1135
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
562
1136
|
}
|
|
563
1137
|
}
|
|
564
1138
|
}
|