pi-daddy 0.19.0 → 0.20.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 (125) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +105 -36
  3. package/contracts/ledger/v3/README.md +36 -0
  4. package/contracts/ledger/v3/fixtures/capability-decision.json +98 -0
  5. package/contracts/ledger/v3/fixtures/check-receipt.json +42 -0
  6. package/contracts/ledger/v3/fixtures/child-lifecycle.json +45 -0
  7. package/contracts/ledger/v3/fixtures/workflow-fact.json +41 -0
  8. package/contracts/ledger/v3/fixtures/workspace-lease.json +43 -0
  9. package/contracts/ledger/v3/ledger-event.schema.json +930 -0
  10. package/dist/check-runner.d.ts.map +1 -1
  11. package/dist/check-runner.js +14 -0
  12. package/dist/check-runner.js.map +1 -1
  13. package/dist/cli.js +0 -0
  14. package/dist/correlation.d.ts.map +1 -1
  15. package/dist/correlation.js +10 -0
  16. package/dist/correlation.js.map +1 -1
  17. package/dist/dashboard-cli.d.ts +18 -0
  18. package/dist/dashboard-cli.d.ts.map +1 -0
  19. package/dist/dashboard-cli.js +154 -0
  20. package/dist/dashboard-cli.js.map +1 -0
  21. package/dist/dashboard-handshake.d.ts +37 -0
  22. package/dist/dashboard-handshake.d.ts.map +1 -0
  23. package/dist/dashboard-handshake.js +127 -0
  24. package/dist/dashboard-handshake.js.map +1 -0
  25. package/dist/dashboard-herdr.d.ts +54 -0
  26. package/dist/dashboard-herdr.d.ts.map +1 -0
  27. package/dist/dashboard-herdr.js +286 -0
  28. package/dist/dashboard-herdr.js.map +1 -0
  29. package/dist/dashboard-projection.d.ts +73 -0
  30. package/dist/dashboard-projection.d.ts.map +1 -0
  31. package/dist/dashboard-projection.js +294 -0
  32. package/dist/dashboard-projection.js.map +1 -0
  33. package/dist/dashboard-render.d.ts +10 -0
  34. package/dist/dashboard-render.d.ts.map +1 -0
  35. package/dist/dashboard-render.js +208 -0
  36. package/dist/dashboard-render.js.map +1 -0
  37. package/dist/delegate-types.d.ts +6 -2
  38. package/dist/delegate-types.d.ts.map +1 -1
  39. package/dist/delegate-types.js.map +1 -1
  40. package/dist/delegate.d.ts.map +1 -1
  41. package/dist/delegate.js +4 -1
  42. package/dist/delegate.js.map +1 -1
  43. package/dist/execution-id.d.ts +6 -0
  44. package/dist/execution-id.d.ts.map +1 -0
  45. package/dist/execution-id.js +13 -0
  46. package/dist/execution-id.js.map +1 -0
  47. package/dist/index.d.ts +4 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/ledger-events.d.ts +27 -4
  52. package/dist/ledger-events.d.ts.map +1 -1
  53. package/dist/ledger-events.js +30 -9
  54. package/dist/ledger-events.js.map +1 -1
  55. package/dist/ledger-identifiers.d.ts +7 -0
  56. package/dist/ledger-identifiers.d.ts.map +1 -0
  57. package/dist/ledger-identifiers.js +20 -0
  58. package/dist/ledger-identifiers.js.map +1 -0
  59. package/dist/ledger-report.d.ts +5 -2
  60. package/dist/ledger-report.d.ts.map +1 -1
  61. package/dist/ledger-report.js +38 -14
  62. package/dist/ledger-report.js.map +1 -1
  63. package/dist/ledger-v3-validation.d.ts +11 -0
  64. package/dist/ledger-v3-validation.d.ts.map +1 -0
  65. package/dist/ledger-v3-validation.js +265 -0
  66. package/dist/ledger-v3-validation.js.map +1 -0
  67. package/dist/ledger.d.ts +18 -5
  68. package/dist/ledger.d.ts.map +1 -1
  69. package/dist/ledger.js +29 -7
  70. package/dist/ledger.js.map +1 -1
  71. package/dist/propagation.d.ts +3 -1
  72. package/dist/propagation.d.ts.map +1 -1
  73. package/dist/propagation.js +3 -0
  74. package/dist/propagation.js.map +1 -1
  75. package/dist/run-child.d.ts +3 -1
  76. package/dist/run-child.d.ts.map +1 -1
  77. package/dist/run-child.js +30 -2
  78. package/dist/run-child.js.map +1 -1
  79. package/dist/run-herdr.d.ts +2 -0
  80. package/dist/run-herdr.d.ts.map +1 -1
  81. package/dist/run-herdr.js +8 -0
  82. package/dist/run-herdr.js.map +1 -1
  83. package/dist/workflow-fact-id.d.ts +4 -0
  84. package/dist/workflow-fact-id.d.ts.map +1 -0
  85. package/dist/workflow-fact-id.js +14 -0
  86. package/dist/workflow-fact-id.js.map +1 -0
  87. package/dist/workflow-facts.d.ts +34 -0
  88. package/dist/workflow-facts.d.ts.map +1 -0
  89. package/dist/workflow-facts.js +43 -0
  90. package/dist/workflow-facts.js.map +1 -0
  91. package/extensions/chain-ledger.ts +4 -0
  92. package/extensions/chain-plan.ts +98 -0
  93. package/extensions/delegate-chain.ts +63 -122
  94. package/extensions/delegation-ledger.ts +60 -0
  95. package/extensions/delegation.ts +13 -13
  96. package/extensions/execute-child.ts +85 -21
  97. package/extensions/execution-occurrence.ts +20 -0
  98. package/extensions/grants-command.ts +22 -3
  99. package/extensions/grants.ts +44 -0
  100. package/extensions/run-delegation.ts +36 -56
  101. package/extensions/session.ts +7 -2
  102. package/extensions/workspace-runtime.ts +10 -0
  103. package/herdr-plugin/herdr-plugin.toml +18 -0
  104. package/package.json +16 -4
  105. package/src/check-runner.ts +16 -0
  106. package/src/correlation.ts +11 -0
  107. package/src/dashboard-cli.ts +171 -0
  108. package/src/dashboard-handshake.ts +185 -0
  109. package/src/dashboard-herdr.ts +355 -0
  110. package/src/dashboard-projection.ts +390 -0
  111. package/src/dashboard-render.ts +254 -0
  112. package/src/delegate-types.ts +6 -2
  113. package/src/delegate.ts +4 -2
  114. package/src/execution-id.ts +18 -0
  115. package/src/index.ts +25 -0
  116. package/src/ledger-events.ts +54 -10
  117. package/src/ledger-identifiers.ts +21 -0
  118. package/src/ledger-report.ts +40 -19
  119. package/src/ledger-v3-validation.ts +266 -0
  120. package/src/ledger.ts +49 -10
  121. package/src/propagation.ts +3 -0
  122. package/src/run-child.ts +35 -3
  123. package/src/run-herdr.ts +9 -0
  124. package/src/workflow-fact-id.ts +16 -0
  125. package/src/workflow-facts.ts +65 -0
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { parseDashboardLedger } from "./dashboard-projection.ts";
7
+ import { renderDashboard } from "./dashboard-render.ts";
8
+
9
+ export const DASHBOARD_PROTOCOL_VERSION = 1 as const;
10
+ export const ENV_DASHBOARD_LEDGER = "PI_DADDY_LEDGER";
11
+ export const ENV_DASHBOARD_PROTOCOL = "PI_DADDY_DASHBOARD_PROTOCOL";
12
+ export const ENV_DASHBOARD_KEY = "PI_DADDY_DASHBOARD_KEY";
13
+ export const DASHBOARD_REFRESH_MS = 250;
14
+
15
+ export interface DashboardFrameOptions {
16
+ cwd: string;
17
+ ledgerPath?: string;
18
+ protocol?: number;
19
+ color?: boolean;
20
+ width?: number;
21
+ details?: boolean;
22
+ now?: Date;
23
+ }
24
+
25
+ function shellQuote(value: string): string {
26
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
27
+ }
28
+
29
+ function setupFrame(cwd: string): string {
30
+ const ledger = resolve(cwd, ".pi", "grants.jsonl");
31
+ return [
32
+ "PI-DADDY",
33
+ "",
34
+ "pi-daddy is missing or its ledger is inactive in this workspace.",
35
+ "Nothing was installed or configured automatically.",
36
+ "",
37
+ "Run exactly:",
38
+ " pi install npm:pi-daddy",
39
+ ` export PI_GRANTS_LEDGER=${shellQuote(ledger)}`,
40
+ " pi",
41
+ "",
42
+ "Then run /grants dashboard inside that Herdr-hosted pi session.",
43
+ ].join("\n");
44
+ }
45
+
46
+ function incompatibleFrame(protocol: number): string {
47
+ return [
48
+ "PI-DADDY — INCOMPATIBLE DASHBOARD",
49
+ "",
50
+ `dashboard plugin protocol ${DASHBOARD_PROTOCOL_VERSION}; core requested ${protocol}.`,
51
+ "No execution state is rendered because assigning old fields new meaning would be unsafe.",
52
+ "Relink the dashboard from the same installed pi-daddy package, then retry /grants dashboard.",
53
+ ].join("\n");
54
+ }
55
+
56
+ export async function dashboardFrame(options: DashboardFrameOptions): Promise<string> {
57
+ if (options.protocol !== undefined && options.protocol !== DASHBOARD_PROTOCOL_VERSION) {
58
+ return incompatibleFrame(options.protocol);
59
+ }
60
+ if (!options.ledgerPath) return setupFrame(options.cwd);
61
+
62
+ const ledgerPath = resolve(options.cwd, options.ledgerPath);
63
+ let text = "";
64
+ let waiting = false;
65
+ try {
66
+ text = await readFile(ledgerPath, "utf8");
67
+ } catch (error) {
68
+ if ((error as { code?: string }).code === "ENOENT") waiting = true;
69
+ else {
70
+ return [
71
+ "PI-DADDY — LEDGER UNAVAILABLE",
72
+ "",
73
+ `Configured ledger: ${ledgerPath}`,
74
+ `Could not read it: ${error instanceof Error ? error.message : String(error)}`,
75
+ "The dashboard did not substitute an empty tree for this failure.",
76
+ ].join("\n");
77
+ }
78
+ }
79
+
80
+ const rendered = renderDashboard(parseDashboardLedger(text, { now: options.now }), {
81
+ color: options.color,
82
+ width: options.width,
83
+ details: options.details,
84
+ });
85
+ return waiting ? `${rendered}\n\nwaiting for ledger ${ledgerPath}` : rendered;
86
+ }
87
+
88
+ interface CliOptions {
89
+ once: boolean;
90
+ details: boolean;
91
+ color: boolean;
92
+ ledgerPath?: string;
93
+ }
94
+
95
+ function parseArgs(argv: string[]): CliOptions {
96
+ let once = false;
97
+ let details = false;
98
+ let color = process.stdout.isTTY;
99
+ let ledgerPath: string | undefined;
100
+ for (let index = 0; index < argv.length; index += 1) {
101
+ const arg = argv[index];
102
+ if (arg === "--once") once = true;
103
+ else if (arg === "--details") details = true;
104
+ else if (arg === "--no-color") color = false;
105
+ else if (arg === "--ledger") ledgerPath = argv[++index];
106
+ else throw new Error(`unknown dashboard argument ${JSON.stringify(arg)}`);
107
+ }
108
+ return { once, details, color, ledgerPath };
109
+ }
110
+
111
+ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.ProcessEnv = process.env): Promise<void> {
112
+ const cli = parseArgs(argv);
113
+ const cwd = process.cwd();
114
+ const ledgerPath = cli.ledgerPath ?? (env[ENV_DASHBOARD_LEDGER]?.trim() || undefined);
115
+ const rawProtocol = env[ENV_DASHBOARD_PROTOCOL]?.trim();
116
+ const protocol = rawProtocol === undefined || rawProtocol === "" ? undefined : Number(rawProtocol);
117
+ const key = env[ENV_DASHBOARD_KEY]?.trim();
118
+ process.title = `pi-daddy-dashboard${key ? `:${key.slice(0, 12)}` : ""}`;
119
+
120
+ let previous = "";
121
+ const draw = async (clear: boolean): Promise<void> => {
122
+ const frame = await dashboardFrame({
123
+ cwd,
124
+ ledgerPath,
125
+ protocol,
126
+ color: cli.color,
127
+ width: process.stdout.columns || 80,
128
+ details: cli.details,
129
+ });
130
+ if (frame === previous && !clear) return;
131
+ previous = frame;
132
+ process.stdout.write(clear ? `\u001b]0;PI-DADDY\u0007\u001b[2J\u001b[H${frame}` : `${frame}\n`);
133
+ };
134
+
135
+ if (cli.once) {
136
+ await draw(false);
137
+ return;
138
+ }
139
+ await draw(true);
140
+ let drawing = false;
141
+ const redraw = (): void => {
142
+ if (drawing) return;
143
+ drawing = true;
144
+ void draw(true).finally(() => { drawing = false; });
145
+ };
146
+ const timer = setInterval(redraw, DASHBOARD_REFRESH_MS);
147
+ process.on("SIGWINCH", redraw);
148
+ await new Promise<void>((settle) => {
149
+ const stop = () => {
150
+ clearInterval(timer);
151
+ process.off("SIGWINCH", redraw);
152
+ settle();
153
+ };
154
+ process.once("SIGINT", stop);
155
+ process.once("SIGTERM", stop);
156
+ });
157
+ }
158
+
159
+ const invoked = (() => {
160
+ try {
161
+ return process.argv[1] ? import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href : false;
162
+ } catch {
163
+ return false;
164
+ }
165
+ })();
166
+ if (invoked) {
167
+ runDashboard().catch((error) => {
168
+ process.stderr.write(`pi-daddy dashboard failed: ${error instanceof Error ? error.message : String(error)}\n`);
169
+ process.exitCode = 1;
170
+ });
171
+ }
@@ -0,0 +1,185 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { dirname, resolve } from "node:path";
4
+ import {
5
+ DASHBOARD_PLUGIN_ID,
6
+ dashboardHerdrExec,
7
+ dashboardPaneStatePath,
8
+ inspectDashboardPlugin,
9
+ linkDashboardPlugin,
10
+ openOrReuseDashboard,
11
+ verifyHerdrHost,
12
+ type DashboardOpenResult,
13
+ } from "./dashboard-herdr.ts";
14
+ import { DASHBOARD_PROTOCOL_VERSION } from "./dashboard-cli.ts";
15
+ import type { HerdrExec } from "./herdr-cli.ts";
16
+
17
+ export type DashboardPromptChoice = "not-now" | "never";
18
+ interface DashboardPreference {
19
+ version: 1;
20
+ protocol: number;
21
+ choice: DashboardPromptChoice;
22
+ recordedAt: string;
23
+ }
24
+
25
+ export function dashboardPreferencePath(agentDir: string): string {
26
+ return resolve(agentDir, "pi-daddy", "dashboard-preference.json");
27
+ }
28
+
29
+ async function loadPreference(path: string): Promise<DashboardPreference | null> {
30
+ try {
31
+ const parsed = JSON.parse(await readFile(path, "utf8")) as DashboardPreference;
32
+ if (parsed?.version !== 1 || !Number.isInteger(parsed.protocol) ||
33
+ !["not-now", "never"].includes(parsed.choice)) {
34
+ throw new Error("unsupported preference shape");
35
+ }
36
+ return parsed;
37
+ } catch (error) {
38
+ if ((error as { code?: string }).code === "ENOENT") return null;
39
+ throw new Error(`dashboard preference ${path} is corrupt or unreadable: ${error instanceof Error ? error.message : String(error)}`);
40
+ }
41
+ }
42
+
43
+ async function savePreference(path: string, choice: DashboardPromptChoice): Promise<void> {
44
+ const preference: DashboardPreference = {
45
+ version: 1,
46
+ protocol: DASHBOARD_PROTOCOL_VERSION,
47
+ choice,
48
+ recordedAt: new Date().toISOString(),
49
+ };
50
+ await mkdir(dirname(path), { recursive: true });
51
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
52
+ await writeFile(temporary, `${JSON.stringify(preference, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
53
+ await rename(temporary, path);
54
+ }
55
+
56
+ function quote(value: string): string {
57
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
58
+ }
59
+
60
+ export interface DashboardHandshakeUI {
61
+ select(title: string, choices: string[]): Promise<string | undefined>;
62
+ notify(message: string, level?: "info" | "warning" | "error"): void;
63
+ }
64
+
65
+ export interface DashboardHandshakeInput {
66
+ mode: string;
67
+ env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
68
+ pid?: number;
69
+ exec?: HerdrExec;
70
+ cwd: string;
71
+ ledgerPath?: string;
72
+ pluginRoot: string;
73
+ preferencePath: string;
74
+ paneStatePath: string;
75
+ ui: DashboardHandshakeUI;
76
+ }
77
+
78
+ export type DashboardHandshakeOutcome = "not-applicable" | "suppressed" | "already-installed" | "installed" | "deferred" | "failed";
79
+
80
+ export async function offerDashboardHandshake(input: DashboardHandshakeInput): Promise<DashboardHandshakeOutcome> {
81
+ if (input.mode !== "tui") return "not-applicable";
82
+ const exec = input.exec ?? dashboardHerdrExec;
83
+ const host = await verifyHerdrHost({ env: input.env, pid: input.pid, exec });
84
+ if (!host.ok) return "not-applicable";
85
+
86
+ try {
87
+ const preference = await loadPreference(input.preferencePath);
88
+ if (preference) return "suppressed";
89
+ const plugin = await inspectDashboardPlugin(exec, input.pluginRoot);
90
+ if (plugin.state === "compatible") return "already-installed";
91
+ if (plugin.state !== "absent") {
92
+ input.ui.notify(`pi-daddy dashboard: ${plugin.diagnostic}`, "error");
93
+ return "failed";
94
+ }
95
+
96
+ const choice = await input.ui.select(
97
+ "Herdr detected. Install and open the pi-daddy dashboard?",
98
+ ["Install and open", "Not now", "Never ask"],
99
+ );
100
+ if (choice !== "Install and open" && choice !== "Not now" && choice !== "Never ask") {
101
+ // Escape, timeout and UI teardown return undefined. None is an operator choice, so none may become a
102
+ // durable preference merely because the dialog disappeared.
103
+ input.ui.notify("pi-daddy dashboard: installation prompt dismissed; no preference was stored.", "info");
104
+ return "deferred";
105
+ }
106
+ if (choice !== "Install and open") {
107
+ const recorded = choice === "Never ask" ? "never" : "not-now";
108
+ await savePreference(input.preferencePath, recorded);
109
+ input.ui.notify(
110
+ recorded === "never"
111
+ ? "pi-daddy dashboard: startup installation prompt disabled. /grants dashboard remains available."
112
+ : "pi-daddy dashboard: not installed. /grants dashboard will report the manual setup when requested.",
113
+ "info",
114
+ );
115
+ return "deferred";
116
+ }
117
+
118
+ await linkDashboardPlugin(input.pluginRoot, exec);
119
+ const linked = await inspectDashboardPlugin(exec, input.pluginRoot);
120
+ if (linked.state !== "compatible") throw new Error(linked.diagnostic);
121
+ const opened = await openOrReuseDashboard({
122
+ exec,
123
+ host: host.host,
124
+ ledgerPath: input.ledgerPath ?? "",
125
+ cwd: input.cwd,
126
+ statePath: input.paneStatePath,
127
+ pluginRoot: input.pluginRoot,
128
+ allowInactive: true,
129
+ });
130
+ input.ui.notify(
131
+ `pi-daddy dashboard ${opened.kind} in pane ${opened.paneId} without changing focus.`,
132
+ "info",
133
+ );
134
+ return "installed";
135
+ } catch (error) {
136
+ input.ui.notify(`pi-daddy dashboard setup failed: ${error instanceof Error ? error.message : String(error)}`, "error");
137
+ return "failed";
138
+ }
139
+ }
140
+
141
+ export interface DashboardCommandInput {
142
+ env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
143
+ pid?: number;
144
+ exec?: HerdrExec;
145
+ cwd: string;
146
+ ledgerPath?: string;
147
+ pluginRoot: string;
148
+ paneStatePath: string;
149
+ }
150
+
151
+ export async function openDashboardCommand(input: DashboardCommandInput): Promise<DashboardOpenResult> {
152
+ const exec = input.exec ?? dashboardHerdrExec;
153
+ // Order is deliberate: a server elsewhere must not make an outside-pi command look hosted.
154
+ const host = await verifyHerdrHost({ env: input.env, pid: input.pid, exec });
155
+ if (!host.ok) throw new Error(host.diagnostic);
156
+ if (!input.ledgerPath?.trim()) {
157
+ throw new Error("no pi-daddy ledger is configured — set PI_GRANTS_LEDGER, then retry /grants dashboard");
158
+ }
159
+ const plugin = await inspectDashboardPlugin(exec, input.pluginRoot);
160
+ if (plugin.state === "absent") {
161
+ throw new Error(
162
+ `the pi-daddy Herdr plugin is not installed. Nothing was installed automatically. Run: ` +
163
+ `herdr plugin link ${quote(resolve(input.pluginRoot))} --enabled`,
164
+ );
165
+ }
166
+ if (plugin.state === "disabled") {
167
+ throw new Error(`the pi-daddy Herdr plugin is disabled. Run: herdr plugin enable ${DASHBOARD_PLUGIN_ID}`);
168
+ }
169
+ if (plugin.state !== "compatible") throw new Error(plugin.diagnostic);
170
+ return openOrReuseDashboard({
171
+ exec,
172
+ host: host.host,
173
+ ledgerPath: input.ledgerPath,
174
+ cwd: input.cwd,
175
+ statePath: input.paneStatePath,
176
+ pluginRoot: input.pluginRoot,
177
+ });
178
+ }
179
+
180
+ export function defaultDashboardPaths(agentDir: string): { preferencePath: string; paneStatePath: string } {
181
+ return {
182
+ preferencePath: dashboardPreferencePath(agentDir),
183
+ paneStatePath: dashboardPaneStatePath(agentDir),
184
+ };
185
+ }