@lightcone-ai/daemon 0.6.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent-manager.js +61 -61
- package/src/chat-bridge.js +48 -48
- package/src/drivers/claude.js +35 -35
- package/src/drivers/codex.js +4 -4
- package/src/drivers/kimi.js +2 -2
package/package.json
CHANGED
package/src/agent-manager.js
CHANGED
|
@@ -10,20 +10,20 @@ export class AgentManager {
|
|
|
10
10
|
constructor({ serverUrl, machineApiKey }) {
|
|
11
11
|
this.serverUrl = serverUrl;
|
|
12
12
|
this.machineApiKey = machineApiKey;
|
|
13
|
-
// key: `
|
|
13
|
+
// key: `teamId:agentId` → { config, teamId, agentId, sessionId, proc }
|
|
14
14
|
this.agents = new Map();
|
|
15
15
|
// key → true (spawn in progress)
|
|
16
16
|
this.starting = new Set();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
_key(agentId,
|
|
20
|
-
return `${
|
|
19
|
+
_key(agentId, teamId) {
|
|
20
|
+
return `${teamId ?? ''}:${agentId}`;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
handle(msg, connection) {
|
|
24
24
|
switch (msg.type) {
|
|
25
25
|
case 'agent:start': return this._startAgent(msg, connection);
|
|
26
|
-
case 'agent:stop': return this._stopAgent(msg.agentId, msg.
|
|
26
|
+
case 'agent:stop': return this._stopAgent(msg.agentId, msg.teamId, connection);
|
|
27
27
|
case 'agent:deliver': return this._deliverMessage(msg, connection);
|
|
28
28
|
case 'ping': return connection.send({ type: 'pong' });
|
|
29
29
|
default:
|
|
@@ -40,16 +40,16 @@ export class AgentManager {
|
|
|
40
40
|
|
|
41
41
|
// ── private ───────────────────────────────────────────────────────────────
|
|
42
42
|
|
|
43
|
-
_workspaceDir(agentId,
|
|
44
|
-
const dir =
|
|
45
|
-
? path.join(homedir(), '.lightcone', 'agents',
|
|
43
|
+
_workspaceDir(agentId, teamId) {
|
|
44
|
+
const dir = teamId
|
|
45
|
+
? path.join(homedir(), '.lightcone', 'agents', teamId, agentId)
|
|
46
46
|
: path.join(homedir(), '.lightcone', 'agents', agentId);
|
|
47
47
|
mkdirSync(dir, { recursive: true });
|
|
48
48
|
return dir;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
_formatDeliveryText(message) {
|
|
52
|
-
return `New message in ${message.
|
|
52
|
+
return `New message in ${message.team_type === 'dm' ? 'dm from' : `#${message.team_name} from`} ${message.sender_name}: ${message.content}`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
_takePendingMessage(key) {
|
|
@@ -61,23 +61,23 @@ export class AgentManager {
|
|
|
61
61
|
return msg;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
async _startAgent({ agentId,
|
|
65
|
-
const key = this._key(agentId,
|
|
64
|
+
async _startAgent({ agentId, teamId, teamName, config }, connection) {
|
|
65
|
+
const key = this._key(agentId, teamId);
|
|
66
66
|
if (this.agents.has(key) || this.starting.has(key)) {
|
|
67
|
-
console.log(`[AgentManager] Agent ${agentId} in
|
|
67
|
+
console.log(`[AgentManager] Agent ${agentId} in team ${teamId} already registered`);
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
this.starting.add(key);
|
|
71
71
|
|
|
72
72
|
const runtime = config.runtime ?? 'claude';
|
|
73
|
-
const workspaceDir = this._workspaceDir(agentId,
|
|
73
|
+
const workspaceDir = this._workspaceDir(agentId, teamId);
|
|
74
74
|
const chatBridgePath = new URL('./chat-bridge.js', import.meta.url).pathname;
|
|
75
75
|
const startupMsg = runtime === 'codex' ? this._takePendingMessage(key) : null;
|
|
76
76
|
|
|
77
77
|
// Fetch skills index for system prompt + MCP derivation (non-blocking on failure)
|
|
78
78
|
let skills = [];
|
|
79
79
|
try {
|
|
80
|
-
const chParam =
|
|
80
|
+
const chParam = teamId ? `?teamId=${encodeURIComponent(teamId)}` : '';
|
|
81
81
|
const res = await fetch(`${this.serverUrl}/internal/agent/${agentId}/skills${chParam}`, {
|
|
82
82
|
headers: { 'Authorization': `Bearer ${this.machineApiKey}` },
|
|
83
83
|
});
|
|
@@ -91,11 +91,11 @@ export class AgentManager {
|
|
|
91
91
|
if (runtime === 'kimi') {
|
|
92
92
|
// ── Kimi CLI ──────────────────────────────────────────────────────────
|
|
93
93
|
const kimiSpawn = buildKimiSpawn({
|
|
94
|
-
config, agentId,
|
|
94
|
+
config, agentId, teamId, workspaceDir, chatBridgePath,
|
|
95
95
|
serverUrl: this.serverUrl, machineApiKey: this.machineApiKey, skills,
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
console.log(`[AgentManager] Spawning kimi for ${config.displayName ?? agentId}
|
|
98
|
+
console.log(`[AgentManager] Spawning kimi for ${config.displayName ?? agentId} team=${teamName ?? teamId ?? 'none'} (session=${kimiSpawn.sessionId})`);
|
|
99
99
|
|
|
100
100
|
proc = spawn('kimi', kimiSpawn.args, {
|
|
101
101
|
cwd: workspaceDir,
|
|
@@ -113,7 +113,7 @@ export class AgentManager {
|
|
|
113
113
|
const kimiState = { sessionId: kimiSpawn.sessionId, sessionAnnounced: false };
|
|
114
114
|
|
|
115
115
|
this.agents.set(key, {
|
|
116
|
-
config,
|
|
116
|
+
config, teamId, agentId, sessionId: kimiSpawn.sessionId, proc,
|
|
117
117
|
runtime: 'kimi', kimiState, kimiIdle: false,
|
|
118
118
|
});
|
|
119
119
|
this.starting.delete(key);
|
|
@@ -125,7 +125,7 @@ export class AgentManager {
|
|
|
125
125
|
buffer = lines.pop();
|
|
126
126
|
for (const line of lines) {
|
|
127
127
|
if (!line.trim()) continue;
|
|
128
|
-
this._parseKimiLine(key, agentId,
|
|
128
|
+
this._parseKimiLine(key, agentId, teamId, line, connection);
|
|
129
129
|
}
|
|
130
130
|
});
|
|
131
131
|
} else if (runtime === 'codex') {
|
|
@@ -136,7 +136,7 @@ export class AgentManager {
|
|
|
136
136
|
const codexSpawn = buildCodexSpawn({
|
|
137
137
|
config,
|
|
138
138
|
agentId,
|
|
139
|
-
|
|
139
|
+
teamId,
|
|
140
140
|
workspaceDir,
|
|
141
141
|
chatBridgePath,
|
|
142
142
|
serverUrl: this.serverUrl,
|
|
@@ -145,7 +145,7 @@ export class AgentManager {
|
|
|
145
145
|
skills,
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
console.log(`[AgentManager] Spawning codex for ${config.displayName ?? agentId}
|
|
148
|
+
console.log(`[AgentManager] Spawning codex for ${config.displayName ?? agentId} team=${teamName ?? teamId ?? 'none'} (session=${config.sessionId ?? 'new'})`);
|
|
149
149
|
|
|
150
150
|
proc = spawn('codex', codexSpawn.args, {
|
|
151
151
|
cwd: workspaceDir,
|
|
@@ -154,7 +154,7 @@ export class AgentManager {
|
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
this.agents.set(key, {
|
|
157
|
-
config,
|
|
157
|
+
config, teamId, agentId, sessionId: config.sessionId ?? null, proc,
|
|
158
158
|
runtime: 'codex',
|
|
159
159
|
});
|
|
160
160
|
this.starting.delete(key);
|
|
@@ -166,7 +166,7 @@ export class AgentManager {
|
|
|
166
166
|
buffer = lines.pop();
|
|
167
167
|
for (const line of lines) {
|
|
168
168
|
if (!line.trim()) continue;
|
|
169
|
-
this._parseCodexLine(key, agentId,
|
|
169
|
+
this._parseCodexLine(key, agentId, teamId, line, connection);
|
|
170
170
|
}
|
|
171
171
|
});
|
|
172
172
|
} else {
|
|
@@ -179,7 +179,7 @@ export class AgentManager {
|
|
|
179
179
|
SERVER_URL: this.serverUrl,
|
|
180
180
|
MACHINE_API_KEY: this.machineApiKey,
|
|
181
181
|
AGENT_ID: agentId,
|
|
182
|
-
|
|
182
|
+
TEAM_ID: teamId ?? '',
|
|
183
183
|
},
|
|
184
184
|
},
|
|
185
185
|
};
|
|
@@ -235,7 +235,7 @@ export class AgentManager {
|
|
|
235
235
|
const spawnEnv = { ...process.env, FORCE_COLOR: '0', ...(config.envVars ?? {}) };
|
|
236
236
|
delete spawnEnv.CLAUDECODE;
|
|
237
237
|
|
|
238
|
-
console.log(`[AgentManager] Spawning claude for ${config.displayName ?? agentId}
|
|
238
|
+
console.log(`[AgentManager] Spawning claude for ${config.displayName ?? agentId} team=${teamName ?? teamId ?? 'none'} (session=${config.sessionId ?? 'new'})`);
|
|
239
239
|
|
|
240
240
|
proc = spawn('claude', args, {
|
|
241
241
|
cwd: workspaceDir,
|
|
@@ -243,7 +243,7 @@ export class AgentManager {
|
|
|
243
243
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
244
244
|
});
|
|
245
245
|
|
|
246
|
-
this.agents.set(key, { config,
|
|
246
|
+
this.agents.set(key, { config, teamId, agentId, sessionId: config.sessionId ?? null, proc, runtime: 'claude' });
|
|
247
247
|
this.starting.delete(key);
|
|
248
248
|
|
|
249
249
|
// Parse stdout stream for session ID and activity updates
|
|
@@ -254,24 +254,24 @@ export class AgentManager {
|
|
|
254
254
|
buffer = lines.pop();
|
|
255
255
|
for (const line of lines) {
|
|
256
256
|
if (!line.trim()) continue;
|
|
257
|
-
this._parseLine(key, agentId,
|
|
257
|
+
this._parseLine(key, agentId, teamId, line, connection);
|
|
258
258
|
}
|
|
259
259
|
});
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
proc.stderr.on('data', (data) => {
|
|
263
263
|
const text = data.toString().trim();
|
|
264
|
-
if (text) console.error(`[AgentManager][${agentId}][${
|
|
264
|
+
if (text) console.error(`[AgentManager][${agentId}][${teamId}] stderr:`, text.slice(0, 300));
|
|
265
265
|
});
|
|
266
266
|
|
|
267
267
|
proc.on('exit', (code) => {
|
|
268
268
|
const agent = this.agents.get(key);
|
|
269
|
-
console.log(`[AgentManager] Agent ${agentId}
|
|
269
|
+
console.log(`[AgentManager] Agent ${agentId} team=${teamId ?? 'none'} exited (code=${code})`);
|
|
270
270
|
this.agents.delete(key);
|
|
271
271
|
|
|
272
272
|
if (code === 0 && runtime === 'codex' && this._pendingMessages?.get(key)?.length) {
|
|
273
273
|
const restartConfig = { ...config, sessionId: agent?.sessionId ?? config.sessionId ?? null };
|
|
274
|
-
this._startAgent({ agentId,
|
|
274
|
+
this._startAgent({ agentId, teamId, config: restartConfig }, connection);
|
|
275
275
|
return;
|
|
276
276
|
}
|
|
277
277
|
|
|
@@ -279,14 +279,14 @@ export class AgentManager {
|
|
|
279
279
|
if (code !== 0 && config.sessionId && !this._retried?.has(key)) {
|
|
280
280
|
if (!this._retried) this._retried = new Set();
|
|
281
281
|
this._retried.add(key);
|
|
282
|
-
console.log(`[AgentManager] Retrying ${agentId}
|
|
282
|
+
console.log(`[AgentManager] Retrying ${agentId} team=${teamId} without session (session may not exist locally)`);
|
|
283
283
|
const retryConfig = { ...config, sessionId: null };
|
|
284
|
-
this._startAgent({ agentId,
|
|
284
|
+
this._startAgent({ agentId, teamId, config: retryConfig }, connection);
|
|
285
285
|
return;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
connection.send({ type: 'agent:status', agentId,
|
|
289
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
288
|
+
connection.send({ type: 'agent:status', agentId, teamId, status: 'inactive' });
|
|
289
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'offline', detail: '', entries: [] });
|
|
290
290
|
});
|
|
291
291
|
|
|
292
292
|
// Send startup prompt
|
|
@@ -296,41 +296,41 @@ export class AgentManager {
|
|
|
296
296
|
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.');
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
connection.send({ type: 'agent:status', agentId,
|
|
300
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
299
|
+
connection.send({ type: 'agent:status', agentId, teamId, status: 'active' });
|
|
300
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'online', detail: '', entries: [] });
|
|
301
301
|
|
|
302
302
|
// Flush any messages that arrived while the agent was starting
|
|
303
303
|
this._flushPending(key, connection);
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
_stopAgent(agentId,
|
|
307
|
-
const key = this._key(agentId,
|
|
306
|
+
_stopAgent(agentId, teamId, connection) {
|
|
307
|
+
const key = this._key(agentId, teamId);
|
|
308
308
|
const agent = this.agents.get(key);
|
|
309
309
|
if (!agent) return;
|
|
310
|
-
console.log(`[AgentManager] Stopping agent ${agentId}
|
|
310
|
+
console.log(`[AgentManager] Stopping agent ${agentId} team=${teamId ?? 'none'}`);
|
|
311
311
|
agent.proc?.kill();
|
|
312
312
|
// exit handler will report status
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
_deliverMessage(msg, connection) {
|
|
316
|
-
const { agentId,
|
|
317
|
-
const key = this._key(agentId,
|
|
318
|
-
connection.send({ type: 'agent:deliver:ack', agentId,
|
|
316
|
+
const { agentId, teamId, seq, message } = msg;
|
|
317
|
+
const key = this._key(agentId, teamId);
|
|
318
|
+
connection.send({ type: 'agent:deliver:ack', agentId, teamId, seq });
|
|
319
319
|
|
|
320
320
|
if (!this.agents.has(key) && !this.starting.has(key)) {
|
|
321
321
|
// Agent not running — queue the message and request config to spawn it
|
|
322
|
-
console.log(`[AgentManager] Agent ${agentId}
|
|
322
|
+
console.log(`[AgentManager] Agent ${agentId} team=${teamId} not running, requesting start for seq=${seq}`);
|
|
323
323
|
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
324
324
|
const pending = this._pendingMessages.get(key) ?? [];
|
|
325
325
|
pending.push(msg);
|
|
326
326
|
this._pendingMessages.set(key, pending);
|
|
327
|
-
connection.send({ type: 'agent:request_start', agentId,
|
|
327
|
+
connection.send({ type: 'agent:request_start', agentId, teamId });
|
|
328
328
|
return;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
if (this.starting.has(key)) {
|
|
332
332
|
// Spawn in progress — queue the message for delivery after start
|
|
333
|
-
console.log(`[AgentManager] Agent ${agentId}
|
|
333
|
+
console.log(`[AgentManager] Agent ${agentId} team=${teamId} still starting, queuing seq=${seq}`);
|
|
334
334
|
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
335
335
|
const pending = this._pendingMessages.get(key) ?? [];
|
|
336
336
|
pending.push(msg);
|
|
@@ -339,7 +339,7 @@ export class AgentManager {
|
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
const text = this._formatDeliveryText(message);
|
|
342
|
-
console.log(`[AgentManager] Delivering seq=${seq} to agent ${agentId}
|
|
342
|
+
console.log(`[AgentManager] Delivering seq=${seq} to agent ${agentId} team=${teamId}`);
|
|
343
343
|
const agent = this.agents.get(key);
|
|
344
344
|
if (agent?.runtime === 'codex') {
|
|
345
345
|
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
@@ -358,9 +358,9 @@ export class AgentManager {
|
|
|
358
358
|
if (!pending || pending.length === 0) return;
|
|
359
359
|
this._pendingMessages.delete(key);
|
|
360
360
|
for (const msg of pending) {
|
|
361
|
-
const { agentId,
|
|
361
|
+
const { agentId, teamId, seq, message } = msg;
|
|
362
362
|
const text = this._formatDeliveryText(message);
|
|
363
|
-
console.log(`[AgentManager] Flushing queued seq=${seq} to agent ${agentId}
|
|
363
|
+
console.log(`[AgentManager] Flushing queued seq=${seq} to agent ${agentId} team=${teamId}`);
|
|
364
364
|
this._write(key, text);
|
|
365
365
|
}
|
|
366
366
|
}
|
|
@@ -393,7 +393,7 @@ export class AgentManager {
|
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
-
_parseKimiLine(key, agentId,
|
|
396
|
+
_parseKimiLine(key, agentId, teamId, line, connection) {
|
|
397
397
|
const agent = this.agents.get(key);
|
|
398
398
|
if (!agent) return;
|
|
399
399
|
const events = parseKimiLine(line, agent.kimiState);
|
|
@@ -402,18 +402,18 @@ export class AgentManager {
|
|
|
402
402
|
case 'session_init':
|
|
403
403
|
if (agent.sessionId !== evt.sessionId) {
|
|
404
404
|
agent.sessionId = evt.sessionId;
|
|
405
|
-
connection.send({ type: 'agent:session', agentId,
|
|
405
|
+
connection.send({ type: 'agent:session', agentId, teamId, sessionId: evt.sessionId });
|
|
406
406
|
}
|
|
407
407
|
break;
|
|
408
408
|
case 'thinking':
|
|
409
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
409
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'thinking', detail: '', entries: [] });
|
|
410
410
|
break;
|
|
411
411
|
case 'tool_call':
|
|
412
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
412
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'working', detail: evt.name, entries: [] });
|
|
413
413
|
break;
|
|
414
414
|
case 'turn_end':
|
|
415
415
|
agent.kimiIdle = true;
|
|
416
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
416
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'online', detail: '', entries: [] });
|
|
417
417
|
break;
|
|
418
418
|
case 'error':
|
|
419
419
|
console.error(`[AgentManager][kimi][${agentId}] Error: ${evt.message}`);
|
|
@@ -422,7 +422,7 @@ export class AgentManager {
|
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
-
_parseCodexLine(key, agentId,
|
|
425
|
+
_parseCodexLine(key, agentId, teamId, line, connection) {
|
|
426
426
|
const agent = this.agents.get(key);
|
|
427
427
|
if (!agent) return;
|
|
428
428
|
const events = parseCodexLine(line);
|
|
@@ -431,17 +431,17 @@ export class AgentManager {
|
|
|
431
431
|
case 'session_init':
|
|
432
432
|
if (agent.sessionId !== evt.sessionId) {
|
|
433
433
|
agent.sessionId = evt.sessionId;
|
|
434
|
-
connection.send({ type: 'agent:session', agentId,
|
|
434
|
+
connection.send({ type: 'agent:session', agentId, teamId, sessionId: evt.sessionId });
|
|
435
435
|
}
|
|
436
436
|
break;
|
|
437
437
|
case 'thinking':
|
|
438
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
438
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'thinking', detail: '', entries: [] });
|
|
439
439
|
break;
|
|
440
440
|
case 'tool_call':
|
|
441
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
441
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'working', detail: evt.name, entries: [] });
|
|
442
442
|
break;
|
|
443
443
|
case 'turn_end':
|
|
444
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
444
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'online', detail: '', entries: [] });
|
|
445
445
|
break;
|
|
446
446
|
case 'error':
|
|
447
447
|
console.error(`[AgentManager][codex][${agentId}] Error: ${evt.message}`);
|
|
@@ -450,7 +450,7 @@ export class AgentManager {
|
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
-
_parseLine(key, agentId,
|
|
453
|
+
_parseLine(key, agentId, teamId, line, connection) {
|
|
454
454
|
let event;
|
|
455
455
|
try { event = JSON.parse(line); }
|
|
456
456
|
catch { return; }
|
|
@@ -460,7 +460,7 @@ export class AgentManager {
|
|
|
460
460
|
const agent = this.agents.get(key);
|
|
461
461
|
if (agent && agent.sessionId !== event.session_id) {
|
|
462
462
|
agent.sessionId = event.session_id;
|
|
463
|
-
connection.send({ type: 'agent:session', agentId,
|
|
463
|
+
connection.send({ type: 'agent:session', agentId, teamId, sessionId: event.session_id });
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
466
|
|
|
@@ -469,12 +469,12 @@ export class AgentManager {
|
|
|
469
469
|
const hasToolUse = event.message?.content?.some?.(c => c.type === 'tool_use');
|
|
470
470
|
if (hasToolUse) {
|
|
471
471
|
const toolName = event.message.content.find(c => c.type === 'tool_use')?.name ?? 'tool';
|
|
472
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
472
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'working', detail: toolName, entries: [] });
|
|
473
473
|
} else {
|
|
474
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
474
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'thinking', detail: '', entries: [] });
|
|
475
475
|
}
|
|
476
476
|
} else if (event.type === 'result') {
|
|
477
|
-
connection.send({ type: 'agent:activity', agentId,
|
|
477
|
+
connection.send({ type: 'agent:activity', agentId, teamId, activity: 'online', detail: '', entries: [] });
|
|
478
478
|
}
|
|
479
479
|
}
|
|
480
480
|
}
|
package/src/chat-bridge.js
CHANGED
|
@@ -12,10 +12,10 @@ function getArg(name) {
|
|
|
12
12
|
const SERVER_URL = process.env.SERVER_URL || getArg('--server-url') || 'http://localhost:8777';
|
|
13
13
|
const MACHINE_API_KEY = process.env.MACHINE_API_KEY || getArg('--auth-token') || '';
|
|
14
14
|
const AGENT_ID = process.env.AGENT_ID || getArg('--agent-id') || '';
|
|
15
|
-
const
|
|
15
|
+
const TEAM_ID = process.env.TEAM_ID || getArg('--team-id') || ''; // injected per-team at spawn time
|
|
16
16
|
|
|
17
|
-
// Current active
|
|
18
|
-
let
|
|
17
|
+
// Current active teamId for memory isolation (defaults to spawn-time TEAM_ID)
|
|
18
|
+
let currentTeamId = TEAM_ID;
|
|
19
19
|
|
|
20
20
|
async function api(method, path, body) {
|
|
21
21
|
const url = `${SERVER_URL}/internal/agent/${AGENT_ID}${path}`;
|
|
@@ -42,20 +42,20 @@ server.tool('check_messages', 'Check for new messages in your inbox', {}, async
|
|
|
42
42
|
const msgs = data.messages ?? [];
|
|
43
43
|
if (msgs.length === 0) return { content: [{ type: 'text', text: 'No new messages.' }] };
|
|
44
44
|
|
|
45
|
-
// Track the
|
|
45
|
+
// Track the teamId of the most recent message for memory isolation
|
|
46
46
|
const lastMsg = msgs[msgs.length - 1];
|
|
47
|
-
if (lastMsg.
|
|
47
|
+
if (lastMsg.team_id) currentTeamId = lastMsg.team_id;
|
|
48
48
|
|
|
49
49
|
const text = msgs.map(m =>
|
|
50
|
-
`[${m.
|
|
50
|
+
`[${m.team_type === 'dm' ? `dm:@${m.team_name}` : `#${m.team_name}`}] ${m.sender_name}: ${m.content}`
|
|
51
51
|
+ (m.task_status ? ` [task #${m.task_number} ${m.task_status}]` : '')
|
|
52
52
|
).join('\n');
|
|
53
53
|
return { content: [{ type: 'text', text }] };
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// ── send_message ──────────────────────────────────────────────────────────────
|
|
57
|
-
server.tool('send_message', 'Send a message to a
|
|
58
|
-
target: z.string().describe('Target: #
|
|
57
|
+
server.tool('send_message', 'Send a message to a team, DM, or thread', {
|
|
58
|
+
target: z.string().describe('Target: #team-name | dm:@agentName | #team-name:shortMsgId'),
|
|
59
59
|
content: z.string().describe('Message content'),
|
|
60
60
|
}, async ({ target, content }) => {
|
|
61
61
|
const data = await api('POST', '/send', { target, content });
|
|
@@ -63,14 +63,14 @@ server.tool('send_message', 'Send a message to a channel, DM, or thread', {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
// ── read_history ──────────────────────────────────────────────────────────────
|
|
66
|
-
server.tool('read_history', 'Read message history from a
|
|
67
|
-
|
|
66
|
+
server.tool('read_history', 'Read message history from a team, DM, or thread. Supports pagination via before/after and context jumps via around.', {
|
|
67
|
+
team: z.string().describe('Target: #team-name | dm:@agentName | #team-name:shortMsgId'),
|
|
68
68
|
limit: z.number().optional().describe('Max messages to return (default 50, max 100)'),
|
|
69
69
|
around: z.union([z.string(), z.number()]).optional().describe('Center the result window around a messageId or seq number'),
|
|
70
70
|
before: z.number().optional().describe('Return messages before this seq'),
|
|
71
71
|
after: z.number().optional().describe('Return messages after this seq'),
|
|
72
|
-
}, async ({
|
|
73
|
-
const params = new URLSearchParams({
|
|
72
|
+
}, async ({ team, limit, around, before, after }) => {
|
|
73
|
+
const params = new URLSearchParams({ team, limit: String(Math.min(limit ?? 50, 100)) });
|
|
74
74
|
if (around != null) params.set('around', String(around));
|
|
75
75
|
if (before != null) params.set('before', String(before));
|
|
76
76
|
if (after != null) params.set('after', String(after));
|
|
@@ -97,29 +97,29 @@ server.tool('read_history', 'Read message history from a channel, DM, or thread.
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
return { content: [{ type: 'text', text: `## History for ${
|
|
100
|
+
return { content: [{ type: 'text', text: `## History for ${team} (${msgs.length} messages)\n\n${text}${footer}` }] };
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
// ── search_messages ──────────────────────────────────────────────────────────
|
|
104
|
-
server.tool('search_messages', 'Search messages visible to you. Use this to find relevant conversations, then inspect a hit with read_history(
|
|
104
|
+
server.tool('search_messages', 'Search messages visible to you. Use this to find relevant conversations, then inspect a hit with read_history(team=..., around=messageId).', {
|
|
105
105
|
query: z.string().describe('Search query'),
|
|
106
|
-
|
|
106
|
+
team: z.string().optional().describe('Optional target to scope the search, e.g. "#general", "dm:@richard"'),
|
|
107
107
|
limit: z.number().optional().describe('Max results (default 10, max 20)'),
|
|
108
|
-
}, async ({ query,
|
|
108
|
+
}, async ({ query, team, limit }) => {
|
|
109
109
|
const trimmed = query.trim();
|
|
110
110
|
if (!trimmed) return { content: [{ type: 'text', text: 'Search query cannot be empty.' }] };
|
|
111
111
|
const params = new URLSearchParams({ q: trimmed, limit: String(Math.min(limit ?? 10, 20)) });
|
|
112
|
-
if (
|
|
112
|
+
if (team) params.set('team', team);
|
|
113
113
|
try {
|
|
114
114
|
const data = await api('GET', `/search?${params}`);
|
|
115
115
|
if (!data.results || data.results.length === 0)
|
|
116
116
|
return { content: [{ type: 'text', text: 'No search results.' }] };
|
|
117
117
|
const formatted = data.results.map((r, i) => [
|
|
118
118
|
`[${i + 1}] msg=${r.id} seq=${r.seq} time=${r.createdAt}`,
|
|
119
|
-
`
|
|
119
|
+
`team: #${r.teamName}`,
|
|
120
120
|
`sender: @${r.senderName}${r.senderType === 'agent' ? ' (agent)' : ''}`,
|
|
121
121
|
`content: ${r.snippet}`,
|
|
122
|
-
`next: read_history(
|
|
122
|
+
`next: read_history(team="#${r.teamName}", around="${r.id}", limit=20)`,
|
|
123
123
|
].join('\n')).join('\n\n');
|
|
124
124
|
return { content: [{ type: 'text', text: `## Search Results for "${trimmed}" (${data.results.length} results)\n\n${formatted}` }] };
|
|
125
125
|
} catch (err) {
|
|
@@ -160,20 +160,20 @@ server.tool('view_file', 'Download an attached image by its attachment ID and sa
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
// ── list_server ───────────────────────────────────────────────────────────────
|
|
163
|
-
server.tool('list_server', 'List
|
|
163
|
+
server.tool('list_server', 'List teams, agents, and humans on the server', {}, async () => {
|
|
164
164
|
const data = await api('GET', '/server');
|
|
165
|
-
const
|
|
165
|
+
const teams = (data.teams ?? []).map(c => ` #${c.name}${c.joined ? ' (joined)' : ''} — ${c.description}`).join('\n');
|
|
166
166
|
const agents = (data.agents ?? []).map(a => ` @${a.name} [${a.status}]`).join('\n');
|
|
167
167
|
const humans = (data.humans ?? []).map(h => ` @${h.name}`).join('\n');
|
|
168
|
-
return { content: [{ type: 'text', text: `
|
|
168
|
+
return { content: [{ type: 'text', text: `Teams:\n${teams}\n\nAgents:\n${agents}\n\nHumans:\n${humans}` }] };
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
// ── list_tasks ────────────────────────────────────────────────────────────────
|
|
172
|
-
server.tool('list_tasks', 'List tasks in a
|
|
173
|
-
|
|
172
|
+
server.tool('list_tasks', 'List tasks in a team', {
|
|
173
|
+
team: z.string().describe('Target: #team-name'),
|
|
174
174
|
status: z.enum(['all', 'todo', 'in_progress', 'in_review', 'done']).optional(),
|
|
175
|
-
}, async ({
|
|
176
|
-
const params = new URLSearchParams({
|
|
175
|
+
}, async ({ team, status }) => {
|
|
176
|
+
const params = new URLSearchParams({ team, status: status ?? 'all' });
|
|
177
177
|
const data = await api('GET', `/tasks?${params}`);
|
|
178
178
|
const tasks = data.tasks ?? [];
|
|
179
179
|
if (tasks.length === 0) return { content: [{ type: 'text', text: 'No tasks found.' }] };
|
|
@@ -186,22 +186,22 @@ server.tool('list_tasks', 'List tasks in a channel', {
|
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
// ── create_tasks ──────────────────────────────────────────────────────────────
|
|
189
|
-
server.tool('create_tasks', 'Create one or more tasks in a
|
|
190
|
-
|
|
189
|
+
server.tool('create_tasks', 'Create one or more tasks in a team', {
|
|
190
|
+
team: z.string().describe('Target: #team-name'),
|
|
191
191
|
tasks: z.array(z.object({ title: z.string() })).describe('Array of tasks to create'),
|
|
192
|
-
}, async ({
|
|
193
|
-
const data = await api('POST', '/tasks', {
|
|
192
|
+
}, async ({ team, tasks }) => {
|
|
193
|
+
const data = await api('POST', '/tasks', { team, tasks });
|
|
194
194
|
const created = (data.tasks ?? []).map(t => `#${t.taskNumber} ${t.title}`).join('\n');
|
|
195
195
|
return { content: [{ type: 'text', text: `Created:\n${created}` }] };
|
|
196
196
|
});
|
|
197
197
|
|
|
198
198
|
// ── claim_tasks ───────────────────────────────────────────────────────────────
|
|
199
199
|
server.tool('claim_tasks', 'Claim one or more tasks to work on', {
|
|
200
|
-
|
|
200
|
+
team: z.string().describe('Target: #team-name'),
|
|
201
201
|
task_numbers: z.array(z.number()).optional().describe('Task numbers to claim'),
|
|
202
202
|
message_ids: z.array(z.string()).optional().describe('Short message IDs to claim'),
|
|
203
|
-
}, async ({
|
|
204
|
-
const data = await api('POST', '/tasks/claim', {
|
|
203
|
+
}, async ({ team, task_numbers, message_ids }) => {
|
|
204
|
+
const data = await api('POST', '/tasks/claim', { team, task_numbers, message_ids });
|
|
205
205
|
const results = (data.results ?? []).map(r =>
|
|
206
206
|
`#${r.taskNumber}: ${r.success ? 'claimed' : `failed (${r.reason})`}`
|
|
207
207
|
).join('\n');
|
|
@@ -210,26 +210,26 @@ server.tool('claim_tasks', 'Claim one or more tasks to work on', {
|
|
|
210
210
|
|
|
211
211
|
// ── unclaim_task ──────────────────────────────────────────────────────────────
|
|
212
212
|
server.tool('unclaim_task', 'Release a claimed task', {
|
|
213
|
-
|
|
213
|
+
team: z.string().describe('Target: #team-name'),
|
|
214
214
|
task_number: z.number().describe('Task number to unclaim'),
|
|
215
|
-
}, async ({
|
|
216
|
-
await api('POST', '/tasks/unclaim', {
|
|
215
|
+
}, async ({ team, task_number }) => {
|
|
216
|
+
await api('POST', '/tasks/unclaim', { team, task_number });
|
|
217
217
|
return { content: [{ type: 'text', text: `Task #${task_number} unclaimed.` }] };
|
|
218
218
|
});
|
|
219
219
|
|
|
220
220
|
// ── update_task_status ────────────────────────────────────────────────────────
|
|
221
221
|
server.tool('update_task_status', 'Update the status of a task', {
|
|
222
|
-
|
|
222
|
+
team: z.string().describe('Target: #team-name'),
|
|
223
223
|
task_number: z.number().describe('Task number'),
|
|
224
224
|
status: z.enum(['todo', 'in_progress', 'in_review', 'done']).describe('New status'),
|
|
225
|
-
}, async ({
|
|
226
|
-
await api('POST', '/tasks/update-status', {
|
|
225
|
+
}, async ({ team, task_number, status }) => {
|
|
226
|
+
await api('POST', '/tasks/update-status', { team, task_number, status });
|
|
227
227
|
return { content: [{ type: 'text', text: `Task #${task_number} → ${status}` }] };
|
|
228
228
|
});
|
|
229
229
|
|
|
230
230
|
// ── list_memory ───────────────────────────────────────────────────────────────
|
|
231
|
-
server.tool('list_memory', 'List all memory files stored for this agent in the current
|
|
232
|
-
const chParam =
|
|
231
|
+
server.tool('list_memory', 'List all memory files stored for this agent in the current team', {}, async () => {
|
|
232
|
+
const chParam = currentTeamId ? `&teamId=${encodeURIComponent(currentTeamId)}` : '';
|
|
233
233
|
const data = await api('GET', `/memory?_=1${chParam}`);
|
|
234
234
|
const files = data.files ?? [];
|
|
235
235
|
if (files.length === 0) return { content: [{ type: 'text', text: 'No memory files yet.' }] };
|
|
@@ -238,9 +238,9 @@ server.tool('list_memory', 'List all memory files stored for this agent in the c
|
|
|
238
238
|
|
|
239
239
|
// ── read_memory ───────────────────────────────────────────────────────────────
|
|
240
240
|
server.tool('read_memory', 'Read a memory file by path (e.g. "MEMORY.md" or "notes/work-log.md")', {
|
|
241
|
-
path: z.string().describe('File path, e.g. "MEMORY.md" or "notes/
|
|
241
|
+
path: z.string().describe('File path, e.g. "MEMORY.md" or "notes/teams.md"'),
|
|
242
242
|
}, async ({ path }) => {
|
|
243
|
-
const chParam =
|
|
243
|
+
const chParam = currentTeamId ? `&teamId=${encodeURIComponent(currentTeamId)}` : '';
|
|
244
244
|
try {
|
|
245
245
|
const data = await api('GET', `/memory?path=${encodeURIComponent(path)}${chParam}`);
|
|
246
246
|
return { content: [{ type: 'text', text: data.content }] };
|
|
@@ -255,14 +255,14 @@ server.tool('write_memory', 'Write or update a memory file (full content replace
|
|
|
255
255
|
path: z.string().describe('File path, e.g. "MEMORY.md" or "notes/work-log.md"'),
|
|
256
256
|
content: z.string().describe('Full file content to store'),
|
|
257
257
|
}, async ({ path, content }) => {
|
|
258
|
-
const chParam =
|
|
258
|
+
const chParam = currentTeamId ? `&teamId=${encodeURIComponent(currentTeamId)}` : '';
|
|
259
259
|
await api('PUT', `/memory?path=${encodeURIComponent(path)}${chParam}`, { content });
|
|
260
260
|
return { content: [{ type: 'text', text: `Saved ${path}` }] };
|
|
261
261
|
});
|
|
262
262
|
|
|
263
263
|
// ── skill_list ───────────────────────────────────────────────────────────────
|
|
264
264
|
server.tool('skill_list', 'List all skills available to you (platform + bound). Returns index only (name + description), not full content.', {}, async () => {
|
|
265
|
-
const chParam =
|
|
265
|
+
const chParam = currentTeamId ? `?teamId=${encodeURIComponent(currentTeamId)}` : '';
|
|
266
266
|
const skills = await api('GET', `/skills${chParam}`);
|
|
267
267
|
if (!skills || skills.length === 0) return { content: [{ type: 'text', text: 'No skills available.' }] };
|
|
268
268
|
const lines = skills.map(s => `- [${s.type}] **${s.name}** — ${s.description}`);
|
|
@@ -283,16 +283,16 @@ server.tool('skill_read', 'Read the full content of a skill by name or ID', {
|
|
|
283
283
|
});
|
|
284
284
|
|
|
285
285
|
// ── skill_create ─────────────────────────────────────────────────────────────
|
|
286
|
-
server.tool('skill_create', 'Create a new reusable skill from what you have learned. Auto-binds to the current
|
|
286
|
+
server.tool('skill_create', 'Create a new reusable skill from what you have learned. Auto-binds to the current team.', {
|
|
287
287
|
name: z.string().describe('Short skill name (lowercase, hyphens ok), e.g. "xhs-posting"'),
|
|
288
288
|
description: z.string().describe('One-line description of what this skill covers'),
|
|
289
289
|
content: z.string().describe('Full skill content in markdown — procedures, steps, tips'),
|
|
290
290
|
tags: z.array(z.string()).optional().describe('Optional tags for categorization'),
|
|
291
291
|
}, async ({ name, description, content, tags }) => {
|
|
292
292
|
const body = { name, description, content, tags: tags ?? [] };
|
|
293
|
-
if (
|
|
293
|
+
if (currentTeamId) body.teamId = currentTeamId;
|
|
294
294
|
const result = await api('POST', '/skills', body);
|
|
295
|
-
return { content: [{ type: 'text', text: `Skill "${result.name}" created and bound to current
|
|
295
|
+
return { content: [{ type: 'text', text: `Skill "${result.name}" created and bound to current team.` }] };
|
|
296
296
|
});
|
|
297
297
|
|
|
298
298
|
// ── skill_update ─────────────────────────────────────────────────────────────
|
package/src/drivers/claude.js
CHANGED
|
@@ -13,12 +13,12 @@ Your workspace and MEMORY.md persist across turns, so you can recover context wh
|
|
|
13
13
|
You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
14
14
|
|
|
15
15
|
1. **${t("check_messages")}** — Non-blocking check for new messages. Use freely during work — at natural breakpoints or after notifications.
|
|
16
|
-
2. **${t("send_message")}** — Send a message to a
|
|
17
|
-
3. **${t("list_server")}** — List all
|
|
18
|
-
4. **${t("read_history")}** — Read past messages from a
|
|
16
|
+
2. **${t("send_message")}** — Send a message to a team or DM.
|
|
17
|
+
3. **${t("list_server")}** — List all teams in this server, which ones you have joined, plus all agents and humans.
|
|
18
|
+
4. **${t("read_history")}** — Read past messages from a team, DM, or thread. Supports \`before\` / \`after\` pagination and \`around\` for centered context.
|
|
19
19
|
5. **${t("search_messages")}** — Search messages visible to you, then inspect a hit with \`${t("read_history")}\`.
|
|
20
|
-
6. **${t("list_tasks")}** — View a
|
|
21
|
-
7. **${t("create_tasks")}** — Create new task-messages in a
|
|
20
|
+
6. **${t("list_tasks")}** — View a team's task board.
|
|
21
|
+
7. **${t("create_tasks")}** — Create new task-messages in a team (supports batch; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
|
|
22
22
|
8. **${t("claim_tasks")}** — Claim tasks by number (supports batch, handles conflicts).
|
|
23
23
|
9. **${t("unclaim_task")}** — Release your claim on a task.
|
|
24
24
|
10. **${t("update_task_status")}** — Change a task's status (e.g. to in_review or done).
|
|
@@ -58,40 +58,40 @@ Header fields:
|
|
|
58
58
|
|
|
59
59
|
### Sending messages
|
|
60
60
|
|
|
61
|
-
- **Reply to a
|
|
61
|
+
- **Reply to a team**: \`send_message(target="#team-name", content="...")\`
|
|
62
62
|
- **Reply to a DM**: \`send_message(target="dm:@peer-name", content="...")\`
|
|
63
|
-
- **Reply in a thread**: \`send_message(target="#
|
|
63
|
+
- **Reply in a thread**: \`send_message(target="#team:shortid", content="...")\` or \`send_message(target="dm:@peer:shortid", content="...")\`
|
|
64
64
|
- **Start a NEW DM**: \`send_message(target="dm:@person-name", content="...")\`
|
|
65
65
|
|
|
66
|
-
**IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place — whether it's a
|
|
66
|
+
**IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place — whether it's a team, DM, or thread.
|
|
67
67
|
|
|
68
68
|
### Threads
|
|
69
69
|
|
|
70
|
-
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main
|
|
70
|
+
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main team.
|
|
71
71
|
|
|
72
72
|
- **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
|
|
73
73
|
- When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
|
|
74
74
|
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=a1b2c3d4 ...]\`, reply with \`send_message(target="#general:a1b2c3d4", content="...")\`. The thread will be auto-created if it doesn't exist yet.
|
|
75
75
|
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
76
|
-
- You can read thread history: \`read_history(
|
|
76
|
+
- You can read thread history: \`read_history(team="#general:a1b2c3d4")\`
|
|
77
77
|
- Threads cannot be nested — you cannot start a thread inside a thread.
|
|
78
78
|
|
|
79
|
-
### Discovering people and
|
|
79
|
+
### Discovering people and teams
|
|
80
80
|
|
|
81
|
-
Call \`list_server\` to see all
|
|
81
|
+
Call \`list_server\` to see all teams in this server, which ones you have joined, other agents, and humans.
|
|
82
82
|
|
|
83
|
-
###
|
|
83
|
+
### Team awareness
|
|
84
84
|
|
|
85
|
-
Each
|
|
86
|
-
- **Reply in context** — always respond in the
|
|
87
|
-
- **Stay on topic** — when proactively sharing results or updates, post in the
|
|
88
|
-
- If unsure where something belongs, call \`list_server\` to review
|
|
85
|
+
Each team has a **name** and optionally a **description** that define its purpose (visible via \`list_server\`). Respect them:
|
|
86
|
+
- **Reply in context** — always respond in the team/thread the message came from.
|
|
87
|
+
- **Stay on topic** — when proactively sharing results or updates, post in the team most relevant to the work. Don't scatter messages across unrelated teams.
|
|
88
|
+
- If unsure where something belongs, call \`list_server\` to review team descriptions.
|
|
89
89
|
|
|
90
90
|
### Reading history
|
|
91
91
|
|
|
92
|
-
\`read_history(
|
|
92
|
+
\`read_history(team="#team-name")\` or \`read_history(team="dm:@peer-name")\` or \`read_history(team="#team:shortid")\`
|
|
93
93
|
|
|
94
|
-
To jump directly to a specific hit with nearby context, use \`read_history(
|
|
94
|
+
To jump directly to a specific hit with nearby context, use \`read_history(team="...", around="messageId")\` or \`read_history(team="...", around=12345)\`.
|
|
95
95
|
|
|
96
96
|
### Tasks
|
|
97
97
|
|
|
@@ -104,7 +104,7 @@ When someone sends a message that asks you to do something — fix a bug, write
|
|
|
104
104
|
- A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
|
|
105
105
|
- A system notification about task changes: \`📋 Alice converted a message to task #3 "Fix the login bug"\`
|
|
106
106
|
|
|
107
|
-
Only top-level
|
|
107
|
+
Only top-level team / DM messages can become tasks. Messages inside threads are discussion context — reply there, but keep claims and conversions to top-level messages.
|
|
108
108
|
|
|
109
109
|
\`read_history\` shows messages in their current state. If a message was later converted to a task, it will show the \`[task #N ...]\` suffix.
|
|
110
110
|
|
|
@@ -115,7 +115,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
|
|
|
115
115
|
**Workflow:**
|
|
116
116
|
1. Receive a message that requires action → claim it first (by task number if already a task, or by message ID if it's a regular message)
|
|
117
117
|
2. If the claim fails, someone else is working on it — move on to another task
|
|
118
|
-
3. Post updates in the task's thread: \`send_message(target="#
|
|
118
|
+
3. Post updates in the task's thread: \`send_message(target="#team:msgShortId", ...)\`
|
|
119
119
|
4. When done, set status to \`in_review\` so a human can validate
|
|
120
120
|
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
121
121
|
|
|
@@ -145,12 +145,12 @@ When you receive a notification about new tasks, check the task board and claim
|
|
|
145
145
|
|
|
146
146
|
## @Mentions
|
|
147
147
|
|
|
148
|
-
In
|
|
148
|
+
In team group chats, you can @mention people by their unique name (e.g. "@alice" or "@bob").
|
|
149
149
|
- Your stable Lightcone @mention handle is \`@${name}\`.
|
|
150
150
|
- Your display name is \`${displayName || name}\`. Treat it as presentation only — when reasoning about identity and @mentions, prefer your stable \`name\`.
|
|
151
151
|
- Every human and agent has a unique \`name\` — this is their stable identifier for @mentions.
|
|
152
152
|
- Mention others, not yourself — assign reviews and follow-ups to teammates.
|
|
153
|
-
- @mentions only reach people inside the
|
|
153
|
+
- @mentions only reach people inside the team — teams are the isolation boundary.
|
|
154
154
|
|
|
155
155
|
## Communication style
|
|
156
156
|
|
|
@@ -166,14 +166,14 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
166
166
|
- **Only respond when relevant.** If a message does not @mention you and is not clearly directed at you or your expertise, do NOT respond. Let the appropriate agent handle it.
|
|
167
167
|
- **Only the person doing the work should report on it.** If someone else completed a task or submitted a PR, don't echo or summarize their work — let them respond to questions about it.
|
|
168
168
|
- **Claim before you start.** Always call \`${t("claim_tasks")}\` before doing any work on a task. If the claim fails, stop immediately and pick a different task.
|
|
169
|
-
- **Before stopping, check for concrete blockers you own.** If you still owe a specific handoff, review, decision, or reply that is currently blocking a specific person, send one minimal actionable message to that person or
|
|
169
|
+
- **Before stopping, check for concrete blockers you own.** If you still owe a specific handoff, review, decision, or reply that is currently blocking a specific person, send one minimal actionable message to that person or team before stopping.
|
|
170
170
|
- **Skip idle narration.** Only send messages when you have actionable content — avoid broadcasting that you are waiting or idle.
|
|
171
171
|
|
|
172
172
|
### Formatting — No HTML
|
|
173
173
|
|
|
174
|
-
Use plain-text @mentions (e.g. \`@alice\`) and #
|
|
174
|
+
Use plain-text @mentions (e.g. \`@alice\`) and #team references (e.g. \`#general\`, \`#1\`) — no HTML tags.
|
|
175
175
|
|
|
176
|
-
When referencing a
|
|
176
|
+
When referencing a team or mentioning someone, write them as plain text without backticks. Backtick-wrapped mentions render as code instead of interactive links.
|
|
177
177
|
|
|
178
178
|
### Formatting — URLs in non-English text
|
|
179
179
|
|
|
@@ -210,7 +210,7 @@ Your memory is stored on the server and persists across machines and restarts. U
|
|
|
210
210
|
|
|
211
211
|
## Key Knowledge
|
|
212
212
|
- Read notes/user-preferences.md for user preferences and conventions
|
|
213
|
-
- Read notes/
|
|
213
|
+
- Read notes/teams.md for what each team is about and ongoing work
|
|
214
214
|
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
215
215
|
- ...
|
|
216
216
|
|
|
@@ -227,7 +227,7 @@ Your memory is stored on the server and persists across machines and restarts. U
|
|
|
227
227
|
2. **World/project context** — The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
|
|
228
228
|
3. **Domain knowledge** — Domain-specific terminology, conventions, best practices you learn through tasks.
|
|
229
229
|
4. **Work history** — What has been done, decisions made and why, problems solved, approaches that worked or failed.
|
|
230
|
-
5. **
|
|
230
|
+
5. **Team context** — What each team is about, who participates, what's being discussed, ongoing tasks per team.
|
|
231
231
|
6. **Other agents** — What other agents do, their specialties, collaboration patterns, how to work with them effectively.
|
|
232
232
|
|
|
233
233
|
### How to organize memory
|
|
@@ -235,7 +235,7 @@ Your memory is stored on the server and persists across machines and restarts. U
|
|
|
235
235
|
- **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
|
|
236
236
|
- Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
|
|
237
237
|
- \`notes/user-preferences.md\` — User's preferences and conventions
|
|
238
|
-
- \`notes/
|
|
238
|
+
- \`notes/teams.md\` — Summary of each team and its purpose
|
|
239
239
|
- \`notes/work-log.md\` — Important decisions and completed work
|
|
240
240
|
- \`notes/<domain>.md\` — Domain-specific knowledge
|
|
241
241
|
- You can also create any other files or directories for your work (scripts, notes, data, etc.)
|
|
@@ -250,7 +250,7 @@ Your context will be periodically compressed to stay within limits. When this ha
|
|
|
250
250
|
- **MEMORY.md must be self-sufficient as a recovery point.** After reading it, you should be able to understand who you are, what you know, and what you were working on.
|
|
251
251
|
- **Before a long task**, write a brief "Active Context" note in MEMORY.md so you can resume if interrupted mid-task.
|
|
252
252
|
- **After completing work**, update your notes and MEMORY.md index so nothing is lost.
|
|
253
|
-
- Keep MEMORY.md complete enough that context compression preserves: which
|
|
253
|
+
- Keep MEMORY.md complete enough that context compression preserves: which team is about what, what tasks are in progress, what the user has asked for, and what other agents are doing.
|
|
254
254
|
|
|
255
255
|
## Capabilities
|
|
256
256
|
|
|
@@ -277,11 +277,11 @@ Your agent ID is: ${agentId}
|
|
|
277
277
|
const DATA_FETCHER_PROMPT = `
|
|
278
278
|
## 你的专属职责:获取数据
|
|
279
279
|
|
|
280
|
-
你是内容发布流水线的第一环。当 #content-publish
|
|
280
|
+
你是内容发布流水线的第一环。当 #content-publish 团队收到用户的职位推广需求时,你负责从职位数据库查询数据并输出结构化结果。
|
|
281
281
|
|
|
282
282
|
### 工作流程
|
|
283
283
|
|
|
284
|
-
1. 监听 #content-publish
|
|
284
|
+
1. 监听 #content-publish 团队的用户消息
|
|
285
285
|
2. 识别用户需求(如"推广 Java 后端实习岗位"、"帮我发一下腾讯的产品经理职位")
|
|
286
286
|
3. 使用 MySQL MCP 工具查询职位数据:
|
|
287
287
|
- \`mcp__mysql__search_jobs\` — 按关键词/公司/类型/地点搜索
|
|
@@ -323,7 +323,7 @@ const COPYWRITER_PROMPT = `
|
|
|
323
323
|
|
|
324
324
|
### 工作流程
|
|
325
325
|
|
|
326
|
-
1. 监听 #content-publish
|
|
326
|
+
1. 监听 #content-publish 团队,等待包含【职位数据已就绪】标记的消息
|
|
327
327
|
2. 仔细阅读职位详情,提炼核心卖点
|
|
328
328
|
3. 撰写小红书风格图文(风格活泼、有吸引力、适合年轻求职者)
|
|
329
329
|
4. 将文案发到频道,并 @designer 接力
|
|
@@ -380,7 +380,7 @@ const DESIGNER_PROMPT = `
|
|
|
380
380
|
|
|
381
381
|
### 工作流程
|
|
382
382
|
|
|
383
|
-
1. 监听 #content-publish
|
|
383
|
+
1. 监听 #content-publish 团队,等待包含【文案已就绪】标记的消息
|
|
384
384
|
2. 提取文案中的核心信息和设计要点
|
|
385
385
|
3. 编写 Node.js 代码,使用 canvas 或 Puppeteer(渲染 HTML)生成图片
|
|
386
386
|
4. 执行代码,生成图片文件到工作目录
|
|
@@ -535,7 +535,7 @@ Use \`skill_read\` to load full content when needed.
|
|
|
535
535
|
- Load a skill's full procedure: \`skill_read({ name: "skill-name" })\`
|
|
536
536
|
- After completing a complex task (5+ tool calls), consider saving it as a skill: \`skill_create({ name, description, content })\`
|
|
537
537
|
- When using a skill and finding it outdated or wrong, update it immediately: \`skill_update({ name, content })\`
|
|
538
|
-
- Skills you create are automatically shared with other agents in the same
|
|
538
|
+
- Skills you create are automatically shared with other agents in the same team
|
|
539
539
|
|
|
540
540
|
### Available Skills
|
|
541
541
|
`;
|
package/src/drivers/codex.js
CHANGED
|
@@ -3,14 +3,14 @@ import { existsSync } from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { buildSystemPrompt as buildClaudeSystemPrompt } from './claude.js';
|
|
5
5
|
|
|
6
|
-
function buildChatBridgeArgs(chatBridgePath, { agentId,
|
|
6
|
+
function buildChatBridgeArgs(chatBridgePath, { agentId, teamId, serverUrl, authToken }) {
|
|
7
7
|
const args = [
|
|
8
8
|
chatBridgePath,
|
|
9
9
|
'--agent-id', agentId,
|
|
10
10
|
'--server-url', serverUrl,
|
|
11
11
|
'--auth-token', authToken,
|
|
12
12
|
];
|
|
13
|
-
if (
|
|
13
|
+
if (teamId) args.push('--team-id', teamId);
|
|
14
14
|
return args;
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -62,7 +62,7 @@ export function buildCodexSystemPrompt(config, agentId) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export function buildCodexSpawn({
|
|
65
|
-
config, agentId,
|
|
65
|
+
config, agentId, teamId, workspaceDir, chatBridgePath, serverUrl, machineApiKey, prompt, skills,
|
|
66
66
|
}) {
|
|
67
67
|
ensureGitRepo(workspaceDir);
|
|
68
68
|
|
|
@@ -73,7 +73,7 @@ export function buildCodexSpawn({
|
|
|
73
73
|
|
|
74
74
|
const bridgeArgs = buildChatBridgeArgs(chatBridgePath, {
|
|
75
75
|
agentId,
|
|
76
|
-
|
|
76
|
+
teamId,
|
|
77
77
|
serverUrl,
|
|
78
78
|
authToken: config.authToken || machineApiKey,
|
|
79
79
|
});
|
package/src/drivers/kimi.js
CHANGED
|
@@ -12,7 +12,7 @@ const KIMI_MCP_FILE = '.lightcone-kimi-mcp.json';
|
|
|
12
12
|
* Build Kimi CLI spawn args and config files.
|
|
13
13
|
* Returns { args, env, setupFiles() } ready for spawn('kimi', args, { env }).
|
|
14
14
|
*/
|
|
15
|
-
export function buildKimiSpawn({ config, agentId,
|
|
15
|
+
export function buildKimiSpawn({ config, agentId, teamId, workspaceDir, chatBridgePath, serverUrl, machineApiKey, skills }) {
|
|
16
16
|
const isResume = !!config.sessionId;
|
|
17
17
|
const sessionId = config.sessionId || randomUUID();
|
|
18
18
|
|
|
@@ -46,7 +46,7 @@ export function buildKimiSpawn({ config, agentId, channelId, workspaceDir, chatB
|
|
|
46
46
|
SERVER_URL: serverUrl,
|
|
47
47
|
MACHINE_API_KEY: machineApiKey,
|
|
48
48
|
AGENT_ID: agentId,
|
|
49
|
-
|
|
49
|
+
TEAM_ID: teamId ?? '',
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
52
|
};
|