teamshare-bridge 0.11.2 → 0.14.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/dist/bridge/daemon.js +339 -48
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/index.js +38 -0
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/local-server.d.ts +1 -1
- package/dist/bridge/local-server.js +20 -5
- package/dist/bridge/local-server.js.map +1 -1
- package/dist/bridge/service.d.ts +11 -0
- package/dist/bridge/service.js +137 -7
- package/dist/bridge/service.js.map +1 -1
- package/dist/bridge/spawn.d.ts +3 -1
- package/dist/bridge/spawn.js +6 -2
- package/dist/bridge/spawn.js.map +1 -1
- package/dist/cli/commands/build.js +1 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/chat.js +2 -1
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/connect.js +21 -0
- package/dist/cli/commands/connect.js.map +1 -1
- package/dist/cli/commands/doc.js +5 -1
- package/dist/cli/commands/doc.js.map +1 -1
- package/dist/cli/commands/harness.js +5 -9
- package/dist/cli/commands/harness.js.map +1 -1
- package/dist/cli/commands/link.js +1 -1
- package/dist/cli/commands/link.js.map +1 -1
- package/dist/cli/commands/run.js +1 -0
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/worker.js +1 -0
- package/dist/cli/commands/worker.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils.d.ts +5 -0
- package/dist/cli/utils.js +19 -1
- package/dist/cli/utils.js.map +1 -1
- package/dist/lib/agy-pty.js +20 -7
- package/dist/lib/agy-pty.js.map +1 -1
- package/dist/lib/api.d.ts +1 -0
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/chat-reply.d.ts +4 -0
- package/dist/lib/chat-reply.js +71 -12
- package/dist/lib/chat-reply.js.map +1 -1
- package/dist/lib/doc-reply.d.ts +8 -0
- package/dist/lib/doc-reply.js +104 -16
- package/dist/lib/doc-reply.js.map +1 -1
- package/dist/lib/git-auto.d.ts +6 -9
- package/dist/lib/git-auto.js +9 -23
- package/dist/lib/git-auto.js.map +1 -1
- package/dist/lib/git-pipeline.js +6 -23
- package/dist/lib/git-pipeline.js.map +1 -1
- package/dist/lib/harness-adapter.d.ts +5 -1
- package/dist/lib/harness-adapter.js +360 -35
- package/dist/lib/harness-adapter.js.map +1 -1
- package/dist/lib/harness-models.d.ts +17 -0
- package/dist/lib/harness-models.js +146 -0
- package/dist/lib/harness-models.js.map +1 -0
- package/dist/lib/harness-output.d.ts +19 -0
- package/dist/lib/harness-output.js +114 -0
- package/dist/lib/harness-output.js.map +1 -0
- package/dist/lib/harness.d.ts +13 -2
- package/dist/lib/harness.js +103 -19
- package/dist/lib/harness.js.map +1 -1
- package/dist/lib/llm.d.ts +11 -0
- package/dist/lib/llm.js +34 -4
- package/dist/lib/llm.js.map +1 -1
- package/dist/lib/opencode-model-seed.d.ts +7 -0
- package/dist/lib/opencode-model-seed.js +97 -0
- package/dist/lib/opencode-model-seed.js.map +1 -0
- package/package.json +47 -50
package/dist/bridge/daemon.js
CHANGED
|
@@ -16,6 +16,7 @@ const cache_cleaner_1 = require("../lib/cache-cleaner");
|
|
|
16
16
|
const lock_1 = require("../lib/lock");
|
|
17
17
|
const wake_state_1 = require("../lib/wake-state");
|
|
18
18
|
const rate_gate_1 = require("../lib/rate-gate");
|
|
19
|
+
const harness_models_1 = require("../lib/harness-models");
|
|
19
20
|
/** IMP-917: wakes already deferred once for a rate-limit pause (bounded). */
|
|
20
21
|
const rateDeferred = new Set();
|
|
21
22
|
const bus_1 = require("../lib/workspace/bus");
|
|
@@ -143,6 +144,40 @@ function inQuietHours(quietHours) {
|
|
|
143
144
|
return start <= end ? mins >= start && mins < end : mins >= start || mins < end;
|
|
144
145
|
}
|
|
145
146
|
async function runDaemon(portOverride) {
|
|
147
|
+
// IMP-9xx crash resilience: an unhandled rejection killed the daemon
|
|
148
|
+
// silently (Node >= 15 default) and nothing revived it. Rejections now
|
|
149
|
+
// log and stay alive; hard exceptions log and exit(1) on purpose so the
|
|
150
|
+
// platform watchdog (scheduled-task restart / LaunchAgent / systemd)
|
|
151
|
+
// brings a fresh instance up. Signals log the shutdown reason so future
|
|
152
|
+
// log gaps are diagnosable.
|
|
153
|
+
process.on('unhandledRejection', (reason) => {
|
|
154
|
+
log(`[fatal] unhandled rejection (continuing): ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}`);
|
|
155
|
+
});
|
|
156
|
+
process.on('uncaughtException', (err) => {
|
|
157
|
+
log(`[fatal] uncaught exception - exiting for watchdog restart: ${err.stack ?? err.message}`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
});
|
|
160
|
+
for (const sig of ['SIGTERM', 'SIGINT']) {
|
|
161
|
+
process.on(sig, () => {
|
|
162
|
+
log(`shutting down (${sig})`);
|
|
163
|
+
process.exit(0);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// Hidden drill hooks (IMP-9xx verification):
|
|
167
|
+
// TS_DAEMON_TEST_CRASH=1 -> async REJECTION after 5s: must LOG and
|
|
168
|
+
// SURVIVE (the silent-killer case)
|
|
169
|
+
// TS_DAEMON_TEST_CRASH=throw -> sync THROW after 5s: logs then exits(1),
|
|
170
|
+
// platform watchdog revives
|
|
171
|
+
if (process.env.TS_DAEMON_TEST_CRASH === '1') {
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
void Promise.reject(new Error('TS_DAEMON_TEST_CRASH rejection drill'));
|
|
174
|
+
}, 5000);
|
|
175
|
+
}
|
|
176
|
+
else if (process.env.TS_DAEMON_TEST_CRASH === 'throw') {
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
throw new Error('TS_DAEMON_TEST_CRASH throw drill');
|
|
179
|
+
}, 5000);
|
|
180
|
+
}
|
|
146
181
|
// Idempotency guard: a second `daemon` invocation (manual or autostart)
|
|
147
182
|
// must never fight the live instance for ports - it would crash with
|
|
148
183
|
// "no free probe port found" while the real daemon keeps running.
|
|
@@ -156,16 +191,44 @@ async function runDaemon(portOverride) {
|
|
|
156
191
|
const port = portOverride ?? config.port;
|
|
157
192
|
const apiBase = process.env.TEAMSHARE_API_URL ?? api_1.DEFAULT_API_URL;
|
|
158
193
|
const wsBase = (process.env.TEAMSHARE_WS_URL ?? WS_DEFAULT).replace(/\/$/, '');
|
|
194
|
+
// IMP-9xx model catalog discovery: push each installed harness's native
|
|
195
|
+
// list to the backend so the web picker stays current. Best-effort -
|
|
196
|
+
// failures never gate sessions.
|
|
197
|
+
void (async () => {
|
|
198
|
+
const apiKey = config.agents[0]?.apiKey;
|
|
199
|
+
if (!apiKey)
|
|
200
|
+
return;
|
|
201
|
+
for (const harness of ['opencode', 'antigravity', 'codex', 'cursor']) {
|
|
202
|
+
try {
|
|
203
|
+
const batch = (0, harness_models_1.discoverHarnessModels)(harness);
|
|
204
|
+
if (!batch?.models.length)
|
|
205
|
+
continue;
|
|
206
|
+
const stored = await (0, harness_models_1.pushHarnessCatalog)(apiBase, apiKey, harness, batch);
|
|
207
|
+
log(`[models] pushed ${stored} ${harness} model(s) (${batch.source})`);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
log(`[models] ${harness} discovery failed: ${(0, config_1.msg)(err)}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
})();
|
|
159
214
|
/** agentId -> queued wakes waiting for the session lock to free up. */
|
|
160
215
|
const wakeQueue = new Map();
|
|
161
216
|
/** dedupeKey -> last spawn timestamp (usage guard against wake storms). */
|
|
162
217
|
const recentSpawns = new Map();
|
|
163
218
|
/** agentId -> ISO timestamp of the last wake event (probe/status). */
|
|
164
219
|
const lastWakeByAgent = new Map();
|
|
220
|
+
const handles = new Map();
|
|
221
|
+
/** Fresh config snapshot per wake - project links/wake rules apply live. */
|
|
222
|
+
const getConfig = () => (0, config_1.loadConfig)();
|
|
223
|
+
const credsOf = (h) => ({
|
|
224
|
+
agentId: h.agentId,
|
|
225
|
+
apiKey: h.apiKey,
|
|
226
|
+
});
|
|
165
227
|
const getStatus = () => ({
|
|
166
228
|
configPath: (0, config_1.configPath)(),
|
|
167
|
-
agents:
|
|
168
|
-
const
|
|
229
|
+
agents: [...handles.values()].map((h) => {
|
|
230
|
+
const a = { agentId: h.agentId };
|
|
231
|
+
const lock = (0, lock_1.readLock)(h.agentId);
|
|
169
232
|
const base = {
|
|
170
233
|
agentId: a.agentId,
|
|
171
234
|
lastWake: lastWakeByAgent.get(a.agentId),
|
|
@@ -187,7 +250,7 @@ async function runDaemon(portOverride) {
|
|
|
187
250
|
registeredSchemes: false,
|
|
188
251
|
projects: config.projects,
|
|
189
252
|
});
|
|
190
|
-
const { server, port: boundPort } = await (0, local_server_1.startProbeServer)(port, getStatus);
|
|
253
|
+
const { server, port: boundPort } = await (0, local_server_1.startProbeServer)(port, getStatus, () => syncAgents());
|
|
191
254
|
log(`probe server listening on http://localhost:${boundPort}/ping`);
|
|
192
255
|
// Track this instance for stop/restart/status even when started in the
|
|
193
256
|
// foreground (only detached `start` used to leave a pid file behind).
|
|
@@ -235,8 +298,17 @@ async function runDaemon(portOverride) {
|
|
|
235
298
|
? new orchestrator_1.OrchestratorClient(config.agents[0].apiKey, apiBase)
|
|
236
299
|
: null;
|
|
237
300
|
orchestratorClient?.connect();
|
|
238
|
-
|
|
301
|
+
function startAgentLoop(agent) {
|
|
302
|
+
if (handles.has(agent.agentId))
|
|
303
|
+
return handles.get(agent.agentId);
|
|
304
|
+
log(`[agents] starting loop for ${agent.agentId}`);
|
|
239
305
|
const api = api_1.TeamshareApi.forAgent(apiBase, agent);
|
|
306
|
+
// Per-wake fresh config: handleWake accepts a getter, so project link /
|
|
307
|
+
// wake-rule edits apply to the NEXT wake without a restart. Shadowing
|
|
308
|
+
// the outer snapshot keeps every existing `config,` call-site argument
|
|
309
|
+
// below valid and always current.
|
|
310
|
+
const config = getConfig;
|
|
311
|
+
const agentTimers = [];
|
|
240
312
|
/** Keeps the agent marked online; errors are surfaced, never swallowed. */
|
|
241
313
|
const heartbeat = () => {
|
|
242
314
|
api
|
|
@@ -299,14 +371,15 @@ async function runDaemon(portOverride) {
|
|
|
299
371
|
joinUntilReady(0);
|
|
300
372
|
heartbeat();
|
|
301
373
|
pushState();
|
|
302
|
-
|
|
303
|
-
|
|
374
|
+
agentTimers.push(setInterval(heartbeat, 60_000));
|
|
375
|
+
agentTimers.push(setInterval(pushState, 60_000));
|
|
304
376
|
// Periodic mention poll (catches wakes lost while connected — WS
|
|
305
377
|
// emits are best-effort; a silent hiccup loses the wake until the
|
|
306
378
|
// next reconnect). Runs every 10 minutes and processes only
|
|
307
|
-
// mentions (never re-wakes
|
|
379
|
+
// mentions + stranded task assignments (never re-wakes claimed or
|
|
380
|
+
// recently-spawned tasks to avoid repeated work).
|
|
308
381
|
const MENTION_POLL_MS = 10 * 60_000;
|
|
309
|
-
|
|
382
|
+
agentTimers.push(setInterval(async () => {
|
|
310
383
|
try {
|
|
311
384
|
const inbox = await api.inbox(agent.agentId);
|
|
312
385
|
for (const mention of inbox.mentions ?? []) {
|
|
@@ -318,13 +391,15 @@ async function runDaemon(portOverride) {
|
|
|
318
391
|
if (!projectId)
|
|
319
392
|
continue;
|
|
320
393
|
log(`agent ${agent.agentId}: periodic poll wake chat.mention id=${mention.id}`);
|
|
321
|
-
await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
394
|
+
const pollChatOutcome = await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
322
395
|
type: 'chat.mention',
|
|
323
396
|
projectId,
|
|
324
397
|
messageId: String(mention.id ?? ''),
|
|
325
398
|
mention: String(mention.mention ?? ''),
|
|
326
399
|
...(mention.channelId ? { channelId: String(mention.channelId) } : {}),
|
|
327
400
|
}, config, { settings: settingsByAgent.get(agent.agentId), wakeQueue, recentSpawns });
|
|
401
|
+
if (pollChatOutcome === 'queued' || pollChatOutcome === 'deferred' || pollChatOutcome === 'blocked')
|
|
402
|
+
continue;
|
|
328
403
|
(0, wake_state_1.recordProcessed)(agent.agentId, 'chat.mention', key);
|
|
329
404
|
}
|
|
330
405
|
else if (String(mention.type) === 'comment') {
|
|
@@ -333,28 +408,65 @@ async function runDaemon(portOverride) {
|
|
|
333
408
|
if (!projectId)
|
|
334
409
|
continue;
|
|
335
410
|
log(`agent ${agent.agentId}: periodic poll wake document.mention id=${mention.id}`);
|
|
336
|
-
await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
411
|
+
const pollDocOutcome = await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
337
412
|
type: 'document.mention',
|
|
338
413
|
documentId: String(mention.documentId),
|
|
339
414
|
projectId,
|
|
340
415
|
commentId: String(mention.id ?? ''),
|
|
341
416
|
mention: String(mention.mention ?? ''),
|
|
342
417
|
}, config, { settings: settingsByAgent.get(agent.agentId), wakeQueue, recentSpawns });
|
|
418
|
+
if (pollDocOutcome === 'queued' || pollDocOutcome === 'deferred' || pollDocOutcome === 'blocked')
|
|
419
|
+
continue;
|
|
343
420
|
(0, wake_state_1.recordProcessed)(agent.agentId, 'document.mention', key);
|
|
344
421
|
}
|
|
345
422
|
else if (mention.taskId) {
|
|
346
423
|
log(`agent ${agent.agentId}: periodic poll wake comment.mention id=${mention.id}`);
|
|
347
|
-
await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
424
|
+
const pollCommentOutcome = await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
348
425
|
type: 'comment.mention',
|
|
349
426
|
taskId: String(mention.taskId),
|
|
350
427
|
...(mention.projectId ? { projectId: String(mention.projectId) } : {}),
|
|
351
428
|
commentId: String(mention.id ?? ''),
|
|
352
429
|
mention: String(mention.mention ?? ''),
|
|
353
430
|
}, config, { settings: settingsByAgent.get(agent.agentId), wakeQueue, recentSpawns });
|
|
431
|
+
if (pollCommentOutcome === 'queued' || pollCommentOutcome === 'deferred' || pollCommentOutcome === 'blocked')
|
|
432
|
+
continue;
|
|
354
433
|
(0, wake_state_1.recordProcessed)(agent.agentId, 'comment.mention', key);
|
|
355
434
|
}
|
|
356
435
|
}
|
|
357
436
|
}
|
|
437
|
+
// --- Stranded-assignment recovery (IMP-9xx): a task.assigned
|
|
438
|
+
// wake that was BLOCKED (e.g. no linked folder) or lost mid-blip
|
|
439
|
+
// used to strand forever - the backend only emits on assignment
|
|
440
|
+
// and re-sync only runs on reconnect. Sweep open assigned tasks
|
|
441
|
+
// that no session has claimed yet; 'blocked' outcomes stay
|
|
442
|
+
// unmarked so the next cycle retries them automatically.
|
|
443
|
+
const stranded = (inbox.tasks ?? []).filter((task) => String(task.status ?? '') === 'open' &&
|
|
444
|
+
!task.claimedAt);
|
|
445
|
+
let sweptCount = 0;
|
|
446
|
+
for (const task of stranded) {
|
|
447
|
+
if (sweptCount >= 3)
|
|
448
|
+
break;
|
|
449
|
+
const taskId = String(task.id ?? '');
|
|
450
|
+
if (!taskId)
|
|
451
|
+
continue;
|
|
452
|
+
if ((0, wake_state_1.isRecentlyProcessed)(agent.agentId, 'task.assigned', taskId))
|
|
453
|
+
continue;
|
|
454
|
+
const lastSpawn = recentSpawns.get(`task:${taskId}`);
|
|
455
|
+
if (lastSpawn && Date.now() - lastSpawn < TASK_WAKE_COOLDOWN_MS)
|
|
456
|
+
continue;
|
|
457
|
+
log(`agent ${agent.agentId}: stranded-task sweep wake ${taskId} (${String(task.title ?? '')})`);
|
|
458
|
+
sweptCount++;
|
|
459
|
+
const sweepOutcome = await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
460
|
+
type: 'task.assigned',
|
|
461
|
+
taskId,
|
|
462
|
+
projectId: task.projectId ?? undefined,
|
|
463
|
+
}, config, { settings: settingsByAgent.get(agent.agentId), wakeQueue, recentSpawns });
|
|
464
|
+
if (sweepOutcome !== 'queued' &&
|
|
465
|
+
sweepOutcome !== 'deferred' &&
|
|
466
|
+
sweepOutcome !== 'blocked') {
|
|
467
|
+
(0, wake_state_1.recordProcessed)(agent.agentId, 'task.assigned', taskId);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
358
470
|
}
|
|
359
471
|
catch {
|
|
360
472
|
// best-effort: don't crash the daemon on periodic poll failure
|
|
@@ -396,7 +508,7 @@ async function runDaemon(portOverride) {
|
|
|
396
508
|
if ((0, wake_state_1.isRecentlyProcessed)(agent.agentId, 'task.assigned', taskId))
|
|
397
509
|
continue;
|
|
398
510
|
log(`agent ${agent.agentId}: re-sync wake task=${taskId} (${String(task.title ?? '')})`);
|
|
399
|
-
await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
511
|
+
const resyncTaskOutcome = await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
400
512
|
type: 'task.assigned',
|
|
401
513
|
taskId,
|
|
402
514
|
projectId: task.projectId ?? undefined,
|
|
@@ -406,7 +518,11 @@ async function runDaemon(portOverride) {
|
|
|
406
518
|
wakeQueue,
|
|
407
519
|
recentSpawns,
|
|
408
520
|
});
|
|
409
|
-
|
|
521
|
+
if (resyncTaskOutcome !== 'queued' &&
|
|
522
|
+
resyncTaskOutcome !== 'deferred' &&
|
|
523
|
+
resyncTaskOutcome !== 'blocked') {
|
|
524
|
+
(0, wake_state_1.recordProcessed)(agent.agentId, 'task.assigned', taskId);
|
|
525
|
+
}
|
|
410
526
|
totalTasks++;
|
|
411
527
|
}
|
|
412
528
|
// --- 2. Process inbox.mentions (server-authoritative, replaces
|
|
@@ -423,7 +539,7 @@ async function runDaemon(portOverride) {
|
|
|
423
539
|
continue;
|
|
424
540
|
}
|
|
425
541
|
log(`agent ${agent.agentId}: re-sync wake chat.mention id=${mention.id} project=${projectId}`);
|
|
426
|
-
await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
542
|
+
const resyncChatOutcome = await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
427
543
|
type: 'chat.mention',
|
|
428
544
|
projectId,
|
|
429
545
|
messageId: String(mention.id ?? ''),
|
|
@@ -434,7 +550,11 @@ async function runDaemon(portOverride) {
|
|
|
434
550
|
wakeQueue,
|
|
435
551
|
recentSpawns,
|
|
436
552
|
});
|
|
437
|
-
|
|
553
|
+
if (resyncChatOutcome !== 'queued' &&
|
|
554
|
+
resyncChatOutcome !== 'deferred' &&
|
|
555
|
+
resyncChatOutcome !== 'blocked') {
|
|
556
|
+
(0, wake_state_1.recordProcessed)(agent.agentId, 'chat.mention', key);
|
|
557
|
+
}
|
|
438
558
|
totalChatMentions++;
|
|
439
559
|
}
|
|
440
560
|
else if (String(mention.type) === 'comment') {
|
|
@@ -446,7 +566,7 @@ async function runDaemon(portOverride) {
|
|
|
446
566
|
continue;
|
|
447
567
|
}
|
|
448
568
|
log(`agent ${agent.agentId}: re-sync wake document.mention id=${mention.id} doc=${mention.documentId}`);
|
|
449
|
-
await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
569
|
+
const resyncDocOutcome = await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
450
570
|
type: 'document.mention',
|
|
451
571
|
documentId: String(mention.documentId),
|
|
452
572
|
projectId,
|
|
@@ -457,13 +577,17 @@ async function runDaemon(portOverride) {
|
|
|
457
577
|
wakeQueue,
|
|
458
578
|
recentSpawns,
|
|
459
579
|
});
|
|
460
|
-
|
|
580
|
+
if (resyncDocOutcome !== 'queued' &&
|
|
581
|
+
resyncDocOutcome !== 'deferred' &&
|
|
582
|
+
resyncDocOutcome !== 'blocked') {
|
|
583
|
+
(0, wake_state_1.recordProcessed)(agent.agentId, 'document.mention', key);
|
|
584
|
+
}
|
|
461
585
|
totalDocMentions++;
|
|
462
586
|
}
|
|
463
587
|
else if (mention.taskId) {
|
|
464
588
|
const projectId = String(mention.projectId ?? '');
|
|
465
589
|
log(`agent ${agent.agentId}: re-sync wake comment.mention id=${mention.id} task=${mention.taskId}`);
|
|
466
|
-
await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
590
|
+
const resyncCommentOutcome = await handleWake(api_1.TeamshareApi.forAgent(apiBase, agent), agent.agentId, agent.apiKey, {
|
|
467
591
|
type: 'comment.mention',
|
|
468
592
|
taskId: String(mention.taskId),
|
|
469
593
|
...(projectId ? { projectId } : {}),
|
|
@@ -474,7 +598,11 @@ async function runDaemon(portOverride) {
|
|
|
474
598
|
wakeQueue,
|
|
475
599
|
recentSpawns,
|
|
476
600
|
});
|
|
477
|
-
|
|
601
|
+
if (resyncCommentOutcome !== 'queued' &&
|
|
602
|
+
resyncCommentOutcome !== 'deferred' &&
|
|
603
|
+
resyncCommentOutcome !== 'blocked') {
|
|
604
|
+
(0, wake_state_1.recordProcessed)(agent.agentId, 'comment.mention', key);
|
|
605
|
+
}
|
|
478
606
|
totalCommentMentions++;
|
|
479
607
|
}
|
|
480
608
|
else {
|
|
@@ -515,7 +643,127 @@ async function runDaemon(portOverride) {
|
|
|
515
643
|
});
|
|
516
644
|
sockets.push(socket);
|
|
517
645
|
wakeQueue.set(agent.agentId, []);
|
|
646
|
+
const handle = {
|
|
647
|
+
agentId: agent.agentId,
|
|
648
|
+
apiKey: agent.apiKey,
|
|
649
|
+
api,
|
|
650
|
+
socket,
|
|
651
|
+
timers: agentTimers,
|
|
652
|
+
};
|
|
653
|
+
handles.set(agent.agentId, handle);
|
|
654
|
+
return handle;
|
|
518
655
|
}
|
|
656
|
+
/** Stops one agent loop. Caller must have verified lock/queue are drained. */
|
|
657
|
+
function stopAgentLoop(handle) {
|
|
658
|
+
log(`[agents] stopping loop for ${handle.agentId}`);
|
|
659
|
+
for (const t of handle.timers)
|
|
660
|
+
clearInterval(t);
|
|
661
|
+
try {
|
|
662
|
+
handle.socket.close();
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
/* already closed */
|
|
666
|
+
}
|
|
667
|
+
wakeQueue.delete(handle.agentId);
|
|
668
|
+
settingsByAgent.delete(handle.agentId);
|
|
669
|
+
handles.delete(handle.agentId);
|
|
670
|
+
}
|
|
671
|
+
// Start every configured agent, then keep the set in sync with config.json.
|
|
672
|
+
for (const agent of (0, config_1.loadConfig)().agents)
|
|
673
|
+
startAgentLoop(agent);
|
|
674
|
+
let removalPending = false;
|
|
675
|
+
/**
|
|
676
|
+
* Diff the live loops against config.json:
|
|
677
|
+
* - added agents -> startAgentLoop immediately (zero impact on running
|
|
678
|
+
* sessions of other agents)
|
|
679
|
+
* - removed agents -> deferred while their lock is held / wakes queued;
|
|
680
|
+
* a 30s sweeper finishes the removal once drained.
|
|
681
|
+
*/
|
|
682
|
+
function syncAgents() {
|
|
683
|
+
try {
|
|
684
|
+
// Guard against corrupt / mid-write reads: loadConfig() falls back to
|
|
685
|
+
// an EMPTY default config when JSON.parse fails (BOM, partial write),
|
|
686
|
+
// which would otherwise be misread as "remove every agent". A strict
|
|
687
|
+
// re-parse keeps the current loops alive until the file is readable.
|
|
688
|
+
if (!(0, node_fs_1.existsSync)((0, config_1.configPath)()))
|
|
689
|
+
return;
|
|
690
|
+
JSON.parse((0, node_fs_1.readFileSync)((0, config_1.configPath)(), 'utf8'));
|
|
691
|
+
JSON.parse(JSON.stringify((0, config_1.loadConfig)()));
|
|
692
|
+
}
|
|
693
|
+
catch {
|
|
694
|
+
return; // unreadable - keep current loops, next event retries
|
|
695
|
+
}
|
|
696
|
+
const fresh = (0, config_1.loadConfig)();
|
|
697
|
+
const wanted = new Map(fresh.agents.map((a) => [a.agentId, a]));
|
|
698
|
+
for (const [id, a] of wanted) {
|
|
699
|
+
if (!handles.has(id)) {
|
|
700
|
+
log(`[agents] hot-add ${id}`);
|
|
701
|
+
try {
|
|
702
|
+
startAgentLoop(a);
|
|
703
|
+
}
|
|
704
|
+
catch (err) {
|
|
705
|
+
log(`[agents] hot-add ${id} failed: ${(0, config_1.msg)(err)}`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
let deferred = false;
|
|
710
|
+
for (const [id, h] of [...handles.entries()]) {
|
|
711
|
+
if (wanted.has(id))
|
|
712
|
+
continue;
|
|
713
|
+
const busy = (0, lock_1.isLockBusy)(id);
|
|
714
|
+
const queued = wakeQueue.get(id)?.length ?? 0;
|
|
715
|
+
if (busy || queued > 0) {
|
|
716
|
+
log(`[agents] hot-remove ${id} deferred (busy=${busy}, queued=${queued})`);
|
|
717
|
+
deferred = true;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
stopAgentLoop(h);
|
|
721
|
+
}
|
|
722
|
+
removalPending = deferred;
|
|
723
|
+
}
|
|
724
|
+
// Config watcher (debounced - saveConfig writes are not atomic): react to
|
|
725
|
+
// agent-set changes only; model/path edits already apply per-wake.
|
|
726
|
+
let lastAgentSig = '';
|
|
727
|
+
try {
|
|
728
|
+
lastAgentSig = JSON.stringify((0, config_1.loadConfig)().agents.map((a) => a.agentId).sort());
|
|
729
|
+
}
|
|
730
|
+
catch {
|
|
731
|
+
lastAgentSig = '';
|
|
732
|
+
}
|
|
733
|
+
let watchDebounce = null;
|
|
734
|
+
let cfgWatcher = null;
|
|
735
|
+
try {
|
|
736
|
+
cfgWatcher = (0, node_fs_1.watch)((0, config_1.configPath)(), () => {
|
|
737
|
+
if (watchDebounce)
|
|
738
|
+
clearTimeout(watchDebounce);
|
|
739
|
+
watchDebounce = setTimeout(() => {
|
|
740
|
+
watchDebounce = null;
|
|
741
|
+
let next;
|
|
742
|
+
try {
|
|
743
|
+
// Strict parse - a corrupt read must never produce a "[]" signature
|
|
744
|
+
// that wipes the live agent set (see syncAgents guard).
|
|
745
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)((0, config_1.configPath)(), 'utf8'));
|
|
746
|
+
if (!parsed || !Array.isArray(parsed.agents))
|
|
747
|
+
return;
|
|
748
|
+
next = JSON.stringify(parsed.agents.map((a) => a.agentId).sort());
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (!next || next === lastAgentSig)
|
|
754
|
+
return;
|
|
755
|
+
log(`[agents] config change detected (${lastAgentSig} -> ${next})`);
|
|
756
|
+
lastAgentSig = next;
|
|
757
|
+
syncAgents();
|
|
758
|
+
}, 2_000);
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
catch (err) {
|
|
762
|
+
log(`[agents] config watcher unavailable: ${(0, config_1.msg)(err)} - connect pings will still reload`);
|
|
763
|
+
}
|
|
764
|
+
// Finishes removals that were deferred behind a busy session.
|
|
765
|
+
timers.push(setInterval(() => { if (removalPending)
|
|
766
|
+
syncAgents(); }, 30_000));
|
|
519
767
|
// Queue flush: spawn queued wakes once the agent's session lock frees up
|
|
520
768
|
// (staleness-aware - a crashed session's dead-pid lock is treated as free).
|
|
521
769
|
const flushInterval = setInterval(() => {
|
|
@@ -524,13 +772,18 @@ async function runDaemon(portOverride) {
|
|
|
524
772
|
continue;
|
|
525
773
|
if ((0, lock_1.isLockBusy)(agentId))
|
|
526
774
|
continue; // still busy - keep waiting
|
|
527
|
-
const
|
|
775
|
+
const h = handles.get(agentId);
|
|
528
776
|
const event = queue.shift();
|
|
529
|
-
if (!
|
|
777
|
+
if (!h || !event) {
|
|
778
|
+
// Agent removed while queued - drop the stale wake.
|
|
779
|
+
if (!h)
|
|
780
|
+
wakeQueue.delete(agentId);
|
|
530
781
|
continue;
|
|
782
|
+
}
|
|
783
|
+
const agent = credsOf(h);
|
|
531
784
|
void (async () => {
|
|
532
785
|
const settings = await freshSettings(api_1.TeamshareApi.forAgent(apiBase, agent), agentId);
|
|
533
|
-
return spawnWake(api_1.TeamshareApi.forAgent(apiBase, agent), agentId, agent.apiKey, event,
|
|
786
|
+
return spawnWake(api_1.TeamshareApi.forAgent(apiBase, agent), agentId, agent.apiKey, event, getConfig(), { settings, recentSpawns });
|
|
534
787
|
})()
|
|
535
788
|
.then(() => {
|
|
536
789
|
log(`agent ${agentId}: spawned queued wake ${event.type}`);
|
|
@@ -554,6 +807,14 @@ async function runDaemon(portOverride) {
|
|
|
554
807
|
timers.push(flushInterval);
|
|
555
808
|
const shutdown = () => {
|
|
556
809
|
log('shutting down');
|
|
810
|
+
if (watchDebounce)
|
|
811
|
+
clearTimeout(watchDebounce);
|
|
812
|
+
try {
|
|
813
|
+
cfgWatcher?.close();
|
|
814
|
+
}
|
|
815
|
+
catch {
|
|
816
|
+
/* watcher already gone */
|
|
817
|
+
}
|
|
557
818
|
for (const t of timers)
|
|
558
819
|
clearInterval(t);
|
|
559
820
|
for (const s of sockets)
|
|
@@ -574,7 +835,11 @@ async function runDaemon(portOverride) {
|
|
|
574
835
|
.filter((p) => typeof p === 'string' && p.length > 0);
|
|
575
836
|
const sweep = () => {
|
|
576
837
|
const fresh = (0, config_1.loadConfig)();
|
|
577
|
-
void (0, cache_cleaner_1.runCleanup)(fresh.cleanup, {
|
|
838
|
+
void (0, cache_cleaner_1.runCleanup)(fresh.cleanup, {
|
|
839
|
+
projectPaths,
|
|
840
|
+
agents: [...handles.values()].map(credsOf),
|
|
841
|
+
log,
|
|
842
|
+
})
|
|
578
843
|
.catch((err) => log(`[cleanup] sweep failed: ${(0, config_1.msg)(err)}`));
|
|
579
844
|
};
|
|
580
845
|
try {
|
|
@@ -591,7 +856,8 @@ async function runDaemon(portOverride) {
|
|
|
591
856
|
return typeof raw?.intervalHours === 'number' && raw.intervalHours >= 1 ? raw.intervalHours : 6;
|
|
592
857
|
}
|
|
593
858
|
}
|
|
594
|
-
async function handleWake(api, agentId, apiKey, event,
|
|
859
|
+
async function handleWake(api, agentId, apiKey, event, configInput, extra) {
|
|
860
|
+
const config = typeof configInput === 'function' ? configInput() : configInput;
|
|
595
861
|
const { wakeQueue, settings, recentSpawns, orchestratorClient } = extra;
|
|
596
862
|
// IMP-917: user rate-limited - defer ONCE until the shared gate lifts
|
|
597
863
|
// (bounded single re-fire; the second pass proceeds and individual API
|
|
@@ -608,7 +874,7 @@ async function handleWake(api, agentId, apiKey, event, config, extra) {
|
|
|
608
874
|
rateDeferred.delete(deferKey);
|
|
609
875
|
void handleWake(api, agentId, apiKey, event, config, extra);
|
|
610
876
|
}, wait);
|
|
611
|
-
return;
|
|
877
|
+
return 'deferred';
|
|
612
878
|
}
|
|
613
879
|
}
|
|
614
880
|
}
|
|
@@ -629,21 +895,21 @@ async function handleWake(api, agentId, apiKey, event, config, extra) {
|
|
|
629
895
|
const now = Date.now();
|
|
630
896
|
if (last && now - last < cooldownMs) {
|
|
631
897
|
log(`agent ${agentId}: wake ${event.type} skipped (dedupe - spawned ${Math.round((now - last) / 1000)}s ago)`);
|
|
632
|
-
return;
|
|
898
|
+
return 'skipped';
|
|
633
899
|
}
|
|
634
900
|
}
|
|
635
901
|
// Quiet hours: the per-agent setting wins; the bridge config is the
|
|
636
902
|
// machine-wide fallback for agents without one.
|
|
637
903
|
if (inQuietHours(settings?.quietHours ?? config.quietHours)) {
|
|
638
904
|
log(`agent ${agentId}: wake ${event.type} skipped (quiet hours)`);
|
|
639
|
-
return;
|
|
905
|
+
return 'skipped';
|
|
640
906
|
}
|
|
641
907
|
if (event.taskId && config.wakeRules?.priorities?.length) {
|
|
642
908
|
try {
|
|
643
909
|
const task = await api.getTask(event.taskId);
|
|
644
910
|
if (!config.wakeRules.priorities.includes(task.priority)) {
|
|
645
911
|
log(`agent ${agentId}: wake ${event.type} skipped (priority ${task.priority})`);
|
|
646
|
-
return;
|
|
912
|
+
return 'skipped';
|
|
647
913
|
}
|
|
648
914
|
}
|
|
649
915
|
catch {
|
|
@@ -659,9 +925,9 @@ async function handleWake(api, agentId, apiKey, event, config, extra) {
|
|
|
659
925
|
queue.push(event);
|
|
660
926
|
wakeQueue.set(agentId, queue);
|
|
661
927
|
log(`agent ${agentId}: wake ${event.type} queued (busy since ${lock.startedAt}, ${lock.kind}) - ${queue.length} waiting`);
|
|
662
|
-
return;
|
|
928
|
+
return 'queued';
|
|
663
929
|
}
|
|
664
|
-
await spawnWake(api, agentId, apiKey, event, config, { settings, recentSpawns, orchestratorClient });
|
|
930
|
+
return await spawnWake(api, agentId, apiKey, event, config, { settings, recentSpawns, orchestratorClient });
|
|
665
931
|
}
|
|
666
932
|
async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
667
933
|
log(`agent ${agentId}: wake ${event.type}${event.taskId ? ` task=${event.taskId}` : ''}`);
|
|
@@ -709,6 +975,26 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
709
975
|
? `project ${projectId} has no linked folder`
|
|
710
976
|
: `could not determine the project (guard requires one)`;
|
|
711
977
|
log(`agent ${agentId}: wake ${event.type} BLOCKED - ${why}`);
|
|
978
|
+
// IMP-9xx visible feedback: the error terminal is easy to miss, so tell
|
|
979
|
+
// people IN the project chat (once per event-type+project window) what
|
|
980
|
+
// blocked the session and exactly how to fix it.
|
|
981
|
+
if (projectId) {
|
|
982
|
+
const fbKey = `${event.type}:${projectId}`;
|
|
983
|
+
try {
|
|
984
|
+
if (!(0, wake_state_1.isRecentlyProcessed)(agentId, 'blocked', fbKey)) {
|
|
985
|
+
const who = event.by?.name?.trim();
|
|
986
|
+
await api.postChatMessage(projectId, `${who ? `${who} ` : 'Hi! '}I can't work here yet - this project has no linked folder on this machine.` +
|
|
987
|
+
` Set one in TeamShare (Edit project > Linked folder) or run:` +
|
|
988
|
+
` teamshare-agent set-folder --project ${projectId} --path C:\\path\\to\\folder` +
|
|
989
|
+
` - then mention or assign me again.`);
|
|
990
|
+
(0, wake_state_1.recordProcessed)(agentId, 'blocked', fbKey);
|
|
991
|
+
log(`agent ${agentId}: blocked-feedback posted to project chat (${fbKey})`);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
catch (err) {
|
|
995
|
+
log(`agent ${agentId}: blocked-feedback chat failed: ${(0, config_1.msg)(err)}`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
712
998
|
(0, spawn_1.openErrorTerminal)(`TeamShare agent ${agentId} cannot work: ${why}.\n\n` +
|
|
713
999
|
`The project guard blocks sessions until a folder is linked.\n` +
|
|
714
1000
|
`Set one in the TeamShare UI (Edit agent / Edit project > Linked folder)\n` +
|
|
@@ -717,7 +1003,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
717
1003
|
? ` teamshare-agent set-folder --project ${projectId} --path C:\\path\\to\\your\\project-folder\n`
|
|
718
1004
|
: ` teamshare-agent set-folder --project <projectId> --path C:\\path\\to\\your\\project-folder\n`) +
|
|
719
1005
|
`\nThen mention or assign the agent again.`);
|
|
720
|
-
return;
|
|
1006
|
+
return 'blocked';
|
|
721
1007
|
}
|
|
722
1008
|
log(`agent ${agentId}: wake ${event.type} -> working folder ${cwd}`);
|
|
723
1009
|
// IMP-950 auto-worktree escalation (opt-in per project link): when another
|
|
@@ -768,35 +1054,38 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
768
1054
|
if (event.type === 'chat.mention') {
|
|
769
1055
|
if (!event.projectId) {
|
|
770
1056
|
log(`agent ${agentId}: chat.mention without projectId - skipping`);
|
|
771
|
-
return;
|
|
1057
|
+
return 'skipped';
|
|
772
1058
|
}
|
|
773
1059
|
// Chat mentions spawn a chat-reply loop (no task context).
|
|
774
1060
|
// Pass messageId so the loop replies to only that specific message.
|
|
775
1061
|
(0, spawn_1.spawnChatSession)(agentId, apiKey, event.projectId, event.mention ?? '', extra.settings, undefined, cwd, event.messageId);
|
|
776
|
-
return;
|
|
1062
|
+
return 'spawned';
|
|
777
1063
|
}
|
|
778
1064
|
if (event.type === 'document.mention') {
|
|
779
1065
|
if (!event.documentId) {
|
|
780
1066
|
log(`agent ${agentId}: document.mention without documentId - skipping`);
|
|
781
|
-
return;
|
|
1067
|
+
return 'skipped';
|
|
782
1068
|
}
|
|
783
1069
|
// Document mentions spawn the document assistant loop (Phase F).
|
|
784
|
-
|
|
785
|
-
|
|
1070
|
+
// IMP-9xx: pass the waking comment id so the loop answers ONLY that
|
|
1071
|
+
// comment (answer-once guard still applies) instead of rescanning
|
|
1072
|
+
// every historical mention in the thread.
|
|
1073
|
+
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings, undefined, cwd, event.commentId);
|
|
1074
|
+
return 'spawned';
|
|
786
1075
|
}
|
|
787
1076
|
if (event.type === 'build.trigger') {
|
|
788
1077
|
if (!event.projectId) {
|
|
789
1078
|
log(`agent ${agentId}: build.trigger without projectId - skipping`);
|
|
790
|
-
return;
|
|
1079
|
+
return 'skipped';
|
|
791
1080
|
}
|
|
792
1081
|
// Build mode: start (or restart) the project's build run.
|
|
793
1082
|
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined, cwd);
|
|
794
|
-
return;
|
|
1083
|
+
return 'spawned';
|
|
795
1084
|
}
|
|
796
1085
|
if (event.type === 'build.stop') {
|
|
797
1086
|
if (!event.projectId) {
|
|
798
1087
|
log(`agent ${agentId}: build.stop without projectId - skipping`);
|
|
799
|
-
return;
|
|
1088
|
+
return 'skipped';
|
|
800
1089
|
}
|
|
801
1090
|
// Write the stop file - the running build loop honors it (wraps up the
|
|
802
1091
|
// current task and exits). Best-effort: if no build session is running,
|
|
@@ -809,7 +1098,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
809
1098
|
catch (err) {
|
|
810
1099
|
log(`agent ${agentId}: build.stop failed to write stop file: ${(0, config_1.msg)(err)}`);
|
|
811
1100
|
}
|
|
812
|
-
return;
|
|
1101
|
+
return 'spawned';
|
|
813
1102
|
}
|
|
814
1103
|
if (event.type === 'question.answered') {
|
|
815
1104
|
// Resume-on-answer: spawn a build session when a build is actually
|
|
@@ -818,7 +1107,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
818
1107
|
// and resumes it. A stale answer for a finished build does nothing.
|
|
819
1108
|
if (!event.projectId) {
|
|
820
1109
|
log(`agent ${agentId}: question.answered without projectId - skipping`);
|
|
821
|
-
return;
|
|
1110
|
+
return 'skipped';
|
|
822
1111
|
}
|
|
823
1112
|
const stateFile = (0, node_path_1.join)(BUILD_STATE_DIR, `${agentId}.${event.projectId}.json`);
|
|
824
1113
|
const hasBuildState = (0, node_fs_1.existsSync)(stateFile);
|
|
@@ -841,7 +1130,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
841
1130
|
}
|
|
842
1131
|
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined, cwd);
|
|
843
1132
|
}
|
|
844
|
-
return;
|
|
1133
|
+
return 'spawned';
|
|
845
1134
|
}
|
|
846
1135
|
if (event.type === 'question.expired') {
|
|
847
1136
|
// Expired-on-timeout: same wake path as question.answered — the agent
|
|
@@ -850,7 +1139,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
850
1139
|
// just wake the agent loop so it picks up the new state.
|
|
851
1140
|
if (!event.projectId) {
|
|
852
1141
|
log(`agent ${agentId}: question.expired without projectId - skipping`);
|
|
853
|
-
return;
|
|
1142
|
+
return 'skipped';
|
|
854
1143
|
}
|
|
855
1144
|
const stateFile = (0, node_path_1.join)(BUILD_STATE_DIR, `${agentId}.${event.projectId}.json`);
|
|
856
1145
|
const hasBuildState = (0, node_fs_1.existsSync)(stateFile);
|
|
@@ -868,30 +1157,32 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
868
1157
|
}
|
|
869
1158
|
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined, cwd);
|
|
870
1159
|
}
|
|
871
|
-
return;
|
|
1160
|
+
return 'spawned';
|
|
872
1161
|
}
|
|
873
1162
|
if (event.type === 'comment.mention') {
|
|
874
1163
|
// Comment mentions on tasks spawn a task session (agent responds in
|
|
875
1164
|
// the task thread). Document comment mentions spawn the doc assistant.
|
|
876
1165
|
if (event.documentId) {
|
|
877
1166
|
if (projectId && cwd) {
|
|
878
|
-
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings, undefined, cwd);
|
|
1167
|
+
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings, undefined, cwd, event.commentId);
|
|
1168
|
+
return 'spawned';
|
|
879
1169
|
}
|
|
880
|
-
return;
|
|
1170
|
+
return 'skipped';
|
|
881
1171
|
}
|
|
882
1172
|
// Fall through to the task-id check below for taskId-carrying comment mentions.
|
|
883
1173
|
}
|
|
884
1174
|
const taskId = event.taskId ?? '';
|
|
885
1175
|
if (!UUID_RE.test(taskId)) {
|
|
886
1176
|
log(`agent ${agentId}: wake ${event.type} skipped (no/malformed task id "${taskId}")`);
|
|
887
|
-
return;
|
|
1177
|
+
return 'skipped';
|
|
888
1178
|
}
|
|
889
1179
|
// IMP-810: Worker mode — spawn the worker loop for orchestrator subtasks
|
|
890
1180
|
if (event.role === 'worker') {
|
|
891
1181
|
log(`agent ${agentId}: spawning worker session for task ${taskId}`);
|
|
892
1182
|
(0, spawn_1.spawnWorkerSession)(agentId, apiKey, taskId, event.orchestratorTaskId, extra.settings, undefined, cwd);
|
|
893
|
-
return;
|
|
1183
|
+
return 'spawned';
|
|
894
1184
|
}
|
|
895
1185
|
(0, spawn_1.spawnAgentSession)(agentId, apiKey, taskId, extra.settings, undefined, cwd);
|
|
1186
|
+
return 'spawned';
|
|
896
1187
|
}
|
|
897
1188
|
//# sourceMappingURL=daemon.js.map
|