pi-daddy 0.48.3 → 0.49.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 +7 -0
- package/README.md +53 -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/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/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,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
|
+
}
|
|
@@ -4,7 +4,7 @@ import { realpath } from "node:fs/promises";
|
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { shouldSeekApproval } from "../src/kernel/approval.ts";
|
|
6
6
|
import { planDelegation } from "../src/kernel/delegate.ts";
|
|
7
|
-
import type { OperationClaim } from "../src/kernel/dispatch-operation.ts";
|
|
7
|
+
import type { OperationClaim, OperationReference } from "../src/kernel/dispatch-operation.ts";
|
|
8
8
|
import { validOperationId } from "../src/kernel/dispatch-operation.ts";
|
|
9
9
|
import { GovernanceRefusal } from "../src/kernel/refusals.ts";
|
|
10
10
|
import { assertDefinitionIdentity } from "./definition-describe.ts";
|
|
@@ -15,6 +15,79 @@ import type { ChildSpec, DelegationToolContext } from "./run-delegation.ts";
|
|
|
15
15
|
import type { ExecutionOccurrenceIds } from "./execution-occurrence.ts";
|
|
16
16
|
import type { DelegationOutcome } from "./execute-child.ts";
|
|
17
17
|
|
|
18
|
+
// Short-lived exact finals for explicit workflow retention. The operation registry/socket
|
|
19
|
+
// remains identity-only; losing this observation never changes the original child result.
|
|
20
|
+
// The native-owner lifecycle survives extension reloads, which create a new GrantsSession.
|
|
21
|
+
const FINAL_BYTES = 4 * 1024 * 1024,
|
|
22
|
+
FINAL_ENTRIES = 128;
|
|
23
|
+
type Final = Extract<NonNullable<DelegationOutcome["final"]>, { state: "complete" }>;
|
|
24
|
+
export interface OperationFinalStore {
|
|
25
|
+
lifecycle: object;
|
|
26
|
+
owner: string;
|
|
27
|
+
bytes: number;
|
|
28
|
+
entries: Map<string, { executionId: string; final: Final }>;
|
|
29
|
+
}
|
|
30
|
+
export function bindOperationFinals(session: GrantsSession, sessionId: string, cwd: string) {
|
|
31
|
+
const owner = JSON.stringify([sessionId, cwd]),
|
|
32
|
+
old = session.reloadLifecycle.operationFinals;
|
|
33
|
+
if (!old || old.lifecycle !== session.reloadLifecycle || old.owner !== owner)
|
|
34
|
+
session.reloadLifecycle.operationFinals = {
|
|
35
|
+
lifecycle: session.reloadLifecycle,
|
|
36
|
+
owner,
|
|
37
|
+
bytes: 0,
|
|
38
|
+
entries: new Map(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Capture before admitting work; a late child cannot write a replacement session's store. */
|
|
42
|
+
export function operationFinalScope(session: GrantsSession): object | undefined {
|
|
43
|
+
return session.reloadLifecycle?.operationFinals;
|
|
44
|
+
}
|
|
45
|
+
export function operationFinal(session: GrantsSession, operation: OperationReference) {
|
|
46
|
+
const store = session.reloadLifecycle?.operationFinals,
|
|
47
|
+
item = store?.entries.get(operation.operationId);
|
|
48
|
+
if (!store || store.lifecycle !== session.reloadLifecycle || !item || item.executionId !== operation.executionId)
|
|
49
|
+
return {
|
|
50
|
+
state: "unavailable" as const,
|
|
51
|
+
reason:
|
|
52
|
+
"exact native final was not retained, expired from the 4 MiB session buffer, or belongs to an earlier session",
|
|
53
|
+
};
|
|
54
|
+
const observed = operation.runtime?.final,
|
|
55
|
+
final = item.final;
|
|
56
|
+
if (
|
|
57
|
+
operation.state !== "settled" ||
|
|
58
|
+
observed?.state !== "complete" ||
|
|
59
|
+
["sessionId", "messageId", "leafId", "sha256"].some(
|
|
60
|
+
(key) => observed[key as keyof typeof observed] !== final[key as keyof Final],
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
return { state: "unavailable" as const, reason: "native final identity no longer matches the operation" };
|
|
64
|
+
return { ...final };
|
|
65
|
+
}
|
|
66
|
+
function retainOperationFinal(
|
|
67
|
+
session: GrantsSession,
|
|
68
|
+
claim: OperationClaim,
|
|
69
|
+
result: DelegationOutcome,
|
|
70
|
+
scope: object | undefined,
|
|
71
|
+
) {
|
|
72
|
+
const store = session.reloadLifecycle?.operationFinals,
|
|
73
|
+
final = result.final;
|
|
74
|
+
if (!store || store !== scope || store.lifecycle !== session.reloadLifecycle || final?.state !== "complete") return;
|
|
75
|
+
const bytes = Buffer.byteLength(final.text);
|
|
76
|
+
if (!bytes || bytes > FINAL_BYTES || createHash("sha256").update(final.text).digest("hex") !== final.sha256) return;
|
|
77
|
+
const old = store.entries.get(claim.operation.operationId);
|
|
78
|
+
if (old) {
|
|
79
|
+
store.bytes -= Buffer.byteLength(old.final.text);
|
|
80
|
+
store.entries.delete(claim.operation.operationId);
|
|
81
|
+
}
|
|
82
|
+
while (store.bytes + bytes > FINAL_BYTES || store.entries.size >= FINAL_ENTRIES) {
|
|
83
|
+
const first = store.entries.entries().next().value!;
|
|
84
|
+
store.bytes -= Buffer.byteLength(first[1].final.text);
|
|
85
|
+
store.entries.delete(first[0]);
|
|
86
|
+
}
|
|
87
|
+
store.entries.set(claim.operation.operationId, { executionId: claim.operation.executionId, final: { ...final } });
|
|
88
|
+
store.bytes += bytes;
|
|
89
|
+
}
|
|
90
|
+
|
|
18
91
|
export const operationIdShape = () =>
|
|
19
92
|
Type.Optional(
|
|
20
93
|
Type.String({
|
|
@@ -121,7 +194,12 @@ export function existingOperationOutcome(claim: OperationClaim, depth: number):
|
|
|
121
194
|
operation: { ...claim.operation, reused: true },
|
|
122
195
|
};
|
|
123
196
|
}
|
|
124
|
-
export async function finishDelegationOperation(
|
|
197
|
+
export async function finishDelegationOperation(
|
|
198
|
+
claim: OperationClaim,
|
|
199
|
+
result: DelegationOutcome,
|
|
200
|
+
session: GrantsSession,
|
|
201
|
+
finalScope: object | undefined,
|
|
202
|
+
) {
|
|
125
203
|
const cleanup = result.cleanup;
|
|
126
204
|
const state =
|
|
127
205
|
cleanup?.state === "settled"
|
|
@@ -161,6 +239,7 @@ export async function finishDelegationOperation(claim: OperationClaim, result: D
|
|
|
161
239
|
}
|
|
162
240
|
: {}),
|
|
163
241
|
});
|
|
242
|
+
retainOperationFinal(session, claim, result, finalScope);
|
|
164
243
|
result.operation = {
|
|
165
244
|
...claim.operation,
|
|
166
245
|
state,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { childExecutionLimits } from "./dashboard-settings.ts";
|
|
1
2
|
import { realpath } from "node:fs/promises";
|
|
2
3
|
import { BoundedReadCleanupError } from "../src/kernel/bounded-read.ts";
|
|
3
4
|
import { retainDiscoveryCleanupFailure } from "./session.ts";
|
|
@@ -16,13 +17,7 @@ import { appendLedgerEvent, buildChildLifecycleEvent } from "../src/governance/l
|
|
|
16
17
|
import { hasFinalizerError } from "../src/governance/finalization.ts";
|
|
17
18
|
import { mergeChildEnv } from "../src/kernel/propagation.ts";
|
|
18
19
|
import type { Capability } from "../src/kernel/resolve.ts";
|
|
19
|
-
import {
|
|
20
|
-
DEFAULT_KILL_GRACE_MS,
|
|
21
|
-
ENV_CHILD_IDLE_TIMEOUT,
|
|
22
|
-
ENV_CHILD_TIMEOUT,
|
|
23
|
-
idleTimeoutFromEnv,
|
|
24
|
-
timeoutFromEnv,
|
|
25
|
-
} from "../src/kernel/run-child.ts";
|
|
20
|
+
import { DEFAULT_KILL_GRACE_MS, ENV_CHILD_IDLE_TIMEOUT, ENV_CHILD_TIMEOUT } from "../src/kernel/run-child.ts";
|
|
26
21
|
import { activitySessionFor } from "../src/executors/activity-session.ts";
|
|
27
22
|
import { processTreeActivity } from "../src/executors/process-activity.ts";
|
|
28
23
|
import { resolveWorkspace } from "../src/executors/herdr-cli.ts";
|
|
@@ -147,6 +142,7 @@ export async function executePlannedChild(input: ChildExecutionInput): Promise<D
|
|
|
147
142
|
const runtime = input.session.reloadLifecycle?.runtimeSettlement;
|
|
148
143
|
let registered = false;
|
|
149
144
|
try {
|
|
145
|
+
const limits = childExecutionLimits(input.session);
|
|
150
146
|
input = { ...input, cwd: await realpath(input.preparedWorkspace?.workspace.root ?? input.cwd) };
|
|
151
147
|
runtime?.begin(input.executionId);
|
|
152
148
|
registered = Boolean(runtime);
|
|
@@ -157,6 +153,7 @@ export async function executePlannedChild(input: ChildExecutionInput): Promise<D
|
|
|
157
153
|
input.onExecutorEntry?.();
|
|
158
154
|
},
|
|
159
155
|
runtime,
|
|
156
|
+
limits,
|
|
160
157
|
);
|
|
161
158
|
try {
|
|
162
159
|
await runtime?.finish(
|
|
@@ -220,7 +217,8 @@ export async function executePlannedChild(input: ChildExecutionInput): Promise<D
|
|
|
220
217
|
async function executePreparedChild(
|
|
221
218
|
input: ChildExecutionInput,
|
|
222
219
|
onExecutorEntry: () => void,
|
|
223
|
-
runtime
|
|
220
|
+
runtime: import("../src/governance/runtime-settlement.ts").RuntimeSettlement | undefined,
|
|
221
|
+
limits: ReturnType<typeof childExecutionLimits>,
|
|
224
222
|
): Promise<DelegationOutcome> {
|
|
225
223
|
const { session, plan, childId, executionId, parentExecutionId, preparedWorkspace, signal, onProgress } = input;
|
|
226
224
|
const ledgerPath = session.ledgerPath;
|
|
@@ -269,10 +267,10 @@ async function executePreparedChild(
|
|
|
269
267
|
);
|
|
270
268
|
activityStarted = true;
|
|
271
269
|
} catch {}
|
|
272
|
-
const configuredTimeoutMs =
|
|
270
|
+
const configuredTimeoutMs = limits.wallMs;
|
|
273
271
|
// PR 3e: the working bound is inactivity; `deadlineAt` below is the runaway ceiling. Every child gets a pi session
|
|
274
272
|
// file so the parent can see it working when its stdout is quiet (pi appends each message and tool result to it).
|
|
275
|
-
const configuredIdleMs =
|
|
273
|
+
const configuredIdleMs = limits.idleMs;
|
|
276
274
|
const activitySession = await activitySessionFor(plan.args, executionId);
|
|
277
275
|
// Disposed on every path (review finding: it leaked on every throw), except when the operator keeps a Herdr pane,
|
|
278
276
|
// where the interactive pi in that pane is still alive and still appending to this file.
|
|
@@ -378,7 +376,7 @@ async function executePreparedChild(
|
|
|
378
376
|
workspaceId: preparedWorkspace?.workspace.workspaceId ?? null,
|
|
379
377
|
});
|
|
380
378
|
const sessionFlag = plan.args.indexOf("--session");
|
|
381
|
-
|
|
379
|
+
// Pi creates the file during execution; observe its exact final below, not a guaranteed pre-spawn miss.
|
|
382
380
|
let releaseReason = "failed";
|
|
383
381
|
let retainWriterLease = false;
|
|
384
382
|
let terminalAttempted = false;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { childExecutionLimits } from "./dashboard-settings.ts";
|
|
1
2
|
/** Operator-facing execution settings; display never changes admission, deadlines or retention. */
|
|
2
3
|
import { timeoutFromEnv, idleTimeoutFromEnv } from "../src/kernel/run-child.ts";
|
|
3
4
|
import { timeoutMsFromEnv } from "../src/governance/approval-prompt.ts";
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
export function renderExecutionControls(
|
|
12
13
|
nativeSessionRoot: string | undefined,
|
|
13
14
|
env: NodeJS.ProcessEnv = process.env,
|
|
15
|
+
limits?: ReturnType<typeof childExecutionLimits>,
|
|
14
16
|
): string[] {
|
|
15
17
|
let approval: string;
|
|
16
18
|
try {
|
|
@@ -20,9 +22,12 @@ export function renderExecutionControls(
|
|
|
20
22
|
approval = `${ENV_APPROVAL_TIMEOUT} invalid; a needed new prompt will refuse`;
|
|
21
23
|
}
|
|
22
24
|
const source = (key: string) => (env[key] === undefined ? "default" : `from ${key}; zero/invalid uses default`);
|
|
25
|
+
const wallSource = limits?.wallSource === "session" ? "session override; future children" : source(ENV_CHILD_TIMEOUT);
|
|
26
|
+
const idleSource =
|
|
27
|
+
limits?.idleSource === "session" ? "session override; future children" : source(ENV_CHILD_IDLE_TIMEOUT);
|
|
23
28
|
return [
|
|
24
|
-
` child wall ${timeoutFromEnv(env[ENV_CHILD_TIMEOUT]) / 1000}s (${
|
|
25
|
-
` child idle ${idleTimeoutFromEnv(env[ENV_CHILD_IDLE_TIMEOUT]) / 1000}s (${
|
|
29
|
+
` child wall ${(limits?.wallMs ?? timeoutFromEnv(env[ENV_CHILD_TIMEOUT])) / 1000}s (${wallSource})`,
|
|
30
|
+
` child idle ${(limits?.idleMs ?? idleTimeoutFromEnv(env[ENV_CHILD_IDLE_TIMEOUT])) / 1000}s (${idleSource})`,
|
|
26
31
|
" process safety controls; no time/token budget is added to the model prompt",
|
|
27
32
|
` approval ${approval}`,
|
|
28
33
|
` transcripts ${
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { childExecutionLimits } from "./dashboard-settings.ts";
|
|
1
2
|
/**
|
|
2
3
|
* The `/grants` command — session status, approvals, and ledger integrity.
|
|
3
4
|
*
|
|
@@ -28,6 +29,7 @@ export interface GrantsCommandContext {
|
|
|
28
29
|
governed: boolean;
|
|
29
30
|
/** Host opt-in captured by this session, not a later environment reread. */
|
|
30
31
|
nativeSessionRoot?: string;
|
|
32
|
+
executionLimits?: ReturnType<typeof childExecutionLimits>;
|
|
31
33
|
autoModeStatus?: string;
|
|
32
34
|
/** Explicit owner mutation; the command parser itself holds no permission authority. */
|
|
33
35
|
changeAutoMode?: (enabled: boolean) => Promise<string>;
|
|
@@ -439,7 +441,7 @@ export const grantsCommand = {
|
|
|
439
441
|
// ADR-0031, and its probe is only defensible because this line exists. Placed next to `holding` so the
|
|
440
442
|
// two facts about what a spawn will be sit together.
|
|
441
443
|
` executor ${executor.disclosure}`,
|
|
442
|
-
...renderExecutionControls(ctx.grants.nativeSessionRoot),
|
|
444
|
+
...renderExecutionControls(ctx.grants.nativeSessionRoot, process.env, ctx.grants.executionLimits),
|
|
443
445
|
` Auto ${ctx.grants.autoModeStatus ?? "OFF (default)"}`,
|
|
444
446
|
` depth ${depth} of max ${maxDepth}${maxDepth <= 0 ? " (spawning disabled)" : ""}`,
|
|
445
447
|
` ledger ${ledgerPath || "(not recording — set PI_DADDY_LEDGER)"}`,
|
package/extensions/grants.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerDashboardSettings, childExecutionLimits } from "./dashboard-settings.ts";
|
|
1
2
|
import { bindEcosystemVersions } from "./ecosystem-versions.ts";
|
|
2
3
|
import {
|
|
3
4
|
initializeSessionAutoMode,
|
|
@@ -90,6 +91,7 @@ export default function (pi: ExtensionAPI, trustedSession?: GrantsSession) {
|
|
|
90
91
|
session.currentThinking = typeof pi.getThinkingLevel === "function" ? () => pi.getThinkingLevel() : undefined;
|
|
91
92
|
registerActivityTimeline(pi, session);
|
|
92
93
|
const runtimeSnapshot = registerRuntimeSnapshot(pi, session);
|
|
94
|
+
registerDashboardSettings(pi, session);
|
|
93
95
|
const dashboardPluginRoot = fileURLToPath(new URL("../herdr-plugin/", import.meta.url));
|
|
94
96
|
const dashboardPaths = defaultDashboardPaths(agentDir());
|
|
95
97
|
// Definitions are registered only after owner-bound session_start. Until then there is no delegation
|
|
@@ -481,6 +483,7 @@ export default function (pi: ExtensionAPI, trustedSession?: GrantsSession) {
|
|
|
481
483
|
}
|
|
482
484
|
: {}),
|
|
483
485
|
nativeSessionRoot: session.nativeSessionRoot,
|
|
486
|
+
executionLimits: childExecutionLimits(session),
|
|
484
487
|
changeAutoMode: async (enabled: boolean) => {
|
|
485
488
|
const { auto } = await setAutoApproval(session, enabled, "command");
|
|
486
489
|
return `${auto.enabled ? "ON" : "OFF"} (${auto.source}; current session permissions)`;
|
|
@@ -5,6 +5,8 @@ import type { BoundedReadCleanupError } from "../src/kernel/bounded-read.ts";
|
|
|
5
5
|
|
|
6
6
|
/** The root baseline and latest child publication for one real Pi session owner. */
|
|
7
7
|
export interface ReloadLifecycle {
|
|
8
|
+
operationFinals?: import("./delegation-operations.ts").OperationFinalStore;
|
|
9
|
+
executionSettings?: { nativeSessionId: string; wallMs?: number; idleMs?: number };
|
|
8
10
|
autoMode?: import("./session-auto-mode.ts").SessionAutoLifecycle;
|
|
9
11
|
runtimeSnapshotUnsubscribe?: () => void;
|
|
10
12
|
runtimeSnapshotGeneration?: object;
|
|
@@ -115,6 +117,7 @@ export function bindReloadLifecycle(
|
|
|
115
117
|
existing = {
|
|
116
118
|
root: current,
|
|
117
119
|
autoMode: existing.autoMode,
|
|
120
|
+
executionSettings: existing.executionSettings,
|
|
118
121
|
activityRootId: existing.activityRootId,
|
|
119
122
|
episodeId: existing.episodeId,
|
|
120
123
|
capacity: existing.capacity,
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
claimDelegationOperation,
|
|
3
3
|
existingOperationOutcome,
|
|
4
4
|
finishDelegationOperation,
|
|
5
|
+
operationFinalScope,
|
|
5
6
|
} from "./delegation-operations.ts";
|
|
6
7
|
import type { OperationClaim } from "../src/kernel/dispatch-operation.ts";
|
|
7
8
|
import { reconcileDelegationCapacity, reserveDelegationCapacity } from "./session-capacity.ts";
|
|
@@ -552,6 +553,7 @@ export async function runOneDelegation(
|
|
|
552
553
|
...args: Parameters<typeof runOneDelegationImplementation>
|
|
553
554
|
): Promise<DelegationOutcome> {
|
|
554
555
|
const [session, spec, ids, , ctx, signal] = args;
|
|
556
|
+
const finalScope = operationFinalScope(session);
|
|
555
557
|
const options = args[6] ?? {};
|
|
556
558
|
const claim = options.operationClaim ?? (await claimDelegationOperation(session, spec, ids, ctx, signal));
|
|
557
559
|
if (claim?.reused) {
|
|
@@ -571,7 +573,7 @@ export async function runOneDelegation(
|
|
|
571
573
|
const result = await runOneDelegationImplementation(...args);
|
|
572
574
|
if (claim) {
|
|
573
575
|
try {
|
|
574
|
-
await finishDelegationOperation(claim, result);
|
|
576
|
+
await finishDelegationOperation(claim, result, session, finalScope);
|
|
575
577
|
} catch (error) {
|
|
576
578
|
// A post-execution observation failure must not erase the native final or settlement receipt.
|
|
577
579
|
result.control = "failed";
|