pi-daddy 0.23.0 → 0.25.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 (96) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +20 -6
  3. package/contracts/ledger/v4/README.md +1 -1
  4. package/dist/cli.d.ts +3 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +49 -3
  7. package/dist/cli.js.map +1 -1
  8. package/dist/daily-dashboard-host.d.ts +123 -0
  9. package/dist/daily-dashboard-host.d.ts.map +1 -0
  10. package/dist/daily-dashboard-host.js +141 -0
  11. package/dist/daily-dashboard-host.js.map +1 -0
  12. package/dist/dashboard-cli.d.ts.map +1 -1
  13. package/dist/dashboard-cli.js +9 -2
  14. package/dist/dashboard-cli.js.map +1 -1
  15. package/dist/dashboard-handshake.d.ts.map +1 -1
  16. package/dist/dashboard-handshake.js +13 -4
  17. package/dist/dashboard-handshake.js.map +1 -1
  18. package/dist/dashboard-harness.d.ts +3 -0
  19. package/dist/dashboard-harness.d.ts.map +1 -1
  20. package/dist/dashboard-harness.js +15 -1
  21. package/dist/dashboard-harness.js.map +1 -1
  22. package/dist/dashboard-herdr.d.ts +1 -0
  23. package/dist/dashboard-herdr.d.ts.map +1 -1
  24. package/dist/dashboard-herdr.js +10 -8
  25. package/dist/dashboard-herdr.js.map +1 -1
  26. package/dist/dashboard-host-contract.d.ts +15 -1
  27. package/dist/dashboard-host-contract.d.ts.map +1 -1
  28. package/dist/dashboard-host-contract.js +1 -1
  29. package/dist/dashboard-host-contract.js.map +1 -1
  30. package/dist/dashboard-host-transport.d.ts +1 -0
  31. package/dist/dashboard-host-transport.d.ts.map +1 -1
  32. package/dist/dashboard-host-transport.js +5 -3
  33. package/dist/dashboard-host-transport.js.map +1 -1
  34. package/dist/dashboard-host.d.ts +62 -0
  35. package/dist/dashboard-host.d.ts.map +1 -1
  36. package/dist/dashboard-host.js +71 -6
  37. package/dist/dashboard-host.js.map +1 -1
  38. package/dist/dashboard-observation.d.ts.map +1 -1
  39. package/dist/dashboard-observation.js +6 -5
  40. package/dist/dashboard-observation.js.map +1 -1
  41. package/dist/experiment-store.d.ts +3 -1
  42. package/dist/experiment-store.d.ts.map +1 -1
  43. package/dist/experiment-store.js +20 -3
  44. package/dist/experiment-store.js.map +1 -1
  45. package/dist/experiment.d.ts.map +1 -1
  46. package/dist/experiment.js +9 -4
  47. package/dist/experiment.js.map +1 -1
  48. package/dist/index.d.ts +3 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +3 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/intent-application.d.ts.map +1 -1
  53. package/dist/intent-application.js +3 -2
  54. package/dist/intent-application.js.map +1 -1
  55. package/dist/measured-order.d.ts +249 -0
  56. package/dist/measured-order.d.ts.map +1 -0
  57. package/dist/measured-order.js +251 -0
  58. package/dist/measured-order.js.map +1 -0
  59. package/dist/measured-session.d.ts +97 -0
  60. package/dist/measured-session.d.ts.map +1 -0
  61. package/dist/measured-session.js +134 -0
  62. package/dist/measured-session.js.map +1 -0
  63. package/dist/work-command.d.ts +32 -0
  64. package/dist/work-command.d.ts.map +1 -0
  65. package/dist/work-command.js +161 -0
  66. package/dist/work-command.js.map +1 -0
  67. package/extensions/daily-dashboard-session.ts +31 -0
  68. package/extensions/daily-work-session.ts +20 -0
  69. package/extensions/delegation.ts +21 -8
  70. package/extensions/execute-child.ts +10 -1
  71. package/extensions/executor-session.ts +9 -0
  72. package/extensions/grants-command.ts +16 -15
  73. package/extensions/grants-connected-command.ts +19 -0
  74. package/extensions/grants.ts +25 -3
  75. package/extensions/primary-shadow.ts +11 -0
  76. package/extensions/run-delegation.ts +2 -0
  77. package/extensions/session.ts +7 -19
  78. package/extensions/work-runtime.ts +44 -0
  79. package/package.json +13 -1
  80. package/src/cli.ts +41 -4
  81. package/src/daily-dashboard-host.ts +88 -0
  82. package/src/dashboard-cli.ts +7 -2
  83. package/src/dashboard-handshake.ts +14 -5
  84. package/src/dashboard-harness.ts +10 -1
  85. package/src/dashboard-herdr.ts +12 -9
  86. package/src/dashboard-host-contract.ts +8 -2
  87. package/src/dashboard-host-transport.ts +8 -6
  88. package/src/dashboard-host.ts +37 -9
  89. package/src/dashboard-observation.ts +4 -2
  90. package/src/experiment-store.ts +11 -3
  91. package/src/experiment.ts +8 -3
  92. package/src/index.ts +17 -0
  93. package/src/intent-application.ts +3 -2
  94. package/src/measured-order.ts +67 -0
  95. package/src/measured-session.ts +70 -0
  96. package/src/work-command.ts +185 -0
@@ -0,0 +1,185 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, isAbsolute, join, resolve } from "node:path";
4
+ import {
5
+ appendWorkLedgerEventOnce,
6
+ buildWorkOccurrenceEvent,
7
+ buildWorkRevisionEvent,
8
+ buildWorkSnapshotEvent,
9
+ parseWorkLedgerText,
10
+ projectWorkLedger,
11
+ type EventRef,
12
+ type RevisionRef,
13
+ type WorkFrozen,
14
+ type WorkOccurrencePayload,
15
+ type WorkProjectionContext,
16
+ type WorkRevision,
17
+ } from "./work-ledger.ts";
18
+ import { withFileLock } from "./file-lock.ts";
19
+ import { resolveWorkSnapshotText } from "./work-ledger-snapshot.ts";
20
+
21
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._:/@+\-]{0,127}$/;
22
+ const DIGEST = /^[a-f0-9]{64}$/;
23
+ const sha256 = (value: string): string => createHash("sha256").update(value).digest("hex");
24
+ const eventRef = (event: { eventId: string; digest: string }): EventRef => ({ eventId: event.eventId, digest: event.digest });
25
+ const revisionRef = (event: { payload: { revision: { kind: WorkRevision["kind"]; id: string; revision: number; digest: string } } }): RevisionRef => {
26
+ const { kind, id, revision, digest } = event.payload.revision;
27
+ return { kind, id, revision, digest };
28
+ };
29
+
30
+ export interface DeclaredWorkState {
31
+ readonly version: "pi-daddy-declared-work-v1";
32
+ readonly id: string;
33
+ readonly outcomeDigest: string;
34
+ readonly ledgerPath: string;
35
+ readonly statePath: string;
36
+ readonly grantLedgerPath: string | null;
37
+ readonly selectedSnapshot: NonNullable<WorkProjectionContext["selectedSnapshot"]>;
38
+ readonly scope: RevisionRef;
39
+ readonly intent: RevisionRef;
40
+ readonly obligation: RevisionRef;
41
+ readonly policy: RevisionRef;
42
+ }
43
+
44
+ interface StoredDeclaredWorkState extends Omit<DeclaredWorkState, "statePath"> {}
45
+
46
+ export interface DeclareWorkInput {
47
+ cwd: string;
48
+ id: string;
49
+ outcome: string;
50
+ }
51
+
52
+ function paths(input: DeclareWorkInput): { ledgerPath: string; statePath: string; grantLedgerPath: null } {
53
+ const root = resolve(input.cwd);
54
+ return { ledgerPath: join(root, ".pi", "work.jsonl"), statePath: join(root, ".pi", "work-current.json"), grantLedgerPath: null };
55
+ }
56
+
57
+ const exactKeys = (value: object, expected: readonly string[]): boolean => {
58
+ const actual = Object.keys(value).sort();
59
+ return actual.length === expected.length && actual.every((key, index) => key === [...expected].sort()[index]);
60
+ };
61
+ const validRef = (value: unknown, kind: WorkRevision["kind"]): value is RevisionRef => {
62
+ if (!value || typeof value !== "object" || !exactKeys(value, ["kind", "id", "revision", "digest"])) return false;
63
+ const ref = value as RevisionRef;
64
+ return ref.kind === kind && ID.test(ref.id) && Number.isSafeInteger(ref.revision) && ref.revision > 0 && DIGEST.test(ref.digest);
65
+ };
66
+
67
+ export async function loadDeclaredWork(statePath: string): Promise<WorkFrozen<DeclaredWorkState> | null> {
68
+ let text: string;
69
+ try { text = await readFile(statePath, "utf8"); }
70
+ catch (error) { if ((error as { code?: string }).code === "ENOENT") return null; throw error; }
71
+ try {
72
+ const value = JSON.parse(text) as StoredDeclaredWorkState;
73
+ if (!value || typeof value !== "object" || !exactKeys(value, [
74
+ "version", "id", "outcomeDigest", "ledgerPath", "grantLedgerPath", "selectedSnapshot", "scope", "intent", "obligation", "policy",
75
+ ]) || value.version !== "pi-daddy-declared-work-v1" || !ID.test(value.id) || !DIGEST.test(value.outcomeDigest) ||
76
+ typeof value.ledgerPath !== "string" || !isAbsolute(value.ledgerPath) ||
77
+ (value.grantLedgerPath !== null && (typeof value.grantLedgerPath !== "string" || !isAbsolute(value.grantLedgerPath))) ||
78
+ !validRef(value.scope, "scope") || !validRef(value.intent, "goal") || !validRef(value.obligation, "obligation") || !validRef(value.policy, "policy") ||
79
+ !value.selectedSnapshot || typeof value.selectedSnapshot !== "object" ||
80
+ !exactKeys(value.selectedSnapshot, ["snapshot", "event"]) ||
81
+ !exactKeys(value.selectedSnapshot.snapshot, ["id", "digest"]) || !ID.test(value.selectedSnapshot.snapshot.id) || !DIGEST.test(value.selectedSnapshot.snapshot.digest) ||
82
+ !exactKeys(value.selectedSnapshot.event, ["eventId", "digest"]) || !ID.test(value.selectedSnapshot.event.eventId) || !DIGEST.test(value.selectedSnapshot.event.digest)) {
83
+ throw new Error("shape");
84
+ }
85
+ const expectedLedger = resolve(dirname(statePath), "work.jsonl");
86
+ if (resolve(value.ledgerPath) !== expectedLedger) throw new Error("relocated");
87
+ const ledger = await readFile(value.ledgerPath, "utf8");
88
+ const ingestion = parseWorkLedgerText(ledger);
89
+ const projection = projectWorkLedger(ledger, { selectedSnapshot: value.selectedSnapshot, authority: null });
90
+ const obligation = projection.obligations.find(item => item.binding.obligation.digest === value.obligation.digest);
91
+ if (!ingestion.complete || projection.scopeState !== "valid" || !obligation || projection.selectedSnapshot?.digest !== value.selectedSnapshot.snapshot.digest) throw new Error("unbound");
92
+ return Object.freeze({ ...value, statePath: resolve(statePath) });
93
+ } catch {
94
+ throw new Error("invalid declared work state; no task was selected");
95
+ }
96
+ }
97
+
98
+ export async function declareWork(input: DeclareWorkInput): Promise<WorkFrozen<DeclaredWorkState>> {
99
+ if (!input || typeof input !== "object" || Object.keys(input).some(key => !["cwd", "id", "outcome"].includes(key))) {
100
+ throw new TypeError("unsupported work declaration field");
101
+ }
102
+ if (!ID.test(input.id) || typeof input.outcome !== "string" || !input.outcome.trim()) throw new TypeError("work declaration requires an identifier and non-empty outcome");
103
+ const resolved = paths(input), outcomeDigest = sha256(input.outcome.trim());
104
+ await mkdir(dirname(resolved.ledgerPath), { recursive: true, mode: 0o700 });
105
+ await mkdir(dirname(resolved.statePath), { recursive: true, mode: 0o700 });
106
+ return withFileLock(resolved.statePath, "declared work", async () => {
107
+ const existing = await loadDeclaredWork(resolved.statePath);
108
+ if (existing) {
109
+ if (existing.id === input.id && existing.outcomeDigest === outcomeDigest) return existing;
110
+ throw new Error(`declared work id ${input.id} already names a different outcome`);
111
+ }
112
+ const pendingPath = `${resolved.statePath}.pending`;
113
+ let prepared: { version: "pi-daddy-work-preparation-v1"; id: string; outcomeDigest: string; createdAt: string };
114
+ try {
115
+ prepared = JSON.parse(await readFile(pendingPath, "utf8"));
116
+ if (!prepared || Object.keys(prepared).sort().join() !== "createdAt,id,outcomeDigest,version" || prepared.version !== "pi-daddy-work-preparation-v1" || prepared.id !== input.id || prepared.outcomeDigest !== outcomeDigest || Number.isNaN(Date.parse(prepared.createdAt))) throw new Error("invalid");
117
+ } catch (error) {
118
+ if ((error as { code?: string }).code !== "ENOENT") throw new Error("another or invalid work declaration is pending; no events were replaced");
119
+ prepared = { version: "pi-daddy-work-preparation-v1", id: input.id, outcomeDigest, createdAt: new Date().toISOString() };
120
+ await writeFile(pendingPath, `${JSON.stringify(prepared)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
121
+ }
122
+ const now = new Date(prepared.createdAt);
123
+ const revision = (kind: WorkRevision["kind"], name: string, patch: Partial<Omit<WorkRevision, "digest">> = {}) =>
124
+ buildWorkRevisionEvent({ eventId: `work:${input.id}:${name}:1`, now, revision: {
125
+ kind, id: `work:${input.id}:${name}`, revision: 1, scopeId: `work:${input.id}:scope`, predecessor: null,
126
+ contentDigest: sha256(`${name}\0${outcomeDigest}`), parent: null, dependencies: [], ownerId: "local-operator",
127
+ permittedEffects: [], policy: null, ...patch,
128
+ } });
129
+ const scope = revision("scope", "scope"), policy = revision("policy", "policy");
130
+ const intent = revision("goal", "goal", { parent: revisionRef(scope) });
131
+ const obligation = revision("obligation", "obligation", { parent: revisionRef(intent), policy: revisionRef(policy) });
132
+ const binding = { intent: revisionRef(intent), obligation: revisionRef(obligation), artifact: null, policy: revisionRef(policy) };
133
+ const snapshot = buildWorkSnapshotEvent({ eventId: `work:${input.id}:snapshot:1`, now, snapshot: {
134
+ snapshotId: `work:${input.id}:selected`, scope: revisionRef(scope), revisions: [revisionRef(policy), revisionRef(intent), revisionRef(obligation)], bindings: [binding],
135
+ } });
136
+ for (const event of [scope, policy, intent, obligation, snapshot]) await appendWorkLedgerEventOnce({ path: resolved.ledgerPath, grantLedgerPath: resolved.grantLedgerPath }, event);
137
+ const stored: StoredDeclaredWorkState = {
138
+ version: "pi-daddy-declared-work-v1", id: input.id, outcomeDigest, ledgerPath: resolved.ledgerPath, grantLedgerPath: resolved.grantLedgerPath,
139
+ selectedSnapshot: { snapshot: { id: snapshot.payload.snapshot.snapshotId, digest: snapshot.payload.snapshot.digest }, event: eventRef(snapshot) },
140
+ scope: revisionRef(scope), intent: revisionRef(intent), obligation: revisionRef(obligation), policy: revisionRef(policy),
141
+ };
142
+ const temporary = `${resolved.statePath}.${process.pid}.${randomUUID()}.tmp`;
143
+ try {
144
+ await writeFile(temporary, `${JSON.stringify(stored, null, 2)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
145
+ await rename(temporary, resolved.statePath);
146
+ } finally { await rm(temporary, { force: true }); }
147
+ await rm(pendingPath);
148
+ return Object.freeze({ ...stored, statePath: resolved.statePath });
149
+ });
150
+ }
151
+
152
+ /** Persist an already-applied exact selection so the next governed dispatch cannot keep using the old cached declaration. */
153
+ export async function rebindDeclaredWork(state:WorkFrozen<DeclaredWorkState>,selectedSnapshot:NonNullable<WorkProjectionContext["selectedSnapshot"]>):Promise<WorkFrozen<DeclaredWorkState>>{
154
+ return withFileLock(state.statePath,"declared work rebind",async()=>{const current=await loadDeclaredWork(state.statePath);if(!current||JSON.stringify(current.selectedSnapshot)!==JSON.stringify(state.selectedSnapshot))throw Error("declared work rebind is stale; new dispatch remains unsupported");const text=await readFile(state.ledgerPath,"utf8"),resolved=resolveWorkSnapshotText(text,selectedSnapshot),binding=resolved.snapshot?.bindings[0];if(resolved.scopeState!=="valid"||!resolved.scope||!resolved.snapshot||!binding)throw Error("applied declared selection cannot bind future dispatch");const stored:StoredDeclaredWorkState={version:"pi-daddy-declared-work-v1",id:state.id,outcomeDigest:state.outcomeDigest,ledgerPath:state.ledgerPath,grantLedgerPath:state.grantLedgerPath,selectedSnapshot,scope:{kind:resolved.scope.kind,id:resolved.scope.id,revision:resolved.scope.revision,digest:resolved.scope.digest},intent:binding.intent,obligation:binding.obligation,policy:binding.policy};const temporary=`${state.statePath}.${process.pid}.${randomUUID()}.tmp`;try{await writeFile(temporary,`${JSON.stringify(stored,null,2)}\n`,{encoding:"utf8",mode:0o600,flag:"wx"});await rename(temporary,state.statePath);}finally{await rm(temporary,{force:true});}return Object.freeze({...stored,statePath:state.statePath});});
155
+ }
156
+
157
+ export type DeclaredOccurrenceIdentity = Pick<WorkOccurrencePayload["labels"], "toolCallId" | "taskId" | "workspaceId" | "definitionDigest" | "configurationDigest" | "modelId" | "effortId"> & {
158
+ executionId: string;
159
+ parentExecutionId: string | null;
160
+ childId: string | null;
161
+ variantId: string | null;
162
+ now: Date;
163
+ };
164
+
165
+ export async function appendDeclaredWorkOccurrence(state: WorkFrozen<DeclaredWorkState>, identity: DeclaredOccurrenceIdentity,
166
+ occurrenceState: WorkOccurrencePayload["state"]): Promise<void> {
167
+ const { executionId, parentExecutionId, childId, variantId, now, ...rawLabels } = identity;
168
+ const label = (name: string, value: string | null): string | null => value === null ? null : ID.test(value) ? value : `${name}:${sha256(value)}`;
169
+ const labels = {
170
+ toolCallId: label("toolcall", rawLabels.toolCallId), taskId: label("task", rawLabels.taskId),
171
+ workspaceId: label("workspace", rawLabels.workspaceId), definitionDigest: rawLabels.definitionDigest,
172
+ configurationDigest: rawLabels.configurationDigest, modelId: label("model", rawLabels.modelId), effortId: label("effort", rawLabels.effortId),
173
+ };
174
+ const event = buildWorkOccurrenceEvent({
175
+ eventId: `work:${state.id}:attempt:${identity.executionId}:${occurrenceState}`,
176
+ now,
177
+ payload: {
178
+ scope: state.scope, obligation: state.obligation, executionId,
179
+ parentExecutionId, childId, variantId: label("variant", variantId), artifact: null,
180
+ provenance: "observed", state: occurrenceState,
181
+ labels: { sessionId: null, branchLeafId: null, ...labels },
182
+ },
183
+ });
184
+ await appendWorkLedgerEventOnce({ path: state.ledgerPath, grantLedgerPath: state.grantLedgerPath }, event);
185
+ }