@fitchmultz/pi-subagents 0.52.7 → 0.52.9
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 +26 -0
- package/dist/agent/launch.js +1 -1
- package/dist/agent/observation-view.js +15 -7
- package/dist/agent/observe-card.js +2 -2
- package/dist/agent/observe-client.js +1 -1
- package/dist/agent/observe.js +4 -1
- package/dist/agent/process.js +5 -3
- package/dist/agent/runner.js +9 -4
- package/dist/agent/saved.js +16 -6
- package/dist/agent/view.js +5 -2
- package/dist/build-info.js +1 -1
- package/dist/pi-intercom/bridge-routes.js +1 -1
- package/dist/pi-intercom/bridge.js +4 -3
- package/dist/pi-intercom/broker/broker.js +1 -1
- package/dist/pi-intercom/broker/client-last.d.ts +1 -1
- package/dist/pi-intercom/broker/client.d.ts +9 -9
- package/dist/pi-intercom/broker/framing.js +6 -0
- package/dist/pi-intercom/broker/last.d.ts +5 -1
- package/dist/pi-intercom/broker/last.js +33 -15
- package/dist/pi-intercom/broker/parked.js +1 -1
- package/dist/pi-intercom/outbound.js +1 -0
- package/dist/pi-intercom/reply-tracker.js +11 -5
- package/dist/pi-intercom/session-targets.d.ts +2 -2
- package/dist/pi-intercom/session-targets.js +3 -2
- package/dist/pi-intercom/tool-registration.js +1 -0
- package/dist/pi-intercom/types.d.ts +37 -37
- package/dist/pi-intercom/types.js +9 -7
- package/dist/shared/output-page.js +1 -1
- package/package.json +5 -4
- package/scripts/archive-retired-history.mjs +3 -1
- package/src/agent/index.ts +3 -3
- package/src/agent/journal-tail.ts +2 -2
- package/src/agent/launch.ts +1 -1
- package/src/agent/notifications.ts +3 -3
- package/src/agent/observation-state.ts +2 -2
- package/src/agent/observation-view.ts +20 -7
- package/src/agent/observation.ts +4 -4
- package/src/agent/observe-card.ts +7 -3
- package/src/agent/observe-chrome.ts +2 -2
- package/src/agent/observe-client.ts +5 -5
- package/src/agent/observe-reading.ts +1 -1
- package/src/agent/observe-screen.ts +4 -4
- package/src/agent/observe-transcript.ts +4 -4
- package/src/agent/observe.ts +13 -8
- package/src/agent/parking.ts +1 -1
- package/src/agent/process.ts +6 -4
- package/src/agent/runner.ts +15 -7
- package/src/agent/saved.ts +17 -7
- package/src/agent/usage.ts +1 -1
- package/src/agent/view.ts +18 -9
- package/src/agent/wake.ts +2 -2
- package/src/pi-intercom/bridge-config.ts +2 -2
- package/src/pi-intercom/bridge-mailbox.ts +1 -1
- package/src/pi-intercom/bridge-presence.ts +1 -1
- package/src/pi-intercom/bridge-routes.ts +5 -5
- package/src/pi-intercom/bridge-session.ts +5 -5
- package/src/pi-intercom/bridge-wake.ts +2 -2
- package/src/pi-intercom/bridge.ts +4 -3
- package/src/pi-intercom/broker/broker.ts +1 -1
- package/src/pi-intercom/broker/client-last.ts +1 -1
- package/src/pi-intercom/broker/client-park.ts +1 -1
- package/src/pi-intercom/broker/client.ts +9 -9
- package/src/pi-intercom/broker/framing.ts +6 -0
- package/src/pi-intercom/broker/last.ts +62 -18
- package/src/pi-intercom/broker/parked.ts +8 -5
- package/src/pi-intercom/config.ts +1 -1
- package/src/pi-intercom/outbound.ts +1 -0
- package/src/pi-intercom/reply-tracker.ts +13 -6
- package/src/pi-intercom/reply-wait.ts +3 -3
- package/src/pi-intercom/runtime-types.ts +1 -1
- package/src/pi-intercom/session-targets.ts +5 -4
- package/src/pi-intercom/tool-registration.ts +1 -0
- package/src/pi-intercom/tool-results.ts +1 -1
- package/src/pi-intercom/types.ts +48 -43
- package/src/shared/journal-frames.ts +1 -1
- package/src/shared/json-projection.ts +2 -2
- package/src/shared/native-journal.ts +2 -2
- package/src/shared/output-page.ts +3 -3
- package/src/shared/post-exit-stdio-guard.ts +4 -4
- package/tsconfig.json +8 -1
|
@@ -6,23 +6,23 @@ export type WakeResult = {
|
|
|
6
6
|
};
|
|
7
7
|
export interface SessionInfo {
|
|
8
8
|
readonly id: string;
|
|
9
|
-
readonly name?: string;
|
|
9
|
+
readonly name?: string | undefined;
|
|
10
10
|
readonly cwd: string;
|
|
11
11
|
readonly model: string;
|
|
12
12
|
/** Opaque same-repository/worktree identity used for ambient peer awareness. */
|
|
13
|
-
readonly projectId?: string;
|
|
14
|
-
readonly rootSessionId?: string;
|
|
15
|
-
readonly parentSessionId?: string;
|
|
16
|
-
readonly incarnation?: string;
|
|
17
|
-
readonly guardedDelivery?: 1;
|
|
18
|
-
readonly applicationGuard?: string;
|
|
19
|
-
readonly status?: string;
|
|
20
|
-
readonly lastSeen?: number;
|
|
21
|
-
readonly lastIntercomActivity?: number;
|
|
22
|
-
readonly pendingAsks?: number;
|
|
23
|
-
readonly acceptsAsks?: boolean;
|
|
24
|
-
readonly subscriptions?: readonly TopicSubscription[];
|
|
25
|
-
readonly topics?: readonly TopicUpdate[];
|
|
13
|
+
readonly projectId?: string | undefined;
|
|
14
|
+
readonly rootSessionId?: string | undefined;
|
|
15
|
+
readonly parentSessionId?: string | undefined;
|
|
16
|
+
readonly incarnation?: string | undefined;
|
|
17
|
+
readonly guardedDelivery?: 1 | undefined;
|
|
18
|
+
readonly applicationGuard?: string | undefined;
|
|
19
|
+
readonly status?: string | undefined;
|
|
20
|
+
readonly lastSeen?: number | undefined;
|
|
21
|
+
readonly lastIntercomActivity?: number | undefined;
|
|
22
|
+
readonly pendingAsks?: number | undefined;
|
|
23
|
+
readonly acceptsAsks?: boolean | undefined;
|
|
24
|
+
readonly subscriptions?: readonly TopicSubscription[] | undefined;
|
|
25
|
+
readonly topics?: readonly TopicUpdate[] | undefined;
|
|
26
26
|
}
|
|
27
27
|
export interface TopicSubscription {
|
|
28
28
|
readonly topic: string;
|
|
@@ -52,10 +52,10 @@ export interface DeliveryGuard extends ContextTarget {
|
|
|
52
52
|
export interface PublicationReplyHint {
|
|
53
53
|
readonly tool: string;
|
|
54
54
|
readonly commandId: string;
|
|
55
|
-
readonly commandFingerprint?: string;
|
|
55
|
+
readonly commandFingerprint?: string | undefined;
|
|
56
56
|
}
|
|
57
57
|
export type SendOptions = Omit<Message, "id" | "timestamp" | "content"> & Message["content"] & {
|
|
58
|
-
readonly messageId?: string;
|
|
58
|
+
readonly messageId?: string | undefined;
|
|
59
59
|
};
|
|
60
60
|
export declare function isContextTarget(value: unknown): value is ContextTarget;
|
|
61
61
|
export declare function isDeliveryGuard(value: unknown): value is DeliveryGuard;
|
|
@@ -69,20 +69,20 @@ export declare function isHumanMessageOrigin(value: unknown): value is HumanMess
|
|
|
69
69
|
export interface Message {
|
|
70
70
|
readonly id: string;
|
|
71
71
|
readonly timestamp: number;
|
|
72
|
-
readonly replyTo?: string;
|
|
73
|
-
readonly expectsReply?: boolean;
|
|
72
|
+
readonly replyTo?: string | undefined;
|
|
73
|
+
readonly expectsReply?: boolean | undefined;
|
|
74
74
|
/** Recipients verify parent ownership before elevating human origin. */
|
|
75
|
-
readonly human?: HumanMessageOrigin;
|
|
76
|
-
readonly topic?: TopicUpdate;
|
|
77
|
-
readonly delivery?: MessageDelivery;
|
|
78
|
-
readonly queueMode?: QueueMode;
|
|
79
|
-
readonly threadId?: string;
|
|
80
|
-
readonly passive?: boolean;
|
|
81
|
-
readonly guard?: DeliveryGuard;
|
|
82
|
-
readonly replyHint?: PublicationReplyHint;
|
|
75
|
+
readonly human?: HumanMessageOrigin | undefined;
|
|
76
|
+
readonly topic?: TopicUpdate | undefined;
|
|
77
|
+
readonly delivery?: MessageDelivery | undefined;
|
|
78
|
+
readonly queueMode?: QueueMode | undefined;
|
|
79
|
+
readonly threadId?: string | undefined;
|
|
80
|
+
readonly passive?: boolean | undefined;
|
|
81
|
+
readonly guard?: DeliveryGuard | undefined;
|
|
82
|
+
readonly replyHint?: PublicationReplyHint | undefined;
|
|
83
83
|
readonly content: {
|
|
84
84
|
readonly text: string;
|
|
85
|
-
readonly attachments?: readonly Attachment[];
|
|
85
|
+
readonly attachments?: readonly Attachment[] | undefined;
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
export interface Attachment {
|
|
@@ -119,17 +119,17 @@ export interface SessionSnapshot {
|
|
|
119
119
|
})[];
|
|
120
120
|
}
|
|
121
121
|
type CompactTopicMessage = Omit<Message, "topic"> & {
|
|
122
|
-
readonly topic?: Omit<TopicUpdate, "text"> & {
|
|
123
|
-
readonly text?: string;
|
|
124
|
-
};
|
|
122
|
+
readonly topic?: (Omit<TopicUpdate, "text"> & {
|
|
123
|
+
readonly text?: string | undefined;
|
|
124
|
+
}) | undefined;
|
|
125
125
|
};
|
|
126
126
|
/** Topic text and the standard coalescing key need only one copy on the wire. */
|
|
127
127
|
export declare function compactTopicMessage(message: Message): CompactTopicMessage;
|
|
128
128
|
export declare function normalizeMessage(value: unknown): Message | null;
|
|
129
129
|
export interface LastMessageQuery {
|
|
130
|
-
readonly entryId?: string;
|
|
131
|
-
readonly offset?: number;
|
|
132
|
-
readonly limit?: number;
|
|
130
|
+
readonly entryId?: string | undefined;
|
|
131
|
+
readonly offset?: number | undefined;
|
|
132
|
+
readonly limit?: number | undefined;
|
|
133
133
|
}
|
|
134
134
|
export declare const LAST_MESSAGE_PAGE_LIMIT = 16384;
|
|
135
135
|
export declare function isLastMessageQuery(value: unknown): value is LastMessageQuery;
|
|
@@ -152,13 +152,13 @@ export interface NativeReceipt extends ReceiptQuery {
|
|
|
152
152
|
readonly version: 1;
|
|
153
153
|
readonly sender: {
|
|
154
154
|
readonly peerId: string;
|
|
155
|
-
readonly rootSessionId?: string;
|
|
156
|
-
readonly incarnation?: string;
|
|
155
|
+
readonly rootSessionId?: string | undefined;
|
|
156
|
+
readonly incarnation?: string | undefined;
|
|
157
157
|
};
|
|
158
158
|
readonly textSha256: string;
|
|
159
159
|
readonly delivery: MessageDelivery;
|
|
160
|
-
readonly guard?: DeliveryGuard;
|
|
161
|
-
readonly replyHint?: PublicationReplyHint;
|
|
160
|
+
readonly guard?: DeliveryGuard | undefined;
|
|
161
|
+
readonly replyHint?: PublicationReplyHint | undefined;
|
|
162
162
|
readonly checkpointEntryId: string;
|
|
163
163
|
readonly receivedAt: number;
|
|
164
164
|
/** Historical observations, not a promise that an input is still queued. */
|
|
@@ -207,16 +207,18 @@ export function compactTopicMessage(message) {
|
|
|
207
207
|
if (!message.topic) {
|
|
208
208
|
return message;
|
|
209
209
|
}
|
|
210
|
+
const { threadId, ...rest } = message;
|
|
211
|
+
const dropThreadId = message.queueMode === "replace" && threadId === `topic:${message.topic.topic}`;
|
|
210
212
|
return {
|
|
211
|
-
...
|
|
212
|
-
topic: message.topic.text === message.content.text
|
|
213
|
-
|
|
214
|
-
: message.topic,
|
|
215
|
-
threadId: message.queueMode === "replace" && message.threadId === `topic:${message.topic.topic}`
|
|
216
|
-
? undefined
|
|
217
|
-
: message.threadId,
|
|
213
|
+
...rest,
|
|
214
|
+
topic: message.topic.text === message.content.text ? withoutTopicText(message.topic) : message.topic,
|
|
215
|
+
...(dropThreadId || threadId === undefined ? {} : { threadId }),
|
|
218
216
|
};
|
|
219
217
|
}
|
|
218
|
+
function withoutTopicText(topic) {
|
|
219
|
+
const { text: _duplicate, ...rest } = topic;
|
|
220
|
+
return rest;
|
|
221
|
+
}
|
|
220
222
|
export function normalizeMessage(value) {
|
|
221
223
|
if (isMessage(value)) {
|
|
222
224
|
return value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitchmultz/pi-subagents",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.9",
|
|
4
4
|
"description": "Pi extension for subagent orchestration and local session coordination",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -63,8 +63,9 @@
|
|
|
63
63
|
"smoke:package": "node scripts/package-smoke.mjs",
|
|
64
64
|
"smoke:install": "node scripts/local-install-smoke.mjs",
|
|
65
65
|
"check:size": "node scripts/measure-size.mjs",
|
|
66
|
-
"lint": "oxlint --type-aware .",
|
|
66
|
+
"lint": "oxlint --type-aware --deny-warnings .",
|
|
67
67
|
"lint:fix": "oxlint --type-aware --fix .",
|
|
68
|
+
"lint:agent": "oxlint --type-aware --deny-warnings --format=agent .",
|
|
68
69
|
"format": "oxfmt --write .",
|
|
69
70
|
"format:check": "oxfmt --check .",
|
|
70
71
|
"ci": "npm run lint && npm run format:check && npm run typecheck && npm run check:size && npm run smoke:package && npm run smoke:install && npm run test:all",
|
|
@@ -109,8 +110,8 @@
|
|
|
109
110
|
"@earendil-works/pi-tui": "1.4.0",
|
|
110
111
|
"@types/node": "24.19.2",
|
|
111
112
|
"@types/sinon": "22.0.2",
|
|
112
|
-
"oxfmt": "0.
|
|
113
|
-
"oxlint": "1.
|
|
113
|
+
"oxfmt": "0.75.0",
|
|
114
|
+
"oxlint": "1.90.0",
|
|
114
115
|
"oxlint-tsgolint": "7.0.2005",
|
|
115
116
|
"sinon": "22.1.0",
|
|
116
117
|
"type-fest": "5.10.0",
|
|
@@ -418,7 +418,9 @@ function main() {
|
|
|
418
418
|
fs.readFileSync("/proc/self/status", "utf8"),
|
|
419
419
|
);
|
|
420
420
|
assert.ok(capability !== null, "E_PROCESS_VISIBILITY");
|
|
421
|
-
|
|
421
|
+
const capEff = capability[1];
|
|
422
|
+
assert.ok(capEff !== undefined, "E_PROCESS_VISIBILITY");
|
|
423
|
+
assert.ok((BigInt(`0x${capEff}`) & (1n << 19n)) !== 0n, "E_PROCESS_VISIBILITY");
|
|
422
424
|
}
|
|
423
425
|
assert.match(values["owner-uid"] ?? "", /^(?:0|[1-9][0-9]*)$/, "E_OWNER_UID");
|
|
424
426
|
const uid = Number(values["owner-uid"]);
|
package/src/agent/index.ts
CHANGED
|
@@ -82,17 +82,17 @@ function legacyInput(): string[] {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
class AgentRuntime {
|
|
85
|
-
private ctx?: ExtensionContext;
|
|
85
|
+
private ctx?: ExtensionContext | undefined;
|
|
86
86
|
private readonly reporter = new BackgroundReporter(() => this.ctx);
|
|
87
87
|
// A managed launcher owns children across worker replacement; otherwise this process does.
|
|
88
|
-
private host?: ManagedHost;
|
|
88
|
+
private host?: ManagedHost | undefined;
|
|
89
89
|
private accepted = false;
|
|
90
90
|
private base = "";
|
|
91
91
|
private owner = "";
|
|
92
92
|
private generation = "";
|
|
93
93
|
// Polling remains only while change hints are unavailable or a reconciliation failed.
|
|
94
94
|
private timer?: NodeJS.Timeout;
|
|
95
|
-
private woken?: NodeJS.Immediate;
|
|
95
|
+
private woken?: NodeJS.Immediate | undefined;
|
|
96
96
|
// Change hints for the owner directory, legacy results and every awaited canonical file.
|
|
97
97
|
private readonly watches: CompletionWatches;
|
|
98
98
|
private awaited: ReadonlySet<string> = new Set();
|
|
@@ -37,7 +37,7 @@ function contentIndex(keys: readonly (string | number)[]): number | undefined {
|
|
|
37
37
|
}
|
|
38
38
|
class TextPage {
|
|
39
39
|
private chars = 0;
|
|
40
|
-
private carry?: { index: number; text: string };
|
|
40
|
+
private carry?: { index: number; text: string } | undefined;
|
|
41
41
|
private readonly text = new Map<number, string>();
|
|
42
42
|
private readonly start: number;
|
|
43
43
|
constructor(page: number) {
|
|
@@ -147,7 +147,7 @@ export class ConversationJournal {
|
|
|
147
147
|
async page(
|
|
148
148
|
entry: NativeRecord,
|
|
149
149
|
page = 0,
|
|
150
|
-
options: { signal?: AbortSignal; hideThinking?: boolean } = {},
|
|
150
|
+
options: { signal?: AbortSignal | undefined; hideThinking?: boolean } = {},
|
|
151
151
|
): Promise<ConversationPage> {
|
|
152
152
|
const text = new TextPage(page);
|
|
153
153
|
const value = await this.native.project(
|
package/src/agent/launch.ts
CHANGED
|
@@ -126,7 +126,7 @@ export async function prepareLaunch(options: {
|
|
|
126
126
|
model: selection.model.id,
|
|
127
127
|
thinking: selection.thinking,
|
|
128
128
|
trusted,
|
|
129
|
-
name: input.name,
|
|
129
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
130
130
|
createdAt: Date.now(),
|
|
131
131
|
};
|
|
132
132
|
// Retain even unpublished native journals on failure, including full fork evidence.
|
|
@@ -114,12 +114,12 @@ interface PendingJournal {
|
|
|
114
114
|
interface Loaded {
|
|
115
115
|
readonly launch: Launch;
|
|
116
116
|
readonly result: Result | undefined;
|
|
117
|
-
readonly journal?: PendingJournal;
|
|
117
|
+
readonly journal?: PendingJournal | undefined;
|
|
118
118
|
}
|
|
119
119
|
interface Settled {
|
|
120
120
|
readonly launch: string;
|
|
121
121
|
readonly result: string;
|
|
122
|
-
readonly pending?: Loaded;
|
|
122
|
+
readonly pending?: Loaded | undefined;
|
|
123
123
|
readonly endedAt: number;
|
|
124
124
|
readonly createdAt: number;
|
|
125
125
|
}
|
|
@@ -127,7 +127,7 @@ interface Completed {
|
|
|
127
127
|
readonly id: string;
|
|
128
128
|
readonly endedAt: number;
|
|
129
129
|
readonly createdAt: number;
|
|
130
|
-
readonly loaded?: Loaded;
|
|
130
|
+
readonly loaded?: Loaded | undefined;
|
|
131
131
|
}
|
|
132
132
|
// Validated settled runs by file stamp, so polling does not reparse unchanged history.
|
|
133
133
|
const settledRuns = new Map<string, Map<string, Settled>>();
|
|
@@ -24,7 +24,7 @@ const WRITING = { text: "writing", thinking: "thinking", toolCall: "calling" } a
|
|
|
24
24
|
/** A live in-flight assistant message: a transient native reference, never a receipt. */
|
|
25
25
|
export interface LiveMessage {
|
|
26
26
|
readonly key: number;
|
|
27
|
-
message: AssistantMessage;
|
|
27
|
+
message: AssistantMessage | undefined;
|
|
28
28
|
ended: boolean;
|
|
29
29
|
}
|
|
30
30
|
/** A native message found in the session tree: `[seq, entry id, live message key, tool call id]`. */
|
|
@@ -77,7 +77,7 @@ export class LiveTool {
|
|
|
77
77
|
export class LiveState {
|
|
78
78
|
version = 0;
|
|
79
79
|
activityVersion = 0;
|
|
80
|
-
message?: LiveMessage;
|
|
80
|
+
message?: LiveMessage | undefined;
|
|
81
81
|
readonly tools = new Map<string, LiveTool>();
|
|
82
82
|
readonly settlements: Settlement[] = [];
|
|
83
83
|
settled = 0;
|
|
@@ -206,7 +206,7 @@ export class Viewer {
|
|
|
206
206
|
}
|
|
207
207
|
private message(frame: Frame, state: LiveState): string | undefined {
|
|
208
208
|
const live = state.message;
|
|
209
|
-
if (live === undefined) {
|
|
209
|
+
if (live === undefined || live.message === undefined) {
|
|
210
210
|
return undefined;
|
|
211
211
|
}
|
|
212
212
|
const reset = this.key !== live.key;
|
|
@@ -222,18 +222,31 @@ export class Viewer {
|
|
|
222
222
|
this.ended = live.ended;
|
|
223
223
|
const limit = Math.min(content.length, BLOCKS);
|
|
224
224
|
this.blocks.length = Math.min(this.blocks.length, limit);
|
|
225
|
-
|
|
226
|
-
const item = this.cursor(index, content[index]).next(index, frame, state);
|
|
227
|
-
if (item !== undefined) {
|
|
228
|
-
frame.blocks.push(item);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
225
|
+
this.appendBlocks(frame, state, content, limit);
|
|
231
226
|
if (!changed && frame.blocks.length === 0) {
|
|
232
227
|
return undefined;
|
|
233
228
|
}
|
|
234
229
|
// An ended message stays live until its session entry is found and handed over.
|
|
235
230
|
return `{"key":${live.key},"reset":${reset},"ended":${live.ended},"count":${content.length},"blocks":[${frame.blocks.join(",")}]}`;
|
|
236
231
|
}
|
|
232
|
+
/** Refill the frame from live content blocks, reusing cursors by block identity. */
|
|
233
|
+
private appendBlocks(
|
|
234
|
+
frame: Frame,
|
|
235
|
+
state: LiveState,
|
|
236
|
+
content: readonly Block[],
|
|
237
|
+
limit: number,
|
|
238
|
+
): void {
|
|
239
|
+
for (let index = 0; index < limit && !frame.full; index++) {
|
|
240
|
+
const block = content[index];
|
|
241
|
+
if (block === undefined) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const item = this.cursor(index, block).next(index, frame, state);
|
|
245
|
+
if (item !== undefined) {
|
|
246
|
+
frame.blocks.push(item);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
237
250
|
private cursor(index: number, block: Block): BlockCursor {
|
|
238
251
|
const existing = this.blocks.at(index);
|
|
239
252
|
if (existing?.ref === block) {
|
package/src/agent/observation.ts
CHANGED
|
@@ -62,11 +62,11 @@ export class ActorObservation {
|
|
|
62
62
|
private readonly state: LiveState;
|
|
63
63
|
private readonly viewers = new Set<Viewer>();
|
|
64
64
|
private readonly greeting = new Set<net.Socket>();
|
|
65
|
-
private server?: net.Server;
|
|
66
|
-
private socketPath?: string;
|
|
65
|
+
private server?: net.Server | undefined;
|
|
66
|
+
private socketPath?: string | undefined;
|
|
67
67
|
private inode?: number;
|
|
68
|
-
private unsubscribe?: () => void;
|
|
69
|
-
private timer?: NodeJS.Timeout;
|
|
68
|
+
private unsubscribe?: (() => void) | undefined;
|
|
69
|
+
private timer?: NodeJS.Timeout | undefined;
|
|
70
70
|
private closed = false;
|
|
71
71
|
/** The actor's run is in progress, including native work before and after the agent loop. */
|
|
72
72
|
private running = false;
|
|
@@ -125,7 +125,7 @@ export class Card implements Component {
|
|
|
125
125
|
ready = false;
|
|
126
126
|
page = 0;
|
|
127
127
|
pages = 1;
|
|
128
|
-
private cache?: { width: number; lines: string[] };
|
|
128
|
+
private cache?: { width: number; lines: string[] } | undefined;
|
|
129
129
|
private arguments?: unknown;
|
|
130
130
|
private phase = "";
|
|
131
131
|
constructor(id: string) {
|
|
@@ -168,7 +168,9 @@ export class Card implements Component {
|
|
|
168
168
|
this.tool.updateResult({
|
|
169
169
|
content: blocks(message.content)
|
|
170
170
|
.filter((part) => part.type === "text")
|
|
171
|
-
.map((part) =>
|
|
171
|
+
.map((part) =>
|
|
172
|
+
part.text === undefined ? { type: "text" } : { type: "text", text: part.text },
|
|
173
|
+
),
|
|
172
174
|
isError: message.isError === true,
|
|
173
175
|
});
|
|
174
176
|
this.tool.setExpanded(this.expanded);
|
|
@@ -221,7 +223,9 @@ export class Card implements Component {
|
|
|
221
223
|
{
|
|
222
224
|
content: blocks(result.content)
|
|
223
225
|
.filter((part) => part.type === "text")
|
|
224
|
-
.map((part) =>
|
|
226
|
+
.map((part) =>
|
|
227
|
+
part.text === undefined ? { type: "text" } : { type: "text", text: part.text },
|
|
228
|
+
),
|
|
225
229
|
isError,
|
|
226
230
|
},
|
|
227
231
|
partial,
|
|
@@ -19,7 +19,7 @@ export interface ObserverRow {
|
|
|
19
19
|
status: string;
|
|
20
20
|
live: boolean;
|
|
21
21
|
createdAt: number;
|
|
22
|
-
activity?: string;
|
|
22
|
+
activity?: string | undefined;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export class Details extends ScrollView {
|
|
@@ -149,7 +149,7 @@ export function appendHeader(
|
|
|
149
149
|
model: string;
|
|
150
150
|
thinking: string;
|
|
151
151
|
activity: string;
|
|
152
|
-
details?: Details;
|
|
152
|
+
details?: Details | undefined;
|
|
153
153
|
toggle: () => void;
|
|
154
154
|
},
|
|
155
155
|
): void {
|
|
@@ -6,7 +6,7 @@ import { agentObservationSocketPath } from "../pi-intercom/broker/paths.ts";
|
|
|
6
6
|
import { createMessageReader, writeFrame } from "../pi-intercom/broker/framing.ts";
|
|
7
7
|
import type { ObservationIdentity } from "./observation.ts";
|
|
8
8
|
import type { Launch } from "./contracts.ts";
|
|
9
|
-
export type Watch = { launch: Launch; identity?: ObservationIdentity };
|
|
9
|
+
export type Watch = { launch: Launch; identity?: ObservationIdentity | undefined };
|
|
10
10
|
import { Assert, Type } from "./typebox.ts";
|
|
11
11
|
|
|
12
12
|
const activitySchema = Type.Object({
|
|
@@ -86,8 +86,8 @@ export interface ViewedTool {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export class ObservationChannel {
|
|
89
|
-
current?: ObservationClient;
|
|
90
|
-
private pending?: ObservationClient;
|
|
89
|
+
current?: ObservationClient | undefined;
|
|
90
|
+
private pending?: ObservationClient | undefined;
|
|
91
91
|
private readonly changed: () => void;
|
|
92
92
|
constructor(changed: () => void) {
|
|
93
93
|
this.changed = changed;
|
|
@@ -134,7 +134,7 @@ export class ObservationClient {
|
|
|
134
134
|
closed = false;
|
|
135
135
|
revision = 0;
|
|
136
136
|
leaf?: string | null;
|
|
137
|
-
message?: { key: number; ended: boolean; blocks: ViewedBlock[] };
|
|
137
|
+
message?: { key: number; ended: boolean; blocks: ViewedBlock[] } | undefined;
|
|
138
138
|
readonly tools = new Map<string, ViewedTool>();
|
|
139
139
|
private readonly handovers: Array<Static<typeof settlementSchema>> = [];
|
|
140
140
|
private seq = -1;
|
|
@@ -290,7 +290,7 @@ export class ObservationClient {
|
|
|
290
290
|
}
|
|
291
291
|
/** Retire live state only after its exact journal entries have been indexed. */
|
|
292
292
|
settle(has: (entry: string) => boolean): boolean {
|
|
293
|
-
while (this.handovers.length > 0 && has(this.handovers[0][1])) {
|
|
293
|
+
while (this.handovers.length > 0 && has(this.handovers[0]?.[1] ?? "")) {
|
|
294
294
|
const item = this.handovers.shift();
|
|
295
295
|
if (item) {
|
|
296
296
|
this.retire(item[2], item[3]);
|
|
@@ -45,7 +45,7 @@ interface ScreenOptions {
|
|
|
45
45
|
export class ObserverScreen extends VStack {
|
|
46
46
|
private selected: string;
|
|
47
47
|
private readonly list = new ChildList((id) => this.open(id));
|
|
48
|
-
private reading?: Reading;
|
|
48
|
+
private reading?: Reading | undefined;
|
|
49
49
|
private readonly connection = new ObservationChannel(() => this.schedule());
|
|
50
50
|
private get client(): ObservationClient | undefined {
|
|
51
51
|
return this.connection.current;
|
|
@@ -53,10 +53,10 @@ export class ObserverScreen extends VStack {
|
|
|
53
53
|
private observed = { revision: -1, status: "", signature: "" };
|
|
54
54
|
private readonly saved = new Map<string, Reading>();
|
|
55
55
|
private focus: "list" | "conversation" = "list";
|
|
56
|
-
private details?: Details;
|
|
56
|
+
private details?: Details | undefined;
|
|
57
57
|
private failure = "";
|
|
58
|
-
private timer?: NodeJS.Timeout;
|
|
59
|
-
private indexer?: NodeJS.Immediate;
|
|
58
|
+
private timer?: NodeJS.Timeout | undefined;
|
|
59
|
+
private indexer?: NodeJS.Immediate | undefined;
|
|
60
60
|
private lastFlush = -Infinity;
|
|
61
61
|
private closed = false;
|
|
62
62
|
private frame?: LayoutFrame;
|
|
@@ -37,11 +37,11 @@ export class ObserverTranscript extends Container {
|
|
|
37
37
|
private readonly cards = new Map<string, Card>();
|
|
38
38
|
private readonly pages = new Map<string, PageRequest>();
|
|
39
39
|
private readonly expanded = new Set<string>();
|
|
40
|
-
private before?: string;
|
|
40
|
+
private before?: string | undefined;
|
|
41
41
|
private start = 0;
|
|
42
42
|
private stop = 0;
|
|
43
43
|
private entries: NativeRecord[] = [];
|
|
44
|
-
private selected?: string;
|
|
44
|
+
private selected?: string | undefined;
|
|
45
45
|
height = 0;
|
|
46
46
|
private mounted: Card[] = [];
|
|
47
47
|
waiting = false;
|
|
@@ -50,8 +50,8 @@ export class ObserverTranscript extends Container {
|
|
|
50
50
|
private readonly changed: (event: TranscriptChange) => void;
|
|
51
51
|
private readonly loaded: () => void;
|
|
52
52
|
private paused = false;
|
|
53
|
-
private hydration?: Promise<void
|
|
54
|
-
private active?: Hydration;
|
|
53
|
+
private hydration?: Promise<void> | undefined;
|
|
54
|
+
private active?: Hydration | undefined;
|
|
55
55
|
private readonly jobs = new Map<string, Hydration>();
|
|
56
56
|
private readonly pending = new Map<string, PageRequest>();
|
|
57
57
|
version = 0;
|
package/src/agent/observe.ts
CHANGED
|
@@ -24,20 +24,20 @@ type Commit = (live: ReadonlyMap<string, boolean>) => void;
|
|
|
24
24
|
type WidgetSnapshot = { status: string; rows: readonly ObserverRow[]; more: number; error: string };
|
|
25
25
|
|
|
26
26
|
class Observer {
|
|
27
|
-
private ctx?: ExtensionContext;
|
|
27
|
+
private ctx?: ExtensionContext | undefined;
|
|
28
28
|
private owner = "";
|
|
29
29
|
private base = "";
|
|
30
|
-
private timer?: NodeJS.Timeout;
|
|
31
|
-
private scanning?: Promise<void
|
|
30
|
+
private timer?: NodeJS.Timeout | undefined;
|
|
31
|
+
private scanning?: Promise<void> | undefined;
|
|
32
32
|
private rescan = false;
|
|
33
|
-
private close?: () => void;
|
|
34
|
-
private refresh?: () => void;
|
|
33
|
+
private close?: (() => void) | undefined;
|
|
34
|
+
private refresh?: (() => void) | undefined;
|
|
35
35
|
private readonly rows = new Map<string, ObserverRow>();
|
|
36
36
|
private readonly peers = new Map<string, { token: string; client: ObservationClient }>();
|
|
37
|
-
private watcher?: fs.FSWatcher;
|
|
37
|
+
private watcher?: fs.FSWatcher | undefined;
|
|
38
38
|
private widgetVisible = false;
|
|
39
39
|
private widgetSignature = "";
|
|
40
|
-
private widgetRender?: (snapshot: WidgetSnapshot) => void;
|
|
40
|
+
private widgetRender?: ((snapshot: WidgetSnapshot) => void) | undefined;
|
|
41
41
|
private readonly launches = new Map<string, Launch>();
|
|
42
42
|
private readonly finished = new Set<string>();
|
|
43
43
|
private pending: string[] = [];
|
|
@@ -209,7 +209,12 @@ class Observer {
|
|
|
209
209
|
if (this.ctx !== ctx) {
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
|
-
const live = new Map(
|
|
212
|
+
const live = new Map(
|
|
213
|
+
claims.flatMap(({ root }, index) => {
|
|
214
|
+
const verdict = verdicts[index];
|
|
215
|
+
return verdict === undefined ? [] : ([[root, verdict]] as const);
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
213
218
|
for (const commit of commits) {
|
|
214
219
|
commit(live);
|
|
215
220
|
}
|
package/src/agent/parking.ts
CHANGED
|
@@ -162,7 +162,7 @@ export class Parking {
|
|
|
162
162
|
private restored?: string;
|
|
163
163
|
private since = 0;
|
|
164
164
|
private leaf?: string | null;
|
|
165
|
-
private shell?: string;
|
|
165
|
+
private shell?: string | undefined;
|
|
166
166
|
private readonly base: string;
|
|
167
167
|
private readonly launch: Launch;
|
|
168
168
|
private readonly token: string;
|
package/src/agent/process.ts
CHANGED
|
@@ -76,7 +76,7 @@ export async function observeAlive(identities: readonly ClaimedProcess[]): Promi
|
|
|
76
76
|
* may adopt the child meanwhile.
|
|
77
77
|
*/
|
|
78
78
|
export class ParentCheck {
|
|
79
|
-
private current?: object;
|
|
79
|
+
private current?: object | undefined;
|
|
80
80
|
/**
|
|
81
81
|
* Reads the attachment synchronously, so an unreadable one still fails the caller at once.
|
|
82
82
|
* Missing or released attachments detach immediately; a check in flight makes this a no-op.
|
|
@@ -97,7 +97,7 @@ export class ParentCheck {
|
|
|
97
97
|
const [live] = await observeAlive([attachment]);
|
|
98
98
|
if (
|
|
99
99
|
this.current === check &&
|
|
100
|
-
|
|
100
|
+
live !== true &&
|
|
101
101
|
read(file, attachmentSchema)?.generation === attachment.generation
|
|
102
102
|
) {
|
|
103
103
|
detached();
|
|
@@ -123,10 +123,11 @@ function replaced(identity: ClaimedProcess): boolean {
|
|
|
123
123
|
}
|
|
124
124
|
export function ownIdentity(pid = process.pid): Omit<ProcessIdentity, "token"> {
|
|
125
125
|
// An ordinary main or managed launcher need not be a group leader. Ambiguous live PIDs fail closed.
|
|
126
|
+
const clock = readProcessClock();
|
|
126
127
|
return {
|
|
127
128
|
pid,
|
|
128
129
|
identity: readChildProcessIdentity(pid) ?? "unverified",
|
|
129
|
-
clock:
|
|
130
|
+
...(clock !== undefined ? { clock } : {}),
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
133
|
export function actor(base: string, launch: Launch): OwnedProcess | undefined {
|
|
@@ -218,11 +219,12 @@ async function spawnActor(
|
|
|
218
219
|
trySignalChildTree(child, "SIGKILL");
|
|
219
220
|
});
|
|
220
221
|
child.unref();
|
|
222
|
+
const clock = readProcessClock();
|
|
221
223
|
write(path.join(dir, "actor.lock", "process.json"), {
|
|
222
224
|
pid: child.pid,
|
|
223
225
|
identity: readChildProcessIdentity(child.pid) ?? "unverified",
|
|
224
226
|
token,
|
|
225
|
-
clock:
|
|
227
|
+
...(clock !== undefined ? { clock } : {}),
|
|
226
228
|
});
|
|
227
229
|
}
|
|
228
230
|
async function awaitStopping(
|
package/src/agent/runner.ts
CHANGED
|
@@ -43,8 +43,16 @@ function completionStatus(
|
|
|
43
43
|
}
|
|
44
44
|
return "completed";
|
|
45
45
|
}
|
|
46
|
+
/** The recorded failure: the launch error, the model's error, or the missing terminal message. */
|
|
47
|
+
const failureReason = (
|
|
48
|
+
error: string | undefined,
|
|
49
|
+
message: AssistantMessage | undefined,
|
|
50
|
+
): string | undefined =>
|
|
51
|
+
error ??
|
|
52
|
+
message?.errorMessage ??
|
|
53
|
+
(message ? undefined : "Native run produced no terminal assistant message.");
|
|
46
54
|
class Actor {
|
|
47
|
-
private active?: Promise<void
|
|
55
|
+
private active?: Promise<void> | undefined;
|
|
48
56
|
private stopping = false;
|
|
49
57
|
private stopDeadline?: NodeJS.Timeout;
|
|
50
58
|
private nextParentCheck = 0;
|
|
@@ -54,7 +62,7 @@ class Actor {
|
|
|
54
62
|
private readonly wake = new Wake();
|
|
55
63
|
// A deferred run waits for an idle session, which has no command-file event.
|
|
56
64
|
private retry = false;
|
|
57
|
-
private current?: Launch;
|
|
65
|
+
private current?: Launch | undefined;
|
|
58
66
|
private readonly errors: string[] = [];
|
|
59
67
|
private readonly started = new Set<string>();
|
|
60
68
|
// Actor-local validation metadata only; never retain command prompts or receipt authority.
|
|
@@ -96,17 +104,17 @@ class Actor {
|
|
|
96
104
|
const message = terminal?.message;
|
|
97
105
|
const accounting = this.usage(launch, baseline, terminal?.id);
|
|
98
106
|
const output = this.output(message);
|
|
107
|
+
const failure = failureReason(error, message);
|
|
99
108
|
const result: Result = {
|
|
100
109
|
id: launch.id,
|
|
101
110
|
owner: launch.owner,
|
|
102
111
|
status: completionStatus(this.stopping, message, error),
|
|
103
112
|
endedAt: Date.now(),
|
|
104
|
-
|
|
113
|
+
...(accounting.accountingError === undefined && terminal?.id !== undefined
|
|
114
|
+
? { terminalEntryId: terminal.id }
|
|
115
|
+
: {}),
|
|
105
116
|
...accounting,
|
|
106
|
-
error:
|
|
107
|
-
error ??
|
|
108
|
-
message?.errorMessage ??
|
|
109
|
-
(message ? undefined : "Native run produced no terminal assistant message."),
|
|
117
|
+
...(failure !== undefined ? { error: failure } : {}),
|
|
110
118
|
};
|
|
111
119
|
fs.writeFileSync(path.join(runDirectory(this.base, launch.id), "output.txt"), output, {
|
|
112
120
|
mode: 0o600,
|