teamshare-bridge 0.21.76 → 0.21.78
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-inbox.js.map +1 -1
- package/dist/bridge/daemon.js +150 -20
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/local-server.js +6 -1
- package/dist/bridge/local-server.js.map +1 -1
- package/dist/bridge/spawn.d.ts +19 -2
- package/dist/bridge/spawn.js +17 -4
- package/dist/bridge/spawn.js.map +1 -1
- package/dist/bridge/wake-handler.d.ts +1 -0
- package/dist/bridge/wake-handler.js +253 -100
- package/dist/bridge/wake-handler.js.map +1 -1
- package/dist/bridge/wake-queue-store.d.ts +20 -0
- package/dist/bridge/wake-queue-store.js +107 -0
- package/dist/bridge/wake-queue-store.js.map +1 -0
- package/dist/bridge/wake-routing.d.ts +60 -6
- package/dist/bridge/wake-routing.js +155 -6
- package/dist/bridge/wake-routing.js.map +1 -1
- package/dist/cli/commands/board.js +7 -0
- package/dist/cli/commands/board.js.map +1 -1
- package/dist/cli/commands/browse.js +5 -1
- package/dist/cli/commands/browse.js.map +1 -1
- package/dist/cli/commands/browser.js +53 -77
- package/dist/cli/commands/browser.js.map +1 -1
- package/dist/cli/commands/build.js +4 -2
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/chat.js +3 -1
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/cleanup.js +4 -2
- package/dist/cli/commands/cleanup.js.map +1 -1
- package/dist/cli/commands/doc.js +8 -0
- package/dist/cli/commands/doc.js.map +1 -1
- package/dist/cli/commands/plan.js +6 -3
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/run.js +38 -17
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/worker.js +4 -2
- package/dist/cli/commands/worker.js.map +1 -1
- package/dist/cli/index.js +5 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/api-agents.d.ts +27 -0
- package/dist/lib/api-agents.js +39 -0
- package/dist/lib/api-agents.js.map +1 -1
- package/dist/lib/api-content.d.ts +1 -1
- package/dist/lib/api-content.js +7 -1
- package/dist/lib/api-content.js.map +1 -1
- package/dist/lib/api-types.d.ts +18 -0
- package/dist/lib/brief.d.ts +8 -0
- package/dist/lib/brief.js +49 -0
- package/dist/lib/brief.js.map +1 -1
- package/dist/lib/browser/launch.d.ts +20 -7
- package/dist/lib/browser/launch.js +21 -9
- package/dist/lib/browser/launch.js.map +1 -1
- package/dist/lib/browser/manager.d.ts +43 -9
- package/dist/lib/browser/manager.js +64 -11
- package/dist/lib/browser/manager.js.map +1 -1
- package/dist/lib/browser/owned-registry.d.ts +19 -0
- package/dist/lib/browser/owned-registry.js +121 -0
- package/dist/lib/browser/owned-registry.js.map +1 -0
- package/dist/lib/browser/process.d.ts +14 -0
- package/dist/lib/browser/process.js +100 -0
- package/dist/lib/browser/process.js.map +1 -0
- package/dist/lib/browser/session.d.ts +74 -15
- package/dist/lib/browser/session.js +134 -32
- package/dist/lib/browser/session.js.map +1 -1
- package/dist/lib/cache-cleaner.d.ts +36 -0
- package/dist/lib/cache-cleaner.js +101 -5
- package/dist/lib/cache-cleaner.js.map +1 -1
- package/dist/lib/chat/chat-reply.d.ts +3 -2
- package/dist/lib/chat/chat-reply.js +117 -9
- package/dist/lib/chat/chat-reply.js.map +1 -1
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/orchestrator/client.js +13 -2
- package/dist/lib/orchestrator/client.js.map +1 -1
- package/dist/lib/orchestrator/handlers/browser.js +35 -8
- package/dist/lib/orchestrator/handlers/browser.js.map +1 -1
- package/dist/lib/tools/browser-tool.js +19 -5
- package/dist/lib/tools/browser-tool.js.map +1 -1
- package/dist/lib/workspace/mirror-lease.d.ts +62 -0
- package/dist/lib/workspace/mirror-lease.js +183 -0
- package/dist/lib/workspace/mirror-lease.js.map +1 -0
- package/dist/lib/workspace/mirror.d.ts +76 -1
- package/dist/lib/workspace/mirror.js +296 -13
- package/dist/lib/workspace/mirror.js.map +1 -1
- package/dist/lib/workspace/session-mention.d.ts +31 -0
- package/dist/lib/workspace/session-mention.js +30 -0
- package/dist/lib/workspace/session-mention.js.map +1 -0
- package/package.json +1 -1
|
@@ -19,12 +19,23 @@ const wake_state_1 = require("../lib/wake-state");
|
|
|
19
19
|
const presence_1 = require("../lib/workspace/presence");
|
|
20
20
|
const repo_lock_1 = require("../lib/workspace/repo-lock");
|
|
21
21
|
const mirror_1 = require("../lib/workspace/mirror");
|
|
22
|
+
const wake_queue_store_1 = require("./wake-queue-store");
|
|
23
|
+
const git_branch_1 = require("../lib/git/git-branch");
|
|
22
24
|
const git_presence_1 = require("../lib/git/git-presence");
|
|
23
25
|
const session_store_1 = require("../lib/harness/session-store");
|
|
24
|
-
const plan_gate_1 = require("../lib/harness/plan-gate");
|
|
25
26
|
const spawn_1 = require("./spawn");
|
|
26
27
|
const daemon_support_1 = require("./daemon-support");
|
|
27
28
|
const wake_routing_1 = require("./wake-routing");
|
|
29
|
+
/**
|
|
30
|
+
* Seed recorded in a mirror marker. Task-scoped runs are keyed by the task id
|
|
31
|
+
* (the PATH is deterministic too, so a retry can adopt the previous tree);
|
|
32
|
+
* wakes with no task id get a type+random seed and are never adopted.
|
|
33
|
+
*/
|
|
34
|
+
function mirrorSeed(event) {
|
|
35
|
+
if (event.taskId && wake_routing_1.UUID_RE.test(event.taskId))
|
|
36
|
+
return event.taskId;
|
|
37
|
+
return `${event.type.replace(/[^a-z.]/gi, '')}-${Math.random().toString(16).slice(2, 8)}`;
|
|
38
|
+
}
|
|
28
39
|
/** IMP-917: wakes already deferred once for a rate-limit pause (bounded). */
|
|
29
40
|
const rateDeferred = new Set();
|
|
30
41
|
/**
|
|
@@ -36,6 +47,8 @@ const rateDeferred = new Set();
|
|
|
36
47
|
* Covers every delivery path because they all funnel through `handleWake`.
|
|
37
48
|
*/
|
|
38
49
|
const inFlightWakes = new Set();
|
|
50
|
+
/** Quiet-hours deferrals (2026-09) - bounded by one timer per wake identity. */
|
|
51
|
+
const quietDeferred = new Set();
|
|
39
52
|
exports.folderWaitQueue = new Map();
|
|
40
53
|
/**
|
|
41
54
|
* Multi-repo: name -> path map of a project's linked git repos (undefined
|
|
@@ -77,6 +90,25 @@ async function handleWake(api, agentId, apiKey, event, configInput, extra) {
|
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
92
|
}
|
|
93
|
+
// DUPLICATE AUTOMATED WAKE (2026-09): the hub re-sends a `task.assigned`
|
|
94
|
+
// wake it never saw acknowledged (empty room during a socket flap, or the
|
|
95
|
+
// 15s ack watchdog) and delivers the replay on the next reconnect. The 60s
|
|
96
|
+
// spawn cooldown above does not cover a replay 10 minutes later, and the
|
|
97
|
+
// 15-minute processed-wake ledger was only ever consulted by the re-sync and
|
|
98
|
+
// stranded-sweep pumps - never on this live path. So the duplicate sailed
|
|
99
|
+
// through, queued behind the busy lock, and on flush built a whole mirror for
|
|
100
|
+
// a task that was already being worked.
|
|
101
|
+
//
|
|
102
|
+
// Only AUTOMATED assignment wakes are filtered. `task.run` is a human
|
|
103
|
+
// pressing "Run task" and must always work, and a resume/continuation wake
|
|
104
|
+
// is an explicit instruction to pick the task back up.
|
|
105
|
+
if (event.type === 'task.assigned' &&
|
|
106
|
+
!event.resume &&
|
|
107
|
+
event.taskId &&
|
|
108
|
+
(0, wake_state_1.isRecentlyProcessed)(agentId, 'task.assigned', event.taskId)) {
|
|
109
|
+
(0, daemon_support_1.log)(`agent ${agentId}: wake task.assigned task=${event.taskId} skipped (already processed recently - duplicate wake)`);
|
|
110
|
+
return 'skipped';
|
|
111
|
+
}
|
|
80
112
|
// Usage guard: skip wakes for the same task/message/document within the
|
|
81
113
|
// cooldown window - a reconnect storm must never respawn the same work.
|
|
82
114
|
// The per-agent setting (seconds) overrides the bridge defaults.
|
|
@@ -100,8 +132,28 @@ async function handleWake(api, agentId, apiKey, event, configInput, extra) {
|
|
|
100
132
|
// Quiet hours: the per-agent setting wins; the bridge config is the
|
|
101
133
|
// machine-wide fallback for agents without one.
|
|
102
134
|
if ((0, daemon_support_1.inQuietHours)(settings?.quietHours ?? config.quietHours)) {
|
|
103
|
-
|
|
104
|
-
|
|
135
|
+
// DEFERRED, never skipped (2026-09). This used to return 'skipped', and the
|
|
136
|
+
// inbox pumps stamp anything that is not queued/deferred/blocked as
|
|
137
|
+
// "processed" - so a chat mention that arrived during quiet hours was
|
|
138
|
+
// acked by the hub, skipped here, written to the 15-minute ledger, and then
|
|
139
|
+
// never retried by anyone. It was simply lost.
|
|
140
|
+
const deferKey = `${agentId}:${event.type}:${(0, wake_routing_1.wakeDedupeKey)(event) ?? `${event.projectId ?? ''}:${event.messageId ?? event.taskId ?? Math.random()}`}`;
|
|
141
|
+
if (!quietDeferred.has(deferKey)) {
|
|
142
|
+
quietDeferred.add(deferKey);
|
|
143
|
+
// Re-check in 30 minutes; a mention waits out quiet hours instead of
|
|
144
|
+
// disappearing.
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
quietDeferred.delete(deferKey);
|
|
147
|
+
if ((0, daemon_support_1.inQuietHours)(settings?.quietHours ?? config.quietHours)) {
|
|
148
|
+
// Still quiet - re-arm for the next window.
|
|
149
|
+
void handleWake(api, agentId, apiKey, event, config, extra);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
void handleWake(api, agentId, apiKey, event, config, extra);
|
|
153
|
+
}, 30 * 60_000);
|
|
154
|
+
(0, daemon_support_1.log)(`agent ${agentId}: wake ${event.type} deferred (quiet hours) - will retry in 30m until quiet hours end`);
|
|
155
|
+
}
|
|
156
|
+
return 'deferred';
|
|
105
157
|
}
|
|
106
158
|
// IN-FLIGHT GUARD (2026-09): the same wake delivered twice while the
|
|
107
159
|
// first copy is still resolving (WS live wake vs stranded-task sweep vs
|
|
@@ -138,10 +190,15 @@ async function handleWake(api, agentId, apiKey, event, configInput, extra) {
|
|
|
138
190
|
const queue = wakeQueue.get(agentId) ?? [];
|
|
139
191
|
queue.push(event);
|
|
140
192
|
wakeQueue.set(agentId, queue);
|
|
193
|
+
// Persisted (2026-09): a queued wake is often a chat mention or a task
|
|
194
|
+
// comment - a message addressed to the agent. It must survive a daemon
|
|
195
|
+
// restart/upgrade; the hub has already been told it was delivered, so there
|
|
196
|
+
// is no server-side copy to fall back on.
|
|
197
|
+
(0, wake_queue_store_1.writeWakeQueueFile)(agentId, queue);
|
|
141
198
|
(0, daemon_support_1.log)(`agent ${agentId}: wake ${event.type} queued (busy since ${lock.startedAt}, ${lock.kind}) - ${queue.length} waiting`);
|
|
142
199
|
// Stuck-queue visibility: only a lock with NO live session after 10+
|
|
143
200
|
// minutes is a problem (PID reuse / hung process / orphan). A running
|
|
144
|
-
// session (sessionRelay has it) is a normal long run
|
|
201
|
+
// session (sessionRelay has it) is a normal long run —” the queue keeps
|
|
145
202
|
// waiting (flushInterval) and we never warn. Fingerprint includes the
|
|
146
203
|
// lock's startedAt so all wakes behind the SAME bad lock coalesce.
|
|
147
204
|
const lockAgeMs = Date.now() - new Date(lock.startedAt).getTime();
|
|
@@ -156,7 +213,7 @@ async function handleWake(api, agentId, apiKey, event, configInput, extra) {
|
|
|
156
213
|
message: `wake ${event.type} queued behind a ${lock.kind} lock held over 10 minutes with no live session (since ${lock.startedAt}, pid ${lock.pid}) - if the pid is orphaned: teamshare-bridge clear-lock --agent ${agentId}`,
|
|
157
214
|
});
|
|
158
215
|
}
|
|
159
|
-
// Session running: stay quiet
|
|
216
|
+
// Session running: stay quiet —” the wake is queued and will spawn on free.
|
|
160
217
|
}
|
|
161
218
|
return 'queued';
|
|
162
219
|
}
|
|
@@ -180,14 +237,14 @@ async function handleWake(api, agentId, apiKey, event, configInput, extra) {
|
|
|
180
237
|
}
|
|
181
238
|
/**
|
|
182
239
|
* NO-GIT NOTICE (2026-09): tell the user, ON THE TASK, why this run is not
|
|
183
|
-
* isolated. Without this the only symptom is "the agent behaves differently"
|
|
240
|
+
* isolated. Without this the only symptom is "the agent behaves differently" —”
|
|
184
241
|
* a silent downgrade is indistinguishable from a bug. Deduped per
|
|
185
242
|
* (agent, project, task) so a retried task is not spammed, and best-effort:
|
|
186
243
|
* a failed notice must never block the run.
|
|
187
244
|
*/
|
|
188
245
|
async function announceGitUnavailable(api, agentId, projectId, taskId, detail) {
|
|
189
246
|
if (!taskId) {
|
|
190
|
-
// No task to comment on (e.g. a document/board mention)
|
|
247
|
+
// No task to comment on (e.g. a document/board mention) —” the daemon log
|
|
191
248
|
// already carries the reason and `doctor` reports it.
|
|
192
249
|
return;
|
|
193
250
|
}
|
|
@@ -329,7 +386,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
329
386
|
(0, daemon_support_1.log)(`agent ${agentId}: wake ${event.type} -> working folder ${cwd}`);
|
|
330
387
|
// Plain chat (mention with NO task reference) is a READ-ONLY plan-mode
|
|
331
388
|
// session: it never edits local files and runs no git, so it needs no
|
|
332
|
-
// isolation
|
|
389
|
+
// isolation —” it runs in the linked folder IMMEDIATELY (no mirror, no
|
|
333
390
|
// folder queue) even while other runs are active. Task-linked chat DOES run
|
|
334
391
|
// the git flow and gets a mirror. Computed here because the isolation gate
|
|
335
392
|
// and the folder gate both need it.
|
|
@@ -337,7 +394,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
337
394
|
const plainChat = event.type === 'chat.mention' && !linkedTaskId;
|
|
338
395
|
// Task wake PRE-CHECKS (2026-09): fetch once and apply the SKIP guards
|
|
339
396
|
// BEFORE isolation, so a wake that will be skipped (part of a combined run,
|
|
340
|
-
// suspended, or a closed task.assigned) never builds a mirror
|
|
397
|
+
// suspended, or a closed task.assigned) never builds a mirror —” that used to
|
|
341
398
|
// leave an orphan mirror + 5 worktrees. The fetched task is reused by the
|
|
342
399
|
// plan/build routing below (no second GET).
|
|
343
400
|
let routedTask = null;
|
|
@@ -348,7 +405,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
348
405
|
try {
|
|
349
406
|
const task = await api.getTask(event.taskId);
|
|
350
407
|
// Combined-run lock: a member folded into a combined run is never spawned
|
|
351
|
-
// on its own
|
|
408
|
+
// on its own —” the combined (group) task IS its run.
|
|
352
409
|
if (task.combinedIntoId) {
|
|
353
410
|
(0, daemon_support_1.log)(`agent ${agentId}: wake ${event.type} task=${event.taskId} SKIPPED - part of a combined run`);
|
|
354
411
|
return 'skipped';
|
|
@@ -367,12 +424,94 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
367
424
|
routedTask = task;
|
|
368
425
|
}
|
|
369
426
|
catch {
|
|
370
|
-
// fetch failed
|
|
427
|
+
// fetch failed —” fall through; claim will fail if truly non-open
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// PRE-FLIGHT (2026-09): decide whether this wake can spawn, and whether it
|
|
431
|
+
// needs a private mirror, BEFORE anything is created. Every skip decision
|
|
432
|
+
// used to live BELOW the isolation block, so a wake that decided not to run
|
|
433
|
+
// had already built 5 worktrees (~45 MB) and left them behind for hours.
|
|
434
|
+
//
|
|
435
|
+
// `preflightWake` relocates those decisions - it adds no new skip reason, so
|
|
436
|
+
// it can never strand work that would previously have run. The
|
|
437
|
+
// folder-serialization gate deliberately stays BELOW isolation (it must keep
|
|
438
|
+
// testing the EFFECTIVE cwd, where a successful mirror has no presence).
|
|
439
|
+
const preflight = (0, wake_routing_1.preflightWake)({
|
|
440
|
+
event,
|
|
441
|
+
task: routedTask,
|
|
442
|
+
hasLocalPlanSession: event.taskId
|
|
443
|
+
? (0, session_store_1.hasHarnessSession)(agentId, event.taskId, 'plan')
|
|
444
|
+
: false,
|
|
445
|
+
hasLocalRunSession: event.taskId
|
|
446
|
+
? (0, session_store_1.hasHarnessSession)(agentId, event.taskId, 'run')
|
|
447
|
+
: false,
|
|
448
|
+
hasLocalBuildSession: event.taskId
|
|
449
|
+
? (0, session_store_1.hasHarnessSession)(agentId, event.taskId, 'build')
|
|
450
|
+
: false,
|
|
451
|
+
hasBuildState: false,
|
|
452
|
+
plainChat,
|
|
453
|
+
});
|
|
454
|
+
if (preflight.outcome === 'stop') {
|
|
455
|
+
// Defensive: handleWake normally intercepts stop signals before here.
|
|
456
|
+
if (projectId) {
|
|
457
|
+
try {
|
|
458
|
+
(0, node_fs_1.mkdirSync)(daemon_support_1.STOP_DIR, { recursive: true });
|
|
459
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(daemon_support_1.STOP_DIR, `${agentId}.stop`), new Date().toISOString(), 'utf8');
|
|
460
|
+
(0, daemon_support_1.log)(`agent ${agentId}: ${event.type} -> stop file written (pre-flight)`);
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
(0, daemon_support_1.log)(`agent ${agentId}: ${event.type} stop file failed: ${(0, config_1.msg)(err)}`);
|
|
464
|
+
}
|
|
371
465
|
}
|
|
466
|
+
return 'spawned';
|
|
467
|
+
}
|
|
468
|
+
if (preflight.outcome === 'skip') {
|
|
469
|
+
(0, daemon_support_1.log)(`agent ${agentId}: ${event.type} task=${event.taskId ?? ''} -> skipped (${preflight.reason}${preflight.detail ? `, ${preflight.detail}` : ''})`);
|
|
470
|
+
if (event.type === 'task.assigned' && event.taskId) {
|
|
471
|
+
(0, wake_state_1.recordProcessed)(agentId, 'task.assigned', event.taskId);
|
|
472
|
+
}
|
|
473
|
+
return 'skipped';
|
|
372
474
|
}
|
|
373
|
-
//
|
|
475
|
+
// DETERMINISTIC MENTION CLAIM (2026-09).
|
|
476
|
+
//
|
|
477
|
+
// Every mention wake (chat, task comment, document comment, board comment, AI
|
|
478
|
+
// edit) now carries the hub's AgentMention id. Claiming it HERE - once, in the
|
|
479
|
+
// one place every mention passes through - is what makes "answered" a fact
|
|
480
|
+
// rather than a guess. Previously the bridge decided with a name-scan behind a
|
|
481
|
+
// moving cursor and a "is my reply in the newest 50?" window, so a replayed
|
|
482
|
+
// wake (a PendingWake redelivery, a reconnect re-sync, a stranded sweep) could
|
|
483
|
+
// start a SECOND session on a question that was already answered.
|
|
484
|
+
//
|
|
485
|
+
// `claimed: false` means another session already has it, or it is answered ->
|
|
486
|
+
// do not spawn. An older hub without the endpoint logs and proceeds, so this
|
|
487
|
+
// is additive, never a new way to drop a mention.
|
|
488
|
+
if (event.mentionId) {
|
|
489
|
+
try {
|
|
490
|
+
const claim = await api.triggerMention(agentId, event.mentionId);
|
|
491
|
+
if (claim && claim.claimed === false) {
|
|
492
|
+
const state = claim.mention?.answeredAt
|
|
493
|
+
? 'already answered'
|
|
494
|
+
: 'already being handled';
|
|
495
|
+
(0, daemon_support_1.log)(`agent ${agentId}: mention ${event.mentionId} (${event.type}) is ${state} - not starting a second session`);
|
|
496
|
+
if (event.type === 'task.assigned') {
|
|
497
|
+
(0, wake_state_1.recordProcessed)(agentId, 'task.assigned', event.taskId ?? event.mentionId);
|
|
498
|
+
}
|
|
499
|
+
return 'skipped';
|
|
500
|
+
}
|
|
501
|
+
if (claim?.claimed) {
|
|
502
|
+
(0, daemon_support_1.log)(`agent ${agentId}: claimed mention ${event.mentionId} (${event.type})`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
(0, daemon_support_1.log)(`agent ${agentId}: could not claim mention ${event.mentionId} (${(0, config_1.msg)(err)}) - proceeding`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// Past the folder guard `cwd` is always a real path. Keep a non-null alias
|
|
510
|
+
// for the dispatch chain below - it is reassigned by the mirror block.
|
|
511
|
+
let runDir = cwd;
|
|
512
|
+
// ALWAYS-ISOLATE (2026-09): every code run gets its OWN private mirror —”
|
|
374
513
|
// a container-root worktree plus one worktree per pre-existing nested repo
|
|
375
|
-
//
|
|
514
|
+
// —” so concurrent agents never share a working tree and EACH repo commits
|
|
376
515
|
// its own changes on its own branch (never a mixed commit or a gitlink
|
|
377
516
|
// instead of the nested repo's work). The container root repo is
|
|
378
517
|
// initialized on demand (never nested repos). Exempt: orchestrator-owned
|
|
@@ -382,29 +521,12 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
382
521
|
// Opt out per project: `teamshare-agent link --project <id> --auto-worktree
|
|
383
522
|
// false`.
|
|
384
523
|
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
// folder
|
|
390
|
-
|
|
391
|
-
// because the routing gate below runs after it.
|
|
392
|
-
const planRunWake = event.type === 'plan.trigger' ||
|
|
393
|
-
(routedTask ? (0, wake_routing_1.planModeRoute)(event, routedTask) === 'plan' : false);
|
|
394
|
-
const isolationExempt = plainChat ||
|
|
395
|
-
Boolean(event.worktreePath) ||
|
|
396
|
-
event.type === 'question.answered' ||
|
|
397
|
-
event.type === 'question.expired' ||
|
|
398
|
-
event.type === 'tool.trigger' ||
|
|
399
|
-
event.type === 'document.job' ||
|
|
400
|
-
planRunWake;
|
|
401
|
-
if (!isolationExempt && cwd && projectId &&
|
|
402
|
-
config.projects[projectId]?.autoWorktree !== false) {
|
|
403
|
-
const seedBase = (event.taskId ?? `${event.type}-${Date.now()}`)
|
|
404
|
-
.replace(/-/g, '')
|
|
405
|
-
.slice(0, 8);
|
|
406
|
-
// Unique per run so a retry never tears down an earlier run's mirror.
|
|
407
|
-
const seed = `${seedBase}-${Math.random().toString(16).slice(2, 6)}`;
|
|
524
|
+
// The exemption decision now comes from the pre-flight (`needsMirror`), so
|
|
525
|
+
// plan runs are excluded here for the same reason as before: a planning
|
|
526
|
+
// pass never runs the git pipeline, so a mirror buys it nothing while
|
|
527
|
+
// costing 5 worktrees per pass. `cmdPlan` registers presence instead, so
|
|
528
|
+
// the folder-serialization gate below still keeps it to one at a time.
|
|
529
|
+
if (preflight.needsMirror && !event.worktreePath && projectId && config.projects[projectId]?.autoWorktree !== false) {
|
|
408
530
|
// NO-GIT FALLBACK (2026-09): a machine without Git cannot have a mirror at
|
|
409
531
|
// all (no container-root `git init`, no `git worktree add`). Don't burn a
|
|
410
532
|
// doomed attempt on it: run in the linked folder and let the folder
|
|
@@ -418,17 +540,60 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
418
540
|
}
|
|
419
541
|
else {
|
|
420
542
|
try {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
543
|
+
// Base the tree on the task's own branch when it exists, so a retry
|
|
544
|
+
// CONTINUES the previous attempt instead of landing on a clean
|
|
545
|
+
// default-branch checkout and doing the same job twice.
|
|
546
|
+
//
|
|
547
|
+
// The inputs MUST mirror `cmdRun`'s `prepareRunGitAsync` call exactly
|
|
548
|
+
// (taskId + project.name + title, no branchName): if the two derive
|
|
549
|
+
// different names, the mirror bases on a branch the run never commits
|
|
550
|
+
// to and the continuation silently misses.
|
|
551
|
+
const result = await (0, mirror_1.createIsolationMirror)(cwd, mirrorSeed(event), agentId, projectId, {
|
|
552
|
+
taskId: routedTask?.id ?? event.taskId,
|
|
553
|
+
baseBranch: routedTask
|
|
554
|
+
? (0, git_branch_1.desiredBranch)({
|
|
555
|
+
taskId: routedTask.id,
|
|
556
|
+
projectSlug: routedTask.project?.name,
|
|
557
|
+
taskTitle: routedTask.title,
|
|
558
|
+
})
|
|
559
|
+
: null,
|
|
560
|
+
});
|
|
561
|
+
if (result.ok) {
|
|
562
|
+
runDir = result.mirror.path;
|
|
563
|
+
const adopted = result.mirror.adopted
|
|
564
|
+
? ` (adopted previous tree, ${result.mirror.dirtyFiles ?? 0} uncommitted file(s) carried over)`
|
|
565
|
+
: '';
|
|
566
|
+
(0, daemon_support_1.log)(`agent ${agentId}: isolated in ${runDir} (${result.mirror.repos.length} repo(s): ${result.mirror.repos
|
|
425
567
|
.map((r) => r.name)
|
|
426
|
-
.join(', ')})`);
|
|
568
|
+
.join(', ')})${adopted}`);
|
|
569
|
+
}
|
|
570
|
+
else if (result.reason === 'blocked') {
|
|
571
|
+
// Someone is genuinely using this task's tree. Refuse rather than
|
|
572
|
+
// touch it, and make the refusal visible - a same-task double wake is
|
|
573
|
+
// a duplicate-wake bug, not a normal state.
|
|
574
|
+
// A same-task double wake reached the mirror guard: this task's tree
|
|
575
|
+
// is genuinely in use. Queue for real (the pre-2026 code returned
|
|
576
|
+
// 'queued' WITHOUT queueing, so the wake was referenced nowhere: no
|
|
577
|
+
// retry, no server copy, no reply) - and say so loudly.
|
|
578
|
+
const queue = extra.wakeQueue?.get(agentId) ?? [];
|
|
579
|
+
queue.push(event);
|
|
580
|
+
extra.wakeQueue?.set(agentId, queue);
|
|
581
|
+
(0, wake_queue_store_1.writeWakeQueueFile)(agentId, queue);
|
|
582
|
+
(0, daemon_support_1.log)(`agent ${agentId}: mirror for ${event.type} task=${event.taskId ?? ''} is owned by a live session (${result.detail ?? 'busy'}) - queued ${queue.length} waiting`);
|
|
583
|
+
(0, diagnostics_1.reportDiagnosticSafe)({
|
|
584
|
+
kind: 'wake_failure',
|
|
585
|
+
severity: 'warning',
|
|
586
|
+
agentId,
|
|
587
|
+
errorCode: 'TASK_MIRROR_OWNED',
|
|
588
|
+
message: `wake ${event.type} for task ${event.taskId ?? '(no task)'} found its isolation mirror already owned by a live session (${result.detail ?? 'busy'}) - queued for retry`,
|
|
589
|
+
includeDoctor: false,
|
|
590
|
+
});
|
|
591
|
+
return 'queued';
|
|
427
592
|
}
|
|
428
593
|
else {
|
|
429
|
-
// Isolation unavailable (no base branch / init failed)
|
|
594
|
+
// Isolation unavailable (no base branch / init failed) —” the
|
|
430
595
|
// serialization gate below queues instead of sharing the folder.
|
|
431
|
-
(0, daemon_support_1.log)(`agent ${agentId}: isolation unavailable - will queue if the linked folder is occupied`);
|
|
596
|
+
(0, daemon_support_1.log)(`agent ${agentId}: isolation unavailable (${result.detail ?? 'unknown'}) - will queue if the linked folder is occupied`);
|
|
432
597
|
}
|
|
433
598
|
}
|
|
434
599
|
catch (err) {
|
|
@@ -436,59 +601,47 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
436
601
|
}
|
|
437
602
|
}
|
|
438
603
|
}
|
|
439
|
-
// Task-scoped routing
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
// the ISOLATED cwd.
|
|
443
|
-
if (routedTask) {
|
|
604
|
+
// Task-scoped routing dispatch. The SKIP half of this decision was made in
|
|
605
|
+
// the pre-flight above the mirror block, so reaching here can only spawn.
|
|
606
|
+
if (routedTask && (preflight.kind === 'tool' || preflight.kind === 'plan')) {
|
|
444
607
|
const task = routedTask;
|
|
445
608
|
// Tools-as-tasks: a research/tool task must run through the spec-driven
|
|
446
609
|
// research session (recipe + column contract), NOT the code agent loop.
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
|
|
610
|
+
if (preflight.kind === 'tool') {
|
|
611
|
+
// The pre-flight only returns 'tool' when toolRunId is set; String()
|
|
612
|
+
// keeps the spawn signature honest without a cast.
|
|
613
|
+
const toolRunId = String(task.toolRunId ?? '');
|
|
450
614
|
(0, daemon_support_1.log)(`agent ${agentId}: task.assigned task=${event.taskId} -> research session (${task.toolKey ?? task.kind})`);
|
|
451
|
-
(0, spawn_1.spawnResearchSession)(agentId, apiKey, projectId ?? task.projectId,
|
|
615
|
+
(0, spawn_1.spawnResearchSession)(agentId, apiKey, projectId ?? task.projectId, toolRunId, task.toolKey ?? 'tool', extra.settings, undefined, runDir);
|
|
452
616
|
(0, wake_state_1.recordProcessed)(agentId, 'tool.trigger', String(task.toolRunId));
|
|
453
617
|
return 'spawned';
|
|
454
618
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
return 'skipped';
|
|
464
|
-
}
|
|
465
|
-
(0, daemon_support_1.log)(`agent ${agentId}: ${event.type} task=${event.taskId} -> planning session (plan mode)`);
|
|
466
|
-
const planDedupeKey = (0, wake_routing_1.wakeDedupeKey)(event);
|
|
467
|
-
if (planDedupeKey)
|
|
468
|
-
extra.recentSpawns?.set(planDedupeKey, Date.now());
|
|
469
|
-
(0, spawn_1.spawnPlanSession)(agentId, apiKey, task.id, projectId ?? task.projectId, extra.settings, undefined, cwd, { auto: true });
|
|
470
|
-
if (event.type === 'task.assigned') {
|
|
471
|
-
(0, wake_state_1.recordProcessed)(agentId, 'task.assigned', task.id);
|
|
472
|
-
}
|
|
473
|
-
return 'spawned';
|
|
619
|
+
(0, daemon_support_1.log)(`agent ${agentId}: ${event.type} task=${event.taskId} -> planning session (plan mode)`);
|
|
620
|
+
const planDedupeKey = (0, wake_routing_1.wakeDedupeKey)(event);
|
|
621
|
+
if (planDedupeKey)
|
|
622
|
+
extra.recentSpawns?.set(planDedupeKey, Date.now());
|
|
623
|
+
(0, spawn_1.spawnPlanSession)(agentId, apiKey, task.id, projectId ?? task.projectId, extra.settings, undefined, runDir, { auto: true });
|
|
624
|
+
if (event.type === 'task.assigned') {
|
|
625
|
+
(0, wake_state_1.recordProcessed)(agentId, 'task.assigned', task.id);
|
|
474
626
|
}
|
|
627
|
+
return 'spawned';
|
|
475
628
|
}
|
|
476
629
|
// FOLDER SERIALIZATION (safety net): when this wake could not get its own
|
|
477
630
|
// worktree (creation refused/failed) and another session occupies the
|
|
478
631
|
// linked folder, defer the wake until the folder becomes free. Covers
|
|
479
632
|
// runs, build/plan/tool triggers, document mentions and TASK-linked chat.
|
|
480
|
-
// Excluded: plain chat (read-only
|
|
633
|
+
// Excluded: plain chat (read-only —” never edits files, runs immediately),
|
|
481
634
|
// resume wakes queue here by design, stop signals return earlier in
|
|
482
635
|
// handleWake, and worktree-scoped wakes bypass it (they are isolated).
|
|
483
636
|
if (!event.worktreePath && !plainChat) {
|
|
484
|
-
const folderKey = (0, repo_lock_1.canonicalDir)(
|
|
485
|
-
const occupants = (0, presence_1.listPresent)(
|
|
637
|
+
const folderKey = (0, repo_lock_1.canonicalDir)(runDir);
|
|
638
|
+
const occupants = (0, presence_1.listPresent)(runDir).filter((p) => p.agentId !== agentId || p.pid !== process.pid);
|
|
486
639
|
if (occupants.length > 0) {
|
|
487
640
|
const queue = exports.folderWaitQueue.get(folderKey) ?? [];
|
|
488
641
|
queue.push({ agentId, apiKey, event, enqueuedAt: Date.now() });
|
|
489
642
|
exports.folderWaitQueue.set(folderKey, queue);
|
|
490
643
|
const blocking = occupants.map((o) => `${o.agentId ?? 'pid:' + o.pid}`).join(', ');
|
|
491
|
-
(0, daemon_support_1.log)(`agent ${agentId}: wake ${event.type} queued behind ${blocking} in folder ${folderKey}
|
|
644
|
+
(0, daemon_support_1.log)(`agent ${agentId}: wake ${event.type} queued behind ${blocking} in folder ${folderKey} —” ${queue.length} waiting`);
|
|
492
645
|
return 'queued';
|
|
493
646
|
}
|
|
494
647
|
}
|
|
@@ -518,10 +671,10 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
518
671
|
const repoMap = buildRepoMap(event.projectId);
|
|
519
672
|
(0, daemon_support_1.log)(`agent ${agentId}: chat.mention task-linked -> ${linkedTaskId}` +
|
|
520
673
|
`${repoMap ? ` (${Object.keys(repoMap).length} repos)` : ''}`);
|
|
521
|
-
(0, spawn_1.spawnChatSession)(agentId, apiKey, event.projectId, event.mention ?? '', extra.settings, undefined,
|
|
674
|
+
(0, spawn_1.spawnChatSession)(agentId, apiKey, event.projectId, event.mention ?? '', extra.settings, undefined, runDir, event.messageId, event.mentionId ?? undefined, linkedTaskId, repoMap);
|
|
522
675
|
return 'spawned';
|
|
523
676
|
}
|
|
524
|
-
(0, spawn_1.spawnChatSession)(agentId, apiKey, event.projectId, event.mention ?? '', extra.settings, undefined,
|
|
677
|
+
(0, spawn_1.spawnChatSession)(agentId, apiKey, event.projectId, event.mention ?? '', extra.settings, undefined, runDir, event.messageId, event.mentionId ?? undefined);
|
|
525
678
|
return 'spawned';
|
|
526
679
|
}
|
|
527
680
|
if (event.type === 'document.mention') {
|
|
@@ -533,7 +686,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
533
686
|
// IMP-9xx: pass the waking comment id so the loop answers ONLY that
|
|
534
687
|
// comment (answer-once guard still applies) instead of rescanning
|
|
535
688
|
// every historical mention in the thread.
|
|
536
|
-
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings, undefined,
|
|
689
|
+
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings, undefined, runDir, event.commentId);
|
|
537
690
|
return 'spawned';
|
|
538
691
|
}
|
|
539
692
|
if (event.type === 'document.edit_request') {
|
|
@@ -545,10 +698,10 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
545
698
|
// The agent reads the document, applies the edit instruction, and
|
|
546
699
|
// replies with what changed.
|
|
547
700
|
const editCtx = event.editRequest
|
|
548
|
-
? ` [EDIT REQUEST: "${event.editRequest.instruction}" on page ${event.editRequest.pageNumber ?? 'N/A'}
|
|
701
|
+
? ` [EDIT REQUEST: "${event.editRequest.instruction}" on page ${event.editRequest.pageNumber ?? 'N/A'} —” quoted: "${event.editRequest.quote.slice(0, 200)}"]`
|
|
549
702
|
: '';
|
|
550
703
|
(0, daemon_support_1.log)(`agent ${agentId}: document.edit_request doc=${event.documentId} comment=${event.commentId ?? 'none'}${editCtx}`);
|
|
551
|
-
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings, undefined,
|
|
704
|
+
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings, undefined, runDir, event.commentId);
|
|
552
705
|
return 'spawned';
|
|
553
706
|
}
|
|
554
707
|
if (event.type === 'board.mention') {
|
|
@@ -558,7 +711,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
558
711
|
}
|
|
559
712
|
// Board assistant: read the board, draw/edit with the board MCP tools,
|
|
560
713
|
// reply as a board comment (mirrors the document assistant).
|
|
561
|
-
(0, spawn_1.spawnBoardSession)(agentId, apiKey, event.whiteboardId, event.mention ?? '', extra.settings, undefined,
|
|
714
|
+
(0, spawn_1.spawnBoardSession)(agentId, apiKey, event.whiteboardId, event.mention ?? '', extra.settings, undefined, runDir, event.commentId);
|
|
562
715
|
return 'spawned';
|
|
563
716
|
}
|
|
564
717
|
if (event.type === 'build.trigger') {
|
|
@@ -567,7 +720,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
567
720
|
return 'skipped';
|
|
568
721
|
}
|
|
569
722
|
// Build mode: start (or restart) the project's build run.
|
|
570
|
-
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined,
|
|
723
|
+
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined, runDir);
|
|
571
724
|
return 'spawned';
|
|
572
725
|
}
|
|
573
726
|
if (event.type === 'build.stop') {
|
|
@@ -594,7 +747,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
594
747
|
return 'skipped';
|
|
595
748
|
}
|
|
596
749
|
(0, daemon_support_1.log)(`agent ${agentId}: plan.trigger task=${event.taskId} -> planning session`);
|
|
597
|
-
(0, spawn_1.spawnPlanSession)(agentId, apiKey, event.taskId, event.projectId ?? undefined, extra.settings, undefined,
|
|
750
|
+
(0, spawn_1.spawnPlanSession)(agentId, apiKey, event.taskId, event.projectId ?? undefined, extra.settings, undefined, runDir);
|
|
598
751
|
return 'spawned';
|
|
599
752
|
}
|
|
600
753
|
if (event.type === 'tool.trigger') {
|
|
@@ -603,7 +756,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
603
756
|
return 'skipped';
|
|
604
757
|
}
|
|
605
758
|
// Tools-as-tasks: spawn the research/data-collection session for this run.
|
|
606
|
-
(0, spawn_1.spawnResearchSession)(agentId, apiKey, event.projectId, event.runId, event.toolKey ?? 'tool', extra.settings, undefined,
|
|
759
|
+
(0, spawn_1.spawnResearchSession)(agentId, apiKey, event.projectId, event.runId, event.toolKey ?? 'tool', extra.settings, undefined, runDir);
|
|
607
760
|
// Mark this run as worked so a later reconnect re-sync does not spawn a
|
|
608
761
|
// duplicate research session for the same run.
|
|
609
762
|
(0, wake_state_1.recordProcessed)(agentId, 'tool.trigger', String(event.runId));
|
|
@@ -657,7 +810,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
657
810
|
catch {
|
|
658
811
|
/* best-effort */
|
|
659
812
|
}
|
|
660
|
-
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined,
|
|
813
|
+
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined, runDir);
|
|
661
814
|
spawned = true;
|
|
662
815
|
}
|
|
663
816
|
else if (event.taskId) {
|
|
@@ -667,7 +820,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
667
820
|
// has no record here, so nothing is spawned - the next run is fresh.
|
|
668
821
|
//
|
|
669
822
|
// workMode-aware (2026-09): a task in build mode must NEVER resume a
|
|
670
|
-
// plan session (the plan conversation opens with "PLAN MODE
|
|
823
|
+
// plan session (the plan conversation opens with "PLAN MODE —¦ must NOT
|
|
671
824
|
// execute" and the model inherits that read-only claim). Tool/research
|
|
672
825
|
// tasks are always build-mode and skip the plan branch entirely.
|
|
673
826
|
let workMode;
|
|
@@ -677,21 +830,21 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
677
830
|
workMode = t.workMode;
|
|
678
831
|
taskKind = t.kind;
|
|
679
832
|
}
|
|
680
|
-
catch { /* best-effort
|
|
833
|
+
catch { /* best-effort —” fall through to session-only logic */ }
|
|
681
834
|
const isToolTask = taskKind === 'research' || taskKind === 'tool';
|
|
682
835
|
const planAllowed = !isToolTask && workMode !== 'build';
|
|
683
836
|
if ((0, session_store_1.hasHarnessSession)(agentId, event.taskId, 'run')) {
|
|
684
837
|
(0, daemon_support_1.log)(`agent ${agentId}: question.answered -> resuming run session for task ${event.taskId}`);
|
|
685
|
-
(0, spawn_1.spawnAgentSession)(agentId, apiKey, event.taskId, extra.settings, undefined,
|
|
838
|
+
(0, spawn_1.spawnAgentSession)(agentId, apiKey, event.taskId, extra.settings, undefined, runDir);
|
|
686
839
|
spawned = true;
|
|
687
840
|
}
|
|
688
841
|
else if (planAllowed && (0, session_store_1.hasHarnessSession)(agentId, event.taskId, 'plan')) {
|
|
689
842
|
(0, daemon_support_1.log)(`agent ${agentId}: question.answered -> resuming plan session for task ${event.taskId}`);
|
|
690
|
-
(0, spawn_1.spawnPlanSession)(agentId, apiKey, event.taskId, event.projectId ?? undefined, extra.settings, undefined,
|
|
843
|
+
(0, spawn_1.spawnPlanSession)(agentId, apiKey, event.taskId, event.projectId ?? undefined, extra.settings, undefined, runDir);
|
|
691
844
|
spawned = true;
|
|
692
845
|
}
|
|
693
846
|
else if ((0, session_store_1.hasHarnessSession)(agentId, event.taskId, 'build')) {
|
|
694
|
-
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined,
|
|
847
|
+
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined, runDir);
|
|
695
848
|
spawned = true;
|
|
696
849
|
}
|
|
697
850
|
else {
|
|
@@ -701,7 +854,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
701
854
|
return spawned ? 'spawned' : 'skipped';
|
|
702
855
|
}
|
|
703
856
|
if (event.type === 'question.expired') {
|
|
704
|
-
// Expired-on-timeout: same wake path as question.answered
|
|
857
|
+
// Expired-on-timeout: same wake path as question.answered —” the agent
|
|
705
858
|
// needs to know the question timed out so it can retry, escalate, or
|
|
706
859
|
// skip the task. The backend already marked the question expired; we
|
|
707
860
|
// just wake the agent loop so it picks up the new state.
|
|
@@ -726,7 +879,7 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
726
879
|
catch {
|
|
727
880
|
/* best-effort */
|
|
728
881
|
}
|
|
729
|
-
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined,
|
|
882
|
+
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined, runDir);
|
|
730
883
|
spawned = true;
|
|
731
884
|
}
|
|
732
885
|
else if (event.taskId) {
|
|
@@ -743,15 +896,15 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
743
896
|
const isToolTask = taskKind === 'research' || taskKind === 'tool';
|
|
744
897
|
const planAllowed = !isToolTask && workMode !== 'build';
|
|
745
898
|
if ((0, session_store_1.hasHarnessSession)(agentId, event.taskId, 'run')) {
|
|
746
|
-
(0, spawn_1.spawnAgentSession)(agentId, apiKey, event.taskId, extra.settings, undefined,
|
|
899
|
+
(0, spawn_1.spawnAgentSession)(agentId, apiKey, event.taskId, extra.settings, undefined, runDir);
|
|
747
900
|
spawned = true;
|
|
748
901
|
}
|
|
749
902
|
else if (planAllowed && (0, session_store_1.hasHarnessSession)(agentId, event.taskId, 'plan')) {
|
|
750
|
-
(0, spawn_1.spawnPlanSession)(agentId, apiKey, event.taskId, event.projectId ?? undefined, extra.settings, undefined,
|
|
903
|
+
(0, spawn_1.spawnPlanSession)(agentId, apiKey, event.taskId, event.projectId ?? undefined, extra.settings, undefined, runDir);
|
|
751
904
|
spawned = true;
|
|
752
905
|
}
|
|
753
906
|
else if ((0, session_store_1.hasHarnessSession)(agentId, event.taskId, 'build')) {
|
|
754
|
-
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined,
|
|
907
|
+
(0, spawn_1.spawnBuildSession)(agentId, apiKey, event.projectId, extra.settings, undefined, runDir);
|
|
755
908
|
spawned = true;
|
|
756
909
|
}
|
|
757
910
|
}
|
|
@@ -761,8 +914,8 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
761
914
|
// Comment mentions on tasks spawn a task session (agent responds in
|
|
762
915
|
// the task thread). Document comment mentions spawn the doc assistant.
|
|
763
916
|
if (event.documentId) {
|
|
764
|
-
if (projectId &&
|
|
765
|
-
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings, undefined,
|
|
917
|
+
if (projectId && runDir) {
|
|
918
|
+
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings, undefined, runDir, event.commentId, event.mentionId ?? undefined, projectId ?? undefined);
|
|
766
919
|
return 'spawned';
|
|
767
920
|
}
|
|
768
921
|
return 'skipped';
|
|
@@ -776,16 +929,16 @@ async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
|
776
929
|
}
|
|
777
930
|
// Multi-repo: build repoMap from config for spawned sessions.
|
|
778
931
|
const repoMap = buildRepoMap(projectId);
|
|
779
|
-
// IMP-810: Worker mode
|
|
932
|
+
// IMP-810: Worker mode —” spawn the worker loop for orchestrator subtasks
|
|
780
933
|
if (event.role === 'worker') {
|
|
781
934
|
(0, daemon_support_1.log)(`agent ${agentId}: spawning worker session for task ${taskId}`);
|
|
782
|
-
(0, spawn_1.spawnWorkerSession)(agentId, apiKey, taskId, event.orchestratorTaskId, extra.settings, undefined,
|
|
935
|
+
(0, spawn_1.spawnWorkerSession)(agentId, apiKey, taskId, event.orchestratorTaskId, extra.settings, undefined, runDir, repoMap);
|
|
783
936
|
return 'spawned';
|
|
784
937
|
}
|
|
785
938
|
// `task.run` is an explicit human "Run task": it must run exactly like an
|
|
786
939
|
// open task (status bypass) AND, on success, end in `in_review` regardless
|
|
787
940
|
// of the prior status (including closed/resolved). Flag it for the CLI.
|
|
788
|
-
(0, spawn_1.spawnAgentSession)(agentId, apiKey, taskId, extra.settings, undefined,
|
|
941
|
+
(0, spawn_1.spawnAgentSession)(agentId, apiKey, taskId, extra.settings, undefined, runDir, repoMap, event.type === 'task.run');
|
|
789
942
|
return 'spawned';
|
|
790
943
|
}
|
|
791
944
|
//# sourceMappingURL=wake-handler.js.map
|