@lightcone-ai/daemon 0.9.79 → 0.10.1
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/index.js +14 -14
- package/mcp-servers/publisher/manifest.json +16 -0
- package/package.json +4 -2
- package/src/_vendor/mcp/registry.js +327 -0
- package/src/agent-manager.js +761 -188
- 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 -8
- package/src/drivers/kimi.js +80 -35
- 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 -23
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,81 +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
|
-
const mcpConfig = { mcpServers };
|
|
248
|
-
console.log(`[AgentManager] MCP servers for ${config.displayName ?? agentId}: ${Object.keys(mcpServers).join(', ')}`);
|
|
249
|
-
for (const [name, mc] of Object.entries(mcpServers)) {
|
|
250
|
-
console.log(`[AgentManager] mcp:${name} → ${mc.command} ${(mc.args ?? []).join(' ')}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const args = [
|
|
254
|
-
'--print',
|
|
255
|
-
'--allow-dangerously-skip-permissions',
|
|
256
|
-
'--dangerously-skip-permissions',
|
|
257
|
-
'--verbose',
|
|
258
|
-
'--output-format', 'stream-json',
|
|
259
|
-
'--input-format', 'stream-json',
|
|
260
|
-
'--mcp-config', JSON.stringify(mcpConfig),
|
|
261
|
-
'--system-prompt', buildSystemPrompt(config, agentId, skills),
|
|
262
|
-
'--disallowed-tools', 'EnterPlanMode,ExitPlanMode',
|
|
263
|
-
];
|
|
264
|
-
|
|
265
|
-
if (config.sessionId) {
|
|
266
|
-
// Only resume if the session file exists locally
|
|
267
|
-
const projectSlug = workspaceDir.replace(/[\/\.]/g, '-');
|
|
268
|
-
const sessionFile = path.join(homedir(), '.claude', 'projects', projectSlug, `${config.sessionId}.jsonl`);
|
|
269
|
-
try {
|
|
270
|
-
statSync(sessionFile);
|
|
271
|
-
args.push('--resume', config.sessionId);
|
|
272
|
-
} catch {
|
|
273
|
-
console.log(`[AgentManager] Session ${config.sessionId} not found locally, starting fresh`);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const spawnEnv = { ...process.env, FORCE_COLOR: '0', ...(config.envVars ?? {}) };
|
|
278
|
-
delete spawnEnv.CLAUDECODE;
|
|
279
|
-
|
|
280
|
-
console.log(`[AgentManager] Spawning claude for ${config.displayName ?? agentId} team=${teamName ?? teamId ?? 'none'} (session=${config.sessionId ?? 'new'})`);
|
|
281
|
-
|
|
282
|
-
proc = spawn('claude', args, {
|
|
283
|
-
cwd: workspaceDir,
|
|
284
|
-
env: spawnEnv,
|
|
285
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
760
|
+
sessionId: config.sessionId ?? null,
|
|
761
|
+
proc,
|
|
762
|
+
runtime: 'claude',
|
|
763
|
+
directive,
|
|
764
|
+
requiredCredentials,
|
|
765
|
+
stopCause: null,
|
|
286
766
|
});
|
|
287
|
-
|
|
288
|
-
this.agents.set(key, { config, teamId, agentId, sessionId: config.sessionId ?? null, proc, runtime: 'claude' });
|
|
289
767
|
this.starting.delete(key);
|
|
290
768
|
|
|
291
|
-
// Parse stdout stream for session ID and activity updates
|
|
292
769
|
let buffer = '';
|
|
293
770
|
proc.stdout.on('data', (chunk) => {
|
|
294
771
|
buffer += chunk.toString();
|
|
@@ -296,7 +773,7 @@ export class AgentManager {
|
|
|
296
773
|
buffer = lines.pop();
|
|
297
774
|
for (const line of lines) {
|
|
298
775
|
if (!line.trim()) continue;
|
|
299
|
-
this._parseLine(key, agentId,
|
|
776
|
+
this._parseLine(key, agentId, workspaceId, line, connection);
|
|
300
777
|
}
|
|
301
778
|
});
|
|
302
779
|
}
|
|
@@ -306,14 +783,16 @@ export class AgentManager {
|
|
|
306
783
|
if (text) console.error(`[AgentManager][${config.displayName ?? agentId}] stderr: ${text.slice(0, 500)}`);
|
|
307
784
|
});
|
|
308
785
|
|
|
309
|
-
proc.on('exit', (code) => {
|
|
786
|
+
proc.on('exit', (code, signal) => {
|
|
787
|
+
if (spawnErrorReported) return;
|
|
310
788
|
const agent = this.agents.get(key);
|
|
311
|
-
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);
|
|
312
791
|
this.agents.delete(key);
|
|
313
792
|
|
|
314
793
|
if (code === 0 && runtime === 'codex' && this._pendingMessages?.get(key)?.length) {
|
|
315
794
|
const restartConfig = { ...config, sessionId: agent?.sessionId ?? config.sessionId ?? null };
|
|
316
|
-
this._startAgent({ agentId,
|
|
795
|
+
this._startAgent({ agentId, workspaceId, config: restartConfig }, connection);
|
|
317
796
|
return;
|
|
318
797
|
}
|
|
319
798
|
|
|
@@ -321,14 +800,20 @@ export class AgentManager {
|
|
|
321
800
|
if (code !== 0 && config.sessionId && !this._retried?.has(key)) {
|
|
322
801
|
if (!this._retried) this._retried = new Set();
|
|
323
802
|
this._retried.add(key);
|
|
324
|
-
console.log(`[AgentManager] Retrying ${agentId}
|
|
803
|
+
console.log(`[AgentManager] Retrying ${agentId} workspace=${workspaceId} without session (session may not exist locally)`);
|
|
325
804
|
const retryConfig = { ...config, sessionId: null };
|
|
326
|
-
this._startAgent({ agentId,
|
|
805
|
+
this._startAgent({ agentId, workspaceId, config: retryConfig }, connection);
|
|
327
806
|
return;
|
|
328
807
|
}
|
|
329
808
|
|
|
330
|
-
|
|
331
|
-
|
|
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: [] });
|
|
332
817
|
});
|
|
333
818
|
|
|
334
819
|
// Send startup prompt
|
|
@@ -338,9 +823,9 @@ export class AgentManager {
|
|
|
338
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.');
|
|
339
824
|
}
|
|
340
825
|
|
|
341
|
-
console.log(`[AgentManager] Agent ${config.displayName ?? agentId} is now active (
|
|
342
|
-
connection.send({ type: 'agent:status', agentId,
|
|
343
|
-
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: [] });
|
|
344
829
|
|
|
345
830
|
// Flush any messages that arrived while the agent was starting
|
|
346
831
|
this._flushPending(key, connection);
|
|
@@ -363,34 +848,122 @@ export class AgentManager {
|
|
|
363
848
|
return stopSession(platform);
|
|
364
849
|
}
|
|
365
850
|
|
|
366
|
-
|
|
367
|
-
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);
|
|
368
939
|
const agent = this.agents.get(key);
|
|
369
940
|
if (!agent) return;
|
|
370
|
-
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';
|
|
371
944
|
agent.proc?.kill();
|
|
372
945
|
// exit handler will report status
|
|
373
946
|
}
|
|
374
947
|
|
|
375
948
|
_deliverMessage(msg, connection) {
|
|
376
|
-
const { agentId,
|
|
377
|
-
const key = this._key(agentId,
|
|
378
|
-
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 });
|
|
379
952
|
|
|
380
953
|
if (!this.agents.has(key) && !this.starting.has(key)) {
|
|
381
954
|
// Agent not running — queue the message and request config to spawn it
|
|
382
|
-
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}`);
|
|
383
956
|
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
384
957
|
const pending = this._pendingMessages.get(key) ?? [];
|
|
385
958
|
pending.push(msg);
|
|
386
959
|
this._pendingMessages.set(key, pending);
|
|
387
|
-
connection.send({ type: 'agent:request_start', agentId,
|
|
960
|
+
connection.send({ type: 'agent:request_start', agentId, workspaceId });
|
|
388
961
|
return;
|
|
389
962
|
}
|
|
390
963
|
|
|
391
964
|
if (this.starting.has(key)) {
|
|
392
965
|
// Spawn in progress — queue the message for delivery after start
|
|
393
|
-
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}`);
|
|
394
967
|
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
395
968
|
const pending = this._pendingMessages.get(key) ?? [];
|
|
396
969
|
pending.push(msg);
|
|
@@ -400,7 +973,7 @@ export class AgentManager {
|
|
|
400
973
|
|
|
401
974
|
const text = this._formatDeliveryText(message);
|
|
402
975
|
const agent = this.agents.get(key);
|
|
403
|
-
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}`);
|
|
404
977
|
if (agent?.runtime === 'codex') {
|
|
405
978
|
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
406
979
|
const pending = this._pendingMessages.get(key) ?? [];
|
|
@@ -418,10 +991,10 @@ export class AgentManager {
|
|
|
418
991
|
if (!pending || pending.length === 0) return;
|
|
419
992
|
this._pendingMessages.delete(key);
|
|
420
993
|
for (const msg of pending) {
|
|
421
|
-
const { agentId,
|
|
994
|
+
const { agentId, workspaceId, seq, message } = msg;
|
|
422
995
|
const text = this._formatDeliveryText(message);
|
|
423
996
|
const agent = this.agents.get(key);
|
|
424
|
-
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}`);
|
|
425
998
|
this._write(key, text);
|
|
426
999
|
}
|
|
427
1000
|
}
|
|
@@ -454,7 +1027,7 @@ export class AgentManager {
|
|
|
454
1027
|
}
|
|
455
1028
|
}
|
|
456
1029
|
|
|
457
|
-
_parseKimiLine(key, agentId,
|
|
1030
|
+
_parseKimiLine(key, agentId, workspaceId, line, connection) {
|
|
458
1031
|
const agent = this.agents.get(key);
|
|
459
1032
|
if (!agent) return;
|
|
460
1033
|
const events = parseKimiLine(line, agent.kimiState);
|
|
@@ -463,18 +1036,18 @@ export class AgentManager {
|
|
|
463
1036
|
case 'session_init':
|
|
464
1037
|
if (agent.sessionId !== evt.sessionId) {
|
|
465
1038
|
agent.sessionId = evt.sessionId;
|
|
466
|
-
connection.send({ type: 'agent:session', agentId,
|
|
1039
|
+
connection.send({ type: 'agent:session', agentId, workspaceId, sessionId: evt.sessionId });
|
|
467
1040
|
}
|
|
468
1041
|
break;
|
|
469
1042
|
case 'thinking':
|
|
470
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1043
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'thinking', detail: '', entries: [] });
|
|
471
1044
|
break;
|
|
472
1045
|
case 'tool_call':
|
|
473
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1046
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'working', detail: evt.name, entries: [] });
|
|
474
1047
|
break;
|
|
475
1048
|
case 'turn_end':
|
|
476
1049
|
agent.kimiIdle = true;
|
|
477
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1050
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
478
1051
|
break;
|
|
479
1052
|
case 'error':
|
|
480
1053
|
console.error(`[AgentManager][kimi][${agentId}] Error: ${evt.message}`);
|
|
@@ -483,7 +1056,7 @@ export class AgentManager {
|
|
|
483
1056
|
}
|
|
484
1057
|
}
|
|
485
1058
|
|
|
486
|
-
_parseCodexLine(key, agentId,
|
|
1059
|
+
_parseCodexLine(key, agentId, workspaceId, line, connection) {
|
|
487
1060
|
const agent = this.agents.get(key);
|
|
488
1061
|
if (!agent) return;
|
|
489
1062
|
const events = parseCodexLine(line);
|
|
@@ -492,17 +1065,17 @@ export class AgentManager {
|
|
|
492
1065
|
case 'session_init':
|
|
493
1066
|
if (agent.sessionId !== evt.sessionId) {
|
|
494
1067
|
agent.sessionId = evt.sessionId;
|
|
495
|
-
connection.send({ type: 'agent:session', agentId,
|
|
1068
|
+
connection.send({ type: 'agent:session', agentId, workspaceId, sessionId: evt.sessionId });
|
|
496
1069
|
}
|
|
497
1070
|
break;
|
|
498
1071
|
case 'thinking':
|
|
499
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1072
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'thinking', detail: '', entries: [] });
|
|
500
1073
|
break;
|
|
501
1074
|
case 'tool_call':
|
|
502
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1075
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'working', detail: evt.name, entries: [] });
|
|
503
1076
|
break;
|
|
504
1077
|
case 'turn_end':
|
|
505
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1078
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
506
1079
|
break;
|
|
507
1080
|
case 'error':
|
|
508
1081
|
console.error(`[AgentManager][codex][${agentId}] Error: ${evt.message}`);
|
|
@@ -511,7 +1084,7 @@ export class AgentManager {
|
|
|
511
1084
|
}
|
|
512
1085
|
}
|
|
513
1086
|
|
|
514
|
-
_parseLine(key, agentId,
|
|
1087
|
+
_parseLine(key, agentId, workspaceId, line, connection) {
|
|
515
1088
|
let event;
|
|
516
1089
|
try { event = JSON.parse(line); }
|
|
517
1090
|
catch { return; }
|
|
@@ -521,7 +1094,7 @@ export class AgentManager {
|
|
|
521
1094
|
const agent = this.agents.get(key);
|
|
522
1095
|
if (agent && agent.sessionId !== event.session_id) {
|
|
523
1096
|
agent.sessionId = event.session_id;
|
|
524
|
-
connection.send({ type: 'agent:session', agentId,
|
|
1097
|
+
connection.send({ type: 'agent:session', agentId, workspaceId, sessionId: event.session_id });
|
|
525
1098
|
}
|
|
526
1099
|
}
|
|
527
1100
|
|
|
@@ -536,11 +1109,11 @@ export class AgentManager {
|
|
|
536
1109
|
console.log(`[AgentManager][${displayName}] <text> ${block.text?.slice(0, 500)}`);
|
|
537
1110
|
} else if (block.type === 'tool_use') {
|
|
538
1111
|
console.log(`[AgentManager][${displayName}] <tool_use> ${block.name} params=${JSON.stringify(block.input ?? {}).slice(0, 500)}`);
|
|
539
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1112
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'working', detail: block.name, entries: [] });
|
|
540
1113
|
}
|
|
541
1114
|
}
|
|
542
1115
|
if (!content.some(c => c.type === 'tool_use')) {
|
|
543
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1116
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'thinking', detail: '', entries: [] });
|
|
544
1117
|
}
|
|
545
1118
|
} else if (event.type === 'tool') {
|
|
546
1119
|
const content = event.content;
|
|
@@ -559,7 +1132,7 @@ export class AgentManager {
|
|
|
559
1132
|
}
|
|
560
1133
|
} else if (event.type === 'result') {
|
|
561
1134
|
console.log(`[AgentManager][${displayName}] turn done (stop_reason=${event.stop_reason ?? '?'})`);
|
|
562
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
1135
|
+
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
563
1136
|
}
|
|
564
1137
|
}
|
|
565
1138
|
}
|