pi-daddy 0.48.3 → 0.49.1
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 +11 -0
- package/README.md +55 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -0
- package/dist/cli.js.map +1 -1
- package/dist/governance/retention-contract.js +3 -3
- package/dist/governance/retention-contract.js.map +1 -1
- package/dist/governance/retention-validator.d.ts +7 -0
- package/dist/governance/retention-validator.d.ts.map +1 -0
- package/dist/governance/retention-validator.js +100 -0
- package/dist/governance/retention-validator.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/kernel/selected-definitions.d.ts.map +1 -1
- package/dist/kernel/selected-definitions.js +6 -3
- package/dist/kernel/selected-definitions.js.map +1 -1
- package/dist/products/dashboard-cli.d.ts.map +1 -1
- package/dist/products/dashboard-cli.js +46 -1
- package/dist/products/dashboard-cli.js.map +1 -1
- package/dist/products/dashboard-display-controls.d.ts +1 -1
- package/dist/products/dashboard-display-controls.d.ts.map +1 -1
- package/dist/products/dashboard-display-controls.js +11 -3
- package/dist/products/dashboard-display-controls.js.map +1 -1
- package/dist/products/dashboard-screen.d.ts +3 -1
- package/dist/products/dashboard-screen.d.ts.map +1 -1
- package/dist/products/dashboard-screen.js +21 -8
- package/dist/products/dashboard-screen.js.map +1 -1
- package/dist/products/dashboard-session-client.d.ts +9 -0
- package/dist/products/dashboard-session-client.d.ts.map +1 -1
- package/dist/products/dashboard-session-client.js.map +1 -1
- package/dist/products/dashboard-settings.d.ts +28 -0
- package/dist/products/dashboard-settings.d.ts.map +1 -0
- package/dist/products/dashboard-settings.js +42 -0
- package/dist/products/dashboard-settings.js.map +1 -0
- package/dist/products/diagnostic-export.d.ts +23 -0
- package/dist/products/diagnostic-export.d.ts.map +1 -0
- package/dist/products/diagnostic-export.js +264 -0
- package/dist/products/diagnostic-export.js.map +1 -0
- package/dist/products/diagnostic-filter.d.ts +7 -0
- package/dist/products/diagnostic-filter.d.ts.map +1 -0
- package/dist/products/diagnostic-filter.js +135 -0
- package/dist/products/diagnostic-filter.js.map +1 -0
- package/extensions/dashboard-session-server.ts +17 -2
- package/extensions/dashboard-settings.ts +149 -0
- package/extensions/delegation-operations.ts +81 -2
- package/extensions/execute-child.ts +9 -11
- package/extensions/execution-controls.ts +7 -2
- package/extensions/grants-command.ts +3 -1
- package/extensions/grants.ts +3 -0
- package/extensions/reload-environment.ts +3 -0
- package/extensions/run-delegation.ts +3 -1
- package/extensions/runtime-snapshot.ts +9 -1
- package/package.json +1 -1
- package/src/cli.ts +24 -1
- package/src/governance/retention-contract.ts +3 -3
- package/src/governance/retention-validator.ts +110 -0
- package/src/index.ts +2 -0
- package/src/kernel/selected-definitions.ts +5 -3
- package/src/products/dashboard-cli.ts +38 -1
- package/src/products/dashboard-display-controls.ts +10 -4
- package/src/products/dashboard-screen.ts +21 -10
- package/src/products/dashboard-session-client.ts +7 -1
- package/src/products/dashboard-settings.ts +72 -0
- package/src/products/diagnostic-export.ts +288 -0
- package/src/products/diagnostic-filter.ts +140 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/** Offline, explicit-selection export. Original files are read only; output is private and never training data. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { lstat, mkdir, open, readdir, realpath, writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { readBoundedBytes } from "../kernel/bounded-read.js";
|
|
7
|
+
import { parseRetentionJson } from "../governance/retention-json.js";
|
|
8
|
+
import { parseExecutionRetentionManifest, RETENTION_CONTENT_KINDS } from "../governance/retention-contract.js";
|
|
9
|
+
import { filterDiagnostic } from "./diagnostic-filter.js";
|
|
10
|
+
const MAX_FILE = 16 * 1024 * 1024;
|
|
11
|
+
const MAX_TOTAL = 128 * 1024 * 1024;
|
|
12
|
+
const MAX_SOURCES = 1024;
|
|
13
|
+
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
14
|
+
const object = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
|
|
15
|
+
const pathValue = (x) => typeof x === "string" && x.length > 0 && x.length <= 4096 && !/[\x00-\x1f]/.test(x);
|
|
16
|
+
const hashValue = (x) => typeof x === "string" && /^[a-f0-9]{64}$/.test(x);
|
|
17
|
+
export function parseDiagnosticSelection(text, base) {
|
|
18
|
+
const value = parseRetentionJson(text, 256 * 1024);
|
|
19
|
+
if (!object(value) ||
|
|
20
|
+
value.version !== 1 ||
|
|
21
|
+
!Array.isArray(value.sources) ||
|
|
22
|
+
Object.keys(value).some((key) => !["version", "sources", "retentionRoots"].includes(key)) ||
|
|
23
|
+
value.sources.length > MAX_SOURCES ||
|
|
24
|
+
(value.retentionRoots !== undefined && (!Array.isArray(value.retentionRoots) || value.retentionRoots.length > 16)))
|
|
25
|
+
throw new Error("invalid diagnostic selection v1");
|
|
26
|
+
const sources = value.sources.map((source) => {
|
|
27
|
+
if (!object(source) ||
|
|
28
|
+
!pathValue(source.path) ||
|
|
29
|
+
!["session", "json", "jsonl", "text"].includes(String(source.format)) ||
|
|
30
|
+
Object.keys(source).some((key) => !["path", "format", "sha256"].includes(key)) ||
|
|
31
|
+
(source.sha256 !== undefined && !hashValue(source.sha256)))
|
|
32
|
+
throw new Error("invalid diagnostic source");
|
|
33
|
+
return {
|
|
34
|
+
path: resolve(base, source.path),
|
|
35
|
+
format: source.format,
|
|
36
|
+
...(source.sha256 ? { sha256: source.sha256 } : {}),
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
const retentionRoots = (value.retentionRoots ?? []).map((path) => {
|
|
40
|
+
if (!pathValue(path))
|
|
41
|
+
throw new Error("invalid retention root");
|
|
42
|
+
return resolve(base, path);
|
|
43
|
+
});
|
|
44
|
+
return { version: 1, sources, retentionRoots };
|
|
45
|
+
}
|
|
46
|
+
async function readSource(path, limit = MAX_FILE) {
|
|
47
|
+
const before = await lstat(path);
|
|
48
|
+
if (!before.isFile() || (await realpath(path)) !== resolve(path))
|
|
49
|
+
throw new Error("source-must-be-canonical-regular-file");
|
|
50
|
+
const result = await readBoundedBytes(path, { maxBytes: limit, timeoutMs: 10000 }, {
|
|
51
|
+
open: (file, flags) => open(file, flags | constants.O_NOFOLLOW),
|
|
52
|
+
read: (handle, buffer, offset, length, position) => handle.read(buffer, offset, length, position),
|
|
53
|
+
close: (handle) => handle.close(),
|
|
54
|
+
});
|
|
55
|
+
if (!result.ok)
|
|
56
|
+
throw new Error(`source-${result.why}`);
|
|
57
|
+
const after = await lstat(path);
|
|
58
|
+
if (!after.isFile() ||
|
|
59
|
+
before.dev !== after.dev ||
|
|
60
|
+
before.ino !== after.ino ||
|
|
61
|
+
before.size !== after.size ||
|
|
62
|
+
before.mtimeMs !== after.mtimeMs ||
|
|
63
|
+
before.ctimeMs !== after.ctimeMs ||
|
|
64
|
+
after.size !== result.bytes.length)
|
|
65
|
+
throw new Error("source-changed-during-read");
|
|
66
|
+
return result.bytes;
|
|
67
|
+
}
|
|
68
|
+
const reason = (error) => {
|
|
69
|
+
const code = object(error) && typeof error.code === "string" ? error.code : undefined;
|
|
70
|
+
if (code)
|
|
71
|
+
return code;
|
|
72
|
+
return error instanceof Error && /^source-[a-z-]+$/.test(error.message)
|
|
73
|
+
? error.message
|
|
74
|
+
: "invalid-or-unreadable-source";
|
|
75
|
+
};
|
|
76
|
+
export async function exportDiagnostics(selectionPath, destination) {
|
|
77
|
+
const selectedPath = resolve(selectionPath);
|
|
78
|
+
const selectedBytes = await readSource(selectedPath, 256 * 1024);
|
|
79
|
+
const selection = parseDiagnosticSelection(new TextDecoder("utf-8", { fatal: true }).decode(selectedBytes), dirname(selectedPath));
|
|
80
|
+
const choices = new Map();
|
|
81
|
+
for (const source of selection.sources) {
|
|
82
|
+
const prior = choices.get(source.path);
|
|
83
|
+
if (prior && (prior.format !== source.format || prior.sha256 !== source.sha256))
|
|
84
|
+
throw new Error("conflicting diagnostic selection for one source");
|
|
85
|
+
choices.set(source.path, source);
|
|
86
|
+
}
|
|
87
|
+
const target = resolve(destination);
|
|
88
|
+
if ((await realpath(dirname(target))) !== dirname(target))
|
|
89
|
+
throw new Error("destination parent must be canonical");
|
|
90
|
+
await mkdir(target, { mode: 0o700 }); // Exclusive: never overwrite or remove an existing directory.
|
|
91
|
+
await mkdir(join(target, "objects"), { mode: 0o700 });
|
|
92
|
+
const inventory = [];
|
|
93
|
+
const objects = new Map();
|
|
94
|
+
const seen = new Map();
|
|
95
|
+
let total = 0;
|
|
96
|
+
let readTotal = 0;
|
|
97
|
+
const read = async (source) => {
|
|
98
|
+
if (readTotal >= MAX_TOTAL)
|
|
99
|
+
throw new Error("source-total-size-limit");
|
|
100
|
+
const raw = await readSource(source.path, Math.min(MAX_FILE, MAX_TOTAL - readTotal));
|
|
101
|
+
readTotal += raw.length;
|
|
102
|
+
return raw;
|
|
103
|
+
};
|
|
104
|
+
const add = async (source, expectedBytes, traverse = false) => {
|
|
105
|
+
const prior = seen.get(source.path);
|
|
106
|
+
const expected = (digest, bytes) => {
|
|
107
|
+
if (source.sha256 && digest !== source.sha256)
|
|
108
|
+
throw new Error("source-digest-mismatch");
|
|
109
|
+
if (expectedBytes !== undefined && bytes !== expectedBytes)
|
|
110
|
+
throw new Error("source-size-mismatch");
|
|
111
|
+
};
|
|
112
|
+
if (prior) {
|
|
113
|
+
if (prior.source.format !== source.format)
|
|
114
|
+
throw new Error("conflicting diagnostic selection for one source");
|
|
115
|
+
if (prior.row.status !== "included")
|
|
116
|
+
return undefined;
|
|
117
|
+
try {
|
|
118
|
+
expected(prior.row.sourceSha256, prior.row.sourceBytes);
|
|
119
|
+
if (!traverse)
|
|
120
|
+
return undefined;
|
|
121
|
+
// A selected manifest still needs traversal. Re-read and compare rather than caching all raw payloads.
|
|
122
|
+
const raw = await read(source);
|
|
123
|
+
if (sha256(raw) !== prior.row.sourceSha256 || raw.length !== prior.row.sourceBytes)
|
|
124
|
+
throw new Error("source-changed-before-traversal");
|
|
125
|
+
return raw;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
inventory.push({ source: source.path, format: source.format, status: "missing", reason: reason(error) });
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (seen.size >= MAX_SOURCES)
|
|
133
|
+
throw new Error("diagnostic source count exceeds bounds");
|
|
134
|
+
const row = { source: source.path, format: source.format, status: "missing" };
|
|
135
|
+
seen.set(source.path, { source, row });
|
|
136
|
+
inventory.push(row);
|
|
137
|
+
try {
|
|
138
|
+
const raw = await read(source);
|
|
139
|
+
row.sourceSha256 = sha256(raw);
|
|
140
|
+
row.sourceBytes = raw.length;
|
|
141
|
+
expected(row.sourceSha256, row.sourceBytes);
|
|
142
|
+
const filtered = filterDiagnostic(raw, source.format);
|
|
143
|
+
const hash = sha256(filtered.bytes), path = `objects/${hash}`;
|
|
144
|
+
if (!objects.has(hash)) {
|
|
145
|
+
if (total + filtered.bytes.length > MAX_TOTAL)
|
|
146
|
+
throw new Error("source-export-size-limit");
|
|
147
|
+
await writeFile(join(target, path), filtered.bytes, { flag: "wx", mode: 0o600 });
|
|
148
|
+
objects.set(hash, filtered.bytes.length);
|
|
149
|
+
total += filtered.bytes.length;
|
|
150
|
+
}
|
|
151
|
+
Object.assign(row, {
|
|
152
|
+
status: "included",
|
|
153
|
+
path,
|
|
154
|
+
sha256: hash,
|
|
155
|
+
bytes: filtered.bytes.length,
|
|
156
|
+
omissions: filtered.omissions,
|
|
157
|
+
});
|
|
158
|
+
return raw;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
row.reason = reason(error);
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
for (const source of [...choices.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)))
|
|
166
|
+
await add(source);
|
|
167
|
+
const captures = [];
|
|
168
|
+
for (const root of [...new Set(selection.retentionRoots)].sort()) {
|
|
169
|
+
try {
|
|
170
|
+
if ((await realpath(root)) !== root || !(await lstat(root)).isDirectory())
|
|
171
|
+
throw new Error("source-invalid-retention-root");
|
|
172
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
173
|
+
if (entries.length > MAX_SOURCES)
|
|
174
|
+
throw new Error("source-directory-size-limit");
|
|
175
|
+
for (const entry of entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
|
|
176
|
+
if (!entry.isDirectory())
|
|
177
|
+
continue;
|
|
178
|
+
const manifestPath = join(root, entry.name, "manifest.json");
|
|
179
|
+
const raw = await add({ path: manifestPath, format: "json" }, undefined, true);
|
|
180
|
+
if (!raw)
|
|
181
|
+
continue;
|
|
182
|
+
let manifest;
|
|
183
|
+
try {
|
|
184
|
+
manifest = parseExecutionRetentionManifest(raw.toString("utf8"));
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
inventory.push({
|
|
188
|
+
source: `${manifestPath}#manifest`,
|
|
189
|
+
format: "retention-manifest",
|
|
190
|
+
status: "missing",
|
|
191
|
+
reason: "invalid-retention-manifest",
|
|
192
|
+
});
|
|
193
|
+
captures.push({ source: manifestPath, status: "invalid-retention-manifest" });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const losses = manifest.coverage.losses;
|
|
197
|
+
captures.push({
|
|
198
|
+
source: manifestPath,
|
|
199
|
+
nativeSession: manifest.nativeSession,
|
|
200
|
+
state: manifest.state,
|
|
201
|
+
currentLosses: losses.filter((loss) => loss !== "native-session-read-failed-earlier"),
|
|
202
|
+
recoveredObservations: losses.filter((loss) => loss === "native-session-read-failed-earlier"),
|
|
203
|
+
acceptance: "not-assessed",
|
|
204
|
+
});
|
|
205
|
+
for (const kind of RETENTION_CONTENT_KINDS) {
|
|
206
|
+
const value = manifest.content[kind];
|
|
207
|
+
const format = kind === "session" ? "session" : kind === "checkReceipt" ? "json" : "text";
|
|
208
|
+
const source = `${manifestPath}#${kind}`;
|
|
209
|
+
if (!object(value) || value.status !== "retained") {
|
|
210
|
+
inventory.push({ source, format, status: "missing", reason: "not-retained" });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (!["session", "checkReceipt", "result"].includes(kind)) {
|
|
214
|
+
inventory.push({
|
|
215
|
+
source,
|
|
216
|
+
format,
|
|
217
|
+
status: "excluded",
|
|
218
|
+
reason: "raw-stream-or-pane",
|
|
219
|
+
sourceSha256: hashValue(value.sha256) ? value.sha256 : undefined,
|
|
220
|
+
});
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
await add({ path: join(dirname(manifestPath), value.path), format, sha256: value.sha256 }, value.bytes);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
inventory.push({ source: root, format: "retention-root", status: "missing", reason: reason(error) });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Capture metadata is also filtered; an untrusted manifest must not smuggle fields past the projection.
|
|
232
|
+
const manifest = {
|
|
233
|
+
version: 1,
|
|
234
|
+
policy: "pi-daddy-visible-diagnostic-v1",
|
|
235
|
+
selectionSha256: sha256(selectedBytes),
|
|
236
|
+
entries: inventory,
|
|
237
|
+
objects: objects.size,
|
|
238
|
+
exportedBytes: total,
|
|
239
|
+
captures,
|
|
240
|
+
private: true,
|
|
241
|
+
trainingEligible: false,
|
|
242
|
+
exportEligible: false,
|
|
243
|
+
acceptance: "not-assessed",
|
|
244
|
+
limitations: [
|
|
245
|
+
"Not an atomic filesystem snapshot; sources are checked individually.",
|
|
246
|
+
"Known reasoning/environment fields omitted; free-text secret detection is heuristic. Review before sharing.",
|
|
247
|
+
"Raw streams, images, unsupported native entries and unselected files are not exported.",
|
|
248
|
+
"Source hashes identify original bytes; object hashes identify filtered bytes. Neither grants approval or training rights.",
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
const safeManifest = filterDiagnostic(Buffer.from(JSON.stringify(manifest)), "json").bytes;
|
|
252
|
+
await writeFile(join(target, "inventory.json"), safeManifest, { mode: 0o600, flag: "wx" });
|
|
253
|
+
const missing = inventory.filter((entry) => entry.status === "missing").length;
|
|
254
|
+
return {
|
|
255
|
+
directory: target,
|
|
256
|
+
inventory: join(target, "inventory.json"),
|
|
257
|
+
sha256: sha256(safeManifest),
|
|
258
|
+
entries: inventory.length,
|
|
259
|
+
objects: objects.size,
|
|
260
|
+
missing,
|
|
261
|
+
trainingEligible: false,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=diagnostic-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostic-export.js","sourceRoot":"","sources":["../../src/products/diagnostic-export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface DiagnosticFilterResult {
|
|
2
|
+
bytes: Buffer;
|
|
3
|
+
omissions: Record<string, number>;
|
|
4
|
+
}
|
|
5
|
+
export type DiagnosticFormat = "session" | "json" | "jsonl" | "text";
|
|
6
|
+
export declare function filterDiagnostic(bytes: Uint8Array, format: DiagnosticFormat): DiagnosticFilterResult;
|
|
7
|
+
//# sourceMappingURL=diagnostic-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostic-filter.d.ts","sourceRoot":"","sources":["../../src/products/diagnostic-filter.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AACD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAErE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,gBAAgB,GAAG,sBAAsB,CA0HpG"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Local diagnostic projection. Known private fields are omitted; free-text secret detection is heuristic. */
|
|
2
|
+
import { parseRetentionJson } from "../governance/retention-json.js";
|
|
3
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4
|
+
const privateKey = new RegExp("^(?:thinking|reasoning|reasoning_content|reasoning_details|encrypted_content|thoughtSignature|thinkingSignature|signature|" +
|
|
5
|
+
"env|environment|environ|credentials?|authorization|api[-_]?key|token|access[-_]?token|refresh[-_]?token|password|secret)$", "i");
|
|
6
|
+
const privateType = /^(?:thinking|reasoning|redacted_thinking|reasoning_content|reasoning_details)$/i;
|
|
7
|
+
export function filterDiagnostic(bytes, format) {
|
|
8
|
+
const omissions = {};
|
|
9
|
+
const note = (reason) => (omissions[reason] = (omissions[reason] ?? 0) + 1);
|
|
10
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
11
|
+
const redact = (input) => {
|
|
12
|
+
let output = input.replace(/\b(?:sk-(?:or-v1-)?[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})\b/g, () => {
|
|
13
|
+
note("credential-pattern");
|
|
14
|
+
return "[REDACTED]";
|
|
15
|
+
});
|
|
16
|
+
output = output.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/<>"']+@/gi, (_match, scheme) => {
|
|
17
|
+
note("credential-url-userinfo");
|
|
18
|
+
return `${scheme}[REDACTED]@`;
|
|
19
|
+
});
|
|
20
|
+
output = output.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, () => {
|
|
21
|
+
note("credential-pattern");
|
|
22
|
+
return "Bearer [REDACTED]";
|
|
23
|
+
});
|
|
24
|
+
output = output.replace(/\b([A-Z][A-Z0-9_]*(?:API_KEY|TOKEN|PASSWORD|SECRET)|API_KEY|TOKEN|PASSWORD|SECRET)(\s*[=:]\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;]+)/gi, (_match, key, separator) => {
|
|
25
|
+
note("credential-assignment");
|
|
26
|
+
return `${key}${separator}[REDACTED]`;
|
|
27
|
+
});
|
|
28
|
+
return output;
|
|
29
|
+
};
|
|
30
|
+
const clean = (value, depth = 0) => {
|
|
31
|
+
if (depth > 64)
|
|
32
|
+
throw new Error("diagnostic nesting exceeds bounds");
|
|
33
|
+
if (typeof value === "string") {
|
|
34
|
+
// JSON embedded in a packet is a byte-bearing string. Keep it exactly when no redaction is needed.
|
|
35
|
+
if (/^\s*[\[{]/.test(value)) {
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = parseRetentionJson(value, 16 * 1024 * 1024);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return redact(value);
|
|
42
|
+
}
|
|
43
|
+
const filtered = clean(parsed, depth + 1);
|
|
44
|
+
return JSON.stringify(filtered) === JSON.stringify(parsed) ? value : JSON.stringify(filtered);
|
|
45
|
+
}
|
|
46
|
+
return redact(value);
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(value))
|
|
49
|
+
return value.flatMap((item) => {
|
|
50
|
+
if (object(item) && typeof item.type === "string" && privateType.test(item.type)) {
|
|
51
|
+
note("private-reasoning");
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return [clean(item, depth + 1)];
|
|
55
|
+
});
|
|
56
|
+
if (!object(value))
|
|
57
|
+
return value;
|
|
58
|
+
if (typeof value.type === "string" && privateType.test(value.type)) {
|
|
59
|
+
note("private-reasoning");
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const result = Object.create(null);
|
|
63
|
+
for (const [key, item] of Object.entries(value)) {
|
|
64
|
+
if (privateKey.test(key) || /(?:^|_)(?:API_KEY|ACCESS_TOKEN|REFRESH_TOKEN|PASSWORD|SECRET)$/.test(key)) {
|
|
65
|
+
note("private-field");
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
result[key] = clean(item, depth + 1);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
const pick = (value, keys) => Object.fromEntries(keys.filter((key) => Object.hasOwn(value, key)).map((key) => [key, value[key]]));
|
|
73
|
+
const session = (value) => {
|
|
74
|
+
if (!object(value))
|
|
75
|
+
throw new Error("invalid native session record");
|
|
76
|
+
const envelope = pick(value, ["type", "id", "parentId", "timestamp"]);
|
|
77
|
+
if (value.type === "session")
|
|
78
|
+
return clean({ ...envelope, ...pick(value, ["version", "cwd", "parentSession"]) });
|
|
79
|
+
if (value.type === "message" && object(value.message)) {
|
|
80
|
+
const message = value.message;
|
|
81
|
+
if (!["user", "assistant", "toolResult"].includes(String(message.role))) {
|
|
82
|
+
note("unsupported-message");
|
|
83
|
+
return { ...envelope, omitted: "unsupported-message" };
|
|
84
|
+
}
|
|
85
|
+
const content = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
|
|
86
|
+
if (!Array.isArray(content))
|
|
87
|
+
throw new Error("invalid native message content");
|
|
88
|
+
const visible = content.flatMap((block) => {
|
|
89
|
+
if (!object(block))
|
|
90
|
+
throw new Error("invalid native content block");
|
|
91
|
+
if (block.type === "text" && typeof block.text === "string")
|
|
92
|
+
return [pick(block, ["type", "text"])];
|
|
93
|
+
if (block.type === "toolCall")
|
|
94
|
+
return [pick(block, ["type", "id", "name", "arguments"])];
|
|
95
|
+
note(typeof block.type === "string" && privateType.test(block.type) ? "private-reasoning" : "non-text-content");
|
|
96
|
+
return [];
|
|
97
|
+
});
|
|
98
|
+
return clean({
|
|
99
|
+
...envelope,
|
|
100
|
+
message: {
|
|
101
|
+
...pick(message, ["role", "toolCallId", "toolName", "isError", "provider", "model", "stopReason", "usage"]),
|
|
102
|
+
content: visible,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (["model_change", "thinking_level_change", "session_info"].includes(String(value.type)))
|
|
107
|
+
return clean({ ...envelope, ...pick(value, ["provider", "modelId", "thinkingLevel", "name"]) });
|
|
108
|
+
note("unsupported-session-entry");
|
|
109
|
+
return clean({ ...envelope, omitted: "unsupported-session-entry" });
|
|
110
|
+
};
|
|
111
|
+
let output;
|
|
112
|
+
if (format === "text")
|
|
113
|
+
output = redact(text);
|
|
114
|
+
else if (format === "json")
|
|
115
|
+
output = JSON.stringify(clean(parseRetentionJson(text, 16 * 1024 * 1024))) + "\n";
|
|
116
|
+
else {
|
|
117
|
+
const lines = text.split("\n");
|
|
118
|
+
if (lines.at(-1) === "")
|
|
119
|
+
lines.pop();
|
|
120
|
+
if (format === "session") {
|
|
121
|
+
const header = parseRetentionJson(lines[0] ?? "", 16 * 1024 * 1024);
|
|
122
|
+
if (!object(header) || header.type !== "session" || header.version !== 3 || typeof header.id !== "string")
|
|
123
|
+
throw new Error("expected native Pi session v3");
|
|
124
|
+
}
|
|
125
|
+
output =
|
|
126
|
+
lines
|
|
127
|
+
.map((line) => {
|
|
128
|
+
const value = parseRetentionJson(line, 16 * 1024 * 1024);
|
|
129
|
+
return JSON.stringify(format === "session" ? session(value) : clean(value));
|
|
130
|
+
})
|
|
131
|
+
.join("\n") + "\n";
|
|
132
|
+
}
|
|
133
|
+
return { bytes: Buffer.from(output), omissions };
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=diagnostic-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostic-filter.js","sourceRoot":"","sources":["../../src/products/diagnostic-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dashboardSettingsSnapshot, dashboardJev, setDashboardLimit } from "./dashboard-settings.ts";
|
|
1
2
|
import { randomBytes } from "node:crypto";
|
|
2
3
|
import { chmod, rm } from "node:fs/promises";
|
|
3
4
|
import { createServer, type Server } from "node:net";
|
|
@@ -69,9 +70,22 @@ async function start(session: GrantsSession): Promise<DashboardSessionEndpoint>
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
async function respond(session: GrantsSession, token: string, line: string): Promise<Record<string, unknown>> {
|
|
72
|
-
const request = JSON.parse(line) as {
|
|
73
|
+
const request = JSON.parse(line) as {
|
|
74
|
+
token?: unknown;
|
|
75
|
+
action?: unknown;
|
|
76
|
+
edits?: unknown;
|
|
77
|
+
enabled?: unknown;
|
|
78
|
+
key?: unknown;
|
|
79
|
+
seconds?: unknown;
|
|
80
|
+
};
|
|
73
81
|
if (request.token !== token) return { ok: false, error: "unauthorised dashboard request" };
|
|
74
|
-
|
|
82
|
+
let jev;
|
|
83
|
+
if (request.action === "set-limit") {
|
|
84
|
+
setDashboardLimit(session, request.key, request.seconds);
|
|
85
|
+
} else if (request.action === "set-jev") {
|
|
86
|
+
if (typeof request.enabled !== "boolean") return { ok: false, error: "JEV requires an explicit boolean" };
|
|
87
|
+
jev = await dashboardJev(session, request.enabled ? "enable" : "disable");
|
|
88
|
+
} else if (request.action === "set-auto") {
|
|
75
89
|
if (typeof request.enabled !== "boolean") return { ok: false, error: "Auto mode requires an explicit boolean" };
|
|
76
90
|
await setAutoApproval(session, request.enabled, "dashboard");
|
|
77
91
|
} else if (request.action === "set") {
|
|
@@ -91,6 +105,7 @@ async function respond(session: GrantsSession, token: string, line: string): Pro
|
|
|
91
105
|
...(session.activity ? { activity: { ...session.activity } } : {}),
|
|
92
106
|
rows: sessionModelRows(session, [...session.definitions.keys()].sort()),
|
|
93
107
|
versions: versionSnapshot(session),
|
|
108
|
+
settings: await dashboardSettingsSnapshot(session, jev),
|
|
94
109
|
cost: null, // Live complete episode cost is unavailable; retained usage remains in the episode report.
|
|
95
110
|
};
|
|
96
111
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** Owner-bound dashboard settings. No provider call, global env edit, or permission inference. */
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { GrantsSession } from "./session.ts";
|
|
5
|
+
import type { DashboardJevState, DashboardSettingsSnapshot } from "../src/products/dashboard-settings.ts";
|
|
6
|
+
import { MAX_CHILDREN_PER_CALL } from "../src/kernel/fanout.ts";
|
|
7
|
+
import {
|
|
8
|
+
ENV_CHILD_TIMEOUT,
|
|
9
|
+
ENV_CHILD_IDLE_TIMEOUT,
|
|
10
|
+
timeoutFromEnv,
|
|
11
|
+
idleTimeoutFromEnv,
|
|
12
|
+
} from "../src/kernel/run-child.ts";
|
|
13
|
+
|
|
14
|
+
const bridges = new WeakMap<GrantsSession, Pick<ExtensionAPI, "events">>();
|
|
15
|
+
const unavailable = (error?: string): DashboardJevState => ({
|
|
16
|
+
available: false,
|
|
17
|
+
enabled: false,
|
|
18
|
+
pending: false,
|
|
19
|
+
...(error ? { error } : {}),
|
|
20
|
+
});
|
|
21
|
+
export function registerDashboardSettings(pi: Pick<ExtensionAPI, "events">, session: GrantsSession): void {
|
|
22
|
+
bridges.set(session, pi);
|
|
23
|
+
}
|
|
24
|
+
function owner(session: GrantsSession, edit = false) {
|
|
25
|
+
const current = session.reloadLifecycle?.autoMode;
|
|
26
|
+
if (!session.ownerBound || !current || current.reader !== session.autoMode)
|
|
27
|
+
throw new Error("Session settings owner is unavailable");
|
|
28
|
+
if (edit && !current.authority) throw new Error("Only the owning root session may edit settings");
|
|
29
|
+
return current;
|
|
30
|
+
}
|
|
31
|
+
export function childExecutionLimits(session: GrantsSession, env: NodeJS.ProcessEnv = process.env) {
|
|
32
|
+
const configured = session.reloadLifecycle?.executionSettings;
|
|
33
|
+
const current = session.reloadLifecycle?.autoMode;
|
|
34
|
+
const selected = configured?.nativeSessionId === current?.nativeSessionId ? configured : undefined;
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
wallMs: selected?.wallMs ?? timeoutFromEnv(env[ENV_CHILD_TIMEOUT]),
|
|
37
|
+
idleMs: selected?.idleMs ?? idleTimeoutFromEnv(env[ENV_CHILD_IDLE_TIMEOUT]),
|
|
38
|
+
wallSource: selected?.wallMs === undefined ? ("startup" as const) : ("session" as const),
|
|
39
|
+
idleSource: selected?.idleMs === undefined ? ("startup" as const) : ("session" as const),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function setDashboardLimit(session: GrantsSession, key: unknown, seconds: unknown): void {
|
|
43
|
+
const current = owner(session, true);
|
|
44
|
+
if (key !== "wall" && key !== "idle") throw new Error("Only child wall and idle limits are editable in this session");
|
|
45
|
+
if (
|
|
46
|
+
typeof seconds !== "number" ||
|
|
47
|
+
!Number.isSafeInteger(seconds) ||
|
|
48
|
+
seconds < 0 ||
|
|
49
|
+
seconds > Math.floor(2_147_483_647 / 1000)
|
|
50
|
+
)
|
|
51
|
+
throw new Error("Child limit must be whole seconds from 0 to 2147483; 0 restores the default");
|
|
52
|
+
const existing = session.reloadLifecycle.executionSettings;
|
|
53
|
+
const settings =
|
|
54
|
+
existing?.nativeSessionId === current.nativeSessionId
|
|
55
|
+
? { ...existing }
|
|
56
|
+
: { nativeSessionId: current.nativeSessionId };
|
|
57
|
+
const ms = key === "wall" ? timeoutFromEnv(String(seconds)) : idleTimeoutFromEnv(String(seconds));
|
|
58
|
+
session.reloadLifecycle.executionSettings = { ...settings, [key === "wall" ? "wallMs" : "idleMs"]: ms };
|
|
59
|
+
}
|
|
60
|
+
const safeText = (value: unknown) => (typeof value === "string" ? value.slice(0, 120) : undefined);
|
|
61
|
+
export async function dashboardJev(
|
|
62
|
+
session: GrantsSession,
|
|
63
|
+
action: "status" | "enable" | "disable",
|
|
64
|
+
): Promise<DashboardJevState> {
|
|
65
|
+
const current = owner(session, action !== "status");
|
|
66
|
+
const pi = bridges.get(session);
|
|
67
|
+
if (!pi?.events?.emit) {
|
|
68
|
+
if (action !== "status") throw new Error("JEV bridge unavailable; load skill-harness in the owner Pi session");
|
|
69
|
+
return unavailable();
|
|
70
|
+
}
|
|
71
|
+
const requestId = randomUUID();
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const timer = setTimeout(
|
|
74
|
+
() => (action === "status" ? resolve(unavailable()) : reject(new Error("JEV control was not acknowledged"))),
|
|
75
|
+
200,
|
|
76
|
+
);
|
|
77
|
+
const reply = (input: unknown) => {
|
|
78
|
+
const value = input as {
|
|
79
|
+
version?: number;
|
|
80
|
+
requestId?: string;
|
|
81
|
+
sessionId?: string;
|
|
82
|
+
pending?: boolean;
|
|
83
|
+
error?: string;
|
|
84
|
+
status?: {
|
|
85
|
+
enabled?: boolean;
|
|
86
|
+
mode?: string;
|
|
87
|
+
storage?: string | null;
|
|
88
|
+
remaining?: number;
|
|
89
|
+
availability?: string;
|
|
90
|
+
providerReadiness?: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
if (!value || value.version !== 1 || value.requestId !== requestId || value.sessionId !== current.nativeSessionId)
|
|
94
|
+
return;
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
if (session.reloadLifecycle.autoMode !== current || session.autoMode !== current.reader) {
|
|
97
|
+
reject(new Error("JEV settings owner changed"));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!value.status || typeof value.status.enabled !== "boolean") {
|
|
101
|
+
if (action !== "status") reject(new Error("JEV owner could not apply the request"));
|
|
102
|
+
else resolve(unavailable("JEV owner is not bound to this session"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
resolve({
|
|
106
|
+
available: true,
|
|
107
|
+
enabled: value.status.enabled,
|
|
108
|
+
pending: value.pending === true,
|
|
109
|
+
mode: safeText(value.status.mode),
|
|
110
|
+
storage: value.status.storage === null ? null : safeText(value.status.storage),
|
|
111
|
+
remaining: Number.isSafeInteger(value.status.remaining) ? value.status.remaining : undefined,
|
|
112
|
+
availability: safeText(value.status.availability),
|
|
113
|
+
providerReadiness: safeText(value.status.providerReadiness),
|
|
114
|
+
...(value.error ? { error: "JEV request declined or unavailable; inspect the owner Pi session." } : {}),
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
pi.events.emit("skill-harness:jev-control-v1", {
|
|
119
|
+
version: 1,
|
|
120
|
+
requestId,
|
|
121
|
+
sessionId: current.nativeSessionId,
|
|
122
|
+
action,
|
|
123
|
+
reply,
|
|
124
|
+
});
|
|
125
|
+
} catch {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
reject(new Error("JEV control bridge failed"));
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
export async function dashboardSettingsSnapshot(
|
|
132
|
+
session: GrantsSession,
|
|
133
|
+
jev?: DashboardJevState,
|
|
134
|
+
): Promise<DashboardSettingsSnapshot> {
|
|
135
|
+
const current = owner(session);
|
|
136
|
+
const limits = childExecutionLimits(session);
|
|
137
|
+
return {
|
|
138
|
+
editable: Boolean(current.authority),
|
|
139
|
+
wallSeconds: limits.wallMs / 1000,
|
|
140
|
+
idleSeconds: limits.idleMs / 1000,
|
|
141
|
+
wallSource: limits.wallSource,
|
|
142
|
+
idleSource: limits.idleSource,
|
|
143
|
+
descendants: session.capacity?.total ?? 0,
|
|
144
|
+
reserved: session.capacity?.reserved ?? 0,
|
|
145
|
+
maxDepth: session.maxDepth ?? 0,
|
|
146
|
+
perCall: MAX_CHILDREN_PER_CALL,
|
|
147
|
+
jev: jev ?? (await dashboardJev(session, "status")),
|
|
148
|
+
};
|
|
149
|
+
}
|