@fitchmultz/pi-subagents 0.50.0 → 0.52.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/CHANGELOG.md +63 -2
- package/README.md +36 -5
- package/dist/agent/child-runtime.js +80 -0
- package/dist/agent/contracts.js +15 -24
- package/dist/agent/host.js +3 -16
- package/dist/agent/index.js +212 -192
- package/dist/agent/journal-tail.js +115 -156
- package/dist/agent/launch.js +20 -8
- package/dist/agent/legacy.js +65 -23
- package/dist/agent/notifications.js +138 -47
- package/dist/agent/observation-state.js +246 -0
- package/dist/agent/observation-view.js +258 -0
- package/dist/agent/observation.js +350 -0
- package/dist/agent/observe-card.js +243 -0
- package/dist/agent/observe-chrome.js +85 -0
- package/dist/agent/observe-client.js +285 -0
- package/dist/agent/observe-layout.js +65 -0
- package/dist/agent/observe-reading.js +84 -0
- package/dist/agent/observe-screen.js +400 -96
- package/dist/agent/observe-transcript.js +382 -0
- package/dist/agent/observe.js +250 -43
- package/dist/agent/parking.js +253 -0
- package/dist/agent/process.js +228 -28
- package/dist/agent/receipts.js +92 -20
- package/dist/agent/runner.js +229 -126
- package/dist/agent/shell.js +2 -1
- package/dist/agent/storage.js +47 -12
- package/dist/agent/tool-schemas.js +20 -0
- package/dist/agent/typebox.js +10 -5
- package/dist/agent/usage.js +0 -115
- package/dist/agent/view.js +46 -16
- package/dist/agent/wake.js +99 -0
- package/dist/build-info.js +1 -1
- package/dist/intercom-client.d.ts +26 -0
- package/dist/intercom-client.js +45 -0
- package/dist/pi-intercom/agent-dir.d.ts +1 -0
- package/dist/pi-intercom/bridge-session.js +2 -1
- package/dist/pi-intercom/broker/broker.js +62 -43
- package/dist/pi-intercom/broker/client-last.d.ts +24 -0
- package/dist/pi-intercom/broker/client-last.js +42 -5
- package/dist/pi-intercom/broker/client-park.d.ts +9 -0
- package/dist/pi-intercom/broker/client-park.js +21 -0
- package/dist/pi-intercom/broker/client.d.ts +59 -0
- package/dist/pi-intercom/broker/client.js +55 -29
- package/dist/pi-intercom/broker/connection.d.ts +29 -0
- package/dist/pi-intercom/broker/framing.d.ts +17 -0
- package/dist/pi-intercom/broker/framing.js +10 -7
- package/dist/pi-intercom/broker/guard.js +23 -0
- package/dist/pi-intercom/broker/last.d.ts +30 -0
- package/dist/pi-intercom/broker/last.js +87 -40
- package/dist/pi-intercom/broker/parked.js +107 -0
- package/dist/pi-intercom/broker/paths.d.ts +13 -0
- package/dist/pi-intercom/broker/paths.js +35 -12
- package/dist/pi-intercom/broker/pid.js +5 -1
- package/dist/pi-intercom/broker/server-connection.js +20 -1
- package/dist/pi-intercom/broker/snapshot-reader.d.ts +9 -0
- package/dist/pi-intercom/broker/snapshots.d.ts +29 -0
- package/dist/pi-intercom/broker/targets.d.ts +9 -0
- package/dist/pi-intercom/connection.js +71 -20
- package/dist/pi-intercom/identity.js +6 -5
- package/dist/pi-intercom/inbound-delivery.js +16 -60
- package/dist/pi-intercom/inbound-journal.js +83 -6
- package/dist/pi-intercom/inbound-record.js +18 -1
- package/dist/pi-intercom/inbound.js +22 -37
- package/dist/pi-intercom/index.js +41 -0
- package/dist/pi-intercom/last-message.js +4 -16
- package/dist/pi-intercom/lifecycle.js +88 -3
- package/dist/pi-intercom/outbound.js +5 -15
- package/dist/pi-intercom/reply-tracker.js +7 -6
- package/dist/pi-intercom/reply-wait.js +2 -2
- package/dist/pi-intercom/session-targets.d.ts +24 -0
- package/dist/pi-intercom/tool-registration.js +4 -1
- package/dist/pi-intercom/tool-results.js +5 -7
- package/dist/pi-intercom/tool-schemas.js +12 -26
- package/dist/pi-intercom/types.d.ts +212 -0
- package/dist/pi-intercom/types.js +54 -3
- package/dist/pi-intercom/validation.d.ts +12 -0
- package/dist/pi-intercom/validation.js +3 -0
- package/dist/shared/bounded-json.js +151 -0
- package/dist/shared/compact-cards.js +66 -0
- package/dist/shared/journal-frames.js +3 -6
- package/dist/shared/json-projection.js +267 -129
- package/dist/shared/native-journal.js +123 -56
- package/dist/shared/parking.js +12 -0
- package/dist/shared/post-exit-stdio-guard.js +120 -7
- package/dist/shared/tui-action.js +29 -0
- package/dist/shared/unknown.d.ts +9 -0
- package/docs/code-quality.md +5 -3
- package/docs/development.md +42 -3
- package/docs/intercom-bridge.md +9 -4
- package/docs/intercom.md +228 -7
- package/docs/performance.md +118 -14
- package/docs/reference.md +168 -20
- package/docs/retired-history-maintenance.md +252 -0
- package/package.json +28 -15
- package/scripts/archive-retired-history.mjs +464 -0
- package/scripts/build.mjs +48 -28
- package/scripts/measure-size.mjs +1 -1
- package/scripts/native-package-smoke.mjs +153 -12
- package/scripts/package-smoke.mjs +80 -11
- package/scripts/prepare.mjs +2 -2
- package/scripts/real-pi-live.mjs +377 -0
- package/scripts/real-pi-processes.mjs +6 -1
- package/scripts/real-pi-smoke.mjs +6 -282
- package/scripts/run-tests.mjs +4 -2
- package/scripts/typescript.mjs +35 -0
- package/skills/pi-intercom/SKILL.md +2 -1
- package/skills/pi-subagents/SKILL.md +23 -5
- package/src/agent/child-runtime.ts +105 -0
- package/src/agent/contracts.ts +18 -34
- package/src/agent/host.ts +39 -20
- package/src/agent/index.ts +234 -207
- package/src/agent/journal-tail.ts +141 -153
- package/src/agent/launch.ts +33 -9
- package/src/agent/legacy.ts +85 -31
- package/src/agent/notifications.ts +192 -67
- package/src/agent/observation-state.ts +277 -0
- package/src/agent/observation-view.ts +267 -0
- package/src/agent/observation.ts +409 -0
- package/src/agent/observe-card.ts +304 -0
- package/src/agent/observe-chrome.ts +170 -0
- package/src/agent/observe-client.ts +318 -0
- package/src/agent/observe-layout.ts +79 -0
- package/src/agent/observe-reading.ts +97 -0
- package/src/agent/observe-screen.ts +443 -129
- package/src/agent/observe-transcript.ts +434 -0
- package/src/agent/observe.ts +304 -71
- package/src/agent/parking.ts +341 -0
- package/src/agent/process.ts +280 -37
- package/src/agent/receipts.ts +114 -24
- package/src/agent/runner.ts +227 -149
- package/src/agent/shell.ts +2 -1
- package/src/agent/storage.ts +44 -11
- package/src/agent/tool-schemas.ts +33 -0
- package/src/agent/typebox.ts +12 -7
- package/src/agent/usage.ts +0 -130
- package/src/agent/view.ts +70 -20
- package/src/agent/wake.ts +107 -0
- package/src/intercom-client.ts +108 -0
- package/src/pi-intercom/bridge-session.ts +2 -2
- package/src/pi-intercom/broker/broker.ts +64 -53
- package/src/pi-intercom/broker/client-last.ts +65 -12
- package/src/pi-intercom/broker/client-park.ts +23 -0
- package/src/pi-intercom/broker/client.ts +63 -45
- package/src/pi-intercom/broker/framing.ts +11 -8
- package/src/pi-intercom/broker/guard.ts +33 -0
- package/src/pi-intercom/broker/last.ts +131 -43
- package/src/pi-intercom/broker/parked.ts +130 -0
- package/src/pi-intercom/broker/paths.ts +39 -12
- package/src/pi-intercom/broker/pid.ts +6 -1
- package/src/pi-intercom/broker/server-connection.ts +27 -2
- package/src/pi-intercom/connection.ts +103 -39
- package/src/pi-intercom/identity.ts +8 -5
- package/src/pi-intercom/inbound-delivery.ts +20 -66
- package/src/pi-intercom/inbound-journal.ts +103 -7
- package/src/pi-intercom/inbound-record.ts +21 -4
- package/src/pi-intercom/inbound.ts +32 -49
- package/src/pi-intercom/index.ts +44 -0
- package/src/pi-intercom/last-message.ts +11 -20
- package/src/pi-intercom/lifecycle.ts +117 -14
- package/src/pi-intercom/outbound.ts +7 -16
- package/src/pi-intercom/reply-tracker.ts +8 -6
- package/src/pi-intercom/reply-wait.ts +3 -2
- package/src/pi-intercom/runtime-types.ts +0 -1
- package/src/pi-intercom/tool-registration.ts +4 -1
- package/src/pi-intercom/tool-results.ts +10 -9
- package/src/pi-intercom/tool-schemas.ts +12 -44
- package/src/pi-intercom/types.ts +115 -4
- package/src/pi-intercom/validation.ts +4 -0
- package/src/shared/bounded-json.ts +162 -0
- package/src/shared/compact-cards.ts +118 -0
- package/src/shared/journal-frames.ts +3 -19
- package/src/shared/json-projection.ts +267 -147
- package/src/shared/native-journal.ts +157 -75
- package/src/shared/parking.ts +22 -0
- package/src/shared/post-exit-stdio-guard.ts +165 -7
- package/src/shared/tui-action.ts +39 -0
- package/tsconfig.client.json +8 -0
- package/tsconfig.json +1 -0
- package/src/shared/stream-json.d.ts +0 -10
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
// Launches set both variables to the parent's exact native session ID. Absence means a main;
|
|
2
|
+
// anything else is refused, never normalized into a different identity.
|
|
1
3
|
export function readIntercomLineage() {
|
|
2
|
-
const rootSessionId = process.env.PI_INTERCOM_ROOT_SESSION_ID
|
|
3
|
-
const parentSessionId = process.env.PI_INTERCOM_PARENT_SESSION_ID
|
|
4
|
+
const rootSessionId = process.env.PI_INTERCOM_ROOT_SESSION_ID;
|
|
5
|
+
const parentSessionId = process.env.PI_INTERCOM_PARENT_SESSION_ID;
|
|
4
6
|
if (rootSessionId === undefined && parentSessionId === undefined) {
|
|
5
7
|
return;
|
|
6
8
|
}
|
|
7
9
|
if (rootSessionId === undefined ||
|
|
8
|
-
parentSessionId === undefined ||
|
|
9
10
|
rootSessionId === "" ||
|
|
10
|
-
|
|
11
|
+
rootSessionId !== rootSessionId.trim() ||
|
|
11
12
|
rootSessionId !== parentSessionId) {
|
|
12
|
-
throw new Error("
|
|
13
|
+
throw new Error("Child launch requires identical, unpadded root and parent native session IDs.");
|
|
13
14
|
}
|
|
14
15
|
return { rootSessionId, parentSessionId };
|
|
15
16
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import { requestedDelivery } from "./inbound-record.js";
|
|
3
|
-
import { isRecord } from "./validation.js";
|
|
4
2
|
import { runCooperatively } from "../shared/cooperative.js";
|
|
5
3
|
/** Owns cooperative inbound batches, restoration and the flush timer. */
|
|
6
4
|
export class InboundDeliveryOwner {
|
|
@@ -83,6 +81,9 @@ export class InboundDeliveryOwner {
|
|
|
83
81
|
this.timer.unref();
|
|
84
82
|
}
|
|
85
83
|
queue(entry, flushDelivery = "auto", delayMs = 200) {
|
|
84
|
+
if (!this.journal.owns(entry)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
86
87
|
this.journal.remember(entry, "queued", flushDelivery);
|
|
87
88
|
this.hooks.syncStatus();
|
|
88
89
|
this.schedule(delayMs);
|
|
@@ -112,8 +113,7 @@ export class InboundDeliveryOwner {
|
|
|
112
113
|
}
|
|
113
114
|
triggerIndex(entries) {
|
|
114
115
|
const canTrigger = (entry) => entry.flushDelivery !== "passive" && requestedDelivery(entry.message) !== "passive";
|
|
115
|
-
|
|
116
|
-
return ask === -1 ? entries.findIndex(canTrigger) : ask;
|
|
116
|
+
return entries.findLastIndex(canTrigger);
|
|
117
117
|
}
|
|
118
118
|
*sendTriggerLast(entries, generation) {
|
|
119
119
|
const passives = entries.filter((entry) => entry.flushDelivery === "passive");
|
|
@@ -124,14 +124,15 @@ export class InboundDeliveryOwner {
|
|
|
124
124
|
}
|
|
125
125
|
const index = this.triggerIndex(rest);
|
|
126
126
|
const trigger = index === -1 ? undefined : rest.splice(index, 1)[0];
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
const replyTarget = entries.find((entry) => entry.message.expectsReply === true) ?? trigger;
|
|
128
|
+
if (replyTarget && this.journal.get(replyTarget.message.id) === replyTarget) {
|
|
129
|
+
this.hooks.replies.queueTurnContext({ ...replyTarget, receivedAt: Date.now() });
|
|
129
130
|
}
|
|
130
131
|
for (const entry of rest) {
|
|
131
132
|
if (!this.live(generation)) {
|
|
132
133
|
return;
|
|
133
134
|
}
|
|
134
|
-
this.send(entry, entry.
|
|
135
|
+
this.send(entry, requestedDelivery(entry.message) === "queue" ? "followUp" : "steer", generation);
|
|
135
136
|
yield;
|
|
136
137
|
}
|
|
137
138
|
if (trigger) {
|
|
@@ -150,7 +151,7 @@ export class InboundDeliveryOwner {
|
|
|
150
151
|
live(generation) {
|
|
151
152
|
return this.lifecycle.generation === generation && this.lifecycle.live() !== null;
|
|
152
153
|
}
|
|
153
|
-
*sendSteers(entries, generation
|
|
154
|
+
*sendSteers(entries, generation) {
|
|
154
155
|
for (const entry of entries) {
|
|
155
156
|
if (!this.live(generation)) {
|
|
156
157
|
return;
|
|
@@ -159,9 +160,6 @@ export class InboundDeliveryOwner {
|
|
|
159
160
|
continue;
|
|
160
161
|
}
|
|
161
162
|
this.send(entry, "steer", generation);
|
|
162
|
-
if (requestedDelivery(entry.message) === "steer") {
|
|
163
|
-
onDetach(this.detach(entry, entry.message.expectsReply !== true));
|
|
164
|
-
}
|
|
165
163
|
yield;
|
|
166
164
|
}
|
|
167
165
|
}
|
|
@@ -184,13 +182,7 @@ export class InboundDeliveryOwner {
|
|
|
184
182
|
this.schedule(500);
|
|
185
183
|
return;
|
|
186
184
|
}
|
|
187
|
-
return this.runBatch(
|
|
188
|
-
const detachments = [];
|
|
189
|
-
await runCooperatively(this.sendSteers(steers, generation, (task) => {
|
|
190
|
-
detachments.push(task);
|
|
191
|
-
}));
|
|
192
|
-
await Promise.all(detachments);
|
|
193
|
-
}, generation);
|
|
185
|
+
return this.runBatch(() => runCooperatively(this.sendSteers(steers, generation)), generation);
|
|
194
186
|
}
|
|
195
187
|
messageOptions(delivery) {
|
|
196
188
|
if (delivery === "trigger") {
|
|
@@ -229,56 +221,20 @@ export class InboundDeliveryOwner {
|
|
|
229
221
|
}
|
|
230
222
|
}
|
|
231
223
|
nativeMessage(entry) {
|
|
232
|
-
const sender = entry.from.name
|
|
233
|
-
? entry.from.id.slice(0, 8)
|
|
234
|
-
: entry.from.name;
|
|
224
|
+
const sender = entry.from.name ?? entry.from.id.slice(0, 8);
|
|
235
225
|
const command = entry.replyCommand ?? "";
|
|
236
226
|
const reply = command === "" ? "" : `\n\nTo reply, use the intercom tool: ${command}`;
|
|
227
|
+
const hint = entry.message.replyHint;
|
|
228
|
+
const publication = hint
|
|
229
|
+
? `\n\nTo publish a correlated reply, use ${hint.tool} with commandId ${hint.commandId}. This is not an intercom ask.`
|
|
230
|
+
: "";
|
|
237
231
|
return {
|
|
238
232
|
customType: "intercom_message",
|
|
239
|
-
content: `**📨 From ${sender}** (Native session cwd: ${entry.from.cwd})${reply}\n\n${entry.bodyText}`,
|
|
233
|
+
content: `**📨 From ${sender}** (Native session cwd: ${entry.from.cwd})${reply}${publication}\n\n${entry.bodyText}`,
|
|
240
234
|
display: true,
|
|
241
235
|
details: { ...entry, bodyText: entry.bodyText },
|
|
242
236
|
};
|
|
243
237
|
}
|
|
244
|
-
async detach(entry, attention = false) {
|
|
245
|
-
if (!attention && entry.message.expectsReply !== true) {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
const requestId = randomUUID();
|
|
249
|
-
return await new Promise((resolve) => {
|
|
250
|
-
let settled = false;
|
|
251
|
-
const finish = (accepted) => {
|
|
252
|
-
if (settled) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
settled = true;
|
|
256
|
-
clearTimeout(timer);
|
|
257
|
-
unsubscribe();
|
|
258
|
-
resolve(accepted);
|
|
259
|
-
};
|
|
260
|
-
const timer = setTimeout(() => finish(false), 500);
|
|
261
|
-
timer.unref();
|
|
262
|
-
const unsubscribe = this.pi.events.on("pi-intercom:detach-response", (payload) => {
|
|
263
|
-
if (isRecord(payload) && payload.requestId === requestId) {
|
|
264
|
-
finish(payload.accepted === true);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
try {
|
|
268
|
-
this.pi.events.emit("pi-intercom:detach-request", {
|
|
269
|
-
requestId,
|
|
270
|
-
...(attention ? { reason: "attention" } : {}),
|
|
271
|
-
});
|
|
272
|
-
// Owned waits answer ordinary attention synchronously; an unanswered probe completes now.
|
|
273
|
-
if (attention) {
|
|
274
|
-
finish(false);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
finish(false);
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
238
|
async redeliver(ctx, generation) {
|
|
283
239
|
if (this.batchTask) {
|
|
284
240
|
await this.batchTask;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isContextRequest } from "./lifecycle.js";
|
|
1
3
|
import { INBOUND_CHECKPOINT_TYPE, } from "./runtime-types.js";
|
|
2
|
-
import { isInboundCheckpoint } from "./inbound-record.js";
|
|
4
|
+
import { isInboundCheckpoint, publicationMetadata, requestedDelivery } from "./inbound-record.js";
|
|
5
|
+
import { isReceiptQuery, } from "./types.js";
|
|
3
6
|
import { inboundIdFromCustomMessage } from "./message-format.js";
|
|
4
7
|
import { isRecord } from "./validation.js";
|
|
5
8
|
export class InboundJournal {
|
|
@@ -14,6 +17,72 @@ export class InboundJournal {
|
|
|
14
17
|
this.pi = pi;
|
|
15
18
|
this.lifecycle = lifecycle;
|
|
16
19
|
this.replies = replies;
|
|
20
|
+
pi.events.on("pi-intercom:provenance-query", (request) => {
|
|
21
|
+
if (!isContextRequest(request)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
request.respond(request.version === 1 && isReceiptQuery(request)
|
|
25
|
+
? this.receipt(request)
|
|
26
|
+
: { error: "publication_unavailable" });
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
receipt(query) {
|
|
30
|
+
const ctx = this.lifecycle.live();
|
|
31
|
+
if (!ctx || ctx.sessionManager.getSessionId() !== query.rootSessionId) {
|
|
32
|
+
return { error: "publication_unavailable" };
|
|
33
|
+
}
|
|
34
|
+
let receipt;
|
|
35
|
+
for (const item of ctx.sessionManager.getEntries()) {
|
|
36
|
+
receipt = this.receiptEntry(receipt, query, item);
|
|
37
|
+
}
|
|
38
|
+
return receipt ? { receipt } : { error: "receipt_not_found" };
|
|
39
|
+
}
|
|
40
|
+
receiptEntry(receipt, query, item) {
|
|
41
|
+
if (item.type === "custom" &&
|
|
42
|
+
item.customType === INBOUND_CHECKPOINT_TYPE &&
|
|
43
|
+
isInboundCheckpoint(item.data) &&
|
|
44
|
+
item.data.sessionId === query.rootSessionId) {
|
|
45
|
+
return this.receiptCheckpoint(receipt, query, item.id, item.data);
|
|
46
|
+
}
|
|
47
|
+
return receipt && inboundIdFromCustomMessage(item) === query.messageId
|
|
48
|
+
? { ...receipt, placementEntryId: item.id }
|
|
49
|
+
: receipt;
|
|
50
|
+
}
|
|
51
|
+
receiptCheckpoint(receipt, query, entryId, data) {
|
|
52
|
+
if ("entry" in data) {
|
|
53
|
+
if (receipt || data.entry.message.id !== query.messageId) {
|
|
54
|
+
return receipt;
|
|
55
|
+
}
|
|
56
|
+
const { from, message, stage, receivedAt } = data.entry;
|
|
57
|
+
return {
|
|
58
|
+
version: 1,
|
|
59
|
+
rootSessionId: query.rootSessionId,
|
|
60
|
+
messageId: query.messageId,
|
|
61
|
+
sender: {
|
|
62
|
+
peerId: from.id,
|
|
63
|
+
rootSessionId: from.rootSessionId,
|
|
64
|
+
incarnation: from.incarnation,
|
|
65
|
+
},
|
|
66
|
+
textSha256: createHash("sha256").update(message.content.text, "utf8").digest("hex"),
|
|
67
|
+
delivery: requestedDelivery(message),
|
|
68
|
+
...publicationMetadata(message),
|
|
69
|
+
checkpointEntryId: entryId,
|
|
70
|
+
receivedAt,
|
|
71
|
+
queued: stage === "queued",
|
|
72
|
+
nativeHandoff: stage === "native",
|
|
73
|
+
placementEntryId: null,
|
|
74
|
+
discarded: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (!receipt || data.messageId !== query.messageId) {
|
|
78
|
+
return receipt;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
...receipt,
|
|
82
|
+
queued: receipt.queued || data.stage === "queued",
|
|
83
|
+
nativeHandoff: receipt.nativeHandoff || data.stage === "native",
|
|
84
|
+
discarded: receipt.discarded || data.stage === "discarded",
|
|
85
|
+
};
|
|
17
86
|
}
|
|
18
87
|
peerLeft(id) {
|
|
19
88
|
for (const pending of this.replies.listPending()) {
|
|
@@ -24,7 +93,7 @@ export class InboundJournal {
|
|
|
24
93
|
this.replies.expireSender(id);
|
|
25
94
|
for (const pending of this.queued()) {
|
|
26
95
|
if (pending.from.id === id && pending.message.expectsReply === true) {
|
|
27
|
-
this.
|
|
96
|
+
this.consume(pending.message.id);
|
|
28
97
|
this.checkpoint({ messageId: pending.message.id, stage: "discarded" });
|
|
29
98
|
}
|
|
30
99
|
}
|
|
@@ -42,7 +111,10 @@ export class InboundJournal {
|
|
|
42
111
|
return this.pending.get(id);
|
|
43
112
|
}
|
|
44
113
|
owns(entry) {
|
|
45
|
-
|
|
114
|
+
const id = entry.message.id;
|
|
115
|
+
return "stage" in entry
|
|
116
|
+
? this.pending.get(id) === entry
|
|
117
|
+
: !this.pending.has(id) && !this.consumed.has(id);
|
|
46
118
|
}
|
|
47
119
|
checkpoint(update) {
|
|
48
120
|
const sessionId = this.lifecycle.currentSessionId;
|
|
@@ -77,7 +149,7 @@ export class InboundJournal {
|
|
|
77
149
|
if (pending.stage === "queued" &&
|
|
78
150
|
pending.from.id === entry.from.id &&
|
|
79
151
|
pending.message.threadId === entry.message.threadId) {
|
|
80
|
-
this.
|
|
152
|
+
this.consume(pending.message.id);
|
|
81
153
|
this.replies.markReplied(pending.message.id);
|
|
82
154
|
}
|
|
83
155
|
}
|
|
@@ -135,7 +207,7 @@ export class InboundJournal {
|
|
|
135
207
|
return;
|
|
136
208
|
}
|
|
137
209
|
if (data.stage === "discarded") {
|
|
138
|
-
this.
|
|
210
|
+
this.consume(data.messageId);
|
|
139
211
|
this.replies.markReplied(data.messageId);
|
|
140
212
|
return;
|
|
141
213
|
}
|
|
@@ -163,6 +235,11 @@ export class InboundJournal {
|
|
|
163
235
|
if (item.customType === "intercom_sent") {
|
|
164
236
|
this.restoreSent(item.data);
|
|
165
237
|
}
|
|
238
|
+
if (item.customType === "intercom_received" &&
|
|
239
|
+
isRecord(item.data) &&
|
|
240
|
+
typeof item.data.messageId === "string") {
|
|
241
|
+
this.consume(item.data.messageId);
|
|
242
|
+
}
|
|
166
243
|
}
|
|
167
244
|
restoreSent(data) {
|
|
168
245
|
if (isRecord(data) &&
|
|
@@ -194,7 +271,7 @@ export class InboundJournal {
|
|
|
194
271
|
return ((entry.type === "custom_message" &&
|
|
195
272
|
["intercom_message", "subagent-human-message"].includes(entry.customType)) ||
|
|
196
273
|
(entry.type === "custom" &&
|
|
197
|
-
[INBOUND_CHECKPOINT_TYPE, "intercom_sent"].includes(entry.customType)));
|
|
274
|
+
[INBOUND_CHECKPOINT_TYPE, "intercom_sent", "intercom_received"].includes(entry.customType)));
|
|
198
275
|
}
|
|
199
276
|
recentEntries(ctx, fullScan) {
|
|
200
277
|
if (fullScan) {
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { isMessage, isSessionRegistration } from "./types.js";
|
|
2
2
|
import { finiteNumber, isRecord } from "./validation.js";
|
|
3
|
+
export function publicationMetadata(message) {
|
|
4
|
+
return {
|
|
5
|
+
guard: message.guard && {
|
|
6
|
+
rootSessionId: message.guard.rootSessionId,
|
|
7
|
+
peerId: message.guard.peerId,
|
|
8
|
+
incarnation: message.guard.incarnation,
|
|
9
|
+
applicationGuard: message.guard.applicationGuard,
|
|
10
|
+
},
|
|
11
|
+
replyHint: message.replyHint && {
|
|
12
|
+
tool: message.replyHint.tool,
|
|
13
|
+
commandId: message.replyHint.commandId,
|
|
14
|
+
commandFingerprint: message.replyHint.commandFingerprint,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
3
18
|
export function requestedDelivery(message) {
|
|
4
19
|
if (message.passive === true || message.delivery === "passive") {
|
|
5
20
|
return "passive";
|
|
6
21
|
}
|
|
7
|
-
return message.delivery ??
|
|
22
|
+
return message.delivery ?? "steer";
|
|
8
23
|
}
|
|
9
24
|
function isLocalJournalMessage(value) {
|
|
10
25
|
if (!isRecord(value) || !isRecord(value.content)) {
|
|
@@ -25,6 +40,8 @@ function isLocalJournalMessage(value) {
|
|
|
25
40
|
"queueMode",
|
|
26
41
|
"threadId",
|
|
27
42
|
"passive",
|
|
43
|
+
"guard",
|
|
44
|
+
"replyHint",
|
|
28
45
|
].every((key) => value[key] === undefined));
|
|
29
46
|
}
|
|
30
47
|
function isJournalMessage(value, sender) {
|
|
@@ -45,65 +45,43 @@ export class IntercomInbound {
|
|
|
45
45
|
}
|
|
46
46
|
return true;
|
|
47
47
|
}
|
|
48
|
-
async busyNonInteractive(entry, ctx, generation) {
|
|
49
|
-
if (entry.message.expectsReply !== true) {
|
|
50
|
-
this.owners.delivery.send(entry, "steer", generation);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
const active = this.owners.connection.active;
|
|
54
|
-
if (entry.message.replyTo !== undefined || active?.isConnected() !== true) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
const result = await active.send(entry.from.id, {
|
|
59
|
-
text: "This agent is running in non-interactive mode and cannot respond to intercom messages while it is working. It will continue its current task and exit when done.",
|
|
60
|
-
replyTo: entry.message.id,
|
|
61
|
-
});
|
|
62
|
-
if (result.delivered && this.lifecycle.live(ctx, generation)) {
|
|
63
|
-
this.owners.replies.markReplied(entry.message.id);
|
|
64
|
-
this.lifecycle.markActivity();
|
|
65
|
-
this.owners.connection.syncStatus();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
/* Best-effort busy reply; the non-interactive task must continue either way. */
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
48
|
async admit(entry, live, generation) {
|
|
73
49
|
const active = this.lifecycle.live(live, generation);
|
|
74
50
|
if (!active || this.queueAdmission(entry, active)) {
|
|
75
51
|
return;
|
|
76
52
|
}
|
|
77
53
|
if (!this.lifecycle.isIdle(active)) {
|
|
78
|
-
|
|
54
|
+
this.busy(entry, generation);
|
|
79
55
|
return;
|
|
80
56
|
}
|
|
81
57
|
if (this.lifecycle.live(live, generation)) {
|
|
82
58
|
this.owners.delivery.send(entry, requestedDelivery(entry.message) === "passive" ? "passive" : "trigger", generation);
|
|
83
59
|
}
|
|
84
60
|
}
|
|
85
|
-
|
|
61
|
+
busy(entry, generation) {
|
|
86
62
|
const delivery = requestedDelivery(entry.message);
|
|
87
63
|
if (delivery === "steer") {
|
|
88
64
|
this.owners.delivery.send(entry, "steer", generation);
|
|
89
|
-
await this.owners.delivery.detach(entry, entry.message.expectsReply !== true);
|
|
90
65
|
return;
|
|
91
66
|
}
|
|
92
|
-
if (delivery === "queue"
|
|
67
|
+
if (delivery === "queue") {
|
|
93
68
|
this.owners.delivery.send(entry, "followUp", generation);
|
|
94
69
|
return;
|
|
95
70
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
71
|
+
this.owners.delivery.queue(entry, "passive");
|
|
72
|
+
}
|
|
73
|
+
/** Counted synchronously so a parking fence observes frames received before its ack. */
|
|
74
|
+
receiving = 0;
|
|
75
|
+
async receive(ctx, from, input) {
|
|
76
|
+
this.receiving++;
|
|
77
|
+
try {
|
|
78
|
+
await this.receiveCounted(ctx, from, input);
|
|
99
79
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return;
|
|
80
|
+
finally {
|
|
81
|
+
this.receiving--;
|
|
103
82
|
}
|
|
104
|
-
this.owners.delivery.queue(entry, "auto");
|
|
105
83
|
}
|
|
106
|
-
async
|
|
84
|
+
async receiveCounted(ctx, from, input) {
|
|
107
85
|
const generation = this.lifecycle.generation;
|
|
108
86
|
const restore = this.owners.delivery.restoring;
|
|
109
87
|
if (restore) {
|
|
@@ -113,9 +91,17 @@ export class IntercomInbound {
|
|
|
113
91
|
if (!live) {
|
|
114
92
|
return;
|
|
115
93
|
}
|
|
94
|
+
if (input.guard && !this.lifecycle.accepts(input.guard)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
116
97
|
// Peer metadata never grants user authority.
|
|
117
98
|
const message = input.human ? { ...input, human: undefined } : input;
|
|
99
|
+
const entry = this.entry(from, message);
|
|
100
|
+
if (!this.owners.journal.owns(entry)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
118
103
|
if (this.owners.wait.receive(from, message, () => {
|
|
104
|
+
this.owners.journal.consume(message.id);
|
|
119
105
|
this.lifecycle.markActivity();
|
|
120
106
|
this.owners.connection.syncStatus();
|
|
121
107
|
})) {
|
|
@@ -124,7 +110,6 @@ export class IntercomInbound {
|
|
|
124
110
|
this.owners.replies.recordIncomingMessage(from, message);
|
|
125
111
|
this.lifecycle.markActivity();
|
|
126
112
|
this.owners.connection.syncStatus();
|
|
127
|
-
const entry = this.entry(from, message);
|
|
128
113
|
await this.admit(entry, live, generation);
|
|
129
114
|
}
|
|
130
115
|
}
|
|
@@ -12,6 +12,18 @@ import { IntercomInspection } from "./inspection.js";
|
|
|
12
12
|
import { IntercomTools } from "./tools.js";
|
|
13
13
|
import { IntercomSessionEvents } from "./session-events.js";
|
|
14
14
|
import { registerIntercomTools } from "./tool-registration.js";
|
|
15
|
+
import { isParkRequest, PARK, PARKED_HISTORY, WAKE } from "../shared/parking.js";
|
|
16
|
+
/** The agent runtime answers for its exited parked child from the captured native session. */
|
|
17
|
+
function parkedHistory(pi, peer) {
|
|
18
|
+
let history;
|
|
19
|
+
pi.events.emit(PARKED_HISTORY, {
|
|
20
|
+
peer,
|
|
21
|
+
reply: (found) => {
|
|
22
|
+
history = found;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
return history ?? Promise.reject(new Error("No agent runtime owns parked history."));
|
|
26
|
+
}
|
|
15
27
|
export default function piIntercomExtension(pi) {
|
|
16
28
|
const config = loadConfig();
|
|
17
29
|
const lifecycle = new IntercomLifecycle(pi, config, readIntercomLineage());
|
|
@@ -21,6 +33,7 @@ export default function piIntercomExtension(pi) {
|
|
|
21
33
|
const connection = new IntercomConnection(lifecycle, config, {
|
|
22
34
|
restoring: () => delivery.restoring,
|
|
23
35
|
pendingAsks: () => replies.listPending().length,
|
|
36
|
+
receipt: (query) => journal.receipt(query),
|
|
24
37
|
incoming: (ctx, from, message) => inbound.receive(ctx, from, message),
|
|
25
38
|
peerLeft: (id) => {
|
|
26
39
|
wait.peerDisconnected(id);
|
|
@@ -29,13 +42,19 @@ export default function piIntercomExtension(pi) {
|
|
|
29
42
|
disconnected: (error) => {
|
|
30
43
|
wait.reject(new Error(`Disconnected while waiting for reply: ${error.message}`, { cause: error }));
|
|
31
44
|
},
|
|
45
|
+
parkedHistory: (peer) => parkedHistory(pi, peer.id),
|
|
46
|
+
wake: (peer) => {
|
|
47
|
+
pi.events.emit(WAKE, peer);
|
|
48
|
+
},
|
|
32
49
|
});
|
|
33
50
|
const delivery = new InboundDeliveryOwner(pi, lifecycle, journal, {
|
|
34
51
|
replies,
|
|
35
52
|
syncStatus: () => connection.syncStatus(),
|
|
36
53
|
});
|
|
54
|
+
lifecycle.registerContextEvents(() => connection.syncStatus());
|
|
37
55
|
const inbound = new IntercomInbound(lifecycle, config, {
|
|
38
56
|
delivery,
|
|
57
|
+
journal,
|
|
39
58
|
replies,
|
|
40
59
|
wait,
|
|
41
60
|
connection,
|
|
@@ -52,4 +71,26 @@ export default function piIntercomExtension(pi) {
|
|
|
52
71
|
wait,
|
|
53
72
|
}).register();
|
|
54
73
|
registerIntercomTools(pi, tools);
|
|
74
|
+
pi.events.on(PARK, (request) => {
|
|
75
|
+
if (isParkRequest(request)) {
|
|
76
|
+
request.reply(request.park ? connection.park() : connection.unpark());
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// Native fork sleep readiness; it vetoes only this package's own parking, never other saves.
|
|
80
|
+
Reflect.apply(pi.on.bind(pi), undefined, [
|
|
81
|
+
"working_session_save",
|
|
82
|
+
() => !connection.parking
|
|
83
|
+
? undefined
|
|
84
|
+
: {
|
|
85
|
+
blockers: [
|
|
86
|
+
inbound.receiving > 0 && "intercom is receiving a message",
|
|
87
|
+
journal.size > 0 && "intercom inbound messages await delivery",
|
|
88
|
+
replies.listPending().length > 0 && "intercom asks await replies",
|
|
89
|
+
wait.waiting && "intercom is waiting for a reply",
|
|
90
|
+
(delivery.restoring !== undefined || delivery.batching) &&
|
|
91
|
+
"intercom delivery is restoring",
|
|
92
|
+
connection.reconnected && "intercom reconnected while parking",
|
|
93
|
+
].filter((blocker) => typeof blocker === "string"),
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
55
96
|
}
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
return (Number.isSafeInteger(offset) &&
|
|
3
|
-
offset >= 0 &&
|
|
4
|
-
Number.isSafeInteger(limit) &&
|
|
5
|
-
limit >= 1 &&
|
|
6
|
-
limit <= 16384);
|
|
7
|
-
}
|
|
1
|
+
import { isLastMessageQuery, LAST_MESSAGE_PAGE_LIMIT, } from "./types.js";
|
|
8
2
|
function completedResponse(manager, entryId) {
|
|
9
|
-
|
|
10
|
-
while (id !== null) {
|
|
11
|
-
const entry = manager.getEntry(id);
|
|
12
|
-
if (!entry) {
|
|
13
|
-
throw new Error("Native session history is unavailable.");
|
|
14
|
-
}
|
|
3
|
+
for (const entry of manager.getBranch().reverse()) {
|
|
15
4
|
if (entry.type === "message" &&
|
|
16
5
|
entry.message.role === "assistant" &&
|
|
17
6
|
(entry.message.stopReason === "stop" || entry.message.stopReason === "length") &&
|
|
@@ -24,15 +13,14 @@ function completedResponse(manager, entryId) {
|
|
|
24
13
|
.join("\n"),
|
|
25
14
|
};
|
|
26
15
|
}
|
|
27
|
-
id = entry.parentId;
|
|
28
16
|
}
|
|
29
17
|
return;
|
|
30
18
|
}
|
|
31
19
|
/** Reads finalized native history only; no model invocation or synthesized response. */
|
|
32
20
|
export function readLastMessage(manager, session, query) {
|
|
33
21
|
const offset = query.offset ?? 0;
|
|
34
|
-
const limit = query.limit ??
|
|
35
|
-
if (!
|
|
22
|
+
const limit = query.limit ?? LAST_MESSAGE_PAGE_LIMIT;
|
|
23
|
+
if (!isLastMessageQuery({ offset, limit })) {
|
|
36
24
|
return {
|
|
37
25
|
error: "Invalid last response page: offset must be non-negative and limit must be 1–16384.",
|
|
38
26
|
};
|