agent-relay-server 0.70.1 → 0.72.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/docs/openapi.json +1 -1
- package/package.json +2 -2
- package/public/assets/MessageBubble-ak21YMQE.js +3 -0
- package/public/assets/{MessageBubble-DPcJxTJj.js.map → MessageBubble-ak21YMQE.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-BYmE8ldg.js → PlanGraphPanel-CubzRNGK.js} +2 -2
- package/public/assets/{PlanGraphPanel-BYmE8ldg.js.map → PlanGraphPanel-CubzRNGK.js.map} +1 -1
- package/public/assets/{activity-BzU196gP.js → activity-83lDNDEp.js} +2 -2
- package/public/assets/{activity-BzU196gP.js.map → activity-83lDNDEp.js.map} +1 -1
- package/public/assets/{agent-profiles-CQuoP8Nr.js → agent-profiles-BzjoH-NX.js} +2 -2
- package/public/assets/{agent-profiles-CQuoP8Nr.js.map → agent-profiles-BzjoH-NX.js.map} +1 -1
- package/public/assets/{agents-qH6q44nK.js → agents-BQhoABy8.js} +2 -2
- package/public/assets/{agents-qH6q44nK.js.map → agents-BQhoABy8.js.map} +1 -1
- package/public/assets/{analytics-EJH9GFID.js → analytics-BrsfrNFw.js} +3 -3
- package/public/assets/{analytics-EJH9GFID.js.map → analytics-BrsfrNFw.js.map} +1 -1
- package/public/assets/{automation-Dahmxg07.js → automation-VYuix2Iu.js} +2 -2
- package/public/assets/{automation-Dahmxg07.js.map → automation-VYuix2Iu.js.map} +1 -1
- package/public/assets/{badge-C6NJQ60B.js → badge-lljplQxm.js} +2 -2
- package/public/assets/{badge-C6NJQ60B.js.map → badge-lljplQxm.js.map} +1 -1
- package/public/assets/{branch-state-badge-C7H8HY22.js → branch-state-badge-DjdzycOv.js} +2 -2
- package/public/assets/{branch-state-badge-C7H8HY22.js.map → branch-state-badge-DjdzycOv.js.map} +1 -1
- package/public/assets/{button-DD2CCM3t.js → button-BwkKot02.js} +2 -2
- package/public/assets/{button-DD2CCM3t.js.map → button-BwkKot02.js.map} +1 -1
- package/public/assets/card-ByAPej7n.js +2 -0
- package/public/assets/{card-GZqj3rMm.js.map → card-ByAPej7n.js.map} +1 -1
- package/public/assets/{channels-VhlwES_A.js → channels-n4vJBF5t.js} +2 -2
- package/public/assets/{channels-VhlwES_A.js.map → channels-n4vJBF5t.js.map} +1 -1
- package/public/assets/chat-Dh-3tqyP.js +2 -0
- package/public/assets/{chat-DJqvtoFT.js.map → chat-Dh-3tqyP.js.map} +1 -1
- package/public/assets/{connectors-C03pRoXx.js → connectors-B8cKMHKY.js} +2 -2
- package/public/assets/{connectors-C03pRoXx.js.map → connectors-B8cKMHKY.js.map} +1 -1
- package/public/assets/copy-button-Pha61-a_.js +2 -0
- package/public/assets/{copy-button-DqAoeiPC.js.map → copy-button-Pha61-a_.js.map} +1 -1
- package/public/assets/display-LAWaAboe.js.map +1 -1
- package/public/assets/{dist-CePvvSwo.js → dist-0mCQ5WMT.js} +2 -2
- package/public/assets/{dist-CePvvSwo.js.map → dist-0mCQ5WMT.js.map} +1 -1
- package/public/assets/{dist-DByqf8W0.js → dist-B2WRWI7O.js} +2 -2
- package/public/assets/{dist-DByqf8W0.js.map → dist-B2WRWI7O.js.map} +1 -1
- package/public/assets/{dist-CROpCfON.js → dist-BBMeJabC.js} +2 -2
- package/public/assets/{dist-CROpCfON.js.map → dist-BBMeJabC.js.map} +1 -1
- package/public/assets/{dist-C_XTOHbv.js → dist-BXI214fV.js} +2 -2
- package/public/assets/{dist-C_XTOHbv.js.map → dist-BXI214fV.js.map} +1 -1
- package/public/assets/{dist-CpugiXtB.js → dist-BowDWFij.js} +2 -2
- package/public/assets/{dist-CpugiXtB.js.map → dist-BowDWFij.js.map} +1 -1
- package/public/assets/{dist-BauUE17c.js → dist-CT_Zr-w5.js} +2 -2
- package/public/assets/{dist-BauUE17c.js.map → dist-CT_Zr-w5.js.map} +1 -1
- package/public/assets/{dist-Djf_WfhW.js → dist-CXLRXIMd.js} +2 -2
- package/public/assets/{dist-Djf_WfhW.js.map → dist-CXLRXIMd.js.map} +1 -1
- package/public/assets/dist-CXzYZzCR.js +2 -0
- package/public/assets/{dist-DMD9fD99.js.map → dist-CXzYZzCR.js.map} +1 -1
- package/public/assets/{dist-otKoCUDq.js → dist-CcBB6z4x.js} +2 -2
- package/public/assets/{dist-otKoCUDq.js.map → dist-CcBB6z4x.js.map} +1 -1
- package/public/assets/dist-CgbWbsmS.js +2 -0
- package/public/assets/{dist-CW_EpVCQ.js.map → dist-CgbWbsmS.js.map} +1 -1
- package/public/assets/dist-DTgYq8HB.js +2 -0
- package/public/assets/{dist-DJ6ZVqRl.js.map → dist-DTgYq8HB.js.map} +1 -1
- package/public/assets/{es2015-t3pwjvKi.js → es2015-CkzQKfKp.js} +2 -2
- package/public/assets/{es2015-t3pwjvKi.js.map → es2015-CkzQKfKp.js.map} +1 -1
- package/public/assets/{formatted-body-impl-DhvR8Z-A.js → formatted-body-impl-Drko1nzi.js} +2 -2
- package/public/assets/{formatted-body-impl-DhvR8Z-A.js.map → formatted-body-impl-Drko1nzi.js.map} +1 -1
- package/public/assets/{index-Dh7NjR-l.css → index-Cczea_lj.css} +1 -1
- package/public/assets/index-ChQVgQyf.js +22 -0
- package/public/assets/{index-VGRP16ua.js.map → index-ChQVgQyf.js.map} +1 -1
- package/public/assets/{input-08ItMIki.js → input-sp_EWS3J.js} +2 -2
- package/public/assets/{input-08ItMIki.js.map → input-sp_EWS3J.js.map} +1 -1
- package/public/assets/{insights-v9noub5E.js → insights-BRclOzwJ.js} +2 -2
- package/public/assets/{insights-v9noub5E.js.map → insights-BRclOzwJ.js.map} +1 -1
- package/public/assets/{integrations-BEa3ArUw.js → integrations-CfaOLw9i.js} +2 -2
- package/public/assets/{integrations-BEa3ArUw.js.map → integrations-CfaOLw9i.js.map} +1 -1
- package/public/assets/{lib-CMqRPJG-.js → lib-DNMJa4Hh.js} +3 -3
- package/public/assets/{lib-CMqRPJG-.js.map → lib-DNMJa4Hh.js.map} +1 -1
- package/public/assets/{lucide-react-BbMYCUlW.js → lucide-react--HSX8ykl.js} +2 -2
- package/public/assets/{lucide-react-BbMYCUlW.js.map → lucide-react--HSX8ykl.js.map} +1 -1
- package/public/assets/{maintenance-DyRfVkhp.js → maintenance-BWbsnscm.js} +2 -2
- package/public/assets/{maintenance-DyRfVkhp.js.map → maintenance-BWbsnscm.js.map} +1 -1
- package/public/assets/{managed-agents-BSdMoBgG.js → managed-agents-D6yncoFy.js} +2 -2
- package/public/assets/{managed-agents-BSdMoBgG.js.map → managed-agents-D6yncoFy.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-DidsrMmn.js → markdown-preview-impl-o59KaM_T.js} +2 -2
- package/public/assets/{markdown-preview-impl-DidsrMmn.js.map → markdown-preview-impl-o59KaM_T.js.map} +1 -1
- package/public/assets/{memory-DM8muGVW.js → memory-CXoJ1LvP.js} +2 -2
- package/public/assets/{memory-DM8muGVW.js.map → memory-CXoJ1LvP.js.map} +1 -1
- package/public/assets/{messages-BqjAkcBF.js → messages-EqiGQvcb.js} +2 -2
- package/public/assets/{messages-BqjAkcBF.js.map → messages-EqiGQvcb.js.map} +1 -1
- package/public/assets/{orchestrators-C_mjUDZg.js → orchestrators-Ds_vwaEy.js} +2 -2
- package/public/assets/{orchestrators-C_mjUDZg.js.map → orchestrators-Ds_vwaEy.js.map} +1 -1
- package/public/assets/{overview-Cpu2plX-.js → overview-DSe3b-6K.js} +2 -2
- package/public/assets/{overview-Cpu2plX-.js.map → overview-DSe3b-6K.js.map} +1 -1
- package/public/assets/{pairs--8_0A0lq.js → pairs-C6RT1uqI.js} +2 -2
- package/public/assets/{pairs--8_0A0lq.js.map → pairs-C6RT1uqI.js.map} +1 -1
- package/public/assets/{projects-nCww9PG1.js → projects-QriMCcqU.js} +2 -2
- package/public/assets/{projects-nCww9PG1.js.map → projects-QriMCcqU.js.map} +1 -1
- package/public/assets/{react-dom-HJwa4sZ-.js → react-dom-c3AoN3Om.js} +2 -2
- package/public/assets/{react-dom-HJwa4sZ-.js.map → react-dom-c3AoN3Om.js.map} +1 -1
- package/public/assets/{security-CwgoEvJT.js → security-CbpLH77r.js} +2 -2
- package/public/assets/{security-CwgoEvJT.js.map → security-CbpLH77r.js.map} +1 -1
- package/public/assets/{select-Bhx6zSZr.js → select-B50dx5dg.js} +2 -2
- package/public/assets/{select-Bhx6zSZr.js.map → select-B50dx5dg.js.map} +1 -1
- package/public/assets/settings-mBwuRyo3.js +4 -0
- package/public/assets/{settings-BNQBnKXg.js.map → settings-mBwuRyo3.js.map} +1 -1
- package/public/assets/store-C_mEOgkp.js +9 -0
- package/public/assets/{store-BB-YW24l.js.map → store-C_mEOgkp.js.map} +1 -1
- package/public/assets/{tasks-C5Kv18N7.js → tasks-XzhOn6rE.js} +2 -2
- package/public/assets/{tasks-C5Kv18N7.js.map → tasks-XzhOn6rE.js.map} +1 -1
- package/public/assets/teams-Dm6ToSMX.js +2 -0
- package/public/assets/{teams-D_Ttv7qD.js.map → teams-Dm6ToSMX.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-5y8v9jNx.js → terminal-viewer-impl-BRu1_SEu.js} +3 -3
- package/public/assets/{terminal-viewer-impl-5y8v9jNx.js.map → terminal-viewer-impl-BRu1_SEu.js.map} +1 -1
- package/public/assets/{use-keyboard-viewport-CEmOxaNm.js → use-keyboard-viewport-NLJzBfhI.js} +2 -2
- package/public/assets/{use-keyboard-viewport-CEmOxaNm.js.map → use-keyboard-viewport-NLJzBfhI.js.map} +1 -1
- package/public/assets/{work-queue-BB7bYLIK.js → work-queue-CjnV15xQ.js} +2 -2
- package/public/assets/{work-queue-BB7bYLIK.js.map → work-queue-CjnV15xQ.js.map} +1 -1
- package/public/assets/workspaces-DgeUNiwv.js +3 -0
- package/public/assets/{workspaces-CrtAeVnv.js.map → workspaces-DgeUNiwv.js.map} +1 -1
- package/public/index.html +21 -21
- package/runner/src/adapter.ts +7 -1
- package/src/config-store.ts +4 -2
- package/src/db/message-reads.ts +8 -46
- package/src/db/messages.ts +176 -0
- package/src/mcp/tools-spawn.ts +4 -3
- package/src/project-instructions.ts +12 -1
- package/src/services/issue-lifecycle.ts +24 -1
- package/src/services/send-message.ts +3 -0
- package/src/services/spawn-agent.ts +16 -17
- package/src/spawn-targets.ts +3 -3
- package/public/assets/MessageBubble-DPcJxTJj.js +0 -3
- package/public/assets/card-GZqj3rMm.js +0 -2
- package/public/assets/chat-DJqvtoFT.js +0 -2
- package/public/assets/copy-button-DqAoeiPC.js +0 -2
- package/public/assets/dist-CW_EpVCQ.js +0 -2
- package/public/assets/dist-DJ6ZVqRl.js +0 -2
- package/public/assets/dist-DMD9fD99.js +0 -2
- package/public/assets/index-VGRP16ua.js +0 -22
- package/public/assets/settings-BNQBnKXg.js +0 -4
- package/public/assets/store-BB-YW24l.js +0 -9
- package/public/assets/teams-D_Ttv7qD.js +0 -2
- package/public/assets/workspaces-CrtAeVnv.js +0 -3
package/src/db/message-reads.ts
CHANGED
|
@@ -415,11 +415,13 @@ function isCoveredByLaterAgentResponse(message: Message, agentId: string): boole
|
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
function replyObligationFromMessage(message: Message, agentId: string): ReplyObligation {
|
|
418
|
+
const source = stringField(message.payload?.source);
|
|
418
419
|
return {
|
|
419
420
|
messageId: message.id,
|
|
420
421
|
agentId,
|
|
421
422
|
from: message.from,
|
|
422
423
|
kind: message.kind,
|
|
424
|
+
...(source ? { source } : {}),
|
|
423
425
|
...(message.subject ? { subject: message.subject } : {}),
|
|
424
426
|
...(message.channel ? { channel: message.channel } : {}),
|
|
425
427
|
bodyPreview: message.body.length > 240 ? `${message.body.slice(0, 240)}\n[truncated]` : message.body,
|
|
@@ -502,18 +504,11 @@ export function spawnObligationAnsweredBy(
|
|
|
502
504
|
}
|
|
503
505
|
|
|
504
506
|
/**
|
|
505
|
-
* #
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* finds nothing to enforce and lets the worker idle with its result undelivered. This recovers the
|
|
509
|
-
* spawn command by its `spawnRequestId` (same lookup as `seedContinuationObjectiveFromSpawnPrompt`),
|
|
510
|
-
* and — only when the spawn carried `expectReply` — posts a message FROM the spawner TO the worker
|
|
511
|
-
* and marks it read, making it an obligation immediately. The Stop hook then refuses to let the
|
|
512
|
-
* worker idle until it reports back; a natural direct send to the spawner is linked at send time.
|
|
507
|
+
* #573 — spawned children no longer get a report-back reply obligation. Their turn-final
|
|
508
|
+
* response is promoted server-side to the spawn-parent by lineage, so creating a separate
|
|
509
|
+
* obligation would only reintroduce nag/reply-tool ceremony.
|
|
513
510
|
*
|
|
514
|
-
*
|
|
515
|
-
* Never throws — a failure here must not abort registration. Returns the obligation message id,
|
|
516
|
-
* or null when nothing was seeded.
|
|
511
|
+
* Kept as a no-op compatibility seam for old registration code/tests that still call it.
|
|
517
512
|
*/
|
|
518
513
|
export function seedSpawnReplyObligation(
|
|
519
514
|
agentId: string,
|
|
@@ -521,41 +516,8 @@ export function seedSpawnReplyObligation(
|
|
|
521
516
|
spawnRequestId: string | undefined,
|
|
522
517
|
now = Date.now(),
|
|
523
518
|
): number | null {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (!getAgent(spawnedBy)) return null;
|
|
527
|
-
try {
|
|
528
|
-
const row = getDb().query(`
|
|
529
|
-
SELECT params FROM commands
|
|
530
|
-
WHERE type = 'agent.spawn' AND correlation_id = ?
|
|
531
|
-
ORDER BY created_at DESC
|
|
532
|
-
LIMIT 1
|
|
533
|
-
`).get(spawnRequestId) as { params: string } | undefined;
|
|
534
|
-
const params = row ? parseJson<unknown>(row.params, {}) : {};
|
|
535
|
-
if (!isRecord(params) || params.expectReply !== true) return null;
|
|
536
|
-
|
|
537
|
-
const contract = `Report-back requested by ${spawnedBy}. When you finish your spawned task, reply here with your result so the coordinator is notified — you should not go idle before delivering it.`;
|
|
538
|
-
|
|
539
|
-
const { message } = sendMessageWithResult({
|
|
540
|
-
from: spawnedBy,
|
|
541
|
-
to: agentId,
|
|
542
|
-
subject: "Report your result when done",
|
|
543
|
-
body: contract,
|
|
544
|
-
replyExpected: true,
|
|
545
|
-
// `payload.source` is the server-owned "this requires a reply" signal (see messageRequiresReply).
|
|
546
|
-
payload: { source: "spawn-obligation", spawnRequestId },
|
|
547
|
-
idempotencyKey: `spawn-obligation:${spawnRequestId}`,
|
|
548
|
-
occurredAt: now,
|
|
549
|
-
});
|
|
550
|
-
// Pre-read so it is a *pending* obligation immediately: the worker got its task via the
|
|
551
|
-
// positional spawn prompt and may finish in one turn without ever polling its inbox, so an
|
|
552
|
-
// unread message would never become an obligation — the exact gap this closes.
|
|
553
|
-
markRead(message.id, agentId);
|
|
554
|
-
return message.id;
|
|
555
|
-
} catch {
|
|
556
|
-
// Registration must succeed even if obligation seeding fails (e.g. a racing spawner exit).
|
|
557
|
-
return null;
|
|
558
|
-
}
|
|
519
|
+
void agentId; void spawnedBy; void spawnRequestId; void now;
|
|
520
|
+
return null;
|
|
559
521
|
}
|
|
560
522
|
|
|
561
523
|
/**
|
package/src/db/messages.ts
CHANGED
|
@@ -157,6 +157,7 @@ function inferMessageKind(input: SendMessageInput): Message["kind"] {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
const REPLY_DUPLICATE_WINDOW_MS = 2 * 60 * 1000;
|
|
160
|
+
const LINEAGE_REPORT_DUPLICATE_WINDOW_MS = 2 * 60 * 1000;
|
|
160
161
|
|
|
161
162
|
function findRecentDuplicateReply(input: SendMessageInput, threadId: number | null, now: number, hasAttachments: boolean): Message | null {
|
|
162
163
|
if (!input.replyTo || threadId === null || hasAttachments) return null;
|
|
@@ -186,6 +187,178 @@ function findRecentDuplicateReply(input: SendMessageInput, threadId: number | nu
|
|
|
186
187
|
return row ? rowToMessage(row) : null;
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
function sessionMeta(input: Message | SendMessageInput): Record<string, unknown> | undefined {
|
|
191
|
+
return isRecord(input.payload?.session) ? input.payload.session : undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function responseCaptureMeta(input: Message | SendMessageInput): Record<string, unknown> | undefined {
|
|
195
|
+
return isRecord(input.payload?.responseCapture) ? input.payload.responseCapture : undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function isFinalSessionResponse(session: Record<string, unknown>): boolean {
|
|
199
|
+
return session.type === "response" && session.final === true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function captureTurnId(capture: Record<string, unknown>): string | undefined {
|
|
203
|
+
return stringValue(capture.turnId);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function lineageResponseTurnId(message: Message, session: Record<string, unknown>): string {
|
|
207
|
+
return stringValue(session.turnId) ?? `message-${message.id}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function lineageResponseReportKeyForTurn(childId: string, turnId: string): string {
|
|
211
|
+
return `response-report:${childId}:${turnId}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function lineageResponseReportKey(message: Message, session: Record<string, unknown>): string {
|
|
215
|
+
return lineageResponseReportKeyForTurn(message.from, lineageResponseTurnId(message, session));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function findLineageReportByTurn(input: {
|
|
219
|
+
from: string;
|
|
220
|
+
to: string;
|
|
221
|
+
turnId: string;
|
|
222
|
+
since: number;
|
|
223
|
+
}): Message | null {
|
|
224
|
+
const lineageKey = lineageResponseReportKeyForTurn(input.from, input.turnId);
|
|
225
|
+
const row = getDb().query(`
|
|
226
|
+
${MSG_SELECT}
|
|
227
|
+
WHERE m.from_agent = ?
|
|
228
|
+
AND m.to_target = ?
|
|
229
|
+
AND m.kind = 'chat'
|
|
230
|
+
AND m.created_at >= ?
|
|
231
|
+
AND (
|
|
232
|
+
m.idempotency_key = ?
|
|
233
|
+
OR m.idempotency_key LIKE ?
|
|
234
|
+
OR (
|
|
235
|
+
json_extract(m.payload, '$.responseCapture.type') = 'response'
|
|
236
|
+
AND json_extract(m.payload, '$.responseCapture.turnId') = ?
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
ORDER BY m.id DESC
|
|
240
|
+
LIMIT 1
|
|
241
|
+
`).get(
|
|
242
|
+
input.from,
|
|
243
|
+
input.to,
|
|
244
|
+
input.since,
|
|
245
|
+
lineageKey,
|
|
246
|
+
`response-report:${input.from}:%:${input.turnId}`,
|
|
247
|
+
input.turnId,
|
|
248
|
+
) as any;
|
|
249
|
+
return row ? rowToMessage(row) : null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function findRecentLineageReport(input: SendMessageInput, now: number): Message | null {
|
|
253
|
+
if ((input.kind ?? "chat") !== "chat") return null;
|
|
254
|
+
const from = input.from;
|
|
255
|
+
const to = input.to;
|
|
256
|
+
if (!from || !to) return null;
|
|
257
|
+
const child = getAgent(from);
|
|
258
|
+
if (!child?.spawnedBy || child.spawnedBy !== to) return null;
|
|
259
|
+
const responseCapture = responseCaptureMeta(input);
|
|
260
|
+
const turnId = responseCapture ? captureTurnId(responseCapture) : undefined;
|
|
261
|
+
if (turnId) {
|
|
262
|
+
const duplicate = findLineageReportByTurn({
|
|
263
|
+
from,
|
|
264
|
+
to,
|
|
265
|
+
turnId,
|
|
266
|
+
since: now - LINEAGE_REPORT_DUPLICATE_WINDOW_MS,
|
|
267
|
+
});
|
|
268
|
+
if (duplicate) return duplicate;
|
|
269
|
+
}
|
|
270
|
+
if (responseCapture) return null;
|
|
271
|
+
const row = getDb().query(`
|
|
272
|
+
${MSG_SELECT}
|
|
273
|
+
WHERE m.from_agent = ?
|
|
274
|
+
AND m.to_target = ?
|
|
275
|
+
AND m.kind = 'chat'
|
|
276
|
+
AND m.body = ?
|
|
277
|
+
AND m.created_at >= ?
|
|
278
|
+
AND (
|
|
279
|
+
m.idempotency_key LIKE ?
|
|
280
|
+
OR json_extract(m.payload, '$.responseCapture.type') = 'response'
|
|
281
|
+
)
|
|
282
|
+
ORDER BY m.id DESC
|
|
283
|
+
LIMIT 1
|
|
284
|
+
`).get(from, to, input.body, now - LINEAGE_REPORT_DUPLICATE_WINDOW_MS, `response-report:${from}:%`) as any;
|
|
285
|
+
return row ? rowToMessage(row) : null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function findRecentExplicitLineageDuplicate(input: {
|
|
289
|
+
from: string;
|
|
290
|
+
to: string;
|
|
291
|
+
body: string;
|
|
292
|
+
since: number;
|
|
293
|
+
}): Message | null {
|
|
294
|
+
const row = getDb().query(`
|
|
295
|
+
${MSG_SELECT}
|
|
296
|
+
WHERE m.from_agent = ?
|
|
297
|
+
AND m.to_target = ?
|
|
298
|
+
AND m.kind = 'chat'
|
|
299
|
+
AND m.body = ?
|
|
300
|
+
AND m.created_at >= ?
|
|
301
|
+
ORDER BY m.id DESC
|
|
302
|
+
LIMIT 1
|
|
303
|
+
`).get(
|
|
304
|
+
input.from,
|
|
305
|
+
input.to,
|
|
306
|
+
input.body,
|
|
307
|
+
input.since,
|
|
308
|
+
) as any;
|
|
309
|
+
return row ? rowToMessage(row) : null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function promoteLineageCapturedResponse(message: Message): { message: Message; created: boolean } | null {
|
|
313
|
+
if (message.kind !== "session") return null;
|
|
314
|
+
const session = sessionMeta(message);
|
|
315
|
+
if (!session || !isFinalSessionResponse(session)) return null;
|
|
316
|
+
const child = getAgent(message.from);
|
|
317
|
+
const parent = child?.spawnedBy;
|
|
318
|
+
if (!parent) return null;
|
|
319
|
+
|
|
320
|
+
const idempotencyKey = lineageResponseReportKey(message, session);
|
|
321
|
+
const existing = findMessageByIdempotencyKey(message.from, idempotencyKey);
|
|
322
|
+
if (existing) return { message: existing, created: false };
|
|
323
|
+
const turnId = captureTurnId(session);
|
|
324
|
+
if (turnId) {
|
|
325
|
+
const crossKeyDuplicate = findLineageReportByTurn({
|
|
326
|
+
from: message.from,
|
|
327
|
+
to: parent,
|
|
328
|
+
turnId,
|
|
329
|
+
since: message.createdAt - LINEAGE_REPORT_DUPLICATE_WINDOW_MS,
|
|
330
|
+
});
|
|
331
|
+
if (crossKeyDuplicate) return { message: crossKeyDuplicate, created: false };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const duplicate = findRecentExplicitLineageDuplicate({
|
|
335
|
+
from: message.from,
|
|
336
|
+
to: parent,
|
|
337
|
+
body: message.body,
|
|
338
|
+
since: message.createdAt - LINEAGE_REPORT_DUPLICATE_WINDOW_MS,
|
|
339
|
+
});
|
|
340
|
+
if (duplicate && !responseCaptureMeta(duplicate)) {
|
|
341
|
+
getDb().query(`
|
|
342
|
+
UPDATE messages
|
|
343
|
+
SET idempotency_key = coalesce(idempotency_key, ?),
|
|
344
|
+
payload = ?
|
|
345
|
+
WHERE id = ?
|
|
346
|
+
`).run(idempotencyKey, JSON.stringify({ ...(duplicate.payload ?? {}), responseCapture: { ...session } }), duplicate.id);
|
|
347
|
+
return { message: getMessage(duplicate.id) ?? duplicate, created: false };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return sendMessageWithResult({
|
|
351
|
+
from: message.from,
|
|
352
|
+
to: parent,
|
|
353
|
+
kind: "chat",
|
|
354
|
+
body: message.body,
|
|
355
|
+
replyExpected: false,
|
|
356
|
+
idempotencyKey,
|
|
357
|
+
payload: { responseCapture: { ...session } },
|
|
358
|
+
occurredAt: message.occurredAt ?? message.createdAt,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
189
362
|
// Event time may be queued-then-backfilled, so it can legitimately be older than the
|
|
190
363
|
// receive time — but it must be a sane epoch-ms value. Returns null (column stays NULL, so
|
|
191
364
|
// readers fall back to created_at) for absent/invalid values or anything more than a minute
|
|
@@ -206,6 +379,9 @@ export function sendMessageWithResult(input: SendMessageInput): { message: Messa
|
|
|
206
379
|
}
|
|
207
380
|
validateAttachmentRefs(attachmentRefs);
|
|
208
381
|
|
|
382
|
+
const lineageDuplicate = findRecentLineageReport(input, now);
|
|
383
|
+
if (lineageDuplicate) return { message: lineageDuplicate, created: false };
|
|
384
|
+
|
|
209
385
|
if (input.idempotencyKey) {
|
|
210
386
|
const existing = findMessageByIdempotencyKey(input.from, input.idempotencyKey);
|
|
211
387
|
if (existing) {
|
package/src/mcp/tools-spawn.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { buildSpawnTargets } from "../spawn-targets";
|
|
|
5
5
|
import { McpAuthError, McpNotFoundError } from "../mcp-errors";
|
|
6
6
|
import { parseProjectInstructions, PROJECT_INSTRUCTION_POLICY_NAMES } from "../project-instructions";
|
|
7
7
|
import { ValidationError } from "../db";
|
|
8
|
+
import { DEFAULT_SPAWN_PROFILE_NAME } from "../config-store";
|
|
8
9
|
import { optionalEnum } from "../validation";
|
|
9
10
|
import { APPROVAL_MODES, SPAWN_PROVIDERS, VALID_AGENT_LIFECYCLES, VALID_EFFORTS, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
|
|
10
11
|
import type { ProviderEffort } from "agent-relay-sdk/provider-catalog";
|
|
@@ -16,7 +17,7 @@ import { enumField, optionalNonNegativeInt, optionalPositiveInt, optionalString,
|
|
|
16
17
|
export const SPAWN_TOOLS: ToolDefinition[] = [
|
|
17
18
|
{
|
|
18
19
|
name: "relay_spawn_agent",
|
|
19
|
-
description:
|
|
20
|
+
description: `Spawn a long-living provider agent through Relay's orchestrator, optionally handing it its first task via \`prompt\` in the same call. Defaults to your own host (override with orchestratorId), applies profile \`${DEFAULT_SPAWN_PROFILE_NAME}\` when profile is omitted, and returns the resolved agent id once it registers. Gated: requires the command:spawn scope, granted only to agents whose profile sets maxSpawnedAgents>0, up to that live-children quota. Spawned agents cannot themselves spawn (no grandchildren).`,
|
|
20
21
|
requiredScopes: ["command:spawn"],
|
|
21
22
|
inputSchema: {
|
|
22
23
|
type: "object",
|
|
@@ -29,10 +30,10 @@ export const SPAWN_TOOLS: ToolDefinition[] = [
|
|
|
29
30
|
effort: { type: "string", enum: VALID_EFFORTS },
|
|
30
31
|
approvalMode: { type: "string", enum: APPROVAL_MODES, description: "Permission posture for the worker. Defaults to YOUR OWN approval mode when omitted (a headless `guarded` worker wedges on the first approval prompt), so a coordinator running `open` spawns workers that can act autonomously. Pass an explicit value to narrow a child (e.g. `read-only` reviewer)." },
|
|
31
32
|
prompt: { type: "string", description: "Initial task/message delivered to the agent on launch — spawn and hand it its first instruction in one call (no separate follow-up message needed)." },
|
|
32
|
-
expectReply: { type: "boolean", description: "
|
|
33
|
+
expectReply: { type: "boolean", description: "Legacy compatibility flag. Spawned workers now report up automatically: each turn-final response is routed to the spawner by lineage, without a reply obligation or idle nag." },
|
|
33
34
|
systemPromptAppend: { type: "string" },
|
|
34
35
|
projectInstructions: { type: "object", description: "#409 — project instruction projection (default OFF). When set, relay composes a knowledge preamble from the spawn cwd's resolved project (own + inherited entries) and prepends it to systemPromptAppend, instead of relying on host CLAUDE.md. `policy: \"all\"` includes ingested host content; `\"vanilla\"` excludes it (manual+promoted only). Omit to keep current behavior.", properties: { policy: { type: "string", enum: PROJECT_INSTRUCTION_POLICY_NAMES } }, additionalProperties: false },
|
|
35
|
-
profile: { type: "string", description:
|
|
36
|
+
profile: { type: "string", description: `Agent profile name to apply (env, instructions, permissions, MCP/skills, spawn quota). Omit to use \`${DEFAULT_SPAWN_PROFILE_NAME}\`.` },
|
|
36
37
|
tags: { type: "array", items: { type: "string" } },
|
|
37
38
|
capabilities: { type: "array", items: { type: "string" } },
|
|
38
39
|
providerArgs: { type: "array", items: { type: "string" } },
|
|
@@ -49,6 +49,14 @@ interface ComposeProjectInstructionsOptions {
|
|
|
49
49
|
const DEFAULT_KINDS: readonly ProjectEntryKind[] = ["rule", "fact"];
|
|
50
50
|
const DEFAULT_MAX_BYTES = 8192;
|
|
51
51
|
const TRUNCATION_NOTICE = "_(… additional project entries omitted to fit the instruction budget.)_";
|
|
52
|
+
const SPAWNED_CHILD_REPORTUP_INSTRUCTION = [
|
|
53
|
+
"Agent Relay report-up:",
|
|
54
|
+
"You are a spawned child agent. Just respond normally when your turn is complete; Relay automatically delivers your final response to your coordinator. Do not call a messaging tool only to report completion.",
|
|
55
|
+
].join("\n");
|
|
56
|
+
const COORDINATOR_CHILD_REPORTUP_INSTRUCTION = [
|
|
57
|
+
"Agent Relay child responses:",
|
|
58
|
+
"When you spawn child agents, their turn-final responses arrive automatically as Relay messages. Do not put report-up instructions or your own agent id in child briefs.",
|
|
59
|
+
].join("\n");
|
|
52
60
|
|
|
53
61
|
const KIND_HEADINGS: Record<ProjectEntryKind, string> = {
|
|
54
62
|
rule: "Rules",
|
|
@@ -173,7 +181,8 @@ export function composeProjectInstructions(
|
|
|
173
181
|
export function composeSpawnInstructions(input: {
|
|
174
182
|
callerProject?: Pick<Project, "id" | "slug" | "title">;
|
|
175
183
|
projectInstructions?: ProjectInstructionPolicy;
|
|
176
|
-
profile?: Pick<AgentProfile, "instructions">;
|
|
184
|
+
profile?: Pick<AgentProfile, "instructions" | "maxSpawnedAgents">;
|
|
185
|
+
spawnedBy?: string;
|
|
177
186
|
callerSystemPromptAppend?: string;
|
|
178
187
|
}): string | undefined {
|
|
179
188
|
const appendBlocks: string[] = [];
|
|
@@ -184,6 +193,8 @@ export function composeSpawnInstructions(input: {
|
|
|
184
193
|
if (block.trim()) appendBlocks.push(block);
|
|
185
194
|
}
|
|
186
195
|
// [#559 seam] profile/user-scoped knowledge preamble composes in here.
|
|
196
|
+
if (input.spawnedBy) appendBlocks.push(SPAWNED_CHILD_REPORTUP_INSTRUCTION);
|
|
197
|
+
if ((input.profile?.maxSpawnedAgents ?? 0) > 0) appendBlocks.push(COORDINATOR_CHILD_REPORTUP_INSTRUCTION);
|
|
187
198
|
if (input.profile?.instructions.system?.trim()) appendBlocks.push(input.profile.instructions.system);
|
|
188
199
|
appendBlocks.push(...(input.profile?.instructions.append.filter((entry) => entry.trim()) ?? []));
|
|
189
200
|
if (input.callerSystemPromptAppend?.trim()) appendBlocks.push(input.callerSystemPromptAppend);
|
|
@@ -62,7 +62,7 @@ type LandIssueRefSource = "association" | "commit" | "backlink";
|
|
|
62
62
|
|
|
63
63
|
type CloseIssueLifecycleResult =
|
|
64
64
|
| { issueRef: IssueRef; action: "closed"; source: LandIssueRefSource; landKey: string }
|
|
65
|
-
| { issueRef: IssueRef; action: "skipped"; reason: "already-closed" | "already-processed"; landKey: string }
|
|
65
|
+
| { issueRef: IssueRef; action: "skipped"; reason: "already-closed" | "already-processed" | "open-sub-issues"; landKey: string }
|
|
66
66
|
| { issueRef: IssueRef; action: "failed"; error: string; landKey: string };
|
|
67
67
|
|
|
68
68
|
type ReopenIssueLifecycleResult =
|
|
@@ -208,6 +208,16 @@ function closeAuditComment(input: LandedIssueLifecycleInput): string {
|
|
|
208
208
|
return `Closed by \`${commit}\` on merge to \`main\` - reopen if release surfaces more work.`;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
function issueLabel(ref: IssueRef, parent: IssueRef): string {
|
|
212
|
+
return ref.repo === parent.repo ? `#${ref.externalKey}` : `${ref.repo}#${ref.externalKey}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function backlinkProgressComment(input: LandedIssueLifecycleInput, parent: IssueRef, openSubIssues: IssueRef[]): string {
|
|
216
|
+
const commit = input.mergedSha ? input.mergedSha.slice(0, 12) : input.workspace.branch ?? input.workspace.id;
|
|
217
|
+
const childList = openSubIssues.map((ref) => issueLabel(ref, parent)).join(", ");
|
|
218
|
+
return `Linked landed slice \`${commit}\` to this parent issue, but Agent Relay left it open because these sub-issues are still open: ${childList}.`;
|
|
219
|
+
}
|
|
220
|
+
|
|
211
221
|
async function reconcileClosedIssueRefBeforeSkip(issueRef: IssueRef, opts: IssueLifecycleOptions): Promise<{
|
|
212
222
|
issueRef: IssueRef;
|
|
213
223
|
liveStatus?: IssueRef["status"];
|
|
@@ -240,6 +250,19 @@ async function closeIssueRefForLand(
|
|
|
240
250
|
if (reconciled.liveStatus !== "open") return { issueRef: latest, action: "skipped", reason: "already-closed", landKey: key };
|
|
241
251
|
}
|
|
242
252
|
|
|
253
|
+
const openSubIssues = source === "backlink"
|
|
254
|
+
? listIssuesForEntity("epic", latest.id).map((item) => item.issueRef).filter((ref) => ref.status === "open")
|
|
255
|
+
: [];
|
|
256
|
+
if (openSubIssues.length > 0) {
|
|
257
|
+
const comment = backlinkProgressComment(input, latest, openSubIssues);
|
|
258
|
+
try {
|
|
259
|
+
await adapterFor(latest, opts).comment(latest, comment);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logFailure(opts, `issue lifecycle epic backlink comment failed for ${latest.id} on ${key}: ${errMessage(error)}`);
|
|
262
|
+
}
|
|
263
|
+
return { issueRef: latest, action: "skipped", reason: "open-sub-issues", landKey: key };
|
|
264
|
+
}
|
|
265
|
+
|
|
243
266
|
const comment = closeAuditComment(input);
|
|
244
267
|
try {
|
|
245
268
|
await adapterFor(latest, opts).close(latest, { comment, labels: ["fixed"] });
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
ValidationError,
|
|
27
27
|
getMessage,
|
|
28
28
|
listAgents,
|
|
29
|
+
promoteLineageCapturedResponse,
|
|
29
30
|
sendMessageWithResult,
|
|
30
31
|
channelObligationAnsweredBy,
|
|
31
32
|
spawnObligationAnsweredBy,
|
|
@@ -188,6 +189,8 @@ export function sendMessageService(
|
|
|
188
189
|
} else {
|
|
189
190
|
emitNewMessage(result.message);
|
|
190
191
|
}
|
|
192
|
+
const promoted = promoteLineageCapturedResponse(result.message);
|
|
193
|
+
if (promoted?.created) emitNewMessage(promoted.message);
|
|
191
194
|
} else if (result.updated) {
|
|
192
195
|
// Session-step upsert (#parity): the row changed in place (same DB id). Re-emit so the
|
|
193
196
|
// dashboard's id-keyed merge replaces the existing step live — no duplicate row.
|
|
@@ -31,7 +31,7 @@ import { emitCommandEvent } from "../command-events";
|
|
|
31
31
|
import { buildSpawnCommand, generateSpawnRequestId, resolveSpawnModelParams } from "../spawn-command";
|
|
32
32
|
import { runnerRuntimeTokenEnv } from "../runtime-tokens";
|
|
33
33
|
import { selectSpawnOrchestrator } from "../spawn-targets";
|
|
34
|
-
import { getAgentProfile } from "../config-store";
|
|
34
|
+
import { DEFAULT_SPAWN_PROFILE_NAME, getAgentProfile } from "../config-store";
|
|
35
35
|
import { composeSpawnInstructions, type ProjectInstructionPolicy } from "../project-instructions";
|
|
36
36
|
import { autoSeedProjectForCwd } from "./project";
|
|
37
37
|
import { hasComponentScope, isComponentAuthorizedFor } from "../security";
|
|
@@ -75,12 +75,9 @@ export interface SpawnAgentInput {
|
|
|
75
75
|
*/
|
|
76
76
|
projectInstructions?: ProjectInstructionPolicy;
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* that hands the child a prompt (a coordinator handing a worker a task almost always wants
|
|
82
|
-
* the result back); off for system/dashboard/automation spawns (no caller to report to).
|
|
83
|
-
* Pass false to opt a fire-and-forget agent spawn out.
|
|
78
|
+
* Legacy report-back contract flag. Spawned children now report up by lineage: their
|
|
79
|
+
* turn-final response is delivered to the spawner automatically, without a reply obligation.
|
|
80
|
+
* Explicit true is preserved in the command payload for compatibility only.
|
|
84
81
|
*/
|
|
85
82
|
expectReply?: boolean;
|
|
86
83
|
tags?: string[];
|
|
@@ -167,10 +164,11 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
167
164
|
const spawnRequestId = input.spawnRequestId ?? generateSpawnRequestId();
|
|
168
165
|
|
|
169
166
|
// Resolve + validate the named agent profile server-side (was HTTP-only; the MCP path passed
|
|
170
|
-
// the name through unvalidated).
|
|
171
|
-
//
|
|
172
|
-
const
|
|
173
|
-
|
|
167
|
+
// the name through unvalidated). Omitted profile uses Relay's spawn default so coordinator
|
|
168
|
+
// spawns still get the bundled Relay context/affordances. Explicit profile always wins.
|
|
169
|
+
const profileName = input.profile ?? DEFAULT_SPAWN_PROFILE_NAME;
|
|
170
|
+
const agentProfile = withResolvedProvisioning(getAgentProfile(profileName)?.value, input.provider);
|
|
171
|
+
if (!agentProfile) throw new McpNotFoundError(input.profile ? "agent profile not found" : `default agent profile ${DEFAULT_SPAWN_PROFILE_NAME} not found`);
|
|
174
172
|
|
|
175
173
|
// --- THE GATE (uniform across transports) ---------------------------------
|
|
176
174
|
// Coarse capability: spawning requires command:spawn. The MCP dispatcher already enforced
|
|
@@ -228,6 +226,7 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
228
226
|
...(callerProject ? { callerProject } : {}),
|
|
229
227
|
...(projectInstructions ? { projectInstructions } : {}),
|
|
230
228
|
...(agentProfile ? { profile: agentProfile } : {}),
|
|
229
|
+
...(callerId ? { spawnedBy: callerId } : {}),
|
|
231
230
|
...(input.systemPromptAppend ? { callerSystemPromptAppend: input.systemPromptAppend } : {}),
|
|
232
231
|
});
|
|
233
232
|
|
|
@@ -247,16 +246,16 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
247
246
|
// dropped this when it unified the spawn service, silently un-spawning every default-spawner
|
|
248
247
|
// (P0 regression #364). For an agent caller, agentInitiated still forces a non-spawn-capable
|
|
249
248
|
// child regardless of profile (no grandchildren).
|
|
250
|
-
profile:
|
|
249
|
+
profile: profileName,
|
|
251
250
|
...(callerId ? { agentInitiated: true, spawnedBy: callerId } : {}),
|
|
252
251
|
...(callerTeam ? { teamId: callerTeam.id } : {}),
|
|
253
252
|
...(callerProject ? { projectId: callerProject.id } : {}),
|
|
254
253
|
});
|
|
255
254
|
|
|
256
|
-
// #
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
const expectReply = input.expectReply
|
|
255
|
+
// #573 — no default reply obligation for spawned children. Report-up is lineage-routed from
|
|
256
|
+
// captured turn-final responses; explicit true is retained only for old clients inspecting the
|
|
257
|
+
// command payload.
|
|
258
|
+
const expectReply = input.expectReply === true;
|
|
260
259
|
|
|
261
260
|
const command = createCommand({
|
|
262
261
|
type: "agent.spawn",
|
|
@@ -270,7 +269,7 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
270
269
|
...(workspaceMode ? { workspaceMode } : {}),
|
|
271
270
|
lifecycle,
|
|
272
271
|
label: input.label,
|
|
273
|
-
profile:
|
|
272
|
+
profile: profileName,
|
|
274
273
|
agentProfile,
|
|
275
274
|
tags: input.tags ?? [],
|
|
276
275
|
capabilities: input.capabilities ?? [],
|
package/src/spawn-targets.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { countLiveSpawnedAgents, getAgent, getOrchestrator, listOrchestrators, ValidationError } from "./db";
|
|
2
|
-
import { listAgentProfiles } from "./config-store";
|
|
2
|
+
import { DEFAULT_SPAWN_PROFILE_NAME, listAgentProfiles } from "./config-store";
|
|
3
3
|
import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
4
4
|
import { isPathWithinBase } from "./utils";
|
|
5
5
|
import { McpNotFoundError } from "./mcp-errors";
|
|
@@ -39,7 +39,7 @@ export function buildSpawnTargets(input: { callerId?: string; quota: number }):
|
|
|
39
39
|
})),
|
|
40
40
|
profiles: spawnProfilesSummary(),
|
|
41
41
|
guidance:
|
|
42
|
-
|
|
42
|
+
`Default to your own host (isSelf:true); only set orchestratorId to spawn onto another host that runs a provider/model yours doesn't. Omitting profile applies ${DEFAULT_SPAWN_PROFILE_NAME}; omitting model/effort uses the provider default (marked below). Prefer a named profile over assembling raw knobs; set spawnRequestId for idempotency. After spawning you'll be pushed agent.ready / agent.exited / agent.spawn_failed — don't block-poll.`,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -109,7 +109,7 @@ export function spawnCapablePrimer(input: { canSpawn: boolean; quota?: number })
|
|
|
109
109
|
`You can spawn long-living child agents${quotaLabel}. Shut your own down with relay_shutdown_agent.`,
|
|
110
110
|
"- BEFORE spawning, call relay_spawn_targets: it returns the LIVE matrix of hosts × providers × models × per-model effort, your remaining quota, and named profiles. Don't guess — that tool is the discovery path.",
|
|
111
111
|
"- Default to your OWN host. Only set orchestratorId to spawn onto another host when it runs a provider/model yours doesn't, or the work must run there.",
|
|
112
|
-
|
|
112
|
+
`- Prefer a named profile (profile: "<name>") over hand-assembling provider+model+effort+approvalMode. Omitting profile applies ${DEFAULT_SPAWN_PROFILE_NAME}; omitting model/effort uses the provider default. Lean to the cheapest provider/model/effort that fits; escalate only for complex work.`,
|
|
113
113
|
"- Set spawnRequestId for idempotency so a retried spawn doesn't double-create.",
|
|
114
114
|
"- Spawn is fire-and-forget: pass waitForRegistrationMs:0 and keep working — you'll be PUSHED agent.ready when the child is genuinely ready (past onboarding), or agent.spawn_failed / agent.exited otherwise. Don't block-poll.",
|
|
115
115
|
"- Spawned children cannot themselves spawn (no grandchildren).",
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{An as t,Bn as n,Ct as r,F as i,Hn as a,Pt as o,Qn as s,Qt as c,R as l,Rn as u,St as d,Wt as f,at as p,b as m,bn as h,c as g,cn as _,ct as v,fn as ee,h as y,i as b,jn as x,ln as S,mn as C,nn as te,p as w,pn as T,r as E,tn as D,un as O,y as k,z as A}from"./lucide-react-BbMYCUlW.js";import{g as j,r as M,t as N}from"./store-BB-YW24l.js";import{D as P,I as F,M as ne,W as I,at as L,b as re,d as ie,f as ae,j as R,w as z,z as oe}from"./display-LAWaAboe.js";import{C as se,a as ce,i as le,o as ue,t as B}from"./index-VGRP16ua.js";var V=e(s(),1),H={online:`came online`,offline:`went offline`,shutdown:`shut down`,restarted:`restarted`,"compaction-requested":`compaction requested`,compacting:`compaction started`,compacted:`context compacted`,"compaction-stalled":`compaction stalled — may need manual intervention`,"clear-requested":`clear requested`,"clearing-context":`clear started`,"context-cleared":`context cleared`,"clear-stalled":`context clear stalled — may need manual intervention`,"shutting-down":`shutting down`},de=new Set(Object.keys(H)),fe=3e3;function U(e){return new Date(e.occurredAt??e.createdAt).getTime()}function pe(e){if(e.kind!==`session`)return null;let t=e.payload?.session;return!t||t.type!==`narration`&&t.type!==`reasoning`&&t.type!==`tool`?null:{id:e.id,kind:t.type,label:typeof t.label==`string`?t.label:void 0,text:e.body,ts:U(e),turnId:typeof t.turnId==`string`?t.turnId:void 0}}function me(e){if(e.kind!==`session`)return null;let t=e.payload?.session;if(t?.type!==`notice`)return null;let n=typeof t.label==`string`&&t.label?t.label:`compacted`;return{id:`notice-${e.id}`,status:n,timestamp:U(e)}}function W(e,t){let n=e.trim();for(let e of t){let t=e.trim();if(t){if(n===t)break;n.startsWith(`${t}\n\n`)&&(n=n.slice(t.length+2).trimStart())}}return n.trim()||e.trim()}function G(e){return e.kind===`system`&&e.from===`system`}var he=new Set([`agent.ready`,`agent.exited`,`agent.spawn_failed`]);function K(e){let t=new Date(e);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,`0`)}-${String(t.getDate()).padStart(2,`0`)}`}function ge(e){let t=e.filter(e=>de.has(e.status)).slice().sort((e,t)=>e.timestamp-t.timestamp),n=[];for(let e of t)n.some(t=>t.id===e.id)||n.some(t=>t.status===e.status&&Math.abs(t.timestamp-e.timestamp)<=fe)||n.push(e);return n}function _e(e,t,n,r=[],i){let a=[];for(let e of r){let t=e.entries.reduce((e,t)=>Math.min(e,I(t.originalCreatedAt)),1/0),n=Number.isFinite(t)?t-1:e.importedAt;a.push({ts:n,entry:{type:`import-boundary`,history:e,timestamp:n}});for(let t of e.entries)a.push({ts:I(t.originalCreatedAt),entry:{type:`imported-message`,history:e,entry:t}})}if(n){let e=new Date(n).getTime();Number.isFinite(e)&&a.push({ts:e,entry:{type:`created`,timestamp:e}})}if(i?.spawnedBy){let e=n?new Date(n).getTime():0,t=Number.isFinite(e)?e:0;a.push({ts:t,entry:{type:`agent-action`,direction:`spawned-by`,agentId:i.spawnedBy.agentId,provider:i.spawnedBy.provider,status:`ready`,timestamp:t}})}let o=i?.spawnedBy?.agentId,s=null,c=1/0;for(let t of e){if(t.kind===`session`||G(t)||z(t)||!(t.from===`user`||o!==void 0&&t.from===o)||!P(t).trim())continue;let e=U(t);e<c&&(c=e,s=t.id)}let l=new Map,u=new Map,d=new Map;for(let t of e){if(t.kind!==`session`)continue;let e=t.payload?.session;if(e?.type===`narration`&&typeof e.turnId==`string`){let n=u.get(e.turnId)??[];n.push({id:t.id,body:t.body.trim()}),u.set(e.turnId,n)}else if(e?.type===`response`&&typeof e.turnId==`string`){let n=l.get(e.turnId)??[];n.push(t.body.trim()),l.set(e.turnId,n)}}let f=new Map;for(let[e,t]of l){let n=(u.get(e)??[]).map(e=>e.body);f.set(e,t.map(e=>W(e,n)))}for(let t of e){if(t.kind!==`session`)continue;let e=t.payload?.session;if(e?.type!==`response`||typeof e.turnId!=`string`)continue;let n=W(t.body,(u.get(e.turnId)??[]).map(e=>e.body));n&&n!==t.body&&d.set(t.id,n)}let p=new Set;for(let[e,t]of f){let n=u.get(e)??[];for(let e of t)for(let t=n.length-1;t>=0;t--){let r=n[t];if(!p.has(r.id)&&oe(e,r.body)){p.add(r.id);break}}}let m=new Map,h=[];for(let t of e){if(G(t)){let e=typeof t.payload?.kind==`string`?t.payload.kind:``;if(he.has(e)){let n=t.payload,r=typeof n.agentId==`string`?n.agentId:``,i=U(t);if(!r){a.push({ts:i,entry:{type:`notice`,msg:t}});continue}let o=e===`agent.ready`?`ready`:e===`agent.exited`?`exited`:`failed`,s=m.get(r);if(s)s.entry.status=o,typeof n.reason==`string`&&(s.entry.reason=n.reason),s.entry.timestamp=i,s.ts=i;else{let e={ts:i,entry:{type:`agent-action`,direction:`spawned-child`,agentId:r,provider:typeof n.provider==`string`?n.provider:void 0,status:o,reason:typeof n.reason==`string`?n.reason:void 0,timestamp:i}};m.set(r,e),a.push(e)}continue}a.push({ts:U(t),entry:{type:`notice`,msg:t}});continue}let e=me(t);if(e){h.push(e);continue}let n=pe(t);if(n){if(n.kind===`narration`&&p.has(n.id))continue;a.push({ts:n.ts,entry:{type:`activity`,steps:[n],timestamp:n.ts}});continue}if(t.kind===`session`){let e=t.payload?.session;if(e?.type===`response`&&typeof e.turnId==`string`){let e=d.get(t.id);if(e&&e!==t.body){a.push({ts:U(t),entry:{type:`message`,msg:{...t,body:e}}});continue}}}let r=t.id===s?`initial-prompt`:`message`;a.push({ts:U(t),entry:{type:r,msg:t}})}for(let e of ge([...t,...h]))a.push({ts:e.timestamp,entry:{type:`status`,event:e}});let g={created:0,date:1,"import-boundary":2,"imported-message":3,"initial-prompt":4,message:4,activity:4,notice:4,"agent-action":4,status:5};a.sort((e,t)=>{let n=e.ts-t.ts;return Math.abs(n)<3e3&&g[e.entry.type]-g[t.entry.type]||n});let _=[],v=``;for(let{ts:e,entry:t}of a){if(t.type===`created`){_.push(t);continue}let n=K(e);if(n!==v&&(_.push({type:`date`,date:n,timestamp:e}),v=n),t.type===`activity`){let e=_[_.length-1];if(e&&e.type===`activity`&&be(e,t)){e.steps.push(...t.steps);continue}}_.push(t)}return _}function ve(e){return e.type===`message`?`msg-${e.msg.id}`:e.type===`initial-prompt`?`initprompt-${e.msg.id}`:e.type===`notice`?`notice-${e.msg.id}`:e.type===`agent-action`?`agent-action-${e.direction}-${e.agentId}`:e.type===`activity`?`act-${e.steps[0]?.id??e.timestamp}`:e.type===`import-boundary`?`import-${e.history.id}`:e.type===`imported-message`?`import-msg-${e.history.id}-${e.entry.originalMessageId}-${e.entry.position}`:e.type===`status`?`status-${e.event.id}`:e.type===`date`?`date-${e.date}`:`created-${e.timestamp}`}function ye(e){return e.type===`message`||e.type===`initial-prompt`||e.type===`notice`?(e.msg.subject?`${e.msg.subject} `:``)+P(e.msg):e.type===`imported-message`?P(e.entry.message):e.type===`activity`?e.steps.map(e=>[e.label,e.text].filter(Boolean).join(` `)).join(`
|
|
2
|
-
`):``}function be(e,t){let n=e.steps[e.steps.length-1]?.turnId,r=t.steps[0]?.turnId;return n&&r?n===r:!n&&!r}function xe(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/1024/1024).toFixed(1)} MB`}function Se(e,t){if(t.startsWith(`/`))return t;let n=`${e.replace(/\/+$/,``)}/${t}`.split(`/`),r=[];for(let e of n)!e||e===`.`||(e===`..`?r.pop():r.push(e));return`/`+r.join(`/`)}function Ce(e){let t=e.replace(/\/+$/,``),n=t.lastIndexOf(`/`);return n>0?t.slice(0,n):`/`}function we(e,t){return e.filter(e=>e.status===`online`&&F(t,e.baseDir)).sort((e,t)=>R(t.baseDir).length-R(e.baseDir).length)[0]||e.find(e=>e.status===`online`)}function Te(){return typeof window<`u`&&window.matchMedia(`(min-width: 1280px)`).matches}function q(){return typeof window<`u`&&!!window.getSelection()?.toString()}function Ee(e){let t=e.payload?.attachments;return Array.isArray(t)?t.filter(e=>!!(e&&typeof e==`object`&&typeof e.artifactId==`string`)):[]}var De=[`👍`,`❤️`,`✅`,`👀`];function Oe(e){let t=new Map;for(let n of e.reactions||[]){let e=ne(n.emoji),r=t.get(e)??{emoji:e,count:0,mine:!1,actors:[]};r.count+=1,r.mine=r.mine||n.actorId===`user`,r.actors.push(n.actorId),t.set(e,r)}return[...t.values()].sort((e,t)=>t.count-e.count||e.emoji.localeCompare(t.emoji))}function J(e){return z(e)}function ke(e,t){let n=e.readBy||[],r=e.resolvedToAgent||t||e.to,i=r?n.includes(r):!1,a=n.some(e=>e!==L);if(i||a){let e=i?r:n.find(e=>e!==L);return{label:`read`,title:e?`Read by ${e}`:`Read by agent`,tone:`ok`,icon:C}}if(e.deliveryStatus===`delivered`)return{label:`delivered`,title:`Delivered to agent inbox`,tone:`ok`,icon:C};if(e.deliveryStatus===`queued`)return{label:`queued`,title:`Queued until the target agent is available`,tone:`warn`,icon:c};if(e.deliveryStatus===`failed`||e.deliveryStatus===`dead`){let t=e.deliveryPoisonReason||e.deliveryLastError||e.deliveryStatus;return{label:e.deliveryStatus,title:t,tone:`danger`,icon:_}}return{label:`sent`,title:`Accepted by Relay`,tone:`muted`,icon:T}}function Ae(e){let t=I(e.createdAt);return(e.reactions||[]).reduce((e,t)=>Math.max(e,I(t.updatedAt||t.createdAt)),t)}function je(e){return e?e.messages.reduce((e,t)=>Math.max(e,Ae(t)),0):0}function Me(e,t,n){return(e-t)*n}function Ne(e){if(!e)return 0;let t=0;for(let n of e.messages)re(n)&&(t=Math.max(t,I(n.createdAt)));return t}function Y(e){return new Date(e).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`})}function Pe(e){for(let t of e)t.previewUrl&&URL.revokeObjectURL(t.previewUrl)}function Fe(e,t,n,r){if(e?.teamId)return{teamId:e.teamId};let i=[...t];n&&!i.some(e=>e.id===n.id)&&i.push(n);let a=i.filter(e=>e.threadId).reverse(),o=a.find(e=>!!r[M(e.threadId,e.channel)])??a[0];return o?.threadId?{threadId:o.threadId,...o.channel?{channel:o.channel}:{}}:null}var X=n(),Ie={online:`text-emerald-400`,offline:`text-zinc-400`,shutdown:`text-zinc-400`,restarted:`text-blue-400`,"compaction-requested":`text-zinc-400`,compacting:`text-amber-400`,compacted:`text-amber-400`,"compaction-stalled":`text-red-400`,"clear-requested":`text-zinc-400`,"clearing-context":`text-amber-400`,"context-cleared":`text-amber-400`,"clear-stalled":`text-red-400`,"shutting-down":`text-zinc-400`};function Le(e){let t=new Date,[n,r,i]=e.split(`-`).map(Number),a=new Date(n,r-1,i);if(e===K(t.getTime()))return`Today`;let o=new Date(t);return o.setDate(o.getDate()-1),e===K(o.getTime())?`Yesterday`:a.toLocaleDateString(void 0,{weekday:`short`,month:`short`,day:`numeric`,year:a.getFullYear()===t.getFullYear()?void 0:`numeric`})}function Re({event:e}){return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs text-muted-foreground`,children:[(0,X.jsx)(D,{className:u(`w-2 h-2 fill-current`,Ie[e.status]||`text-zinc-400`)}),(0,X.jsx)(`span`,{children:H[e.status]||e.status}),(0,X.jsx)(`span`,{className:`text-muted-foreground/60`,children:Y(e.timestamp)})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}var ze={"branch.landed":d,"context.warning":w,"agent.context_threshold":w,"agent.resume_budget_exhausted":w,"agent.rate_limit_resume":A},Be={"branch.landed":`branch landed`,"context.warning":`context warning`,"agent.context_threshold":`context threshold reached`};function Ve(e){return e&&(e.includes(`.`)?e.slice(e.indexOf(`.`)+1):e).replace(/[._-]+/g,` `).trim()||`system signal`}function Z({agentId:e,fallbackLabel:t,className:n}){let r=N(t=>t.agentsById[e])||null,i=N(e=>e.openInboxThread),a=N(e=>e.switchView),o=r?ie(r):t||e;return(0,X.jsxs)(`button`,{type:`button`,className:u(`inline-flex max-w-full items-center gap-1 rounded px-1 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground`,n),onClick:t=>{t.stopPropagation(),i(e),a(`chat`)},title:`Open chat for ${o}`,children:[(0,X.jsx)(se,{agent:r,className:`w-4 h-4 text-[10px]`}),(0,X.jsx)(`span`,{className:`max-w-[160px] truncate`,children:o})]})}function He({msg:e}){let t=e.payload??{},n=typeof t.sha==`string`?t.sha:``,r=n?n.slice(0,7):``,i=typeof t.author==`string`?t.author:``,a=typeof t.branch==`string`?t.branch:``,o=typeof t.base==`string`?t.base:``,s=t.pushed;return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:`flex max-w-full flex-wrap items-center justify-center gap-x-1.5 gap-y-0.5 rounded-full border border-emerald-500/25 bg-emerald-500/10 px-2.5 py-1 text-xs text-emerald-300/90`,children:[(0,X.jsx)(d,{className:`h-3 w-3 shrink-0 text-emerald-400`}),(0,X.jsx)(`span`,{className:`text-emerald-300`,children:`branch landed`}),a&&o&&(0,X.jsxs)(`span`,{className:`font-mono text-emerald-200/70`,title:`${a} → ${o}`,children:[a,(0,X.jsx)(`span`,{className:`opacity-50`,children:` → `}),o]}),r&&(0,X.jsx)(`span`,{className:`rounded bg-emerald-500/15 px-1 font-mono text-emerald-200`,title:n,children:r}),i&&(0,X.jsxs)(`span`,{className:`inline-flex items-center gap-1 text-emerald-300/80`,children:[(0,X.jsx)(`span`,{className:`opacity-70`,children:`by`}),(0,X.jsx)(Z,{agentId:i,className:`text-emerald-300/90 hover:text-emerald-100`})]}),s===!1&&(0,X.jsx)(`span`,{className:`text-amber-300/80`,title:`Local-only land — not pushed to origin`,children:`· local only`}),(0,X.jsx)(`span`,{className:`text-emerald-300/50`,children:Y(e.occurredAt??e.createdAt)})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function Ue({msg:e}){let t=typeof e.payload?.kind==`string`?e.payload.kind:``;if(t===`branch.landed`)return(0,X.jsx)(He,{msg:e});let n=ze[t]??i,r=Be[t]??Ve(t);return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:`flex items-center gap-1.5 rounded-full border border-border bg-muted/30 px-2.5 py-1 text-xs text-muted-foreground`,children:[(0,X.jsx)(n,{className:`h-3 w-3`}),(0,X.jsx)(`span`,{children:r}),(0,X.jsx)(`span`,{className:`text-muted-foreground/60`,children:Y(e.occurredAt??e.createdAt)})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}var We={"spawned-child":{ready:`spawned`,exited:`spawned · exited`,failed:`spawn failed`},"spawned-by":{ready:`spawned by`,exited:`spawned by`,failed:`spawned by`}},Ge={"spawned-child-ready":l,"spawned-child-exited":p,"spawned-child-failed":_,"spawned-by-ready":x,"spawned-by-exited":x,"spawned-by-failed":x},Ke={"spawned-child-ready":`text-emerald-400`,"spawned-child-exited":`text-zinc-400`,"spawned-child-failed":`text-red-400`,"spawned-by-ready":`text-blue-400`,"spawned-by-exited":`text-blue-400`,"spawned-by-failed":`text-blue-400`};function qe({entry:e}){let t=`${e.direction}-${e.status}`,n=We[e.direction]?.[e.status]??e.direction,r=Ge[t]??i,a=Ke[t]??`text-muted-foreground`,o=e.status===`failed`;return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:u(`flex items-center gap-1.5 rounded-full border bg-muted/30 px-2.5 py-1 text-xs`,o?`border-red-500/30`:`border-border`),children:[(0,X.jsx)(r,{className:u(`h-3 w-3 shrink-0`,a)}),(0,X.jsx)(`span`,{className:`text-muted-foreground`,children:n}),(0,X.jsx)(Z,{agentId:e.agentId}),e.reason&&o&&(0,X.jsxs)(`span`,{className:`text-red-400/80 max-w-[120px] truncate`,title:e.reason,children:[`— `,e.reason]}),(0,X.jsx)(`span`,{className:`text-muted-foreground/60 shrink-0`,children:Y(e.timestamp)})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function Je({blockedLabel:e,onInterrupt:t}){return e?(0,X.jsx)(`div`,{className:`flex justify-start mb-3 animate-in fade-in slide-in-from-bottom-1 duration-150`,children:(0,X.jsxs)(`div`,{className:`flex h-9 items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-red-500/30 px-4`,children:[(0,X.jsx)(_,{className:`w-3.5 h-3.5 text-red-400`}),(0,X.jsxs)(`span`,{className:`text-xs text-red-300`,children:[`blocked: `,e]})]})}):(0,X.jsx)(`div`,{className:`flex justify-start mb-3 animate-in fade-in slide-in-from-bottom-1 duration-150`,children:(0,X.jsxs)(`div`,{className:`flex h-9 items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-foreground/10 px-4`,children:[(0,X.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,X.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_infinite]`}),(0,X.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.2s_infinite]`}),(0,X.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.4s_infinite]`})]}),(0,X.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`working`}),t&&(0,X.jsxs)(`button`,{onClick:t,title:`Interrupt`,className:`ml-1 flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs text-muted-foreground hover:text-red-300 hover:bg-red-500/10 transition-colors`,children:[(0,X.jsx)(te,{className:`w-3.5 h-3.5`}),`Stop`]})]})})}var Q=`agent-relay:chat-hide-tools`,$=(()=>{let e=new Set,t=(()=>{try{return window.localStorage.getItem(Q)===`on`}catch{return!1}})(),n=()=>e.forEach(e=>e());return typeof window<`u`&&window.addEventListener(`storage`,e=>{e.key===Q&&(t=e.newValue===`on`,n())}),{subscribe(t){return e.add(t),()=>e.delete(t)},get:()=>t,toggle(){t=!t;try{window.localStorage.setItem(Q,t?`on`:`off`)}catch{}n()}}})();function Ye(){return[(0,V.useSyncExternalStore)($.subscribe,$.get,()=>!1),$.toggle]}function Xe({steps:e,showReasoning:t,issueRepo:n}){let[r,i]=Ye(),a=t?e:e.filter(e=>e.kind!==`reasoning`);if(!a.length)return null;let o=a.filter(e=>e.kind===`tool`).length;return(0,X.jsx)(`div`,{className:`flex justify-start mb-2`,children:(0,X.jsxs)(`div`,{className:`max-w-[85%] md:max-w-[75%] min-w-0 space-y-1.5`,children:[a.map(e=>e.kind===`narration`?(0,X.jsx)(B,{text:e.text,issueRepo:n,className:`text-sm leading-relaxed break-words text-foreground/90 min-w-0`},e.id):e.kind===`reasoning`?(0,X.jsxs)(`div`,{className:`flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground/80`,children:[(0,X.jsx)(h,{className:`w-3.5 h-3.5 mt-0.5 shrink-0 opacity-70`}),(0,X.jsx)(B,{text:e.text,issueRepo:n,className:`italic break-words min-w-0`})]},e.id):r?null:(0,X.jsxs)(`div`,{className:`flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground`,children:[(0,X.jsx)(y,{className:`w-3 h-3 mt-0.5 shrink-0 opacity-70`}),(0,X.jsxs)(`span`,{className:`min-w-0 break-words`,children:[(0,X.jsx)(`span`,{className:`font-medium`,children:e.label||`tool`}),e.text&&e.text.trim()&&e.text.trim()!==(e.label??``).trim()?(0,X.jsxs)(`span`,{className:`opacity-70`,children:[` — `,e.text]}):null]})]},e.id)),o>0&&(0,X.jsxs)(`button`,{onClick:i,className:`flex items-center gap-1 text-[11px] text-muted-foreground/50 hover:text-muted-foreground transition-colors text-left`,children:[(0,X.jsx)(O,{className:u(`w-3 h-3 shrink-0 transition-transform`,!r&&`rotate-90`)}),(0,X.jsx)(`span`,{children:r?`show ${o} tool step${o===1?``:`s`}`:`hide tool steps`})]})]})})}function Ze({date:e}){return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-3 py-3 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsx)(`span`,{className:`text-xs font-medium text-muted-foreground`,children:Le(e)}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function Qe({timestamp:e}){let t=new Date(e).toLocaleString(void 0,{month:`short`,day:`numeric`,year:`numeric`,hour:`2-digit`,minute:`2-digit`});return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`span`,{className:`text-xs text-muted-foreground/60`,children:[`created `,t]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function $e({history:e}){let t=e.sourceAgentLabel||e.sourcePeerId,n=e.entries.length;return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:`flex items-center gap-1.5 rounded-full border border-dashed border-border bg-muted/30 px-2 py-1 text-[11px] text-muted-foreground`,children:[(0,X.jsx)(r,{className:`h-3 w-3`}),(0,X.jsxs)(`span`,{children:[`forked history from `,t]}),(0,X.jsxs)(`span`,{className:`text-muted-foreground/60`,children:[n,` `,n===1?`entry`:`entries`]})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function et({entry:e,sourceLabel:t,issueRepo:n}){let r=e.message,i=P(r),a=e.originalFrom===L;return(0,X.jsx)(`div`,{className:u(`flex mb-3 opacity-80`,a?`justify-end`:`justify-start`),children:(0,X.jsx)(`div`,{className:`max-w-[85%] md:max-w-[75%]`,children:(0,X.jsxs)(`div`,{className:u(`rounded-2xl border border-dashed px-3.5 py-2 text-sm select-text`,a?`border-primary/25 bg-primary/10 text-foreground rounded-br-sm`:`border-border bg-card/70 rounded-bl-sm`),children:[(0,X.jsxs)(`div`,{className:u(`mb-1 text-[10px] uppercase tracking-normal`,a?`text-primary/70`:`text-muted-foreground`),children:[`forked history · `,a?`you`:t||e.originalFrom]}),(0,X.jsx)(B,{text:i,rawBody:r.body,issueRepo:n,className:`leading-relaxed`}),(0,X.jsxs)(`p`,{className:u(`mt-1 flex items-center justify-end gap-1.5 text-xs`,a?`text-primary/60`:`text-muted-foreground`),children:[(0,X.jsxs)(`span`,{className:`opacity-40`,children:[`#`,e.originalMessageId]}),(0,X.jsx)(`span`,{children:Y(e.originalCreatedAt)})]})]})})})}function tt({msg:e,issueRepo:t}){let n=P(e),r=Y(e.occurredAt??e.createdAt);return(0,X.jsx)(`div`,{"data-msg-id":e.id,className:`mb-3`,children:(0,X.jsxs)(`div`,{className:`rounded-xl border border-primary/30 bg-primary/[0.06] px-3.5 py-2.5 shadow-sm`,children:[(0,X.jsxs)(`div`,{className:`mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-primary/80`,children:[(0,X.jsx)(k,{className:`h-3.5 w-3.5`}),(0,X.jsx)(`span`,{children:`Initial prompt`}),(0,X.jsx)(`span`,{className:`ml-auto text-[10px] font-normal normal-case text-muted-foreground/60`,children:r})]}),(0,X.jsx)(B,{text:n,rawBody:e.body,issueRepo:t,className:`text-sm leading-relaxed text-foreground`})]})})}function nt({scrollEl:e,timeline:t}){let[n,r]=(0,V.useState)(!0),[i,a]=(0,V.useState)(null),[o,s]=(0,V.useState)(null),[c,l]=(0,V.useState)(!1),d=(0,V.useMemo)(()=>{for(let e=t.length-1;e>=0;e--){let n=t[e];if(!(!n||n.type!==`message`&&n.type!==`initial-prompt`)&&n.msg.from===`user`)return{id:n.msg.id,body:P(n.msg)}}return null},[t]);if((0,V.useEffect)(()=>{if(!d||!e){r(!0);return}let t=null,n=null,i=()=>{let i=e.querySelector(`[data-msg-id="${d.id}"]`);if(!i){t?.disconnect(),t=null,n=null,r(!1);return}let a=i.getBoundingClientRect(),o=e.getBoundingClientRect();l(a.top<o.top),i!==n&&(t?.disconnect(),n=i,t=new IntersectionObserver(([e])=>{e&&r(e.isIntersecting)},{root:e,threshold:0}),t.observe(i))};return i(),e.addEventListener(`scroll`,i,{passive:!0}),()=>{t?.disconnect(),e.removeEventListener(`scroll`,i)}},[d?.id,e]),n||!d||!d.body.trim())return null;let f=i?.id===d.id?i.value:!1,p=o?.id===d.id?o.value:!1;function m(){!e||!d||e.querySelector(`[data-msg-id="${d.id}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`})}let h=c?`↑`:`↓`;return p?(0,X.jsx)(`div`,{className:`flex shrink-0 bg-card/45 border-b border-border`,children:(0,X.jsxs)(`button`,{type:`button`,className:`w-full flex items-center justify-center gap-1.5 py-1 text-[11px] text-primary/60 hover:text-primary transition-colors`,onClick:()=>s({id:d.id,value:!1}),children:[(0,X.jsx)(`span`,{children:`Your prompt`}),(0,X.jsx)(ee,{className:`w-3 h-3`})]})}):(0,X.jsxs)(`section`,{className:`flex shrink-0 flex-col border-b border-border bg-card/45 px-3 py-2`,children:[(0,X.jsxs)(`div`,{className:`flex items-center gap-2 text-[11px] text-muted-foreground mb-0.5`,children:[(0,X.jsx)(`span`,{className:`font-medium text-primary/70`,children:`Your prompt`}),(0,X.jsxs)(`button`,{type:`button`,className:`ml-auto hover:text-foreground transition-colors`,onClick:m,children:[h,` scroll to original`]}),(0,X.jsx)(`button`,{type:`button`,className:`hover:text-foreground transition-colors`,onClick:()=>s({id:d.id,value:!0}),title:`Minimize`,children:(0,X.jsx)(S,{className:`w-3 h-3`})})]}),(0,X.jsx)(`p`,{className:u(`text-sm text-foreground leading-snug cursor-pointer`,!f&&`line-clamp-2`),onClick:()=>a({id:d.id,value:!f}),children:d.body})]})}function rt(){return(0,V.useSyncExternalStore)(e=>j.subscribe(e),()=>j.getPlayingKey(),()=>null)}function it({attachment:e,outbound:t}){let n=e.kind===`image`,r=e.title||e.artifactId,[i,s]=(0,V.useState)(null),[c,l]=(0,V.useState)(null),[d,p]=(0,V.useState)(!1),[m,h]=(0,V.useState)(!1);(0,V.useEffect)(()=>{let t=!1,n=null;async function r(){p(!0),l(null),s(null);try{let r=await a(`/artifacts/${encodeURIComponent(e.artifactId)}/content`);if(t)return;n=URL.createObjectURL(r),s(n)}catch(e){t||l(e.message)}finally{t||p(!1)}}return r(),()=>{t=!0,n&&URL.revokeObjectURL(n)}},[e.artifactId]);function g(){if(!i)return;let e=document.createElement(`a`);e.href=i,e.download=r,document.body.appendChild(e),e.click(),e.remove()}return n?(0,X.jsxs)(X.Fragment,{children:[(0,X.jsx)(`button`,{type:`button`,onClick:()=>i&&h(!0),disabled:!i,className:`group/img mt-2 block overflow-hidden rounded-md border border-foreground/10 bg-black/10 text-left disabled:cursor-default relative`,children:i?(0,X.jsxs)(X.Fragment,{children:[(0,X.jsx)(`img`,{src:i,alt:r,loading:`lazy`,className:`block max-h-72 max-w-full object-contain`}),(0,X.jsx)(`span`,{className:`absolute inset-0 flex items-center justify-center bg-black/0 group-hover/img:bg-black/30 transition-colors`,children:(0,X.jsx)(E,{className:`w-6 h-6 text-white opacity-0 group-hover/img:opacity-100 transition-opacity drop-shadow-md`})})]}):(0,X.jsxs)(`span`,{className:u(`flex min-h-20 items-center gap-2 px-3 py-2 text-xs`,c?`text-red-400`:`text-muted-foreground`),children:[d?(0,X.jsx)(v,{className:`h-4 w-4 animate-spin`}):(0,X.jsx)(_,{className:`h-4 w-4`}),c||`Loading image`]})}),i&&(0,X.jsx)(le,{open:m,onOpenChange:h,children:(0,X.jsxs)(ce,{className:`max-w-[95vw] max-h-[95vh] w-auto p-0 bg-black/95 border-zinc-800 gap-0 overflow-hidden`,showCloseButton:!1,children:[(0,X.jsx)(ue,{className:`sr-only`,children:r}),(0,X.jsxs)(`div`,{className:`relative flex items-center justify-center min-h-[200px]`,children:[(0,X.jsx)(`img`,{src:i,alt:r,className:`max-w-[93vw] max-h-[90vh] object-contain`}),(0,X.jsxs)(`div`,{className:`absolute top-2 right-2 flex gap-1`,children:[(0,X.jsx)(`button`,{type:`button`,onClick:g,className:`rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors`,title:`Download`,children:(0,X.jsx)(f,{className:`w-4 h-4`})}),(0,X.jsx)(`button`,{type:`button`,onClick:()=>h(!1),className:`rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors`,title:`Close`,children:(0,X.jsx)(b,{className:`w-4 h-4`})})]})]})]})})]}):(0,X.jsxs)(`button`,{type:`button`,onClick:g,disabled:!i,className:u(`mt-2 flex w-full items-center gap-2 rounded-md border px-2.5 py-2 text-left text-xs transition-colors disabled:cursor-default disabled:opacity-70`,t?`border-primary-foreground/20 bg-primary-foreground/10 hover:bg-primary-foreground/15`:`border-border bg-background hover:bg-muted`),children:[d?(0,X.jsx)(v,{className:`h-4 w-4 shrink-0 animate-spin`}):c?(0,X.jsx)(_,{className:`h-4 w-4 shrink-0 text-red-400`}):(0,X.jsx)(o,{className:`h-4 w-4 shrink-0`}),(0,X.jsx)(`span`,{className:u(`min-w-0 flex-1 truncate`,c&&`text-red-400`),children:c||r}),(0,X.jsx)(f,{className:`h-3.5 w-3.5 shrink-0`})]})}function at({open:e,onToggle:t,onReact:n}){return(0,X.jsxs)(`div`,{className:`relative shrink-0`,children:[(0,X.jsx)(`button`,{type:`button`,title:`Add reaction`,onClick:t,className:u(`inline-flex h-5 w-6 items-center justify-center rounded-full border border-border bg-popover text-muted-foreground shadow-sm transition hover:bg-muted hover:text-foreground`,e?`opacity-100`:`opacity-0 pointer-events-none md:group-hover/msg:opacity-100 md:group-hover/msg:pointer-events-auto`),children:(0,X.jsx)(m,{className:`h-3 w-3`})}),e&&(0,X.jsx)(`div`,{className:`absolute bottom-full left-0 z-30 mb-1 flex gap-0.5 rounded-full border border-border bg-popover p-0.5 shadow-md`,children:De.map(e=>(0,X.jsx)(`button`,{type:`button`,title:`React ${e}`,onClick:()=>n(e),className:`inline-flex h-6 w-7 items-center justify-center rounded-full text-xs transition-colors hover:bg-muted`,children:e},e))})]})}function ot({msg:e,peer:n}){let[r,i]=(0,V.useState)(!1),a=N(e=>e.agentsById),o=e.from===n,s=(o?e.to:e.from)||``,c=a[o?n:s]?.issueRepo??a[n]?.issueRepo,l=!s||s===`broadcast`||s.includes(`:`),d=P(e),f=Y(e.occurredAt??e.createdAt),p=o?t:x;return(0,X.jsx)(`div`,{"data-msg-id":e.id,className:`flex justify-center mb-1.5`,children:(0,X.jsxs)(`div`,{className:`w-full max-w-[94%] rounded-md border border-border/50 bg-muted/15 px-2.5 py-1`,children:[(0,X.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs cursor-pointer select-none`,onClick:()=>i(e=>!e),children:[(0,X.jsx)(O,{className:u(`h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform`,r&&`rotate-90`)}),(0,X.jsx)(p,{className:`h-3 w-3 shrink-0 text-muted-foreground/70`}),(0,X.jsxs)(`span`,{className:`shrink-0 text-muted-foreground`,children:[`Message `,o?`to`:`from`]}),l?(0,X.jsx)(`span`,{className:`shrink-0 font-medium text-muted-foreground`,children:ae(s,a)}):(0,X.jsx)(Z,{agentId:s,className:`shrink-0 px-0.5 text-foreground/80`}),!r&&(0,X.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-muted-foreground/50`,children:d}),(0,X.jsx)(`span`,{className:`ml-auto shrink-0 text-muted-foreground/40`,children:f})]}),r&&(0,X.jsx)(`div`,{className:`mt-1.5 pl-5`,children:(0,X.jsx)(B,{text:d,rawBody:e.body,issueRepo:c,className:`text-sm leading-relaxed text-foreground/90`})})]})})}var st=(0,V.memo)(function({msg:e,peer:t,onOpenReferencedPath:n,onPreviewReferencedPath:r,onPreviewReferencedPathEnd:i}){if(e.from!==`user`&&e.to!==`user`)return(0,X.jsx)(ot,{msg:e,peer:t});let a=e.from===L,o=N(e=>e.reactToMessage),s=N(e=>e.voiceTtsEnabled),c=rt(),l=N(e=>{let n=e.agentsById[t]?.meta?.cwd;return typeof n==`string`?n:``}),d=N(e=>e.agentsById[t]?.issueRepo),[f,p]=(0,V.useState)(!1),m=(0,V.useRef)(null),h=(0,V.useRef)(null),_=(0,V.useRef)(!1),v=P(e),ee=Y(e.occurredAt??e.createdAt),y=Ee(e),b=Oe(e),x=a?ke(e,t):null,S=x?.icon,C=c===String(e.id),w=s&&!a&&!J(e)&&v.trim().length>0;function T(t){t.stopPropagation(),C?j.bargeIn():j.speak(e.body,String(e.id))}(0,V.useEffect)(()=>{if(!f)return;function e(e){q()||m.current&&!m.current.contains(e.target)&&p(!1)}return document.addEventListener(`mousedown`,e),document.addEventListener(`touchstart`,e),()=>{document.removeEventListener(`mousedown`,e),document.removeEventListener(`touchstart`,e)}},[f]);function E(e){return Se(l||`/`,e)}function D(e,t){let r=E(e);n?.(r,t)}function O(e,t,n){let i=E(e);r?.(i,t,n)}function k(t){o(e,t),p(!1)}function A(e){e.button===0&&(h.current={x:e.clientX,y:e.clientY},_.current=!1)}function M(e){let t=h.current;t&&(Math.abs(e.clientX-t.x)>3||Math.abs(e.clientY-t.y)>3)&&(_.current=!0)}function F(t){if(!(J(e)||a)&&!t.target.closest(`a,button,input,textarea,select,[role="button"]`)){if(_.current||q()){_.current=!1;return}p(e=>!e)}}return(0,X.jsx)(`div`,{"data-msg-id":e.id,className:u(`group/msg flex mb-3`,a?`justify-end`:`justify-start`),children:(0,X.jsxs)(`div`,{ref:m,className:`relative max-w-[85%] md:max-w-[75%]`,children:[w&&(0,X.jsx)(`button`,{type:`button`,title:C?`Stop playback`:`Play aloud`,onClick:T,className:u(`absolute -top-2 -right-2 z-20 inline-flex h-6 w-6 items-center justify-center rounded-full border bg-popover shadow-sm transition`,C?`border-primary/50 text-primary opacity-100`:`border-border text-muted-foreground opacity-70 hover:bg-muted hover:text-foreground hover:opacity-100 [@media(hover:hover)]:md:opacity-0 [@media(hover:hover)]:md:group-hover/msg:opacity-100`),children:C?(0,X.jsx)(te,{className:`h-3.5 w-3.5 animate-pulse`}):(0,X.jsx)(g,{className:`h-3.5 w-3.5`})}),(0,X.jsxs)(`div`,{className:u(`rounded-2xl px-3.5 py-2 text-sm select-text`,!a&&!J(e)&&`cursor-pointer`,a?`bg-primary text-primary-foreground rounded-br-sm`:`bg-card ring-1 ring-foreground/10 rounded-bl-sm`),onPointerDown:A,onPointerMove:M,onClick:F,children:[e.subject&&(0,X.jsx)(`p`,{className:u(`text-xs font-semibold mb-1`,a?`text-primary-foreground/70`:`text-muted-foreground`),children:e.subject}),(0,X.jsx)(B,{text:v,rawBody:e.body,issueRepo:d,className:`leading-relaxed`,onOpenPath:D,onPreviewPath:O,onPreviewPathEnd:i,resolvePathTitle:(e,t)=>`${E(e)}${t?`:${t}`:``}`}),y.map(t=>(0,X.jsx)(it,{attachment:t,outbound:a},`${e.id}-${t.artifactId}-${t.role||``}`)),(0,X.jsxs)(`p`,{className:u(`text-xs mt-1 flex items-center gap-1.5`,a?`justify-end text-primary-foreground/60`:`justify-end text-muted-foreground`),children:[(0,X.jsxs)(`span`,{className:`opacity-40`,children:[`#`,e.id]}),(0,X.jsx)(`span`,{children:ee}),x&&S&&(0,X.jsxs)(`span`,{className:u(`inline-flex items-center gap-1`,x.tone===`ok`&&`text-emerald-300`,x.tone===`warn`&&`text-amber-200`,x.tone===`danger`&&`text-red-200`),title:x.title,children:[(0,X.jsx)(S,{className:`h-3 w-3`}),x.label]})]})]}),!J(e)&&b.length>0&&(0,X.jsxs)(`div`,{className:u(`flex flex-wrap items-center gap-0.5 -mt-1 relative z-10`,a?`justify-end pr-2`:`justify-start pl-2`),children:[!a&&(0,X.jsx)(at,{open:f,onToggle:()=>p(e=>!e),onReact:k}),b.map(e=>(0,X.jsxs)(`button`,{type:`button`,title:e.actors.join(`, `),onClick:()=>k(e.emoji),className:u(`inline-flex h-5 items-center gap-0.5 rounded-full border px-1.5 text-[11px] shadow-sm transition-colors`,e.mine?`border-primary/40 bg-primary/10 text-primary`:`border-border bg-background hover:bg-muted text-foreground`),children:[(0,X.jsx)(`span`,{children:e.emoji}),e.count>1&&(0,X.jsx)(`span`,{children:e.count})]},e.emoji))]}),!J(e)&&!a&&b.length===0&&(0,X.jsx)(`div`,{className:`absolute -bottom-2.5 left-1 z-20`,children:(0,X.jsx)(at,{open:f,onToggle:()=>p(e=>!e),onReact:k})})]})})});export{ve as C,_e as S,Ce as _,Qe as a,Te as b,et as c,Re as d,nt as f,we as g,Ne as h,Je as i,tt as l,xe as m,Xe as n,Ze as o,Me as p,qe as r,$e as s,st as t,Ue as u,Fe as v,ye as w,je as x,Pe as y};
|
|
3
|
-
//# sourceMappingURL=MessageBubble-DPcJxTJj.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import"./chunk-CilyBKbf.js";import{Bn as e,Qn as t,Rn as n}from"./lucide-react-BbMYCUlW.js";t();var r=e();function i({className:e,size:t=`default`,...i}){return(0,r.jsx)(`div`,{"data-slot":`card`,"data-size":t,className:n(`group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl`,e),...i})}function a({className:e,...t}){return(0,r.jsx)(`div`,{"data-slot":`card-header`,className:n(`group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3`,e),...t})}function o({className:e,...t}){return(0,r.jsx)(`div`,{"data-slot":`card-title`,className:n(`font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm`,e),...t})}function s({className:e,...t}){return(0,r.jsx)(`div`,{"data-slot":`card-description`,className:n(`text-sm text-muted-foreground`,e),...t})}function c({className:e,...t}){return(0,r.jsx)(`div`,{"data-slot":`card-action`,className:n(`col-start-2 row-span-2 row-start-1 self-start justify-self-end`,e),...t})}function l({className:e,...t}){return(0,r.jsx)(`div`,{"data-slot":`card-content`,className:n(`px-4 group-data-[size=sm]/card:px-3`,e),...t})}export{a,s as i,c as n,o,l as r,i as t};
|
|
2
|
-
//# sourceMappingURL=card-GZqj3rMm.js.map
|