pi-daddy 0.40.1 → 0.41.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 +17 -0
- package/contracts/ledger-record/v1/fixtures/capability-decision.json +1 -0
- package/contracts/ledger-record/v1/fixtures/child-lifecycle.json +16 -0
- package/contracts/ledger-record/v1/fixtures/ledger-record.jsonl +3 -3
- package/contracts/ledger-record/v1/fixtures/workspace-lease.json +1 -0
- package/contracts/ledger-record/v1/governance-event.schema.json +100 -0
- package/dist/advisors/advisor.d.ts +19 -1
- package/dist/advisors/advisor.d.ts.map +1 -1
- package/dist/advisors/advisor.js +62 -2
- package/dist/advisors/advisor.js.map +1 -1
- package/dist/advisors/decider.d.ts +2 -4
- package/dist/advisors/decider.d.ts.map +1 -1
- package/dist/advisors/decider.js.map +1 -1
- package/dist/advisors/settings.d.ts +3 -1
- package/dist/advisors/settings.d.ts.map +1 -1
- package/dist/advisors/settings.js +4 -3
- package/dist/advisors/settings.js.map +1 -1
- package/dist/executors/activity-session.d.ts +7 -0
- package/dist/executors/activity-session.d.ts.map +1 -1
- package/dist/executors/activity-session.js +101 -3
- package/dist/executors/activity-session.js.map +1 -1
- package/dist/governance/ledger-events.d.ts +23 -0
- package/dist/governance/ledger-events.d.ts.map +1 -1
- package/dist/governance/ledger-events.js +7 -0
- package/dist/governance/ledger-events.js.map +1 -1
- package/dist/governance/ledger-v3-validation.d.ts.map +1 -1
- package/dist/governance/ledger-v3-validation.js +29 -0
- package/dist/governance/ledger-v3-validation.js.map +1 -1
- package/dist/governance/ledger.d.ts +5 -1
- package/dist/governance/ledger.d.ts.map +1 -1
- package/dist/governance/ledger.js +4 -0
- package/dist/governance/ledger.js.map +1 -1
- package/dist/kernel/context-handoff.d.ts.map +1 -1
- package/dist/kernel/context-handoff.js +4 -1
- package/dist/kernel/context-handoff.js.map +1 -1
- package/dist/kernel/delegate-types.d.ts +2 -0
- package/dist/kernel/delegate-types.d.ts.map +1 -1
- package/dist/kernel/delegate-types.js.map +1 -1
- package/dist/kernel/delegate.d.ts.map +1 -1
- package/dist/kernel/delegate.js +3 -1
- package/dist/kernel/delegate.js.map +1 -1
- package/dist/kernel/env-names.d.ts +3 -0
- package/dist/kernel/env-names.d.ts.map +1 -1
- package/dist/kernel/env-names.js +5 -0
- package/dist/kernel/env-names.js.map +1 -1
- package/dist/kernel/episode-id.d.ts +6 -0
- package/dist/kernel/episode-id.d.ts.map +1 -0
- package/dist/kernel/episode-id.js +13 -0
- package/dist/kernel/episode-id.js.map +1 -0
- package/dist/kernel/propagation.d.ts +4 -2
- package/dist/kernel/propagation.d.ts.map +1 -1
- package/dist/kernel/propagation.js +6 -2
- package/dist/kernel/propagation.js.map +1 -1
- package/dist/products/activity-timeline.d.ts +3 -0
- package/dist/products/activity-timeline.d.ts.map +1 -1
- package/dist/products/activity-timeline.js +6 -0
- package/dist/products/activity-timeline.js.map +1 -1
- package/extensions/activity-timeline.ts +11 -5
- package/extensions/advisor-session.ts +3 -0
- package/extensions/chain-ledger.ts +1 -0
- package/extensions/context-shape.ts +19 -4
- package/extensions/delegation-ledger.ts +1 -0
- package/extensions/effort-advice.ts +6 -5
- package/extensions/execute-child.ts +16 -0
- package/extensions/grant-store-refusal.ts +1 -0
- package/extensions/grants-command.ts +4 -4
- package/extensions/grants.ts +4 -1
- package/extensions/pruning-advice.ts +37 -6
- package/extensions/reload-environment.ts +1 -0
- package/extensions/run-delegation.ts +10 -1
- package/extensions/session-environment.ts +9 -0
- package/extensions/session.ts +27 -4
- package/extensions/workspace-runtime.ts +5 -0
- package/package.json +1 -1
- package/src/advisors/advisor.ts +83 -3
- package/src/advisors/decider.ts +2 -4
- package/src/advisors/settings.ts +6 -4
- package/src/executors/activity-session.ts +105 -3
- package/src/governance/ledger-events.ts +35 -1
- package/src/governance/ledger-v3-validation.ts +44 -0
- package/src/governance/ledger.ts +8 -0
- package/src/kernel/context-handoff.ts +5 -1
- package/src/kernel/delegate-types.ts +2 -0
- package/src/kernel/delegate.ts +2 -0
- package/src/kernel/env-names.ts +5 -0
- package/src/kernel/episode-id.ts +18 -0
- package/src/kernel/propagation.ts +8 -0
- package/src/products/activity-timeline.ts +8 -0
|
@@ -3,6 +3,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { ENV_EPISODE_ID } from "../src/kernel/env-names.ts";
|
|
6
7
|
import { Type } from "typebox";
|
|
7
8
|
import {
|
|
8
9
|
ActivityTimelineRecorder,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
|
|
14
15
|
export interface ActivitySessionState {
|
|
15
16
|
activityRootId?: string;
|
|
17
|
+
episodeId?: string;
|
|
16
18
|
activity?: ActivityIdentity & { taskId?: string };
|
|
17
19
|
}
|
|
18
20
|
interface HookContext {
|
|
@@ -44,15 +46,19 @@ export function registerActivityTimeline(pi: ExtensionAPI, session?: ActivitySes
|
|
|
44
46
|
}
|
|
45
47
|
};
|
|
46
48
|
const current = (ctx: HookContext): ActivityTimelineRecorder => {
|
|
47
|
-
const env =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
:
|
|
49
|
+
const env = {
|
|
50
|
+
...process.env,
|
|
51
|
+
...(session?.activityRootId && !process.env.PI_DADDY_ACTIVITY_ROOT
|
|
52
|
+
? { PI_DADDY_ACTIVITY_ROOT: session.activityRootId }
|
|
53
|
+
: {}),
|
|
54
|
+
...(session?.episodeId ? { [ENV_EPISODE_ID]: session.episodeId } : {}),
|
|
55
|
+
};
|
|
51
56
|
// session_start binds reload ownership after this hook registration; re-create before the first turn
|
|
52
57
|
// if that binding recovered a prior root identity rather than the factory's provisional UUID.
|
|
53
58
|
if (
|
|
54
59
|
!recorder ||
|
|
55
|
-
(!process.env.PI_DADDY_ACTIVITY_ROOT && session?.activityRootId && recorder.rootId !== session.activityRootId)
|
|
60
|
+
(!process.env.PI_DADDY_ACTIVITY_ROOT && session?.activityRootId && recorder.rootId !== session.activityRootId) ||
|
|
61
|
+
(session?.episodeId !== undefined && recorder.episodeId !== session.episodeId)
|
|
56
62
|
)
|
|
57
63
|
recorder = new ActivityTimelineRecorder(ctx.cwd, env);
|
|
58
64
|
return recorder;
|
|
@@ -33,6 +33,7 @@ export function createAdvisorSession(input: {
|
|
|
33
33
|
block: unknown;
|
|
34
34
|
ledgerPath?: string;
|
|
35
35
|
env?: NodeJS.ProcessEnv;
|
|
36
|
+
episodeId?: string;
|
|
36
37
|
}): AdvisorSession {
|
|
37
38
|
const env = input.env ?? process.env;
|
|
38
39
|
const settings = advisorSettingsFrom(input.block, env);
|
|
@@ -58,6 +59,8 @@ export function createAdvisorSession(input: {
|
|
|
58
59
|
decider,
|
|
59
60
|
record,
|
|
60
61
|
enabled: settings.enabled,
|
|
62
|
+
taskEgress: settings.taskEgress,
|
|
63
|
+
...(input.episodeId ? { episodeId: input.episodeId } : {}),
|
|
61
64
|
...(settings.timeoutMs ? { timeoutMs: settings.timeoutMs } : {}),
|
|
62
65
|
}),
|
|
63
66
|
};
|
|
@@ -21,6 +21,7 @@ export async function recordChainRefusal(input: {
|
|
|
21
21
|
await appendRecord(
|
|
22
22
|
{ path: input.session.ledgerPath, strict: true },
|
|
23
23
|
buildRecord({
|
|
24
|
+
episodeId: input.session.episodeId,
|
|
24
25
|
executionId: input.executionId,
|
|
25
26
|
parentExecutionId: input.parentExecutionId,
|
|
26
27
|
parentId: input.session.ownSpawnId,
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
* first. This shape exists to tell the model what the parameter is for.
|
|
8
8
|
*/
|
|
9
9
|
import { Type } from "typebox";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
CONTEXT_MODES,
|
|
12
|
+
DEFAULT_CONTEXT_TURNS,
|
|
13
|
+
MAX_CONTEXT_FILES,
|
|
14
|
+
MAX_CONTEXT_TURNS,
|
|
15
|
+
} from "../src/kernel/context-handoff.ts";
|
|
11
16
|
|
|
12
17
|
export function contextShape() {
|
|
13
18
|
return Type.Object({
|
|
@@ -18,13 +23,23 @@ export function contextShape() {
|
|
|
18
23
|
"What of YOUR session this sub-agent receives. none: nothing beyond its definition and the task. " +
|
|
19
24
|
"files: the contents of paths you name. pruned: recent turns of your session plus turns naming those " +
|
|
20
25
|
"files. summary: what you write in 'summary'. fork: your whole session (needs a human's approval). " +
|
|
21
|
-
"A definition's allowed-tools caps this; asking for more than it permits is refused."
|
|
26
|
+
"A definition's allowed-tools caps this; asking for more than it permits is refused. If the child has " +
|
|
27
|
+
"read tools in the same workspace, prefer none and ask it to inspect the checkout instead of embedding files.",
|
|
22
28
|
},
|
|
23
29
|
),
|
|
24
30
|
files: Type.Optional(
|
|
25
|
-
Type.Array(Type.String(
|
|
31
|
+
Type.Array(Type.String({ minLength: 1 }), {
|
|
32
|
+
maxItems: MAX_CONTEXT_FILES,
|
|
33
|
+
description: `At most ${MAX_CONTEXT_FILES} repository-relative paths, for files and pruned.`,
|
|
34
|
+
}),
|
|
26
35
|
),
|
|
27
36
|
summary: Type.Optional(Type.String({ description: "What the sub-agent needs to know, in your own words." })),
|
|
28
|
-
turns: Type.Optional(
|
|
37
|
+
turns: Type.Optional(
|
|
38
|
+
Type.Integer({
|
|
39
|
+
minimum: 1,
|
|
40
|
+
maximum: MAX_CONTEXT_TURNS,
|
|
41
|
+
description: `For pruned: how many recent turns to keep. Default ${DEFAULT_CONTEXT_TURNS}.`,
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
29
44
|
});
|
|
30
45
|
}
|
|
@@ -29,6 +29,7 @@ export async function recordDelegationDecision(input: {
|
|
|
29
29
|
await appendRecord(
|
|
30
30
|
{ path: ledgerPath, strict: true },
|
|
31
31
|
buildRecord({
|
|
32
|
+
episodeId: session.episodeId,
|
|
32
33
|
executionId: ids.executionId,
|
|
33
34
|
parentExecutionId: ids.parentExecutionId,
|
|
34
35
|
parentId: ids.parentId,
|
|
@@ -38,6 +38,7 @@ export async function adviseEffort(input: {
|
|
|
38
38
|
model?: string;
|
|
39
39
|
registry: { find(provider: string, modelId: string): unknown };
|
|
40
40
|
task: string;
|
|
41
|
+
executionId?: string;
|
|
41
42
|
agent?: string;
|
|
42
43
|
signal?: AbortSignal;
|
|
43
44
|
}): Promise<string | undefined> {
|
|
@@ -52,14 +53,13 @@ export async function adviseEffort(input: {
|
|
|
52
53
|
// ledger line to be told the only thing that could be said.
|
|
53
54
|
if (levels.length < 2) return undefined;
|
|
54
55
|
|
|
56
|
+
const task = advisor.task(input.task);
|
|
55
57
|
const advice = await advisor.ask(
|
|
56
58
|
EFFORT_PURPOSE,
|
|
57
59
|
{
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
// is not willing to send task text to a third party leaves the advisor off, which is the default.
|
|
62
|
-
state: { task: input.task, ...(input.agent ? { definition: input.agent } : {}) },
|
|
60
|
+
// Raw text is a separate opt-in from enabling the advisor. By default `advisor.task` returns only structural
|
|
61
|
+
// facts; raw mode preserves the prior request, including the definition name.
|
|
62
|
+
state: { task, ...(typeof task === "string" && input.agent ? { definition: input.agent } : {}) },
|
|
63
63
|
questions: {
|
|
64
64
|
effort: {
|
|
65
65
|
kind: "choice",
|
|
@@ -70,6 +70,7 @@ export async function adviseEffort(input: {
|
|
|
70
70
|
},
|
|
71
71
|
},
|
|
72
72
|
input.signal,
|
|
73
|
+
input.executionId,
|
|
73
74
|
);
|
|
74
75
|
const chosen = advice?.answers.effort;
|
|
75
76
|
// Belt and braces: `parseAnswer` already refuses a choice outside the options it was given, so this can only
|
|
@@ -22,6 +22,7 @@ import { resolveWorkspace } from "../src/executors/herdr-cli.ts";
|
|
|
22
22
|
import { HerdrWriterCloseError, runHerdrPane } from "../src/executors/run-herdr.ts";
|
|
23
23
|
import { GovernanceRefusal, refusal, type StructuredRefusal } from "../src/kernel/refusals.ts";
|
|
24
24
|
import { ENV_HERDR_KEEP_PANE, type GrantsSession } from "./session.ts";
|
|
25
|
+
import { ENV_EPISODE_ID } from "../src/kernel/env-names.ts";
|
|
25
26
|
import { releaseDelegationWorkspace, type PreparedWorkspace } from "./workspace-runtime.ts";
|
|
26
27
|
import { ActivityTimelineRecorder, ENV_ACTIVITY_PARENT_TASK } from "../src/products/activity-timeline.ts";
|
|
27
28
|
export interface DelegationOutcome {
|
|
@@ -120,6 +121,7 @@ export async function executePlannedChild(input: {
|
|
|
120
121
|
"PI_DADDY_ACTIVITY_ROOT",
|
|
121
122
|
"PI_DADDY_ACTIVITY_TASK",
|
|
122
123
|
ENV_ACTIVITY_PARENT_TASK,
|
|
124
|
+
ENV_EPISODE_ID,
|
|
123
125
|
]) {
|
|
124
126
|
const value = plan.env[name];
|
|
125
127
|
if (value) env[name] = value;
|
|
@@ -177,6 +179,7 @@ export async function executePlannedChild(input: {
|
|
|
177
179
|
await appendLedgerEvent(
|
|
178
180
|
{ path: ledgerPath, strict: true },
|
|
179
181
|
buildChildLifecycleEvent({
|
|
182
|
+
episodeId: session.episodeId,
|
|
180
183
|
executionId,
|
|
181
184
|
parentExecutionId,
|
|
182
185
|
childId,
|
|
@@ -192,6 +195,7 @@ export async function executePlannedChild(input: {
|
|
|
192
195
|
await releaseDelegationWorkspace({
|
|
193
196
|
prepared: preparedWorkspace,
|
|
194
197
|
childId,
|
|
198
|
+
episodeId: session.episodeId,
|
|
195
199
|
executionId,
|
|
196
200
|
parentExecutionId,
|
|
197
201
|
reason: "ledger-failed",
|
|
@@ -252,6 +256,7 @@ export async function executePlannedChild(input: {
|
|
|
252
256
|
onFailure: (cause) => teardownFailures.push(`child runtime identity record failed: ${String(cause)}`),
|
|
253
257
|
},
|
|
254
258
|
buildChildLifecycleEvent({
|
|
259
|
+
episodeId: session.episodeId,
|
|
255
260
|
executionId,
|
|
256
261
|
parentExecutionId,
|
|
257
262
|
childId,
|
|
@@ -337,6 +342,7 @@ export async function executePlannedChild(input: {
|
|
|
337
342
|
truncated: output.truncated,
|
|
338
343
|
failed: childFailed,
|
|
339
344
|
});
|
|
345
|
+
const usageObservation = await activitySession.usage();
|
|
340
346
|
releaseReason = output.timedOut ? "timeout" : output.aborted ? "cancelled" : childFailed ? "failed" : "completed";
|
|
341
347
|
if (activityStarted)
|
|
342
348
|
try {
|
|
@@ -366,6 +372,7 @@ export async function executePlannedChild(input: {
|
|
|
366
372
|
onFailure: (cause) => teardownFailures.push(`child lifecycle record failed: ${String(cause)}`),
|
|
367
373
|
},
|
|
368
374
|
buildChildLifecycleEvent({
|
|
375
|
+
episodeId: session.episodeId,
|
|
369
376
|
executionId,
|
|
370
377
|
parentExecutionId,
|
|
371
378
|
childId,
|
|
@@ -379,6 +386,9 @@ export async function executePlannedChild(input: {
|
|
|
379
386
|
idleTimeoutMs: configuredIdleMs,
|
|
380
387
|
reason:
|
|
381
388
|
output.spawnError ?? (output.timedOut ? (output.idle ? "idle-timeout" : "wall-clock") : undefined),
|
|
389
|
+
...(usageObservation.usage
|
|
390
|
+
? { usage: usageObservation.usage }
|
|
391
|
+
: { usageUnavailable: usageObservation.unavailable! }),
|
|
382
392
|
correlation: plan.correlation,
|
|
383
393
|
now: new Date(),
|
|
384
394
|
}),
|
|
@@ -462,6 +472,7 @@ export async function executePlannedChild(input: {
|
|
|
462
472
|
failed: true,
|
|
463
473
|
});
|
|
464
474
|
retainWriterLease = Boolean(writerLease && isHerdrWriterCloseFailure(error));
|
|
475
|
+
const usageObservation = await activitySession.usage();
|
|
465
476
|
if (ledgerPath && !terminalAttempted) {
|
|
466
477
|
// Best-effort: this records the failure, so it must not REPLACE the failure. A strict append that
|
|
467
478
|
// throws here would discard the original error — including HerdrWriterCloseError, whose whole
|
|
@@ -474,6 +485,7 @@ export async function executePlannedChild(input: {
|
|
|
474
485
|
onFailure: (cause) => teardownFailures.push(`child lifecycle record failed: ${String(cause)}`),
|
|
475
486
|
},
|
|
476
487
|
buildChildLifecycleEvent({
|
|
488
|
+
episodeId: session.episodeId,
|
|
477
489
|
executionId,
|
|
478
490
|
parentExecutionId,
|
|
479
491
|
childId,
|
|
@@ -486,6 +498,9 @@ export async function executePlannedChild(input: {
|
|
|
486
498
|
: error instanceof Error
|
|
487
499
|
? error.name
|
|
488
500
|
: "unknown executor error",
|
|
501
|
+
...(usageObservation.usage
|
|
502
|
+
? { usage: usageObservation.usage }
|
|
503
|
+
: { usageUnavailable: usageObservation.unavailable! }),
|
|
489
504
|
correlation: plan.correlation,
|
|
490
505
|
now: new Date(),
|
|
491
506
|
}),
|
|
@@ -536,6 +551,7 @@ export async function executePlannedChild(input: {
|
|
|
536
551
|
await releaseDelegationWorkspace({
|
|
537
552
|
prepared: preparedWorkspace,
|
|
538
553
|
childId,
|
|
554
|
+
episodeId: session.episodeId,
|
|
539
555
|
executionId,
|
|
540
556
|
parentExecutionId,
|
|
541
557
|
ledgerPath,
|
|
@@ -22,6 +22,7 @@ export async function reportGrantStoreRefusal(session: GrantsSession, ui: Refusa
|
|
|
22
22
|
await appendRecord(
|
|
23
23
|
{ path: session.ledgerPath, strict: true },
|
|
24
24
|
buildRecord({
|
|
25
|
+
episodeId: session.episodeId,
|
|
25
26
|
executionId: newExecutionId(),
|
|
26
27
|
parentExecutionId: null,
|
|
27
28
|
parentId: session.ownSpawnId,
|
|
@@ -32,8 +32,8 @@ export interface GrantsCommandContext {
|
|
|
32
32
|
* sentence from the one the session banner printed. Two spellings of one fact is R-28.
|
|
33
33
|
*/
|
|
34
34
|
executor: ExecutorChoice;
|
|
35
|
-
/** ADR-0077: which advisor
|
|
36
|
-
advisor: { decider: string; refusal?: string };
|
|
35
|
+
/** ADR-0077: which advisor and task-egress mode are in force, or why none is. */
|
|
36
|
+
advisor: { decider: string; taskEgress: "digest" | "raw"; refusal?: string };
|
|
37
37
|
/** ADR-0042: which ids this session pinned, so a routing refusal is discoverable before it happens. */
|
|
38
38
|
workspacePin?: ReadonlyMap<string, string>;
|
|
39
39
|
/** Registry ids this machine has never accepted: present, not routable, and worth naming (rule 8). */
|
|
@@ -384,10 +384,10 @@ export const grantsCommand = {
|
|
|
384
384
|
` depth ${depth} of max ${maxDepth}${maxDepth <= 0 ? " (spawning disabled)" : ""}`,
|
|
385
385
|
// Rule 8's loud half, which review found missing: an operator who upgraded from 0.34.0, or who mistyped the
|
|
386
386
|
// variable, saw an advisor silently absent and nothing saying why. This is also where an operator sees that
|
|
387
|
-
// task
|
|
387
|
+
// which representation of the task leaves the machine, which no other status surface says.
|
|
388
388
|
advisor.decider === "none"
|
|
389
389
|
? ` advisor off${advisor.refusal ? ` — ${advisor.refusal}` : ""}`
|
|
390
|
-
: ` advisor ${advisor.decider} —
|
|
390
|
+
: ` advisor ${advisor.decider} — task egress ${advisor.taskEgress}; ` +
|
|
391
391
|
`a pruned context handoff also sends session turns`,
|
|
392
392
|
` ledger ${ledgerPath || "(not recording — set PI_DADDY_LEDGER)"}`,
|
|
393
393
|
` approvals ${sessionApprovals.size} this session, ${valid.size} persisted` +
|
package/extensions/grants.ts
CHANGED
|
@@ -35,7 +35,7 @@ import { registerDelegationTools } from "./delegation.ts";
|
|
|
35
35
|
import { grantsCommand } from "./grants-command.ts";
|
|
36
36
|
import { runInit } from "./init-command.ts";
|
|
37
37
|
import { planWithApprovals } from "./run-delegation.ts";
|
|
38
|
-
import { createGrantsSession, loadProjectDefinitions, type GrantsSession } from "./session.ts";
|
|
38
|
+
import { createGrantsSession, loadProjectDefinitions, reconcileAdvisorSession, type GrantsSession } from "./session.ts";
|
|
39
39
|
import { acceptWorkspaces } from "../src/governance/workspace-acceptance.ts";
|
|
40
40
|
import { loadWorkspaceRegistry } from "../src/kernel/workspace.ts";
|
|
41
41
|
import { bindReloadLifecycle } from "./reload-environment.ts";
|
|
@@ -93,6 +93,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
93
93
|
// execution tree across workspaces. Resolve it once at the root's actual pi cwd; descendants inherit
|
|
94
94
|
// this absolute identity verbatim, and resolve(abs) remains abs at every deeper session start.
|
|
95
95
|
if (session.ledgerPath) session.ledgerPath = resolve(ctx.cwd, session.ledgerPath);
|
|
96
|
+
reconcileAdvisorSession(session, reload.environment);
|
|
96
97
|
try {
|
|
97
98
|
// ADR-0076 PR 3d: a pre-format project ledger is imported once into the record envelope, bodies verbatim, each
|
|
98
99
|
// record marked with its source line; the old file is left untouched. Happens before any spawn can append.
|
|
@@ -341,6 +342,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
341
342
|
await appendRecord(
|
|
342
343
|
{ path: session.ledgerPath, strict: true },
|
|
343
344
|
buildRecord({
|
|
345
|
+
episodeId: session.episodeId,
|
|
344
346
|
parentId: `d${session.depth}`,
|
|
345
347
|
childId: `${event.toolName}@d${session.depth + 1}`,
|
|
346
348
|
depth: session.depth + 1,
|
|
@@ -378,6 +380,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
378
380
|
ledgerPath: session.ledgerPath,
|
|
379
381
|
advisor: {
|
|
380
382
|
decider: session.advisorSession.deciderName,
|
|
383
|
+
taskEgress: session.advisorSession.settings.taskEgress,
|
|
381
384
|
...(session.advisorSession.settings.refusal ? { refusal: session.advisorSession.settings.refusal } : {}),
|
|
382
385
|
},
|
|
383
386
|
...(session.workspacePin ? { workspacePin: session.workspacePin } : {}),
|
|
@@ -36,6 +36,9 @@ export async function advisePruning(input: {
|
|
|
36
36
|
session: { advisorSession: { advisor: Advisor }; parentSession?: ParentTurnSource };
|
|
37
37
|
granted: ContextRequest;
|
|
38
38
|
task: string;
|
|
39
|
+
executionId?: string;
|
|
40
|
+
/** The current delegation call, excluded from advisor input because its arguments contain the raw task. */
|
|
41
|
+
toolCallId?: string;
|
|
39
42
|
signal?: AbortSignal;
|
|
40
43
|
}): Promise<string[] | undefined> {
|
|
41
44
|
if (input.granted.mode !== "pruned" || !input.session.parentSession) return undefined;
|
|
@@ -44,7 +47,12 @@ export async function advisePruning(input: {
|
|
|
44
47
|
...(input.granted.turns !== undefined ? { turns: input.granted.turns } : {}),
|
|
45
48
|
...(input.granted.files !== undefined ? { files: input.granted.files } : {}),
|
|
46
49
|
}).kept;
|
|
47
|
-
|
|
50
|
+
const privateIds = new Set(
|
|
51
|
+
input.toolCallId ? all.filter((turn) => turn.toolCallIds.includes(input.toolCallId!)).map((turn) => turn.id) : [],
|
|
52
|
+
);
|
|
53
|
+
const privateCandidates = candidates.filter((turn) => privateIds.has(turn.id));
|
|
54
|
+
const candidatesForAdvice = candidates.filter((turn) => !privateCandidates.includes(turn));
|
|
55
|
+
if (candidatesForAdvice.length < 2) return undefined;
|
|
48
56
|
// **Judge the MOST RECENT candidates, and keep the rest unjudged.** This was `> MAX_JUDGED_TURNS` → give up
|
|
49
57
|
// entirely, which was fine while the rule offered six turns and became a silent feature death the moment
|
|
50
58
|
// `DEFAULT_CONTEXT_TURNS` rose to 20: every default request would have exceeded the bound, so the pruning
|
|
@@ -54,16 +62,18 @@ export async function advisePruning(input: {
|
|
|
54
62
|
// Judging a SUBSET is still only narrowing — an unjudged turn is kept, never dropped — so the attenuation
|
|
55
63
|
// property is untouched. The recent end is judged because that is where the rule's own recall is
|
|
56
64
|
// concentrated, and because a turn adjacent to the task is the one a wrong answer costs most.
|
|
57
|
-
const unjudged =
|
|
58
|
-
const judged =
|
|
65
|
+
const unjudged = candidatesForAdvice.slice(0, Math.max(0, candidatesForAdvice.length - MAX_JUDGED_TURNS));
|
|
66
|
+
const judged = candidatesForAdvice.slice(unjudged.length);
|
|
59
67
|
|
|
68
|
+
const task = input.session.advisorSession.advisor.task(input.task);
|
|
69
|
+
const taskDescription = typeof task === "string" ? task.slice(0, 2000) : JSON.stringify(task);
|
|
60
70
|
const questions: Record<string, Question> = {};
|
|
61
71
|
for (const [index, turn] of judged.entries())
|
|
62
72
|
questions[`turn${index}`] = {
|
|
63
73
|
kind: "noul",
|
|
64
74
|
// Both bounded: the task is embedded once per candidate, so an unbounded task became a request twelve times
|
|
65
75
|
// its size, on a two-second budget and the operator's key.
|
|
66
|
-
instructions: `Would a sub-agent doing this task be helped by seeing this part of the parent's session?\n\nTASK: ${
|
|
76
|
+
instructions: `Would a sub-agent doing this task be helped by seeing this part of the parent's session?\n\nTASK: ${taskDescription}\n\nPART:\n${turn.text.slice(0, 2000)}`,
|
|
67
77
|
whenTrue: "It bears on the task: a decision, a constraint, a fact the task depends on.",
|
|
68
78
|
whenFalse: "It does not: unrelated work, chatter, or something the task already states.",
|
|
69
79
|
};
|
|
@@ -74,12 +84,14 @@ export async function advisePruning(input: {
|
|
|
74
84
|
// task and one turn rather than the whole session. Nothing here is recorded — `createAdvisor` writes keys.
|
|
75
85
|
{ state: {}, questions },
|
|
76
86
|
input.signal,
|
|
87
|
+
input.executionId,
|
|
77
88
|
);
|
|
78
89
|
if (!advice) return undefined;
|
|
79
90
|
// Every candidate or none. A response missing eleven of twelve answers would otherwise read as "drop eleven",
|
|
80
91
|
// which is a narrowing nobody asked for rather than the "unrecognised response means no advice" contract.
|
|
81
92
|
if (judged.some((_, index) => advice.answers[`turn${index}`]?.kind !== "noul")) return undefined;
|
|
82
93
|
const kept = [
|
|
94
|
+
...privateCandidates.map((turn) => turn.id),
|
|
83
95
|
...unjudged.map((turn) => turn.id),
|
|
84
96
|
...judged.filter((_, index) => (advice.answers[`turn${index}`] as { value: boolean }).value).map((turn) => turn.id),
|
|
85
97
|
];
|
|
@@ -88,12 +100,31 @@ export async function advisePruning(input: {
|
|
|
88
100
|
return kept.length === 0 ? undefined : kept;
|
|
89
101
|
}
|
|
90
102
|
|
|
91
|
-
function turnsOf(session: ParentTurnSource): Array<{ id: string; text: string }> {
|
|
103
|
+
function turnsOf(session: ParentTurnSource): Array<{ id: string; text: string; toolCallIds: string[] }> {
|
|
92
104
|
try {
|
|
93
105
|
return session
|
|
94
106
|
.getEntries()
|
|
95
107
|
.filter((entry) => entry.type === "message")
|
|
96
|
-
.map((entry) =>
|
|
108
|
+
.map((entry) => {
|
|
109
|
+
const message = (entry as { message?: unknown }).message ?? entry;
|
|
110
|
+
const content =
|
|
111
|
+
typeof message === "object" && message !== null && Array.isArray((message as { content?: unknown }).content)
|
|
112
|
+
? (message as { content: unknown[] }).content
|
|
113
|
+
: [];
|
|
114
|
+
return {
|
|
115
|
+
id: entry.id,
|
|
116
|
+
text: JSON.stringify(message),
|
|
117
|
+
toolCallIds: content
|
|
118
|
+
.filter(
|
|
119
|
+
(part): part is { type: "toolCall"; id: string } =>
|
|
120
|
+
typeof part === "object" &&
|
|
121
|
+
part !== null &&
|
|
122
|
+
(part as { type?: unknown }).type === "toolCall" &&
|
|
123
|
+
typeof (part as { id?: unknown }).id === "string",
|
|
124
|
+
)
|
|
125
|
+
.map((part) => part.id),
|
|
126
|
+
};
|
|
127
|
+
});
|
|
97
128
|
} catch {
|
|
98
129
|
return [];
|
|
99
130
|
}
|
|
@@ -5,6 +5,7 @@ export interface ReloadLifecycle {
|
|
|
5
5
|
root: Record<string, string | undefined>;
|
|
6
6
|
published?: Record<string, string | undefined>;
|
|
7
7
|
activityRootId?: string;
|
|
8
|
+
episodeId?: string;
|
|
8
9
|
/**
|
|
9
10
|
* The destination pin this owner settled on (ADR-0042), which must survive an extension reload.
|
|
10
11
|
*
|
|
@@ -242,7 +242,7 @@ export async function runOneDelegation(
|
|
|
242
242
|
approvalFacts?: ApprovalLedgerFacts;
|
|
243
243
|
} = {},
|
|
244
244
|
): Promise<DelegationOutcome> {
|
|
245
|
-
const { onProgress, preApproved, taskFrom, taskFromExecutionId, approvalFacts } = options;
|
|
245
|
+
const { toolCallId, onProgress, preApproved, taskFrom, taskFromExecutionId, approvalFacts } = options;
|
|
246
246
|
// pi resolves a BARE model id to an unauthenticated provider and the child dies at startup — the id
|
|
247
247
|
// alone is not enough, it must be qualified with its provider (`Model<Api>` carries both).
|
|
248
248
|
const defaultModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
@@ -303,6 +303,7 @@ export async function runOneDelegation(
|
|
|
303
303
|
model: spec.model ?? defaultModel,
|
|
304
304
|
registry: ctx.modelRegistry,
|
|
305
305
|
task: spec.task,
|
|
306
|
+
executionId: ids.executionId,
|
|
306
307
|
agent: spec.agent,
|
|
307
308
|
signal,
|
|
308
309
|
});
|
|
@@ -311,6 +312,8 @@ export async function runOneDelegation(
|
|
|
311
312
|
session,
|
|
312
313
|
base: extra,
|
|
313
314
|
task: spec.task,
|
|
315
|
+
executionId: ids.executionId,
|
|
316
|
+
toolCallId,
|
|
314
317
|
blocked: Boolean(executorRefusal || modelRefusal),
|
|
315
318
|
preview: () => planWithApprovals(session, request, extra, null, signal, preApproved).then((r) => r.plan),
|
|
316
319
|
...(signal ? { signal } : {}),
|
|
@@ -332,6 +335,7 @@ export async function runOneDelegation(
|
|
|
332
335
|
spec: { ...spec.workspace, access: governedWorkspaceAccess(spec.workspace.access, plan.requested) },
|
|
333
336
|
correlation: spec.correlation,
|
|
334
337
|
childId: ids.childId,
|
|
338
|
+
episodeId: session.episodeId,
|
|
335
339
|
executionId: ids.executionId,
|
|
336
340
|
parentExecutionId: ids.parentExecutionId,
|
|
337
341
|
signal,
|
|
@@ -403,6 +407,7 @@ export async function runOneDelegation(
|
|
|
403
407
|
await releaseDelegationWorkspace({
|
|
404
408
|
prepared: preparedWorkspace,
|
|
405
409
|
childId: ids.childId,
|
|
410
|
+
episodeId: session.episodeId,
|
|
406
411
|
executionId: ids.executionId,
|
|
407
412
|
parentExecutionId: ids.parentExecutionId,
|
|
408
413
|
ledgerPath: session.ledgerPath,
|
|
@@ -451,6 +456,8 @@ export async function handoffPlanContext(input: {
|
|
|
451
456
|
session: Parameters<typeof advisePruning>[0]["session"];
|
|
452
457
|
base: Record<string, unknown>;
|
|
453
458
|
task: string;
|
|
459
|
+
executionId?: string;
|
|
460
|
+
toolCallId?: string;
|
|
454
461
|
/** A refusal is already certain, so nothing may be asked. */
|
|
455
462
|
blocked: boolean;
|
|
456
463
|
/** Plans with no human in the loop; its result decides whether an advisor is consulted at all. */
|
|
@@ -464,6 +471,8 @@ export async function handoffPlanContext(input: {
|
|
|
464
471
|
session: input.session,
|
|
465
472
|
granted: plan.handoff as Parameters<typeof advisePruning>[0]["granted"],
|
|
466
473
|
task: input.task,
|
|
474
|
+
...(input.executionId ? { executionId: input.executionId } : {}),
|
|
475
|
+
...(input.toolCallId ? { toolCallId: input.toolCallId } : {}),
|
|
467
476
|
...(input.signal ? { signal: input.signal } : {}),
|
|
468
477
|
});
|
|
469
478
|
return ids ? { ...input.base, handoffTurnIds: ids } : { ...input.base };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseInherited } from "../src/kernel/approval.ts";
|
|
2
2
|
import { DELEGATE_CAPABILITY } from "../src/kernel/delegate.ts";
|
|
3
3
|
import { budgetFromEnv } from "../src/kernel/fanout.ts";
|
|
4
|
+
import { isEpisodeId } from "../src/kernel/episode-id.ts";
|
|
4
5
|
import { WILDCARD } from "../src/kernel/pi-tools.ts";
|
|
5
6
|
import {
|
|
6
7
|
depthConfig,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
ENV_APPROVED,
|
|
10
11
|
ENV_DEPTH,
|
|
11
12
|
ENV_EXECUTION_ID,
|
|
13
|
+
ENV_EPISODE_ID,
|
|
12
14
|
ENV_FANOUT,
|
|
13
15
|
ENV_GATED,
|
|
14
16
|
ENV_GRANT,
|
|
@@ -45,6 +47,13 @@ export function reconcileSessionEnvironment(
|
|
|
45
47
|
session.executor = chooseExecutor(environment[ENV_HERDR], null);
|
|
46
48
|
session.ownSpawnId = environment[ENV_PARENT_ID]?.trim() || `d${session.depth}`;
|
|
47
49
|
session.ownExecutionId = environment[ENV_EXECUTION_ID]?.trim() || undefined;
|
|
50
|
+
const inheritedEpisodeId = environment[ENV_EPISODE_ID]?.trim();
|
|
51
|
+
session.episodeId = isEpisodeId(inheritedEpisodeId)
|
|
52
|
+
? inheritedEpisodeId
|
|
53
|
+
: isEpisodeId(lifecycle.episodeId)
|
|
54
|
+
? lifecycle.episodeId
|
|
55
|
+
: session.episodeId;
|
|
56
|
+
lifecycle.episodeId = session.episodeId;
|
|
48
57
|
session.fanoutBudget = budgetFromEnv(environment[ENV_FANOUT]);
|
|
49
58
|
session.mayDelegate =
|
|
50
59
|
!session.governed || session.inherited.includes(DELEGATE_CAPABILITY) || session.inherited.includes(WILDCARD);
|
package/extensions/session.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* copy of `ownGrant` before observation is exactly how a stale upper bound would become an enforced one.
|
|
15
15
|
*/
|
|
16
16
|
import { randomUUID } from "node:crypto";
|
|
17
|
+
import { isEpisodeId, newEpisodeId } from "../src/kernel/episode-id.ts";
|
|
17
18
|
import { parseInherited, type InheritableApproval } from "../src/kernel/approval.ts";
|
|
18
19
|
import type { ApprovalBinding } from "../src/kernel/correlation.ts";
|
|
19
20
|
import { createApprovalGateProvider } from "../src/governance/approval-prompt.ts";
|
|
@@ -100,7 +101,7 @@ import {
|
|
|
100
101
|
ENV_ACTIVITY_ROOT,
|
|
101
102
|
ENV_ACTIVITY_TASK,
|
|
102
103
|
} from "../src/products/activity-timeline.ts";
|
|
103
|
-
import { ENV_HERDR_KEEP_PANE, ENV_GOVERNANCE, ENV_ADVISOR } from "../src/kernel/env-names.ts";
|
|
104
|
+
import { ENV_HERDR_KEEP_PANE, ENV_GOVERNANCE, ENV_ADVISOR, ENV_EPISODE_ID } from "../src/kernel/env-names.ts";
|
|
104
105
|
export { ENV_HERDR_KEEP_PANE, ENV_GOVERNANCE } from "../src/kernel/env-names.ts";
|
|
105
106
|
import { adoptLegacyEnvironment } from "../src/kernel/env-names.ts";
|
|
106
107
|
|
|
@@ -148,6 +149,8 @@ export interface GrantsSession extends NativeSessionHost {
|
|
|
148
149
|
ownSpawnId: string;
|
|
149
150
|
/** Unique identity when this session is itself a governed child; roots have no governed parent. */
|
|
150
151
|
ownExecutionId?: string;
|
|
152
|
+
/** Stable identity shared by this root session and every descendant. */
|
|
153
|
+
episodeId: string;
|
|
151
154
|
/** Descendants this subtree may still create — the cardinality bound ADR-0008 never had. */
|
|
152
155
|
fanoutBudget: number;
|
|
153
156
|
/** Whether delegation tools are active. Reconciled against the owner-bound root at session_start. */
|
|
@@ -407,6 +410,13 @@ export function createGrantsSession(
|
|
|
407
410
|
const environment = lifecycle ? process.env : started!.environment;
|
|
408
411
|
const activityRootId = activeLifecycle.activityRootId ?? randomUUID();
|
|
409
412
|
activeLifecycle.activityRootId = activityRootId;
|
|
413
|
+
const inheritedEpisodeId = environment[ENV_EPISODE_ID];
|
|
414
|
+
const episodeId = isEpisodeId(inheritedEpisodeId)
|
|
415
|
+
? inheritedEpisodeId
|
|
416
|
+
: activeLifecycle.episodeId && isEpisodeId(activeLifecycle.episodeId)
|
|
417
|
+
? activeLifecycle.episodeId
|
|
418
|
+
: newEpisodeId();
|
|
419
|
+
activeLifecycle.episodeId = episodeId;
|
|
410
420
|
// Local governance is on unless PI_DADDY_GOVERNANCE opts out; explicit inherited grants still win.
|
|
411
421
|
// The factory precedes ctx, so its cwd/store identity is reconciled at session_start.
|
|
412
422
|
const grantRaw = environment[ENV_GRANT];
|
|
@@ -435,7 +445,8 @@ export function createGrantsSession(
|
|
|
435
445
|
// Read from `storeCwd` for `loadStoredGrantStateSync`'s reason: this factory runs before any hook, so `ctx.cwd`
|
|
436
446
|
// does not exist yet. Reading a workspace-writable file here is safe precisely because it can only narrow.
|
|
437
447
|
const advisorSession = createAdvisorSession({
|
|
438
|
-
block: projectAdvisorBlock(storeCwd),
|
|
448
|
+
block: projectAdvisorBlock(storeCwd, environment),
|
|
449
|
+
episodeId,
|
|
439
450
|
...(storedLedger ? { ledgerPath: storedLedger } : {}),
|
|
440
451
|
});
|
|
441
452
|
const session: GrantsSession = {
|
|
@@ -469,6 +480,7 @@ export function createGrantsSession(
|
|
|
469
480
|
// every level restarting at `d0` and the ledger becoming unjoinable.
|
|
470
481
|
ownSpawnId: environment[ENV_PARENT_ID]?.trim() || `d${depth}`,
|
|
471
482
|
ownExecutionId: environment[ENV_EXECUTION_ID]?.trim() || undefined,
|
|
483
|
+
episodeId,
|
|
472
484
|
// The cardinality bound ADR-0008 never had: it attenuates downward like depth, so a subtree can never
|
|
473
485
|
// create more descendants than its root was given — with no shared state, no lock and no counter file.
|
|
474
486
|
fanoutBudget: budgetFromEnv(environment[ENV_FANOUT]),
|
|
@@ -501,6 +513,7 @@ export function createGrantsSession(
|
|
|
501
513
|
catalogReady: Promise.resolve(emptyCatalog),
|
|
502
514
|
delegationContext: async (approved?: InheritableApproval[]) => ({
|
|
503
515
|
ownGrant: session.ownGrant,
|
|
516
|
+
episodeId: session.episodeId,
|
|
504
517
|
depth: session.depth,
|
|
505
518
|
maxDepth: session.maxDepth,
|
|
506
519
|
gated: session.gated,
|
|
@@ -559,6 +572,7 @@ export function createGrantsSession(
|
|
|
559
572
|
publishChildEnv: () => {
|
|
560
573
|
const env = childEnv({
|
|
561
574
|
ownGrant: session.ownGrant,
|
|
575
|
+
episodeId: session.episodeId,
|
|
562
576
|
depth: session.depth,
|
|
563
577
|
maxDepth: session.maxDepth,
|
|
564
578
|
gated: session.gated,
|
|
@@ -586,10 +600,19 @@ export function createGrantsSession(
|
|
|
586
600
|
* Unreadable, absent or malformed all yield undefined: this file is the reviewable record, not authority, and the
|
|
587
601
|
* only thing it can do to an advisor is turn one off. A parse failure therefore costs nothing worth reporting.
|
|
588
602
|
*/
|
|
589
|
-
function
|
|
603
|
+
export function reconcileAdvisorSession(session: GrantsSession, environment: NodeJS.ProcessEnv): void {
|
|
604
|
+
session.advisorSession = createAdvisorSession({
|
|
605
|
+
block: projectAdvisorBlock(session.storeCwd, environment),
|
|
606
|
+
episodeId: session.episodeId,
|
|
607
|
+
...(session.ledgerPath ? { ledgerPath: session.ledgerPath } : {}),
|
|
608
|
+
env: environment,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function projectAdvisorBlock(cwd: string, env: NodeJS.ProcessEnv = process.env): unknown {
|
|
590
613
|
// Only when an advisor could exist at all. This runs in every session including every child, before any hook, and
|
|
591
614
|
// a child can never use the result because `PI_DADDY_ADVISOR` is stripped from it.
|
|
592
|
-
if (!
|
|
615
|
+
if (!env[ENV_ADVISOR]?.trim()) return undefined;
|
|
593
616
|
try {
|
|
594
617
|
const path = projectSettingsPath(cwd);
|
|
595
618
|
// Bounded and type-checked first: this is the third unbounded session-start read AGENTS.md warns about, and the
|
|
@@ -69,6 +69,7 @@ export async function prepareDelegationWorkspace(input: {
|
|
|
69
69
|
spec: DelegationWorkspaceSpec;
|
|
70
70
|
correlation?: CorrelationMetadata;
|
|
71
71
|
childId: string;
|
|
72
|
+
episodeId?: string;
|
|
72
73
|
executionId: string;
|
|
73
74
|
parentExecutionId: string | null;
|
|
74
75
|
signal?: AbortSignal;
|
|
@@ -120,6 +121,7 @@ export async function prepareDelegationWorkspace(input: {
|
|
|
120
121
|
await appendLedgerEvent(
|
|
121
122
|
{ path: input.ledgerPath, strict: true },
|
|
122
123
|
buildWorkspaceLeaseEvent({
|
|
124
|
+
...(input.episodeId ? { episodeId: input.episodeId } : {}),
|
|
123
125
|
executionId: input.executionId,
|
|
124
126
|
parentExecutionId: input.parentExecutionId,
|
|
125
127
|
childId: input.childId,
|
|
@@ -146,6 +148,7 @@ export async function prepareDelegationWorkspace(input: {
|
|
|
146
148
|
await appendLedgerEvent(
|
|
147
149
|
{ path: input.ledgerPath, strict: true },
|
|
148
150
|
buildWorkspaceLeaseEvent({
|
|
151
|
+
...(input.episodeId ? { episodeId: input.episodeId } : {}),
|
|
149
152
|
executionId: input.executionId,
|
|
150
153
|
parentExecutionId: input.parentExecutionId,
|
|
151
154
|
childId: input.childId,
|
|
@@ -174,6 +177,7 @@ export async function prepareDelegationWorkspace(input: {
|
|
|
174
177
|
export async function releaseDelegationWorkspace(input: {
|
|
175
178
|
prepared: PreparedWorkspace | undefined;
|
|
176
179
|
childId: string;
|
|
180
|
+
episodeId?: string;
|
|
177
181
|
executionId: string;
|
|
178
182
|
parentExecutionId: string | null;
|
|
179
183
|
ledgerPath?: string;
|
|
@@ -195,6 +199,7 @@ export async function releaseDelegationWorkspace(input: {
|
|
|
195
199
|
await appendLedgerEvent(
|
|
196
200
|
{ path: input.ledgerPath, strict: true },
|
|
197
201
|
buildWorkspaceLeaseEvent({
|
|
202
|
+
...(input.episodeId ? { episodeId: input.episodeId } : {}),
|
|
198
203
|
executionId: input.executionId,
|
|
199
204
|
parentExecutionId: input.parentExecutionId,
|
|
200
205
|
childId: input.childId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-daddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.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",
|