@holon-run/agentinbox 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/service.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ActivationDispatcher = exports.AgentInboxService = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
4
5
|
const backend_1 = require("./backend");
|
|
5
6
|
const util_1 = require("./util");
|
|
6
7
|
const source_schema_1 = require("./source_schema");
|
|
@@ -1407,15 +1408,26 @@ class AgentInboxService {
|
|
|
1407
1408
|
const state = this.store.getActivationDispatchState(buffer.agentId, buffer.targetId);
|
|
1408
1409
|
if (!state) {
|
|
1409
1410
|
const inbox = this.ensureInboxForAgent(buffer.agentId);
|
|
1411
|
+
const unackedItems = this.store.listInboxItems(inbox.inboxId, { includeAcked: false }).map((item) => ({
|
|
1412
|
+
itemId: item.itemId,
|
|
1413
|
+
sourceId: item.sourceId,
|
|
1414
|
+
sourceNativeId: item.sourceNativeId,
|
|
1415
|
+
eventVariant: item.eventVariant,
|
|
1416
|
+
inboxId: item.inboxId,
|
|
1417
|
+
occurredAt: item.occurredAt,
|
|
1418
|
+
metadata: item.metadata,
|
|
1419
|
+
rawPayload: item.rawPayload,
|
|
1420
|
+
deliveryHandle: item.deliveryHandle,
|
|
1421
|
+
}));
|
|
1410
1422
|
const dispatched = await this.dispatchActivationTarget({
|
|
1411
1423
|
agentId: buffer.agentId,
|
|
1412
1424
|
targetId: buffer.targetId,
|
|
1413
1425
|
newItemCount: entries.length,
|
|
1414
|
-
totalUnackedCount:
|
|
1415
|
-
summary: entries
|
|
1426
|
+
totalUnackedCount: unackedItems.length,
|
|
1427
|
+
summary: latestSummary(entries),
|
|
1416
1428
|
subscriptionIds: uniqueSortedNullable(entries.map((entry) => entry.subscriptionId)),
|
|
1417
1429
|
sourceIds: uniqueSorted(entries.map((entry) => entry.sourceId)),
|
|
1418
|
-
items:
|
|
1430
|
+
items: unackedItems,
|
|
1419
1431
|
});
|
|
1420
1432
|
if (dispatched === "retryable_failure") {
|
|
1421
1433
|
this.upsertDirtyDispatchState(buffer.agentId, buffer.targetId, entries);
|
|
@@ -1427,8 +1439,9 @@ class AgentInboxService {
|
|
|
1427
1439
|
targetId: buffer.targetId,
|
|
1428
1440
|
status: "dirty",
|
|
1429
1441
|
leaseExpiresAt: state.leaseExpiresAt,
|
|
1442
|
+
lastNotifiedFingerprint: state.lastNotifiedFingerprint,
|
|
1430
1443
|
pendingNewItemCount: state.pendingNewItemCount + entries.length,
|
|
1431
|
-
pendingSummary:
|
|
1444
|
+
pendingSummary: latestSummary(entries) ?? state.pendingSummary,
|
|
1432
1445
|
pendingSubscriptionIds: uniqueSortedNullable([...state.pendingSubscriptionIds, ...entries.map((entry) => entry.subscriptionId)]),
|
|
1433
1446
|
pendingSourceIds: uniqueSorted([...state.pendingSourceIds, ...entries.map((entry) => entry.sourceId)]),
|
|
1434
1447
|
updatedAt: (0, util_1.nowIso)(),
|
|
@@ -1477,6 +1490,20 @@ class AgentInboxService {
|
|
|
1477
1490
|
if (reason === "ack" && state.status !== "dirty") {
|
|
1478
1491
|
return;
|
|
1479
1492
|
}
|
|
1493
|
+
if (reason === "lease" && state.status === "notified") {
|
|
1494
|
+
const target = this.getActivationTarget(targetId);
|
|
1495
|
+
if (target.kind !== "terminal") {
|
|
1496
|
+
// Webhook targets keep the existing lease-based behavior.
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
this.store.upsertActivationDispatchState({
|
|
1500
|
+
...state,
|
|
1501
|
+
leaseExpiresAt: new Date(Date.now() + target.notifyLeaseMs).toISOString(),
|
|
1502
|
+
updatedAt: (0, util_1.nowIso)(),
|
|
1503
|
+
});
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1480
1507
|
const dispatched = await this.dispatchActivationTarget({
|
|
1481
1508
|
agentId,
|
|
1482
1509
|
targetId,
|
|
@@ -1515,9 +1542,11 @@ class AgentInboxService {
|
|
|
1515
1542
|
if (target.status === "offline") {
|
|
1516
1543
|
return "offline";
|
|
1517
1544
|
}
|
|
1545
|
+
const state = this.store.getActivationDispatchState(input.agentId, input.targetId);
|
|
1518
1546
|
const inbox = this.ensureInboxForAgent(input.agentId);
|
|
1519
1547
|
const summary = summarizeActivation(inbox.inboxId, input.newItemCount, input.summary);
|
|
1520
1548
|
const totalUnackedCount = input.totalUnackedCount ?? this.store.countInboxItems(inbox.inboxId, false);
|
|
1549
|
+
let terminalPrompt = null;
|
|
1521
1550
|
try {
|
|
1522
1551
|
if (target.kind === "terminal") {
|
|
1523
1552
|
const gate = await this.activationGate.evaluate(target);
|
|
@@ -1532,6 +1561,7 @@ class AgentInboxService {
|
|
|
1532
1561
|
targetId: input.targetId,
|
|
1533
1562
|
status: "dirty",
|
|
1534
1563
|
leaseExpiresAt: new Date(Date.now() + DEFAULT_NOTIFY_RETRY_MS).toISOString(),
|
|
1564
|
+
lastNotifiedFingerprint: state?.lastNotifiedFingerprint ?? null,
|
|
1535
1565
|
pendingNewItemCount: input.newItemCount,
|
|
1536
1566
|
pendingSummary: input.summary,
|
|
1537
1567
|
pendingSubscriptionIds: uniqueSortedNullable(input.subscriptionIds),
|
|
@@ -1540,11 +1570,32 @@ class AgentInboxService {
|
|
|
1540
1570
|
});
|
|
1541
1571
|
return "deferred";
|
|
1542
1572
|
}
|
|
1573
|
+
const preview = totalUnackedCount === 1 && input.items.length === 1
|
|
1574
|
+
? (0, terminal_1.deriveInlineItemPreview)(input.items[0], input.summary)
|
|
1575
|
+
: null;
|
|
1543
1576
|
const prompt = (0, terminal_1.renderAgentPrompt)({
|
|
1544
1577
|
inboxId: inbox.inboxId,
|
|
1545
1578
|
totalUnackedCount,
|
|
1546
1579
|
summary: input.summary,
|
|
1580
|
+
preview,
|
|
1547
1581
|
});
|
|
1582
|
+
terminalPrompt = prompt;
|
|
1583
|
+
const promptFingerprint = terminalReminderFingerprint(prompt, input.items);
|
|
1584
|
+
if (state?.status === "dirty" && state.lastNotifiedFingerprint === promptFingerprint) {
|
|
1585
|
+
this.store.upsertActivationDispatchState({
|
|
1586
|
+
agentId: input.agentId,
|
|
1587
|
+
targetId: input.targetId,
|
|
1588
|
+
status: "notified",
|
|
1589
|
+
leaseExpiresAt: new Date(Date.now() + target.notifyLeaseMs).toISOString(),
|
|
1590
|
+
lastNotifiedFingerprint: promptFingerprint,
|
|
1591
|
+
pendingNewItemCount: 0,
|
|
1592
|
+
pendingSummary: null,
|
|
1593
|
+
pendingSubscriptionIds: [],
|
|
1594
|
+
pendingSourceIds: [],
|
|
1595
|
+
updatedAt: (0, util_1.nowIso)(),
|
|
1596
|
+
});
|
|
1597
|
+
return "suppressed";
|
|
1598
|
+
}
|
|
1548
1599
|
await this.terminalDispatcher.dispatch(target, prompt);
|
|
1549
1600
|
}
|
|
1550
1601
|
else {
|
|
@@ -1573,6 +1624,9 @@ class AgentInboxService {
|
|
|
1573
1624
|
targetId: input.targetId,
|
|
1574
1625
|
status: "notified",
|
|
1575
1626
|
leaseExpiresAt: new Date(Date.now() + target.notifyLeaseMs).toISOString(),
|
|
1627
|
+
lastNotifiedFingerprint: target.kind === "terminal"
|
|
1628
|
+
? terminalReminderFingerprint(terminalPrompt ?? "", input.items)
|
|
1629
|
+
: state?.lastNotifiedFingerprint ?? null,
|
|
1576
1630
|
pendingNewItemCount: 0,
|
|
1577
1631
|
pendingSummary: null,
|
|
1578
1632
|
pendingSubscriptionIds: [],
|
|
@@ -1602,8 +1656,9 @@ class AgentInboxService {
|
|
|
1602
1656
|
targetId,
|
|
1603
1657
|
status: "dirty",
|
|
1604
1658
|
leaseExpiresAt: new Date(Date.now() + DEFAULT_NOTIFY_RETRY_MS).toISOString(),
|
|
1659
|
+
lastNotifiedFingerprint: this.store.getActivationDispatchState(agentId, targetId)?.lastNotifiedFingerprint ?? null,
|
|
1605
1660
|
pendingNewItemCount: entries.length,
|
|
1606
|
-
pendingSummary: entries
|
|
1661
|
+
pendingSummary: latestSummary(entries),
|
|
1607
1662
|
pendingSubscriptionIds: uniqueSortedNullable(entries.map((entry) => entry.subscriptionId)),
|
|
1608
1663
|
pendingSourceIds: uniqueSorted(entries.map((entry) => entry.sourceId)),
|
|
1609
1664
|
updatedAt: (0, util_1.nowIso)(),
|
|
@@ -2288,6 +2343,24 @@ function summarizeActivation(inboxId, newItemCount, firstSummary) {
|
|
|
2288
2343
|
}
|
|
2289
2344
|
return `${newItemCount} new ${itemWord} in ${inboxId}`;
|
|
2290
2345
|
}
|
|
2346
|
+
function latestSummary(entries) {
|
|
2347
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
2348
|
+
const summary = entries[index]?.summary;
|
|
2349
|
+
if (summary) {
|
|
2350
|
+
return summary;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
return null;
|
|
2354
|
+
}
|
|
2355
|
+
function terminalReminderFingerprint(prompt, items) {
|
|
2356
|
+
const payload = JSON.stringify({
|
|
2357
|
+
prompt,
|
|
2358
|
+
itemIds: items
|
|
2359
|
+
.map((item) => item.itemId)
|
|
2360
|
+
.sort((left, right) => left.localeCompare(right)),
|
|
2361
|
+
});
|
|
2362
|
+
return (0, node_crypto_1.createHash)("sha256").update(payload).digest("hex");
|
|
2363
|
+
}
|
|
2291
2364
|
function summarizeSourceEvent(sourceType, sourceKey, eventVariant) {
|
|
2292
2365
|
if (sourceType === "github_repo_ci") {
|
|
2293
2366
|
const parts = eventVariant.split(".");
|
package/dist/src/store.js
CHANGED
|
@@ -729,12 +729,13 @@ class AgentInboxStore {
|
|
|
729
729
|
upsertActivationDispatchState(state) {
|
|
730
730
|
this.db.run(`
|
|
731
731
|
insert into activation_dispatch_states (
|
|
732
|
-
agent_id, target_id, status, lease_expires_at, pending_new_item_count, pending_summary,
|
|
732
|
+
agent_id, target_id, status, lease_expires_at, last_notified_fingerprint, pending_new_item_count, pending_summary,
|
|
733
733
|
pending_subscription_ids_json, pending_source_ids_json, updated_at
|
|
734
|
-
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
734
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
735
735
|
on conflict(agent_id, target_id) do update set
|
|
736
736
|
status = excluded.status,
|
|
737
737
|
lease_expires_at = excluded.lease_expires_at,
|
|
738
|
+
last_notified_fingerprint = excluded.last_notified_fingerprint,
|
|
738
739
|
pending_new_item_count = excluded.pending_new_item_count,
|
|
739
740
|
pending_summary = excluded.pending_summary,
|
|
740
741
|
pending_subscription_ids_json = excluded.pending_subscription_ids_json,
|
|
@@ -745,6 +746,7 @@ class AgentInboxStore {
|
|
|
745
746
|
state.targetId,
|
|
746
747
|
state.status,
|
|
747
748
|
state.leaseExpiresAt ?? null,
|
|
749
|
+
state.lastNotifiedFingerprint ?? null,
|
|
748
750
|
state.pendingNewItemCount,
|
|
749
751
|
state.pendingSummary ?? null,
|
|
750
752
|
JSON.stringify(state.pendingSubscriptionIds),
|
|
@@ -1251,6 +1253,7 @@ class AgentInboxStore {
|
|
|
1251
1253
|
targetId: String(row.target_id),
|
|
1252
1254
|
status: row.status,
|
|
1253
1255
|
leaseExpiresAt: row.lease_expires_at ? String(row.lease_expires_at) : null,
|
|
1256
|
+
lastNotifiedFingerprint: row.last_notified_fingerprint ? String(row.last_notified_fingerprint) : null,
|
|
1254
1257
|
pendingNewItemCount: Number(row.pending_new_item_count),
|
|
1255
1258
|
pendingSummary: row.pending_summary ? String(row.pending_summary) : null,
|
|
1256
1259
|
pendingSubscriptionIds: parseJson(row.pending_subscription_ids_json),
|
package/dist/src/terminal.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.TerminalDispatcher = void 0;
|
|
7
7
|
exports.detectTerminalContext = detectTerminalContext;
|
|
8
8
|
exports.renderAgentPrompt = renderAgentPrompt;
|
|
9
|
+
exports.deriveInlineItemPreview = deriveInlineItemPreview;
|
|
9
10
|
exports.assignedAgentIdFromContext = assignedAgentIdFromContext;
|
|
10
11
|
const node_child_process_1 = require("node:child_process");
|
|
11
12
|
const node_util_1 = require("node:util");
|
|
@@ -49,11 +50,41 @@ function renderAgentPrompt(input) {
|
|
|
49
50
|
const totalUnackedCount = input.totalUnackedCount ?? input.newItemCount ?? 0;
|
|
50
51
|
const itemWord = totalUnackedCount === 1 ? "item" : "items";
|
|
51
52
|
const base = `AgentInbox: ${totalUnackedCount} unacked ${itemWord} in inbox ${input.inboxId}.`;
|
|
53
|
+
if (totalUnackedCount === 1 && input.preview) {
|
|
54
|
+
const suffix = /(?:[.!?…]|\.{3})$/.test(input.preview) ? "" : ".";
|
|
55
|
+
return `${base} Preview: ${input.preview}${suffix} Read the inbox for full details if needed.`;
|
|
56
|
+
}
|
|
52
57
|
if (input.summary) {
|
|
53
58
|
return `${base} Summary: ${input.summary}. Please read the inbox, process them, and ack when finished.`;
|
|
54
59
|
}
|
|
55
60
|
return `${base} Please read the inbox, process them, and ack when finished.`;
|
|
56
61
|
}
|
|
62
|
+
const MAX_INLINE_PREVIEW_INPUT_CHARS = 240;
|
|
63
|
+
const MAX_INLINE_PREVIEW_CHARS = 96;
|
|
64
|
+
const INLINE_PREVIEW_KEYS = ["message", "text", "body", "title"];
|
|
65
|
+
function deriveInlineItemPreview(item, summary) {
|
|
66
|
+
const candidates = [];
|
|
67
|
+
for (const key of INLINE_PREVIEW_KEYS) {
|
|
68
|
+
const rawPayloadValue = item.rawPayload[key];
|
|
69
|
+
if (typeof rawPayloadValue === "string") {
|
|
70
|
+
candidates.push(rawPayloadValue);
|
|
71
|
+
}
|
|
72
|
+
const metadataValue = item.metadata[key];
|
|
73
|
+
if (typeof metadataValue === "string") {
|
|
74
|
+
candidates.push(metadataValue);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (isPreviewFriendlySummary(summary)) {
|
|
78
|
+
candidates.push(summary);
|
|
79
|
+
}
|
|
80
|
+
for (const candidate of candidates) {
|
|
81
|
+
const preview = normalizeInlinePreview(candidate);
|
|
82
|
+
if (preview) {
|
|
83
|
+
return preview;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
57
88
|
function assignedAgentIdFromContext(input) {
|
|
58
89
|
const primary = normalizeOptionalString(input.runtimeSessionId)
|
|
59
90
|
?? normalizeOptionalString(input.tmuxPaneId)
|
|
@@ -176,6 +207,44 @@ function normalizeItermSessionId(raw) {
|
|
|
176
207
|
const parts = value.split(":");
|
|
177
208
|
return parts[parts.length - 1] || null;
|
|
178
209
|
}
|
|
210
|
+
function normalizeInlinePreview(value) {
|
|
211
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
212
|
+
if (!normalized) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
if (normalized.length > MAX_INLINE_PREVIEW_INPUT_CHARS) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
if (looksStructured(normalized)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
if (normalized.length <= MAX_INLINE_PREVIEW_CHARS) {
|
|
222
|
+
return normalized;
|
|
223
|
+
}
|
|
224
|
+
return `${normalized.slice(0, MAX_INLINE_PREVIEW_CHARS - 3).trimEnd()}...`;
|
|
225
|
+
}
|
|
226
|
+
function isPreviewFriendlySummary(summary) {
|
|
227
|
+
if (typeof summary !== "string") {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const normalized = summary.trim();
|
|
231
|
+
if (!normalized) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
if (!normalized.includes(" ")) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
if (/^[a-z0-9_.-]+:/i.test(normalized)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
function looksStructured(value) {
|
|
243
|
+
if ((value.startsWith("{") && value.endsWith("}")) || (value.startsWith("[") && value.endsWith("]"))) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
179
248
|
function detectRuntimeContext(env) {
|
|
180
249
|
const codexThreadId = normalizeOptionalString(env.CODEX_THREAD_ID);
|
|
181
250
|
if (codexThreadId) {
|
package/drizzle/schema.ts
CHANGED
|
@@ -122,6 +122,7 @@ export const activationDispatchStates = sqliteTable("activation_dispatch_states"
|
|
|
122
122
|
targetId: text("target_id").notNull(),
|
|
123
123
|
status: text("status").notNull(),
|
|
124
124
|
leaseExpiresAt: text("lease_expires_at"),
|
|
125
|
+
lastNotifiedFingerprint: text("last_notified_fingerprint"),
|
|
125
126
|
pendingNewItemCount: integer("pending_new_item_count").notNull(),
|
|
126
127
|
pendingSummary: text("pending_summary"),
|
|
127
128
|
pendingSubscriptionIdsJson: text("pending_subscription_ids_json").notNull(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holon-run/agentinbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Local event subscription and delivery service for agents.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^24.5.2",
|
|
51
51
|
"@types/sql.js": "^1.4.9",
|
|
52
|
-
"drizzle-kit": "
|
|
53
|
-
"drizzle-orm": "^0.
|
|
52
|
+
"drizzle-kit": "1.0.0-beta.21",
|
|
53
|
+
"drizzle-orm": "^0.45.2",
|
|
54
54
|
"mdorigin": "^0.5.0",
|
|
55
55
|
"ts-node": "^10.9.2",
|
|
56
56
|
"typescript": "^5.9.2"
|