pi-daddy 0.45.0 → 0.46.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +391 -45
  3. package/contracts/ledger-record/v1/governance-event.schema.json +2 -0
  4. package/dist/executors/captured-execution.d.ts +2 -2
  5. package/dist/executors/captured-execution.d.ts.map +1 -1
  6. package/dist/executors/captured-execution.js +2 -2
  7. package/dist/executors/captured-execution.js.map +1 -1
  8. package/dist/executors/executor.d.ts.map +1 -1
  9. package/dist/executors/executor.js +4 -2
  10. package/dist/executors/executor.js.map +1 -1
  11. package/dist/executors/herdr-cli.d.ts +2 -0
  12. package/dist/executors/herdr-cli.d.ts.map +1 -1
  13. package/dist/executors/herdr-cli.js +2 -2
  14. package/dist/executors/herdr-cli.js.map +1 -1
  15. package/dist/executors/herdr-launcher.d.ts +2 -0
  16. package/dist/executors/herdr-launcher.d.ts.map +1 -0
  17. package/dist/executors/herdr-launcher.js +123 -0
  18. package/dist/executors/herdr-launcher.js.map +1 -0
  19. package/dist/executors/herdr-owned.d.ts +13 -0
  20. package/dist/executors/herdr-owned.d.ts.map +1 -0
  21. package/dist/executors/herdr-owned.js +229 -0
  22. package/dist/executors/herdr-owned.js.map +1 -0
  23. package/dist/executors/herdr-qualification.d.ts +6 -0
  24. package/dist/executors/herdr-qualification.d.ts.map +1 -0
  25. package/dist/executors/herdr-qualification.js +33 -0
  26. package/dist/executors/herdr-qualification.js.map +1 -0
  27. package/dist/executors/herdr-wire.d.ts +5 -0
  28. package/dist/executors/herdr-wire.d.ts.map +1 -0
  29. package/dist/executors/herdr-wire.js +47 -0
  30. package/dist/executors/herdr-wire.js.map +1 -0
  31. package/dist/governance/approval-prompt.d.ts +4 -11
  32. package/dist/governance/approval-prompt.d.ts.map +1 -1
  33. package/dist/governance/approval-prompt.js +83 -24
  34. package/dist/governance/approval-prompt.js.map +1 -1
  35. package/dist/governance/ledger-v3-validation.js +1 -1
  36. package/dist/governance/ledger-v3-validation.js.map +1 -1
  37. package/dist/governance/ledger.d.ts +3 -4
  38. package/dist/governance/ledger.d.ts.map +1 -1
  39. package/dist/governance/ledger.js +1 -1
  40. package/dist/governance/ledger.js.map +1 -1
  41. package/dist/governance/runtime-settlement-lock.d.ts +5 -0
  42. package/dist/governance/runtime-settlement-lock.d.ts.map +1 -0
  43. package/dist/governance/runtime-settlement-lock.js +72 -0
  44. package/dist/governance/runtime-settlement-lock.js.map +1 -0
  45. package/dist/governance/runtime-settlement.d.ts +22 -0
  46. package/dist/governance/runtime-settlement.d.ts.map +1 -0
  47. package/dist/governance/runtime-settlement.js +236 -0
  48. package/dist/governance/runtime-settlement.js.map +1 -0
  49. package/extensions/activity-timeline.ts +94 -31
  50. package/extensions/execute-child.ts +98 -74
  51. package/extensions/execution-controls.ts +42 -0
  52. package/extensions/executor-session.ts +2 -1
  53. package/extensions/grants-command.ts +4 -0
  54. package/extensions/grants.ts +4 -0
  55. package/extensions/reload-environment.ts +8 -0
  56. package/extensions/runtime-snapshot.ts +123 -0
  57. package/extensions/session.ts +1 -1
  58. package/package.json +1 -1
  59. package/src/executors/captured-execution.ts +2 -1
  60. package/src/executors/executor.ts +4 -2
  61. package/src/executors/herdr-cli.ts +19 -12
  62. package/src/executors/herdr-launcher.ts +124 -0
  63. package/src/executors/herdr-owned.ts +260 -0
  64. package/src/executors/herdr-qualification.ts +33 -0
  65. package/src/executors/herdr-wire.ts +43 -0
  66. package/src/governance/approval-prompt.ts +86 -29
  67. package/src/governance/ledger-v3-validation.ts +1 -1
  68. package/src/governance/ledger.ts +3 -4
  69. package/src/governance/runtime-settlement-lock.ts +76 -0
  70. package/src/governance/runtime-settlement.ts +271 -0
@@ -0,0 +1,236 @@
1
+ /** Local runtime facts for explicitly armed resume; no task text, grants, approval or model verdicts. */
2
+ import { lockRuntimeSettlement } from "./runtime-settlement-lock.js";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { mkdirSync, realpathSync, lstatSync, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync, constants, readFileSync, } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { readBoundedTextSync } from "../kernel/bounded-read-sync.js";
7
+ import { isCapturedWorkerIdentity, readCapturedWorkerReceipt, sameWorkerIdentity } from "./captured-worker-record.js";
8
+ const MAX_RECORD = 4 * 1024 * 1024;
9
+ export const settlementHash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
10
+ const ownerId = () => {
11
+ const stat = readFileSync("/proc/self/stat", "utf8");
12
+ return [
13
+ readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim(),
14
+ process.pid,
15
+ stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19],
16
+ ].join(":");
17
+ };
18
+ function ownerAlive(id) {
19
+ const [boot, pid, ticks] = id.split(":");
20
+ if (!/^\d+$/.test(pid ?? "") || !/^\d+$/.test(ticks ?? ""))
21
+ throw Error("invalid runtime journal owner");
22
+ if (readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim() !== boot)
23
+ return false;
24
+ try {
25
+ const text = readFileSync("/proc/" + pid + "/stat", "utf8");
26
+ return text.slice(text.lastIndexOf(")") + 2).split(" ")[19] === ticks;
27
+ }
28
+ catch (error) {
29
+ if (error.code === "ENOENT")
30
+ return false;
31
+ throw error;
32
+ }
33
+ }
34
+ function validate(value, sessionId, cwd) {
35
+ const j = value;
36
+ if (!j ||
37
+ j.version !== 1 ||
38
+ j.sessionId !== sessionId ||
39
+ j.cwd !== cwd ||
40
+ !/^[a-f0-9-]{36}$/.test(j.ownerScope) ||
41
+ typeof j.ownerId !== "string" ||
42
+ !Array.isArray(j.executions) ||
43
+ j.executions.length > 10000)
44
+ throw Error("invalid runtime journal identity");
45
+ const ids = new Set();
46
+ for (const e of j.executions) {
47
+ if (!e ||
48
+ typeof e.id !== "string" ||
49
+ !e.id ||
50
+ e.id.length > 512 ||
51
+ ids.has(e.id) ||
52
+ !["pending", "settled", "not-started", "unknown"].includes(e.state) ||
53
+ (e.identity !== undefined && (!isCapturedWorkerIdentity(e.identity) || e.identity.executionId !== e.id)) ||
54
+ (e.receiptSha256 !== undefined && !/^[a-f0-9]{64}$/.test(e.receiptSha256)) ||
55
+ (e.state === "settled" && (!e.identity || !e.receiptSha256)) ||
56
+ (e.state === "not-started" && e.identity) ||
57
+ (e.problem !== undefined && typeof e.problem !== "string"))
58
+ throw Error("invalid runtime journal execution");
59
+ ids.add(e.id);
60
+ }
61
+ return j;
62
+ }
63
+ export async function openRuntimeSettlement(directory, sessionId, cwd) {
64
+ if (process.platform !== "linux" || process.arch !== "x64")
65
+ throw Error("runtime settlement requires Linux x64");
66
+ if (!sessionId || sessionId.length > 512 || cwd !== realpathSync(cwd))
67
+ throw Error("runtime session/cwd identity is unavailable");
68
+ const root = join(realpathSync(directory), "pi-daddy", "runtime-settlement");
69
+ mkdirSync(root, { recursive: true, mode: 0o700 });
70
+ if (realpathSync(root) !== root)
71
+ throw Error("runtime settlement root must not contain symlinks");
72
+ const info = lstatSync(root);
73
+ if (!info.isDirectory() || info.uid !== process.getuid() || info.mode & 0o077)
74
+ throw Error("runtime settlement root must be private");
75
+ const path = join(root, settlementHash([sessionId, cwd]) + ".json");
76
+ const lock = await lockRuntimeSettlement(path + ".lock");
77
+ try {
78
+ let journal;
79
+ try {
80
+ journal = validate(JSON.parse(readBoundedTextSync(path, { maxBytes: MAX_RECORD }).text), sessionId, cwd);
81
+ if (ownerAlive(journal.ownerId))
82
+ throw Error("prior runtime owner is still alive");
83
+ journal.ownerId = ownerId();
84
+ }
85
+ catch (error) {
86
+ if (error.code !== "ENOENT")
87
+ throw error;
88
+ journal = { version: 1, sessionId, cwd, ownerScope: randomUUID(), ownerId: ownerId(), executions: [] };
89
+ }
90
+ let failed, active = new Set();
91
+ const save = () => {
92
+ let temporary, fd;
93
+ try {
94
+ if (!lock.healthy())
95
+ throw Error("runtime ownership lock was lost");
96
+ const text = JSON.stringify(journal) + "\n";
97
+ if (Buffer.byteLength(text) > MAX_RECORD)
98
+ throw Error("runtime settlement journal exceeds its bound");
99
+ temporary = path + "." + randomUUID();
100
+ fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
101
+ writeFileSync(fd, text);
102
+ fsyncSync(fd);
103
+ closeSync(fd);
104
+ fd = undefined;
105
+ renameSync(temporary, path);
106
+ const parent = openSync(root, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
107
+ try {
108
+ fsyncSync(parent);
109
+ }
110
+ finally {
111
+ closeSync(parent);
112
+ }
113
+ }
114
+ catch (error) {
115
+ failed = String(error);
116
+ throw error;
117
+ }
118
+ finally {
119
+ if (fd !== undefined)
120
+ closeSync(fd);
121
+ if (temporary)
122
+ try {
123
+ unlinkSync(temporary);
124
+ }
125
+ catch { }
126
+ }
127
+ };
128
+ const entry = (id) => {
129
+ const e = journal.executions.find((e) => e.id === id);
130
+ if (!e)
131
+ throw Error("runtime execution was not registered");
132
+ return e;
133
+ };
134
+ save();
135
+ return {
136
+ ownerScope: journal.ownerScope,
137
+ ownerId: journal.ownerId,
138
+ sessionId,
139
+ cwd,
140
+ begin(id) {
141
+ if (failed)
142
+ throw Error(failed);
143
+ if (typeof id !== "string" ||
144
+ !id ||
145
+ id.length > 512 ||
146
+ journal.executions.some((e) => e.id === id) ||
147
+ journal.executions.length >= 10000)
148
+ throw Error("runtime execution id must be fresh and bounded");
149
+ journal.executions.push({ id, state: "pending" });
150
+ active.add(id);
151
+ save();
152
+ },
153
+ bind(identity) {
154
+ const e = entry(identity.executionId);
155
+ if (e.state !== "pending" || (e.identity && !sameWorkerIdentity(e.identity, identity)))
156
+ throw Error("runtime ownership changed");
157
+ e.identity = { ...identity };
158
+ save();
159
+ },
160
+ async finish(id, cleanup, problem) {
161
+ const e = entry(id);
162
+ if (problem)
163
+ e.problem = problem.slice(0, 2000);
164
+ try {
165
+ if (cleanup.state === "not-started" && !e.identity)
166
+ e.state = "not-started";
167
+ else if (cleanup.state === "settled" && e.identity && sameWorkerIdentity(e.identity, cleanup.identity)) {
168
+ const receipt = await readCapturedWorkerReceipt(e.identity);
169
+ if (receipt) {
170
+ e.state = "settled";
171
+ e.receiptSha256 = settlementHash(receipt);
172
+ }
173
+ else
174
+ e.state = "unknown";
175
+ }
176
+ else
177
+ e.state = "unknown";
178
+ save();
179
+ }
180
+ finally {
181
+ active.delete(id);
182
+ }
183
+ },
184
+ async snapshot() {
185
+ if (!lock.healthy())
186
+ failed = "runtime ownership lock was lost";
187
+ let changed = false;
188
+ for (const e of journal.executions) {
189
+ if (active.has(e.id) || !e.identity)
190
+ continue;
191
+ const receipt = await readCapturedWorkerReceipt(e.identity);
192
+ if (!receipt || (e.receiptSha256 && e.receiptSha256 !== settlementHash(receipt))) {
193
+ if (e.state === "settled") {
194
+ e.state = "unknown";
195
+ changed = true;
196
+ }
197
+ continue;
198
+ }
199
+ if (e.state !== "settled") {
200
+ e.state = "settled";
201
+ e.receiptSha256 = settlementHash(receipt);
202
+ changed = true;
203
+ }
204
+ }
205
+ if (changed) {
206
+ try {
207
+ save();
208
+ }
209
+ catch { }
210
+ }
211
+ const outstanding = journal.executions.filter((e) => !["settled", "not-started"].includes(e.state) || e.problem);
212
+ const state = failed || outstanding.some((e) => !active.has(e.id))
213
+ ? "unknown"
214
+ : active.size
215
+ ? "busy"
216
+ : outstanding.length
217
+ ? "unknown"
218
+ : "idle";
219
+ return {
220
+ ownerScope: journal.ownerScope,
221
+ ownerId: journal.ownerId,
222
+ evidenceDigest: settlementHash({ ownerScope: journal.ownerScope, executions: journal.executions }),
223
+ state,
224
+ settledExecutionIds: journal.executions.filter((e) => e.state === "settled").map((e) => e.id),
225
+ outstandingExecutionIds: outstanding.map((e) => e.id),
226
+ ...(failed ? { reason: failed } : {}),
227
+ };
228
+ },
229
+ };
230
+ }
231
+ catch (error) {
232
+ await lock.release();
233
+ throw error;
234
+ }
235
+ }
236
+ //# sourceMappingURL=runtime-settlement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-settlement.js","sourceRoot":"","sources":["../../src/governance/runtime-settlement.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,13 @@ interface HookContext {
21
21
  cwd: string;
22
22
  model?: { id?: string };
23
23
  thinkingLevel?: string;
24
+ sessionManager?: {
25
+ getBranch(): Array<{
26
+ id: string;
27
+ type: string;
28
+ message?: { role?: string; content?: unknown; stopReason?: string };
29
+ }>;
30
+ };
24
31
  ui: { notify(message: string, level?: "info" | "warning" | "error"): void };
25
32
  }
26
33
  const digest = (text: string) => createHash("sha256").update(text).digest("hex");
@@ -32,7 +39,11 @@ const digest = (text: string) => createHash("sha256").update(text).digest("hex")
32
39
  export function registerActivityTimeline(pi: ExtensionAPI, session?: ActivitySessionState, lifecycleTool = true): void {
33
40
  let recorder: ActivityTimelineRecorder | undefined,
34
41
  pendingFinal: string | undefined,
35
- finalizedUser: string | undefined;
42
+ pendingStopReason: string | undefined,
43
+ observedUser: string | undefined,
44
+ pendingStart: { event: unknown; prompt: string; priorIds: Set<string> } | undefined;
45
+ let turnPriorIds = new Set<string>();
46
+ let activeRoot = false;
36
47
  let paths = new Map<string, string>(),
37
48
  available = new Map<string, ActivitySkill>();
38
49
  let reported = false;
@@ -77,6 +88,44 @@ export function registerActivityTimeline(pi: ExtensionAPI, session?: ActivitySes
77
88
  await target.skill("skill_available", fact);
78
89
  }
79
90
  };
91
+ const messageText = (content: unknown): string =>
92
+ typeof content === "string"
93
+ ? content
94
+ : Array.isArray(content)
95
+ ? content
96
+ .filter((part) => part?.type === "text")
97
+ .map((part) => part.text ?? "")
98
+ .join("")
99
+ : "";
100
+ // Pi emits before_agent_start before user message_end. Read the finalized branch only after
101
+ // every message transformer has run; never carry a prior turn's user into the next task.
102
+ const startPending = async (ctx: HookContext) => {
103
+ const pending = pendingStart;
104
+ if (!pending) return;
105
+ const user = ctx.sessionManager
106
+ ?.getBranch()
107
+ .find((entry) => !pending.priorIds.has(entry.id) && entry.type === "message" && entry.message?.role === "user");
108
+ const target = current(ctx);
109
+ const taskId = await target.start(
110
+ user ? messageText(user.message?.content) : (observedUser ?? pending.prompt),
111
+ ctx.model?.id,
112
+ ctx.thinkingLevel,
113
+ );
114
+ pendingStart = undefined;
115
+ observedUser = undefined;
116
+ if (session) session.activity = { rootId: target.rootId, path: target.path, ...(taskId ? { taskId } : {}) };
117
+ await skills(pending.event, target);
118
+ };
119
+ const resetTurn = () => {
120
+ activeRoot = false;
121
+ pendingStart = undefined;
122
+ observedUser = undefined;
123
+ pendingFinal = undefined;
124
+ pendingStopReason = undefined;
125
+ turnPriorIds = new Set();
126
+ paths.clear();
127
+ available.clear();
128
+ };
80
129
  if (lifecycleTool)
81
130
  pi.registerTool({
82
131
  name: "activity_lifecycle",
@@ -101,6 +150,8 @@ export function registerActivityTimeline(pi: ExtensionAPI, session?: ActivitySes
101
150
  },
102
151
  });
103
152
  pi.on("session_start", (_event, ctx: HookContext) => {
153
+ resetTurn();
154
+ recorder = undefined;
104
155
  try {
105
156
  recorder = current(ctx);
106
157
  // A leaf observer attaches to the parent-owned child event; a root gets a stable session identity.
@@ -122,42 +173,45 @@ export function registerActivityTimeline(pi: ExtensionAPI, session?: ActivitySes
122
173
  }
123
174
  return undefined;
124
175
  });
176
+ pi.on("session_shutdown", () => {
177
+ resetTurn();
178
+ recorder = undefined;
179
+ });
125
180
  pi.on("before_agent_start", async (event, ctx: HookContext) => {
126
181
  try {
182
+ resetTurn();
127
183
  const target = current(ctx);
128
- // An injected leaf observer must not fabricate a second child task; its parent-owned execution seam
129
- // already wrote task_started. Root turns always get a fresh task id under their stable root identity.
184
+ // Leaves attach to the parent-owned execution. Roots bind the current finalized user at
185
+ // the first context boundary, or at settlement if cancelled before the first model call.
130
186
  if (!process.env[ENV_ACTIVITY_TASK]) {
131
- const taskId = await target.start(
132
- finalizedUser ?? (event as { prompt?: string }).prompt ?? "",
133
- ctx.model?.id,
134
- ctx.thinkingLevel,
135
- );
136
- finalizedUser = undefined;
137
- if (session?.activity)
138
- session.activity = { rootId: target.rootId, path: target.path, ...(taskId ? { taskId } : {}) };
139
- }
140
- await skills(event, target);
187
+ activeRoot = true;
188
+ turnPriorIds = new Set(ctx.sessionManager?.getBranch().map((entry) => entry.id) ?? []);
189
+ pendingStart = { event, prompt: (event as { prompt?: string }).prompt ?? "", priorIds: turnPriorIds };
190
+ if (session) session.activity = { rootId: target.rootId, path: target.path };
191
+ } else await skills(event, target);
192
+ } catch (error) {
193
+ report(ctx, error);
194
+ }
195
+ return undefined;
196
+ });
197
+ pi.on("context", async (_event, ctx: HookContext) => {
198
+ try {
199
+ await startPending(ctx);
141
200
  } catch (error) {
142
201
  report(ctx, error);
143
202
  }
144
203
  return undefined;
145
204
  });
146
205
  pi.on("message_end", (event) => {
147
- const message = (
148
- event as { message?: { role?: string; content?: string | Array<{ type?: string; text?: string }> } }
149
- ).message;
150
- const text =
151
- typeof message?.content === "string"
152
- ? message.content
153
- : Array.isArray(message?.content)
154
- ? message!.content
155
- .filter((part) => part.type === "text")
156
- .map((part) => part.text ?? "")
157
- .join("")
158
- : "";
159
- if (message?.role === "user") finalizedUser = text;
160
- if (message?.role === "assistant") pendingFinal = text;
206
+ const message = (event as { message?: { role?: string; content?: unknown; stopReason?: string } }).message;
207
+ // This fallback is scoped to this pending start. Steering within an active turn cannot
208
+ // overwrite its initial prompt or seed the next root task.
209
+ if (message?.role === "user" && pendingStart && observedUser === undefined)
210
+ observedUser = messageText(message.content);
211
+ if (message?.role === "assistant") {
212
+ pendingFinal = messageText(message.content);
213
+ pendingStopReason = message.stopReason;
214
+ }
161
215
  return undefined;
162
216
  });
163
217
  pi.on("tool_execution_start", (event) => {
@@ -178,19 +232,28 @@ export function registerActivityTimeline(pi: ExtensionAPI, session?: ActivitySes
178
232
  return undefined;
179
233
  });
180
234
  pi.on("agent_settled", async (event, ctx: HookContext) => {
235
+ if (!activeRoot) return undefined;
181
236
  try {
237
+ await startPending(ctx);
238
+ const final = ctx.sessionManager
239
+ ?.getBranch()
240
+ .findLast(
241
+ (entry) => !turnPriorIds.has(entry.id) && entry.type === "message" && entry.message?.role === "assistant",
242
+ );
243
+ const stopReason = final?.message?.stopReason ?? pendingStopReason;
182
244
  if (recorder && !process.env[ENV_ACTIVITY_TASK])
183
245
  await recorder.finish(
184
- pendingFinal,
185
- (event as { cancelled?: boolean; error?: unknown }).cancelled
246
+ final ? messageText(final.message?.content) : pendingFinal,
247
+ (event as { aborted?: boolean }).aborted || stopReason === "aborted"
186
248
  ? "cancelled"
187
- : (event as { error?: unknown }).error
249
+ : stopReason === "error"
188
250
  ? "failed"
189
251
  : "completed",
190
252
  );
191
- pendingFinal = undefined;
192
253
  } catch (error) {
193
254
  report(ctx, error);
255
+ } finally {
256
+ resetTurn();
194
257
  }
195
258
  return undefined;
196
259
  });