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
|
@@ -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";
|
|
@@ -5,6 +5,7 @@ import { openRuntimeSettlement, settlementHash } from "../src/governance/runtime
|
|
|
5
5
|
import { reconcileDelegationCapacity } from "./session-capacity.ts";
|
|
6
6
|
import { agentDir } from "../src/kernel/project-paths.ts";
|
|
7
7
|
import type { GrantsSession } from "./session.ts";
|
|
8
|
+
import { bindOperationFinals, operationFinal } from "./delegation-operations.ts";
|
|
8
9
|
export const OPERATION_STATUS_EVENT = "pi-daddy:operation-status:v1";
|
|
9
10
|
export const RUNTIME_SNAPSHOT_EVENT = "pi-daddy:runtime-snapshot:v1";
|
|
10
11
|
export function registerRuntimeSnapshot(
|
|
@@ -99,6 +100,7 @@ export function registerRuntimeSnapshot(
|
|
|
99
100
|
sessionId?: string;
|
|
100
101
|
cwd?: string;
|
|
101
102
|
operationId?: string;
|
|
103
|
+
includeFinal?: boolean;
|
|
102
104
|
reply?: (value: unknown) => void;
|
|
103
105
|
};
|
|
104
106
|
if (
|
|
@@ -139,7 +141,12 @@ export function registerRuntimeSnapshot(
|
|
|
139
141
|
lifecycle.runtimeSnapshotGeneration !== generation
|
|
140
142
|
)
|
|
141
143
|
throw Error("operation owner changed during read");
|
|
142
|
-
request.reply!({
|
|
144
|
+
request.reply!({
|
|
145
|
+
...envelope,
|
|
146
|
+
qualified: true,
|
|
147
|
+
operation,
|
|
148
|
+
...(request.includeFinal === true && operation ? { final: operationFinal(session, operation) } : {}),
|
|
149
|
+
});
|
|
143
150
|
} catch (error) {
|
|
144
151
|
request.reply!({ ...envelope, qualified: false, operation: null, reason: String(error) });
|
|
145
152
|
}
|
|
@@ -172,6 +179,7 @@ export function registerRuntimeSnapshot(
|
|
|
172
179
|
}
|
|
173
180
|
if (sequence !== bindSequence || session.reloadLifecycle !== lifecycle) return;
|
|
174
181
|
lifecycle.runtimeSettlement = runtime;
|
|
182
|
+
bindOperationFinals(session, sessionId, cwd);
|
|
175
183
|
bound = { sessionId, cwd, lifecycle: session.reloadLifecycle };
|
|
176
184
|
} catch (error) {
|
|
177
185
|
if (sequence === bindSequence && session.reloadLifecycle === lifecycle) problem = String(error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-daddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.1",
|
|
4
4
|
"description": "Capability governance for pi sub-agents: spawn Agent Skills (SKILL.md) definitions whose allowed-tools becomes a grant that can only narrow going down a delegation tree, enforced by pi's own --tools allowlist, with an append-only ledger.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/src/cli.ts
CHANGED
|
@@ -50,6 +50,7 @@ Usage:
|
|
|
50
50
|
pi-daddy report [--since <date>] [--definition <name>] [--model <id>]
|
|
51
51
|
[--group-by definition|model|thinking] [--json]
|
|
52
52
|
pi-daddy outcomes append changed episode outcomes from Git and CI
|
|
53
|
+
pi-daddy diagnostics export <selection.json> <new-directory> private filtered local evidence
|
|
53
54
|
pi-daddy --help | --version
|
|
54
55
|
|
|
55
56
|
init references skills already enabled in Pi at their installed or local paths. Legacy unregistered npm
|
|
@@ -61,8 +62,11 @@ stays unspawnable. Capabilities that can change your machine
|
|
|
61
62
|
It never rewrites ${SETTINGS_REL} — delete that file if you want it regenerated.`;
|
|
62
63
|
|
|
63
64
|
export interface ParsedArgs {
|
|
64
|
-
command:
|
|
65
|
+
command:
|
|
66
|
+
"init" | "ledger-repair" | "ledger-import" | "report" | "outcomes" | "diagnostics-export" | "help" | "version";
|
|
65
67
|
importTarget?: string;
|
|
68
|
+
selectionPath?: string;
|
|
69
|
+
exportDirectory?: string;
|
|
66
70
|
/** `ledger repair <path>`: the ledger file; `yes` applies, otherwise preview only. */
|
|
67
71
|
ledgerPath?: string;
|
|
68
72
|
yes?: boolean;
|
|
@@ -91,6 +95,19 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
91
95
|
if (args.includes("--version") || args.includes("-v")) return { command: "version", force: false, errors: [] };
|
|
92
96
|
|
|
93
97
|
const [command, ...tail] = args;
|
|
98
|
+
if (command === "diagnostics") {
|
|
99
|
+
const [verb, selectionPath, exportDirectory, ...extra] = tail;
|
|
100
|
+
if (
|
|
101
|
+
verb !== "export" ||
|
|
102
|
+
!selectionPath ||
|
|
103
|
+
!exportDirectory ||
|
|
104
|
+
selectionPath.startsWith("-") ||
|
|
105
|
+
exportDirectory.startsWith("-") ||
|
|
106
|
+
extra.length
|
|
107
|
+
)
|
|
108
|
+
return { command: "help", force: false, errors: ["diagnostics needs: export <selection.json> <new-directory>"] };
|
|
109
|
+
return { command: "diagnostics-export", force: false, errors: [], selectionPath, exportDirectory };
|
|
110
|
+
}
|
|
94
111
|
if (command === "ledger") {
|
|
95
112
|
// ADR-0076 PR 3d: `ledger repair <path> [--yes]` previews, --yes truncates; `ledger import <source> <target>`.
|
|
96
113
|
const [verb, target, ...flags] = tail;
|
|
@@ -388,6 +405,12 @@ export async function main(
|
|
|
388
405
|
console.log(USAGE);
|
|
389
406
|
return 0;
|
|
390
407
|
}
|
|
408
|
+
if (parsed.command === "diagnostics-export") {
|
|
409
|
+
const { exportDiagnostics } = await import("./products/diagnostic-export.ts");
|
|
410
|
+
const result = await exportDiagnostics(parsed.selectionPath!, parsed.exportDirectory!);
|
|
411
|
+
console.log(JSON.stringify(result));
|
|
412
|
+
return result.missing ? 1 : 0;
|
|
413
|
+
}
|
|
391
414
|
if (parsed.command === "outcomes") {
|
|
392
415
|
const cwd = process.cwd();
|
|
393
416
|
const result = await updateEpisodeOutcomes({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compileRetentionShape } from "./retention-validator.ts";
|
|
2
2
|
import { parseRetentionJson } from "./retention-json.ts";
|
|
3
3
|
import type { ExecutionRetentionManifest } from "./execution-retention.ts";
|
|
4
4
|
|
|
@@ -121,7 +121,7 @@ export const RETENTION_SCHEMA = freeze({
|
|
|
121
121
|
},
|
|
122
122
|
],
|
|
123
123
|
});
|
|
124
|
-
const
|
|
124
|
+
const validateShape = compileRetentionShape(RETENTION_SCHEMA);
|
|
125
125
|
function freeze<T>(value: T): T {
|
|
126
126
|
if (value && typeof value === "object") {
|
|
127
127
|
for (const child of Object.values(value)) freeze(child);
|
|
@@ -166,7 +166,7 @@ export function buildExecutionRetentionManifest(value: unknown): ExecutionRetent
|
|
|
166
166
|
}
|
|
167
167
|
};
|
|
168
168
|
inspect(value, 0);
|
|
169
|
-
if (!
|
|
169
|
+
if (!validateShape(value)) throw new TypeError("invalid execution-retention 2.x manifest");
|
|
170
170
|
const m = JSON.parse(JSON.stringify(value)) as ExecutionRetentionManifest;
|
|
171
171
|
if (
|
|
172
172
|
m.native.sessionId !== m.nativeSession.sessionId ||
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** Dependency-free evaluator for the fixed retention schema; unsupported vocabulary fails at construction. */
|
|
2
|
+
type Check = (value: unknown) => boolean;
|
|
3
|
+
type Schema = Record<string, any>;
|
|
4
|
+
const object = (value: unknown): value is Record<string, unknown> =>
|
|
5
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
const keywords = new Set([
|
|
7
|
+
"$schema",
|
|
8
|
+
"$id",
|
|
9
|
+
"type",
|
|
10
|
+
"const",
|
|
11
|
+
"enum",
|
|
12
|
+
"properties",
|
|
13
|
+
"required",
|
|
14
|
+
"additionalProperties",
|
|
15
|
+
"minLength",
|
|
16
|
+
"maxLength",
|
|
17
|
+
"pattern",
|
|
18
|
+
"minimum",
|
|
19
|
+
"maximum",
|
|
20
|
+
"items",
|
|
21
|
+
"maxItems",
|
|
22
|
+
"uniqueItems",
|
|
23
|
+
"anyOf",
|
|
24
|
+
"oneOf",
|
|
25
|
+
"allOf",
|
|
26
|
+
"not",
|
|
27
|
+
"if",
|
|
28
|
+
"then",
|
|
29
|
+
"else",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/** This is internal retention vocabulary, not a general JSON Schema implementation. */
|
|
33
|
+
export function compileRetentionShape(schema: Schema): Check {
|
|
34
|
+
for (const key of Object.keys(schema))
|
|
35
|
+
if (!keywords.has(key)) throw new TypeError(`unsupported retention schema keyword: ${key}`);
|
|
36
|
+
const checks: Check[] = [];
|
|
37
|
+
if (schema.type !== undefined) {
|
|
38
|
+
const types: Record<string, Check> = {
|
|
39
|
+
object,
|
|
40
|
+
array: Array.isArray,
|
|
41
|
+
string: (value) => typeof value === "string",
|
|
42
|
+
boolean: (value) => typeof value === "boolean",
|
|
43
|
+
integer: Number.isInteger,
|
|
44
|
+
null: (value) => value === null,
|
|
45
|
+
};
|
|
46
|
+
if (!Object.hasOwn(types, schema.type)) throw new TypeError("unsupported retention schema type");
|
|
47
|
+
checks.push(types[schema.type]);
|
|
48
|
+
}
|
|
49
|
+
if (Object.hasOwn(schema, "const")) checks.push((value) => value === schema.const);
|
|
50
|
+
if (schema.enum) checks.push((value) => schema.enum.includes(value));
|
|
51
|
+
if (schema.properties) {
|
|
52
|
+
const properties = Object.entries(schema.properties).map(
|
|
53
|
+
([key, shape]) => [key, compileRetentionShape(shape as Schema)] as const,
|
|
54
|
+
);
|
|
55
|
+
checks.push(
|
|
56
|
+
(value) => !object(value) || properties.every(([key, check]) => !Object.hasOwn(value, key) || check(value[key])),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (schema.required)
|
|
60
|
+
checks.push((value) => !object(value) || schema.required.every((key: string) => Object.hasOwn(value, key)));
|
|
61
|
+
if (schema.additionalProperties !== undefined) {
|
|
62
|
+
if (schema.additionalProperties !== false) throw new TypeError("unsupported retention additionalProperties");
|
|
63
|
+
checks.push(
|
|
64
|
+
(value) => !object(value) || Object.keys(value).every((key) => Object.hasOwn(schema.properties ?? {}, key)),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (schema.pattern) {
|
|
68
|
+
const pattern = new RegExp(schema.pattern);
|
|
69
|
+
checks.push((value) => typeof value !== "string" || pattern.test(value));
|
|
70
|
+
}
|
|
71
|
+
if (schema.minLength !== undefined || schema.maxLength !== undefined)
|
|
72
|
+
checks.push(
|
|
73
|
+
(value) =>
|
|
74
|
+
typeof value !== "string" ||
|
|
75
|
+
([...value].length >= (schema.minLength ?? 0) && [...value].length <= (schema.maxLength ?? Infinity)),
|
|
76
|
+
);
|
|
77
|
+
if (schema.minimum !== undefined || schema.maximum !== undefined)
|
|
78
|
+
checks.push(
|
|
79
|
+
(value) =>
|
|
80
|
+
typeof value !== "number" || (value >= (schema.minimum ?? -Infinity) && value <= (schema.maximum ?? Infinity)),
|
|
81
|
+
);
|
|
82
|
+
if (schema.items) {
|
|
83
|
+
const check = compileRetentionShape(schema.items);
|
|
84
|
+
checks.push((value) => !Array.isArray(value) || value.every(check));
|
|
85
|
+
}
|
|
86
|
+
if (schema.maxItems !== undefined) checks.push((value) => !Array.isArray(value) || value.length <= schema.maxItems);
|
|
87
|
+
if (schema.uniqueItems) checks.push((value) => !Array.isArray(value) || new Set(value).size === value.length);
|
|
88
|
+
for (const kind of ["anyOf", "oneOf", "allOf"] as const) {
|
|
89
|
+
if (!schema[kind]) continue;
|
|
90
|
+
const alternatives = schema[kind].map(compileRetentionShape) as Check[];
|
|
91
|
+
checks.push((value) =>
|
|
92
|
+
kind === "anyOf"
|
|
93
|
+
? alternatives.some((check) => check(value))
|
|
94
|
+
: kind === "allOf"
|
|
95
|
+
? alternatives.every((check) => check(value))
|
|
96
|
+
: alternatives.filter((check) => check(value)).length === 1,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (schema.not) {
|
|
100
|
+
const check = compileRetentionShape(schema.not);
|
|
101
|
+
checks.push((value) => !check(value));
|
|
102
|
+
}
|
|
103
|
+
if (schema.if) {
|
|
104
|
+
const condition = compileRetentionShape(schema.if);
|
|
105
|
+
const yes = schema.then ? compileRetentionShape(schema.then) : () => true;
|
|
106
|
+
const no = schema.else ? compileRetentionShape(schema.else) : () => true;
|
|
107
|
+
checks.push((value) => (condition(value) ? yes(value) : no(value)));
|
|
108
|
+
}
|
|
109
|
+
return (value) => checks.every((check) => check(value));
|
|
110
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,8 @@ export interface SelectedCommand {
|
|
|
12
12
|
name: string;
|
|
13
13
|
sourceInfo?: { path?: string };
|
|
14
14
|
}
|
|
15
|
-
const
|
|
15
|
+
const requiredPhases = ["plan", "build", "review", "debug", "investigate"];
|
|
16
|
+
const phases = [...requiredPhases, "test-review"];
|
|
16
17
|
const hash = (value: string) => createHash("sha256").update(value, "utf8").digest("hex");
|
|
17
18
|
const object = (x: unknown): x is Record<string, unknown> => !!x && typeof x === "object" && !Array.isArray(x);
|
|
18
19
|
const exact = (x: Record<string, unknown>, keys: string[]) =>
|
|
@@ -57,10 +58,10 @@ async function principalBinding(
|
|
|
57
58
|
manifest.version !== 1 ||
|
|
58
59
|
manifest.package !== "principal-pi-skills" ||
|
|
59
60
|
!object(manifest.bindings) ||
|
|
60
|
-
!exact(manifest.bindings, phases)
|
|
61
|
+
!exact(manifest.bindings, Object.hasOwn(manifest.bindings, "test-review") ? phases : requiredPhases)
|
|
61
62
|
)
|
|
62
63
|
throw Error("invalid Principal binding manifest");
|
|
63
|
-
for (const name of
|
|
64
|
+
for (const name of Object.keys(manifest.bindings)) {
|
|
64
65
|
const row = manifest.bindings[name];
|
|
65
66
|
if (
|
|
66
67
|
!object(row) ||
|
|
@@ -74,6 +75,7 @@ async function principalBinding(
|
|
|
74
75
|
)
|
|
75
76
|
throw Error("invalid Principal binding row");
|
|
76
77
|
}
|
|
78
|
+
if (!Object.hasOwn(manifest.bindings, phase)) throw Error("selected Principal phase has no binding row");
|
|
77
79
|
const row = manifest.bindings[phase] as Record<string, string>;
|
|
78
80
|
const agentPath = join(root, row.agent);
|
|
79
81
|
if (
|
|
@@ -376,6 +376,17 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
376
376
|
notice = (await connection.change({ action: "set", edits: trimmed.slice(2).trim() }))
|
|
377
377
|
? "Session models updated"
|
|
378
378
|
: connection.state.error || "Session controls unavailable";
|
|
379
|
+
} else if (trimmed.startsWith("limit ")) {
|
|
380
|
+
const edit = /^limit (wall|idle) ([0-9]+)$/.exec(trimmed);
|
|
381
|
+
if (!edit) notice = "Use limit wall <seconds> or limit idle <seconds>; 0 restores default";
|
|
382
|
+
else
|
|
383
|
+
notice = (await connection.change({
|
|
384
|
+
action: "set-limit",
|
|
385
|
+
key: edit[1] as "wall" | "idle",
|
|
386
|
+
seconds: Number(edit[2]),
|
|
387
|
+
}))
|
|
388
|
+
? "Session limit updated for future children; running children unchanged"
|
|
389
|
+
: connection.state.error || "Settings unavailable";
|
|
379
390
|
} else notice = display.input(line) ? "" : `Unknown command: ${trimmed}. Press ? for help.`;
|
|
380
391
|
redraw();
|
|
381
392
|
};
|
|
@@ -397,7 +408,7 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
397
408
|
redraw();
|
|
398
409
|
return;
|
|
399
410
|
}
|
|
400
|
-
|
|
411
|
+
let action = display.key(
|
|
401
412
|
key.name === "return"
|
|
402
413
|
? "return"
|
|
403
414
|
: ["up", "down", "escape", "tab"].includes(key.name || "")
|
|
@@ -409,6 +420,32 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
409
420
|
return;
|
|
410
421
|
}
|
|
411
422
|
if (action === "command") command = "";
|
|
423
|
+
if (action === "setting") {
|
|
424
|
+
const selected = display.screen.settingKey ?? "auto";
|
|
425
|
+
const settings = connection.state.snapshot?.settings;
|
|
426
|
+
if (!settings?.editable) notice = "Settings can only be changed in the connected owning parent";
|
|
427
|
+
else if (selected === "auto") action = "auto";
|
|
428
|
+
else if (selected === "wall" || selected === "idle") command = `limit ${selected} `;
|
|
429
|
+
else if (selected === "descendants") notice = "Startup only: set PI_DADDY_FANOUT before restarting Pi";
|
|
430
|
+
else if (selected === "depth") notice = "Startup only: set PI_DADDY_MAX_DEPTH before restarting Pi";
|
|
431
|
+
else if (selected === "perCall") notice = "Children per call is a fixed safety bound";
|
|
432
|
+
else if (!settings.jev.available)
|
|
433
|
+
notice = "JEV controls unavailable; load skill-harness in the parent Pi session";
|
|
434
|
+
else {
|
|
435
|
+
void connection
|
|
436
|
+
.change({ action: "set-jev", enabled: !(settings.jev.enabled || settings.jev.pending) })
|
|
437
|
+
.then((changed) => {
|
|
438
|
+
const jev = connection.state.snapshot?.settings?.jev;
|
|
439
|
+
notice = !changed
|
|
440
|
+
? connection.state.error || "JEV change already pending"
|
|
441
|
+
: jev?.pending
|
|
442
|
+
? "Complete JEV paid-call and LoRA-storage choices in the parent Pi session"
|
|
443
|
+
: jev?.error ||
|
|
444
|
+
`JEV ${jev?.enabled ? "enabled" : "disabled"}; ${jev?.availability ?? "status unknown"}`;
|
|
445
|
+
redraw();
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
412
449
|
if (action === "auto") {
|
|
413
450
|
const snapshot = connection.state.snapshot;
|
|
414
451
|
if (!snapshot) notice = "Daddy permission controls unavailable";
|