@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,5 +1,11 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { buildPresenceIdentity } from "./identity.js";
|
|
2
3
|
import { resolveSessionProjectId } from "./session-targets.js";
|
|
4
|
+
import { isContextTarget, } from "./types.js";
|
|
5
|
+
import { isRecord, safeLabel } from "./validation.js";
|
|
6
|
+
export function isContextRequest(value) {
|
|
7
|
+
return isRecord(value) && typeof value.respond === "function";
|
|
8
|
+
}
|
|
3
9
|
/** Owns native context validity and occupancy, never journal or socket state. */
|
|
4
10
|
export class IntercomLifecycle {
|
|
5
11
|
context = null;
|
|
@@ -7,6 +13,10 @@ export class IntercomLifecycle {
|
|
|
7
13
|
model = "unknown";
|
|
8
14
|
disposed = true;
|
|
9
15
|
epoch = 0;
|
|
16
|
+
incarnation = null;
|
|
17
|
+
peerId = null;
|
|
18
|
+
guardedDelivery = false;
|
|
19
|
+
applicationGuard;
|
|
10
20
|
running = false;
|
|
11
21
|
activity = 0;
|
|
12
22
|
tools = new Map();
|
|
@@ -27,15 +37,87 @@ export class IntercomLifecycle {
|
|
|
27
37
|
get toolCount() {
|
|
28
38
|
return this.tools.size;
|
|
29
39
|
}
|
|
40
|
+
registerContextEvents(sync) {
|
|
41
|
+
this.pi.events.on("pi-intercom:context-query", (request) => this.contextRequest(request));
|
|
42
|
+
this.pi.events.on("pi-intercom:application-guard", (request) => this.contextRequest(request, sync));
|
|
43
|
+
}
|
|
44
|
+
contextRequest(request, sync) {
|
|
45
|
+
if (!isContextRequest(request)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (request.version !== 1) {
|
|
49
|
+
request.respond({ error: "unsupported_version" });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (sync) {
|
|
53
|
+
if (!isContextTarget(request.target) ||
|
|
54
|
+
!this.matches(request.target) ||
|
|
55
|
+
!(request.revision === null ||
|
|
56
|
+
(typeof request.revision === "string" && safeLabel(request.revision)))) {
|
|
57
|
+
request.respond({ error: "context_changed" });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.applicationGuard = request.revision === null ? undefined : request.revision;
|
|
61
|
+
sync();
|
|
62
|
+
this.publishContext();
|
|
63
|
+
}
|
|
64
|
+
request.respond(this.snapshot());
|
|
65
|
+
}
|
|
66
|
+
snapshot() {
|
|
67
|
+
if (!this.live()) {
|
|
68
|
+
return {
|
|
69
|
+
version: 1,
|
|
70
|
+
rootSessionId: null,
|
|
71
|
+
peerId: null,
|
|
72
|
+
incarnation: null,
|
|
73
|
+
connected: false,
|
|
74
|
+
guardedDelivery: null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
version: 1,
|
|
79
|
+
rootSessionId: this.lineage?.rootSessionId ?? this.sessionId,
|
|
80
|
+
parentSessionId: this.lineage?.parentSessionId,
|
|
81
|
+
peerId: this.peerId,
|
|
82
|
+
incarnation: this.incarnation,
|
|
83
|
+
connected: this.peerId !== null,
|
|
84
|
+
guardedDelivery: this.guardedDelivery ? 1 : null,
|
|
85
|
+
applicationGuard: this.applicationGuard,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
publishContext() {
|
|
89
|
+
this.pi.events.emit("pi-intercom:context-state", this.snapshot());
|
|
90
|
+
}
|
|
91
|
+
transport(peerId, guardedDelivery = false) {
|
|
92
|
+
this.peerId = peerId;
|
|
93
|
+
this.guardedDelivery = guardedDelivery;
|
|
94
|
+
this.publishContext();
|
|
95
|
+
}
|
|
96
|
+
matches(target) {
|
|
97
|
+
const current = this.snapshot();
|
|
98
|
+
return (current.connected &&
|
|
99
|
+
current.rootSessionId === target.rootSessionId &&
|
|
100
|
+
current.peerId === target.peerId &&
|
|
101
|
+
current.incarnation === target.incarnation);
|
|
102
|
+
}
|
|
103
|
+
accepts(guard) {
|
|
104
|
+
return (this.guardedDelivery &&
|
|
105
|
+
this.lineage?.parentSessionId === undefined &&
|
|
106
|
+
this.matches(guard) &&
|
|
107
|
+
this.applicationGuard === guard.applicationGuard);
|
|
108
|
+
}
|
|
30
109
|
start(ctx) {
|
|
31
110
|
this.disposed = false;
|
|
32
111
|
this.epoch += 1;
|
|
33
112
|
this.context = ctx;
|
|
34
113
|
this.sessionId = ctx.sessionManager.getSessionId();
|
|
114
|
+
this.incarnation = randomUUID();
|
|
115
|
+
this.applicationGuard = undefined;
|
|
35
116
|
this.model = ctx.model?.id ?? "unknown";
|
|
36
117
|
this.running = false;
|
|
37
118
|
this.activity = 0;
|
|
38
119
|
this.tools.clear();
|
|
120
|
+
this.transport(null);
|
|
39
121
|
return this.epoch;
|
|
40
122
|
}
|
|
41
123
|
invalidate() {
|
|
@@ -43,6 +125,7 @@ export class IntercomLifecycle {
|
|
|
43
125
|
this.epoch += 1;
|
|
44
126
|
this.running = false;
|
|
45
127
|
this.tools.clear();
|
|
128
|
+
this.publishContext();
|
|
46
129
|
}
|
|
47
130
|
clear() {
|
|
48
131
|
this.context = null;
|
|
@@ -129,12 +212,14 @@ export class IntercomLifecycle {
|
|
|
129
212
|
if (!live || this.sessionId === null) {
|
|
130
213
|
throw new Error("Intercom runtime not initialized");
|
|
131
214
|
}
|
|
132
|
-
const cwd = live.cwd;
|
|
133
215
|
return {
|
|
134
216
|
...this.identity(this.sessionId),
|
|
135
|
-
|
|
217
|
+
incarnation: this.incarnation ?? undefined,
|
|
218
|
+
guardedDelivery: 1,
|
|
219
|
+
applicationGuard: this.applicationGuard,
|
|
220
|
+
cwd: live.cwd,
|
|
136
221
|
model: this.model,
|
|
137
|
-
projectId: await resolveSessionProjectId(cwd),
|
|
222
|
+
projectId: await resolveSessionProjectId(live.cwd),
|
|
138
223
|
lastSeen: Date.now(),
|
|
139
224
|
status: this.status(),
|
|
140
225
|
...this.health(pendingAsks),
|
|
@@ -3,7 +3,7 @@ import { resolveConnectedTarget, resolvePeerHealth, } from "./connection.js";
|
|
|
3
3
|
import { RECIPIENT_TURN_FAILED_ATTACHMENT, } from "./runtime-types.js";
|
|
4
4
|
import { errorMessage } from "./validation.js";
|
|
5
5
|
import { formatAttachments } from "./message-format.js";
|
|
6
|
-
import { AskDeliveryError, failureDetails,
|
|
6
|
+
import { AskDeliveryError, failureDetails, RecoveryError, } from "./tool-results.js";
|
|
7
7
|
import { sendOptions, toolError, toolText } from "./tool-arguments.js";
|
|
8
8
|
export class IntercomOutbound {
|
|
9
9
|
owners;
|
|
@@ -101,8 +101,7 @@ export class IntercomOutbound {
|
|
|
101
101
|
return this.sent(to, params, result);
|
|
102
102
|
}
|
|
103
103
|
catch (error) {
|
|
104
|
-
|
|
105
|
-
return toolError(`Failed to send: ${text}`, failureDetails(text.startsWith("Target ") ? "ambiguous_target" : "send_failed", [
|
|
104
|
+
return toolError(`Failed to send: ${errorMessage(error)}`, failureDetails(error instanceof RecoveryError ? error.code : "send_failed", [
|
|
106
105
|
{ action: "list" },
|
|
107
106
|
{ action: "send", guidance: "Retry with an exact active recipient target." },
|
|
108
107
|
], { error: true }));
|
|
@@ -147,15 +146,7 @@ export class IntercomOutbound {
|
|
|
147
146
|
const result = this.deliveryFailure(to, error.result);
|
|
148
147
|
return { ...result, details: { ...result.details, error: true } };
|
|
149
148
|
}
|
|
150
|
-
|
|
151
|
-
let reason = "ask_failed";
|
|
152
|
-
if (text.startsWith("No reply from")) {
|
|
153
|
-
reason = "reply_timeout";
|
|
154
|
-
}
|
|
155
|
-
else if (text.startsWith("Target ")) {
|
|
156
|
-
reason = "ambiguous_target";
|
|
157
|
-
}
|
|
158
|
-
return toolError(`Failed: ${text}`, failureDetails(reason, [
|
|
149
|
+
return toolError(`Failed: ${errorMessage(error)}`, failureDetails(error instanceof RecoveryError ? error.code : "ask_failed", [
|
|
159
150
|
{ action: "status" },
|
|
160
151
|
{ action: "list" },
|
|
161
152
|
{
|
|
@@ -293,8 +284,7 @@ export class IntercomOutbound {
|
|
|
293
284
|
}
|
|
294
285
|
}
|
|
295
286
|
replyFailure(error, replyTo) {
|
|
296
|
-
const
|
|
297
|
-
const reason = replyFailureReason(text);
|
|
287
|
+
const reason = error instanceof RecoveryError ? error.code : "reply_failed";
|
|
298
288
|
const actions = reason === "no_pending_reply"
|
|
299
289
|
? [
|
|
300
290
|
{ action: "pending" },
|
|
@@ -304,7 +294,7 @@ export class IntercomOutbound {
|
|
|
304
294
|
},
|
|
305
295
|
]
|
|
306
296
|
: [{ action: "pending" }, { action: "list" }];
|
|
307
|
-
return toolError(`Failed to reply: ${
|
|
297
|
+
return toolError(`Failed to reply: ${errorMessage(error)}`, failureDetails(reason, actions, {
|
|
308
298
|
error: true,
|
|
309
299
|
...((replyTo ?? "") !== "" ? { replyTo } : {}),
|
|
310
300
|
}));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatSessionTarget, resolveSessionTarget, shortSessionId } from "./session-targets.js";
|
|
2
|
+
import { RecoveryError } from "./tool-results.js";
|
|
2
3
|
function resolveBySenderTarget(contexts, to) {
|
|
3
4
|
const sessions = contexts.map((context) => context.from);
|
|
4
5
|
const resolution = resolveSessionTarget(sessions, to);
|
|
@@ -26,14 +27,14 @@ function pendingSenderOptions(contexts, allContexts = contexts) {
|
|
|
26
27
|
function matchingSender(contexts, to) {
|
|
27
28
|
const tooShort = tooShortSenderTargetMessage(contexts, to);
|
|
28
29
|
if (tooShort !== null && tooShort !== "") {
|
|
29
|
-
throw new
|
|
30
|
+
throw new RecoveryError("ambiguous_reply_target", tooShort);
|
|
30
31
|
}
|
|
31
32
|
return resolveBySenderTarget(contexts, to);
|
|
32
33
|
}
|
|
33
34
|
function explicitReply(contexts, replyTo, to) {
|
|
34
35
|
const target = contexts.find((context) => context.message.id === replyTo);
|
|
35
36
|
if (!target) {
|
|
36
|
-
throw new
|
|
37
|
+
throw new RecoveryError("no_pending_reply", `No pending ask with replyTo "${replyTo}"`);
|
|
37
38
|
}
|
|
38
39
|
if (to !== "" &&
|
|
39
40
|
!matchingSender(contexts, to).some((context) => context.message.id === target.message.id)) {
|
|
@@ -47,9 +48,9 @@ function replyBySender(contexts, to) {
|
|
|
47
48
|
return matches[0];
|
|
48
49
|
}
|
|
49
50
|
if (matches.length > 1) {
|
|
50
|
-
throw new
|
|
51
|
+
throw new RecoveryError("ambiguous_reply_target", `Multiple pending asks from "${to}" — use one of: ${pendingSenderOptions(matches, contexts)}.`);
|
|
51
52
|
}
|
|
52
|
-
throw new
|
|
53
|
+
throw new RecoveryError("no_pending_reply", `No pending ask from "${to}"`);
|
|
53
54
|
}
|
|
54
55
|
export class ReplyTracker {
|
|
55
56
|
pendingAsks = new Map();
|
|
@@ -151,9 +152,9 @@ export class ReplyTracker {
|
|
|
151
152
|
return pending[0];
|
|
152
153
|
}
|
|
153
154
|
if (pending.length === 0) {
|
|
154
|
-
throw new
|
|
155
|
+
throw new RecoveryError("no_pending_reply", "No active intercom context to reply to");
|
|
155
156
|
}
|
|
156
|
-
throw new
|
|
157
|
+
throw new RecoveryError("ambiguous_reply_target", "Multiple pending asks — specify `to`");
|
|
157
158
|
}
|
|
158
159
|
markReplied(replyTo) {
|
|
159
160
|
this.removeContext(replyTo);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asError } from "./validation.js";
|
|
2
|
-
import { AskDeliveryError } from "./tool-results.js";
|
|
2
|
+
import { AskDeliveryError, RecoveryError } from "./tool-results.js";
|
|
3
3
|
/** One correlated blocking wait; admitted before send so a fast reply cannot race it. */
|
|
4
4
|
export class ReplyWait {
|
|
5
5
|
waiter = null;
|
|
@@ -42,7 +42,7 @@ export class ReplyWait {
|
|
|
42
42
|
return new Promise((resolve, reject) => {
|
|
43
43
|
const { from, replyTo, signal } = options;
|
|
44
44
|
const timeout = setTimeout(() => {
|
|
45
|
-
this.reject(new
|
|
45
|
+
this.reject(new RecoveryError("reply_timeout", `No reply from "${from}" within ${Math.max(1, Math.round(this.timeoutMs / 60000))} minute(s)`));
|
|
46
46
|
}, this.timeoutMs);
|
|
47
47
|
timeout.unref();
|
|
48
48
|
const cleanup = () => {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface TargetIdentity {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly name?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ProjectSessionIdentity extends TargetIdentity {
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
readonly projectId?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const MIN_SESSION_TARGET_PREFIX_LENGTH = 8;
|
|
10
|
+
export declare function nativePeerId(sessionId: string): string;
|
|
11
|
+
export interface TargetResolution<T extends TargetIdentity> {
|
|
12
|
+
readonly status: "none" | "found" | "ambiguous" | "prefix_too_short";
|
|
13
|
+
readonly target?: T;
|
|
14
|
+
readonly matches: readonly T[];
|
|
15
|
+
}
|
|
16
|
+
export declare function shortSessionId(sessionId: string): string;
|
|
17
|
+
export declare function resolveSessionProjectId(cwd: string): Promise<string>;
|
|
18
|
+
export declare function formatSessionTarget(session: TargetIdentity, allSessions?: readonly TargetIdentity[]): string;
|
|
19
|
+
export declare function formatTargetOptions(sessions: readonly TargetIdentity[], allSessions?: readonly TargetIdentity[]): string;
|
|
20
|
+
export declare function targetDisplayName(session: TargetIdentity, allSessions?: readonly TargetIdentity[]): string;
|
|
21
|
+
export declare const PEER_AWARENESS_HINT = "Other Pi sessions may be connected to this project. Use load_intercom({}) if needed, then intercom({ action: \"list\" }) before changing shared state or coordinating known overlapping work. Routine standalone read-only tasks do not need a peer check. Coordinate only when work overlaps; use subagent controls for managed child runs.";
|
|
22
|
+
export declare function filterProjectSessions<T extends ProjectSessionIdentity>(sessions: readonly T[], currentSessionId: string): T[];
|
|
23
|
+
export declare function formatPeerAwarenessHint(sessions: readonly ProjectSessionIdentity[], currentSessionId: string): string | undefined;
|
|
24
|
+
export declare function resolveSessionTarget<T extends TargetIdentity>(sessions: readonly T[], rawTarget: string): TargetResolution<T>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { throwIfToolError } from "./tool-results.js";
|
|
2
2
|
import { intercomSchema } from "./tool-schemas.js";
|
|
3
|
+
import { registerCompactMessages, renderCompactResult } from "../shared/compact-cards.js";
|
|
3
4
|
export function registerIntercomTools(pi, tools) {
|
|
5
|
+
registerCompactMessages(pi, ["intercom_message", "subagent-human-message"]);
|
|
4
6
|
pi.registerTool({
|
|
5
7
|
name: "intercom",
|
|
6
8
|
label: "Intercom",
|
|
7
|
-
description: "
|
|
9
|
+
description: "Message connected Pi sessions in your group; mains also reach other mains across projects. List shows target IDs. Ask waits for a correlated reply; reply answers the active or sole pending ask. Pending lists asks; status shows connection and inbound queue. Last pages a peer's last completed response without waking it. Broker acceptance is not recipient persistence, consumption or completion.",
|
|
8
10
|
parameters: intercomSchema,
|
|
11
|
+
renderResult: renderCompactResult,
|
|
9
12
|
async execute(_id, params, signal, _update, ctx) {
|
|
10
13
|
const result = throwIfToolError(await tools.execute(params, signal, ctx));
|
|
11
14
|
return { content: [...result.content], details: result.details };
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
export function failureDetails(reasonCode, nextActions, details = {}) {
|
|
2
2
|
return { ...details, reasonCode, nextActions };
|
|
3
3
|
}
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return "ambiguous_reply_target";
|
|
4
|
+
export class RecoveryError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
10
9
|
}
|
|
11
|
-
return "reply_failed";
|
|
12
10
|
}
|
|
13
11
|
export function firstTextContent(result) {
|
|
14
12
|
return (result.content
|
|
@@ -1,42 +1,28 @@
|
|
|
1
1
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
2
|
import { Type } from "../agent/typebox.js";
|
|
3
3
|
export const intercomSchema = Type.Object({
|
|
4
|
-
action: StringEnum(["list", "send", "ask", "reply", "pending", "status", "last"],
|
|
5
|
-
description: "List, send, ask, reply, pending asks, connection status, or last completed assistant response.",
|
|
6
|
-
}),
|
|
4
|
+
action: StringEnum(["list", "send", "ask", "reply", "pending", "status", "last"]),
|
|
7
5
|
scope: Type.Optional(StringEnum(["project", "all"], {
|
|
8
|
-
description: "
|
|
9
|
-
})),
|
|
10
|
-
to: Type.Optional(Type.String({
|
|
11
|
-
description: "Target session name or ID (for 'send', 'ask', or disambiguating 'reply')",
|
|
12
|
-
})),
|
|
13
|
-
message: Type.Optional(Type.String({
|
|
14
|
-
description: "Message for send/ask/reply",
|
|
6
|
+
description: "all (default) or project (repo and worktrees)",
|
|
15
7
|
})),
|
|
8
|
+
to: Type.Optional(Type.String({ description: "Session name or ID" })),
|
|
9
|
+
message: Type.Optional(Type.String()),
|
|
16
10
|
attachments: Type.Optional(Type.Array(Type.Object({
|
|
17
11
|
type: StringEnum(["file", "snippet", "context"]),
|
|
18
12
|
name: Type.String(),
|
|
19
13
|
content: Type.String(),
|
|
20
14
|
language: Type.Optional(Type.String()),
|
|
21
15
|
}))),
|
|
22
|
-
replyTo: Type.Optional(Type.String({
|
|
23
|
-
description: "Message ID to reply to (for threading or responding to an 'ask')",
|
|
24
|
-
})),
|
|
16
|
+
replyTo: Type.Optional(Type.String({ description: "Message ID to answer or thread" })),
|
|
25
17
|
delivery: Type.Optional(StringEnum(["queue", "steer", "passive"], {
|
|
26
|
-
description: "
|
|
18
|
+
description: "steer (default) at next tool boundary; queue after active work; passive never wakes the model",
|
|
27
19
|
})),
|
|
28
20
|
queueMode: Type.Optional(StringEnum(["stack", "replace"], {
|
|
29
|
-
description: "
|
|
30
|
-
})),
|
|
31
|
-
threadId: Type.Optional(Type.String({ description: "Stable topic key for queueMode='replace'." })),
|
|
32
|
-
entryId: Type.Optional(Type.String({ description: "Last response entry ID for stable paging." })),
|
|
33
|
-
offset: Type.Optional(Type.Integer({ minimum: 0, description: "Last response character offset (UTF-16)." })),
|
|
34
|
-
limit: Type.Optional(Type.Integer({
|
|
35
|
-
minimum: 1,
|
|
36
|
-
maximum: 16384,
|
|
37
|
-
description: "Last response page size; default/max 16384 characters.",
|
|
38
|
-
})),
|
|
39
|
-
passive: Type.Optional(Type.Boolean({
|
|
40
|
-
description: "For action='send' only: legacy alias for delivery='passive'. Discouraged for agent-to-agent messages.",
|
|
21
|
+
description: "stack keeps all; replace keeps only the latest per threadId",
|
|
41
22
|
})),
|
|
23
|
+
threadId: Type.Optional(Type.String()),
|
|
24
|
+
entryId: Type.Optional(Type.String()),
|
|
25
|
+
offset: Type.Optional(Type.Integer({ minimum: 0, description: "UTF-16 characters" })),
|
|
26
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 16384 })),
|
|
27
|
+
passive: Type.Optional(Type.Boolean({ description: "Legacy alias for delivery=passive" })),
|
|
42
28
|
});
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
export interface SessionInfo {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly name?: string;
|
|
4
|
+
readonly cwd: string;
|
|
5
|
+
readonly model: string;
|
|
6
|
+
/** Opaque same-repository/worktree identity used for ambient peer awareness. */
|
|
7
|
+
readonly projectId?: string;
|
|
8
|
+
readonly rootSessionId?: string;
|
|
9
|
+
readonly parentSessionId?: string;
|
|
10
|
+
readonly incarnation?: string;
|
|
11
|
+
readonly guardedDelivery?: 1;
|
|
12
|
+
readonly applicationGuard?: string;
|
|
13
|
+
readonly status?: string;
|
|
14
|
+
readonly lastSeen?: number;
|
|
15
|
+
readonly lastIntercomActivity?: number;
|
|
16
|
+
readonly pendingAsks?: number;
|
|
17
|
+
readonly acceptsAsks?: boolean;
|
|
18
|
+
readonly subscriptions?: readonly TopicSubscription[];
|
|
19
|
+
readonly topics?: readonly TopicUpdate[];
|
|
20
|
+
}
|
|
21
|
+
export interface TopicSubscription {
|
|
22
|
+
readonly topic: string;
|
|
23
|
+
readonly awaitRelease?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface TopicUpdate {
|
|
26
|
+
readonly topic: string;
|
|
27
|
+
readonly text: string;
|
|
28
|
+
readonly event: "update" | "blocker" | "decision" | "release";
|
|
29
|
+
readonly resource?: string;
|
|
30
|
+
readonly ownership?: "held" | "released";
|
|
31
|
+
readonly revision: number;
|
|
32
|
+
readonly updatedAt: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function isTopicSubscription(value: unknown): value is TopicSubscription;
|
|
35
|
+
export declare function isTopicUpdate(value: unknown): value is TopicUpdate;
|
|
36
|
+
export type MessageDelivery = "queue" | "steer" | "passive";
|
|
37
|
+
export type QueueMode = "stack" | "replace";
|
|
38
|
+
export interface ContextTarget {
|
|
39
|
+
readonly rootSessionId: string;
|
|
40
|
+
readonly peerId: string;
|
|
41
|
+
readonly incarnation: string;
|
|
42
|
+
}
|
|
43
|
+
export interface DeliveryGuard extends ContextTarget {
|
|
44
|
+
readonly applicationGuard: string;
|
|
45
|
+
}
|
|
46
|
+
export interface PublicationReplyHint {
|
|
47
|
+
readonly tool: string;
|
|
48
|
+
readonly commandId: string;
|
|
49
|
+
readonly commandFingerprint?: string;
|
|
50
|
+
}
|
|
51
|
+
export type SendOptions = Omit<Message, "id" | "timestamp" | "content"> & Message["content"] & {
|
|
52
|
+
readonly messageId?: string;
|
|
53
|
+
};
|
|
54
|
+
export declare function isContextTarget(value: unknown): value is ContextTarget;
|
|
55
|
+
export declare function isDeliveryGuard(value: unknown): value is DeliveryGuard;
|
|
56
|
+
export declare function isPublicationReplyHint(value: unknown): value is PublicationReplyHint;
|
|
57
|
+
export interface HumanMessageOrigin {
|
|
58
|
+
readonly ownerSessionId: string;
|
|
59
|
+
readonly runId: string;
|
|
60
|
+
readonly index: number;
|
|
61
|
+
}
|
|
62
|
+
export declare function isHumanMessageOrigin(value: unknown): value is HumanMessageOrigin;
|
|
63
|
+
export interface Message {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
readonly timestamp: number;
|
|
66
|
+
readonly replyTo?: string;
|
|
67
|
+
readonly expectsReply?: boolean;
|
|
68
|
+
/** Recipients verify parent ownership before elevating human origin. */
|
|
69
|
+
readonly human?: HumanMessageOrigin;
|
|
70
|
+
readonly topic?: TopicUpdate;
|
|
71
|
+
readonly delivery?: MessageDelivery;
|
|
72
|
+
readonly queueMode?: QueueMode;
|
|
73
|
+
readonly threadId?: string;
|
|
74
|
+
readonly passive?: boolean;
|
|
75
|
+
readonly guard?: DeliveryGuard;
|
|
76
|
+
readonly replyHint?: PublicationReplyHint;
|
|
77
|
+
readonly content: {
|
|
78
|
+
readonly text: string;
|
|
79
|
+
readonly attachments?: readonly Attachment[];
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export interface Attachment {
|
|
83
|
+
readonly type: "file" | "snippet" | "context";
|
|
84
|
+
readonly name: string;
|
|
85
|
+
readonly content: string;
|
|
86
|
+
readonly language?: string;
|
|
87
|
+
}
|
|
88
|
+
export declare function isAttachment(value: unknown): value is Attachment;
|
|
89
|
+
export declare function isMessage(value: unknown): value is Message;
|
|
90
|
+
export declare function isSessionRegistration(value: unknown): value is Omit<SessionInfo, "id">;
|
|
91
|
+
export declare function normalizeSessionInfo(value: unknown): SessionInfo | null;
|
|
92
|
+
export interface SendResult {
|
|
93
|
+
readonly id: string;
|
|
94
|
+
readonly accepted: boolean;
|
|
95
|
+
readonly delivered: boolean;
|
|
96
|
+
readonly queued?: boolean;
|
|
97
|
+
readonly reason?: string;
|
|
98
|
+
}
|
|
99
|
+
export type TopicChange = {
|
|
100
|
+
readonly action: "publish" | "restore";
|
|
101
|
+
readonly topic: TopicUpdate;
|
|
102
|
+
} | {
|
|
103
|
+
readonly action: "subscribe" | "restore";
|
|
104
|
+
readonly subscription: TopicSubscription;
|
|
105
|
+
} | {
|
|
106
|
+
readonly action: "unsubscribe";
|
|
107
|
+
readonly topic: string;
|
|
108
|
+
};
|
|
109
|
+
export interface SessionSnapshot {
|
|
110
|
+
readonly sessions: readonly SessionInfo[];
|
|
111
|
+
readonly receipts: readonly (SendResult & {
|
|
112
|
+
readonly to: string;
|
|
113
|
+
})[];
|
|
114
|
+
}
|
|
115
|
+
type CompactTopicMessage = Omit<Message, "topic"> & {
|
|
116
|
+
readonly topic?: Omit<TopicUpdate, "text"> & {
|
|
117
|
+
readonly text?: string;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
/** Topic text and the standard coalescing key need only one copy on the wire. */
|
|
121
|
+
export declare function compactTopicMessage(message: Message): CompactTopicMessage;
|
|
122
|
+
export declare function normalizeMessage(value: unknown): Message | null;
|
|
123
|
+
export interface LastMessageQuery {
|
|
124
|
+
readonly entryId?: string;
|
|
125
|
+
readonly offset?: number;
|
|
126
|
+
readonly limit?: number;
|
|
127
|
+
}
|
|
128
|
+
export declare const LAST_MESSAGE_PAGE_LIMIT = 16384;
|
|
129
|
+
export declare function isLastMessageQuery(value: unknown): value is LastMessageQuery;
|
|
130
|
+
export type LastMessageResult = {
|
|
131
|
+
readonly session: SessionInfo;
|
|
132
|
+
readonly entryId: string | null;
|
|
133
|
+
readonly text: string;
|
|
134
|
+
readonly offset: number;
|
|
135
|
+
readonly totalLength: number;
|
|
136
|
+
readonly nextOffset?: number;
|
|
137
|
+
} | {
|
|
138
|
+
readonly error: string;
|
|
139
|
+
};
|
|
140
|
+
export interface ReceiptQuery {
|
|
141
|
+
readonly rootSessionId: string;
|
|
142
|
+
readonly messageId: string;
|
|
143
|
+
}
|
|
144
|
+
export declare function isReceiptQuery(value: unknown): value is ReceiptQuery;
|
|
145
|
+
export interface NativeReceipt extends ReceiptQuery {
|
|
146
|
+
readonly version: 1;
|
|
147
|
+
readonly sender: {
|
|
148
|
+
readonly peerId: string;
|
|
149
|
+
readonly rootSessionId?: string;
|
|
150
|
+
readonly incarnation?: string;
|
|
151
|
+
};
|
|
152
|
+
readonly textSha256: string;
|
|
153
|
+
readonly delivery: MessageDelivery;
|
|
154
|
+
readonly guard?: DeliveryGuard;
|
|
155
|
+
readonly replyHint?: PublicationReplyHint;
|
|
156
|
+
readonly checkpointEntryId: string;
|
|
157
|
+
readonly receivedAt: number;
|
|
158
|
+
/** Historical observations, not a promise that an input is still queued. */
|
|
159
|
+
readonly queued: boolean;
|
|
160
|
+
readonly nativeHandoff: boolean;
|
|
161
|
+
readonly placementEntryId: string | null;
|
|
162
|
+
readonly discarded: boolean;
|
|
163
|
+
}
|
|
164
|
+
export type ReceiptResult = {
|
|
165
|
+
readonly receipt: NativeReceipt;
|
|
166
|
+
} | {
|
|
167
|
+
readonly error: string;
|
|
168
|
+
};
|
|
169
|
+
export type BrokerMessage = {
|
|
170
|
+
readonly type: "registered";
|
|
171
|
+
readonly sessionId: string;
|
|
172
|
+
readonly topicsSupported?: true;
|
|
173
|
+
readonly topicFrames?: true;
|
|
174
|
+
readonly lastMessages?: true;
|
|
175
|
+
readonly receiptReads?: 1;
|
|
176
|
+
readonly guardedDelivery?: 1;
|
|
177
|
+
} | {
|
|
178
|
+
readonly type: "sessions";
|
|
179
|
+
readonly requestId: string;
|
|
180
|
+
readonly sessions: readonly (Partial<SessionInfo> & Pick<SessionInfo, "id">)[];
|
|
181
|
+
readonly more?: boolean;
|
|
182
|
+
readonly receipts?: SessionSnapshot["receipts"];
|
|
183
|
+
readonly error?: string;
|
|
184
|
+
} | {
|
|
185
|
+
readonly type: "message";
|
|
186
|
+
readonly from: SessionInfo;
|
|
187
|
+
readonly message: Message | CompactTopicMessage;
|
|
188
|
+
} | {
|
|
189
|
+
readonly type: "last_request";
|
|
190
|
+
readonly requestId: string;
|
|
191
|
+
readonly from: SessionInfo;
|
|
192
|
+
readonly query: LastMessageQuery | ReceiptQuery;
|
|
193
|
+
} | {
|
|
194
|
+
readonly type: "last_response";
|
|
195
|
+
readonly requestId: string;
|
|
196
|
+
readonly result: LastMessageResult | ReceiptResult;
|
|
197
|
+
} | {
|
|
198
|
+
readonly type: "session_left";
|
|
199
|
+
readonly sessionId: string;
|
|
200
|
+
} | {
|
|
201
|
+
readonly type: "delivered";
|
|
202
|
+
readonly messageId: string;
|
|
203
|
+
} | {
|
|
204
|
+
readonly type: "delivery_queued";
|
|
205
|
+
readonly messageId: string;
|
|
206
|
+
readonly reason: string;
|
|
207
|
+
} | {
|
|
208
|
+
readonly type: "delivery_failed";
|
|
209
|
+
readonly messageId: string;
|
|
210
|
+
readonly reason: string;
|
|
211
|
+
};
|
|
212
|
+
export {};
|