@gobing-ai/spur 0.4.17 → 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 +12 -7
- package/package.json +1 -1
- package/plugins/sp/README.md +10 -6
- package/plugins/sp/agents/super-reviewer.md +3 -1
- 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 +1715 -1157
- 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;
|
|
@@ -120938,11 +121161,16 @@ async function persistWorktreeRuns(input) {
|
|
|
120938
121161
|
if (fromStat === undefined || !fromStat.isDirectory()) {
|
|
120939
121162
|
throw new Error(`persist-out: source worktree does not exist or is not a directory: ${input.fromWorkdir}`);
|
|
120940
121163
|
}
|
|
121164
|
+
const fs = createNodeFileSystem2();
|
|
121165
|
+
const fromReal = fs.realPath?.(fromDir) ?? fromDir;
|
|
121166
|
+
const toReal = fs.realPath?.(toDir) ?? toDir;
|
|
121167
|
+
if (fromReal === toReal) {
|
|
121168
|
+
throw new Error(`persist-out: source and destination are the same tree (${fromDir}) \u2014 run it from the invoking tree, or the evidence is copied onto itself and lost with the worktree`);
|
|
121169
|
+
}
|
|
120941
121170
|
const fromRunDir = join67(fromDir, ".spur", "run");
|
|
120942
121171
|
const toRunDir = join67(toDir, ".spur", "run");
|
|
120943
121172
|
const fromRecordsDir = runStoragePaths(fromDir).recordsDir;
|
|
120944
121173
|
const toRecordsDir = runStoragePaths(toDir).recordsDir;
|
|
120945
|
-
const fs = createNodeFileSystem2();
|
|
120946
121174
|
const fromEvidence = runStoragePaths(fromDir).evidenceDir;
|
|
120947
121175
|
const toEvidence = runStoragePaths(toDir).evidenceDir;
|
|
120948
121176
|
const evidenceCopies = [];
|
|
@@ -122377,6 +122605,7 @@ var init_src3 = __esm(() => {
|
|
|
122377
122605
|
init_history_board_service();
|
|
122378
122606
|
init_history_refresh_service();
|
|
122379
122607
|
init_history_service();
|
|
122608
|
+
init_inbox_service();
|
|
122380
122609
|
init_inline_run_attribution();
|
|
122381
122610
|
init_job_exclusion_guard();
|
|
122382
122611
|
init_member_session2();
|
|
@@ -142511,27 +142740,26 @@ async function runAgentLifecycle(action, agentId, options, context) {
|
|
|
142511
142740
|
async function settleClaimedMessages(context, claimed, outcome) {
|
|
142512
142741
|
if (claimed.length === 0)
|
|
142513
142742
|
return;
|
|
142514
|
-
const
|
|
142743
|
+
const inbox = new InboxService({ getDb: () => context.getDb() });
|
|
142515
142744
|
if (outcome === "accepted") {
|
|
142516
|
-
await
|
|
142745
|
+
await inbox.settle(claimed, "accepted");
|
|
142517
142746
|
return;
|
|
142518
142747
|
}
|
|
142519
|
-
const dao = new InboxMessageDao(await context.getDb());
|
|
142520
142748
|
const releasable = [];
|
|
142521
142749
|
const exhausted = [];
|
|
142522
142750
|
for (const id of claimed) {
|
|
142523
|
-
const row = await
|
|
142524
|
-
if (row?.status !== "
|
|
142751
|
+
const row = await inbox.get(id);
|
|
142752
|
+
if (row?.status !== "claimed")
|
|
142525
142753
|
continue;
|
|
142526
|
-
if (row.
|
|
142754
|
+
if (row.attempts >= MAX_DELIVERY_ATTEMPTS)
|
|
142527
142755
|
exhausted.push(id);
|
|
142528
142756
|
else
|
|
142529
142757
|
releasable.push(id);
|
|
142530
142758
|
}
|
|
142531
142759
|
if (releasable.length > 0)
|
|
142532
|
-
await
|
|
142533
|
-
|
|
142534
|
-
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`);
|
|
142535
142763
|
}
|
|
142536
142764
|
}
|
|
142537
142765
|
async function drainIntoPrompt(prompt, context, flags) {
|
|
@@ -142547,21 +142775,21 @@ async function drainIntoPrompt(prompt, context, flags) {
|
|
|
142547
142775
|
const flagsOut = spec === undefined ? flags : { ...flags, "spec-id": spec.id, agent: drainAgentSelector(spec, context) };
|
|
142548
142776
|
if (flags.drain !== true)
|
|
142549
142777
|
return { prompt, flags: flagsOut, claimed: [] };
|
|
142550
|
-
const
|
|
142551
|
-
if (
|
|
142778
|
+
const claimedRows = await new InboxService({ getDb: () => context.getDb() }).claim(recipient, flags.singleMessage === true ? 1 : 100);
|
|
142779
|
+
if (claimedRows.length === 0)
|
|
142552
142780
|
return { prompt, flags: flagsOut, claimed: [] };
|
|
142553
|
-
const header =
|
|
142781
|
+
const header = claimedRows.map((m) => `- ${m.fromId || "operator"}: ${m.body}`).join(`
|
|
142554
142782
|
`);
|
|
142555
142783
|
const block = `Pending messages:
|
|
142556
142784
|
${header}`;
|
|
142557
142785
|
const merged = prompt === undefined ? block : `${block}
|
|
142558
142786
|
|
|
142559
142787
|
${prompt}`;
|
|
142560
|
-
const claimed =
|
|
142561
|
-
const fleetTask =
|
|
142562
|
-
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);
|
|
142563
142791
|
const requestMessage = claimed.join(",");
|
|
142564
|
-
const requestKeys =
|
|
142792
|
+
const requestKeys = claimedRows.map((message) => message.requestKey);
|
|
142565
142793
|
return {
|
|
142566
142794
|
prompt: merged,
|
|
142567
142795
|
flags: {
|
|
@@ -142630,7 +142858,8 @@ async function makeFleetRuntime(context) {
|
|
|
142630
142858
|
const { foldersConfig } = await resolvePlanningFolders(context.fs);
|
|
142631
142859
|
const check = await makeCheckService(context);
|
|
142632
142860
|
return await check.firstBlockingPrerequisite(context.fs.resolve(foldersConfig.active_folder), wbs);
|
|
142633
|
-
}
|
|
142861
|
+
},
|
|
142862
|
+
guestExpiry: async () => await guestService(context).expire()
|
|
142634
142863
|
});
|
|
142635
142864
|
}
|
|
142636
142865
|
async function runAgentLoop(context, flags, runtime = {}, deps) {
|
|
@@ -143272,7 +143501,7 @@ async function runAgentWait(context, specId, options) {
|
|
|
143272
143501
|
return waitUsageError(context, options, "--until blocked has no first-class signal in this wave; use idle|working|invoke-exit");
|
|
143273
143502
|
}
|
|
143274
143503
|
const agentService = context.agentService();
|
|
143275
|
-
const
|
|
143504
|
+
const inbox = new InboxService({ getDb: () => context.getDb() });
|
|
143276
143505
|
const eventDao = new SystemEventDao(await context.getDb());
|
|
143277
143506
|
const controller = new AbortController;
|
|
143278
143507
|
const onSignal = () => controller.abort();
|
|
@@ -143292,7 +143521,7 @@ async function runAgentWait(context, specId, options) {
|
|
|
143292
143521
|
try {
|
|
143293
143522
|
result = await waitForOccupant({
|
|
143294
143523
|
getOccupant: (id) => agentService.getOccupant({ specId: id }),
|
|
143295
|
-
|
|
143524
|
+
countQueued: (id) => inbox.countQueued(id),
|
|
143296
143525
|
latestInvokeEvent: (r) => readLatestInvokeEvent(eventDao, r),
|
|
143297
143526
|
follow: (afterSequence) => followSystemEventsAfter(context.getDb, {
|
|
143298
143527
|
afterSequence,
|
|
@@ -146882,7 +147111,7 @@ import { createRequire as createRequire3 } from "module";
|
|
|
146882
147111
|
var CLI_CONFIG = {
|
|
146883
147112
|
binaryName: "spur",
|
|
146884
147113
|
binaryLabel: "spur",
|
|
146885
|
-
binaryVersion: "0.4.
|
|
147114
|
+
binaryVersion: "0.4.19",
|
|
146886
147115
|
configDir: ".spur",
|
|
146887
147116
|
configFile: ".spur/config.yaml",
|
|
146888
147117
|
databaseFile: ".spur/spur.db"
|
|
@@ -147817,13 +148046,231 @@ function formatMaintenanceResult(r) {
|
|
|
147817
148046
|
`);
|
|
147818
148047
|
}
|
|
147819
148048
|
|
|
147820
|
-
// src/commands/message.ts
|
|
148049
|
+
// src/commands/message/inbox.ts
|
|
148050
|
+
init_src3();
|
|
148051
|
+
|
|
148052
|
+
// src/commands/message/shared.ts
|
|
147821
148053
|
init_src3();
|
|
147822
|
-
init_src2();
|
|
147823
148054
|
var DEFAULT_FROM = "operator";
|
|
147824
148055
|
var DEFAULT_WATCH_INTERVAL_MS = 2000;
|
|
147825
|
-
function
|
|
147826
|
-
|
|
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) {
|
|
147827
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) => {
|
|
147828
148275
|
const svc = new AgentCoordinationService(context);
|
|
147829
148276
|
let toId = options.to;
|
|
@@ -147860,48 +148307,15 @@ function registerMessageCommand(program, context) {
|
|
|
147860
148307
|
}
|
|
147861
148308
|
toId = resolution.specId;
|
|
147862
148309
|
}
|
|
147863
|
-
const code = await runMessageSend(
|
|
148310
|
+
const code = await runMessageSend(context, body, {
|
|
147864
148311
|
...options,
|
|
147865
148312
|
to: toId ?? "",
|
|
147866
148313
|
requestKey: typeof options.requestKey === "string" && options.requestKey.length > 0 ? options.requestKey.trim() : undefined
|
|
147867
148314
|
});
|
|
147868
148315
|
context.setExitCode(code);
|
|
147869
148316
|
});
|
|
147870
|
-
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) => {
|
|
147871
|
-
const svc = new AgentCoordinationService(context);
|
|
147872
|
-
const code = await runMessageInbox(svc, context, options);
|
|
147873
|
-
context.setExitCode(code);
|
|
147874
|
-
});
|
|
147875
|
-
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) => {
|
|
147876
|
-
const svc = new AgentCoordinationService(context);
|
|
147877
|
-
const code = await runMessageReply(svc, context, msgId, body, options);
|
|
147878
|
-
context.setExitCode(code);
|
|
147879
|
-
});
|
|
147880
|
-
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) => {
|
|
147881
|
-
const svc = new AgentCoordinationService(context);
|
|
147882
|
-
const intervalMs = parseInterval2(options.interval);
|
|
147883
|
-
if (intervalMs === null) {
|
|
147884
|
-
writeJsonError(context.output, options, `invalid --interval "${options.interval}" (expected a positive integer ms)`, "VALIDATION_FAILED");
|
|
147885
|
-
context.setExitCode(2);
|
|
147886
|
-
return;
|
|
147887
|
-
}
|
|
147888
|
-
const controller = new AbortController;
|
|
147889
|
-
const onSigInt = () => controller.abort();
|
|
147890
|
-
process.on("SIGINT", onSigInt);
|
|
147891
|
-
try {
|
|
147892
|
-
await runMessageWatch(svc, context.output, {
|
|
147893
|
-
agent: options.agent,
|
|
147894
|
-
intervalMs,
|
|
147895
|
-
json: options.json ?? false
|
|
147896
|
-
}, {
|
|
147897
|
-
signal: controller.signal
|
|
147898
|
-
});
|
|
147899
|
-
} finally {
|
|
147900
|
-
process.off("SIGINT", onSigInt);
|
|
147901
|
-
}
|
|
147902
|
-
});
|
|
147903
148317
|
}
|
|
147904
|
-
async function runMessageSend(
|
|
148318
|
+
async function runMessageSend(context, body, options) {
|
|
147905
148319
|
const trimmed = body.trim();
|
|
147906
148320
|
if (trimmed === "") {
|
|
147907
148321
|
if (options.json) {
|
|
@@ -147918,22 +148332,36 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
147918
148332
|
}
|
|
147919
148333
|
return 2;
|
|
147920
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
|
+
}
|
|
147921
148339
|
if (options.to === "") {
|
|
147922
|
-
|
|
147923
|
-
|
|
147924
|
-
|
|
147925
|
-
|
|
147926
|
-
|
|
147927
|
-
|
|
147928
|
-
details: { cliCode: "usage" }
|
|
147929
|
-
}
|
|
147930
|
-
}
|
|
147931
|
-
|
|
147932
|
-
|
|
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;
|
|
147933
148352
|
}
|
|
147934
|
-
|
|
148353
|
+
options.to = resolved;
|
|
147935
148354
|
}
|
|
147936
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
|
+
}
|
|
147937
148365
|
let pinnedBeforeSend = null;
|
|
147938
148366
|
if (options.wait === true) {
|
|
147939
148367
|
const agentService = context.agentService();
|
|
@@ -147943,7 +148371,19 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
147943
148371
|
}
|
|
147944
148372
|
pinnedBeforeSend = { specId: options.to, runId: occupant.runId, generation: occupant.generation };
|
|
147945
148373
|
}
|
|
147946
|
-
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
|
+
};
|
|
147947
148387
|
if (options.wait !== true) {
|
|
147948
148388
|
if (options.json) {
|
|
147949
148389
|
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
@@ -147971,7 +148411,7 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
147971
148411
|
for (const until of untilList) {
|
|
147972
148412
|
if (until === "injected") {
|
|
147973
148413
|
try {
|
|
147974
|
-
await
|
|
148414
|
+
await waitForMessageClaim(context, result.msgId, agentService, pin, options.timeout, controller.signal);
|
|
147975
148415
|
satisfied = "injected";
|
|
147976
148416
|
break;
|
|
147977
148417
|
} catch (error) {
|
|
@@ -147986,7 +148426,7 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
147986
148426
|
try {
|
|
147987
148427
|
const res = await waitForOccupant({
|
|
147988
148428
|
getOccupant: (id) => agentService.getOccupant({ specId: id }),
|
|
147989
|
-
|
|
148429
|
+
countQueued: (id) => inboxOf(context).countQueued(id),
|
|
147990
148430
|
latestInvokeEvent: (r) => readLatestInvokeEvent2(eventDao, r),
|
|
147991
148431
|
follow: (afterSequence) => followSystemEventsAfter(context.getDb, {
|
|
147992
148432
|
afterSequence,
|
|
@@ -148029,62 +148469,35 @@ async function runMessageSend(svc, context, body, options) {
|
|
|
148029
148469
|
process.off("SIGTERM", onSignal);
|
|
148030
148470
|
}
|
|
148031
148471
|
}
|
|
148032
|
-
|
|
148033
|
-
|
|
148034
|
-
|
|
148035
|
-
|
|
148036
|
-
|
|
148037
|
-
|
|
148038
|
-
|
|
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;
|
|
148039
148484
|
}
|
|
148040
|
-
const inbox = await svc.getInbox(options.agent);
|
|
148041
|
-
const messages = options.unresolved === true ? inbox.messages.filter((m) => holds.has(m.id)) : inbox.messages;
|
|
148042
148485
|
if (options.json) {
|
|
148043
|
-
context.output.write(toEnvelopeJson({
|
|
148044
|
-
messages: messages.map((m) => widenInboxRow(m, holds.get(m.id))),
|
|
148045
|
-
count: messages.length
|
|
148046
|
-
}, { enveloped: options.jsonEnvelope }));
|
|
148486
|
+
context.output.write(toEnvelopeJson({ messages: rows, count: rows.length }, { enveloped: options.jsonEnvelope }));
|
|
148047
148487
|
return 0;
|
|
148048
148488
|
}
|
|
148049
|
-
|
|
148050
|
-
|
|
148051
|
-
|
|
148052
|
-
|
|
148053
|
-
|
|
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
|
+
|
|
148054
148496
|
`));
|
|
148055
148497
|
return 0;
|
|
148056
148498
|
}
|
|
148057
|
-
|
|
148058
|
-
|
|
148059
|
-
...entry,
|
|
148060
|
-
injectAttempts: entry.injectAttempts,
|
|
148061
|
-
injectError: entry.injectError,
|
|
148062
|
-
reason: hold?.reason,
|
|
148063
|
-
runId: hold?.runId,
|
|
148064
|
-
taskId: hold?.taskId,
|
|
148065
|
-
runStatus: hold?.runStatus,
|
|
148066
|
-
artifacts: hold?.artifacts ?? []
|
|
148067
|
-
};
|
|
148068
|
-
}
|
|
148069
|
-
async function runMessageReply(svc, context, msgId, body, options) {
|
|
148070
|
-
const trimmed = body.trim();
|
|
148071
|
-
if (trimmed === "") {
|
|
148072
|
-
writeJsonError(context.output, options, "message reply requires a non-empty body", "VALIDATION_FAILED");
|
|
148073
|
-
return 2;
|
|
148074
|
-
}
|
|
148075
|
-
const result = await svc.replyToMessage(msgId, trimmed);
|
|
148076
|
-
if (options.json) {
|
|
148077
|
-
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
148078
|
-
} else {
|
|
148079
|
-
context.output.write(`replied ${result.msgId} \u2192 ${result.toId}`);
|
|
148080
|
-
}
|
|
148081
|
-
return 0;
|
|
148082
|
-
}
|
|
148083
|
-
function formatInboxLine(entry) {
|
|
148084
|
-
const from = entry.fromId ?? DEFAULT_FROM;
|
|
148085
|
-
const body = entry.body.length > 60 ? `${entry.body.slice(0, 57)}...` : entry.body;
|
|
148086
|
-
return `${entry.id} ${entry.status} ${from} ${body} ${entry.createdAt}`;
|
|
148087
|
-
}
|
|
148499
|
+
|
|
148500
|
+
// src/commands/message/watch.ts
|
|
148088
148501
|
async function runMessageWatch(svc, output, options, runtime = {}) {
|
|
148089
148502
|
const sleep = runtime.sleep ?? ((ms) => defaultSleep(ms, runtime.signal));
|
|
148090
148503
|
const seen = new Set;
|
|
@@ -148094,9 +148507,9 @@ async function runMessageWatch(svc, output, options, runtime = {}) {
|
|
|
148094
148507
|
return;
|
|
148095
148508
|
if (runtime.maxIterations !== undefined && iteration >= runtime.maxIterations)
|
|
148096
148509
|
return;
|
|
148097
|
-
const
|
|
148098
|
-
for (let i =
|
|
148099
|
-
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];
|
|
148100
148513
|
if (!msg || seen.has(msg.id))
|
|
148101
148514
|
continue;
|
|
148102
148515
|
seen.add(msg.id);
|
|
@@ -148123,91 +148536,40 @@ function defaultSleep(ms, signal) {
|
|
|
148123
148536
|
}, { once: true });
|
|
148124
148537
|
});
|
|
148125
148538
|
}
|
|
148126
|
-
function
|
|
148127
|
-
|
|
148128
|
-
|
|
148129
|
-
|
|
148130
|
-
|
|
148131
|
-
|
|
148132
|
-
var SEND_UNTIL_STATES = new Set(["injected", "invoke-exit"]);
|
|
148133
|
-
function collectSendUntil(value, acc) {
|
|
148134
|
-
if (!SEND_UNTIL_STATES.has(value)) {
|
|
148135
|
-
throw new Error(`invalid --until "${value}" for send --wait (expected one of: ${[...SEND_UNTIL_STATES].join(", ")})`);
|
|
148136
|
-
}
|
|
148137
|
-
return [...acc, value];
|
|
148138
|
-
}
|
|
148139
|
-
function parseTimeout2(raw) {
|
|
148140
|
-
if (raw === undefined || typeof raw === "boolean")
|
|
148141
|
-
return;
|
|
148142
|
-
const n = Number.parseInt(raw, 10);
|
|
148143
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
148144
|
-
throw new Error(`invalid --timeout "${raw}" (expected a positive integer ms)`);
|
|
148145
|
-
}
|
|
148146
|
-
return n;
|
|
148147
|
-
}
|
|
148148
|
-
function sendSleep(ms, signal) {
|
|
148149
|
-
return new Promise((resolve) => {
|
|
148150
|
-
if (signal?.aborted) {
|
|
148151
|
-
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);
|
|
148152
148545
|
return;
|
|
148153
148546
|
}
|
|
148154
|
-
const
|
|
148155
|
-
|
|
148156
|
-
|
|
148157
|
-
|
|
148158
|
-
|
|
148159
|
-
|
|
148160
|
-
|
|
148161
|
-
|
|
148162
|
-
|
|
148163
|
-
|
|
148164
|
-
|
|
148165
|
-
|
|
148166
|
-
|
|
148167
|
-
|
|
148168
|
-
context.output.error(`${code}: ${message}`);
|
|
148169
|
-
}
|
|
148170
|
-
return 1;
|
|
148171
|
-
}
|
|
148172
|
-
async function readLatestInvokeEvent2(dao, runId) {
|
|
148173
|
-
const rows = await dao.query({
|
|
148174
|
-
run_id: runId,
|
|
148175
|
-
names: ["agent.invoke.start", "agent.invoke.exit"],
|
|
148176
|
-
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
|
+
}
|
|
148177
148561
|
});
|
|
148178
|
-
const row = rows[0];
|
|
148179
|
-
if (row === undefined)
|
|
148180
|
-
return null;
|
|
148181
|
-
return { eventName: row.event_name, sequence: row.sequence };
|
|
148182
148562
|
}
|
|
148183
|
-
|
|
148184
|
-
|
|
148185
|
-
|
|
148186
|
-
const
|
|
148187
|
-
|
|
148188
|
-
|
|
148189
|
-
|
|
148190
|
-
|
|
148191
|
-
|
|
148192
|
-
|
|
148193
|
-
throw new WaitError("timeout", `wait timed out after ${timeoutMs}ms`);
|
|
148194
|
-
const occupant = await agentService.getOccupant({ specId: pin.specId });
|
|
148195
|
-
if (occupant === null)
|
|
148196
|
-
throw new WaitError("occupant_gone", `occupant for specId "${pin.specId}" is gone`);
|
|
148197
|
-
if (occupant.runId !== pin.runId)
|
|
148198
|
-
throw new WaitError("run_replaced", `run ${pin.runId} replaced`);
|
|
148199
|
-
if (occupant.generation > pin.generation)
|
|
148200
|
-
throw new WaitError("run_replaced", `generation bumped`);
|
|
148201
|
-
const pending = await coordination.countPending(pin.specId);
|
|
148202
|
-
const progressed = lastPending >= 0 && pending !== lastPending;
|
|
148203
|
-
if (pending === 0)
|
|
148204
|
-
return;
|
|
148205
|
-
if (lastPending >= 0 && !progressed && clock - start >= stallMs) {
|
|
148206
|
-
throw new WaitError("wait_stalled", `inbox not draining within ${stallMs}ms`);
|
|
148207
|
-
}
|
|
148208
|
-
lastPending = pending;
|
|
148209
|
-
await sendSleep(100, signal);
|
|
148210
|
-
}
|
|
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);
|
|
148211
148573
|
}
|
|
148212
148574
|
|
|
148213
148575
|
// src/commands/migrate.ts
|
|
@@ -154915,6 +155277,134 @@ var historyContract = {
|
|
|
154915
155277
|
}).input(historyTriggerImportInputSchema).output(historyTriggerImportResponseSchema)
|
|
154916
155278
|
};
|
|
154917
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
|
+
|
|
154918
155408
|
// ../../packages/contracts/src/planning-event.ts
|
|
154919
155409
|
init_zod();
|
|
154920
155410
|
var planningEventEnvelopeSchema = apiSuccessSchema(object2({
|
|
@@ -155351,6 +155841,7 @@ var contract = {
|
|
|
155351
155841
|
board: { ...boardContract },
|
|
155352
155842
|
feature: { ...featureContract },
|
|
155353
155843
|
history: { ...historyContract },
|
|
155844
|
+
inbox: { ...inboxContract },
|
|
155354
155845
|
fleet: { ...fleetContract },
|
|
155355
155846
|
processes: { ...processesContract },
|
|
155356
155847
|
runs: { ...runsContract },
|
|
@@ -156344,6 +156835,9 @@ var databaseModule = {
|
|
|
156344
156835
|
}
|
|
156345
156836
|
};
|
|
156346
156837
|
|
|
156838
|
+
// ../server/src/modules/events/index.ts
|
|
156839
|
+
init_src3();
|
|
156840
|
+
|
|
156347
156841
|
// ../server/src/modules/sse/stream-helpers.ts
|
|
156348
156842
|
function sendSseKeepalive(closed, controller, encoder) {
|
|
156349
156843
|
if (closed.current)
|
|
@@ -156436,6 +156930,34 @@ function extractSystemEventActor2(event) {
|
|
|
156436
156930
|
function sendKeepalive(closed, controller, encoder) {
|
|
156437
156931
|
sendSseKeepalive(closed, controller, encoder);
|
|
156438
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
|
+
}
|
|
156439
156961
|
var eventsModule = {
|
|
156440
156962
|
name: "events",
|
|
156441
156963
|
mount(app, ctx) {
|
|
@@ -156447,6 +156969,7 @@ var eventsModule = {
|
|
|
156447
156969
|
const diagnosticEnabled = typeof ctx.bootConfig === "function" && ctx.bootConfig().events.diagnostic === true;
|
|
156448
156970
|
const diagnosticNames = diagnosticEnabled ? SYSTEM_EVENT_CATALOG.filter((entry) => entry.tier === "diagnostic" && entry.streamed).map((entry) => entry.name) : [];
|
|
156449
156971
|
const streamNames = [...baseNames, ...diagnosticNames];
|
|
156972
|
+
startMessageRelay(ctx);
|
|
156450
156973
|
app.get("/api/events/planning", (c) => {
|
|
156451
156974
|
const bus = ctx.eventBus();
|
|
156452
156975
|
const closed = { current: false };
|
|
@@ -156485,14 +157008,15 @@ var eventsModule = {
|
|
|
156485
157008
|
if (closed.current)
|
|
156486
157009
|
return;
|
|
156487
157010
|
const entry = systemEventCatalogEntry(name);
|
|
156488
|
-
const
|
|
157011
|
+
const ledger = event !== null && typeof event === "object" ? relayedRowColumns.get(event) : undefined;
|
|
157012
|
+
const actor = ledger !== undefined ? ledger.actor : extractSystemEventActor2(event);
|
|
156489
157013
|
const envelope = {
|
|
156490
157014
|
eventName: name,
|
|
156491
|
-
occurredAt: new Date().toISOString(),
|
|
157015
|
+
occurredAt: ledger?.occurredAt ?? new Date().toISOString(),
|
|
156492
157016
|
actor,
|
|
156493
157017
|
prefix: entry?.prefix ?? name.split(".")[0],
|
|
156494
157018
|
renderer: entry?.renderer ?? "generic",
|
|
156495
|
-
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)
|
|
156496
157020
|
};
|
|
156497
157021
|
try {
|
|
156498
157022
|
controller.enqueue(encoder.encode(`data: ${JSON.stringify(envelope)}
|
|
@@ -156730,11 +157254,10 @@ init_dist5();
|
|
|
156730
157254
|
import { homedir as homedir17 } from "os";
|
|
156731
157255
|
import { basename as basename20, join as join94, relative as relative19, resolve as resolve37 } from "path";
|
|
156732
157256
|
// ../server/package.json
|
|
156733
|
-
var version3 = "0.4.
|
|
157257
|
+
var version3 = "0.4.19";
|
|
156734
157258
|
|
|
156735
157259
|
// ../server/src/modules/health/index.ts
|
|
156736
157260
|
var startedAt = Date.now();
|
|
156737
|
-
var OPERATOR_AGENT_ID = "board-operator";
|
|
156738
157261
|
var healthModule = {
|
|
156739
157262
|
name: "health",
|
|
156740
157263
|
mount(app, ctx) {
|
|
@@ -157003,77 +157526,6 @@ var healthModule = {
|
|
|
157003
157526
|
executors
|
|
157004
157527
|
});
|
|
157005
157528
|
});
|
|
157006
|
-
app.get("/api/project/requests", async (c) => {
|
|
157007
|
-
if (!ctx) {
|
|
157008
|
-
return c.json({ requests: [] });
|
|
157009
|
-
}
|
|
157010
|
-
const limitRaw = c.req.query("limit");
|
|
157011
|
-
const parsedLimit = limitRaw === undefined ? NaN : Number.parseInt(limitRaw, 10);
|
|
157012
|
-
const limit = Number.isNaN(parsedLimit) || parsedLimit < 0 ? 50 : Math.min(parsedLimit, 200);
|
|
157013
|
-
const path = normalizeProjectPath(ctx.cwd);
|
|
157014
|
-
const openDb = () => ctx.getDb();
|
|
157015
|
-
const fleet = new FleetService({
|
|
157016
|
-
fs: ctx.fs,
|
|
157017
|
-
reloadAgentConfig: () => ctx.reloadAgentConfig(),
|
|
157018
|
-
openDb
|
|
157019
|
-
});
|
|
157020
|
-
const runtime = new StrategyRuntime({
|
|
157021
|
-
openDb,
|
|
157022
|
-
tasks: ctx.taskService(),
|
|
157023
|
-
fleet,
|
|
157024
|
-
dependencyBlocked: async () => null
|
|
157025
|
-
});
|
|
157026
|
-
let orchestrator = { state: "unresolvable", reason: "unavailable" };
|
|
157027
|
-
try {
|
|
157028
|
-
const { state, instanceId, holderId, reason } = await fleet.resolveOrchestrator(path);
|
|
157029
|
-
orchestrator = { state, instanceId, holderId, reason };
|
|
157030
|
-
} catch {
|
|
157031
|
-
orchestrator = { state: "unresolvable", reason: "unavailable" };
|
|
157032
|
-
}
|
|
157033
|
-
const instanceId = orchestrator.state === "missing" || orchestrator.state === "unresolvable" ? undefined : orchestrator.instanceId;
|
|
157034
|
-
if (instanceId === undefined) {
|
|
157035
|
-
return c.json({ requests: [] });
|
|
157036
|
-
}
|
|
157037
|
-
const db = await openDb();
|
|
157038
|
-
const unresolved = await new DeliveryReconciler({ getDb: openDb }).classify(instanceId);
|
|
157039
|
-
try {
|
|
157040
|
-
await new FleetGuestService({
|
|
157041
|
-
cwd: ctx.cwd,
|
|
157042
|
-
fs: ctx.fs ?? createNodeFileSystem2(ctx.cwd),
|
|
157043
|
-
getDb: openDb
|
|
157044
|
-
}).expire();
|
|
157045
|
-
} catch {}
|
|
157046
|
-
const reasonByMessage = new Map(unresolved.map((u) => [u.messageId, u.reason]));
|
|
157047
|
-
const runs = new CoordinationRunDao(db);
|
|
157048
|
-
const holdByTask = new Map;
|
|
157049
|
-
try {
|
|
157050
|
-
for (const h of (await runtime.selectNext(path, { readOnly: true })).holds) {
|
|
157051
|
-
holdByTask.set(h.wbs, h.reason);
|
|
157052
|
-
}
|
|
157053
|
-
} catch {}
|
|
157054
|
-
const REQUESTED_RUN_OUTCOMES = ["run-exit-only", "errored", "verified"];
|
|
157055
|
-
const requests = [];
|
|
157056
|
-
for (const row of await new InboxMessageDao(db).inbox(instanceId, 200)) {
|
|
157057
|
-
if (row.fromId !== OPERATOR_AGENT_ID)
|
|
157058
|
-
continue;
|
|
157059
|
-
if (requests.length >= limit)
|
|
157060
|
-
break;
|
|
157061
|
-
const runRow = (await runs.listByMessageId(row.id))[0];
|
|
157062
|
-
requests.push({
|
|
157063
|
-
messageId: row.id,
|
|
157064
|
-
requestKey: row.requestKey,
|
|
157065
|
-
deliveryStatus: row.status,
|
|
157066
|
-
injectAttempts: row.injectAttempts,
|
|
157067
|
-
injectError: row.injectError,
|
|
157068
|
-
runId: runRow?.run_id ?? null,
|
|
157069
|
-
taskId: runRow?.task_id ?? null,
|
|
157070
|
-
outcome: runRow !== undefined && REQUESTED_RUN_OUTCOMES.includes(runRow.outcome) ? runRow.outcome : null,
|
|
157071
|
-
reason: reasonByMessage.get(row.id) ?? null,
|
|
157072
|
-
hold: runRow?.task_id !== null && runRow?.task_id !== undefined ? holdByTask.get(runRow.task_id) ?? null : null
|
|
157073
|
-
});
|
|
157074
|
-
}
|
|
157075
|
-
return c.json({ requests });
|
|
157076
|
-
});
|
|
157077
157529
|
app.get("/api/projects", async (c) => {
|
|
157078
157530
|
if (!ctx) {
|
|
157079
157531
|
return c.json({ projects: [] });
|
|
@@ -157735,130 +158187,6 @@ var jobsModule = {
|
|
|
157735
158187
|
}
|
|
157736
158188
|
};
|
|
157737
158189
|
|
|
157738
|
-
// ../server/src/modules/messages/index.ts
|
|
157739
|
-
init_src3();
|
|
157740
|
-
var messagesModule = {
|
|
157741
|
-
name: "messages",
|
|
157742
|
-
mount(app, ctx) {
|
|
157743
|
-
if (!ctx)
|
|
157744
|
-
return;
|
|
157745
|
-
app.get("/api/messages/inbox", async (c) => {
|
|
157746
|
-
const agent = c.req.query("agent");
|
|
157747
|
-
if (!agent) {
|
|
157748
|
-
return c.json({ error: "missing required query parameter: agent" }, 400);
|
|
157749
|
-
}
|
|
157750
|
-
const limit = parseLimit(c.req.query("limit"), 50);
|
|
157751
|
-
const offset = parseOffset(c.req.query("offset"));
|
|
157752
|
-
const svc = ctx.coordination();
|
|
157753
|
-
const result = await svc.getInbox(agent, limit, offset);
|
|
157754
|
-
return c.json(result);
|
|
157755
|
-
});
|
|
157756
|
-
app.get("/api/messages", async (c) => {
|
|
157757
|
-
const limit = parseLimit(c.req.query("limit"), 50);
|
|
157758
|
-
const svc = ctx.coordination();
|
|
157759
|
-
const result = await svc.listRecent(limit);
|
|
157760
|
-
return c.json(result);
|
|
157761
|
-
});
|
|
157762
|
-
app.post("/api/messages", async (c) => {
|
|
157763
|
-
const parsed = await parseJsonBody(c);
|
|
157764
|
-
if ("error" in parsed)
|
|
157765
|
-
return c.json({ error: parsed.error }, 400);
|
|
157766
|
-
const { to, body, from, requestKey, projectPath } = parsed;
|
|
157767
|
-
if (typeof to !== "string" || to.length === 0)
|
|
157768
|
-
return c.json({ error: 'field "to" is required' }, 400);
|
|
157769
|
-
if (typeof body !== "string" || body.length === 0)
|
|
157770
|
-
return c.json({ error: 'field "body" is required' }, 400);
|
|
157771
|
-
if (from !== undefined && (typeof from !== "string" || from.length === 0)) {
|
|
157772
|
-
return c.json({ error: 'field "from" must be a non-empty string when present' }, 400);
|
|
157773
|
-
}
|
|
157774
|
-
if (requestKey !== undefined && (typeof requestKey !== "string" || requestKey.length === 0)) {
|
|
157775
|
-
return c.json({ error: 'field "requestKey" must be a non-empty string when present' }, 400);
|
|
157776
|
-
}
|
|
157777
|
-
if (projectPath !== undefined && (typeof projectPath !== "string" || projectPath.length === 0)) {
|
|
157778
|
-
return c.json({ error: 'field "projectPath" must be a non-empty string when present' }, 400);
|
|
157779
|
-
}
|
|
157780
|
-
if (typeof projectPath === "string" && projectPath.length > 0) {
|
|
157781
|
-
const served = normalizeProjectPath(ctx.cwd);
|
|
157782
|
-
const claimed = normalizeProjectPath(projectPath);
|
|
157783
|
-
if (claimed !== served) {
|
|
157784
|
-
return c.json({ error: `project mismatch: request targets ${claimed}, server serves ${served}` }, 409);
|
|
157785
|
-
}
|
|
157786
|
-
}
|
|
157787
|
-
const svc = ctx.coordination();
|
|
157788
|
-
try {
|
|
157789
|
-
const result = await svc.sendMessage(from ?? null, to, body, undefined, requestKey);
|
|
157790
|
-
return c.json(result, 201);
|
|
157791
|
-
} catch (err) {
|
|
157792
|
-
return c.json({ error: errMsg(err) }, 400);
|
|
157793
|
-
}
|
|
157794
|
-
});
|
|
157795
|
-
app.post("/api/messages/:id/reply", async (c) => {
|
|
157796
|
-
const id = c.req.param("id");
|
|
157797
|
-
const parsed = await parseJsonBody(c);
|
|
157798
|
-
if ("error" in parsed)
|
|
157799
|
-
return c.json({ error: parsed.error }, 400);
|
|
157800
|
-
const { body } = parsed;
|
|
157801
|
-
if (typeof body !== "string" || body.length === 0)
|
|
157802
|
-
return c.json({ error: 'field "body" is required' }, 400);
|
|
157803
|
-
const svc = ctx.coordination();
|
|
157804
|
-
try {
|
|
157805
|
-
const result = await svc.replyToMessage(id, body);
|
|
157806
|
-
return c.json(result, 201);
|
|
157807
|
-
} catch (err) {
|
|
157808
|
-
return c.json({ error: errMsg(err) }, 400);
|
|
157809
|
-
}
|
|
157810
|
-
});
|
|
157811
|
-
app.post("/api/messages/:id/read", async (c) => {
|
|
157812
|
-
const id = c.req.param("id");
|
|
157813
|
-
const parsed = await parseJsonBody(c);
|
|
157814
|
-
if ("error" in parsed)
|
|
157815
|
-
return c.json({ error: parsed.error }, 400);
|
|
157816
|
-
const { reader } = parsed;
|
|
157817
|
-
if (typeof reader !== "string" || reader.length === 0)
|
|
157818
|
-
return c.json({ error: 'field "reader" is required' }, 400);
|
|
157819
|
-
try {
|
|
157820
|
-
return c.json(await ctx.coordination().markMessageRead(id, reader));
|
|
157821
|
-
} catch (err) {
|
|
157822
|
-
return c.json({ error: errMsg(err) }, err instanceof MessageNotFoundError ? 404 : 400);
|
|
157823
|
-
}
|
|
157824
|
-
});
|
|
157825
|
-
}
|
|
157826
|
-
};
|
|
157827
|
-
async function parseJsonBody(c) {
|
|
157828
|
-
let json;
|
|
157829
|
-
try {
|
|
157830
|
-
json = await c.req.json();
|
|
157831
|
-
} catch {
|
|
157832
|
-
return { error: "request body must be valid JSON" };
|
|
157833
|
-
}
|
|
157834
|
-
if (typeof json !== "object" || json === null || Array.isArray(json)) {
|
|
157835
|
-
return { error: "request body must be a JSON object" };
|
|
157836
|
-
}
|
|
157837
|
-
return json;
|
|
157838
|
-
}
|
|
157839
|
-
function errMsg(err) {
|
|
157840
|
-
if (err instanceof Error)
|
|
157841
|
-
return err.message;
|
|
157842
|
-
return String(err);
|
|
157843
|
-
}
|
|
157844
|
-
function parseLimit(raw, fallback) {
|
|
157845
|
-
if (raw === undefined)
|
|
157846
|
-
return fallback;
|
|
157847
|
-
const parsed = Number.parseInt(raw, 10);
|
|
157848
|
-
if (Number.isNaN(parsed) || parsed < 0)
|
|
157849
|
-
return fallback;
|
|
157850
|
-
return Math.min(parsed, 500);
|
|
157851
|
-
}
|
|
157852
|
-
function parseOffset(raw) {
|
|
157853
|
-
if (raw === undefined)
|
|
157854
|
-
return;
|
|
157855
|
-
const trimmed = raw.trim();
|
|
157856
|
-
if (!/^[0-9]+$/.test(trimmed))
|
|
157857
|
-
return 0;
|
|
157858
|
-
const parsed = Number(trimmed);
|
|
157859
|
-
return Number.isSafeInteger(parsed) ? parsed : 0;
|
|
157860
|
-
}
|
|
157861
|
-
|
|
157862
158190
|
// ../server/src/modules/observability/index.ts
|
|
157863
158191
|
init_src3();
|
|
157864
158192
|
init_src2();
|
|
@@ -158563,7 +158891,6 @@ var builtins = [
|
|
|
158563
158891
|
taskModule,
|
|
158564
158892
|
featureModule,
|
|
158565
158893
|
eventsModule,
|
|
158566
|
-
messagesModule,
|
|
158567
158894
|
jobsModule,
|
|
158568
158895
|
processesModule,
|
|
158569
158896
|
observabilityModule,
|
|
@@ -159889,8 +160216,207 @@ async function generateOpenApiSpec(pluginPaths = {}) {
|
|
|
159889
160216
|
return spec;
|
|
159890
160217
|
}
|
|
159891
160218
|
|
|
159892
|
-
// ../server/src/
|
|
160219
|
+
// ../server/src/modules/inbox/handlers.ts
|
|
160220
|
+
init_src3();
|
|
159893
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);
|
|
159894
160420
|
var EMPTY_BOARD_CATALOG = { catalogVersion: 1, host: null, modules: [] };
|
|
159895
160421
|
var stubCtx = new Proxy({}, {
|
|
159896
160422
|
get(_t, p) {
|
|
@@ -159899,7 +160425,7 @@ var stubCtx = new Proxy({}, {
|
|
|
159899
160425
|
});
|
|
159900
160426
|
function createRouter2(ctx) {
|
|
159901
160427
|
return {
|
|
159902
|
-
health:
|
|
160428
|
+
health: os6.health.handler(() => ({
|
|
159903
160429
|
status: "ok",
|
|
159904
160430
|
timestamp: new Date().toISOString(),
|
|
159905
160431
|
service: "spur",
|
|
@@ -159908,10 +160434,11 @@ function createRouter2(ctx) {
|
|
|
159908
160434
|
task: createTaskHandlers(ctx ?? stubCtx),
|
|
159909
160435
|
feature: createFeatureHandlers(ctx ?? stubCtx),
|
|
159910
160436
|
history: createHistoryHandlers(ctx ?? stubCtx),
|
|
160437
|
+
inbox: createInboxHandlers(ctx ?? stubCtx),
|
|
159911
160438
|
board: {
|
|
159912
|
-
modules:
|
|
160439
|
+
modules: os6.board.modules.handler(() => ctx?.boardModules?.catalog ?? EMPTY_BOARD_CATALOG)
|
|
159913
160440
|
},
|
|
159914
|
-
stream:
|
|
160441
|
+
stream: os6.stream.handler(async () => {
|
|
159915
160442
|
throw new Error("SSE stream served by raw Hono route (modules/events)");
|
|
159916
160443
|
})
|
|
159917
160444
|
};
|
|
@@ -160100,6 +160627,7 @@ function createServerContext(appRt, options) {
|
|
|
160100
160627
|
let featureSvc;
|
|
160101
160628
|
let historyBoardSvc;
|
|
160102
160629
|
let coordinationSvc;
|
|
160630
|
+
let inboxSvc;
|
|
160103
160631
|
let supervisorSvc;
|
|
160104
160632
|
let processInventorySvc;
|
|
160105
160633
|
let processRegistrySvc;
|
|
@@ -160276,6 +160804,11 @@ function createServerContext(appRt, options) {
|
|
|
160276
160804
|
}
|
|
160277
160805
|
return coordinationSvc;
|
|
160278
160806
|
},
|
|
160807
|
+
inbox() {
|
|
160808
|
+
if (!inboxSvc)
|
|
160809
|
+
inboxSvc = new InboxService({ getDb: this.getDb.bind(this) });
|
|
160810
|
+
return inboxSvc;
|
|
160811
|
+
},
|
|
160279
160812
|
reloadAgentConfig: async () => {
|
|
160280
160813
|
try {
|
|
160281
160814
|
return await loadSpurConfig(cwd);
|
|
@@ -161784,6 +162317,7 @@ function registerWorkflowCleanCommand(workflow, context) {
|
|
|
161784
162317
|
}
|
|
161785
162318
|
|
|
161786
162319
|
// ../../packages/app/src/workflow/commit-guard.ts
|
|
162320
|
+
init_src();
|
|
161787
162321
|
init_dist5();
|
|
161788
162322
|
import { mkdirSync as mkdirSync23, readFileSync as readFileSync28, renameSync as renameSync10, writeFileSync as writeFileSync20 } from "fs";
|
|
161789
162323
|
import { join as join100 } from "path";
|
|
@@ -161954,6 +162488,26 @@ function commitGuardStage(run, paths, options = {}) {
|
|
|
161954
162488
|
foreign
|
|
161955
162489
|
};
|
|
161956
162490
|
}
|
|
162491
|
+
function foreignSessionOwner(cwd) {
|
|
162492
|
+
try {
|
|
162493
|
+
const raw = JSON.parse(readFileSync28(join100(cwd, ".spur", "context", ".session.json"), "utf8"));
|
|
162494
|
+
const sessionId = typeof raw.owner?.sessionId === "string" ? raw.owner.sessionId : "";
|
|
162495
|
+
if (sessionId === "")
|
|
162496
|
+
return null;
|
|
162497
|
+
const current = currentHostSessionId();
|
|
162498
|
+
if (current === "" || current === sessionId)
|
|
162499
|
+
return null;
|
|
162500
|
+
return { host: typeof raw.owner?.host === "string" ? raw.owner.host : "unknown", sessionId };
|
|
162501
|
+
} catch {
|
|
162502
|
+
return null;
|
|
162503
|
+
}
|
|
162504
|
+
}
|
|
162505
|
+
function currentHostSessionId() {
|
|
162506
|
+
const transcript = getEnvVar("PI_SESSION_FILE") ?? "";
|
|
162507
|
+
const stem = transcript.replace(/\.jsonl$/, "").split("/").pop() ?? "";
|
|
162508
|
+
const pi = stem.includes("_") ? stem.split("_").pop() ?? "" : "";
|
|
162509
|
+
return pi !== "" ? pi : (getEnvVar("CLAUDE_CODE_SESSION_ID") ?? "").trim();
|
|
162510
|
+
}
|
|
161957
162511
|
function commitGuardCheck(run, options = {}) {
|
|
161958
162512
|
try {
|
|
161959
162513
|
const context = runContext(run, options);
|
|
@@ -161961,7 +162515,11 @@ function commitGuardCheck(run, options = {}) {
|
|
|
161961
162515
|
return missingStart(run);
|
|
161962
162516
|
const { base, git, written } = context;
|
|
161963
162517
|
const foreign = [...changedSinceBase(base, git)].filter((path) => !written.has(path)).sort();
|
|
161964
|
-
|
|
162518
|
+
const owner = foreignSessionOwner(options.cwd ?? process.cwd());
|
|
162519
|
+
const stderr = owner ? [
|
|
162520
|
+
`commit-guard: another session owns this tree \u2014 .spur/context/.session.json records ${owner.host} ${owner.sessionId}; use a separate worktree (one writer per tree)`
|
|
162521
|
+
] : [];
|
|
162522
|
+
return { ...report(0, [JSON.stringify({ foreign, ...owner ? { foreignSession: owner } : {} })], stderr) };
|
|
161965
162523
|
} catch (err) {
|
|
161966
162524
|
return report(1, [], [probeDiagnostic(err)]);
|
|
161967
162525
|
}
|