@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,12 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function record(value) {
|
|
6
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
-
}
|
|
2
|
+
import { NativeJournal } from "../shared/native-journal.js";
|
|
3
|
+
import { nativeProjection } from "../shared/journal-projections.js";
|
|
4
|
+
import { isRecord, recordAt } from "../shared/unknown.js";
|
|
8
5
|
export function plainText(value, length = 512) {
|
|
9
|
-
// Journal content is untrusted terminal input, never terminal control sequences.
|
|
10
6
|
return Array.from(value.slice(0, length))
|
|
11
7
|
.map((char) => {
|
|
12
8
|
const code = char.codePointAt(0) ?? 0;
|
|
@@ -14,170 +10,133 @@ export function plainText(value, length = 512) {
|
|
|
14
10
|
})
|
|
15
11
|
.join("");
|
|
16
12
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
29
|
-
return [plainText(block.text)];
|
|
30
|
-
}
|
|
31
|
-
if (block.type === "toolCall" && typeof block.name === "string") {
|
|
32
|
-
return [`tool ${plainText(block.name, 80)} ${plainText(JSON.stringify(block.arguments))}`];
|
|
33
|
-
}
|
|
34
|
-
return block.type === "thinking"
|
|
35
|
-
? []
|
|
36
|
-
: [`[${typeof block.type === "string" ? plainText(block.type, 40) : "non-text"} omitted]`];
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
function activity(value) {
|
|
40
|
-
if (!record(value) || value.type !== "message" || !record(value.message)) {
|
|
41
|
-
return [];
|
|
13
|
+
const PAGE_CHARS = 32 * 1024;
|
|
14
|
+
const callFields = new Set(["type", "id", "name"]);
|
|
15
|
+
const textFields = new Set(["text", "thinking"]);
|
|
16
|
+
const metadata = (keys, root) => {
|
|
17
|
+
if (keys[0] === "message" &&
|
|
18
|
+
keys[1] === "content" &&
|
|
19
|
+
keys.length > 3 &&
|
|
20
|
+
!callFields.has(String(keys[3]))) {
|
|
21
|
+
return false;
|
|
42
22
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
return lines;
|
|
23
|
+
return nativeProjection(keys, root);
|
|
24
|
+
};
|
|
25
|
+
function contentIndex(keys) {
|
|
26
|
+
if (keys[0] !== "message" || keys[1] !== "content") {
|
|
27
|
+
return undefined;
|
|
50
28
|
}
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
const name = typeof message.toolName === "string" ? plainText(message.toolName, 80) : "unknown";
|
|
54
|
-
return [`${label} ${name}:`, ...contentLines(message.content)];
|
|
29
|
+
if (keys.length === 2) {
|
|
30
|
+
return -1;
|
|
55
31
|
}
|
|
56
|
-
return [];
|
|
32
|
+
return typeof keys[2] === "number" && textFields.has(String(keys[3])) ? keys[2] : undefined;
|
|
57
33
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
identity = "";
|
|
66
|
-
stamp = 0;
|
|
67
|
-
file;
|
|
68
|
-
sessionId;
|
|
69
|
-
constructor(file, sessionId) {
|
|
70
|
-
this.file = file;
|
|
71
|
-
this.sessionId = sessionId;
|
|
72
|
-
}
|
|
73
|
-
reset(size) {
|
|
74
|
-
this.lines.length = 0;
|
|
75
|
-
this.offset = 0;
|
|
76
|
-
this.partial = Buffer.alloc(0);
|
|
77
|
-
this.dropping = false;
|
|
78
|
-
if (size > READ_BYTES) {
|
|
79
|
-
this.skip(size);
|
|
80
|
-
}
|
|
34
|
+
class TextPage {
|
|
35
|
+
chars = 0;
|
|
36
|
+
carry;
|
|
37
|
+
text = new Map();
|
|
38
|
+
start;
|
|
39
|
+
constructor(page) {
|
|
40
|
+
this.start = page * PAGE_CHARS;
|
|
81
41
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
const value = JSON.parse(head.subarray(0, lf).toString("utf8"));
|
|
96
|
-
if (!record(value) || value.type !== "session" || value.id !== this.sessionId) {
|
|
97
|
-
throw new Error("Native journal identity does not match this child.");
|
|
42
|
+
add(index, chunk) {
|
|
43
|
+
const previous = this.carry;
|
|
44
|
+
this.carry = undefined;
|
|
45
|
+
if (previous && previous.index !== index) {
|
|
46
|
+
this.take(previous.index, previous.text);
|
|
47
|
+
}
|
|
48
|
+
const text = previous?.index === index ? previous.text + chunk : chunk;
|
|
49
|
+
const last = text.charCodeAt(text.length - 1);
|
|
50
|
+
const pending = last >= 0xd800 && last <= 0xdbff;
|
|
51
|
+
this.take(index, pending ? text.slice(0, -1) : text);
|
|
52
|
+
if (pending) {
|
|
53
|
+
this.carry = { index, text: text.slice(-1) };
|
|
98
54
|
}
|
|
99
55
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
this.lines.push("[Malformed published native entry omitted.]");
|
|
111
|
-
}
|
|
56
|
+
take(index, text) {
|
|
57
|
+
const points = Array.from(text);
|
|
58
|
+
const from = Math.max(0, this.start - this.chars);
|
|
59
|
+
const to = Math.min(points.length, this.start + PAGE_CHARS - this.chars);
|
|
60
|
+
if (to > from) {
|
|
61
|
+
this.text.set(index, (this.text.get(index) ?? "") + points.slice(from, to).join(""));
|
|
62
|
+
}
|
|
63
|
+
this.chars += points.length;
|
|
112
64
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
65
|
+
finish(value, page) {
|
|
66
|
+
if (this.carry) {
|
|
67
|
+
this.take(this.carry.index, this.carry.text);
|
|
68
|
+
}
|
|
69
|
+
const message = { ...recordAt(value, "message") };
|
|
70
|
+
if (typeof message.content === "string") {
|
|
71
|
+
message.content = this.text.get(-1) ?? "";
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(message.content)) {
|
|
74
|
+
for (const [index, block] of message.content.entries()) {
|
|
75
|
+
if (isRecord(block) && typeof block.type === "string" && textFields.has(block.type)) {
|
|
76
|
+
block[block.type] = this.text.get(index) ?? "";
|
|
77
|
+
}
|
|
120
78
|
}
|
|
121
|
-
this.dropping = false;
|
|
122
|
-
start = lf + 1;
|
|
123
|
-
lf = bytes.indexOf(10, start);
|
|
124
|
-
}
|
|
125
|
-
this.partial = bytes.subarray(start);
|
|
126
|
-
if (this.partial.length > LINE_BYTES) {
|
|
127
|
-
this.partial = Buffer.alloc(0);
|
|
128
|
-
this.dropping = true;
|
|
129
|
-
this.lines.push("[Large native entry omitted; read the full journal.]");
|
|
130
79
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
80
|
+
return { message, page, pages: Math.max(1, Math.ceil(this.chars / PAGE_CHARS)) };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Native indexing owns ancestry and publication; only requested message pages are hydrated. */
|
|
84
|
+
export class ConversationJournal {
|
|
85
|
+
native;
|
|
86
|
+
branch;
|
|
87
|
+
constructor(file, sessionId) {
|
|
88
|
+
const stat = fs.lstatSync(file);
|
|
89
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
90
|
+
throw new Error("Child journal is not a regular file.");
|
|
91
|
+
}
|
|
92
|
+
this.native = new NativeJournal(file, "live", true, {
|
|
93
|
+
readBytes: 256 * 1024,
|
|
94
|
+
select: metadata,
|
|
95
|
+
});
|
|
96
|
+
if (this.native.records[0]?.value.id !== sessionId) {
|
|
97
|
+
throw new Error("Native journal identity does not match this child.");
|
|
134
98
|
}
|
|
135
99
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
100
|
+
entries(requested) {
|
|
101
|
+
const leaf = typeof requested === "string" && !this.native.byId.has(requested) ? undefined : requested;
|
|
102
|
+
if (this.branch?.length !== this.native.records.length || this.branch.leaf !== leaf) {
|
|
103
|
+
this.branch = {
|
|
104
|
+
length: this.native.records.length,
|
|
105
|
+
leaf,
|
|
106
|
+
entries: this.native.branch(leaf).filter((entry) => entry.value.type === "message"),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return this.branch.entries;
|
|
140
110
|
}
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
this.offset > READ_BYTES ? "Recent journal tail only; full journal retained." : "";
|
|
156
|
-
this.consume(chunk.subarray(0, count));
|
|
157
|
-
if (this.lines.length === 0 && this.notice.length === 0) {
|
|
158
|
-
this.notice = "Waiting for published native activity.";
|
|
159
|
-
}
|
|
111
|
+
async call(id, result, signal) {
|
|
112
|
+
const contains = (entry) => {
|
|
113
|
+
const content = recordAt(entry.value, "message")?.content;
|
|
114
|
+
return (Array.isArray(content) &&
|
|
115
|
+
content.some((part) => isRecord(part) && part.type === "toolCall" && part.id === id));
|
|
116
|
+
};
|
|
117
|
+
const entries = this.branch?.entries ?? [];
|
|
118
|
+
const entry = entries
|
|
119
|
+
.slice(0, entries.findIndex((item) => item.value.id === result))
|
|
120
|
+
.findLast(contains);
|
|
121
|
+
const content = entry && (await this.page(entry, 0, { signal })).message.content;
|
|
122
|
+
return Array.isArray(content)
|
|
123
|
+
? content.find((part) => isRecord(part) && part.type === "toolCall" && part.id === id)
|
|
124
|
+
: undefined;
|
|
160
125
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (!stat.isFile()) {
|
|
167
|
-
throw new Error("Native journal is not a regular file.");
|
|
126
|
+
async page(entry, page = 0, options = {}) {
|
|
127
|
+
const text = new TextPage(page);
|
|
128
|
+
const value = await this.native.project(entry, (keys, root) => {
|
|
129
|
+
if (contentIndex(keys) !== undefined && keys.length > 2) {
|
|
130
|
+
return 0;
|
|
168
131
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.identity = "";
|
|
175
|
-
this.notice = error instanceof Error ? plainText(error.message) : "Journal unavailable.";
|
|
176
|
-
}
|
|
177
|
-
finally {
|
|
178
|
-
if (fd !== undefined) {
|
|
179
|
-
fs.closeSync(fd);
|
|
132
|
+
return nativeProjection(keys, root);
|
|
133
|
+
}, (keys, chunk) => {
|
|
134
|
+
const index = contentIndex(keys);
|
|
135
|
+
if (index === undefined || (options.hideThinking === true && keys[3] === "thinking")) {
|
|
136
|
+
return;
|
|
180
137
|
}
|
|
181
|
-
|
|
138
|
+
text.add(index, chunk);
|
|
139
|
+
}, options.signal);
|
|
140
|
+
return text.finish(value, page);
|
|
182
141
|
}
|
|
183
142
|
}
|
package/dist/agent/launch.js
CHANGED
|
@@ -3,22 +3,34 @@ import * as path from "node:path";
|
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { loadSDK, packageRoot, runtimeEntry } from "./host.js";
|
|
5
5
|
import { runDirectory, write } from "./storage.js";
|
|
6
|
-
function selectedModel(input, context) {
|
|
6
|
+
function selectedModel(input, context, thinking, sdk) {
|
|
7
7
|
if (input.model === undefined) {
|
|
8
8
|
if (!context.model) {
|
|
9
9
|
throw new Error("No selected model; no fallback was used.");
|
|
10
10
|
}
|
|
11
|
-
return context.model;
|
|
11
|
+
return { model: context.model, thinking };
|
|
12
12
|
}
|
|
13
13
|
const slash = input.model.indexOf("/");
|
|
14
14
|
if (slash < 1) {
|
|
15
15
|
throw new Error("model must be an exact provider/model ID.");
|
|
16
16
|
}
|
|
17
17
|
const model = context.modelRegistry.find(input.model.slice(0, slash), input.model.slice(slash + 1));
|
|
18
|
-
if (
|
|
18
|
+
if (model) {
|
|
19
|
+
return { model, thinking };
|
|
20
|
+
}
|
|
21
|
+
if (typeof sdk.resolveModelReference !== "function") {
|
|
22
|
+
throw new Error(`Model ${input.model} is unavailable; no fallback was used. Check the provider/model ID. This host supports literal IDs only. If you intended a thinking suffix, update Pi for native thinking-suffix support.`);
|
|
23
|
+
}
|
|
24
|
+
const resolved = sdk.resolveModelReference({
|
|
25
|
+
text: input.model,
|
|
26
|
+
provider: input.model.slice(0, slash),
|
|
27
|
+
thinkingSuffix: "strict",
|
|
28
|
+
fuzzy: false,
|
|
29
|
+
}, context.modelRegistry.getAll(), () => true);
|
|
30
|
+
if (resolved.status !== "resolved") {
|
|
19
31
|
throw new Error(`Model ${input.model} is unavailable; no fallback was used.`);
|
|
20
32
|
}
|
|
21
|
-
return model;
|
|
33
|
+
return { model: resolved.model, thinking: resolved.thinkingLevel ?? thinking };
|
|
22
34
|
}
|
|
23
35
|
function targetTrust(sdk, cwd, context, agentDir) {
|
|
24
36
|
if (fs.realpathSync(cwd) === fs.realpathSync(context.cwd)) {
|
|
@@ -43,7 +55,7 @@ export async function prepareLaunch(options) {
|
|
|
43
55
|
if (!fs.statSync(cwd).isDirectory()) {
|
|
44
56
|
throw new Error("Delegation cwd is not a directory.");
|
|
45
57
|
}
|
|
46
|
-
const
|
|
58
|
+
const selection = selectedModel(input, context, thinking, sdk);
|
|
47
59
|
const trusted = targetTrust(sdk, cwd, context, agentDir);
|
|
48
60
|
const id = randomUUID();
|
|
49
61
|
const directory = runDirectory(base, id);
|
|
@@ -73,9 +85,9 @@ export async function prepareLaunch(options) {
|
|
|
73
85
|
sessionId: manager.getSessionId(),
|
|
74
86
|
prompt: input.prompt,
|
|
75
87
|
context: input.context ?? "fresh",
|
|
76
|
-
provider: model.provider,
|
|
77
|
-
model: model.id,
|
|
78
|
-
thinking,
|
|
88
|
+
provider: selection.model.provider,
|
|
89
|
+
model: selection.model.id,
|
|
90
|
+
thinking: selection.thinking,
|
|
79
91
|
trusted,
|
|
80
92
|
name: input.name,
|
|
81
93
|
createdAt: Date.now(),
|
package/dist/agent/legacy.js
CHANGED
|
@@ -4,31 +4,45 @@ import { journalStamp } from "../shared/native-journal.js";
|
|
|
4
4
|
import { receiptId } from "./receipts.js";
|
|
5
5
|
import { resolveTempRootDir } from "../shared/types.js";
|
|
6
6
|
import { isRecord } from "../shared/unknown.js";
|
|
7
|
-
import {
|
|
7
|
+
import { release, isHeld } from "./notifications.js";
|
|
8
8
|
import { missing } from "./storage.js";
|
|
9
|
-
|
|
9
|
+
import { errorText } from "./host.js";
|
|
10
|
+
// Retain only foreign classifications, never their saved output, for each live native manager.
|
|
11
|
+
const ignoredInputs = new WeakMap();
|
|
12
|
+
function fileStamp(file) {
|
|
10
13
|
const stat = fs.lstatSync(file, { bigint: true });
|
|
11
|
-
|
|
14
|
+
return stat.isFile() && !stat.isSymbolicLink() ? journalStamp(stat) : undefined;
|
|
15
|
+
}
|
|
16
|
+
function legacyInput(file, scan) {
|
|
17
|
+
const stamp = fileStamp(file);
|
|
18
|
+
if (stamp === undefined) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (scan.known?.get(file) === stamp) {
|
|
22
|
+
scan.ignored.set(file, stamp);
|
|
12
23
|
return;
|
|
13
24
|
}
|
|
14
25
|
const raw = fs.readFileSync(file, "utf8");
|
|
15
26
|
const data = JSON.parse(raw);
|
|
16
|
-
if (!isRecord(data) || data.sessionId !== owner) {
|
|
27
|
+
if (!isRecord(data) || data.sessionId !== scan.owner) {
|
|
28
|
+
if (fileStamp(file) === stamp) {
|
|
29
|
+
scan.ignored.set(file, stamp);
|
|
30
|
+
}
|
|
17
31
|
return;
|
|
18
32
|
}
|
|
33
|
+
return ownedInput({ file, raw, stamp, owner: scan.owner }, data);
|
|
34
|
+
}
|
|
35
|
+
function ownedInput(source, data) {
|
|
19
36
|
const id = typeof data.runId === "string" ? data.runId : data.id;
|
|
20
|
-
if (typeof id !== "string" || path.basename(file, ".json") !== id) {
|
|
37
|
+
if (typeof id !== "string" || path.basename(source.file, ".json") !== id) {
|
|
21
38
|
return;
|
|
22
39
|
}
|
|
23
40
|
const legacy = `legacy:${id}:${typeof data.timestamp === "number" ? data.timestamp : "unknown"}`;
|
|
24
41
|
const completionId = typeof data.completionId === "string" ? data.completionId : legacy;
|
|
25
42
|
return {
|
|
26
|
-
|
|
27
|
-
raw,
|
|
28
|
-
stamp: journalStamp(stat),
|
|
43
|
+
...source,
|
|
29
44
|
id,
|
|
30
45
|
key: `completion:${completionId}`,
|
|
31
|
-
owner,
|
|
32
46
|
single: data.mode === "single",
|
|
33
47
|
};
|
|
34
48
|
}
|
|
@@ -81,7 +95,7 @@ function published(ctx, input) {
|
|
|
81
95
|
return (receiptId(ctx.sessionManager.getSessionFile(), input.owner, (body) => receipt(body, input)) !==
|
|
82
96
|
undefined);
|
|
83
97
|
}
|
|
84
|
-
function
|
|
98
|
+
function notification(ctx, input) {
|
|
85
99
|
const identity = `legacy:${input.id}:${input.key}`;
|
|
86
100
|
if (published(ctx, input)) {
|
|
87
101
|
release(input.owner, identity);
|
|
@@ -95,24 +109,51 @@ function deliver(pi, ctx, input) {
|
|
|
95
109
|
if (isHeld(input.owner, identity)) {
|
|
96
110
|
return;
|
|
97
111
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
return {
|
|
113
|
+
owner: input.owner,
|
|
114
|
+
id: identity,
|
|
115
|
+
message: {
|
|
101
116
|
customType: "subagent-notify",
|
|
102
117
|
display: true,
|
|
103
118
|
// Full saved bytes, not a replacement summary. No owner/run record is invented.
|
|
104
119
|
content: input.raw,
|
|
105
120
|
details: { completion: { runId: input.id, key: input.key, ownerSessionId: input.owner } },
|
|
106
|
-
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function legacyNotice(ctx, file, scan) {
|
|
125
|
+
try {
|
|
126
|
+
const input = legacyInput(file, scan);
|
|
127
|
+
return input ? notification(ctx, input) : undefined;
|
|
107
128
|
}
|
|
108
129
|
catch (error) {
|
|
109
|
-
|
|
110
|
-
|
|
130
|
+
console.error(`Skipped legacy completion ${file}: ${errorText(error)}`);
|
|
131
|
+
return;
|
|
111
132
|
}
|
|
112
133
|
}
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
const
|
|
134
|
+
function startScan(ctx, directory) {
|
|
135
|
+
const manager = ctx.sessionManager;
|
|
136
|
+
const owner = manager.getSessionId();
|
|
137
|
+
const prior = ignoredInputs.get(manager);
|
|
138
|
+
const scan = {
|
|
139
|
+
directory,
|
|
140
|
+
owner,
|
|
141
|
+
known: prior?.directory === directory && prior.owner === owner ? prior.ignored : undefined,
|
|
142
|
+
ignored: new Map(),
|
|
143
|
+
};
|
|
144
|
+
// Replace even on a failed directory scan; vanished/unsafe inputs keep no cached decision.
|
|
145
|
+
ignoredInputs.set(manager, { directory, owner, ignored: scan.ignored });
|
|
146
|
+
return scan;
|
|
147
|
+
}
|
|
148
|
+
/** Retired releases created this directory at session start, before any of their runners. */
|
|
149
|
+
export function legacyDirectory() {
|
|
150
|
+
return path.join(resolveTempRootDir(), "async-subagent-results");
|
|
151
|
+
}
|
|
152
|
+
export function legacyNotifications(ctx) {
|
|
153
|
+
const notices = [];
|
|
154
|
+
const directory = legacyDirectory();
|
|
155
|
+
const root = path.dirname(directory);
|
|
156
|
+
const scan = startScan(ctx, directory);
|
|
116
157
|
let names;
|
|
117
158
|
try {
|
|
118
159
|
for (const candidate of [root, directory]) {
|
|
@@ -125,7 +166,7 @@ export function notifyLegacy(pi, ctx) {
|
|
|
125
166
|
}
|
|
126
167
|
catch (error) {
|
|
127
168
|
if (missing(error)) {
|
|
128
|
-
return;
|
|
169
|
+
return notices;
|
|
129
170
|
}
|
|
130
171
|
throw error;
|
|
131
172
|
}
|
|
@@ -133,9 +174,10 @@ export function notifyLegacy(pi, ctx) {
|
|
|
133
174
|
if (!name.endsWith(".json")) {
|
|
134
175
|
continue;
|
|
135
176
|
}
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
|
|
177
|
+
const notice = legacyNotice(ctx, path.join(directory, name), scan);
|
|
178
|
+
if (notice) {
|
|
179
|
+
notices.push(notice);
|
|
139
180
|
}
|
|
140
181
|
}
|
|
182
|
+
return notices;
|
|
141
183
|
}
|