pi-daddy 0.47.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +26 -1
  3. package/dist/executors/child-final.d.ts +3 -0
  4. package/dist/executors/child-final.d.ts.map +1 -1
  5. package/dist/executors/child-final.js +13 -1
  6. package/dist/executors/child-final.js.map +1 -1
  7. package/dist/governance/auto-mode-policy.d.ts.map +1 -1
  8. package/dist/governance/auto-mode-policy.js +32 -1
  9. package/dist/governance/auto-mode-policy.js.map +1 -1
  10. package/dist/governance/dispatch-operation-client.d.ts +4 -0
  11. package/dist/governance/dispatch-operation-client.d.ts.map +1 -0
  12. package/dist/governance/dispatch-operation-client.js +93 -0
  13. package/dist/governance/dispatch-operation-client.js.map +1 -0
  14. package/dist/governance/dispatch-operations.d.ts +17 -0
  15. package/dist/governance/dispatch-operations.d.ts.map +1 -0
  16. package/dist/governance/dispatch-operations.js +81 -0
  17. package/dist/governance/dispatch-operations.js.map +1 -0
  18. package/dist/governance/execution-retention.d.ts +5 -4
  19. package/dist/governance/execution-retention.d.ts.map +1 -1
  20. package/dist/governance/execution-retention.js +3 -1
  21. package/dist/governance/execution-retention.js.map +1 -1
  22. package/dist/governance/ledger-report.d.ts.map +1 -1
  23. package/dist/governance/ledger-report.js +7 -0
  24. package/dist/governance/ledger-report.js.map +1 -1
  25. package/dist/governance/native-session.d.ts +10 -1
  26. package/dist/governance/native-session.d.ts.map +1 -1
  27. package/dist/governance/native-session.js +15 -0
  28. package/dist/governance/native-session.js.map +1 -1
  29. package/dist/governance/retention-contract.d.ts +30 -0
  30. package/dist/governance/retention-contract.d.ts.map +1 -1
  31. package/dist/governance/retention-contract.js +9 -4
  32. package/dist/governance/retention-contract.js.map +1 -1
  33. package/dist/kernel/auto-mode.d.ts +1 -0
  34. package/dist/kernel/auto-mode.d.ts.map +1 -1
  35. package/dist/kernel/auto-mode.js.map +1 -1
  36. package/dist/kernel/chain.d.ts +1 -0
  37. package/dist/kernel/chain.d.ts.map +1 -1
  38. package/dist/kernel/chain.js.map +1 -1
  39. package/dist/kernel/delegate.d.ts.map +1 -1
  40. package/dist/kernel/delegate.js +8 -0
  41. package/dist/kernel/delegate.js.map +1 -1
  42. package/dist/kernel/dispatch-operation.d.ts +47 -0
  43. package/dist/kernel/dispatch-operation.d.ts.map +1 -0
  44. package/dist/kernel/dispatch-operation.js +4 -0
  45. package/dist/kernel/dispatch-operation.js.map +1 -0
  46. package/dist/kernel/propagation.d.ts +4 -1
  47. package/dist/kernel/propagation.d.ts.map +1 -1
  48. package/dist/kernel/propagation.js +11 -1
  49. package/dist/kernel/propagation.js.map +1 -1
  50. package/dist/products/activity-timeline.d.ts +8 -0
  51. package/dist/products/activity-timeline.d.ts.map +1 -1
  52. package/dist/products/activity-timeline.js +8 -1
  53. package/dist/products/activity-timeline.js.map +1 -1
  54. package/dist/products/dashboard-cli.d.ts.map +1 -1
  55. package/dist/products/dashboard-cli.js +2 -1
  56. package/dist/products/dashboard-cli.js.map +1 -1
  57. package/dist/products/dashboard-screen.d.ts +1 -0
  58. package/dist/products/dashboard-screen.d.ts.map +1 -1
  59. package/dist/products/dashboard-screen.js +20 -2
  60. package/dist/products/dashboard-screen.js.map +1 -1
  61. package/extensions/activity-timeline.ts +12 -2
  62. package/extensions/delegate-chain.ts +46 -4
  63. package/extensions/delegation-operations.ts +171 -0
  64. package/extensions/delegation.ts +34 -10
  65. package/extensions/execute-child.ts +21 -3
  66. package/extensions/execution-evidence.ts +3 -0
  67. package/extensions/fanout-outcome.ts +1 -0
  68. package/extensions/grants.ts +2 -3
  69. package/extensions/run-delegation.ts +58 -3
  70. package/extensions/runtime-snapshot.ts +59 -1
  71. package/package.json +1 -1
  72. package/src/executors/child-final.ts +17 -1
  73. package/src/governance/auto-mode-policy.ts +27 -1
  74. package/src/governance/dispatch-operation-client.ts +96 -0
  75. package/src/governance/dispatch-operations.ts +81 -0
  76. package/src/governance/execution-retention.ts +7 -3
  77. package/src/governance/ledger-report.ts +9 -0
  78. package/src/governance/native-session.ts +28 -1
  79. package/src/governance/retention-contract.ts +9 -4
  80. package/src/kernel/auto-mode.ts +1 -0
  81. package/src/kernel/chain.ts +1 -0
  82. package/src/kernel/delegate.ts +14 -0
  83. package/src/kernel/dispatch-operation.ts +38 -0
  84. package/src/kernel/propagation.ts +18 -1
  85. package/src/products/activity-timeline.ts +10 -0
  86. package/src/products/dashboard-cli.ts +2 -1
  87. package/src/products/dashboard-screen.ts +21 -2
@@ -31,7 +31,7 @@ export const RETENTION_SCHEMA = freeze({
31
31
  $schema: "https://json-schema.org/draft/2020-12/schema",
32
32
  $id: "https://pi-daddy.local/contracts/execution-retention/v2/manifest.schema.json",
33
33
  ...closed({
34
- version: literal("2.0"),
34
+ version: { enum: ["2.0", "2.1"] },
35
35
  archiveId: uuid,
36
36
  identity: closed({
37
37
  executionId: string(),
@@ -54,7 +54,7 @@ export const RETENTION_SCHEMA = freeze({
54
54
  branchLeafId: nullable(string(128)),
55
55
  }),
56
56
  nativeSession: closed({
57
- source: { enum: [null, "herdr-id", "herdr-path", "pi-session-file", "pi-session-manager"] },
57
+ source: { enum: [null, "herdr-id", "herdr-path", "pi-session-file", "pi-session-manager", "pi-captured-final"] },
58
58
  status: { enum: ["missing", "verified", "invalid", "changed", "truncated", "unsupported"] },
59
59
  sessionId: nullable(uuid),
60
60
  sessionPath: nullable(string(4096)),
@@ -101,6 +101,10 @@ export const RETENTION_SCHEMA = freeze({
101
101
  acceptance: literal("not-assessed"),
102
102
  }),
103
103
  allOf: [
104
+ {
105
+ if: { properties: { version: literal("2.0") } },
106
+ then: { properties: { nativeSession: { properties: { source: { not: literal("pi-captured-final") } } } } },
107
+ },
104
108
  {
105
109
  if: { properties: { state: literal("running") } },
106
110
  then: { properties: { outcome: literal(null) } },
@@ -162,7 +166,7 @@ export function buildExecutionRetentionManifest(value: unknown): ExecutionRetent
162
166
  }
163
167
  };
164
168
  inspect(value, 0);
165
- if (!validator.Check(value)) throw new TypeError("invalid execution-retention 2.0 manifest");
169
+ if (!validator.Check(value)) throw new TypeError("invalid execution-retention 2.x manifest");
166
170
  const m = JSON.parse(JSON.stringify(value)) as ExecutionRetentionManifest;
167
171
  if (
168
172
  m.native.sessionId !== m.nativeSession.sessionId ||
@@ -178,7 +182,8 @@ export function buildExecutionRetentionManifest(value: unknown): ExecutionRetent
178
182
  }
179
183
  if (
180
184
  m.nativeSession.branchState === "observed" &&
181
- (m.nativeSession.source !== "pi-session-manager" || m.nativeSession.status !== "verified")
185
+ (!["pi-session-manager", "pi-captured-final"].includes(m.nativeSession.source ?? "") ||
186
+ m.nativeSession.status !== "verified")
182
187
  )
183
188
  throw new TypeError("unverified active native branch");
184
189
  if (
@@ -19,6 +19,7 @@ export interface AutoModeRef {
19
19
  }
20
20
  export interface AutoModeReader {
21
21
  readonly reference: AutoModeRef;
22
+ readonly operations?: import("./dispatch-operation.ts").OperationAccess;
22
23
  read(): Promise<AutoModeSnapshot>;
23
24
  /** Linearization point: only a current ON answer admits new work. No approval is banked. */
24
25
  admit(signal?: AbortSignal): Promise<boolean>;
@@ -152,6 +152,7 @@ export function composeStepTask(template: string, previous: string | undefined):
152
152
 
153
153
  /** One chain step as `runOneDelegation` takes it: the operator's spec with its task composed. */
154
154
  export interface ChainStep {
155
+ operation_id?: string;
155
156
  definitionId?: string;
156
157
  task: string;
157
158
  agent?: string;
@@ -201,6 +201,20 @@ export function planDelegation(request: DelegationRequest, ctx: DelegationContex
201
201
  requested = (request.tools ?? []).map(normaliseCapability);
202
202
  }
203
203
 
204
+ // Coordinator-only qualification does not authorize loading native Codemode or its raw model API in children.
205
+ const coordinatorOnly = requested.filter((capability) =>
206
+ ["tool:codemode", "ext:codemode", "tool:principal_codemode", "ext:principal_codemode"].includes(capability),
207
+ );
208
+ if (coordinatorOnly.length)
209
+ return denied(
210
+ {
211
+ ...empty,
212
+ requested,
213
+ reason: `Codemode is qualified only for the coordinator; governed children cannot receive ${coordinatorOnly.join(", ")}`,
214
+ },
215
+ "UNKNOWN_TOOL",
216
+ );
217
+
204
218
  // ADR-0078. A declared `context:` id is a CEILING, not a request: a definition that permits forking must not
205
219
  // fork on every spawn. So the declared modes come out of `requested` and exactly the one this call asked for
206
220
  // goes back in.
@@ -0,0 +1,38 @@
1
+ /** Explicit operation identity prevents duplicate dispatch, never authorizes work or caches results. */
2
+ export interface OperationReference {
3
+ operationId: string;
4
+ executionId: string;
5
+ state: "admitting" | "running" | "settled" | "not-started" | "uncertain";
6
+ cwd: string;
7
+ workspaceId: string | null;
8
+ startedAt: string;
9
+ updatedAt: string;
10
+ runtime?: {
11
+ control?: "failed";
12
+ work?: "succeeded" | "failed" | "unknown";
13
+ final?: { state: string; sessionId?: string; messageId?: string; leafId?: string; sha256?: string };
14
+ cleanup?: { state: string; receiptPath?: string; executionId?: string };
15
+ };
16
+ }
17
+ export interface OperationRequest {
18
+ operationId: string;
19
+ requestDigest: string;
20
+ executionId: string;
21
+ cwd: string;
22
+ workspaceId: string | null;
23
+ }
24
+ export interface OperationClaim {
25
+ operation: OperationReference;
26
+ reused: boolean;
27
+ /** Only the elected caller receives these methods. Neither grants execution authority. */
28
+ started?(cwd: string, workspaceId: string | null): Promise<void>;
29
+ finish?(state: "settled" | "not-started" | "uncertain", runtime?: OperationReference["runtime"]): Promise<void>;
30
+ }
31
+ export interface OperationAccess {
32
+ claim(request: OperationRequest, signal?: AbortSignal): Promise<OperationClaim>;
33
+ read(operationId: string): Promise<OperationReference | null>;
34
+ close(): void;
35
+ }
36
+ export function validOperationId(value: unknown): value is string {
37
+ return typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/.test(value);
38
+ }
@@ -410,7 +410,24 @@ export function mergeChildEnv(parentEnv: NodeJS.ProcessEnv, planEnv: Record<stri
410
410
  }
411
411
 
412
412
  /** Extract bare tool names from a provider payload's tool array, tolerating provider shape differences. */
413
- export function observeToolNames(payload: unknown): string[] | null {
413
+ export function observeToolNames(
414
+ payload: unknown,
415
+ registered?: readonly { name: string; exposure?: string }[],
416
+ active?: readonly string[],
417
+ ): string[] | null {
418
+ // Pi's provider projection may hide declarations while tools remain callable from Codemode.
419
+ // Exposure metadata is native inventory, not authorization; deriveOwnGrant still intersects inheritance.
420
+ if (registered && active && registered.length > 0 && registered.every((tool) => typeof tool.exposure === "string")) {
421
+ const declared = new Set(active);
422
+ return registered
423
+ .filter(
424
+ (tool) =>
425
+ tool.exposure === "codemode" ||
426
+ tool.exposure === "deferred" ||
427
+ ((tool.exposure === "direct" || tool.exposure === "model-only") && declared.has(tool.name)),
428
+ )
429
+ .map((tool) => tool.name);
430
+ }
414
431
  if (!payload || typeof payload !== "object") return null;
415
432
  const record = payload as Record<string, unknown>;
416
433
  for (const key of ["tools", "functions"]) {
@@ -44,6 +44,8 @@ export interface ActivityEvent {
44
44
  agentId?: string;
45
45
  agent?: string;
46
46
  outcome?: string;
47
+ tool?: string;
48
+ cwd?: string;
47
49
  model?: string;
48
50
  thinking?: string;
49
51
  prompt?: ContentReference;
@@ -63,6 +65,8 @@ export interface TimelineTask {
63
65
  agent?: string;
64
66
  startedAt: string;
65
67
  endedAt?: string;
68
+ lastEvent?: { at: string; kind: string; tool?: string };
69
+ cwd?: string;
66
70
  status: "active" | "finished" | "failed" | "cancelled" | "needs-you";
67
71
  model?: string;
68
72
  thinking?: string;
@@ -122,6 +126,8 @@ function eventOf(value: unknown): ActivityEvent | null {
122
126
  )
123
127
  return null;
124
128
  if (
129
+ (event.tool !== undefined && !validId(event.tool)) ||
130
+ (event.cwd !== undefined && (typeof event.cwd !== "string" || !event.cwd || event.cwd.length > 4096)) ||
125
131
  (event.parentTaskId && !validId(event.parentTaskId)) ||
126
132
  (event.agentId && !validId(event.agentId)) ||
127
133
  (event.agent && !validId(event.agent)) ||
@@ -170,6 +176,10 @@ export function parseActivityTimeline(text: string): ActivityTimeline {
170
176
  };
171
177
  tasks.set(key, task);
172
178
  }
179
+ if (!task.lastEvent || Date.parse(event.at) >= Date.parse(task.lastEvent.at)) {
180
+ task.lastEvent = { at: event.at, kind: event.kind, ...(event.tool ? { tool: event.tool } : {}) };
181
+ if (event.cwd) task.cwd = event.cwd;
182
+ }
173
183
  if (event.kind === "task_started") {
174
184
  task.model = event.model;
175
185
  task.thinking = event.thinking;
@@ -347,7 +347,8 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
347
347
  previous = rendered;
348
348
  process.stdout.write(
349
349
  interactive
350
- ? `\u001b[H${frame}\u001b[J`
350
+ ? // Erase every row: clearing only below the final cursor leaves old suffixes on shorter rows.
351
+ `\u001b[H\u001b[2K${frame.replace(/\n/g, "\r\n\u001b[2K")}\u001b[J`
351
352
  : clear
352
353
  ? `\u001b]0;PI-DADDY\u0007\u001b[2J\u001b[H${rendered}`
353
354
  : `${rendered}\n`,
@@ -11,6 +11,7 @@ export interface DashboardItem {
11
11
  status: string;
12
12
  elapsed: string;
13
13
  details: string[];
14
+ observation?: string;
14
15
  taskKey?: string;
15
16
  timelinePath?: string;
16
17
  approval?: boolean;
@@ -49,7 +50,10 @@ function wrapped(lines: string[], width: number): string[] {
49
50
  }
50
51
  function elapsed(ms: number): string {
51
52
  const seconds = Math.max(0, Math.floor(ms / 1000));
52
- return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
53
+ const minutes = Math.floor(seconds / 60);
54
+ return seconds >= 3600
55
+ ? `${Math.floor(minutes / 60)}:${String(minutes % 60).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}`
56
+ : `${minutes}:${String(seconds % 60).padStart(2, "0")}`;
53
57
  }
54
58
  const friendlyStatus = (status: string) =>
55
59
  ({
@@ -87,7 +91,10 @@ export function ledgerDashboardItems(projection: DashboardProjection): Dashboard
87
91
  `Execution: ${node.executionId}`,
88
92
  `State: ${friendlyStatus(node.state)} (observation, not acceptance)`,
89
93
  `Grant: ${node.effectiveGrant.join(", ") || "none"}`,
90
- ...(node.workspace ? [`Workspace: ${node.workspace.id} (${node.workspace.access})`] : []),
94
+ ...(node.workspace
95
+ ? [`Workspace: ${node.workspace.id} (${node.workspace.access})`, `Root: ${node.workspace.root}`]
96
+ : ["Workspace binding: none recorded"]),
97
+ `Last lifecycle event: ${node.updatedAt}`,
91
98
  ...(node.runtime?.herdrPaneId ? [`Pane: ${node.runtime.herdrPaneId}`] : []),
92
99
  ...(node.correlation?.phase ? [`Phase: ${node.correlation.phase}`] : []),
93
100
  ...(node.executor ? [`Executor: ${node.executor}`] : []),
@@ -105,11 +112,21 @@ export function activityDashboardItems(
105
112
  label: `${task.agent || "User turn"} [${aliases.selector(task)}]`,
106
113
  status: task.status,
107
114
  elapsed: elapsed(Date.parse(task.endedAt ?? now.toISOString()) - Date.parse(task.startedAt)),
115
+ observation: task.lastEvent
116
+ ? `${task.lastEvent.tool ? `${task.lastEvent.tool} ` : ""}${task.lastEvent.kind.replaceAll("_", " ")} · ${elapsed(Date.parse(now.toISOString()) - Date.parse(task.lastEvent.at))} ago`
117
+ : undefined,
108
118
  agent: Boolean(task.agent || task.agents.length),
109
119
  skills: task.skills.length > 0,
110
120
  details: [
111
121
  `Task: ${aliases.selector(task)}`,
112
122
  `State: ${friendlyStatus(task.status)} (observation, not acceptance)`,
123
+ ...(task.cwd ? [`Working directory: ${task.cwd}`] : ["Working directory: not recorded"]),
124
+ ...(task.lastEvent
125
+ ? [
126
+ `Last observed: ${task.lastEvent.kind}${task.lastEvent.tool ? ` (${task.lastEvent.tool})` : ""} at ${task.lastEvent.at}`,
127
+ ]
128
+ : []),
129
+ "Silence is not proof of a stalled process. Finished is not review approval.",
113
130
  ...(task.parentTaskId ? [`Parent: ${task.parentTaskId}`] : []),
114
131
  ...(task.model ? [`Model: ${task.model}`] : []),
115
132
  ...(task.thinking ? [`Thinking: ${task.thinking}`] : []),
@@ -281,6 +298,8 @@ export function renderDashboardScreen(options: DashboardScreenOptions): string {
281
298
  }
282
299
  const tail = `${friendlyStatus(item.status)} ${item.elapsed}`.trim(),
283
300
  labelWidth = Math.max(1, width - tail.length - 4);
301
+ if (item.key === screen.selectedKey && item.observation)
302
+ rows.push({ text: ` Observed: ${clean(item.observation)}` });
284
303
  rows.push({
285
304
  key: item.key,
286
305
  text: `${item.key === screen.selectedKey ? ">" : " "} ${truncate(clean(item.label), labelWidth)} ${tail}`,