@gobing-ai/spur 0.4.18 → 0.4.19
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/.claude-plugin/marketplace.json +1 -1
- package/config/workflows/task-pipeline.yaml +6 -1
- package/package.json +1 -1
- package/plugins/sp/README.md +10 -6
- package/plugins/sp/commands/dev-changelog.md +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/spur-cli/SKILL.md +2 -2
- package/plugins/sp/skills/spur-cli/references/message.md +7 -5
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +3 -2
- package/spur.js +1683 -1155
- package/web/_astro/{BoardApp.mWyrWCAR.js → BoardApp.CNF3sCnm.js} +1 -1
- package/web/_astro/BoardApp.DY8fwPfw.js +322 -0
- package/web/_astro/{TaskDetail.CtAXuiv8.js → TaskDetail.8z3DgYux.js} +1 -1
- package/web/_astro/{arc.Duf4u60i.js → arc.CFEYbpFT.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.JoymtqxN.js → architectureDiagram-3BPJPVTR.3eDvG_X8.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.BvmhcioC.js → blockDiagram-GPEHLZMM.CMbLzMpk.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.CfaWkgoQ.js → c4Diagram-AAUBKEIU.2lZW8nZ-.js} +1 -1
- package/web/_astro/channel.CdQ8ZO1m.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.B7uR02cD.js → chunk-2J33WTMH.BtM1eI3D.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.SwiEauYR.js → chunk-4BX2VUAB.CKeckzAf.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.6ZRCK8PO.js → chunk-55IACEB6.BBmHet8U.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.Cxra_G3N.js → chunk-727SXJPM.Bi8v7M_x.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.Cxd7gZeX.js → chunk-AQP2D5EJ.DOcBJyb6.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DCLhFkVc.js → chunk-FMBD7UC4.C-EdUBD7.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.B9AmKcv6.js → chunk-ND2GUHAM.IqTyWQhk.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.DOjCvl6S.js → chunk-QZHKN3VN.a2DShvIL.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.BMiT_mNf.js → classDiagram-4FO5ZUOK.CQBnvIjk.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BMiT_mNf.js → classDiagram-v2-Q7XG4LA2.CQBnvIjk.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.mLBCEGjI.js → cose-bilkent-S5V4N54A.BW9Ryz7c.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.CjDAY0qn.js → cynefin-OW5HDTMX.BPhLeSep.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG._G_sP8fP.js → dagre-BM42HDAG._oD9MsDB.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.39OcfSuz.js → diagram-2AECGRRQ.iciSOlxY.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.D7v_eNC4.js → diagram-5GNKFQAL.ZzqeC45Q.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.Da1x-lhg.js → diagram-KO2AKTUF.CMqWGOYB.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.C8EnzbEb.js → diagram-LMA3HP47.B5MPN99W.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.DPnBj69F.js → diagram-OG6HWLK6.Zgi-ANAF.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.CXMu2TIh.js → erDiagram-TEJ5UH35.Bd5aeIyR.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.ZklePVtQ.js → flowDiagram-I6XJVG4X.B8LBo_PH.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.BhYOkdqk.js → ganttDiagram-6RSMTGT7.Di2HLHk4.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CADyX8a1.js → gitGraphDiagram-PVQCEYII.BqDuU6fc.js} +1 -1
- package/web/_astro/index.DVDW9-eD.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.BsdmwjHt.js → infoDiagram-5YYISTIA.C_hw98uF.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.CzjZ3Crd.js → ishikawaDiagram-YF4QCWOH.uK7JK9Kk.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.Bxj-ZH_t.js → journeyDiagram-JHISSGLW.D03MNAmn.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.Cbg3wVvX.js → kanban-definition-UN3LZRKU.DuiDtKhq.js} +1 -1
- package/web/_astro/{linear.DJNUrBI9.js → linear.Der6_z5m.js} +1 -1
- package/web/_astro/{mermaid.core.DVHQV3SF.js → mermaid.core.CaxkEgwh.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.DyomosRU.js → mindmap-definition-RKZ34NQL.DecT082u.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.DajtFknN.js → pieDiagram-4H26LBE5.DuAEfZXh.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.Bjq6SjGe.js → quadrantDiagram-W4KKPZXB.CWXczU1k.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.ubBVqGLP.js → requirementDiagram-4Y6WPE33.BsAHUsR6.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.I4q46xd-.js → sankeyDiagram-5OEKKPKP.ghBqvXN8.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DsCgZIa8.js → sequenceDiagram-3UESZ5HK.BKbwb7ad.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.DcYjYSqT.js → stateDiagram-AJRCARHV.D5geOKAc.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.Dt8yPqiS.js → stateDiagram-v2-BHNVJYJU.Dc4Sjs4R.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.0YTkoa5F.js → timeline-definition-PNZ67QCA.ve2dW2tR.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.BNl0l1hs.js → vennDiagram-CIIHVFJN.BdJumy3f.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.BYSX6cfn.js → wardleyDiagram-YWT4CUSO.X8HgtN8V.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.DzxxpixK.js → xychartDiagram-2RQKCTM6.C30VVwQ6.js} +1 -1
- package/web/board-runtime.json +0 -6
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.CC7Mwswv.js +0 -324
- package/web/_astro/channel.DtEPXXcH.js +0 -1
- package/web/_astro/index.DiC1a6lh.css +0 -1
package/spur.js
CHANGED
|
@@ -34977,202 +34977,6 @@ var init_entity_dao = __esm(() => {
|
|
|
34977
34977
|
};
|
|
34978
34978
|
});
|
|
34979
34979
|
|
|
34980
|
-
// ../../node_modules/.bun/@gobing-ai+ts-db@0.5.32+3ceefb3da453b19a/node_modules/@gobing-ai/ts-db/dist/inbox-message-dao.js
|
|
34981
|
-
function isPromiseLike(value) {
|
|
34982
|
-
return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
34983
|
-
}
|
|
34984
|
-
function isRequestKeyUniqueViolation(error) {
|
|
34985
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
34986
|
-
return msg.includes("UNIQUE constraint failed") && (msg.includes("idx_inbox_messages_request_key") || msg.includes("inbox_messages.request_key"));
|
|
34987
|
-
}
|
|
34988
|
-
var RequestKeyConflictError, InboxMessageDao;
|
|
34989
|
-
var init_inbox_message_dao = __esm(() => {
|
|
34990
|
-
init_drizzle_orm();
|
|
34991
|
-
init_entity_dao();
|
|
34992
|
-
init_inbox_messages();
|
|
34993
|
-
RequestKeyConflictError = class RequestKeyConflictError extends Error {
|
|
34994
|
-
requestKey;
|
|
34995
|
-
existingId;
|
|
34996
|
-
constructor(requestKey, existingId) {
|
|
34997
|
-
super(`Request key conflict: key is already bound to another payload` + (existingId ? ` (existing message id: ${existingId})` : ""));
|
|
34998
|
-
this.name = "RequestKeyConflictError";
|
|
34999
|
-
this.requestKey = requestKey;
|
|
35000
|
-
this.existingId = existingId;
|
|
35001
|
-
}
|
|
35002
|
-
};
|
|
35003
|
-
InboxMessageDao = class InboxMessageDao extends EntityDao {
|
|
35004
|
-
events;
|
|
35005
|
-
constructor(adapter, options = {}) {
|
|
35006
|
-
super(adapter, inboxMessages, [inboxMessages.id], "inbox_messages");
|
|
35007
|
-
this.events = options.events;
|
|
35008
|
-
}
|
|
35009
|
-
async enqueue(fromId, toId, body, inReplyTo) {
|
|
35010
|
-
const id = crypto.randomUUID();
|
|
35011
|
-
const now = this.now();
|
|
35012
|
-
await this.create({
|
|
35013
|
-
id,
|
|
35014
|
-
fromId,
|
|
35015
|
-
toId,
|
|
35016
|
-
body,
|
|
35017
|
-
status: "queued",
|
|
35018
|
-
injectAttempts: 0,
|
|
35019
|
-
createdAt: now,
|
|
35020
|
-
updatedAt: now,
|
|
35021
|
-
...inReplyTo !== undefined ? { inReplyTo } : {}
|
|
35022
|
-
});
|
|
35023
|
-
this.emitEvent("message.enqueued", {
|
|
35024
|
-
id,
|
|
35025
|
-
fromId,
|
|
35026
|
-
toId,
|
|
35027
|
-
...inReplyTo !== undefined ? { inReplyTo } : {},
|
|
35028
|
-
timestamp: now
|
|
35029
|
-
});
|
|
35030
|
-
return id;
|
|
35031
|
-
}
|
|
35032
|
-
async enqueueIdempotent(fromId, toId, body, requestKey, inReplyTo) {
|
|
35033
|
-
const id = crypto.randomUUID();
|
|
35034
|
-
const now = this.now();
|
|
35035
|
-
try {
|
|
35036
|
-
await this.create({
|
|
35037
|
-
id,
|
|
35038
|
-
fromId,
|
|
35039
|
-
toId,
|
|
35040
|
-
body,
|
|
35041
|
-
status: "queued",
|
|
35042
|
-
injectAttempts: 0,
|
|
35043
|
-
requestKey,
|
|
35044
|
-
createdAt: now,
|
|
35045
|
-
updatedAt: now,
|
|
35046
|
-
...inReplyTo !== undefined ? { inReplyTo } : {}
|
|
35047
|
-
});
|
|
35048
|
-
} catch (error) {
|
|
35049
|
-
if (isRequestKeyUniqueViolation(error)) {
|
|
35050
|
-
const existing = await this.findBy(inboxMessages.requestKey, requestKey);
|
|
35051
|
-
if (existing && existing.body === body && existing.toId === toId) {
|
|
35052
|
-
return { id: existing.id, replayed: true };
|
|
35053
|
-
}
|
|
35054
|
-
throw new RequestKeyConflictError(requestKey, existing?.id ?? "");
|
|
35055
|
-
}
|
|
35056
|
-
throw error;
|
|
35057
|
-
}
|
|
35058
|
-
this.emitEvent("message.enqueued", {
|
|
35059
|
-
id,
|
|
35060
|
-
fromId,
|
|
35061
|
-
toId,
|
|
35062
|
-
...inReplyTo !== undefined ? { inReplyTo } : {},
|
|
35063
|
-
timestamp: now
|
|
35064
|
-
});
|
|
35065
|
-
return { id, replayed: false };
|
|
35066
|
-
}
|
|
35067
|
-
async drainPending(toId, options) {
|
|
35068
|
-
const limit = options?.limit ?? 100;
|
|
35069
|
-
if (!Number.isInteger(limit) || limit <= 0) {
|
|
35070
|
-
throw new RangeError(`drainPending limit must be a positive integer; received ${limit}`);
|
|
35071
|
-
}
|
|
35072
|
-
const now = this.now();
|
|
35073
|
-
const rows = await this.db.update(inboxMessages).set({
|
|
35074
|
-
status: "injected",
|
|
35075
|
-
injectAttempts: sql`${inboxMessages.injectAttempts} + 1`,
|
|
35076
|
-
updatedAt: now
|
|
35077
|
-
}).where(sql`${inboxMessages.id} IN (
|
|
35078
|
-
SELECT id
|
|
35079
|
-
FROM ${inboxMessages}
|
|
35080
|
-
WHERE to_id = ${toId}
|
|
35081
|
-
AND status = 'queued'
|
|
35082
|
-
ORDER BY created_at
|
|
35083
|
-
LIMIT ${limit}
|
|
35084
|
-
)
|
|
35085
|
-
AND ${inboxMessages.status} = 'queued'`).returning();
|
|
35086
|
-
for (const row of rows) {
|
|
35087
|
-
this.emitEvent("message.injected", {
|
|
35088
|
-
id: row.id,
|
|
35089
|
-
fromId: row.fromId,
|
|
35090
|
-
toId: row.toId,
|
|
35091
|
-
inReplyTo: row.inReplyTo,
|
|
35092
|
-
injectAttempts: row.injectAttempts,
|
|
35093
|
-
timestamp: now
|
|
35094
|
-
});
|
|
35095
|
-
}
|
|
35096
|
-
return rows;
|
|
35097
|
-
}
|
|
35098
|
-
async markDelivered(msgId) {
|
|
35099
|
-
const now = this.now();
|
|
35100
|
-
const updated = await this.update(msgId, {
|
|
35101
|
-
status: "delivered",
|
|
35102
|
-
deliveredAt: now
|
|
35103
|
-
});
|
|
35104
|
-
if (updated) {
|
|
35105
|
-
this.emitEvent("message.delivered", {
|
|
35106
|
-
id: updated.id,
|
|
35107
|
-
fromId: updated.fromId,
|
|
35108
|
-
toId: updated.toId,
|
|
35109
|
-
deliveredAt: now,
|
|
35110
|
-
timestamp: now
|
|
35111
|
-
});
|
|
35112
|
-
}
|
|
35113
|
-
}
|
|
35114
|
-
async markFailed(msgId, error) {
|
|
35115
|
-
const updated = await this.update(msgId, {
|
|
35116
|
-
status: "failed",
|
|
35117
|
-
injectError: error
|
|
35118
|
-
});
|
|
35119
|
-
if (updated) {
|
|
35120
|
-
this.emitEvent("message.failed", {
|
|
35121
|
-
id: updated.id,
|
|
35122
|
-
fromId: updated.fromId,
|
|
35123
|
-
toId: updated.toId,
|
|
35124
|
-
error,
|
|
35125
|
-
timestamp: updated.updatedAt
|
|
35126
|
-
});
|
|
35127
|
-
}
|
|
35128
|
-
}
|
|
35129
|
-
async release(msgIds) {
|
|
35130
|
-
if (msgIds.length === 0)
|
|
35131
|
-
return 0;
|
|
35132
|
-
const now = this.now();
|
|
35133
|
-
const rows = await this.db.update(inboxMessages).set({ status: "queued", updatedAt: now }).where(and(inArray(inboxMessages.id, msgIds), eq(inboxMessages.status, "injected"))).returning();
|
|
35134
|
-
for (const row of rows) {
|
|
35135
|
-
this.emitEvent("message.requeued", {
|
|
35136
|
-
id: row.id,
|
|
35137
|
-
fromId: row.fromId,
|
|
35138
|
-
toId: row.toId,
|
|
35139
|
-
timestamp: now
|
|
35140
|
-
});
|
|
35141
|
-
}
|
|
35142
|
-
return rows.length;
|
|
35143
|
-
}
|
|
35144
|
-
async inbox(toId, limit, offset) {
|
|
35145
|
-
return this.list({
|
|
35146
|
-
where: { col: inboxMessages.toId, op: "eq", value: toId },
|
|
35147
|
-
orderBy: [{ col: inboxMessages.createdAt, dir: "desc" }],
|
|
35148
|
-
...limit !== undefined ? { limit } : {},
|
|
35149
|
-
...offset !== undefined ? { offset } : {}
|
|
35150
|
-
});
|
|
35151
|
-
}
|
|
35152
|
-
async countPending(toId) {
|
|
35153
|
-
return this.count({
|
|
35154
|
-
and: [
|
|
35155
|
-
{ col: inboxMessages.toId, op: "eq", value: toId },
|
|
35156
|
-
{ col: inboxMessages.status, op: "eq", value: "queued" }
|
|
35157
|
-
]
|
|
35158
|
-
});
|
|
35159
|
-
}
|
|
35160
|
-
async getById(id) {
|
|
35161
|
-
return this.findBy(inboxMessages.id, id);
|
|
35162
|
-
}
|
|
35163
|
-
emitEvent(event, ...args) {
|
|
35164
|
-
if (!this.events)
|
|
35165
|
-
return;
|
|
35166
|
-
try {
|
|
35167
|
-
const pending = this.events.emit(event, ...args);
|
|
35168
|
-
if (isPromiseLike(pending)) {
|
|
35169
|
-
Promise.resolve(pending).catch(() => {});
|
|
35170
|
-
}
|
|
35171
|
-
} catch {}
|
|
35172
|
-
}
|
|
35173
|
-
};
|
|
35174
|
-
});
|
|
35175
|
-
|
|
35176
34980
|
// ../../node_modules/.bun/@gobing-ai+ts-db@0.5.32+3ceefb3da453b19a/node_modules/@gobing-ai/ts-db/dist/queue-job-dao.js
|
|
35177
34981
|
function assertValidTimeoutMs(value) {
|
|
35178
34982
|
if (value === undefined || value === null)
|
|
@@ -35398,8 +35202,8 @@ var init_queue_job_dao = __esm(() => {
|
|
|
35398
35202
|
|
|
35399
35203
|
// ../../node_modules/.bun/@gobing-ai+ts-db@0.5.32+3ceefb3da453b19a/node_modules/@gobing-ai/ts-db/dist/index.js
|
|
35400
35204
|
var init_dist4 = __esm(() => {
|
|
35205
|
+
init_base_dao();
|
|
35401
35206
|
init_entity_dao();
|
|
35402
|
-
init_inbox_message_dao();
|
|
35403
35207
|
init_queue_job_dao();
|
|
35404
35208
|
});
|
|
35405
35209
|
|
|
@@ -48413,16 +48217,34 @@ class RunSessionDao {
|
|
|
48413
48217
|
var init_retro_correlation = () => {};
|
|
48414
48218
|
|
|
48415
48219
|
// ../../packages/domain/src/dao/system-event-dao.ts
|
|
48220
|
+
function systemEventInsertSql(input) {
|
|
48221
|
+
return {
|
|
48222
|
+
sql: `INSERT INTO system_events (${SYSTEM_EVENT_COLUMNS})
|
|
48223
|
+
SELECT ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8,
|
|
48224
|
+
CASE WHEN ?9 IS NULL THEN COALESCE(MAX(sequence), 0) + 1 ELSE ?9 END
|
|
48225
|
+
FROM system_events`,
|
|
48226
|
+
params: [
|
|
48227
|
+
input.id,
|
|
48228
|
+
input.event_name,
|
|
48229
|
+
input.occurred_at,
|
|
48230
|
+
input.actor ?? null,
|
|
48231
|
+
input.payload_json ?? null,
|
|
48232
|
+
input.run_id ?? null,
|
|
48233
|
+
input.entity_kind ?? null,
|
|
48234
|
+
input.entity_id ?? null,
|
|
48235
|
+
input.sequence ?? null
|
|
48236
|
+
]
|
|
48237
|
+
};
|
|
48238
|
+
}
|
|
48239
|
+
|
|
48416
48240
|
class SystemEventDao {
|
|
48417
48241
|
db;
|
|
48418
48242
|
constructor(db) {
|
|
48419
48243
|
this.db = db;
|
|
48420
48244
|
}
|
|
48421
48245
|
async insert(input) {
|
|
48422
|
-
|
|
48423
|
-
|
|
48424
|
-
CASE WHEN ?9 IS NULL THEN COALESCE(MAX(sequence), 0) + 1 ELSE ?9 END
|
|
48425
|
-
FROM system_events`, input.id, input.event_name, input.occurred_at, input.actor ?? null, input.payload_json ?? null, input.run_id ?? null, input.entity_kind ?? null, input.entity_id ?? null, input.sequence ?? null);
|
|
48246
|
+
const statement = systemEventInsertSql(input);
|
|
48247
|
+
await this.db.run(statement.sql, ...statement.params);
|
|
48426
48248
|
}
|
|
48427
48249
|
async pruneQuotas(quotas, prefix) {
|
|
48428
48250
|
try {
|
|
@@ -50199,6 +50021,228 @@ class ActionRunDao {
|
|
|
50199
50021
|
}
|
|
50200
50022
|
}
|
|
50201
50023
|
}
|
|
50024
|
+
|
|
50025
|
+
// ../../packages/domain/src/dao/inbox-dao.ts
|
|
50026
|
+
function toMessage(row) {
|
|
50027
|
+
return {
|
|
50028
|
+
id: row.id,
|
|
50029
|
+
fromId: row.from_id,
|
|
50030
|
+
toId: row.to_id,
|
|
50031
|
+
body: row.body,
|
|
50032
|
+
kind: row.kind === "request" ? "request" : "chat",
|
|
50033
|
+
status: row.status,
|
|
50034
|
+
claimedAt: row.claimed_at,
|
|
50035
|
+
attempts: row.attempts,
|
|
50036
|
+
error: row.error,
|
|
50037
|
+
deliveredAt: row.delivered_at,
|
|
50038
|
+
readAt: row.read_at,
|
|
50039
|
+
inReplyTo: row.in_reply_to,
|
|
50040
|
+
threadId: row.thread_id,
|
|
50041
|
+
requestKey: row.request_key,
|
|
50042
|
+
taskId: row.task_id,
|
|
50043
|
+
parentRunId: row.parent_run_id,
|
|
50044
|
+
createdAt: row.created_at,
|
|
50045
|
+
updatedAt: row.updated_at
|
|
50046
|
+
};
|
|
50047
|
+
}
|
|
50048
|
+
function placeholders(values, start) {
|
|
50049
|
+
return values.map((_, index) => `?${start + index}`).join(", ");
|
|
50050
|
+
}
|
|
50051
|
+
var COLUMNS = `id, from_id, to_id, body, kind, status, claimed_at, attempts, error, delivered_at,
|
|
50052
|
+
read_at, in_reply_to, thread_id, request_key, task_id, parent_run_id, created_at, updated_at`, transactionLocks, InboxDao;
|
|
50053
|
+
var init_inbox_dao = __esm(() => {
|
|
50054
|
+
init_dist4();
|
|
50055
|
+
init_system_event_dao();
|
|
50056
|
+
transactionLocks = new WeakMap;
|
|
50057
|
+
InboxDao = class InboxDao extends BaseDao {
|
|
50058
|
+
constructor(adapter) {
|
|
50059
|
+
super(adapter);
|
|
50060
|
+
}
|
|
50061
|
+
async withTransaction(fn) {
|
|
50062
|
+
const previous = transactionLocks.get(this.adapter) ?? Promise.resolve();
|
|
50063
|
+
const run = previous.then(async () => {
|
|
50064
|
+
await this.adapter.exec("BEGIN IMMEDIATE");
|
|
50065
|
+
try {
|
|
50066
|
+
const result = await fn();
|
|
50067
|
+
await this.adapter.exec("COMMIT");
|
|
50068
|
+
return result;
|
|
50069
|
+
} catch (error) {
|
|
50070
|
+
await this.adapter.exec("ROLLBACK");
|
|
50071
|
+
throw error;
|
|
50072
|
+
}
|
|
50073
|
+
});
|
|
50074
|
+
transactionLocks.set(this.adapter, run.then(() => {
|
|
50075
|
+
return;
|
|
50076
|
+
}, () => {
|
|
50077
|
+
return;
|
|
50078
|
+
}));
|
|
50079
|
+
return run;
|
|
50080
|
+
}
|
|
50081
|
+
async writeEvents(toEvent, changed) {
|
|
50082
|
+
for (const row of changed) {
|
|
50083
|
+
const statement = systemEventInsertSql(toEvent(row));
|
|
50084
|
+
await this.adapter.run(statement.sql, ...statement.params);
|
|
50085
|
+
}
|
|
50086
|
+
}
|
|
50087
|
+
async insert(input, toEvent) {
|
|
50088
|
+
return this.withTransaction(async () => {
|
|
50089
|
+
const [row] = await this.adapter.queryAll(`INSERT INTO inbox_messages (${COLUMNS})
|
|
50090
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, NULL, 0, NULL, NULL, NULL, ?7, ?8, ?9, ?10, ?11, ?12, ?13)
|
|
50091
|
+
RETURNING ${COLUMNS}`, input.id, input.fromId, input.toId, input.body, input.kind, input.status, input.inReplyTo, input.threadId, input.requestKey, input.taskId, input.parentRunId, input.createdAt, input.updatedAt);
|
|
50092
|
+
if (row === undefined)
|
|
50093
|
+
throw new Error(`inbox insert returned no row for ${input.id}`);
|
|
50094
|
+
const message = toMessage(row);
|
|
50095
|
+
await this.writeEvents(toEvent, [message]);
|
|
50096
|
+
return message;
|
|
50097
|
+
});
|
|
50098
|
+
}
|
|
50099
|
+
async findByRequestKey(requestKey) {
|
|
50100
|
+
const [row] = await this.adapter.queryAll(`SELECT ${COLUMNS} FROM inbox_messages WHERE request_key = ?1`, requestKey);
|
|
50101
|
+
return row === undefined ? null : toMessage(row);
|
|
50102
|
+
}
|
|
50103
|
+
async get(id) {
|
|
50104
|
+
const [row] = await this.adapter.queryAll(`SELECT ${COLUMNS} FROM inbox_messages WHERE id = ?1`, id);
|
|
50105
|
+
return row === undefined ? null : toMessage(row);
|
|
50106
|
+
}
|
|
50107
|
+
async claim(member, limit, now, toEvent) {
|
|
50108
|
+
return this.withTransaction(async () => {
|
|
50109
|
+
const rows = await this.adapter.queryAll(`UPDATE inbox_messages
|
|
50110
|
+
SET status = 'claimed', claimed_at = ?2, attempts = attempts + 1, updated_at = ?2
|
|
50111
|
+
WHERE id IN (
|
|
50112
|
+
SELECT id FROM inbox_messages
|
|
50113
|
+
WHERE to_id = ?1 AND status = 'queued'
|
|
50114
|
+
ORDER BY created_at ASC, rowid ASC
|
|
50115
|
+
LIMIT ?3
|
|
50116
|
+
)
|
|
50117
|
+
RETURNING ${COLUMNS}`, member, now, Math.max(0, limit));
|
|
50118
|
+
const changed = rows.map(toMessage);
|
|
50119
|
+
await this.writeEvents(toEvent, changed);
|
|
50120
|
+
return changed;
|
|
50121
|
+
});
|
|
50122
|
+
}
|
|
50123
|
+
async settle(ids, to, now, toEvent, error = null) {
|
|
50124
|
+
if (ids.length === 0)
|
|
50125
|
+
return [];
|
|
50126
|
+
return this.withTransaction(async () => {
|
|
50127
|
+
const rows = await this.adapter.queryAll(`UPDATE inbox_messages
|
|
50128
|
+
SET status = ?1, error = ?2,
|
|
50129
|
+
delivered_at = CASE WHEN ?1 = 'delivered' THEN ?3 ELSE NULL END,
|
|
50130
|
+
updated_at = ?3
|
|
50131
|
+
WHERE id IN (${placeholders(ids, 4)}) AND status = 'claimed'
|
|
50132
|
+
RETURNING ${COLUMNS}`, to, error, now, ...ids);
|
|
50133
|
+
const changed = rows.map(toMessage);
|
|
50134
|
+
await this.writeEvents(toEvent, changed);
|
|
50135
|
+
return changed;
|
|
50136
|
+
});
|
|
50137
|
+
}
|
|
50138
|
+
async release(ids, now, toEvent) {
|
|
50139
|
+
if (ids.length === 0)
|
|
50140
|
+
return [];
|
|
50141
|
+
return this.withTransaction(async () => {
|
|
50142
|
+
const rows = await this.adapter.queryAll(`UPDATE inbox_messages
|
|
50143
|
+
SET status = 'queued', claimed_at = NULL, updated_at = ?1
|
|
50144
|
+
WHERE id IN (${placeholders(ids, 2)}) AND status = 'claimed'
|
|
50145
|
+
RETURNING ${COLUMNS}`, now, ...ids);
|
|
50146
|
+
const changed = rows.map(toMessage);
|
|
50147
|
+
await this.writeEvents(toEvent, changed);
|
|
50148
|
+
return changed;
|
|
50149
|
+
});
|
|
50150
|
+
}
|
|
50151
|
+
async markRead(id, reader, now, toEvent) {
|
|
50152
|
+
return this.withTransaction(async () => {
|
|
50153
|
+
const [stamped] = await this.adapter.queryAll(`UPDATE inbox_messages SET read_at = ?3, updated_at = ?3
|
|
50154
|
+
WHERE id = ?1 AND to_id = ?2 AND read_at IS NULL
|
|
50155
|
+
RETURNING ${COLUMNS}`, id, reader, now);
|
|
50156
|
+
if (stamped !== undefined) {
|
|
50157
|
+
const message = toMessage(stamped);
|
|
50158
|
+
await this.writeEvents(toEvent, [message]);
|
|
50159
|
+
return { message, transitioned: true };
|
|
50160
|
+
}
|
|
50161
|
+
const [existing] = await this.adapter.queryAll(`SELECT ${COLUMNS} FROM inbox_messages WHERE id = ?1 AND to_id = ?2`, id, reader);
|
|
50162
|
+
return existing === undefined ? null : { message: toMessage(existing), transitioned: false };
|
|
50163
|
+
});
|
|
50164
|
+
}
|
|
50165
|
+
async listExpiredClaims(cutoff) {
|
|
50166
|
+
const rows = await this.adapter.queryAll(`SELECT ${COLUMNS} FROM inbox_messages
|
|
50167
|
+
WHERE status = 'claimed' AND claimed_at IS NOT NULL AND claimed_at < ?1
|
|
50168
|
+
ORDER BY claimed_at ASC, rowid ASC`, cutoff);
|
|
50169
|
+
return rows.map(toMessage);
|
|
50170
|
+
}
|
|
50171
|
+
async list(member, direction, limit, before) {
|
|
50172
|
+
const scope = direction === "in" ? "to_id = ?1" : direction === "out" ? "from_id = ?1" : "(to_id = ?1 OR from_id = ?1)";
|
|
50173
|
+
const cursor = before === undefined ? "" : "AND (created_at < ?3 OR (created_at = ?3 AND id < ?4))";
|
|
50174
|
+
const params = [member, Math.max(0, limit) + 1];
|
|
50175
|
+
if (before !== undefined)
|
|
50176
|
+
params.push(before.createdAt, before.id);
|
|
50177
|
+
const rows = await this.adapter.queryAll(`SELECT ${COLUMNS} FROM inbox_messages
|
|
50178
|
+
WHERE ${scope} ${cursor}
|
|
50179
|
+
ORDER BY created_at DESC, id DESC
|
|
50180
|
+
LIMIT ?2`, ...params);
|
|
50181
|
+
return rows.map(toMessage);
|
|
50182
|
+
}
|
|
50183
|
+
async thread(threadId) {
|
|
50184
|
+
const rows = await this.adapter.queryAll(`SELECT ${COLUMNS} FROM inbox_messages
|
|
50185
|
+
WHERE thread_id = ?1 OR id = ?1
|
|
50186
|
+
ORDER BY created_at ASC, rowid ASC`, threadId);
|
|
50187
|
+
return rows.map(toMessage);
|
|
50188
|
+
}
|
|
50189
|
+
async requests(toIds) {
|
|
50190
|
+
if (toIds !== undefined && toIds.length === 0)
|
|
50191
|
+
return [];
|
|
50192
|
+
const scope = toIds === undefined ? `m.kind = 'request'` : `m.kind = 'request' AND m.to_id IN (${placeholders(toIds, 1)})`;
|
|
50193
|
+
const rows = await this.adapter.queryAll(`SELECT ${COLUMNS.split(",").map((column) => `m.${column.trim()}`).join(", ")},
|
|
50194
|
+
r.run_id AS run_id, r.status AS run_status, r.outcome AS run_outcome,
|
|
50195
|
+
r.task_id AS run_task_id, r.artifact_refs_json AS run_artifact_refs_json
|
|
50196
|
+
FROM inbox_messages m
|
|
50197
|
+
LEFT JOIN coordination_runs r ON r.run_id = (
|
|
50198
|
+
SELECT c.run_id FROM coordination_runs c, json_each(c.message_ids_json) j
|
|
50199
|
+
WHERE j.value = m.id
|
|
50200
|
+
ORDER BY c.started_at DESC
|
|
50201
|
+
LIMIT 1
|
|
50202
|
+
)
|
|
50203
|
+
WHERE ${scope}
|
|
50204
|
+
ORDER BY m.created_at DESC, m.rowid DESC`, ...toIds ?? []);
|
|
50205
|
+
return rows.map((row) => ({
|
|
50206
|
+
...toMessage(row),
|
|
50207
|
+
runId: row.run_id ?? null,
|
|
50208
|
+
runStatus: row.run_status ?? null,
|
|
50209
|
+
runOutcome: row.run_outcome ?? null,
|
|
50210
|
+
runTaskId: row.run_task_id ?? null,
|
|
50211
|
+
runArtifactRefsJson: row.run_artifact_refs_json ?? null
|
|
50212
|
+
}));
|
|
50213
|
+
}
|
|
50214
|
+
async countQueued(member) {
|
|
50215
|
+
const [row] = await this.adapter.queryAll(`SELECT COUNT(*) AS n FROM inbox_messages WHERE to_id = ?1 AND status = 'queued'`, member);
|
|
50216
|
+
return row?.n ?? 0;
|
|
50217
|
+
}
|
|
50218
|
+
async distinctRecipients() {
|
|
50219
|
+
return this.adapter.queryAll(`SELECT to_id AS id,
|
|
50220
|
+
SUM(CASE WHEN status = 'queued' THEN 1 ELSE 0 END) AS queued,
|
|
50221
|
+
SUM(CASE WHEN read_at IS NULL THEN 1 ELSE 0 END) AS unread
|
|
50222
|
+
FROM inbox_messages
|
|
50223
|
+
GROUP BY to_id
|
|
50224
|
+
ORDER BY to_id ASC`);
|
|
50225
|
+
}
|
|
50226
|
+
async attemptsForTask(taskId) {
|
|
50227
|
+
const rows = await this.adapter.queryAll(`SELECT ${COLUMNS} FROM inbox_messages
|
|
50228
|
+
WHERE task_id = ?1
|
|
50229
|
+
ORDER BY created_at DESC, rowid DESC`, taskId);
|
|
50230
|
+
return { count: rows.length, latest: rows[0] === undefined ? null : toMessage(rows[0]) };
|
|
50231
|
+
}
|
|
50232
|
+
async listByTask(taskId) {
|
|
50233
|
+
const rows = await this.adapter.queryAll(`SELECT ${COLUMNS} FROM inbox_messages
|
|
50234
|
+
WHERE task_id = ?1
|
|
50235
|
+
ORDER BY created_at DESC, rowid DESC`, taskId);
|
|
50236
|
+
return rows.map(toMessage);
|
|
50237
|
+
}
|
|
50238
|
+
};
|
|
50239
|
+
});
|
|
50240
|
+
|
|
50241
|
+
// ../../packages/domain/src/dao/addressed-spec-ids.ts
|
|
50242
|
+
var init_addressed_spec_ids = __esm(() => {
|
|
50243
|
+
init_inbox_dao();
|
|
50244
|
+
});
|
|
50245
|
+
|
|
50202
50246
|
// ../../packages/domain/src/dao/agent-executor-update-dao.ts
|
|
50203
50247
|
class AgentExecutorUpdateDao {
|
|
50204
50248
|
db;
|
|
@@ -51300,113 +51344,6 @@ var init_decision_log_dao = __esm(() => {
|
|
|
51300
51344
|
];
|
|
51301
51345
|
});
|
|
51302
51346
|
|
|
51303
|
-
// ../../packages/domain/src/dao/inbox-recent-dao.ts
|
|
51304
|
-
class InboxRecentDao {
|
|
51305
|
-
db;
|
|
51306
|
-
constructor(db) {
|
|
51307
|
-
this.db = db;
|
|
51308
|
-
}
|
|
51309
|
-
async listRecent(limit = 50) {
|
|
51310
|
-
const capped = Math.max(1, Math.min(limit, 500));
|
|
51311
|
-
try {
|
|
51312
|
-
return await this.db.queryAll(`SELECT id, from_id, to_id, body, status, created_at, in_reply_to
|
|
51313
|
-
FROM inbox_messages
|
|
51314
|
-
ORDER BY created_at DESC
|
|
51315
|
-
LIMIT ?1`, capped);
|
|
51316
|
-
} catch (error) {
|
|
51317
|
-
if (error instanceof Error && error.message.includes("no such table: inbox_messages")) {
|
|
51318
|
-
return [];
|
|
51319
|
-
}
|
|
51320
|
-
throw error;
|
|
51321
|
-
}
|
|
51322
|
-
}
|
|
51323
|
-
async countReplies(ids) {
|
|
51324
|
-
const result = new Map;
|
|
51325
|
-
if (ids.length === 0)
|
|
51326
|
-
return result;
|
|
51327
|
-
const placeholders = ids.map((_, i) => `?${i + 1}`).join(", ");
|
|
51328
|
-
try {
|
|
51329
|
-
const rows = await this.db.queryAll(`SELECT in_reply_to, COUNT(*) AS n
|
|
51330
|
-
FROM inbox_messages
|
|
51331
|
-
WHERE in_reply_to IS NOT NULL AND in_reply_to IN (${placeholders})
|
|
51332
|
-
GROUP BY in_reply_to`, ...ids);
|
|
51333
|
-
for (const row of rows) {
|
|
51334
|
-
result.set(row.in_reply_to, row.n);
|
|
51335
|
-
}
|
|
51336
|
-
} catch (error) {
|
|
51337
|
-
if (error instanceof Error && error.message.includes("no such table: inbox_messages")) {
|
|
51338
|
-
return result;
|
|
51339
|
-
}
|
|
51340
|
-
throw error;
|
|
51341
|
-
}
|
|
51342
|
-
return result;
|
|
51343
|
-
}
|
|
51344
|
-
async markRead(id, readerId, nowMs = Date.now()) {
|
|
51345
|
-
try {
|
|
51346
|
-
const [stamped] = await this.db.queryAll(`UPDATE inbox_messages SET read_at = ?3
|
|
51347
|
-
WHERE id = ?1 AND to_id = ?2 AND read_at IS NULL
|
|
51348
|
-
RETURNING read_at, from_id, in_reply_to`, id, readerId, nowMs);
|
|
51349
|
-
if (stamped)
|
|
51350
|
-
return toReceipt(stamped, true);
|
|
51351
|
-
const [existing] = await this.db.queryAll("SELECT read_at, from_id, in_reply_to FROM inbox_messages WHERE id = ?1 AND to_id = ?2", id, readerId);
|
|
51352
|
-
return existing?.read_at == null ? null : toReceipt(existing, false);
|
|
51353
|
-
} catch (error) {
|
|
51354
|
-
if (error instanceof Error && error.message.includes("no such table: inbox_messages")) {
|
|
51355
|
-
return null;
|
|
51356
|
-
}
|
|
51357
|
-
throw error;
|
|
51358
|
-
}
|
|
51359
|
-
}
|
|
51360
|
-
}
|
|
51361
|
-
var toReceipt = (row, transitioned) => ({
|
|
51362
|
-
readAt: row.read_at ?? 0,
|
|
51363
|
-
transitioned,
|
|
51364
|
-
fromId: row.from_id,
|
|
51365
|
-
inReplyTo: row.in_reply_to
|
|
51366
|
-
});
|
|
51367
|
-
|
|
51368
|
-
// ../../packages/domain/src/dao/inbox-unfinished-dao.ts
|
|
51369
|
-
class InboxUnfinishedDao {
|
|
51370
|
-
db;
|
|
51371
|
-
constructor(db) {
|
|
51372
|
-
this.db = db;
|
|
51373
|
-
}
|
|
51374
|
-
async listUnfinished(toId) {
|
|
51375
|
-
try {
|
|
51376
|
-
if (toId !== undefined) {
|
|
51377
|
-
return await this.db.queryAll(`SELECT id, to_id, status, inject_attempts, inject_error, created_at, request_key
|
|
51378
|
-
FROM inbox_messages
|
|
51379
|
-
WHERE to_id = ?1
|
|
51380
|
-
ORDER BY created_at DESC`, toId) ?? [];
|
|
51381
|
-
}
|
|
51382
|
-
return await this.db.queryAll(`SELECT id, to_id, status, inject_attempts, inject_error, created_at, request_key
|
|
51383
|
-
FROM inbox_messages
|
|
51384
|
-
ORDER BY created_at DESC`) ?? [];
|
|
51385
|
-
} catch (error) {
|
|
51386
|
-
if (error instanceof Error && error.message.includes("no such table: inbox_messages")) {
|
|
51387
|
-
return [];
|
|
51388
|
-
}
|
|
51389
|
-
throw error;
|
|
51390
|
-
}
|
|
51391
|
-
}
|
|
51392
|
-
async listByRequestKeyPrefix(prefix) {
|
|
51393
|
-
if (prefix === "") {
|
|
51394
|
-
throw new Error("listByRequestKeyPrefix requires a non-empty prefix");
|
|
51395
|
-
}
|
|
51396
|
-
try {
|
|
51397
|
-
return await this.db.queryAll(`SELECT id, to_id, status, inject_attempts, inject_error, created_at, request_key
|
|
51398
|
-
FROM inbox_messages
|
|
51399
|
-
WHERE request_key LIKE ?1 ESCAPE '\\'
|
|
51400
|
-
ORDER BY created_at DESC`, `${prefix.replace(/[\\%_]/g, "\\$&")}%`) ?? [];
|
|
51401
|
-
} catch (error) {
|
|
51402
|
-
if (error instanceof Error && error.message.includes("no such table: inbox_messages")) {
|
|
51403
|
-
return [];
|
|
51404
|
-
}
|
|
51405
|
-
throw error;
|
|
51406
|
-
}
|
|
51407
|
-
}
|
|
51408
|
-
}
|
|
51409
|
-
|
|
51410
51347
|
// ../../packages/domain/src/dao/member-lifecycle.ts
|
|
51411
51348
|
function parseObservation(payloadJson) {
|
|
51412
51349
|
if (payloadJson === null)
|
|
@@ -52264,10 +52201,11 @@ var init_workspace_dao = __esm(() => {
|
|
|
52264
52201
|
|
|
52265
52202
|
// ../../packages/domain/src/dao/index.ts
|
|
52266
52203
|
var init_dao = __esm(() => {
|
|
52267
|
-
|
|
52204
|
+
init_addressed_spec_ids();
|
|
52268
52205
|
init_artifact_dao();
|
|
52269
52206
|
init_database_inspector();
|
|
52270
52207
|
init_decision_log_dao();
|
|
52208
|
+
init_inbox_dao();
|
|
52271
52209
|
init_member_lifecycle();
|
|
52272
52210
|
init_member_session();
|
|
52273
52211
|
init_phase_run_dao();
|
|
@@ -57721,17 +57659,17 @@ function parentDir(filePath) {
|
|
|
57721
57659
|
const idx = filePath.lastIndexOf(sep);
|
|
57722
57660
|
return idx <= 0 ? "." : filePath.slice(0, idx);
|
|
57723
57661
|
}
|
|
57724
|
-
function
|
|
57662
|
+
function isPromiseLike(value) {
|
|
57725
57663
|
return value !== null && typeof value === "object" && "then" in value;
|
|
57726
57664
|
}
|
|
57727
57665
|
function attachFileObserver(lifecycleBus, filePath, writer) {
|
|
57728
57666
|
const logger = getLogger2("event-bus.file-observer");
|
|
57729
57667
|
const ensureResult = writer.ensureDir(parentDir(filePath));
|
|
57730
|
-
let ready = !
|
|
57731
|
-
let pending =
|
|
57668
|
+
let ready = !isPromiseLike(ensureResult);
|
|
57669
|
+
let pending = isPromiseLike(ensureResult);
|
|
57732
57670
|
let setupFailed = false;
|
|
57733
57671
|
let sequence = 0;
|
|
57734
|
-
let writeChain =
|
|
57672
|
+
let writeChain = isPromiseLike(ensureResult) ? Promise.resolve(ensureResult).then(() => {
|
|
57735
57673
|
ready = true;
|
|
57736
57674
|
}).catch((error) => {
|
|
57737
57675
|
setupFailed = true;
|
|
@@ -57760,7 +57698,7 @@ function attachFileObserver(lifecycleBus, filePath, writer) {
|
|
|
57760
57698
|
return;
|
|
57761
57699
|
if (ready && !pending) {
|
|
57762
57700
|
const result = writer.appendFile(filePath, content);
|
|
57763
|
-
if (
|
|
57701
|
+
if (isPromiseLike(result)) {
|
|
57764
57702
|
setPending(Promise.resolve(result));
|
|
57765
57703
|
}
|
|
57766
57704
|
return;
|
|
@@ -65480,6 +65418,41 @@ var init_dist10 = __esm(() => {
|
|
|
65480
65418
|
init_types3();
|
|
65481
65419
|
});
|
|
65482
65420
|
|
|
65421
|
+
// ../../packages/domain/src/schema/inbox-messages.ts
|
|
65422
|
+
var inboxMessagesTable, inboxMessages3, INBOX_MESSAGES_TABLE_SQL;
|
|
65423
|
+
var init_inbox_messages2 = __esm(() => {
|
|
65424
|
+
init_schema();
|
|
65425
|
+
inboxMessagesTable = defineTable("inbox_messages", {
|
|
65426
|
+
id: text("id").primaryKey(),
|
|
65427
|
+
fromId: text("from_id").notNull(),
|
|
65428
|
+
toId: text("to_id").notNull(),
|
|
65429
|
+
body: text("body").notNull(),
|
|
65430
|
+
kind: text("kind").notNull().default("chat"),
|
|
65431
|
+
status: text("status").notNull().default("queued"),
|
|
65432
|
+
claimedAt: integer2("claimed_at"),
|
|
65433
|
+
attempts: integer2("attempts").notNull().default(0),
|
|
65434
|
+
error: text("error"),
|
|
65435
|
+
deliveredAt: integer2("delivered_at"),
|
|
65436
|
+
readAt: integer2("read_at"),
|
|
65437
|
+
inReplyTo: text("in_reply_to"),
|
|
65438
|
+
threadId: text("thread_id"),
|
|
65439
|
+
requestKey: text("request_key"),
|
|
65440
|
+
taskId: text("task_id"),
|
|
65441
|
+
parentRunId: text("parent_run_id"),
|
|
65442
|
+
createdAt: integer2("created_at").notNull(),
|
|
65443
|
+
updatedAt: integer2("updated_at").notNull()
|
|
65444
|
+
});
|
|
65445
|
+
inboxMessages3 = inboxMessagesTable.table;
|
|
65446
|
+
INBOX_MESSAGES_TABLE_SQL = [
|
|
65447
|
+
inboxMessagesTable.createTableSql,
|
|
65448
|
+
"CREATE INDEX IF NOT EXISTS idx_inbox_messages_to_status ON inbox_messages (to_id, status)",
|
|
65449
|
+
"CREATE INDEX IF NOT EXISTS idx_inbox_messages_from_created ON inbox_messages (from_id, created_at)",
|
|
65450
|
+
"CREATE INDEX IF NOT EXISTS idx_inbox_messages_thread ON inbox_messages (thread_id)",
|
|
65451
|
+
"CREATE UNIQUE INDEX IF NOT EXISTS idx_inbox_messages_request_key ON inbox_messages (request_key) WHERE request_key IS NOT NULL"
|
|
65452
|
+
].map((sql) => `${sql};`).join(`
|
|
65453
|
+
`);
|
|
65454
|
+
});
|
|
65455
|
+
|
|
65483
65456
|
// ../../packages/domain/src/schema/planning.ts
|
|
65484
65457
|
var planningEventsTable, planningEvents, taskRunLinksTable, taskRunLinks, PLANNING_SCHEMA_SQL;
|
|
65485
65458
|
var init_planning = __esm(() => {
|
|
@@ -65518,6 +65491,7 @@ var init_schema3 = __esm(() => {
|
|
|
65518
65491
|
init_workflow_states();
|
|
65519
65492
|
init_workspaces();
|
|
65520
65493
|
init_artifacts();
|
|
65494
|
+
init_inbox_messages2();
|
|
65521
65495
|
init_phase_runs();
|
|
65522
65496
|
init_planning();
|
|
65523
65497
|
init_transition_runs();
|
|
@@ -65698,7 +65672,7 @@ ALTER TABLE inbox_messages ADD COLUMN request_key TEXT;
|
|
|
65698
65672
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_inbox_messages_request_key ON inbox_messages (request_key) WHERE request_key IS NOT NULL;
|
|
65699
65673
|
`, INBOX_MESSAGES_READ_AT_SCHEMA_SQL = `
|
|
65700
65674
|
ALTER TABLE inbox_messages ADD COLUMN read_at INTEGER;
|
|
65701
|
-
`, QUEUE_JOBS_SCHEMA_SQL = `
|
|
65675
|
+
`, INBOX_MESSAGES_REBUILD_SQL, QUEUE_JOBS_SCHEMA_SQL = `
|
|
65702
65676
|
CREATE TABLE IF NOT EXISTS queue_jobs (
|
|
65703
65677
|
id TEXT PRIMARY KEY,
|
|
65704
65678
|
type TEXT NOT NULL,
|
|
@@ -66457,6 +66431,10 @@ var init_migrations = __esm(() => {
|
|
|
66457
66431
|
init_dist5();
|
|
66458
66432
|
init_importer_schema_version();
|
|
66459
66433
|
init_schema3();
|
|
66434
|
+
INBOX_MESSAGES_REBUILD_SQL = `
|
|
66435
|
+
DROP TABLE IF EXISTS inbox_messages;
|
|
66436
|
+
${INBOX_MESSAGES_TABLE_SQL}
|
|
66437
|
+
`;
|
|
66460
66438
|
CLI_SCHEMA_SQL = `
|
|
66461
66439
|
${AGENT_EXECUTOR_UPDATES_SCHEMA_SQL}
|
|
66462
66440
|
|
|
@@ -66680,6 +66658,10 @@ ${HISTORY_TASK_SESSION_SCHEMA_SQL}
|
|
|
66680
66658
|
id: "0053_spur_cli_inbox_messages_read_at",
|
|
66681
66659
|
sql: INBOX_MESSAGES_READ_AT_SCHEMA_SQL,
|
|
66682
66660
|
addColumnIfMissing: { table: "inbox_messages", column: "read_at" }
|
|
66661
|
+
},
|
|
66662
|
+
{
|
|
66663
|
+
id: "0054_spur_cli_inbox_messages_rebuild",
|
|
66664
|
+
sql: INBOX_MESSAGES_REBUILD_SQL
|
|
66683
66665
|
}
|
|
66684
66666
|
];
|
|
66685
66667
|
});
|
|
@@ -84619,6 +84601,10 @@ var init_event_names = __esm(() => {
|
|
|
84619
84601
|
baseEvent("message.sent", "message", "message"),
|
|
84620
84602
|
baseEvent("message.replied", "message", "message"),
|
|
84621
84603
|
baseEvent("message.read", "message", "message"),
|
|
84604
|
+
baseEvent("message.claimed", "message", "message"),
|
|
84605
|
+
baseEvent("message.delivered", "message", "message"),
|
|
84606
|
+
baseEvent("message.failed", "message", "message"),
|
|
84607
|
+
baseEvent("message.requeued", "message", "message"),
|
|
84622
84608
|
baseEvent("process.spawned", "process", "process"),
|
|
84623
84609
|
baseEvent("process.exited", "process", "process"),
|
|
84624
84610
|
baseEvent("process.stopped", "process", "process"),
|
|
@@ -84957,6 +84943,66 @@ var init_event_names = __esm(() => {
|
|
|
84957
84943
|
},
|
|
84958
84944
|
outcome: unsupported
|
|
84959
84945
|
},
|
|
84946
|
+
"message.claimed": {
|
|
84947
|
+
description: "A member claimed a queued inbox message; the delivery budget was charged.",
|
|
84948
|
+
fields: [
|
|
84949
|
+
field("msgId", "Message"),
|
|
84950
|
+
field("fromId", "From"),
|
|
84951
|
+
field("toId", "Member"),
|
|
84952
|
+
field("threadId", "Thread"),
|
|
84953
|
+
field("status", "Status")
|
|
84954
|
+
],
|
|
84955
|
+
summary: ({ data }) => {
|
|
84956
|
+
const to = s2(data, "toId");
|
|
84957
|
+
return to !== undefined ? `[message] ${to} claimed` : "[message] claimed";
|
|
84958
|
+
},
|
|
84959
|
+
outcome: unsupported
|
|
84960
|
+
},
|
|
84961
|
+
"message.delivered": {
|
|
84962
|
+
description: "A claimed message was delivered to its member.",
|
|
84963
|
+
fields: [
|
|
84964
|
+
field("msgId", "Message"),
|
|
84965
|
+
field("fromId", "From"),
|
|
84966
|
+
field("toId", "Member"),
|
|
84967
|
+
field("threadId", "Thread"),
|
|
84968
|
+
field("status", "Status")
|
|
84969
|
+
],
|
|
84970
|
+
summary: ({ data }) => {
|
|
84971
|
+
const to = s2(data, "toId");
|
|
84972
|
+
return to !== undefined ? `[message] ${to} delivered` : "[message] delivered";
|
|
84973
|
+
},
|
|
84974
|
+
outcome: unsupported
|
|
84975
|
+
},
|
|
84976
|
+
"message.failed": {
|
|
84977
|
+
description: "A message could not be delivered \u2014 the attempt budget was spent or the run errored.",
|
|
84978
|
+
fields: [
|
|
84979
|
+
field("msgId", "Message"),
|
|
84980
|
+
field("fromId", "From"),
|
|
84981
|
+
field("toId", "Member"),
|
|
84982
|
+
field("threadId", "Thread"),
|
|
84983
|
+
field("status", "Status")
|
|
84984
|
+
],
|
|
84985
|
+
summary: ({ data }) => {
|
|
84986
|
+
const to = s2(data, "toId");
|
|
84987
|
+
return to !== undefined ? `[message] ${to} delivery failed` : "[message] failed";
|
|
84988
|
+
},
|
|
84989
|
+
outcome: unsupported
|
|
84990
|
+
},
|
|
84991
|
+
"message.requeued": {
|
|
84992
|
+
description: "A claim expired or was released, returning the message to the member queue.",
|
|
84993
|
+
fields: [
|
|
84994
|
+
field("msgId", "Message"),
|
|
84995
|
+
field("fromId", "From"),
|
|
84996
|
+
field("toId", "Member"),
|
|
84997
|
+
field("threadId", "Thread"),
|
|
84998
|
+
field("status", "Status")
|
|
84999
|
+
],
|
|
85000
|
+
summary: ({ data }) => {
|
|
85001
|
+
const to = s2(data, "toId");
|
|
85002
|
+
return to !== undefined ? `[message] ${to} requeued` : "[message] requeued";
|
|
85003
|
+
},
|
|
85004
|
+
outcome: unsupported
|
|
85005
|
+
},
|
|
84960
85006
|
"process.spawned": {
|
|
84961
85007
|
description: "A supervised process was spawned with a new pid.",
|
|
84962
85008
|
fields: [field("label", "Label"), field("pid", "PID"), field("agentId", "Agent")],
|
|
@@ -90820,8 +90866,482 @@ var init_fleet_service = __esm(() => {
|
|
|
90820
90866
|
init_project_registry();
|
|
90821
90867
|
});
|
|
90822
90868
|
|
|
90869
|
+
// ../../packages/app/src/services/system-event-retention.ts
|
|
90870
|
+
function resolveRetentionQuotas(config = {}, prefixes = SYSTEM_EVENT_PREFIXES) {
|
|
90871
|
+
const defaultQuota = config.default ?? DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA;
|
|
90872
|
+
const overrides = config.prefixes ?? {};
|
|
90873
|
+
return prefixes.map((prefix) => ({
|
|
90874
|
+
prefix,
|
|
90875
|
+
quota: overrides[prefix] ?? defaultQuota
|
|
90876
|
+
}));
|
|
90877
|
+
}
|
|
90878
|
+
var DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA = 1e4;
|
|
90879
|
+
var init_system_event_retention = __esm(() => {
|
|
90880
|
+
init_event_names();
|
|
90881
|
+
});
|
|
90882
|
+
|
|
90883
|
+
// ../../packages/app/src/services/system-event-tap.ts
|
|
90884
|
+
function registerSystemEventTap(bus, dao, logger, options = {}) {
|
|
90885
|
+
const handlers = new Map;
|
|
90886
|
+
const inFlight = new Set;
|
|
90887
|
+
const diagnosticEnabled = options.diagnosticEnabled === true;
|
|
90888
|
+
const quotas = resolveRetentionQuotas(options.retention);
|
|
90889
|
+
for (const entry of SYSTEM_EVENT_CATALOG) {
|
|
90890
|
+
if (entry.tier === "diagnostic" && !diagnosticEnabled)
|
|
90891
|
+
continue;
|
|
90892
|
+
if (entry.prefix === "message")
|
|
90893
|
+
continue;
|
|
90894
|
+
const handler = (event) => {
|
|
90895
|
+
const actor = extractSystemEventActor(event);
|
|
90896
|
+
const p = persist(dao, entry.prefix, {
|
|
90897
|
+
id: createId("sev"),
|
|
90898
|
+
event_name: entry.name,
|
|
90899
|
+
occurred_at: new Date().toISOString(),
|
|
90900
|
+
actor,
|
|
90901
|
+
payload_json: safeStringify(buildSystemEventEnvelope(entry, event, options.projectContext ?? systemEventProjectContext(""), options.secretValues, actor)),
|
|
90902
|
+
...extractSystemEventCorrelation(event)
|
|
90903
|
+
}, quotas, logger);
|
|
90904
|
+
inFlight.add(p);
|
|
90905
|
+
p.finally(() => inFlight.delete(p));
|
|
90906
|
+
};
|
|
90907
|
+
handlers.set(entry.name, handler);
|
|
90908
|
+
bus.on(entry.name, handler);
|
|
90909
|
+
}
|
|
90910
|
+
return {
|
|
90911
|
+
unsubscribe: () => {
|
|
90912
|
+
for (const [name, handler] of handlers) {
|
|
90913
|
+
bus.off(name, handler);
|
|
90914
|
+
}
|
|
90915
|
+
handlers.clear();
|
|
90916
|
+
},
|
|
90917
|
+
flush: async () => {
|
|
90918
|
+
while (inFlight.size > 0) {
|
|
90919
|
+
await Promise.allSettled(inFlight);
|
|
90920
|
+
}
|
|
90921
|
+
}
|
|
90922
|
+
};
|
|
90923
|
+
}
|
|
90924
|
+
async function persist(dao, prefix, input, quotas, logger) {
|
|
90925
|
+
try {
|
|
90926
|
+
await dao.insert(input);
|
|
90927
|
+
await dao.pruneQuotas(quotas, prefix);
|
|
90928
|
+
} catch (error) {
|
|
90929
|
+
logger.warn("system_events tap: persist failed", {
|
|
90930
|
+
name: input.event_name,
|
|
90931
|
+
error: stringifyError(error)
|
|
90932
|
+
});
|
|
90933
|
+
}
|
|
90934
|
+
}
|
|
90935
|
+
function safeStringify(event) {
|
|
90936
|
+
try {
|
|
90937
|
+
return JSON.stringify(event ?? null);
|
|
90938
|
+
} catch {
|
|
90939
|
+
return null;
|
|
90940
|
+
}
|
|
90941
|
+
}
|
|
90942
|
+
function extractSystemEventActor(event) {
|
|
90943
|
+
if (event && typeof event === "object") {
|
|
90944
|
+
const obj = event;
|
|
90945
|
+
if (typeof obj.actor === "string" && obj.actor.length > 0)
|
|
90946
|
+
return obj.actor;
|
|
90947
|
+
if (typeof obj.agentId === "string" && obj.agentId.length > 0)
|
|
90948
|
+
return obj.agentId;
|
|
90949
|
+
if (typeof obj.memberId === "string" && obj.memberId.length > 0)
|
|
90950
|
+
return obj.memberId;
|
|
90951
|
+
}
|
|
90952
|
+
return null;
|
|
90953
|
+
}
|
|
90954
|
+
function extractSystemEventCorrelation(event) {
|
|
90955
|
+
const correlation = { run_id: null, entity_kind: null, entity_id: null, sequence: null };
|
|
90956
|
+
if (!event || typeof event !== "object")
|
|
90957
|
+
return correlation;
|
|
90958
|
+
const obj = event;
|
|
90959
|
+
const nested = obj.correlation && typeof obj.correlation === "object" ? obj.correlation : {};
|
|
90960
|
+
const runId = obj.runId ?? obj.run_id ?? nested.runId;
|
|
90961
|
+
if (typeof runId === "string" && runId.length > 0)
|
|
90962
|
+
correlation.run_id = runId;
|
|
90963
|
+
if (typeof obj.sequence === "number" && Number.isFinite(obj.sequence))
|
|
90964
|
+
correlation.sequence = obj.sequence;
|
|
90965
|
+
const entity = obj.entity;
|
|
90966
|
+
if (entity && typeof entity === "object") {
|
|
90967
|
+
const { kind, id } = entity;
|
|
90968
|
+
if (typeof kind === "string" && kind.length > 0)
|
|
90969
|
+
correlation.entity_kind = kind;
|
|
90970
|
+
if (typeof id === "string" && id.length > 0)
|
|
90971
|
+
correlation.entity_id = id;
|
|
90972
|
+
}
|
|
90973
|
+
if (typeof obj.entityKind === "string" && obj.entityKind.length > 0)
|
|
90974
|
+
correlation.entity_kind = obj.entityKind;
|
|
90975
|
+
if (typeof obj.entityId === "string" && obj.entityId.length > 0)
|
|
90976
|
+
correlation.entity_id = obj.entityId;
|
|
90977
|
+
return correlation;
|
|
90978
|
+
}
|
|
90979
|
+
function stringifyError(error) {
|
|
90980
|
+
if (error instanceof Error)
|
|
90981
|
+
return `${error.name}: ${error.message}`;
|
|
90982
|
+
return String(error);
|
|
90983
|
+
}
|
|
90984
|
+
var init_system_event_tap = __esm(() => {
|
|
90985
|
+
init_src2();
|
|
90986
|
+
init_event_names();
|
|
90987
|
+
init_system_event_envelope();
|
|
90988
|
+
init_system_event_retention();
|
|
90989
|
+
});
|
|
90990
|
+
|
|
90991
|
+
// ../../packages/app/src/services/system-event-catch-all.ts
|
|
90992
|
+
function genericSystemEventCatalogEntry(name) {
|
|
90993
|
+
return {
|
|
90994
|
+
name,
|
|
90995
|
+
prefix: name.replace(/\..*/, ""),
|
|
90996
|
+
source: "bus",
|
|
90997
|
+
tier: "default",
|
|
90998
|
+
persisted: true,
|
|
90999
|
+
streamed: true,
|
|
91000
|
+
payloadPolicy: "metadata-only",
|
|
91001
|
+
renderer: GENERIC_SYSTEM_EVENT_RENDERER,
|
|
91002
|
+
producerPackage: "spur",
|
|
91003
|
+
subsystem: "unknown",
|
|
91004
|
+
severity: "info",
|
|
91005
|
+
description: "Uncataloged system event captured by the generic fallback; pending catalog promotion.",
|
|
91006
|
+
metadataFields: [],
|
|
91007
|
+
remediationKind: "none"
|
|
91008
|
+
};
|
|
91009
|
+
}
|
|
91010
|
+
function ensureRetentionQuotaForPrefix(quotas, prefix, fallbackQuota) {
|
|
91011
|
+
return quotas.some((quota) => quota.prefix === prefix) ? quotas : [...quotas, { prefix, quota: fallbackQuota }];
|
|
91012
|
+
}
|
|
91013
|
+
function createSystemEventCatchAllSink(options) {
|
|
91014
|
+
const { dao, logger } = options;
|
|
91015
|
+
const quotas = resolveRetentionQuotas(options.retention);
|
|
91016
|
+
const fallbackQuota = options.retention?.default ?? DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA;
|
|
91017
|
+
const projectContext = options.projectContext ?? systemEventProjectContext("");
|
|
91018
|
+
const secretValues = options.secretValues ?? [];
|
|
91019
|
+
const driftWarned = new Set;
|
|
91020
|
+
return async (name, payload) => {
|
|
91021
|
+
const entry = genericSystemEventCatalogEntry(name);
|
|
91022
|
+
const actor = extractSystemEventActor(payload);
|
|
91023
|
+
try {
|
|
91024
|
+
await dao.insert({
|
|
91025
|
+
id: createId("sev"),
|
|
91026
|
+
event_name: entry.name,
|
|
91027
|
+
occurred_at: new Date().toISOString(),
|
|
91028
|
+
actor,
|
|
91029
|
+
payload_json: safeStringify(buildSystemEventEnvelope(entry, payload, projectContext, secretValues, actor)),
|
|
91030
|
+
...extractSystemEventCorrelation(payload)
|
|
91031
|
+
});
|
|
91032
|
+
await dao.pruneQuotas(ensureRetentionQuotaForPrefix(quotas, entry.prefix, fallbackQuota), entry.prefix);
|
|
91033
|
+
} catch (error) {
|
|
91034
|
+
logger.warn("system_events catch-all: persist failed", {
|
|
91035
|
+
event: name,
|
|
91036
|
+
error: error instanceof Error ? `${error.name}: ${error.message}` : String(error)
|
|
91037
|
+
});
|
|
91038
|
+
return;
|
|
91039
|
+
}
|
|
91040
|
+
if (!driftWarned.has(name)) {
|
|
91041
|
+
driftWarned.add(name);
|
|
91042
|
+
logger.warn(SYSTEM_EVENT_UNCATALOGED_WARN_EVENT, { event: name });
|
|
91043
|
+
}
|
|
91044
|
+
};
|
|
91045
|
+
}
|
|
91046
|
+
function installSystemEventCatchAll(bus, sink) {
|
|
91047
|
+
const holder = bus;
|
|
91048
|
+
const current = holder.emit;
|
|
91049
|
+
if (current[CATCH_ALL_MARKER] === true) {
|
|
91050
|
+
return { installed: false, flush: async () => {} };
|
|
91051
|
+
}
|
|
91052
|
+
const underlying = current.bind(bus);
|
|
91053
|
+
const inFlight = new Set;
|
|
91054
|
+
const wrapped = (event, ...args) => {
|
|
91055
|
+
const result = underlying(event, ...args);
|
|
91056
|
+
if (systemEventCatalogEntry(event) === undefined) {
|
|
91057
|
+
const pending = Promise.resolve().then(() => sink(event, args[0]));
|
|
91058
|
+
inFlight.add(pending);
|
|
91059
|
+
pending.catch(() => {}).finally(() => {
|
|
91060
|
+
inFlight.delete(pending);
|
|
91061
|
+
});
|
|
91062
|
+
}
|
|
91063
|
+
return result;
|
|
91064
|
+
};
|
|
91065
|
+
Object.defineProperty(wrapped, CATCH_ALL_MARKER, { value: true });
|
|
91066
|
+
holder.emit = wrapped;
|
|
91067
|
+
return {
|
|
91068
|
+
installed: true,
|
|
91069
|
+
flush: async () => {
|
|
91070
|
+
while (inFlight.size > 0) {
|
|
91071
|
+
await Promise.allSettled([...inFlight]);
|
|
91072
|
+
}
|
|
91073
|
+
}
|
|
91074
|
+
};
|
|
91075
|
+
}
|
|
91076
|
+
var GENERIC_SYSTEM_EVENT_RENDERER = "generic", SYSTEM_EVENT_UNCATALOGED_WARN_EVENT = "system_events.uncataloged", CATCH_ALL_MARKER = "spur.systemEventCatchAll";
|
|
91077
|
+
var init_system_event_catch_all = __esm(() => {
|
|
91078
|
+
init_src2();
|
|
91079
|
+
init_event_names();
|
|
91080
|
+
init_system_event_envelope();
|
|
91081
|
+
init_system_event_retention();
|
|
91082
|
+
init_system_event_tap();
|
|
91083
|
+
});
|
|
91084
|
+
|
|
91085
|
+
// ../../packages/app/src/services/inbox-service.ts
|
|
91086
|
+
function isReservedInboxId(id) {
|
|
91087
|
+
return RESERVED_INBOX_IDS.includes(id);
|
|
91088
|
+
}
|
|
91089
|
+
function assertSameRequest(existing, input) {
|
|
91090
|
+
const sameRequest = existing.fromId === input.from && existing.toId === input.to && existing.body === input.body && existing.kind === (input.kind ?? "chat");
|
|
91091
|
+
if (!sameRequest)
|
|
91092
|
+
throw new RequestKeyConflictError2(input.requestKey ?? "");
|
|
91093
|
+
return existing;
|
|
91094
|
+
}
|
|
91095
|
+
function isRequestKeyCollision(error) {
|
|
91096
|
+
if (!(error instanceof Error))
|
|
91097
|
+
return false;
|
|
91098
|
+
const code = error.code;
|
|
91099
|
+
if (code === "SQLITE_CONSTRAINT_UNIQUE") {
|
|
91100
|
+
return error.message.includes("inbox_messages.request_key") || !error.message.includes("constraint failed");
|
|
91101
|
+
}
|
|
91102
|
+
return error.message.includes("UNIQUE constraint failed") && error.message.includes("inbox_messages.request_key");
|
|
91103
|
+
}
|
|
91104
|
+
function encodeCursor(row) {
|
|
91105
|
+
return Buffer.from(JSON.stringify({ c: row.createdAt, i: row.id })).toString("base64url");
|
|
91106
|
+
}
|
|
91107
|
+
function decodeCursor(cursor) {
|
|
91108
|
+
try {
|
|
91109
|
+
const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
91110
|
+
if (typeof parsed.c !== "number" || typeof parsed.i !== "string")
|
|
91111
|
+
throw new Error("shape");
|
|
91112
|
+
return { createdAt: parsed.c, id: parsed.i };
|
|
91113
|
+
} catch {
|
|
91114
|
+
throw new Error("malformed list cursor");
|
|
91115
|
+
}
|
|
91116
|
+
}
|
|
91117
|
+
|
|
91118
|
+
class InboxService {
|
|
91119
|
+
deps;
|
|
91120
|
+
leaseMs;
|
|
91121
|
+
clock;
|
|
91122
|
+
constructor(deps) {
|
|
91123
|
+
this.deps = deps;
|
|
91124
|
+
this.leaseMs = deps.leaseMs ?? CLAIM_LEASE_MS;
|
|
91125
|
+
this.clock = deps.now ?? (() => Date.now());
|
|
91126
|
+
}
|
|
91127
|
+
async send(input) {
|
|
91128
|
+
if (input.from.trim() === "")
|
|
91129
|
+
throw new Error("inbox message requires a non-empty sender");
|
|
91130
|
+
if (input.to.trim() === "")
|
|
91131
|
+
throw new Error("inbox message requires a non-empty recipient");
|
|
91132
|
+
const db = await this.deps.getDb();
|
|
91133
|
+
const dao = new InboxDao(db);
|
|
91134
|
+
if (input.requestKey !== undefined) {
|
|
91135
|
+
const existing = await dao.findByRequestKey(input.requestKey);
|
|
91136
|
+
if (existing !== null)
|
|
91137
|
+
return { message: assertSameRequest(existing, input), replayed: true };
|
|
91138
|
+
}
|
|
91139
|
+
const now = this.clock();
|
|
91140
|
+
const id = `msg_${crypto.randomUUID()}`;
|
|
91141
|
+
let message;
|
|
91142
|
+
try {
|
|
91143
|
+
message = await dao.insert({
|
|
91144
|
+
id,
|
|
91145
|
+
fromId: input.from,
|
|
91146
|
+
toId: input.to,
|
|
91147
|
+
body: input.body,
|
|
91148
|
+
kind: input.kind ?? "chat",
|
|
91149
|
+
status: "queued",
|
|
91150
|
+
requestKey: input.requestKey ?? null,
|
|
91151
|
+
taskId: input.taskId ?? null,
|
|
91152
|
+
parentRunId: input.parentRunId ?? null,
|
|
91153
|
+
inReplyTo: null,
|
|
91154
|
+
threadId: id,
|
|
91155
|
+
createdAt: now,
|
|
91156
|
+
updatedAt: now
|
|
91157
|
+
}, this.eventFactory("message.sent"));
|
|
91158
|
+
} catch (error) {
|
|
91159
|
+
if (input.requestKey !== undefined && isRequestKeyCollision(error)) {
|
|
91160
|
+
const winner = await dao.findByRequestKey(input.requestKey);
|
|
91161
|
+
if (winner !== null)
|
|
91162
|
+
return { message: assertSameRequest(winner, input), replayed: true };
|
|
91163
|
+
}
|
|
91164
|
+
throw error;
|
|
91165
|
+
}
|
|
91166
|
+
await this.pruneEvents(db);
|
|
91167
|
+
return { message, replayed: false };
|
|
91168
|
+
}
|
|
91169
|
+
async reply(id, from, body) {
|
|
91170
|
+
const db = await this.deps.getDb();
|
|
91171
|
+
const dao = new InboxDao(db);
|
|
91172
|
+
const original = await dao.get(id);
|
|
91173
|
+
if (original === null)
|
|
91174
|
+
throw new MessageNotFoundError(`No message found with id "${id}"`);
|
|
91175
|
+
const now = this.clock();
|
|
91176
|
+
const replyId = `msg_${crypto.randomUUID()}`;
|
|
91177
|
+
const message = await dao.insert({
|
|
91178
|
+
id: replyId,
|
|
91179
|
+
fromId: from,
|
|
91180
|
+
toId: original.fromId,
|
|
91181
|
+
body,
|
|
91182
|
+
kind: original.kind,
|
|
91183
|
+
status: "queued",
|
|
91184
|
+
requestKey: null,
|
|
91185
|
+
taskId: original.taskId,
|
|
91186
|
+
parentRunId: original.parentRunId,
|
|
91187
|
+
inReplyTo: original.id,
|
|
91188
|
+
threadId: original.threadId,
|
|
91189
|
+
createdAt: now,
|
|
91190
|
+
updatedAt: now
|
|
91191
|
+
}, this.eventFactory("message.replied"));
|
|
91192
|
+
await this.pruneEvents(db);
|
|
91193
|
+
return message;
|
|
91194
|
+
}
|
|
91195
|
+
async get(id) {
|
|
91196
|
+
return new InboxDao(await this.deps.getDb()).get(id);
|
|
91197
|
+
}
|
|
91198
|
+
async claim(member, limit) {
|
|
91199
|
+
const db = await this.deps.getDb();
|
|
91200
|
+
const claimed = await new InboxDao(db).claim(member, limit, this.clock(), this.eventFactory("message.claimed"));
|
|
91201
|
+
await this.pruneEvents(db);
|
|
91202
|
+
return claimed;
|
|
91203
|
+
}
|
|
91204
|
+
async settle(ids, outcome, error) {
|
|
91205
|
+
if (ids.length === 0)
|
|
91206
|
+
return [];
|
|
91207
|
+
const db = await this.deps.getDb();
|
|
91208
|
+
const settled = await new InboxDao(db).settle(ids, outcome === "accepted" ? "delivered" : "failed", this.clock(), this.eventFactory(outcome === "accepted" ? "message.delivered" : "message.failed"), outcome === "accepted" ? null : error ?? "delivery did not start");
|
|
91209
|
+
await this.pruneEvents(db);
|
|
91210
|
+
return settled;
|
|
91211
|
+
}
|
|
91212
|
+
async release(ids) {
|
|
91213
|
+
if (ids.length === 0)
|
|
91214
|
+
return [];
|
|
91215
|
+
const db = await this.deps.getDb();
|
|
91216
|
+
const released = await new InboxDao(db).release(ids, this.clock(), this.eventFactory("message.requeued"));
|
|
91217
|
+
await this.pruneEvents(db);
|
|
91218
|
+
return released;
|
|
91219
|
+
}
|
|
91220
|
+
async sweep(now = this.clock()) {
|
|
91221
|
+
const db = await this.deps.getDb();
|
|
91222
|
+
const dao = new InboxDao(db);
|
|
91223
|
+
const runs = new CoordinationRunDao(db);
|
|
91224
|
+
const cutoff = now - this.leaseMs;
|
|
91225
|
+
const result = { requeued: [], failed: [], settled: [] };
|
|
91226
|
+
for (const message of await dao.listExpiredClaims(cutoff)) {
|
|
91227
|
+
const receipt = (await runs.listByMessageId(message.id))[0];
|
|
91228
|
+
if (receipt !== undefined) {
|
|
91229
|
+
if (receipt.status === "running")
|
|
91230
|
+
continue;
|
|
91231
|
+
const delivered = receipt.outcome === "verified" || receipt.outcome === "run-exit-only";
|
|
91232
|
+
const settled = await dao.settle([message.id], delivered ? "delivered" : "failed", now, this.eventFactory(delivered ? "message.delivered" : "message.failed"), delivered ? null : `run ${receipt.run_id} ${receipt.status}`);
|
|
91233
|
+
for (const row of settled)
|
|
91234
|
+
result.settled.push(row.id);
|
|
91235
|
+
continue;
|
|
91236
|
+
}
|
|
91237
|
+
if (message.attempts >= MAX_DELIVERY_ATTEMPTS) {
|
|
91238
|
+
const failed = await dao.settle([message.id], "failed", now, this.eventFactory("message.failed"), `attempts exhausted after ${MAX_DELIVERY_ATTEMPTS} deliveries`);
|
|
91239
|
+
for (const row of failed)
|
|
91240
|
+
result.failed.push(row.id);
|
|
91241
|
+
continue;
|
|
91242
|
+
}
|
|
91243
|
+
const requeued = await dao.release([message.id], now, this.eventFactory("message.requeued"));
|
|
91244
|
+
for (const row of requeued)
|
|
91245
|
+
result.requeued.push(row.id);
|
|
91246
|
+
}
|
|
91247
|
+
await this.pruneEvents(db);
|
|
91248
|
+
return result;
|
|
91249
|
+
}
|
|
91250
|
+
async markRead(id, reader) {
|
|
91251
|
+
const db = await this.deps.getDb();
|
|
91252
|
+
const receipt = await new InboxDao(db).markRead(id, reader, this.clock(), this.eventFactory("message.read"));
|
|
91253
|
+
if (receipt === null)
|
|
91254
|
+
throw new MessageNotFoundError(`message ${id} not found for ${reader}`);
|
|
91255
|
+
await this.pruneEvents(db);
|
|
91256
|
+
return { readAt: receipt.message.readAt ?? this.clock(), transitioned: receipt.transitioned };
|
|
91257
|
+
}
|
|
91258
|
+
async list(member, direction = "in", page) {
|
|
91259
|
+
const dao = new InboxDao(await this.deps.getDb());
|
|
91260
|
+
const limit = page?.limit ?? 100;
|
|
91261
|
+
const before = page?.cursor === undefined ? undefined : decodeCursor(page.cursor);
|
|
91262
|
+
const rows = await dao.list(member, direction, limit, before);
|
|
91263
|
+
const messages = rows.slice(0, limit);
|
|
91264
|
+
const last = messages.at(-1);
|
|
91265
|
+
return {
|
|
91266
|
+
messages,
|
|
91267
|
+
nextCursor: rows.length > limit && last !== undefined ? encodeCursor(last) : null
|
|
91268
|
+
};
|
|
91269
|
+
}
|
|
91270
|
+
async thread(id) {
|
|
91271
|
+
const dao = new InboxDao(await this.deps.getDb());
|
|
91272
|
+
const root = await dao.get(id);
|
|
91273
|
+
return dao.thread(root?.threadId ?? id);
|
|
91274
|
+
}
|
|
91275
|
+
async requests(member) {
|
|
91276
|
+
return new InboxDao(await this.deps.getDb()).requests(member === undefined ? undefined : [member]);
|
|
91277
|
+
}
|
|
91278
|
+
async countQueued(member) {
|
|
91279
|
+
return new InboxDao(await this.deps.getDb()).countQueued(member);
|
|
91280
|
+
}
|
|
91281
|
+
async members() {
|
|
91282
|
+
return new InboxDao(await this.deps.getDb()).distinctRecipients();
|
|
91283
|
+
}
|
|
91284
|
+
async attempts(taskId) {
|
|
91285
|
+
return new InboxDao(await this.deps.getDb()).attemptsForTask(taskId);
|
|
91286
|
+
}
|
|
91287
|
+
async taskAttempts(taskId) {
|
|
91288
|
+
return new InboxDao(await this.deps.getDb()).listByTask(taskId);
|
|
91289
|
+
}
|
|
91290
|
+
eventFactory(name) {
|
|
91291
|
+
return (row) => ({
|
|
91292
|
+
id: `sev_${crypto.randomUUID()}`,
|
|
91293
|
+
event_name: name,
|
|
91294
|
+
occurred_at: new Date(row.updatedAt).toISOString(),
|
|
91295
|
+
actor: name === "message.read" || name === "message.claimed" || name === "message.delivered" || name === "message.failed" || name === "message.requeued" ? row.toId : row.fromId,
|
|
91296
|
+
payload_json: JSON.stringify({
|
|
91297
|
+
msgId: row.id,
|
|
91298
|
+
fromId: row.fromId,
|
|
91299
|
+
toId: row.toId,
|
|
91300
|
+
threadId: row.threadId,
|
|
91301
|
+
status: row.status,
|
|
91302
|
+
createdAt: new Date(row.createdAt).toISOString()
|
|
91303
|
+
})
|
|
91304
|
+
});
|
|
91305
|
+
}
|
|
91306
|
+
async pruneEvents(db) {
|
|
91307
|
+
try {
|
|
91308
|
+
const quotas = ensureRetentionQuotaForPrefix(resolveRetentionQuotas(), "message", DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA);
|
|
91309
|
+
await new SystemEventDao(db).pruneQuotas(quotas, "message");
|
|
91310
|
+
} catch {}
|
|
91311
|
+
}
|
|
91312
|
+
}
|
|
91313
|
+
var OPERATOR_INBOX_ID = "operator", SPUR_INBOX_ID = "spur", RESERVED_INBOX_IDS, MAX_DELIVERY_ATTEMPTS = 3, RequestKeyConflictError2, CLAIM_LEASE_MS = 600000;
|
|
91314
|
+
var init_inbox_service = __esm(() => {
|
|
91315
|
+
init_src2();
|
|
91316
|
+
init_agent_coordination_service();
|
|
91317
|
+
init_system_event_catch_all();
|
|
91318
|
+
init_system_event_retention();
|
|
91319
|
+
RESERVED_INBOX_IDS = [OPERATOR_INBOX_ID, SPUR_INBOX_ID];
|
|
91320
|
+
RequestKeyConflictError2 = class RequestKeyConflictError2 extends Error {
|
|
91321
|
+
requestKey;
|
|
91322
|
+
constructor(requestKey) {
|
|
91323
|
+
super(`request key "${requestKey}" already names a different message`);
|
|
91324
|
+
this.requestKey = requestKey;
|
|
91325
|
+
this.name = "RequestKeyConflictError";
|
|
91326
|
+
}
|
|
91327
|
+
};
|
|
91328
|
+
});
|
|
91329
|
+
|
|
90823
91330
|
// ../../packages/app/src/services/agent-coordination-service.ts
|
|
90824
91331
|
import { join as join22 } from "path";
|
|
91332
|
+
function toInboxEntry(row) {
|
|
91333
|
+
return {
|
|
91334
|
+
id: row.id,
|
|
91335
|
+
fromId: row.fromId,
|
|
91336
|
+
body: row.body,
|
|
91337
|
+
status: row.status,
|
|
91338
|
+
createdAt: new Date(row.createdAt).toISOString(),
|
|
91339
|
+
inReplyTo: row.inReplyTo,
|
|
91340
|
+
injectAttempts: row.attempts,
|
|
91341
|
+
injectError: row.error,
|
|
91342
|
+
requestKey: row.requestKey
|
|
91343
|
+
};
|
|
91344
|
+
}
|
|
90825
91345
|
|
|
90826
91346
|
class AgentCoordinationService {
|
|
90827
91347
|
ctx;
|
|
@@ -90830,157 +91350,88 @@ class AgentCoordinationService {
|
|
|
90830
91350
|
this.ctx = ctx;
|
|
90831
91351
|
this.configDir = join22(ctx.cwd, ".spur", "agents");
|
|
90832
91352
|
}
|
|
90833
|
-
async sendMessage(fromId, toId, body, replyTo, requestKey) {
|
|
91353
|
+
async sendMessage(fromId, toId, body, replyTo, requestKey, extra) {
|
|
90834
91354
|
validateAgentId(toId);
|
|
90835
91355
|
if (fromId !== null)
|
|
90836
91356
|
validateAgentId(fromId);
|
|
90837
91357
|
if (requestKey !== undefined && requestKey.trim() === "") {
|
|
90838
91358
|
throw new Error("requestKey must be a non-empty string");
|
|
90839
91359
|
}
|
|
90840
|
-
const
|
|
90841
|
-
const
|
|
90842
|
-
|
|
90843
|
-
|
|
90844
|
-
msgId,
|
|
90845
|
-
|
|
90846
|
-
|
|
90847
|
-
|
|
90848
|
-
|
|
91360
|
+
const sender = fromId ?? OPERATOR_INBOX_ID;
|
|
91361
|
+
const service = await this.inboxService();
|
|
91362
|
+
if (replyTo !== undefined) {
|
|
91363
|
+
const reply = await service.reply(replyTo, sender, body);
|
|
91364
|
+
return { msgId: reply.id, toId: reply.toId, status: reply.status, injected: false };
|
|
91365
|
+
}
|
|
91366
|
+
const { message, replayed } = await service.send({
|
|
91367
|
+
from: sender,
|
|
91368
|
+
to: toId,
|
|
91369
|
+
body,
|
|
91370
|
+
kind: extra?.kind ?? "chat",
|
|
91371
|
+
...requestKey !== undefined ? { requestKey } : {},
|
|
91372
|
+
...extra?.taskId !== undefined ? { taskId: extra.taskId } : {},
|
|
91373
|
+
...extra?.parentRunId !== undefined ? { parentRunId: extra.parentRunId } : {}
|
|
90849
91374
|
});
|
|
90850
|
-
return
|
|
91375
|
+
return requestKey === undefined ? { msgId: message.id, toId: message.toId, status: message.status, injected: false } : {
|
|
91376
|
+
msgId: message.id,
|
|
91377
|
+
toId: message.toId,
|
|
91378
|
+
status: message.status,
|
|
91379
|
+
injected: false,
|
|
91380
|
+
replayed,
|
|
91381
|
+
requestKey
|
|
91382
|
+
};
|
|
90851
91383
|
}
|
|
90852
91384
|
async markMessageRead(msgId, readerId) {
|
|
90853
91385
|
validateAgentId(readerId);
|
|
90854
|
-
const receipt = await (await this.
|
|
91386
|
+
const receipt = await (await this.inboxService()).markRead(msgId, readerId).catch(() => null);
|
|
90855
91387
|
if (receipt === null)
|
|
90856
91388
|
throw new MessageNotFoundError(`message ${msgId} not found for ${readerId}`);
|
|
90857
|
-
|
|
90858
|
-
if (receipt.transitioned) {
|
|
90859
|
-
await this.emitMessageEvent("message.read", {
|
|
90860
|
-
msgId,
|
|
90861
|
-
fromId: receipt.fromId,
|
|
90862
|
-
toId: readerId,
|
|
90863
|
-
threadId: receipt.inReplyTo,
|
|
90864
|
-
createdAt: readAt
|
|
90865
|
-
});
|
|
90866
|
-
}
|
|
90867
|
-
return { readAt, transitioned: receipt.transitioned };
|
|
91389
|
+
return { readAt: new Date(receipt.readAt).toISOString(), transitioned: receipt.transitioned };
|
|
90868
91390
|
}
|
|
90869
91391
|
async getInbox(agentId, limit, offset) {
|
|
90870
91392
|
validateAgentId(agentId);
|
|
90871
|
-
const
|
|
90872
|
-
|
|
90873
|
-
|
|
90874
|
-
|
|
90875
|
-
id: row.id,
|
|
90876
|
-
fromId: row.fromId,
|
|
90877
|
-
body: row.body,
|
|
90878
|
-
status: row.status,
|
|
90879
|
-
createdAt: new Date(row.createdAt).toISOString(),
|
|
90880
|
-
inReplyTo: row.inReplyTo,
|
|
90881
|
-
injectAttempts: row.injectAttempts,
|
|
90882
|
-
injectError: row.injectError,
|
|
90883
|
-
requestKey: row.requestKey
|
|
90884
|
-
})),
|
|
90885
|
-
count: rows.length
|
|
90886
|
-
};
|
|
91393
|
+
const { messages: rows } = await (await this.inboxService()).list(agentId, "in", {
|
|
91394
|
+
...limit !== undefined ? { limit } : {}
|
|
91395
|
+
});
|
|
91396
|
+
return { messages: rows.map(toInboxEntry), count: rows.length };
|
|
90887
91397
|
}
|
|
90888
91398
|
async getMessage(msgId) {
|
|
90889
|
-
const
|
|
90890
|
-
|
|
90891
|
-
if (row === undefined)
|
|
91399
|
+
const row = await (await this.inboxService()).get(msgId);
|
|
91400
|
+
if (row === null)
|
|
90892
91401
|
return null;
|
|
90893
|
-
return { id: row.id, toId: row.toId, status: row.status, requestKey: row.requestKey
|
|
91402
|
+
return { id: row.id, toId: row.toId, status: row.status, requestKey: row.requestKey };
|
|
90894
91403
|
}
|
|
90895
91404
|
async drainPending(agentId, options) {
|
|
90896
91405
|
validateAgentId(agentId);
|
|
90897
|
-
const
|
|
90898
|
-
|
|
90899
|
-
return {
|
|
90900
|
-
messages: rows.map((row) => ({
|
|
90901
|
-
id: row.id,
|
|
90902
|
-
fromId: row.fromId,
|
|
90903
|
-
body: row.body,
|
|
90904
|
-
status: row.status,
|
|
90905
|
-
createdAt: new Date(row.createdAt).toISOString(),
|
|
90906
|
-
inReplyTo: row.inReplyTo,
|
|
90907
|
-
requestKey: row.requestKey
|
|
90908
|
-
})),
|
|
90909
|
-
count: rows.length
|
|
90910
|
-
};
|
|
91406
|
+
const rows = await (await this.inboxService()).claim(agentId, options?.limit ?? 100);
|
|
91407
|
+
return { messages: rows.map(toInboxEntry), count: rows.length };
|
|
90911
91408
|
}
|
|
90912
91409
|
async releasePending(msgIds) {
|
|
90913
91410
|
if (msgIds.length === 0)
|
|
90914
91411
|
return 0;
|
|
90915
|
-
|
|
90916
|
-
return dao.release(msgIds);
|
|
91412
|
+
return (await (await this.inboxService()).release(msgIds)).length;
|
|
90917
91413
|
}
|
|
90918
91414
|
async settleDelivered(msgIds) {
|
|
90919
91415
|
if (msgIds.length === 0)
|
|
90920
91416
|
return;
|
|
90921
|
-
|
|
90922
|
-
for (const msgId of msgIds)
|
|
90923
|
-
await dao.markDelivered(msgId);
|
|
91417
|
+
await (await this.inboxService()).settle(msgIds, "accepted");
|
|
90924
91418
|
}
|
|
90925
91419
|
async settleFailed(msgIds, error) {
|
|
90926
91420
|
if (msgIds.length === 0)
|
|
90927
91421
|
return;
|
|
90928
|
-
|
|
90929
|
-
for (const msgId of msgIds)
|
|
90930
|
-
await dao.markFailed(msgId, error);
|
|
91422
|
+
await (await this.inboxService()).settle(msgIds, "not-started", error);
|
|
90931
91423
|
}
|
|
90932
91424
|
async countPending(agentId) {
|
|
90933
91425
|
validateAgentId(agentId);
|
|
90934
|
-
|
|
90935
|
-
return dao.countPending(agentId);
|
|
90936
|
-
}
|
|
90937
|
-
async listRecent(limit = 50) {
|
|
90938
|
-
const dao = await this.inboxRecentDao();
|
|
90939
|
-
const rows = await dao.listRecent(limit);
|
|
90940
|
-
if (rows.length === 0) {
|
|
90941
|
-
return { messages: [], count: 0 };
|
|
90942
|
-
}
|
|
90943
|
-
const specs = await this.listAgentSpecs();
|
|
90944
|
-
const identityById = new Map;
|
|
90945
|
-
for (const spec of specs) {
|
|
90946
|
-
identityById.set(spec.id, {
|
|
90947
|
-
agentId: spec.id,
|
|
90948
|
-
memberLabel: spec.name,
|
|
90949
|
-
agentType: spec.type
|
|
90950
|
-
});
|
|
90951
|
-
}
|
|
90952
|
-
const replyCounts = await dao.countReplies(rows.map((r) => r.id));
|
|
90953
|
-
const resolveEndpoint = (agentId) => identityById.get(agentId) ?? { agentId };
|
|
90954
|
-
return {
|
|
90955
|
-
messages: rows.map((row) => {
|
|
90956
|
-
const replyCount = replyCounts.get(row.id) ?? 0;
|
|
90957
|
-
return {
|
|
90958
|
-
id: row.id,
|
|
90959
|
-
fromId: row.from_id,
|
|
90960
|
-
toId: row.to_id,
|
|
90961
|
-
body: row.body,
|
|
90962
|
-
status: row.status,
|
|
90963
|
-
createdAt: new Date(row.created_at).toISOString(),
|
|
90964
|
-
inReplyTo: row.in_reply_to,
|
|
90965
|
-
...row.from_id !== null ? { from: resolveEndpoint(row.from_id) } : {},
|
|
90966
|
-
to: resolveEndpoint(row.to_id),
|
|
90967
|
-
hasReply: replyCount > 0,
|
|
90968
|
-
replyCount
|
|
90969
|
-
};
|
|
90970
|
-
}),
|
|
90971
|
-
count: rows.length
|
|
90972
|
-
};
|
|
91426
|
+
return (await this.inboxService()).countQueued(agentId);
|
|
90973
91427
|
}
|
|
90974
91428
|
async replyToMessage(msgId, body) {
|
|
90975
|
-
const
|
|
90976
|
-
const original = await
|
|
90977
|
-
if (original ===
|
|
91429
|
+
const service = await this.inboxService();
|
|
91430
|
+
const original = await service.get(msgId);
|
|
91431
|
+
if (original === null)
|
|
90978
91432
|
throw new Error(`No message found with id "${msgId}"`);
|
|
90979
|
-
|
|
90980
|
-
|
|
90981
|
-
throw new Error(`Message "${msgId}" has no sender to reply to (operator-originated)`);
|
|
90982
|
-
}
|
|
90983
|
-
return this.sendMessage(original.toId, original.fromId, body, msgId);
|
|
91433
|
+
const reply = await service.reply(msgId, original.toId, body);
|
|
91434
|
+
return { msgId: reply.id, toId: reply.toId, status: reply.status, injected: false };
|
|
90984
91435
|
}
|
|
90985
91436
|
async assignTask(taskId, agentId) {
|
|
90986
91437
|
validateAgentId(agentId);
|
|
@@ -91032,7 +91483,13 @@ class AgentCoordinationService {
|
|
|
91032
91483
|
for (const id of shadowed) {
|
|
91033
91484
|
this.ctx.output?.error(`agent spec "${id}" under .spur/agents/ is shadowed by the agent.fleet declaration`);
|
|
91034
91485
|
}
|
|
91035
|
-
|
|
91486
|
+
const addressable = specs.filter((spec) => {
|
|
91487
|
+
if (!isReservedInboxId(spec.id))
|
|
91488
|
+
return true;
|
|
91489
|
+
this.ctx.output?.error(`agent spec "${spec.id}" uses a reserved inbox identity and is ignored`);
|
|
91490
|
+
return false;
|
|
91491
|
+
});
|
|
91492
|
+
return addressable;
|
|
91036
91493
|
}
|
|
91037
91494
|
async listFleetMembers() {
|
|
91038
91495
|
return (await this.fleetService().resolve(this.ctx.cwd)).members;
|
|
@@ -91061,27 +91518,8 @@ class AgentCoordinationService {
|
|
|
91061
91518
|
openDb: () => this.ctx.getDb()
|
|
91062
91519
|
});
|
|
91063
91520
|
}
|
|
91064
|
-
async
|
|
91065
|
-
|
|
91066
|
-
return new InboxMessageDao(db);
|
|
91067
|
-
}
|
|
91068
|
-
async emitMessageEvent(name, payload) {
|
|
91069
|
-
const bus = this.ctx.eventBus;
|
|
91070
|
-
try {
|
|
91071
|
-
if (bus) {
|
|
91072
|
-
bus.emit(name, { ...payload, severity: "info" });
|
|
91073
|
-
} else {
|
|
91074
|
-
await new SystemEventDao(await this.ctx.getDb()).insert({
|
|
91075
|
-
id: crypto.randomUUID(),
|
|
91076
|
-
event_name: name,
|
|
91077
|
-
occurred_at: payload.createdAt,
|
|
91078
|
-
actor: (name === "message.read" ? payload.toId : payload.fromId) ?? "operator",
|
|
91079
|
-
payload_json: JSON.stringify(payload)
|
|
91080
|
-
});
|
|
91081
|
-
}
|
|
91082
|
-
} catch (error) {
|
|
91083
|
-
this.ctx.output?.error(`Message stored, but wake event failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
91084
|
-
}
|
|
91521
|
+
async inboxService() {
|
|
91522
|
+
return new InboxService({ getDb: () => this.ctx.getDb() });
|
|
91085
91523
|
}
|
|
91086
91524
|
emitTaskAssignedEvent(payload) {
|
|
91087
91525
|
const bus = this.ctx.eventBus;
|
|
@@ -91102,16 +91540,13 @@ class AgentCoordinationService {
|
|
|
91102
91540
|
return null;
|
|
91103
91541
|
}
|
|
91104
91542
|
}
|
|
91105
|
-
async inboxRecentDao() {
|
|
91106
|
-
const db = await this.ctx.getDb();
|
|
91107
|
-
return new InboxRecentDao(db);
|
|
91108
|
-
}
|
|
91109
91543
|
}
|
|
91110
91544
|
var MessageNotFoundError;
|
|
91111
91545
|
var init_agent_coordination_service = __esm(() => {
|
|
91112
91546
|
init_src2();
|
|
91113
91547
|
init_dist9();
|
|
91114
91548
|
init_fleet_service();
|
|
91549
|
+
init_inbox_service();
|
|
91115
91550
|
MessageNotFoundError = class MessageNotFoundError extends Error {
|
|
91116
91551
|
};
|
|
91117
91552
|
});
|
|
@@ -93106,9 +93541,10 @@ async function dispatchToFleet(input, deps) {
|
|
|
93106
93541
|
try {
|
|
93107
93542
|
({ messageId } = await deps.dispatcher.enqueue({
|
|
93108
93543
|
member: member.instanceId,
|
|
93109
|
-
fromId:
|
|
93544
|
+
fromId: "spur",
|
|
93110
93545
|
body,
|
|
93111
|
-
requestKey: `${input.runId}/${input.state}
|
|
93546
|
+
requestKey: `${input.runId}/${input.state}`,
|
|
93547
|
+
parentRunId: input.runId
|
|
93112
93548
|
}));
|
|
93113
93549
|
} catch (error) {
|
|
93114
93550
|
return {
|
|
@@ -103549,64 +103985,58 @@ class DeliveryReconciler {
|
|
|
103549
103985
|
constructor(ctx) {
|
|
103550
103986
|
this.ctx = ctx;
|
|
103551
103987
|
}
|
|
103552
|
-
async
|
|
103553
|
-
|
|
103554
|
-
|
|
103555
|
-
|
|
103988
|
+
async inbox() {
|
|
103989
|
+
return new InboxService({ getDb: () => this.ctx.getDb() });
|
|
103990
|
+
}
|
|
103991
|
+
async classify(agentId, rows) {
|
|
103992
|
+
const requests = rows ?? await (await this.inbox()).requests(agentId);
|
|
103556
103993
|
const unresolved = [];
|
|
103557
|
-
for (const row of
|
|
103994
|
+
for (const row of requests) {
|
|
103558
103995
|
const base = {
|
|
103559
103996
|
messageId: row.id,
|
|
103560
|
-
toId: row.
|
|
103561
|
-
injectAttempts: row.
|
|
103562
|
-
requestKey: row.
|
|
103563
|
-
...row.
|
|
103997
|
+
toId: row.toId,
|
|
103998
|
+
injectAttempts: row.attempts,
|
|
103999
|
+
requestKey: row.requestKey,
|
|
104000
|
+
...row.error !== null && row.error !== "" ? { injectError: row.error } : {}
|
|
103564
104001
|
};
|
|
103565
104002
|
if (row.status === "failed") {
|
|
103566
104003
|
unresolved.push({
|
|
103567
104004
|
...base,
|
|
103568
|
-
reason: row.
|
|
104005
|
+
reason: row.attempts >= MAX_DELIVERY_ATTEMPTS ? "attempts-exhausted" : "delivery-failed",
|
|
103569
104006
|
artifacts: []
|
|
103570
104007
|
});
|
|
103571
104008
|
continue;
|
|
103572
104009
|
}
|
|
103573
|
-
if (row.status === "
|
|
103574
|
-
|
|
103575
|
-
if (runRow === undefined) {
|
|
104010
|
+
if (row.status === "claimed" || row.status === "delivered") {
|
|
104011
|
+
if (row.runId === null) {
|
|
103576
104012
|
unresolved.push({ ...base, reason: "outcome-unknown", artifacts: [] });
|
|
103577
104013
|
continue;
|
|
103578
104014
|
}
|
|
103579
|
-
const completed =
|
|
103580
|
-
if (completed &&
|
|
104015
|
+
const completed = row.runStatus !== "running";
|
|
104016
|
+
if (completed && row.runOutcome === "verified")
|
|
103581
104017
|
continue;
|
|
103582
104018
|
unresolved.push({
|
|
103583
104019
|
...base,
|
|
103584
|
-
reason: !completed ? "outcome-unknown" :
|
|
103585
|
-
runId:
|
|
103586
|
-
runStatus:
|
|
103587
|
-
...
|
|
103588
|
-
artifacts: parseArtifactRefs2(
|
|
104020
|
+
reason: !completed ? "outcome-unknown" : row.runOutcome === "errored" ? "delivery-failed" : row.runOutcome === "run-exit-only" ? "run-exit-only" : "outcome-unknown",
|
|
104021
|
+
runId: row.runId,
|
|
104022
|
+
...row.runStatus !== null ? { runStatus: row.runStatus } : {},
|
|
104023
|
+
...row.runTaskId !== null ? { taskId: row.runTaskId } : {},
|
|
104024
|
+
artifacts: parseArtifactRefs2(row.runArtifactRefsJson)
|
|
103589
104025
|
});
|
|
103590
104026
|
}
|
|
103591
104027
|
}
|
|
103592
104028
|
return unresolved;
|
|
103593
104029
|
}
|
|
103594
104030
|
async reconcile(agentId) {
|
|
103595
|
-
const
|
|
103596
|
-
const
|
|
103597
|
-
const
|
|
103598
|
-
|
|
103599
|
-
for (const row of rows) {
|
|
103600
|
-
if (row.status === "queued" && row.inject_attempts >= MAX_INJECT_ATTEMPTS) {
|
|
103601
|
-
await inbox.markFailed(row.id, `attempts exhausted after ${MAX_INJECT_ATTEMPTS} deliveries`);
|
|
103602
|
-
report.exhausted.push(row.id);
|
|
103603
|
-
}
|
|
103604
|
-
}
|
|
103605
|
-
report.unresolved = await this.classify(agentId);
|
|
103606
|
-
return report;
|
|
104031
|
+
const service = await this.inbox();
|
|
104032
|
+
const swept = await service.sweep();
|
|
104033
|
+
const rows = await service.requests(agentId);
|
|
104034
|
+
return { unresolved: await this.classify(agentId, rows), exhausted: swept.failed, scanned: rows.length };
|
|
103607
104035
|
}
|
|
103608
104036
|
}
|
|
103609
104037
|
function parseArtifactRefs2(artifactRefsJson) {
|
|
104038
|
+
if (artifactRefsJson === null)
|
|
104039
|
+
return [];
|
|
103610
104040
|
try {
|
|
103611
104041
|
const parsed = JSON.parse(artifactRefsJson);
|
|
103612
104042
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -103614,9 +104044,8 @@ function parseArtifactRefs2(artifactRefsJson) {
|
|
|
103614
104044
|
return [];
|
|
103615
104045
|
}
|
|
103616
104046
|
}
|
|
103617
|
-
var MAX_INJECT_ATTEMPTS = 3;
|
|
103618
104047
|
var init_delivery_reconciler = __esm(() => {
|
|
103619
|
-
|
|
104048
|
+
init_inbox_service();
|
|
103620
104049
|
});
|
|
103621
104050
|
|
|
103622
104051
|
// ../../packages/app/src/services/execution-policy.ts
|
|
@@ -104683,7 +105112,11 @@ class FleetDispatcher {
|
|
|
104683
105112
|
this.sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
104684
105113
|
}
|
|
104685
105114
|
async enqueue(req) {
|
|
104686
|
-
const sent = await this.deps.coordination.sendMessage(req.fromId, req.member, req.body, undefined, req.requestKey
|
|
105115
|
+
const sent = await this.deps.coordination.sendMessage(req.fromId, req.member, req.body, undefined, req.requestKey, {
|
|
105116
|
+
kind: "request",
|
|
105117
|
+
...req.taskId !== undefined ? { taskId: req.taskId } : {},
|
|
105118
|
+
...req.parentRunId !== undefined ? { parentRunId: req.parentRunId } : {}
|
|
105119
|
+
});
|
|
104687
105120
|
return { messageId: sent.msgId, replayed: sent.replayed === true };
|
|
104688
105121
|
}
|
|
104689
105122
|
async receipt(messageId, member) {
|
|
@@ -104781,6 +105214,13 @@ class FleetGuestService {
|
|
|
104781
105214
|
const joined = new Set(guests.map((g) => g.id));
|
|
104782
105215
|
const requested = input.id?.trim();
|
|
104783
105216
|
const id = requested !== undefined && requested !== "" ? requested : this.nextGuestId(role, declared, joined);
|
|
105217
|
+
if (isReservedInboxId(id)) {
|
|
105218
|
+
return {
|
|
105219
|
+
ok: false,
|
|
105220
|
+
code: "collision",
|
|
105221
|
+
message: `guest id "${id}" is a reserved inbox identity \u2014 pass a free --id`
|
|
105222
|
+
};
|
|
105223
|
+
}
|
|
104784
105224
|
const own = guests.find((g) => g.id === id);
|
|
104785
105225
|
if (own !== undefined && own.sessionId !== null && own.sessionId === (input.sessionId ?? null)) {
|
|
104786
105226
|
if (await this.heartbeat(id))
|
|
@@ -104854,7 +105294,10 @@ class FleetGuestService {
|
|
|
104854
105294
|
return expired;
|
|
104855
105295
|
}
|
|
104856
105296
|
async pendingCount(id) {
|
|
104857
|
-
return await
|
|
105297
|
+
return await this.inbox().countQueued(id);
|
|
105298
|
+
}
|
|
105299
|
+
inbox() {
|
|
105300
|
+
return new InboxService({ getDb: () => this.ctx.getDb() });
|
|
104858
105301
|
}
|
|
104859
105302
|
async retire(record) {
|
|
104860
105303
|
const db = await this.ctx.getDb();
|
|
@@ -104862,10 +105305,9 @@ class FleetGuestService {
|
|
|
104862
105305
|
if (claim !== null) {
|
|
104863
105306
|
await new ProjectClaimDao(db).release(this.ctx.cwd, this.slot(record.id), record.id, claim.ownerEpoch);
|
|
104864
105307
|
}
|
|
104865
|
-
const
|
|
104866
|
-
const claimed = unfinished.filter((row) => row.status === "injected").map((row) => row.id);
|
|
105308
|
+
const claimed = (await this.inbox().list(record.id, "in", { limit: 500 })).messages.filter((row) => row.status === "claimed").map((row) => row.id);
|
|
104867
105309
|
if (claimed.length > 0)
|
|
104868
|
-
await
|
|
105310
|
+
await this.inbox().release(claimed);
|
|
104869
105311
|
if (record.runId !== "") {
|
|
104870
105312
|
await new CoordinationRunDao(db).updateExit(record.runId, "exited", new Date().toISOString(), "[]", {
|
|
104871
105313
|
messageIds: [],
|
|
@@ -104894,6 +105336,7 @@ class FleetGuestService {
|
|
|
104894
105336
|
var GUEST_LEASE_TTL_MS, GUEST_ROLES, GUEST_RECORD_DIR, GUEST_CLAIM_PREFIX = "guest:";
|
|
104895
105337
|
var init_fleet_guest_service = __esm(() => {
|
|
104896
105338
|
init_src2();
|
|
105339
|
+
init_inbox_service();
|
|
104897
105340
|
GUEST_LEASE_TTL_MS = CLAIM_TTL_MS;
|
|
104898
105341
|
GUEST_ROLES = ["scribe", "coder", "reviewer", "planner"];
|
|
104899
105342
|
GUEST_RECORD_DIR = join44(".spur", "run", "guests");
|
|
@@ -108041,13 +108484,13 @@ class JobWorkerService {
|
|
|
108041
108484
|
|
|
108042
108485
|
// ../../packages/app/src/services/occupant-wait.ts
|
|
108043
108486
|
function projectLifecycle(input) {
|
|
108044
|
-
const { latestInvokeEvent,
|
|
108487
|
+
const { latestInvokeEvent, queuedCount } = input;
|
|
108045
108488
|
if (latestInvokeEvent === null)
|
|
108046
108489
|
return "unknown";
|
|
108047
108490
|
if (latestInvokeEvent.eventName === "agent.invoke.start")
|
|
108048
108491
|
return "working";
|
|
108049
108492
|
if (latestInvokeEvent.eventName === "agent.invoke.exit") {
|
|
108050
|
-
return
|
|
108493
|
+
return queuedCount === 0 ? "idle" : "unknown";
|
|
108051
108494
|
}
|
|
108052
108495
|
return "unknown";
|
|
108053
108496
|
}
|
|
@@ -108072,10 +108515,10 @@ async function snapshotOccupant(deps, specId, opts) {
|
|
|
108072
108515
|
if (opts.runId !== undefined && opts.runId !== occupant.runId) {}
|
|
108073
108516
|
const pin = { specId, runId, generation: occupant.generation };
|
|
108074
108517
|
const latest = await deps.latestInvokeEvent(runId);
|
|
108075
|
-
const
|
|
108518
|
+
const queuedCount = await deps.countQueued(specId);
|
|
108076
108519
|
return {
|
|
108077
108520
|
pin,
|
|
108078
|
-
lifecycle: projectLifecycle({ latestInvokeEvent: latest,
|
|
108521
|
+
lifecycle: projectLifecycle({ latestInvokeEvent: latest, queuedCount }),
|
|
108079
108522
|
startClock: deps.now(),
|
|
108080
108523
|
latestInvoke: latest
|
|
108081
108524
|
};
|
|
@@ -108126,8 +108569,8 @@ async function waitForOccupant(deps, opts) {
|
|
|
108126
108569
|
if (occupant.generation > pin.generation) {
|
|
108127
108570
|
throw new WaitError("run_replaced", `generation ${pin.generation} \u2192 ${occupant.generation}`);
|
|
108128
108571
|
}
|
|
108129
|
-
const
|
|
108130
|
-
const lifecycle = projectLifecycle({ latestInvokeEvent: latestInvoke,
|
|
108572
|
+
const queuedCount = await deps.countQueued(pin.specId);
|
|
108573
|
+
const lifecycle = projectLifecycle({ latestInvokeEvent: latestInvoke, queuedCount });
|
|
108131
108574
|
if (until === "invoke-exit") {
|
|
108132
108575
|
if (latestInvoke !== null && latestInvoke.eventName === "agent.invoke.exit") {
|
|
108133
108576
|
return { pin, satisfied: until };
|
|
@@ -110581,18 +111024,6 @@ import { randomUUID as randomUUID7 } from "crypto";
|
|
|
110581
111024
|
function fleetTaskKeyPrefix(wbs) {
|
|
110582
111025
|
return `${FLEET_TASK_KEY_PREFIX}${wbs}:`;
|
|
110583
111026
|
}
|
|
110584
|
-
function parseFleetTaskAttempt(requestKey, wbs) {
|
|
110585
|
-
const prefix = fleetTaskKeyPrefix(wbs);
|
|
110586
|
-
if (!requestKey.startsWith(prefix))
|
|
110587
|
-
return null;
|
|
110588
|
-
const suffix = requestKey.slice(prefix.length);
|
|
110589
|
-
if (!/^[1-9][0-9]*$/.test(suffix))
|
|
110590
|
-
return null;
|
|
110591
|
-
return Number.parseInt(suffix, 10);
|
|
110592
|
-
}
|
|
110593
|
-
function fleetTaskWbs(requestKey) {
|
|
110594
|
-
return /^fleet:task:([^:]+):[1-9][0-9]*$/.exec(requestKey ?? "")?.[1];
|
|
110595
|
-
}
|
|
110596
111027
|
|
|
110597
111028
|
class StrategyRuntime {
|
|
110598
111029
|
ctx;
|
|
@@ -110640,6 +111071,15 @@ class StrategyRuntime {
|
|
|
110640
111071
|
for (const member of (await this.ctx.fleet.resolve(normalized)).members) {
|
|
110641
111072
|
unresolved.push(...options.readOnly ? await reconciler.classify(member.instanceId) : (await reconciler.reconcile(member.instanceId)).unresolved);
|
|
110642
111073
|
}
|
|
111074
|
+
if (!options.readOnly && this.ctx.guestExpiry !== undefined) {
|
|
111075
|
+
try {
|
|
111076
|
+
await this.ctx.guestExpiry();
|
|
111077
|
+
} catch (error) {
|
|
111078
|
+
this.ctx.logger?.warn("strategy resume: guest expiry failed", {
|
|
111079
|
+
error: error instanceof Error ? error.message : String(error)
|
|
111080
|
+
});
|
|
111081
|
+
}
|
|
111082
|
+
}
|
|
110643
111083
|
return { strategy, version, orchestrator, unresolved, reconciled: true };
|
|
110644
111084
|
}
|
|
110645
111085
|
async tick(projectPath, opts) {
|
|
@@ -110662,7 +111102,7 @@ class StrategyRuntime {
|
|
|
110662
111102
|
};
|
|
110663
111103
|
}
|
|
110664
111104
|
const db = await this.ctx.openDb(normalized);
|
|
110665
|
-
const inbox = new
|
|
111105
|
+
const inbox = new InboxService({ getDb: async () => db });
|
|
110666
111106
|
const slots = new WriteSlotService(this.ctx);
|
|
110667
111107
|
const dispatched = [];
|
|
110668
111108
|
for (const decision of result.decisions) {
|
|
@@ -110685,7 +111125,8 @@ class StrategyRuntime {
|
|
|
110685
111125
|
member: decision.instanceId,
|
|
110686
111126
|
fromId: opts.orchestratorId,
|
|
110687
111127
|
body: `/sp:dev-run ${wbs} --auto${resumeHint ? " --continue" : ""}`,
|
|
110688
|
-
requestKey: `${fleetTaskKeyPrefix(wbs)}${attempt}
|
|
111128
|
+
requestKey: `${fleetTaskKeyPrefix(wbs)}${attempt}`,
|
|
111129
|
+
taskId: wbs
|
|
110689
111130
|
});
|
|
110690
111131
|
dispatched.push(decision);
|
|
110691
111132
|
this.startHeartbeat(normalized, decision.instanceId, opts.ownerEpoch);
|
|
@@ -110722,8 +111163,8 @@ class StrategyRuntime {
|
|
|
110722
111163
|
const dispatcher = this.ctx.dispatcher;
|
|
110723
111164
|
if (dispatcher === undefined)
|
|
110724
111165
|
return { released: [] };
|
|
110725
|
-
const rows = await new
|
|
110726
|
-
const pending = rows.find((row) =>
|
|
111166
|
+
const rows = await new InboxService({ getDb: async () => db }).requests(holder.holderId);
|
|
111167
|
+
const pending = rows.find((row) => row.taskId !== null);
|
|
110727
111168
|
if (pending === undefined)
|
|
110728
111169
|
return { released: [] };
|
|
110729
111170
|
const receipt = await dispatcher.receipt(pending.id, holder.holderId);
|
|
@@ -110732,7 +111173,7 @@ class StrategyRuntime {
|
|
|
110732
111173
|
this.stopHeartbeat(normalized);
|
|
110733
111174
|
const slots = new WriteSlotService(this.ctx);
|
|
110734
111175
|
await slots.validateResult(normalized, holder.holderId, holder.ownerEpoch, {
|
|
110735
|
-
taskId:
|
|
111176
|
+
taskId: pending.taskId ?? undefined
|
|
110736
111177
|
});
|
|
110737
111178
|
const released = await slots.release(normalized, holder.holderId, holder.ownerEpoch);
|
|
110738
111179
|
return { released: released ? [holder.holderId] : [] };
|
|
@@ -110806,7 +111247,7 @@ class StrategyRuntime {
|
|
|
110806
111247
|
idleInstances.push(member);
|
|
110807
111248
|
}
|
|
110808
111249
|
}
|
|
110809
|
-
const inbox = new
|
|
111250
|
+
const inbox = new InboxService({ getDb: async () => db });
|
|
110810
111251
|
const memberLifecycle = new Map;
|
|
110811
111252
|
if (idleInstances.length > 0) {
|
|
110812
111253
|
try {
|
|
@@ -110823,7 +111264,7 @@ class StrategyRuntime {
|
|
|
110823
111264
|
const latest = attempts.latest;
|
|
110824
111265
|
let state;
|
|
110825
111266
|
if (latest !== null) {
|
|
110826
|
-
const receipt = await this.ctx.dispatcher?.receipt(latest.row.id, latest.row.
|
|
111267
|
+
const receipt = await this.ctx.dispatcher?.receipt(latest.row.id, latest.row.toId);
|
|
110827
111268
|
state = {
|
|
110828
111269
|
attempt: latest.attempt,
|
|
110829
111270
|
...receipt !== null && receipt !== undefined && receipt.status !== "outcome-unknown" ? { receipt: receipt.status } : {}
|
|
@@ -110854,22 +111295,18 @@ class StrategyRuntime {
|
|
|
110854
111295
|
return { name, result, statusByWbs };
|
|
110855
111296
|
}
|
|
110856
111297
|
async readAttempts(inbox, wbs) {
|
|
110857
|
-
const
|
|
110858
|
-
|
|
110859
|
-
|
|
110860
|
-
|
|
110861
|
-
|
|
110862
|
-
continue;
|
|
110863
|
-
if (latest === null || attempt > latest.attempt)
|
|
110864
|
-
latest = { row, attempt };
|
|
110865
|
-
}
|
|
110866
|
-
return { count: rows.length, latest };
|
|
111298
|
+
const attempts = await inbox.attempts(wbs);
|
|
111299
|
+
return {
|
|
111300
|
+
count: attempts.count,
|
|
111301
|
+
latest: attempts.latest === null ? null : { row: attempts.latest, attempt: attempts.count }
|
|
111302
|
+
};
|
|
110867
111303
|
}
|
|
110868
111304
|
}
|
|
110869
111305
|
var DEFAULT_STRATEGY = "rest", FLEET_AUTO_TAG = "fleet:auto", MAX_DISPATCH_ATTEMPTS = 3, MAX_HOLD_DETAIL_IDS = 5, FLEET_TASK_KEY_PREFIX = "fleet:task:", restStrategy, gtdStrategy, STRATEGIES;
|
|
110870
111306
|
var init_strategy_runtime = __esm(() => {
|
|
110871
111307
|
init_src2();
|
|
110872
111308
|
init_delivery_reconciler();
|
|
111309
|
+
init_inbox_service();
|
|
110873
111310
|
init_project_registry();
|
|
110874
111311
|
init_write_slot_service();
|
|
110875
111312
|
restStrategy = {
|
|
@@ -111246,220 +111683,6 @@ var init_supervisor_service = __esm(() => {
|
|
|
111246
111683
|
RESTART_BACKOFF_SCHEDULE = [1000, 2000, 4000, 8000, 16000];
|
|
111247
111684
|
});
|
|
111248
111685
|
|
|
111249
|
-
// ../../packages/app/src/services/system-event-retention.ts
|
|
111250
|
-
function resolveRetentionQuotas(config = {}, prefixes = SYSTEM_EVENT_PREFIXES) {
|
|
111251
|
-
const defaultQuota = config.default ?? DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA;
|
|
111252
|
-
const overrides = config.prefixes ?? {};
|
|
111253
|
-
return prefixes.map((prefix) => ({
|
|
111254
|
-
prefix,
|
|
111255
|
-
quota: overrides[prefix] ?? defaultQuota
|
|
111256
|
-
}));
|
|
111257
|
-
}
|
|
111258
|
-
var DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA = 1e4;
|
|
111259
|
-
var init_system_event_retention = __esm(() => {
|
|
111260
|
-
init_event_names();
|
|
111261
|
-
});
|
|
111262
|
-
|
|
111263
|
-
// ../../packages/app/src/services/system-event-tap.ts
|
|
111264
|
-
function registerSystemEventTap(bus, dao, logger, options = {}) {
|
|
111265
|
-
const handlers = new Map;
|
|
111266
|
-
const inFlight = new Set;
|
|
111267
|
-
const diagnosticEnabled = options.diagnosticEnabled === true;
|
|
111268
|
-
const quotas = resolveRetentionQuotas(options.retention);
|
|
111269
|
-
for (const entry of SYSTEM_EVENT_CATALOG) {
|
|
111270
|
-
if (entry.tier === "diagnostic" && !diagnosticEnabled)
|
|
111271
|
-
continue;
|
|
111272
|
-
const handler = (event) => {
|
|
111273
|
-
const actor = extractSystemEventActor(event);
|
|
111274
|
-
const p = persist(dao, entry.prefix, {
|
|
111275
|
-
id: createId("sev"),
|
|
111276
|
-
event_name: entry.name,
|
|
111277
|
-
occurred_at: new Date().toISOString(),
|
|
111278
|
-
actor,
|
|
111279
|
-
payload_json: safeStringify(buildSystemEventEnvelope(entry, event, options.projectContext ?? systemEventProjectContext(""), options.secretValues, actor)),
|
|
111280
|
-
...extractSystemEventCorrelation(event)
|
|
111281
|
-
}, quotas, logger);
|
|
111282
|
-
inFlight.add(p);
|
|
111283
|
-
p.finally(() => inFlight.delete(p));
|
|
111284
|
-
};
|
|
111285
|
-
handlers.set(entry.name, handler);
|
|
111286
|
-
bus.on(entry.name, handler);
|
|
111287
|
-
}
|
|
111288
|
-
return {
|
|
111289
|
-
unsubscribe: () => {
|
|
111290
|
-
for (const [name, handler] of handlers) {
|
|
111291
|
-
bus.off(name, handler);
|
|
111292
|
-
}
|
|
111293
|
-
handlers.clear();
|
|
111294
|
-
},
|
|
111295
|
-
flush: async () => {
|
|
111296
|
-
while (inFlight.size > 0) {
|
|
111297
|
-
await Promise.allSettled(inFlight);
|
|
111298
|
-
}
|
|
111299
|
-
}
|
|
111300
|
-
};
|
|
111301
|
-
}
|
|
111302
|
-
async function persist(dao, prefix, input, quotas, logger) {
|
|
111303
|
-
try {
|
|
111304
|
-
await dao.insert(input);
|
|
111305
|
-
await dao.pruneQuotas(quotas, prefix);
|
|
111306
|
-
} catch (error) {
|
|
111307
|
-
logger.warn("system_events tap: persist failed", {
|
|
111308
|
-
name: input.event_name,
|
|
111309
|
-
error: stringifyError(error)
|
|
111310
|
-
});
|
|
111311
|
-
}
|
|
111312
|
-
}
|
|
111313
|
-
function safeStringify(event) {
|
|
111314
|
-
try {
|
|
111315
|
-
return JSON.stringify(event ?? null);
|
|
111316
|
-
} catch {
|
|
111317
|
-
return null;
|
|
111318
|
-
}
|
|
111319
|
-
}
|
|
111320
|
-
function extractSystemEventActor(event) {
|
|
111321
|
-
if (event && typeof event === "object") {
|
|
111322
|
-
const obj = event;
|
|
111323
|
-
if (typeof obj.actor === "string" && obj.actor.length > 0)
|
|
111324
|
-
return obj.actor;
|
|
111325
|
-
if (typeof obj.agentId === "string" && obj.agentId.length > 0)
|
|
111326
|
-
return obj.agentId;
|
|
111327
|
-
if (typeof obj.memberId === "string" && obj.memberId.length > 0)
|
|
111328
|
-
return obj.memberId;
|
|
111329
|
-
}
|
|
111330
|
-
return null;
|
|
111331
|
-
}
|
|
111332
|
-
function extractSystemEventCorrelation(event) {
|
|
111333
|
-
const correlation = { run_id: null, entity_kind: null, entity_id: null, sequence: null };
|
|
111334
|
-
if (!event || typeof event !== "object")
|
|
111335
|
-
return correlation;
|
|
111336
|
-
const obj = event;
|
|
111337
|
-
const nested = obj.correlation && typeof obj.correlation === "object" ? obj.correlation : {};
|
|
111338
|
-
const runId = obj.runId ?? obj.run_id ?? nested.runId;
|
|
111339
|
-
if (typeof runId === "string" && runId.length > 0)
|
|
111340
|
-
correlation.run_id = runId;
|
|
111341
|
-
if (typeof obj.sequence === "number" && Number.isFinite(obj.sequence))
|
|
111342
|
-
correlation.sequence = obj.sequence;
|
|
111343
|
-
const entity = obj.entity;
|
|
111344
|
-
if (entity && typeof entity === "object") {
|
|
111345
|
-
const { kind, id } = entity;
|
|
111346
|
-
if (typeof kind === "string" && kind.length > 0)
|
|
111347
|
-
correlation.entity_kind = kind;
|
|
111348
|
-
if (typeof id === "string" && id.length > 0)
|
|
111349
|
-
correlation.entity_id = id;
|
|
111350
|
-
}
|
|
111351
|
-
if (typeof obj.entityKind === "string" && obj.entityKind.length > 0)
|
|
111352
|
-
correlation.entity_kind = obj.entityKind;
|
|
111353
|
-
if (typeof obj.entityId === "string" && obj.entityId.length > 0)
|
|
111354
|
-
correlation.entity_id = obj.entityId;
|
|
111355
|
-
return correlation;
|
|
111356
|
-
}
|
|
111357
|
-
function stringifyError(error) {
|
|
111358
|
-
if (error instanceof Error)
|
|
111359
|
-
return `${error.name}: ${error.message}`;
|
|
111360
|
-
return String(error);
|
|
111361
|
-
}
|
|
111362
|
-
var init_system_event_tap = __esm(() => {
|
|
111363
|
-
init_src2();
|
|
111364
|
-
init_event_names();
|
|
111365
|
-
init_system_event_envelope();
|
|
111366
|
-
init_system_event_retention();
|
|
111367
|
-
});
|
|
111368
|
-
|
|
111369
|
-
// ../../packages/app/src/services/system-event-catch-all.ts
|
|
111370
|
-
function genericSystemEventCatalogEntry(name) {
|
|
111371
|
-
return {
|
|
111372
|
-
name,
|
|
111373
|
-
prefix: name.replace(/\..*/, ""),
|
|
111374
|
-
source: "bus",
|
|
111375
|
-
tier: "default",
|
|
111376
|
-
persisted: true,
|
|
111377
|
-
streamed: true,
|
|
111378
|
-
payloadPolicy: "metadata-only",
|
|
111379
|
-
renderer: GENERIC_SYSTEM_EVENT_RENDERER,
|
|
111380
|
-
producerPackage: "spur",
|
|
111381
|
-
subsystem: "unknown",
|
|
111382
|
-
severity: "info",
|
|
111383
|
-
description: "Uncataloged system event captured by the generic fallback; pending catalog promotion.",
|
|
111384
|
-
metadataFields: [],
|
|
111385
|
-
remediationKind: "none"
|
|
111386
|
-
};
|
|
111387
|
-
}
|
|
111388
|
-
function ensureRetentionQuotaForPrefix(quotas, prefix, fallbackQuota) {
|
|
111389
|
-
return quotas.some((quota) => quota.prefix === prefix) ? quotas : [...quotas, { prefix, quota: fallbackQuota }];
|
|
111390
|
-
}
|
|
111391
|
-
function createSystemEventCatchAllSink(options) {
|
|
111392
|
-
const { dao, logger } = options;
|
|
111393
|
-
const quotas = resolveRetentionQuotas(options.retention);
|
|
111394
|
-
const fallbackQuota = options.retention?.default ?? DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA;
|
|
111395
|
-
const projectContext = options.projectContext ?? systemEventProjectContext("");
|
|
111396
|
-
const secretValues = options.secretValues ?? [];
|
|
111397
|
-
const driftWarned = new Set;
|
|
111398
|
-
return async (name, payload) => {
|
|
111399
|
-
const entry = genericSystemEventCatalogEntry(name);
|
|
111400
|
-
const actor = extractSystemEventActor(payload);
|
|
111401
|
-
try {
|
|
111402
|
-
await dao.insert({
|
|
111403
|
-
id: createId("sev"),
|
|
111404
|
-
event_name: entry.name,
|
|
111405
|
-
occurred_at: new Date().toISOString(),
|
|
111406
|
-
actor,
|
|
111407
|
-
payload_json: safeStringify(buildSystemEventEnvelope(entry, payload, projectContext, secretValues, actor)),
|
|
111408
|
-
...extractSystemEventCorrelation(payload)
|
|
111409
|
-
});
|
|
111410
|
-
await dao.pruneQuotas(ensureRetentionQuotaForPrefix(quotas, entry.prefix, fallbackQuota), entry.prefix);
|
|
111411
|
-
} catch (error) {
|
|
111412
|
-
logger.warn("system_events catch-all: persist failed", {
|
|
111413
|
-
event: name,
|
|
111414
|
-
error: error instanceof Error ? `${error.name}: ${error.message}` : String(error)
|
|
111415
|
-
});
|
|
111416
|
-
return;
|
|
111417
|
-
}
|
|
111418
|
-
if (!driftWarned.has(name)) {
|
|
111419
|
-
driftWarned.add(name);
|
|
111420
|
-
logger.warn(SYSTEM_EVENT_UNCATALOGED_WARN_EVENT, { event: name });
|
|
111421
|
-
}
|
|
111422
|
-
};
|
|
111423
|
-
}
|
|
111424
|
-
function installSystemEventCatchAll(bus, sink) {
|
|
111425
|
-
const holder = bus;
|
|
111426
|
-
const current = holder.emit;
|
|
111427
|
-
if (current[CATCH_ALL_MARKER] === true) {
|
|
111428
|
-
return { installed: false, flush: async () => {} };
|
|
111429
|
-
}
|
|
111430
|
-
const underlying = current.bind(bus);
|
|
111431
|
-
const inFlight = new Set;
|
|
111432
|
-
const wrapped = (event, ...args) => {
|
|
111433
|
-
const result = underlying(event, ...args);
|
|
111434
|
-
if (systemEventCatalogEntry(event) === undefined) {
|
|
111435
|
-
const pending = Promise.resolve().then(() => sink(event, args[0]));
|
|
111436
|
-
inFlight.add(pending);
|
|
111437
|
-
pending.catch(() => {}).finally(() => {
|
|
111438
|
-
inFlight.delete(pending);
|
|
111439
|
-
});
|
|
111440
|
-
}
|
|
111441
|
-
return result;
|
|
111442
|
-
};
|
|
111443
|
-
Object.defineProperty(wrapped, CATCH_ALL_MARKER, { value: true });
|
|
111444
|
-
holder.emit = wrapped;
|
|
111445
|
-
return {
|
|
111446
|
-
installed: true,
|
|
111447
|
-
flush: async () => {
|
|
111448
|
-
while (inFlight.size > 0) {
|
|
111449
|
-
await Promise.allSettled([...inFlight]);
|
|
111450
|
-
}
|
|
111451
|
-
}
|
|
111452
|
-
};
|
|
111453
|
-
}
|
|
111454
|
-
var GENERIC_SYSTEM_EVENT_RENDERER = "generic", SYSTEM_EVENT_UNCATALOGED_WARN_EVENT = "system_events.uncataloged", CATCH_ALL_MARKER = "spur.systemEventCatchAll";
|
|
111455
|
-
var init_system_event_catch_all = __esm(() => {
|
|
111456
|
-
init_src2();
|
|
111457
|
-
init_event_names();
|
|
111458
|
-
init_system_event_envelope();
|
|
111459
|
-
init_system_event_retention();
|
|
111460
|
-
init_system_event_tap();
|
|
111461
|
-
});
|
|
111462
|
-
|
|
111463
111686
|
// ../../packages/app/src/services/system-event-emitter.ts
|
|
111464
111687
|
class SystemEventEmitter {
|
|
111465
111688
|
dao;
|
|
@@ -122382,6 +122605,7 @@ var init_src3 = __esm(() => {
|
|
|
122382
122605
|
init_history_board_service();
|
|
122383
122606
|
init_history_refresh_service();
|
|
122384
122607
|
init_history_service();
|
|
122608
|
+
init_inbox_service();
|
|
122385
122609
|
init_inline_run_attribution();
|
|
122386
122610
|
init_job_exclusion_guard();
|
|
122387
122611
|
init_member_session2();
|
|
@@ -142516,27 +142740,26 @@ async function runAgentLifecycle(action, agentId, options, context) {
|
|
|
142516
142740
|
async function settleClaimedMessages(context, claimed, outcome) {
|
|
142517
142741
|
if (claimed.length === 0)
|
|
142518
142742
|
return;
|
|
142519
|
-
const
|
|
142743
|
+
const inbox = new InboxService({ getDb: () => context.getDb() });
|
|
142520
142744
|
if (outcome === "accepted") {
|
|
142521
|
-
await
|
|
142745
|
+
await inbox.settle(claimed, "accepted");
|
|
142522
142746
|
return;
|
|
142523
142747
|
}
|
|
142524
|
-
const dao = new InboxMessageDao(await context.getDb());
|
|
142525
142748
|
const releasable = [];
|
|
142526
142749
|
const exhausted = [];
|
|
142527
142750
|
for (const id of claimed) {
|
|
142528
|
-
const row = await
|
|
142529
|
-
if (row?.status !== "
|
|
142751
|
+
const row = await inbox.get(id);
|
|
142752
|
+
if (row?.status !== "claimed")
|
|
142530
142753
|
continue;
|
|
142531
|
-
if (row.
|
|
142754
|
+
if (row.attempts >= MAX_DELIVERY_ATTEMPTS)
|
|
142532
142755
|
exhausted.push(id);
|
|
142533
142756
|
else
|
|
142534
142757
|
releasable.push(id);
|
|
142535
142758
|
}
|
|
142536
142759
|
if (releasable.length > 0)
|
|
142537
|
-
await
|
|
142538
|
-
|
|
142539
|
-
await
|
|
142760
|
+
await inbox.release(releasable);
|
|
142761
|
+
if (exhausted.length > 0) {
|
|
142762
|
+
await inbox.settle(exhausted, "not-started", `delivery not accepted: invocation never started after ${MAX_DELIVERY_ATTEMPTS} inject attempts`);
|
|
142540
142763
|
}
|
|
142541
142764
|
}
|
|
142542
142765
|
async function drainIntoPrompt(prompt, context, flags) {
|
|
@@ -142552,21 +142775,21 @@ async function drainIntoPrompt(prompt, context, flags) {
|
|
|
142552
142775
|
const flagsOut = spec === undefined ? flags : { ...flags, "spec-id": spec.id, agent: drainAgentSelector(spec, context) };
|
|
142553
142776
|
if (flags.drain !== true)
|
|
142554
142777
|
return { prompt, flags: flagsOut, claimed: [] };
|
|
142555
|
-
const
|
|
142556
|
-
if (
|
|
142778
|
+
const claimedRows = await new InboxService({ getDb: () => context.getDb() }).claim(recipient, flags.singleMessage === true ? 1 : 100);
|
|
142779
|
+
if (claimedRows.length === 0)
|
|
142557
142780
|
return { prompt, flags: flagsOut, claimed: [] };
|
|
142558
|
-
const header =
|
|
142781
|
+
const header = claimedRows.map((m) => `- ${m.fromId || "operator"}: ${m.body}`).join(`
|
|
142559
142782
|
`);
|
|
142560
142783
|
const block = `Pending messages:
|
|
142561
142784
|
${header}`;
|
|
142562
142785
|
const merged = prompt === undefined ? block : `${block}
|
|
142563
142786
|
|
|
142564
142787
|
${prompt}`;
|
|
142565
|
-
const claimed =
|
|
142566
|
-
const fleetTask =
|
|
142567
|
-
const parentRunId =
|
|
142788
|
+
const claimed = claimedRows.map((m) => m.id);
|
|
142789
|
+
const fleetTask = claimedRows.map((message) => message.taskId ?? /^fleet:task:([^:]+):[1-9][0-9]*$/.exec(message.requestKey ?? "")?.[1]).find((wbs) => wbs !== undefined);
|
|
142790
|
+
const parentRunId = claimedRows.map((message) => message.parentRunId ?? /^([^:/][^/]*)\/[^/]+$/.exec(message.requestKey ?? "")?.[1]).find((runId) => runId !== undefined);
|
|
142568
142791
|
const requestMessage = claimed.join(",");
|
|
142569
|
-
const requestKeys =
|
|
142792
|
+
const requestKeys = claimedRows.map((message) => message.requestKey);
|
|
142570
142793
|
return {
|
|
142571
142794
|
prompt: merged,
|
|
142572
142795
|
flags: {
|
|
@@ -142635,7 +142858,8 @@ async function makeFleetRuntime(context) {
|
|
|
142635
142858
|
const { foldersConfig } = await resolvePlanningFolders(context.fs);
|
|
142636
142859
|
const check = await makeCheckService(context);
|
|
142637
142860
|
return await check.firstBlockingPrerequisite(context.fs.resolve(foldersConfig.active_folder), wbs);
|
|
142638
|
-
}
|
|
142861
|
+
},
|
|
142862
|
+
guestExpiry: async () => await guestService(context).expire()
|
|
142639
142863
|
});
|
|
142640
142864
|
}
|
|
142641
142865
|
async function runAgentLoop(context, flags, runtime = {}, deps) {
|
|
@@ -143277,7 +143501,7 @@ async function runAgentWait(context, specId, options) {
|
|
|
143277
143501
|
return waitUsageError(context, options, "--until blocked has no first-class signal in this wave; use idle|working|invoke-exit");
|
|
143278
143502
|
}
|
|
143279
143503
|
const agentService = context.agentService();
|
|
143280
|
-
const
|
|
143504
|
+
const inbox = new InboxService({ getDb: () => context.getDb() });
|
|
143281
143505
|
const eventDao = new SystemEventDao(await context.getDb());
|
|
143282
143506
|
const controller = new AbortController;
|
|
143283
143507
|
const onSignal = () => controller.abort();
|
|
@@ -143297,7 +143521,7 @@ async function runAgentWait(context, specId, options) {
|
|
|
143297
143521
|
try {
|
|
143298
143522
|
result = await waitForOccupant({
|
|
143299
143523
|
getOccupant: (id) => agentService.getOccupant({ specId: id }),
|
|
143300
|
-
|
|
143524
|
+
countQueued: (id) => inbox.countQueued(id),
|
|
143301
143525
|
latestInvokeEvent: (r) => readLatestInvokeEvent(eventDao, r),
|
|
143302
143526
|
follow: (afterSequence) => followSystemEventsAfter(context.getDb, {
|
|
143303
143527
|
afterSequence,
|
|
@@ -146887,7 +147111,7 @@ import { createRequire as createRequire3 } from "module";
|
|
|
146887
147111
|
var CLI_CONFIG = {
|
|
146888
147112
|
binaryName: "spur",
|
|
146889
147113
|
binaryLabel: "spur",
|
|
146890
|
-
binaryVersion: "0.4.
|
|
147114
|
+
binaryVersion: "0.4.19",
|
|
146891
147115
|
configDir: ".spur",
|
|
146892
147116
|
configFile: ".spur/config.yaml",
|
|
146893
147117
|
databaseFile: ".spur/spur.db"
|
|
@@ -147822,13 +148046,231 @@ function formatMaintenanceResult(r) {
|
|
|
147822
148046
|
`);
|
|
147823
148047
|
}
|
|
147824
148048
|
|
|
147825
|
-
// src/commands/message.ts
|
|
148049
|
+
// src/commands/message/inbox.ts
|
|
148050
|
+
init_src3();
|
|
148051
|
+
|
|
148052
|
+
// src/commands/message/shared.ts
|
|
147826
148053
|
init_src3();
|
|
147827
|
-
init_src2();
|
|
147828
148054
|
var DEFAULT_FROM = "operator";
|
|
147829
148055
|
var DEFAULT_WATCH_INTERVAL_MS = 2000;
|
|
147830
|
-
function
|
|
147831
|
-
|
|
148056
|
+
function inboxOf(context) {
|
|
148057
|
+
return new InboxService({ getDb: () => context.getDb() });
|
|
148058
|
+
}
|
|
148059
|
+
async function resolveOrchestratorAddress(context) {
|
|
148060
|
+
let fleet;
|
|
148061
|
+
try {
|
|
148062
|
+
fleet = new FleetService({
|
|
148063
|
+
fs: context.fs,
|
|
148064
|
+
spurConfig: await context.loadAgentConfig(context.cwd),
|
|
148065
|
+
roles: context.agentRoles,
|
|
148066
|
+
openDb: () => context.getDb()
|
|
148067
|
+
});
|
|
148068
|
+
} catch (error) {
|
|
148069
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148070
|
+
throw new Error(`message send could not load the project config: ${message}`);
|
|
148071
|
+
}
|
|
148072
|
+
const { state, instanceId } = await fleet.resolveOrchestrator(normalizeProjectPath(context.cwd));
|
|
148073
|
+
if (state === "missing" || state === "unresolvable")
|
|
148074
|
+
return null;
|
|
148075
|
+
return typeof instanceId === "string" && instanceId !== "" ? instanceId : null;
|
|
148076
|
+
}
|
|
148077
|
+
function formatInboxLine(entry) {
|
|
148078
|
+
const from = entry.fromId || DEFAULT_FROM;
|
|
148079
|
+
const body = entry.body.length > 60 ? `${entry.body.slice(0, 57)}...` : entry.body;
|
|
148080
|
+
return `${entry.id} ${entry.status} ${from} ${body} ${new Date(entry.createdAt).toISOString()}`;
|
|
148081
|
+
}
|
|
148082
|
+
function widenInboxRow(entry, hold) {
|
|
148083
|
+
return {
|
|
148084
|
+
...entry,
|
|
148085
|
+
injectAttempts: entry.attempts,
|
|
148086
|
+
injectError: entry.error ?? null,
|
|
148087
|
+
...hold?.reason !== undefined ? { reason: hold.reason } : {},
|
|
148088
|
+
...hold?.runId !== undefined ? { runId: hold.runId } : {},
|
|
148089
|
+
taskId: hold?.taskId ?? null,
|
|
148090
|
+
...hold?.runStatus !== undefined ? { runStatus: hold.runStatus } : {},
|
|
148091
|
+
artifacts: hold?.artifacts ?? []
|
|
148092
|
+
};
|
|
148093
|
+
}
|
|
148094
|
+
async function waitForMessageClaim(context, msgId, agentService, pin, timeoutMs, signal) {
|
|
148095
|
+
const start = Date.now();
|
|
148096
|
+
const deadline = timeoutMs === undefined ? Number.POSITIVE_INFINITY : start + timeoutMs;
|
|
148097
|
+
const stallMs = Math.min(timeoutMs ?? DEFAULT_STALL_MS, DEFAULT_STALL_MS);
|
|
148098
|
+
let lastPending = -1;
|
|
148099
|
+
for (;; ) {
|
|
148100
|
+
if (signal?.aborted)
|
|
148101
|
+
throw new WaitError("timeout", "wait aborted");
|
|
148102
|
+
const clock = Date.now();
|
|
148103
|
+
if (clock >= deadline)
|
|
148104
|
+
throw new WaitError("timeout", `wait timed out after ${timeoutMs}ms`);
|
|
148105
|
+
const occupant = await agentService.getOccupant({ specId: pin.specId });
|
|
148106
|
+
if (occupant === null)
|
|
148107
|
+
throw new WaitError("occupant_gone", `occupant for specId "${pin.specId}" is gone`);
|
|
148108
|
+
if (occupant.runId !== pin.runId)
|
|
148109
|
+
throw new WaitError("run_replaced", `run ${pin.runId} replaced`);
|
|
148110
|
+
if (occupant.generation > pin.generation)
|
|
148111
|
+
throw new WaitError("run_replaced", `generation bumped`);
|
|
148112
|
+
const row = await inboxOf(context).get(msgId);
|
|
148113
|
+
if (row === null)
|
|
148114
|
+
throw new WaitError("occupant_gone", `message ${msgId} disappeared`);
|
|
148115
|
+
const pending = row.status === "queued" ? 1 : 0;
|
|
148116
|
+
const progressed = lastPending >= 0 && pending !== lastPending;
|
|
148117
|
+
if (pending === 0)
|
|
148118
|
+
return;
|
|
148119
|
+
if (lastPending >= 0 && !progressed && clock - start >= stallMs) {
|
|
148120
|
+
throw new WaitError("wait_stalled", `inbox not draining within ${stallMs}ms`);
|
|
148121
|
+
}
|
|
148122
|
+
lastPending = pending;
|
|
148123
|
+
await sendSleep(100, signal);
|
|
148124
|
+
}
|
|
148125
|
+
}
|
|
148126
|
+
function sendSleep(ms, signal) {
|
|
148127
|
+
return new Promise((resolve) => {
|
|
148128
|
+
if (signal?.aborted) {
|
|
148129
|
+
resolve();
|
|
148130
|
+
return;
|
|
148131
|
+
}
|
|
148132
|
+
const timer = setTimeout(resolve, ms);
|
|
148133
|
+
signal?.addEventListener("abort", () => {
|
|
148134
|
+
clearTimeout(timer);
|
|
148135
|
+
resolve();
|
|
148136
|
+
}, { once: true });
|
|
148137
|
+
});
|
|
148138
|
+
}
|
|
148139
|
+
function parseInterval2(raw) {
|
|
148140
|
+
const n = Number.parseInt(raw, 10);
|
|
148141
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
148142
|
+
return null;
|
|
148143
|
+
return n;
|
|
148144
|
+
}
|
|
148145
|
+
function parseTimeout2(raw) {
|
|
148146
|
+
if (raw === undefined || typeof raw === "boolean")
|
|
148147
|
+
return;
|
|
148148
|
+
const n = Number.parseInt(raw, 10);
|
|
148149
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
148150
|
+
throw new Error(`invalid --timeout "${raw}" (expected a positive integer ms)`);
|
|
148151
|
+
}
|
|
148152
|
+
return n;
|
|
148153
|
+
}
|
|
148154
|
+
function sendWaitFail(context, options, code, message) {
|
|
148155
|
+
if (options.json) {
|
|
148156
|
+
context.output.write(toEnvelopeJson({ error: { code, message } }, {
|
|
148157
|
+
enveloped: options.jsonEnvelope,
|
|
148158
|
+
error: { code: "INTERNAL_ERROR", message, details: { cliCode: code } }
|
|
148159
|
+
}));
|
|
148160
|
+
} else {
|
|
148161
|
+
context.output.error(`${code}: ${message}`);
|
|
148162
|
+
}
|
|
148163
|
+
return 1;
|
|
148164
|
+
}
|
|
148165
|
+
async function readLatestInvokeEvent2(dao, runId) {
|
|
148166
|
+
const rows = await dao.query({
|
|
148167
|
+
run_id: runId,
|
|
148168
|
+
names: ["agent.invoke.start", "agent.invoke.exit"],
|
|
148169
|
+
limit: 1
|
|
148170
|
+
});
|
|
148171
|
+
const row = rows[0];
|
|
148172
|
+
if (row === undefined)
|
|
148173
|
+
return null;
|
|
148174
|
+
return { eventName: row.event_name, sequence: row.sequence };
|
|
148175
|
+
}
|
|
148176
|
+
|
|
148177
|
+
// src/commands/message/inbox.ts
|
|
148178
|
+
function registerMessageInboxCommand(noun, context) {
|
|
148179
|
+
noun.command("inbox").description("List messages addressed to an agent.").requiredOption("--agent <id>", "Agent id").option("--unresolved", "Only messages the 0834 reconciler holds (delivery-failed | attempts-exhausted | outcome-unknown | run-exit-only)").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
|
|
148180
|
+
const code = await runMessageInbox(context, options);
|
|
148181
|
+
context.setExitCode(code);
|
|
148182
|
+
});
|
|
148183
|
+
}
|
|
148184
|
+
async function runMessageInbox(context, options) {
|
|
148185
|
+
const classify = options.json === true || options.unresolved === true;
|
|
148186
|
+
let holds = new Map;
|
|
148187
|
+
if (classify) {
|
|
148188
|
+
const reconciler = new DeliveryReconciler(context);
|
|
148189
|
+
holds = new Map((await reconciler.classify(options.agent)).map((u) => [u.messageId, u]));
|
|
148190
|
+
}
|
|
148191
|
+
const page = await inboxOf(context).list(options.agent, "in");
|
|
148192
|
+
const messages = options.unresolved === true ? page.messages.filter((m) => holds.has(m.id)) : page.messages;
|
|
148193
|
+
if (options.json) {
|
|
148194
|
+
context.output.write(toEnvelopeJson({
|
|
148195
|
+
messages: messages.map((m) => widenInboxRow(m, holds.get(m.id))),
|
|
148196
|
+
count: messages.length
|
|
148197
|
+
}, { enveloped: options.jsonEnvelope }));
|
|
148198
|
+
return 0;
|
|
148199
|
+
}
|
|
148200
|
+
if (messages.length === 0) {
|
|
148201
|
+
context.output.write(`No messages for ${options.agent}`);
|
|
148202
|
+
return 0;
|
|
148203
|
+
}
|
|
148204
|
+
context.output.write(messages.map(formatInboxLine).join(`
|
|
148205
|
+
`));
|
|
148206
|
+
return 0;
|
|
148207
|
+
}
|
|
148208
|
+
|
|
148209
|
+
// src/commands/message/read.ts
|
|
148210
|
+
init_src3();
|
|
148211
|
+
function registerMessageReadCommand(noun, context) {
|
|
148212
|
+
noun.command("read").description("Mark a message read by its recipient (the operator by default).").argument("<msg-id>", "Message id").option("--reader <id>", "Reader address", "operator").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (msgId, options) => {
|
|
148213
|
+
const jsonEnvelope = options.jsonEnvelope;
|
|
148214
|
+
context.setExitCode(await runMessageRead(context, msgId, { ...options, jsonEnvelope }));
|
|
148215
|
+
});
|
|
148216
|
+
}
|
|
148217
|
+
async function runMessageRead(context, msgId, options) {
|
|
148218
|
+
try {
|
|
148219
|
+
const receipt = await inboxOf(context).markRead(msgId, options.reader);
|
|
148220
|
+
if (options.json) {
|
|
148221
|
+
context.output.write(toEnvelopeJson(receipt, { enveloped: options.jsonEnvelope }));
|
|
148222
|
+
} else {
|
|
148223
|
+
context.output.write(receipt.transitioned ? `read ${msgId} by ${options.reader}` : `${msgId} was already read by ${options.reader}`);
|
|
148224
|
+
}
|
|
148225
|
+
return 0;
|
|
148226
|
+
} catch (error) {
|
|
148227
|
+
if (!(error instanceof MessageNotFoundError))
|
|
148228
|
+
throw error;
|
|
148229
|
+
writeJsonError(context.output, options, `message ${msgId} not found for ${options.reader}`, "VALIDATION_FAILED");
|
|
148230
|
+
return 1;
|
|
148231
|
+
}
|
|
148232
|
+
}
|
|
148233
|
+
|
|
148234
|
+
// src/commands/message/reply.ts
|
|
148235
|
+
function registerMessageReplyCommand(noun, context) {
|
|
148236
|
+
noun.command("reply").description("Thread a reply to a message.").argument("<msg-id>", "Message id to reply to").argument("<body>", "Reply body").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (msgId, body, options) => {
|
|
148237
|
+
const code = await runMessageReply(context, msgId, body, options);
|
|
148238
|
+
context.setExitCode(code);
|
|
148239
|
+
});
|
|
148240
|
+
}
|
|
148241
|
+
async function runMessageReply(context, msgId, body, options) {
|
|
148242
|
+
const trimmed = body.trim();
|
|
148243
|
+
if (trimmed === "") {
|
|
148244
|
+
writeJsonError(context.output, options, "message reply requires a non-empty body", "VALIDATION_FAILED");
|
|
148245
|
+
return 2;
|
|
148246
|
+
}
|
|
148247
|
+
const inbox = inboxOf(context);
|
|
148248
|
+
const original = await inbox.get(msgId);
|
|
148249
|
+
if (original === null) {
|
|
148250
|
+
writeJsonError(context.output, options, `message ${msgId} not found`, "VALIDATION_FAILED");
|
|
148251
|
+
return 1;
|
|
148252
|
+
}
|
|
148253
|
+
const reply = await inbox.reply(msgId, original.toId, trimmed);
|
|
148254
|
+
if (options.json) {
|
|
148255
|
+
context.output.write(toEnvelopeJson(reply, { enveloped: options.jsonEnvelope }));
|
|
148256
|
+
} else {
|
|
148257
|
+
context.output.write(`replied ${reply.id} \u2192 ${reply.toId}`);
|
|
148258
|
+
}
|
|
148259
|
+
return 0;
|
|
148260
|
+
}
|
|
148261
|
+
|
|
148262
|
+
// src/commands/message/send.ts
|
|
148263
|
+
init_src3();
|
|
148264
|
+
init_src2();
|
|
148265
|
+
init_dist9();
|
|
148266
|
+
var SEND_UNTIL_STATES = new Set(["injected", "invoke-exit"]);
|
|
148267
|
+
function collectSendUntil(value, acc) {
|
|
148268
|
+
if (!SEND_UNTIL_STATES.has(value)) {
|
|
148269
|
+
throw new Error(`invalid --until "${value}" for send --wait (expected one of: ${[...SEND_UNTIL_STATES].join(", ")})`);
|
|
148270
|
+
}
|
|
148271
|
+
return [...acc, value];
|
|
148272
|
+
}
|
|
148273
|
+
function registerMessageSendCommand(noun, context) {
|
|
147832
148274
|
noun.command("send").description("Enqueue a message for an agent. Use --wait to block until the recipient reaches a state.").argument("<body>", "Message body").option("--to <id>", "Recipient agent id (mutually exclusive with --role)").option("--role <name>", "Address by Layer-1 role or executor name; must resolve to exactly one materialized instance").option("--from <id>", "Sender id", DEFAULT_FROM).option("--request-key <key>", "Caller-minted idempotency key: the same key + payload replays the original receipt instead of enqueuing twice (0832)").option("--wait", "Block until the recipient occupant reaches --until (default: invoke-exit)").option(...SHARED_OPTIONS.untilMessage, collectSendUntil, []).option(...SHARED_OPTIONS.timeout, parseTimeout2).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (body, options) => {
|
|
147833
148275
|
const svc = new AgentCoordinationService(context);
|
|
147834
148276
|
let toId = options.to;
|
|
@@ -147865,48 +148307,15 @@ function registerMessageCommand(program, context) {
|
|
|
147865
148307
|
}
|
|
147866
148308
|
toId = resolution.specId;
|
|
147867
148309
|
}
|
|
147868
|
-
const code = await runMessageSend(
|
|
148310
|
+
const code = await runMessageSend(context, body, {
|
|
147869
148311
|
...options,
|
|
147870
148312
|
to: toId ?? "",
|
|
147871
148313
|
requestKey: typeof options.requestKey === "string" && options.requestKey.length > 0 ? options.requestKey.trim() : undefined
|
|
147872
148314
|
});
|
|
147873
148315
|
context.setExitCode(code);
|
|
147874
148316
|
});
|
|
147875
|
-
noun.command("inbox").description("List messages addressed to an agent.").requiredOption("--agent <id>", "Agent id").option("--unresolved", "Only messages the 0834 reconciler holds (delivery-failed | attempts-exhausted | outcome-unknown | run-exit-only)").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
|
|
147876
|
-
const svc = new AgentCoordinationService(context);
|
|
147877
|
-
const code = await runMessageInbox(svc, context, options);
|
|
147878
|
-
context.setExitCode(code);
|
|
147879
|
-
});
|
|
147880
|
-
noun.command("reply").description("Thread a reply to a message.").argument("<msg-id>", "Message id to reply to").argument("<body>", "Reply body").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (msgId, body, options) => {
|
|
147881
|
-
const svc = new AgentCoordinationService(context);
|
|
147882
|
-
const code = await runMessageReply(svc, context, msgId, body, options);
|
|
147883
|
-
context.setExitCode(code);
|
|
147884
|
-
});
|
|
147885
|
-
noun.command("watch").description("Follow an agent inbox \u2014 surface new messages as they arrive (Ctrl-C to exit).").requiredOption("--agent <id>", "Agent id to watch").option("--interval <ms>", "Poll interval in milliseconds", String(DEFAULT_WATCH_INTERVAL_MS)).option(...SHARED_OPTIONS.jsonMessageStream).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
|
|
147886
|
-
const svc = new AgentCoordinationService(context);
|
|
147887
|
-
const intervalMs = parseInterval2(options.interval);
|
|
147888
|
-
if (intervalMs === null) {
|
|
147889
|
-
writeJsonError(context.output, options, `invalid --interval "${options.interval}" (expected a positive integer ms)`, "VALIDATION_FAILED");
|
|
147890
|
-
context.setExitCode(2);
|
|
147891
|
-
return;
|
|
147892
|
-
}
|
|
147893
|
-
const controller = new AbortController;
|
|
147894
|
-
const onSigInt = () => controller.abort();
|
|
147895
|
-
process.on("SIGINT", onSigInt);
|
|
147896
|
-
try {
|
|
147897
|
-
await runMessageWatch(svc, context.output, {
|
|
147898
|
-
agent: options.agent,
|
|
147899
|
-
intervalMs,
|
|
147900
|
-
json: options.json ?? false
|
|
147901
|
-
}, {
|
|
147902
|
-
signal: controller.signal
|
|
147903
|
-
});
|
|
147904
|
-
} finally {
|
|
147905
|
-
process.off("SIGINT", onSigInt);
|
|
147906
|
-
}
|
|
147907
|
-
});
|
|
147908
148317
|
}
|
|
147909
|
-
async function runMessageSend(
|
|
148318
|
+
async function runMessageSend(context, body, options) {
|
|
147910
148319
|
const trimmed = body.trim();
|
|
147911
148320
|
if (trimmed === "") {
|
|
147912
148321
|
if (options.json) {
|
|
@@ -147923,22 +148332,36 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
147923
148332
|
}
|
|
147924
148333
|
return 2;
|
|
147925
148334
|
}
|
|
148335
|
+
if (options.requestKey !== undefined && options.requestKey.trim() === "") {
|
|
148336
|
+
writeJsonError(context.output, options, "message send requires a non-empty --request-key", "VALIDATION_FAILED");
|
|
148337
|
+
return 2;
|
|
148338
|
+
}
|
|
147926
148339
|
if (options.to === "") {
|
|
147927
|
-
|
|
147928
|
-
|
|
147929
|
-
|
|
147930
|
-
|
|
147931
|
-
|
|
147932
|
-
|
|
147933
|
-
details: { cliCode: "usage" }
|
|
147934
|
-
}
|
|
147935
|
-
}
|
|
147936
|
-
|
|
147937
|
-
|
|
148340
|
+
const resolved = await resolveOrchestratorAddress(context);
|
|
148341
|
+
if (resolved === null) {
|
|
148342
|
+
const message = "message send found no orchestrator member \u2014 pass --to <id> or --role <name>, or bind agent.fleet";
|
|
148343
|
+
if (options.json) {
|
|
148344
|
+
context.output.write(toEnvelopeJson({ error: { code: "usage", message } }, {
|
|
148345
|
+
enveloped: options.jsonEnvelope,
|
|
148346
|
+
error: { code: "INTERNAL_ERROR", message, details: { cliCode: "usage" } }
|
|
148347
|
+
}));
|
|
148348
|
+
} else {
|
|
148349
|
+
context.output.error(message);
|
|
148350
|
+
}
|
|
148351
|
+
return 2;
|
|
147938
148352
|
}
|
|
147939
|
-
|
|
148353
|
+
options.to = resolved;
|
|
147940
148354
|
}
|
|
147941
148355
|
const from = options.from || DEFAULT_FROM;
|
|
148356
|
+
for (const id of [from, options.to]) {
|
|
148357
|
+
try {
|
|
148358
|
+
validateAgentId(id);
|
|
148359
|
+
} catch (error) {
|
|
148360
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148361
|
+
writeJsonError(context.output, options, message, "VALIDATION_FAILED");
|
|
148362
|
+
return 1;
|
|
148363
|
+
}
|
|
148364
|
+
}
|
|
147942
148365
|
let pinnedBeforeSend = null;
|
|
147943
148366
|
if (options.wait === true) {
|
|
147944
148367
|
const agentService = context.agentService();
|
|
@@ -147948,7 +148371,19 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
147948
148371
|
}
|
|
147949
148372
|
pinnedBeforeSend = { specId: options.to, runId: occupant.runId, generation: occupant.generation };
|
|
147950
148373
|
}
|
|
147951
|
-
const
|
|
148374
|
+
const sent = await inboxOf(context).send({
|
|
148375
|
+
from,
|
|
148376
|
+
to: options.to,
|
|
148377
|
+
body: trimmed,
|
|
148378
|
+
...options.requestKey !== undefined ? { requestKey: options.requestKey } : {}
|
|
148379
|
+
});
|
|
148380
|
+
const result = {
|
|
148381
|
+
msgId: sent.message.id,
|
|
148382
|
+
toId: sent.message.toId,
|
|
148383
|
+
status: sent.message.status,
|
|
148384
|
+
injected: false,
|
|
148385
|
+
...options.requestKey !== undefined ? { replayed: sent.replayed, requestKey: options.requestKey } : {}
|
|
148386
|
+
};
|
|
147952
148387
|
if (options.wait !== true) {
|
|
147953
148388
|
if (options.json) {
|
|
147954
148389
|
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
@@ -147976,7 +148411,7 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
147976
148411
|
for (const until of untilList) {
|
|
147977
148412
|
if (until === "injected") {
|
|
147978
148413
|
try {
|
|
147979
|
-
await
|
|
148414
|
+
await waitForMessageClaim(context, result.msgId, agentService, pin, options.timeout, controller.signal);
|
|
147980
148415
|
satisfied = "injected";
|
|
147981
148416
|
break;
|
|
147982
148417
|
} catch (error) {
|
|
@@ -147991,7 +148426,7 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
147991
148426
|
try {
|
|
147992
148427
|
const res = await waitForOccupant({
|
|
147993
148428
|
getOccupant: (id) => agentService.getOccupant({ specId: id }),
|
|
147994
|
-
|
|
148429
|
+
countQueued: (id) => inboxOf(context).countQueued(id),
|
|
147995
148430
|
latestInvokeEvent: (r) => readLatestInvokeEvent2(eventDao, r),
|
|
147996
148431
|
follow: (afterSequence) => followSystemEventsAfter(context.getDb, {
|
|
147997
148432
|
afterSequence,
|
|
@@ -148034,62 +148469,35 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
148034
148469
|
process.off("SIGTERM", onSignal);
|
|
148035
148470
|
}
|
|
148036
148471
|
}
|
|
148037
|
-
|
|
148038
|
-
|
|
148039
|
-
|
|
148040
|
-
|
|
148041
|
-
|
|
148042
|
-
|
|
148043
|
-
|
|
148472
|
+
|
|
148473
|
+
// src/commands/message/show.ts
|
|
148474
|
+
function registerMessageShowCommand(noun, context) {
|
|
148475
|
+
noun.command("show").description("Print one message thread: the root and every reply, oldest first, both directions.").argument("<msg-id>", "Message id (or any id in its thread)").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (msgId, options) => {
|
|
148476
|
+
context.setExitCode(await runMessageShow(context, msgId, { json: options.json, jsonEnvelope: options.jsonEnvelope }));
|
|
148477
|
+
});
|
|
148478
|
+
}
|
|
148479
|
+
async function runMessageShow(context, msgId, options) {
|
|
148480
|
+
const rows = await inboxOf(context).thread(msgId);
|
|
148481
|
+
if (rows.length === 0) {
|
|
148482
|
+
writeJsonError(context.output, options, `message ${msgId} not found`, "VALIDATION_FAILED");
|
|
148483
|
+
return 1;
|
|
148044
148484
|
}
|
|
148045
|
-
const inbox = await svc.getInbox(options.agent);
|
|
148046
|
-
const messages = options.unresolved === true ? inbox.messages.filter((m) => holds.has(m.id)) : inbox.messages;
|
|
148047
148485
|
if (options.json) {
|
|
148048
|
-
context.output.write(toEnvelopeJson({
|
|
148049
|
-
messages: messages.map((m) => widenInboxRow(m, holds.get(m.id))),
|
|
148050
|
-
count: messages.length
|
|
148051
|
-
}, { enveloped: options.jsonEnvelope }));
|
|
148486
|
+
context.output.write(toEnvelopeJson({ messages: rows, count: rows.length }, { enveloped: options.jsonEnvelope }));
|
|
148052
148487
|
return 0;
|
|
148053
148488
|
}
|
|
148054
|
-
|
|
148055
|
-
|
|
148056
|
-
|
|
148057
|
-
|
|
148058
|
-
|
|
148489
|
+
context.output.write(rows.map((row) => {
|
|
148490
|
+
const from = row.fromId || DEFAULT_FROM;
|
|
148491
|
+
const read = row.readAt === null ? "" : " (read)";
|
|
148492
|
+
return `${new Date(row.createdAt).toISOString()} ${from} \u2192 ${row.toId} ${row.status}${read}
|
|
148493
|
+
${row.body}`;
|
|
148494
|
+
}).join(`
|
|
148495
|
+
|
|
148059
148496
|
`));
|
|
148060
148497
|
return 0;
|
|
148061
148498
|
}
|
|
148062
|
-
|
|
148063
|
-
|
|
148064
|
-
...entry,
|
|
148065
|
-
injectAttempts: entry.injectAttempts,
|
|
148066
|
-
injectError: entry.injectError,
|
|
148067
|
-
reason: hold?.reason,
|
|
148068
|
-
runId: hold?.runId,
|
|
148069
|
-
taskId: hold?.taskId,
|
|
148070
|
-
runStatus: hold?.runStatus,
|
|
148071
|
-
artifacts: hold?.artifacts ?? []
|
|
148072
|
-
};
|
|
148073
|
-
}
|
|
148074
|
-
async function runMessageReply(svc, context, msgId, body, options) {
|
|
148075
|
-
const trimmed = body.trim();
|
|
148076
|
-
if (trimmed === "") {
|
|
148077
|
-
writeJsonError(context.output, options, "message reply requires a non-empty body", "VALIDATION_FAILED");
|
|
148078
|
-
return 2;
|
|
148079
|
-
}
|
|
148080
|
-
const result = await svc.replyToMessage(msgId, trimmed);
|
|
148081
|
-
if (options.json) {
|
|
148082
|
-
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
148083
|
-
} else {
|
|
148084
|
-
context.output.write(`replied ${result.msgId} \u2192 ${result.toId}`);
|
|
148085
|
-
}
|
|
148086
|
-
return 0;
|
|
148087
|
-
}
|
|
148088
|
-
function formatInboxLine(entry) {
|
|
148089
|
-
const from = entry.fromId ?? DEFAULT_FROM;
|
|
148090
|
-
const body = entry.body.length > 60 ? `${entry.body.slice(0, 57)}...` : entry.body;
|
|
148091
|
-
return `${entry.id} ${entry.status} ${from} ${body} ${entry.createdAt}`;
|
|
148092
|
-
}
|
|
148499
|
+
|
|
148500
|
+
// src/commands/message/watch.ts
|
|
148093
148501
|
async function runMessageWatch(svc, output, options, runtime = {}) {
|
|
148094
148502
|
const sleep = runtime.sleep ?? ((ms) => defaultSleep(ms, runtime.signal));
|
|
148095
148503
|
const seen = new Set;
|
|
@@ -148099,9 +148507,9 @@ async function runMessageWatch(svc, output, options, runtime = {}) {
|
|
|
148099
148507
|
return;
|
|
148100
148508
|
if (runtime.maxIterations !== undefined && iteration >= runtime.maxIterations)
|
|
148101
148509
|
return;
|
|
148102
|
-
const
|
|
148103
|
-
for (let i =
|
|
148104
|
-
const msg =
|
|
148510
|
+
const page = await svc.list(options.agent, "in");
|
|
148511
|
+
for (let i = page.messages.length - 1;i >= 0; i--) {
|
|
148512
|
+
const msg = page.messages[i];
|
|
148105
148513
|
if (!msg || seen.has(msg.id))
|
|
148106
148514
|
continue;
|
|
148107
148515
|
seen.add(msg.id);
|
|
@@ -148128,91 +148536,40 @@ function defaultSleep(ms, signal) {
|
|
|
148128
148536
|
}, { once: true });
|
|
148129
148537
|
});
|
|
148130
148538
|
}
|
|
148131
|
-
function
|
|
148132
|
-
|
|
148133
|
-
|
|
148134
|
-
|
|
148135
|
-
|
|
148136
|
-
|
|
148137
|
-
var SEND_UNTIL_STATES = new Set(["injected", "invoke-exit"]);
|
|
148138
|
-
function collectSendUntil(value, acc) {
|
|
148139
|
-
if (!SEND_UNTIL_STATES.has(value)) {
|
|
148140
|
-
throw new Error(`invalid --until "${value}" for send --wait (expected one of: ${[...SEND_UNTIL_STATES].join(", ")})`);
|
|
148141
|
-
}
|
|
148142
|
-
return [...acc, value];
|
|
148143
|
-
}
|
|
148144
|
-
function parseTimeout2(raw) {
|
|
148145
|
-
if (raw === undefined || typeof raw === "boolean")
|
|
148146
|
-
return;
|
|
148147
|
-
const n = Number.parseInt(raw, 10);
|
|
148148
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
148149
|
-
throw new Error(`invalid --timeout "${raw}" (expected a positive integer ms)`);
|
|
148150
|
-
}
|
|
148151
|
-
return n;
|
|
148152
|
-
}
|
|
148153
|
-
function sendSleep(ms, signal) {
|
|
148154
|
-
return new Promise((resolve) => {
|
|
148155
|
-
if (signal?.aborted) {
|
|
148156
|
-
resolve();
|
|
148539
|
+
function registerMessageWatchCommand(noun, context) {
|
|
148540
|
+
noun.command("watch").description("Follow an agent inbox \u2014 surface new messages as they arrive (Ctrl-C to exit).").requiredOption("--agent <id>", "Agent id to watch").option("--interval <ms>", "Poll interval in milliseconds", String(DEFAULT_WATCH_INTERVAL_MS)).option(...SHARED_OPTIONS.jsonMessageStream).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
|
|
148541
|
+
const intervalMs = parseInterval2(options.interval);
|
|
148542
|
+
if (intervalMs === null) {
|
|
148543
|
+
writeJsonError(context.output, options, `invalid --interval "${options.interval}" (expected a positive integer ms)`, "VALIDATION_FAILED");
|
|
148544
|
+
context.setExitCode(2);
|
|
148157
148545
|
return;
|
|
148158
148546
|
}
|
|
148159
|
-
const
|
|
148160
|
-
|
|
148161
|
-
|
|
148162
|
-
|
|
148163
|
-
|
|
148164
|
-
|
|
148165
|
-
|
|
148166
|
-
|
|
148167
|
-
|
|
148168
|
-
|
|
148169
|
-
|
|
148170
|
-
|
|
148171
|
-
|
|
148172
|
-
|
|
148173
|
-
context.output.error(`${code}: ${message}`);
|
|
148174
|
-
}
|
|
148175
|
-
return 1;
|
|
148176
|
-
}
|
|
148177
|
-
async function readLatestInvokeEvent2(dao, runId) {
|
|
148178
|
-
const rows = await dao.query({
|
|
148179
|
-
run_id: runId,
|
|
148180
|
-
names: ["agent.invoke.start", "agent.invoke.exit"],
|
|
148181
|
-
limit: 1
|
|
148547
|
+
const controller = new AbortController;
|
|
148548
|
+
const onSigInt = () => controller.abort();
|
|
148549
|
+
process.on("SIGINT", onSigInt);
|
|
148550
|
+
try {
|
|
148551
|
+
await runMessageWatch(inboxOf(context), context.output, {
|
|
148552
|
+
agent: options.agent,
|
|
148553
|
+
intervalMs,
|
|
148554
|
+
json: options.json ?? false
|
|
148555
|
+
}, {
|
|
148556
|
+
signal: controller.signal
|
|
148557
|
+
});
|
|
148558
|
+
} finally {
|
|
148559
|
+
process.off("SIGINT", onSigInt);
|
|
148560
|
+
}
|
|
148182
148561
|
});
|
|
148183
|
-
const row = rows[0];
|
|
148184
|
-
if (row === undefined)
|
|
148185
|
-
return null;
|
|
148186
|
-
return { eventName: row.event_name, sequence: row.sequence };
|
|
148187
148562
|
}
|
|
148188
|
-
|
|
148189
|
-
|
|
148190
|
-
|
|
148191
|
-
const
|
|
148192
|
-
|
|
148193
|
-
|
|
148194
|
-
|
|
148195
|
-
|
|
148196
|
-
|
|
148197
|
-
|
|
148198
|
-
throw new WaitError("timeout", `wait timed out after ${timeoutMs}ms`);
|
|
148199
|
-
const occupant = await agentService.getOccupant({ specId: pin.specId });
|
|
148200
|
-
if (occupant === null)
|
|
148201
|
-
throw new WaitError("occupant_gone", `occupant for specId "${pin.specId}" is gone`);
|
|
148202
|
-
if (occupant.runId !== pin.runId)
|
|
148203
|
-
throw new WaitError("run_replaced", `run ${pin.runId} replaced`);
|
|
148204
|
-
if (occupant.generation > pin.generation)
|
|
148205
|
-
throw new WaitError("run_replaced", `generation bumped`);
|
|
148206
|
-
const pending = await coordination.countPending(pin.specId);
|
|
148207
|
-
const progressed = lastPending >= 0 && pending !== lastPending;
|
|
148208
|
-
if (pending === 0)
|
|
148209
|
-
return;
|
|
148210
|
-
if (lastPending >= 0 && !progressed && clock - start >= stallMs) {
|
|
148211
|
-
throw new WaitError("wait_stalled", `inbox not draining within ${stallMs}ms`);
|
|
148212
|
-
}
|
|
148213
|
-
lastPending = pending;
|
|
148214
|
-
await sendSleep(100, signal);
|
|
148215
|
-
}
|
|
148563
|
+
|
|
148564
|
+
// src/commands/message/index.ts
|
|
148565
|
+
function registerMessageCommand(program, context) {
|
|
148566
|
+
const noun = program.command("message").summary("send and inspect durable inter-agent messages");
|
|
148567
|
+
registerMessageSendCommand(noun, context);
|
|
148568
|
+
registerMessageInboxCommand(noun, context);
|
|
148569
|
+
registerMessageShowCommand(noun, context);
|
|
148570
|
+
registerMessageReadCommand(noun, context);
|
|
148571
|
+
registerMessageReplyCommand(noun, context);
|
|
148572
|
+
registerMessageWatchCommand(noun, context);
|
|
148216
148573
|
}
|
|
148217
148574
|
|
|
148218
148575
|
// src/commands/migrate.ts
|
|
@@ -154920,6 +155277,134 @@ var historyContract = {
|
|
|
154920
155277
|
}).input(historyTriggerImportInputSchema).output(historyTriggerImportResponseSchema)
|
|
154921
155278
|
};
|
|
154922
155279
|
|
|
155280
|
+
// ../../packages/contracts/src/inbox.ts
|
|
155281
|
+
init_zod();
|
|
155282
|
+
var INBOX_MESSAGE_KINDS = ["chat", "request"];
|
|
155283
|
+
var INBOX_MESSAGE_STATUSES = ["queued", "claimed", "delivered", "failed"];
|
|
155284
|
+
var inboxMessageSchema = object2({
|
|
155285
|
+
id: string2(),
|
|
155286
|
+
fromId: string2(),
|
|
155287
|
+
toId: string2(),
|
|
155288
|
+
body: string2(),
|
|
155289
|
+
kind: _enum2(INBOX_MESSAGE_KINDS),
|
|
155290
|
+
status: _enum2(INBOX_MESSAGE_STATUSES),
|
|
155291
|
+
attempts: number2().int().nonnegative(),
|
|
155292
|
+
error: string2().nullable(),
|
|
155293
|
+
claimedAt: number2().int().nullable(),
|
|
155294
|
+
deliveredAt: number2().int().nullable(),
|
|
155295
|
+
readAt: number2().int().nullable(),
|
|
155296
|
+
inReplyTo: string2().nullable(),
|
|
155297
|
+
threadId: string2(),
|
|
155298
|
+
requestKey: string2().nullable(),
|
|
155299
|
+
taskId: string2().nullable(),
|
|
155300
|
+
parentRunId: string2().nullable(),
|
|
155301
|
+
createdAt: number2().int()
|
|
155302
|
+
});
|
|
155303
|
+
var inboxMemberSchema = object2({
|
|
155304
|
+
id: string2(),
|
|
155305
|
+
queued: number2().int().nonnegative(),
|
|
155306
|
+
unread: number2().int().nonnegative()
|
|
155307
|
+
});
|
|
155308
|
+
var inboxRequestSchema = object2({
|
|
155309
|
+
messageId: string2(),
|
|
155310
|
+
requestKey: string2().nullable(),
|
|
155311
|
+
status: _enum2(INBOX_MESSAGE_STATUSES),
|
|
155312
|
+
attempts: number2().int().nonnegative(),
|
|
155313
|
+
error: string2().nullable(),
|
|
155314
|
+
runId: string2().nullable(),
|
|
155315
|
+
taskId: string2().nullable(),
|
|
155316
|
+
outcome: string2().nullable(),
|
|
155317
|
+
reason: string2().nullable(),
|
|
155318
|
+
hold: string2().nullable()
|
|
155319
|
+
});
|
|
155320
|
+
var inboxListInputSchema = object2({
|
|
155321
|
+
member: string2().min(1),
|
|
155322
|
+
direction: _enum2(["in", "out", "both"]).optional(),
|
|
155323
|
+
limit: number2().int().positive().max(500).optional(),
|
|
155324
|
+
cursor: string2().optional()
|
|
155325
|
+
});
|
|
155326
|
+
var inboxListResponseSchema = object2({
|
|
155327
|
+
messages: array(inboxMessageSchema),
|
|
155328
|
+
nextCursor: string2().nullable()
|
|
155329
|
+
});
|
|
155330
|
+
var inboxThreadInputSchema = object2({
|
|
155331
|
+
id: string2().min(1)
|
|
155332
|
+
});
|
|
155333
|
+
var inboxSendInputSchema = object2({
|
|
155334
|
+
to: string2().min(1).optional(),
|
|
155335
|
+
from: string2().min(1).optional(),
|
|
155336
|
+
body: string2().min(1),
|
|
155337
|
+
kind: _enum2(INBOX_MESSAGE_KINDS).optional(),
|
|
155338
|
+
requestKey: string2().min(1).optional(),
|
|
155339
|
+
taskId: string2().min(1).optional(),
|
|
155340
|
+
parentRunId: string2().min(1).optional(),
|
|
155341
|
+
projectPath: string2().optional()
|
|
155342
|
+
});
|
|
155343
|
+
var inboxSendResponseSchema = object2({
|
|
155344
|
+
message: inboxMessageSchema,
|
|
155345
|
+
replayed: boolean2()
|
|
155346
|
+
});
|
|
155347
|
+
var inboxReplyInputSchema = object2({
|
|
155348
|
+
id: string2().min(1),
|
|
155349
|
+
from: string2().min(1),
|
|
155350
|
+
body: string2().min(1)
|
|
155351
|
+
});
|
|
155352
|
+
var inboxReadInputSchema = object2({
|
|
155353
|
+
id: string2().min(1),
|
|
155354
|
+
reader: string2().min(1)
|
|
155355
|
+
});
|
|
155356
|
+
var inboxReadResponseSchema = object2({
|
|
155357
|
+
readAt: number2().int(),
|
|
155358
|
+
transitioned: boolean2()
|
|
155359
|
+
});
|
|
155360
|
+
var inboxRequestsInputSchema = object2({
|
|
155361
|
+
limit: number2().int().positive().max(200).optional()
|
|
155362
|
+
});
|
|
155363
|
+
var inboxContract = {
|
|
155364
|
+
list: oc.route({
|
|
155365
|
+
method: "GET",
|
|
155366
|
+
path: "/inbox/messages",
|
|
155367
|
+
summary: "List one inbox address messages",
|
|
155368
|
+
tags: ["inbox"]
|
|
155369
|
+
}).input(inboxListInputSchema).output(inboxListResponseSchema),
|
|
155370
|
+
thread: oc.route({
|
|
155371
|
+
method: "GET",
|
|
155372
|
+
path: "/inbox/messages/{id}/thread",
|
|
155373
|
+
summary: "Read one message thread (root plus replies, oldest first)",
|
|
155374
|
+
tags: ["inbox"]
|
|
155375
|
+
}).input(inboxThreadInputSchema).output(array(inboxMessageSchema)),
|
|
155376
|
+
send: oc.route({
|
|
155377
|
+
method: "POST",
|
|
155378
|
+
path: "/inbox/messages",
|
|
155379
|
+
summary: "Send a message to a member inbox",
|
|
155380
|
+
tags: ["inbox"]
|
|
155381
|
+
}).input(inboxSendInputSchema).output(inboxSendResponseSchema),
|
|
155382
|
+
reply: oc.route({
|
|
155383
|
+
method: "POST",
|
|
155384
|
+
path: "/inbox/messages/{id}/reply",
|
|
155385
|
+
summary: "Reply in a thread, addressed to the original sender",
|
|
155386
|
+
tags: ["inbox"]
|
|
155387
|
+
}).input(inboxReplyInputSchema).output(inboxMessageSchema),
|
|
155388
|
+
read: oc.route({
|
|
155389
|
+
method: "POST",
|
|
155390
|
+
path: "/inbox/messages/{id}/read",
|
|
155391
|
+
summary: "Mark a message read by its recipient",
|
|
155392
|
+
tags: ["inbox"]
|
|
155393
|
+
}).input(inboxReadInputSchema).output(inboxReadResponseSchema),
|
|
155394
|
+
requests: oc.route({
|
|
155395
|
+
method: "GET",
|
|
155396
|
+
path: "/inbox/requests",
|
|
155397
|
+
summary: "List request results with delivery classification",
|
|
155398
|
+
tags: ["inbox"]
|
|
155399
|
+
}).input(inboxRequestsInputSchema).output(array(inboxRequestSchema)),
|
|
155400
|
+
members: oc.route({
|
|
155401
|
+
method: "GET",
|
|
155402
|
+
path: "/inbox/members",
|
|
155403
|
+
summary: "List inbox addresses with queued and unread counts",
|
|
155404
|
+
tags: ["inbox"]
|
|
155405
|
+
}).input(object2({})).output(array(inboxMemberSchema))
|
|
155406
|
+
};
|
|
155407
|
+
|
|
154923
155408
|
// ../../packages/contracts/src/planning-event.ts
|
|
154924
155409
|
init_zod();
|
|
154925
155410
|
var planningEventEnvelopeSchema = apiSuccessSchema(object2({
|
|
@@ -155356,6 +155841,7 @@ var contract = {
|
|
|
155356
155841
|
board: { ...boardContract },
|
|
155357
155842
|
feature: { ...featureContract },
|
|
155358
155843
|
history: { ...historyContract },
|
|
155844
|
+
inbox: { ...inboxContract },
|
|
155359
155845
|
fleet: { ...fleetContract },
|
|
155360
155846
|
processes: { ...processesContract },
|
|
155361
155847
|
runs: { ...runsContract },
|
|
@@ -156349,6 +156835,9 @@ var databaseModule = {
|
|
|
156349
156835
|
}
|
|
156350
156836
|
};
|
|
156351
156837
|
|
|
156838
|
+
// ../server/src/modules/events/index.ts
|
|
156839
|
+
init_src3();
|
|
156840
|
+
|
|
156352
156841
|
// ../server/src/modules/sse/stream-helpers.ts
|
|
156353
156842
|
function sendSseKeepalive(closed, controller, encoder) {
|
|
156354
156843
|
if (closed.current)
|
|
@@ -156441,6 +156930,34 @@ function extractSystemEventActor2(event) {
|
|
|
156441
156930
|
function sendKeepalive(closed, controller, encoder) {
|
|
156442
156931
|
sendSseKeepalive(closed, controller, encoder);
|
|
156443
156932
|
}
|
|
156933
|
+
var relayedContexts = new WeakSet;
|
|
156934
|
+
var relayedRowColumns = new WeakMap;
|
|
156935
|
+
function startMessageRelay(ctx) {
|
|
156936
|
+
if (relayedContexts.has(ctx))
|
|
156937
|
+
return;
|
|
156938
|
+
relayedContexts.add(ctx);
|
|
156939
|
+
const controller = new AbortController;
|
|
156940
|
+
(async () => {
|
|
156941
|
+
try {
|
|
156942
|
+
const bus = ctx.eventBus();
|
|
156943
|
+
const start = await (await ctx.systemEventDao()).latestSequence();
|
|
156944
|
+
const stream = followSystemEventsAfter(() => ctx.getDb(), {
|
|
156945
|
+
afterSequence: start,
|
|
156946
|
+
match: (row) => row.event_name.startsWith("message."),
|
|
156947
|
+
signal: controller.signal
|
|
156948
|
+
});
|
|
156949
|
+
for await (const row of stream) {
|
|
156950
|
+
try {
|
|
156951
|
+
const payload = JSON.parse(row.payload_json ?? "{}");
|
|
156952
|
+
if (payload !== null && typeof payload === "object") {
|
|
156953
|
+
relayedRowColumns.set(payload, { actor: row.actor, occurredAt: row.occurred_at });
|
|
156954
|
+
}
|
|
156955
|
+
bus.emit(row.event_name, payload);
|
|
156956
|
+
} catch {}
|
|
156957
|
+
}
|
|
156958
|
+
} catch {}
|
|
156959
|
+
})();
|
|
156960
|
+
}
|
|
156444
156961
|
var eventsModule = {
|
|
156445
156962
|
name: "events",
|
|
156446
156963
|
mount(app, ctx) {
|
|
@@ -156452,6 +156969,7 @@ var eventsModule = {
|
|
|
156452
156969
|
const diagnosticEnabled = typeof ctx.bootConfig === "function" && ctx.bootConfig().events.diagnostic === true;
|
|
156453
156970
|
const diagnosticNames = diagnosticEnabled ? SYSTEM_EVENT_CATALOG.filter((entry) => entry.tier === "diagnostic" && entry.streamed).map((entry) => entry.name) : [];
|
|
156454
156971
|
const streamNames = [...baseNames, ...diagnosticNames];
|
|
156972
|
+
startMessageRelay(ctx);
|
|
156455
156973
|
app.get("/api/events/planning", (c) => {
|
|
156456
156974
|
const bus = ctx.eventBus();
|
|
156457
156975
|
const closed = { current: false };
|
|
@@ -156490,14 +157008,15 @@ var eventsModule = {
|
|
|
156490
157008
|
if (closed.current)
|
|
156491
157009
|
return;
|
|
156492
157010
|
const entry = systemEventCatalogEntry(name);
|
|
156493
|
-
const
|
|
157011
|
+
const ledger = event !== null && typeof event === "object" ? relayedRowColumns.get(event) : undefined;
|
|
157012
|
+
const actor = ledger !== undefined ? ledger.actor : extractSystemEventActor2(event);
|
|
156494
157013
|
const envelope = {
|
|
156495
157014
|
eventName: name,
|
|
156496
|
-
occurredAt: new Date().toISOString(),
|
|
157015
|
+
occurredAt: ledger?.occurredAt ?? new Date().toISOString(),
|
|
156497
157016
|
actor,
|
|
156498
157017
|
prefix: entry?.prefix ?? name.split(".")[0],
|
|
156499
157018
|
renderer: entry?.renderer ?? "generic",
|
|
156500
|
-
payload: buildSystemEventEnvelope(entry, event, projectContext, secretValues, actor)
|
|
157019
|
+
payload: event !== null && typeof event === "object" && "schemaVersion" in event ? projectStoredSystemEventEnvelope(entry, event, projectContext, secretValues, actor) : buildSystemEventEnvelope(entry, event, projectContext, secretValues, actor)
|
|
156501
157020
|
};
|
|
156502
157021
|
try {
|
|
156503
157022
|
controller.enqueue(encoder.encode(`data: ${JSON.stringify(envelope)}
|
|
@@ -156735,11 +157254,10 @@ init_dist5();
|
|
|
156735
157254
|
import { homedir as homedir17 } from "os";
|
|
156736
157255
|
import { basename as basename20, join as join94, relative as relative19, resolve as resolve37 } from "path";
|
|
156737
157256
|
// ../server/package.json
|
|
156738
|
-
var version3 = "0.4.
|
|
157257
|
+
var version3 = "0.4.19";
|
|
156739
157258
|
|
|
156740
157259
|
// ../server/src/modules/health/index.ts
|
|
156741
157260
|
var startedAt = Date.now();
|
|
156742
|
-
var OPERATOR_AGENT_ID = "board-operator";
|
|
156743
157261
|
var healthModule = {
|
|
156744
157262
|
name: "health",
|
|
156745
157263
|
mount(app, ctx) {
|
|
@@ -157008,77 +157526,6 @@ var healthModule = {
|
|
|
157008
157526
|
executors
|
|
157009
157527
|
});
|
|
157010
157528
|
});
|
|
157011
|
-
app.get("/api/project/requests", async (c) => {
|
|
157012
|
-
if (!ctx) {
|
|
157013
|
-
return c.json({ requests: [] });
|
|
157014
|
-
}
|
|
157015
|
-
const limitRaw = c.req.query("limit");
|
|
157016
|
-
const parsedLimit = limitRaw === undefined ? NaN : Number.parseInt(limitRaw, 10);
|
|
157017
|
-
const limit = Number.isNaN(parsedLimit) || parsedLimit < 0 ? 50 : Math.min(parsedLimit, 200);
|
|
157018
|
-
const path = normalizeProjectPath(ctx.cwd);
|
|
157019
|
-
const openDb = () => ctx.getDb();
|
|
157020
|
-
const fleet = new FleetService({
|
|
157021
|
-
fs: ctx.fs,
|
|
157022
|
-
reloadAgentConfig: () => ctx.reloadAgentConfig(),
|
|
157023
|
-
openDb
|
|
157024
|
-
});
|
|
157025
|
-
const runtime = new StrategyRuntime({
|
|
157026
|
-
openDb,
|
|
157027
|
-
tasks: ctx.taskService(),
|
|
157028
|
-
fleet,
|
|
157029
|
-
dependencyBlocked: async () => null
|
|
157030
|
-
});
|
|
157031
|
-
let orchestrator = { state: "unresolvable", reason: "unavailable" };
|
|
157032
|
-
try {
|
|
157033
|
-
const { state, instanceId, holderId, reason } = await fleet.resolveOrchestrator(path);
|
|
157034
|
-
orchestrator = { state, instanceId, holderId, reason };
|
|
157035
|
-
} catch {
|
|
157036
|
-
orchestrator = { state: "unresolvable", reason: "unavailable" };
|
|
157037
|
-
}
|
|
157038
|
-
const instanceId = orchestrator.state === "missing" || orchestrator.state === "unresolvable" ? undefined : orchestrator.instanceId;
|
|
157039
|
-
if (instanceId === undefined) {
|
|
157040
|
-
return c.json({ requests: [] });
|
|
157041
|
-
}
|
|
157042
|
-
const db = await openDb();
|
|
157043
|
-
const unresolved = await new DeliveryReconciler({ getDb: openDb }).classify(instanceId);
|
|
157044
|
-
try {
|
|
157045
|
-
await new FleetGuestService({
|
|
157046
|
-
cwd: ctx.cwd,
|
|
157047
|
-
fs: ctx.fs ?? createNodeFileSystem2(ctx.cwd),
|
|
157048
|
-
getDb: openDb
|
|
157049
|
-
}).expire();
|
|
157050
|
-
} catch {}
|
|
157051
|
-
const reasonByMessage = new Map(unresolved.map((u) => [u.messageId, u.reason]));
|
|
157052
|
-
const runs = new CoordinationRunDao(db);
|
|
157053
|
-
const holdByTask = new Map;
|
|
157054
|
-
try {
|
|
157055
|
-
for (const h of (await runtime.selectNext(path, { readOnly: true })).holds) {
|
|
157056
|
-
holdByTask.set(h.wbs, h.reason);
|
|
157057
|
-
}
|
|
157058
|
-
} catch {}
|
|
157059
|
-
const REQUESTED_RUN_OUTCOMES = ["run-exit-only", "errored", "verified"];
|
|
157060
|
-
const requests = [];
|
|
157061
|
-
for (const row of await new InboxMessageDao(db).inbox(instanceId, 200)) {
|
|
157062
|
-
if (row.fromId !== OPERATOR_AGENT_ID)
|
|
157063
|
-
continue;
|
|
157064
|
-
if (requests.length >= limit)
|
|
157065
|
-
break;
|
|
157066
|
-
const runRow = (await runs.listByMessageId(row.id))[0];
|
|
157067
|
-
requests.push({
|
|
157068
|
-
messageId: row.id,
|
|
157069
|
-
requestKey: row.requestKey,
|
|
157070
|
-
deliveryStatus: row.status,
|
|
157071
|
-
injectAttempts: row.injectAttempts,
|
|
157072
|
-
injectError: row.injectError,
|
|
157073
|
-
runId: runRow?.run_id ?? null,
|
|
157074
|
-
taskId: runRow?.task_id ?? null,
|
|
157075
|
-
outcome: runRow !== undefined && REQUESTED_RUN_OUTCOMES.includes(runRow.outcome) ? runRow.outcome : null,
|
|
157076
|
-
reason: reasonByMessage.get(row.id) ?? null,
|
|
157077
|
-
hold: runRow?.task_id !== null && runRow?.task_id !== undefined ? holdByTask.get(runRow.task_id) ?? null : null
|
|
157078
|
-
});
|
|
157079
|
-
}
|
|
157080
|
-
return c.json({ requests });
|
|
157081
|
-
});
|
|
157082
157529
|
app.get("/api/projects", async (c) => {
|
|
157083
157530
|
if (!ctx) {
|
|
157084
157531
|
return c.json({ projects: [] });
|
|
@@ -157740,130 +158187,6 @@ var jobsModule = {
|
|
|
157740
158187
|
}
|
|
157741
158188
|
};
|
|
157742
158189
|
|
|
157743
|
-
// ../server/src/modules/messages/index.ts
|
|
157744
|
-
init_src3();
|
|
157745
|
-
var messagesModule = {
|
|
157746
|
-
name: "messages",
|
|
157747
|
-
mount(app, ctx) {
|
|
157748
|
-
if (!ctx)
|
|
157749
|
-
return;
|
|
157750
|
-
app.get("/api/messages/inbox", async (c) => {
|
|
157751
|
-
const agent = c.req.query("agent");
|
|
157752
|
-
if (!agent) {
|
|
157753
|
-
return c.json({ error: "missing required query parameter: agent" }, 400);
|
|
157754
|
-
}
|
|
157755
|
-
const limit = parseLimit(c.req.query("limit"), 50);
|
|
157756
|
-
const offset = parseOffset(c.req.query("offset"));
|
|
157757
|
-
const svc = ctx.coordination();
|
|
157758
|
-
const result = await svc.getInbox(agent, limit, offset);
|
|
157759
|
-
return c.json(result);
|
|
157760
|
-
});
|
|
157761
|
-
app.get("/api/messages", async (c) => {
|
|
157762
|
-
const limit = parseLimit(c.req.query("limit"), 50);
|
|
157763
|
-
const svc = ctx.coordination();
|
|
157764
|
-
const result = await svc.listRecent(limit);
|
|
157765
|
-
return c.json(result);
|
|
157766
|
-
});
|
|
157767
|
-
app.post("/api/messages", async (c) => {
|
|
157768
|
-
const parsed = await parseJsonBody(c);
|
|
157769
|
-
if ("error" in parsed)
|
|
157770
|
-
return c.json({ error: parsed.error }, 400);
|
|
157771
|
-
const { to, body, from, requestKey, projectPath } = parsed;
|
|
157772
|
-
if (typeof to !== "string" || to.length === 0)
|
|
157773
|
-
return c.json({ error: 'field "to" is required' }, 400);
|
|
157774
|
-
if (typeof body !== "string" || body.length === 0)
|
|
157775
|
-
return c.json({ error: 'field "body" is required' }, 400);
|
|
157776
|
-
if (from !== undefined && (typeof from !== "string" || from.length === 0)) {
|
|
157777
|
-
return c.json({ error: 'field "from" must be a non-empty string when present' }, 400);
|
|
157778
|
-
}
|
|
157779
|
-
if (requestKey !== undefined && (typeof requestKey !== "string" || requestKey.length === 0)) {
|
|
157780
|
-
return c.json({ error: 'field "requestKey" must be a non-empty string when present' }, 400);
|
|
157781
|
-
}
|
|
157782
|
-
if (projectPath !== undefined && (typeof projectPath !== "string" || projectPath.length === 0)) {
|
|
157783
|
-
return c.json({ error: 'field "projectPath" must be a non-empty string when present' }, 400);
|
|
157784
|
-
}
|
|
157785
|
-
if (typeof projectPath === "string" && projectPath.length > 0) {
|
|
157786
|
-
const served = normalizeProjectPath(ctx.cwd);
|
|
157787
|
-
const claimed = normalizeProjectPath(projectPath);
|
|
157788
|
-
if (claimed !== served) {
|
|
157789
|
-
return c.json({ error: `project mismatch: request targets ${claimed}, server serves ${served}` }, 409);
|
|
157790
|
-
}
|
|
157791
|
-
}
|
|
157792
|
-
const svc = ctx.coordination();
|
|
157793
|
-
try {
|
|
157794
|
-
const result = await svc.sendMessage(from ?? null, to, body, undefined, requestKey);
|
|
157795
|
-
return c.json(result, 201);
|
|
157796
|
-
} catch (err) {
|
|
157797
|
-
return c.json({ error: errMsg(err) }, 400);
|
|
157798
|
-
}
|
|
157799
|
-
});
|
|
157800
|
-
app.post("/api/messages/:id/reply", async (c) => {
|
|
157801
|
-
const id = c.req.param("id");
|
|
157802
|
-
const parsed = await parseJsonBody(c);
|
|
157803
|
-
if ("error" in parsed)
|
|
157804
|
-
return c.json({ error: parsed.error }, 400);
|
|
157805
|
-
const { body } = parsed;
|
|
157806
|
-
if (typeof body !== "string" || body.length === 0)
|
|
157807
|
-
return c.json({ error: 'field "body" is required' }, 400);
|
|
157808
|
-
const svc = ctx.coordination();
|
|
157809
|
-
try {
|
|
157810
|
-
const result = await svc.replyToMessage(id, body);
|
|
157811
|
-
return c.json(result, 201);
|
|
157812
|
-
} catch (err) {
|
|
157813
|
-
return c.json({ error: errMsg(err) }, 400);
|
|
157814
|
-
}
|
|
157815
|
-
});
|
|
157816
|
-
app.post("/api/messages/:id/read", async (c) => {
|
|
157817
|
-
const id = c.req.param("id");
|
|
157818
|
-
const parsed = await parseJsonBody(c);
|
|
157819
|
-
if ("error" in parsed)
|
|
157820
|
-
return c.json({ error: parsed.error }, 400);
|
|
157821
|
-
const { reader } = parsed;
|
|
157822
|
-
if (typeof reader !== "string" || reader.length === 0)
|
|
157823
|
-
return c.json({ error: 'field "reader" is required' }, 400);
|
|
157824
|
-
try {
|
|
157825
|
-
return c.json(await ctx.coordination().markMessageRead(id, reader));
|
|
157826
|
-
} catch (err) {
|
|
157827
|
-
return c.json({ error: errMsg(err) }, err instanceof MessageNotFoundError ? 404 : 400);
|
|
157828
|
-
}
|
|
157829
|
-
});
|
|
157830
|
-
}
|
|
157831
|
-
};
|
|
157832
|
-
async function parseJsonBody(c) {
|
|
157833
|
-
let json;
|
|
157834
|
-
try {
|
|
157835
|
-
json = await c.req.json();
|
|
157836
|
-
} catch {
|
|
157837
|
-
return { error: "request body must be valid JSON" };
|
|
157838
|
-
}
|
|
157839
|
-
if (typeof json !== "object" || json === null || Array.isArray(json)) {
|
|
157840
|
-
return { error: "request body must be a JSON object" };
|
|
157841
|
-
}
|
|
157842
|
-
return json;
|
|
157843
|
-
}
|
|
157844
|
-
function errMsg(err) {
|
|
157845
|
-
if (err instanceof Error)
|
|
157846
|
-
return err.message;
|
|
157847
|
-
return String(err);
|
|
157848
|
-
}
|
|
157849
|
-
function parseLimit(raw, fallback) {
|
|
157850
|
-
if (raw === undefined)
|
|
157851
|
-
return fallback;
|
|
157852
|
-
const parsed = Number.parseInt(raw, 10);
|
|
157853
|
-
if (Number.isNaN(parsed) || parsed < 0)
|
|
157854
|
-
return fallback;
|
|
157855
|
-
return Math.min(parsed, 500);
|
|
157856
|
-
}
|
|
157857
|
-
function parseOffset(raw) {
|
|
157858
|
-
if (raw === undefined)
|
|
157859
|
-
return;
|
|
157860
|
-
const trimmed = raw.trim();
|
|
157861
|
-
if (!/^[0-9]+$/.test(trimmed))
|
|
157862
|
-
return 0;
|
|
157863
|
-
const parsed = Number(trimmed);
|
|
157864
|
-
return Number.isSafeInteger(parsed) ? parsed : 0;
|
|
157865
|
-
}
|
|
157866
|
-
|
|
157867
158190
|
// ../server/src/modules/observability/index.ts
|
|
157868
158191
|
init_src3();
|
|
157869
158192
|
init_src2();
|
|
@@ -158568,7 +158891,6 @@ var builtins = [
|
|
|
158568
158891
|
taskModule,
|
|
158569
158892
|
featureModule,
|
|
158570
158893
|
eventsModule,
|
|
158571
|
-
messagesModule,
|
|
158572
158894
|
jobsModule,
|
|
158573
158895
|
processesModule,
|
|
158574
158896
|
observabilityModule,
|
|
@@ -159894,8 +160216,207 @@ async function generateOpenApiSpec(pluginPaths = {}) {
|
|
|
159894
160216
|
return spec;
|
|
159895
160217
|
}
|
|
159896
160218
|
|
|
159897
|
-
// ../server/src/
|
|
160219
|
+
// ../server/src/modules/inbox/handlers.ts
|
|
160220
|
+
init_src3();
|
|
159898
160221
|
var os5 = implement(contract);
|
|
160222
|
+
function toMessageDto(row) {
|
|
160223
|
+
return {
|
|
160224
|
+
id: row.id,
|
|
160225
|
+
fromId: row.fromId,
|
|
160226
|
+
toId: row.toId,
|
|
160227
|
+
body: row.body,
|
|
160228
|
+
kind: row.kind,
|
|
160229
|
+
status: row.status,
|
|
160230
|
+
attempts: row.attempts,
|
|
160231
|
+
error: row.error,
|
|
160232
|
+
claimedAt: row.claimedAt,
|
|
160233
|
+
deliveredAt: row.deliveredAt,
|
|
160234
|
+
readAt: row.readAt,
|
|
160235
|
+
inReplyTo: row.inReplyTo,
|
|
160236
|
+
threadId: row.threadId,
|
|
160237
|
+
requestKey: row.requestKey,
|
|
160238
|
+
taskId: row.taskId,
|
|
160239
|
+
parentRunId: row.parentRunId,
|
|
160240
|
+
createdAt: row.createdAt
|
|
160241
|
+
};
|
|
160242
|
+
}
|
|
160243
|
+
function toOrpcError(error, fallback) {
|
|
160244
|
+
if (error instanceof MessageNotFoundError)
|
|
160245
|
+
return new ORPCError("NOT_FOUND", { message: error.message });
|
|
160246
|
+
if (error instanceof RequestKeyConflictError2)
|
|
160247
|
+
return new ORPCError("CONFLICT", { message: error.message });
|
|
160248
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
160249
|
+
if (message.startsWith("inbox message requires") || message.includes("must be a non-empty")) {
|
|
160250
|
+
return new ORPCError("BAD_REQUEST", { message });
|
|
160251
|
+
}
|
|
160252
|
+
if (message.includes("malformed list cursor")) {
|
|
160253
|
+
return new ORPCError("BAD_REQUEST", { message: "the list cursor is malformed" });
|
|
160254
|
+
}
|
|
160255
|
+
if (message.includes("already names a different message"))
|
|
160256
|
+
return new ORPCError("CONFLICT", { message });
|
|
160257
|
+
return new ORPCError("INTERNAL_SERVER_ERROR", { message: "the inbox request failed" });
|
|
160258
|
+
}
|
|
160259
|
+
function assertServedProject(ctx, projectPath) {
|
|
160260
|
+
if (projectPath === undefined || projectPath === "")
|
|
160261
|
+
return;
|
|
160262
|
+
const served = normalizeProjectPath(ctx.cwd);
|
|
160263
|
+
const claimed = normalizeProjectPath(projectPath);
|
|
160264
|
+
if (claimed !== served) {
|
|
160265
|
+
throw new ORPCError("CONFLICT", {
|
|
160266
|
+
message: `project mismatch: request targets ${claimed}, server serves ${served}`
|
|
160267
|
+
});
|
|
160268
|
+
}
|
|
160269
|
+
}
|
|
160270
|
+
function fleetOver(ctx) {
|
|
160271
|
+
const injected = ctx.fleetService;
|
|
160272
|
+
if (typeof injected === "function")
|
|
160273
|
+
return injected.call(ctx);
|
|
160274
|
+
return new FleetService({
|
|
160275
|
+
fs: ctx.fs,
|
|
160276
|
+
reloadAgentConfig: () => ctx.reloadAgentConfig(),
|
|
160277
|
+
openDb: () => ctx.getDb()
|
|
160278
|
+
});
|
|
160279
|
+
}
|
|
160280
|
+
async function addressableRecipients(ctx) {
|
|
160281
|
+
const addresses = new Set([OPERATOR_INBOX_ID, SPUR_INBOX_ID]);
|
|
160282
|
+
try {
|
|
160283
|
+
for (const spec of await ctx.coordination().listAgentSpecs())
|
|
160284
|
+
addresses.add(spec.id);
|
|
160285
|
+
} catch {}
|
|
160286
|
+
try {
|
|
160287
|
+
for (const member of await new FleetGuestService({
|
|
160288
|
+
cwd: ctx.cwd,
|
|
160289
|
+
fs: ctx.fs,
|
|
160290
|
+
getDb: () => ctx.getDb()
|
|
160291
|
+
}).list()) {
|
|
160292
|
+
addresses.add(member.id);
|
|
160293
|
+
}
|
|
160294
|
+
} catch {}
|
|
160295
|
+
try {
|
|
160296
|
+
for (const member of await ctx.inbox().members())
|
|
160297
|
+
addresses.add(member.id);
|
|
160298
|
+
} catch {}
|
|
160299
|
+
return addresses;
|
|
160300
|
+
}
|
|
160301
|
+
async function orchestratorAddress(ctx) {
|
|
160302
|
+
try {
|
|
160303
|
+
const { state, instanceId } = await fleetOver(ctx).resolveOrchestrator(normalizeProjectPath(ctx.cwd));
|
|
160304
|
+
if (state === "missing" || state === "unresolvable")
|
|
160305
|
+
return "";
|
|
160306
|
+
return typeof instanceId === "string" ? instanceId : "";
|
|
160307
|
+
} catch {
|
|
160308
|
+
return "";
|
|
160309
|
+
}
|
|
160310
|
+
}
|
|
160311
|
+
function createInboxHandlers(ctx) {
|
|
160312
|
+
return {
|
|
160313
|
+
list: os5.inbox.list.handler(async ({ input }) => {
|
|
160314
|
+
try {
|
|
160315
|
+
const page = await ctx.inbox().list(input.member, input.direction ?? "in", {
|
|
160316
|
+
...input.limit !== undefined ? { limit: input.limit } : {},
|
|
160317
|
+
...input.cursor !== undefined ? { cursor: input.cursor } : {}
|
|
160318
|
+
});
|
|
160319
|
+
return { messages: page.messages.map(toMessageDto), nextCursor: page.nextCursor };
|
|
160320
|
+
} catch (error) {
|
|
160321
|
+
throw toOrpcError(error, "BAD_REQUEST");
|
|
160322
|
+
}
|
|
160323
|
+
}),
|
|
160324
|
+
thread: os5.inbox.thread.handler(async ({ input }) => {
|
|
160325
|
+
const rows = await ctx.inbox().thread(input.id);
|
|
160326
|
+
if (rows.length === 0)
|
|
160327
|
+
throw new ORPCError("NOT_FOUND", { message: `no message ${input.id}` });
|
|
160328
|
+
return rows.map(toMessageDto);
|
|
160329
|
+
}),
|
|
160330
|
+
send: os5.inbox.send.handler(async ({ input }) => {
|
|
160331
|
+
assertServedProject(ctx, input.projectPath);
|
|
160332
|
+
let to = input.to;
|
|
160333
|
+
if (to === undefined) {
|
|
160334
|
+
to = await orchestratorAddress(ctx);
|
|
160335
|
+
if (to === "") {
|
|
160336
|
+
throw new ORPCError("BAD_REQUEST", {
|
|
160337
|
+
message: "no orchestrator member is bound for this project \u2014 pass `to` explicitly"
|
|
160338
|
+
});
|
|
160339
|
+
}
|
|
160340
|
+
}
|
|
160341
|
+
if (!(await addressableRecipients(ctx)).has(to)) {
|
|
160342
|
+
throw new ORPCError("BAD_REQUEST", {
|
|
160343
|
+
message: `unknown recipient "${to}" \u2014 no agent spec, fleet member, guest or inbox owns that address`
|
|
160344
|
+
});
|
|
160345
|
+
}
|
|
160346
|
+
try {
|
|
160347
|
+
const result = await ctx.inbox().send({
|
|
160348
|
+
from: input.from ?? OPERATOR_INBOX_ID,
|
|
160349
|
+
to,
|
|
160350
|
+
body: input.body,
|
|
160351
|
+
...input.kind !== undefined ? { kind: input.kind } : {},
|
|
160352
|
+
...input.requestKey !== undefined ? { requestKey: input.requestKey } : {},
|
|
160353
|
+
...input.taskId !== undefined ? { taskId: input.taskId } : {},
|
|
160354
|
+
...input.parentRunId !== undefined ? { parentRunId: input.parentRunId } : {}
|
|
160355
|
+
});
|
|
160356
|
+
return { message: toMessageDto(result.message), replayed: result.replayed };
|
|
160357
|
+
} catch (error) {
|
|
160358
|
+
if (error instanceof ORPCError)
|
|
160359
|
+
throw error;
|
|
160360
|
+
throw toOrpcError(error, "BAD_REQUEST");
|
|
160361
|
+
}
|
|
160362
|
+
}),
|
|
160363
|
+
reply: os5.inbox.reply.handler(async ({ input }) => {
|
|
160364
|
+
try {
|
|
160365
|
+
return toMessageDto(await ctx.inbox().reply(input.id, input.from, input.body));
|
|
160366
|
+
} catch (error) {
|
|
160367
|
+
throw toOrpcError(error, "NOT_FOUND");
|
|
160368
|
+
}
|
|
160369
|
+
}),
|
|
160370
|
+
read: os5.inbox.read.handler(async ({ input }) => {
|
|
160371
|
+
try {
|
|
160372
|
+
const receipt = await ctx.inbox().markRead(input.id, input.reader);
|
|
160373
|
+
return { readAt: receipt.readAt, transitioned: receipt.transitioned };
|
|
160374
|
+
} catch (error) {
|
|
160375
|
+
if (!(error instanceof MessageNotFoundError))
|
|
160376
|
+
throw toOrpcError(error, "NOT_FOUND");
|
|
160377
|
+
throw new ORPCError("NOT_FOUND", { message: "message not found for this reader" });
|
|
160378
|
+
}
|
|
160379
|
+
}),
|
|
160380
|
+
requests: os5.inbox.requests.handler(async ({ input }) => {
|
|
160381
|
+
const orchestratorId = await orchestratorAddress(ctx);
|
|
160382
|
+
if (orchestratorId === "")
|
|
160383
|
+
return [];
|
|
160384
|
+
const limit = input.limit ?? 50;
|
|
160385
|
+
const rows = await ctx.inbox().requests(orchestratorId);
|
|
160386
|
+
const reconciler = new DeliveryReconciler({ getDb: () => ctx.getDb() });
|
|
160387
|
+
const classified = await reconciler.classify(orchestratorId);
|
|
160388
|
+
const reasonByMessage = new Map(classified.map((item) => [item.messageId, item.reason]));
|
|
160389
|
+
const holdByTask = new Map;
|
|
160390
|
+
try {
|
|
160391
|
+
const runtime = new StrategyRuntime({
|
|
160392
|
+
openDb: () => ctx.getDb(),
|
|
160393
|
+
tasks: ctx.taskService(),
|
|
160394
|
+
fleet: fleetOver(ctx),
|
|
160395
|
+
dependencyBlocked: async () => null
|
|
160396
|
+
});
|
|
160397
|
+
const selected = await runtime.selectNext(normalizeProjectPath(ctx.cwd), { readOnly: true });
|
|
160398
|
+
for (const hold of selected.holds)
|
|
160399
|
+
holdByTask.set(hold.wbs, hold.reason);
|
|
160400
|
+
} catch {}
|
|
160401
|
+
return rows.slice(0, limit).map((row) => ({
|
|
160402
|
+
messageId: row.id,
|
|
160403
|
+
requestKey: row.requestKey,
|
|
160404
|
+
status: row.status,
|
|
160405
|
+
attempts: row.attempts,
|
|
160406
|
+
error: row.error,
|
|
160407
|
+
runId: row.runId ?? null,
|
|
160408
|
+
taskId: row.runTaskId ?? row.taskId,
|
|
160409
|
+
outcome: row.runOutcome ?? null,
|
|
160410
|
+
reason: reasonByMessage.get(row.id) ?? null,
|
|
160411
|
+
hold: holdByTask.get(row.runTaskId ?? row.taskId ?? "") ?? null
|
|
160412
|
+
}));
|
|
160413
|
+
}),
|
|
160414
|
+
members: os5.inbox.members.handler(async () => ctx.inbox().members())
|
|
160415
|
+
};
|
|
160416
|
+
}
|
|
160417
|
+
|
|
160418
|
+
// ../server/src/router.ts
|
|
160419
|
+
var os6 = implement(contract);
|
|
159899
160420
|
var EMPTY_BOARD_CATALOG = { catalogVersion: 1, host: null, modules: [] };
|
|
159900
160421
|
var stubCtx = new Proxy({}, {
|
|
159901
160422
|
get(_t, p) {
|
|
@@ -159904,7 +160425,7 @@ var stubCtx = new Proxy({}, {
|
|
|
159904
160425
|
});
|
|
159905
160426
|
function createRouter2(ctx) {
|
|
159906
160427
|
return {
|
|
159907
|
-
health:
|
|
160428
|
+
health: os6.health.handler(() => ({
|
|
159908
160429
|
status: "ok",
|
|
159909
160430
|
timestamp: new Date().toISOString(),
|
|
159910
160431
|
service: "spur",
|
|
@@ -159913,10 +160434,11 @@ function createRouter2(ctx) {
|
|
|
159913
160434
|
task: createTaskHandlers(ctx ?? stubCtx),
|
|
159914
160435
|
feature: createFeatureHandlers(ctx ?? stubCtx),
|
|
159915
160436
|
history: createHistoryHandlers(ctx ?? stubCtx),
|
|
160437
|
+
inbox: createInboxHandlers(ctx ?? stubCtx),
|
|
159916
160438
|
board: {
|
|
159917
|
-
modules:
|
|
160439
|
+
modules: os6.board.modules.handler(() => ctx?.boardModules?.catalog ?? EMPTY_BOARD_CATALOG)
|
|
159918
160440
|
},
|
|
159919
|
-
stream:
|
|
160441
|
+
stream: os6.stream.handler(async () => {
|
|
159920
160442
|
throw new Error("SSE stream served by raw Hono route (modules/events)");
|
|
159921
160443
|
})
|
|
159922
160444
|
};
|
|
@@ -160105,6 +160627,7 @@ function createServerContext(appRt, options) {
|
|
|
160105
160627
|
let featureSvc;
|
|
160106
160628
|
let historyBoardSvc;
|
|
160107
160629
|
let coordinationSvc;
|
|
160630
|
+
let inboxSvc;
|
|
160108
160631
|
let supervisorSvc;
|
|
160109
160632
|
let processInventorySvc;
|
|
160110
160633
|
let processRegistrySvc;
|
|
@@ -160281,6 +160804,11 @@ function createServerContext(appRt, options) {
|
|
|
160281
160804
|
}
|
|
160282
160805
|
return coordinationSvc;
|
|
160283
160806
|
},
|
|
160807
|
+
inbox() {
|
|
160808
|
+
if (!inboxSvc)
|
|
160809
|
+
inboxSvc = new InboxService({ getDb: this.getDb.bind(this) });
|
|
160810
|
+
return inboxSvc;
|
|
160811
|
+
},
|
|
160284
160812
|
reloadAgentConfig: async () => {
|
|
160285
160813
|
try {
|
|
160286
160814
|
return await loadSpurConfig(cwd);
|