@fitchmultz/pi-subagents 0.52.2 → 0.52.3
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 +14 -0
- package/dist/agent/child-runtime.js +4 -4
- package/dist/agent/host.js +13 -0
- package/dist/agent/index.js +4 -1
- package/dist/agent/observe-card.js +3 -3
- package/dist/agent/observe.js +30 -26
- package/dist/agent/parking.js +15 -1
- package/dist/agent/runner.js +4 -2
- package/dist/agent/usage.js +92 -47
- package/dist/build-info.js +1 -1
- package/dist/pi-intercom/bridge-mailbox.js +18 -7
- package/dist/pi-intercom/bridge-wake.js +7 -9
- package/dist/pi-intercom/inbound-journal.js +4 -3
- package/dist/pi-intercom/inbound-record.js +12 -0
- package/dist/pi-intercom/ui/session-format.js +15 -6
- package/docs/performance.md +104 -7
- package/package.json +1 -1
- package/scripts/native-package-smoke.mjs +12 -3
- package/src/agent/child-runtime.ts +4 -6
- package/src/agent/host.ts +15 -0
- package/src/agent/index.ts +4 -1
- package/src/agent/observe-card.ts +3 -3
- package/src/agent/observe.ts +39 -37
- package/src/agent/parking.ts +14 -2
- package/src/agent/runner.ts +4 -2
- package/src/agent/usage.ts +116 -60
- package/src/pi-intercom/bridge-mailbox.ts +17 -7
- package/src/pi-intercom/bridge-wake.ts +7 -8
- package/src/pi-intercom/inbound-journal.ts +9 -3
- package/src/pi-intercom/inbound-record.ts +12 -0
- package/src/pi-intercom/ui/session-format.ts +16 -7
|
@@ -196,14 +196,24 @@ export class Mailbox {
|
|
|
196
196
|
}
|
|
197
197
|
ack(ids: readonly string[]): UnknownRecord {
|
|
198
198
|
const acked: string[] = [];
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
199
|
+
this.stored(() => {
|
|
200
|
+
// One FULL commit per ACK group; a rollback leaves every listed payload and intent pending.
|
|
201
|
+
this.db?.exec("BEGIN IMMEDIATE");
|
|
202
|
+
try {
|
|
203
|
+
for (const id of ids) {
|
|
204
|
+
const entry = this.get(id);
|
|
205
|
+
if (entry) {
|
|
206
|
+
this.notificationAccepted(id);
|
|
207
|
+
this.remove(id);
|
|
208
|
+
acked.push(id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
this.db?.exec("COMMIT");
|
|
212
|
+
} catch (error) {
|
|
213
|
+
this.db?.exec("ROLLBACK");
|
|
214
|
+
throw error;
|
|
205
215
|
}
|
|
206
|
-
}
|
|
216
|
+
});
|
|
207
217
|
return { acked };
|
|
208
218
|
}
|
|
209
219
|
page(after: number, limit: number): InboxPage {
|
|
@@ -138,21 +138,20 @@ export class WakeDeliveries {
|
|
|
138
138
|
|
|
139
139
|
private pending(delivery: Delivery): PendingNotification | undefined {
|
|
140
140
|
let after = 0;
|
|
141
|
-
let next: PendingNotification | undefined;
|
|
142
141
|
let page;
|
|
143
142
|
do {
|
|
144
|
-
|
|
143
|
+
// Decode only the retired prefix and the oldest actionable entry, not the whole backlog.
|
|
144
|
+
page = delivery.mailbox.pendingNotifications(after, 1);
|
|
145
145
|
for (const entry of page.messages) {
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
delivery.mailbox.notificationAccepted(entry.message.id);
|
|
149
|
-
} else if (!next) {
|
|
150
|
-
next = entry;
|
|
146
|
+
if (actionable(entry)) {
|
|
147
|
+
return entry;
|
|
151
148
|
}
|
|
149
|
+
// Retire only notification intent. Payload/reply obligations require actual ACK/reply.
|
|
150
|
+
delivery.mailbox.notificationAccepted(entry.message.id);
|
|
152
151
|
}
|
|
153
152
|
after = Number(page.nextCursor);
|
|
154
153
|
} while (page.hasMore);
|
|
155
|
-
return
|
|
154
|
+
return undefined;
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
private groups(): Map<string, Candidate | undefined> {
|
|
@@ -8,7 +8,12 @@ import {
|
|
|
8
8
|
type InboundMessageEntry,
|
|
9
9
|
type PendingInboundMessage,
|
|
10
10
|
} from "./runtime-types.ts";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
checkpointMessageId,
|
|
13
|
+
isInboundCheckpoint,
|
|
14
|
+
publicationMetadata,
|
|
15
|
+
requestedDelivery,
|
|
16
|
+
} from "./inbound-record.ts";
|
|
12
17
|
import {
|
|
13
18
|
isReceiptQuery,
|
|
14
19
|
type NativeReceipt,
|
|
@@ -66,6 +71,7 @@ export class InboundJournal {
|
|
|
66
71
|
if (
|
|
67
72
|
item.type === "custom" &&
|
|
68
73
|
item.customType === INBOUND_CHECKPOINT_TYPE &&
|
|
74
|
+
checkpointMessageId(item.data) === query.messageId &&
|
|
69
75
|
isInboundCheckpoint(item.data) &&
|
|
70
76
|
item.data.sessionId === query.rootSessionId
|
|
71
77
|
) {
|
|
@@ -82,7 +88,7 @@ export class InboundJournal {
|
|
|
82
88
|
data: InboundCheckpoint,
|
|
83
89
|
): NativeReceipt | undefined {
|
|
84
90
|
if ("entry" in data) {
|
|
85
|
-
if (receipt
|
|
91
|
+
if (receipt) {
|
|
86
92
|
return receipt;
|
|
87
93
|
}
|
|
88
94
|
const { from, message, stage, receivedAt } = data.entry;
|
|
@@ -106,7 +112,7 @@ export class InboundJournal {
|
|
|
106
112
|
discarded: false,
|
|
107
113
|
};
|
|
108
114
|
}
|
|
109
|
-
if (!receipt
|
|
115
|
+
if (!receipt) {
|
|
110
116
|
return receipt;
|
|
111
117
|
}
|
|
112
118
|
return {
|
|
@@ -97,3 +97,15 @@ export function isInboundCheckpoint(
|
|
|
97
97
|
value.stage === "reply-retired")
|
|
98
98
|
);
|
|
99
99
|
}
|
|
100
|
+
/** Unvalidated: lets a receipt skip checkpoints for other messages before full validation. */
|
|
101
|
+
export function checkpointMessageId(value: unknown): unknown {
|
|
102
|
+
if (!isRecord(value)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if ("entry" in value) {
|
|
106
|
+
return isRecord(value.entry) && isRecord(value.entry.message)
|
|
107
|
+
? value.entry.message.id
|
|
108
|
+
: undefined;
|
|
109
|
+
}
|
|
110
|
+
return value.messageId;
|
|
111
|
+
}
|
|
@@ -11,10 +11,19 @@ function localForkStartCommand(): string {
|
|
|
11
11
|
return `pi --name worker --extension ${shellQuote(path.join(PACKAGE_ROOT, "src", "index.ts"))}`;
|
|
12
12
|
}
|
|
13
13
|
function duplicateNames(sessions: readonly SessionInfo[]): ReadonlySet<string> {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const seen = new Set<string>();
|
|
15
|
+
const duplicates = new Set<string>();
|
|
16
|
+
for (const session of sessions) {
|
|
17
|
+
const name = session.name?.toLowerCase();
|
|
18
|
+
if (name === undefined || name === "") {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (seen.has(name)) {
|
|
22
|
+
duplicates.add(name);
|
|
23
|
+
}
|
|
24
|
+
seen.add(name);
|
|
25
|
+
}
|
|
26
|
+
return duplicates;
|
|
18
27
|
}
|
|
19
28
|
export function formatDuration(seconds: number): string {
|
|
20
29
|
if (seconds < 60) {
|
|
@@ -79,7 +88,7 @@ function healthTags(session: SessionInfo, now: number): readonly string[] {
|
|
|
79
88
|
interface RowOptions {
|
|
80
89
|
readonly currentCwd: string;
|
|
81
90
|
readonly self: boolean;
|
|
82
|
-
readonly duplicates:
|
|
91
|
+
readonly duplicates: ReadonlySet<string>;
|
|
83
92
|
readonly allSessions: readonly SessionInfo[];
|
|
84
93
|
readonly now: number;
|
|
85
94
|
}
|
|
@@ -88,7 +97,7 @@ function row(session: SessionInfo, options: RowOptions): string {
|
|
|
88
97
|
const duplicate =
|
|
89
98
|
session.name !== undefined &&
|
|
90
99
|
session.name !== "" &&
|
|
91
|
-
options.duplicates(session.name.toLowerCase());
|
|
100
|
+
options.duplicates.has(session.name.toLowerCase());
|
|
92
101
|
let location: string | undefined;
|
|
93
102
|
if (options.self) {
|
|
94
103
|
location = "self";
|
|
@@ -132,7 +141,7 @@ export function formatSessionListSections(
|
|
|
132
141
|
const others = scoped.filter((session) => session.id !== id);
|
|
133
142
|
const options = {
|
|
134
143
|
currentCwd: current.cwd,
|
|
135
|
-
duplicates:
|
|
144
|
+
duplicates: duplicateNames(allSessions),
|
|
136
145
|
allSessions,
|
|
137
146
|
now: Date.now(),
|
|
138
147
|
};
|