pi-daddy 0.26.1 → 0.27.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 +35 -0
- package/PRODUCT-GUIDE.md +127 -0
- package/README.md +23 -2
- package/REQUIREMENTS.md +41 -0
- package/dist/catalog.d.ts +1 -7
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +8 -40
- package/dist/catalog.js.map +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +44 -14
- package/dist/cli.js.map +1 -1
- package/dist/daily-dashboard-host.d.ts +14 -1
- package/dist/daily-dashboard-host.d.ts.map +1 -1
- package/dist/daily-dashboard-host.js +40 -17
- package/dist/daily-dashboard-host.js.map +1 -1
- package/dist/daily-panel.d.ts +36 -0
- package/dist/daily-panel.d.ts.map +1 -0
- package/dist/daily-panel.js +84 -0
- package/dist/daily-panel.js.map +1 -0
- package/dist/daily-view-render.d.ts +2 -1
- package/dist/daily-view-render.d.ts.map +1 -1
- package/dist/daily-view-render.js +2 -1
- package/dist/daily-view-render.js.map +1 -1
- package/dist/daily-view.d.ts +3 -0
- package/dist/daily-view.d.ts.map +1 -1
- package/dist/daily-view.js +6 -0
- package/dist/daily-view.js.map +1 -1
- package/dist/dashboard-cli.d.ts +2 -0
- package/dist/dashboard-cli.d.ts.map +1 -1
- package/dist/dashboard-cli.js +37 -15
- package/dist/dashboard-cli.js.map +1 -1
- package/dist/dashboard-host-contract.d.ts +2 -0
- package/dist/dashboard-host-contract.d.ts.map +1 -1
- package/dist/dashboard-host-contract.js.map +1 -1
- package/dist/dashboard-host-transport.d.ts.map +1 -1
- package/dist/dashboard-host-transport.js +6 -3
- package/dist/dashboard-host-transport.js.map +1 -1
- package/dist/dashboard-host.d.ts +30 -2
- package/dist/dashboard-host.d.ts.map +1 -1
- package/dist/dashboard-host.js +73 -11
- package/dist/dashboard-host.js.map +1 -1
- package/dist/dashboard-menu.d.ts +16 -0
- package/dist/dashboard-menu.d.ts.map +1 -0
- package/dist/dashboard-menu.js +33 -0
- package/dist/dashboard-menu.js.map +1 -0
- package/dist/definitions.d.ts +3 -8
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +19 -43
- package/dist/definitions.js.map +1 -1
- package/dist/factory-order.d.ts +10 -10
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +7 -19
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +9 -20
- package/dist/init.js.map +1 -1
- package/dist/learning-connection.d.ts +127 -0
- package/dist/learning-connection.d.ts.map +1 -0
- package/dist/learning-connection.js +54 -0
- package/dist/learning-connection.js.map +1 -0
- package/dist/model-preflight.d.ts +5 -0
- package/dist/model-preflight.d.ts.map +1 -1
- package/dist/model-preflight.js +9 -0
- package/dist/model-preflight.js.map +1 -1
- package/dist/product-files.d.ts +6 -0
- package/dist/product-files.d.ts.map +1 -0
- package/dist/product-files.js +55 -0
- package/dist/product-files.js.map +1 -0
- package/dist/skill-packages.d.ts +6 -24
- package/dist/skill-packages.d.ts.map +1 -1
- package/dist/skill-packages.js +74 -33
- package/dist/skill-packages.js.map +1 -1
- package/dist/skill-resources.d.ts +21 -0
- package/dist/skill-resources.d.ts.map +1 -0
- package/dist/skill-resources.js +52 -0
- package/dist/skill-resources.js.map +1 -0
- package/dist/work-command.d.ts.map +1 -1
- package/dist/work-command.js +14 -6
- package/dist/work-command.js.map +1 -1
- package/dist/work-policy-registry.d.ts +133 -0
- package/dist/work-policy-registry.d.ts.map +1 -0
- package/dist/work-policy-registry.js +128 -0
- package/dist/work-policy-registry.js.map +1 -0
- package/dist/work-run.d.ts +56 -0
- package/dist/work-run.d.ts.map +1 -0
- package/dist/work-run.js +102 -0
- package/dist/work-run.js.map +1 -0
- package/dist/work-setup.d.ts +40 -0
- package/dist/work-setup.d.ts.map +1 -0
- package/dist/work-setup.js +174 -0
- package/dist/work-setup.js.map +1 -0
- package/extensions/daily-dashboard-session.ts +16 -7
- package/extensions/execute-child.ts +2 -2
- package/extensions/grants-command.ts +2 -2
- package/extensions/grants.ts +6 -2
- package/extensions/init-command.ts +3 -2
- package/extensions/learning-outcome-session.ts +38 -0
- package/extensions/learning-session.ts +59 -0
- package/extensions/ordinary-runtime.ts +2 -2
- package/extensions/run-delegation.ts +2 -2
- package/extensions/work-policy-session.ts +139 -0
- package/extensions/work-run-session.ts +75 -0
- package/extensions/work-runtime.ts +2 -2
- package/extensions/work-session.ts +132 -0
- package/package.json +9 -1
- package/src/catalog.ts +7 -37
- package/src/cli.ts +34 -14
- package/src/daily-dashboard-host.ts +32 -15
- package/src/daily-panel.ts +80 -0
- package/src/daily-view-render.ts +2 -1
- package/src/daily-view.ts +8 -0
- package/src/dashboard-cli.ts +29 -17
- package/src/dashboard-host-contract.ts +2 -0
- package/src/dashboard-host-transport.ts +7 -4
- package/src/dashboard-host.ts +48 -13
- package/src/dashboard-menu.ts +28 -0
- package/src/definitions.ts +16 -44
- package/src/index.ts +7 -1
- package/src/init.ts +10 -20
- package/src/learning-connection.ts +70 -0
- package/src/model-preflight.ts +9 -0
- package/src/product-files.ts +34 -0
- package/src/skill-packages.ts +59 -34
- package/src/skill-resources.ts +48 -0
- package/src/work-command.ts +11 -6
- package/src/work-policy-registry.ts +81 -0
- package/src/work-run.ts +79 -0
- package/src/work-setup.ts +141 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { appendWorkLedgerEventOnce, buildWorkRevisionEvent, buildWorkSnapshotEvent, type RevisionRef, type WorkRevision, type WorkRevisionEvent, type WorkSnapshotEvent, type WorkFrozen } from "./work-ledger.ts";
|
|
5
|
+
import { resolveWorkSnapshotText } from "./work-ledger-snapshot.ts";
|
|
6
|
+
import { loadDeclaredWork, type DeclaredWorkState } from "./work-command.ts";
|
|
7
|
+
import { withFileLock } from "./file-lock.ts";
|
|
8
|
+
import { intentKey, type IntentSelection } from "./intent-control.ts";
|
|
9
|
+
import { privateDirectory, projectProductDirectory, readProductJson, writeProductJson } from "./product-files.ts";
|
|
10
|
+
import type { WorkPresentation } from "./daily-panel.ts";
|
|
11
|
+
|
|
12
|
+
export const WORK_EFFORTS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
13
|
+
export interface WorkTaskSetup { id: string; outcome: string; agent: string | null; model: string; thinking: typeof WORK_EFFORTS[number]; dependencies: string[] }
|
|
14
|
+
export interface WorkSetup { version: "work-setup-v1"; id: string; outcome: string; maxParallel: number; tasks: WorkTaskSetup[] }
|
|
15
|
+
export interface RecordedWorkSetup { version: "recorded-work-setup-v1"; setup: WorkSetup; state: DeclaredWorkState; tasks: { id: string; obligation: RevisionRef }[]; events: WorkFrozen<WorkRevisionEvent | WorkSnapshotEvent>[] }
|
|
16
|
+
const hash = (value: string) => createHash("sha256").update(value).digest("hex");
|
|
17
|
+
const keys = (value: object, expected: string[]) => { if (!value || typeof value !== "object" || Object.keys(value).sort().join() !== expected.sort().join()) throw Error("unsupported work setup fields"); };
|
|
18
|
+
const id = (value: unknown) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/.test(value);
|
|
19
|
+
const text = (value: unknown) => typeof value === "string" && value.trim().length > 0 && Buffer.byteLength(value) <= 8192;
|
|
20
|
+
const ref = (r: Pick<WorkRevision, "kind" | "id" | "revision" | "digest">): RevisionRef => ({ kind: r.kind, id: r.id, revision: r.revision, digest: r.digest });
|
|
21
|
+
export function workSetup(input: WorkSetup): WorkSetup {
|
|
22
|
+
const s = JSON.parse(JSON.stringify(input)) as WorkSetup;
|
|
23
|
+
keys(s, ["version", "id", "outcome", "maxParallel", "tasks"]);
|
|
24
|
+
if (s.version !== "work-setup-v1" || !id(s.id) || !text(s.outcome) || !Number.isInteger(s.maxParallel) || s.maxParallel < 1 || s.maxParallel > 8 || !Array.isArray(s.tasks) || !s.tasks.length || s.tasks.length > 8) throw Error("work setup requires 1–8 tasks and parallel limit 1–8");
|
|
25
|
+
const seen = new Set<string>();
|
|
26
|
+
for (const task of s.tasks) {
|
|
27
|
+
keys(task, ["id", "outcome", "agent", "model", "thinking", "dependencies"]);
|
|
28
|
+
if (!id(task.id) || seen.has(task.id) || !text(task.outcome) || !(task.agent === null || typeof task.agent === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(task.agent)) || typeof task.model !== "string" || !/^[A-Za-z0-9._-]+\/[A-Za-z0-9._:/@+-]+$/.test(task.model) || task.model.length > 128 || !WORK_EFFORTS.includes(task.thinking) || !Array.isArray(task.dependencies) || new Set(task.dependencies).size !== task.dependencies.length || task.dependencies.some(d => !id(d) || d === task.id)) throw Error("invalid task, model/effort or dependency; no profile fallback");
|
|
29
|
+
seen.add(task.id);
|
|
30
|
+
}
|
|
31
|
+
const done = new Set<string>();
|
|
32
|
+
while (done.size < s.tasks.length) {
|
|
33
|
+
const eligible = s.tasks.filter(t => !done.has(t.id) && t.dependencies.every(d => done.has(d)));
|
|
34
|
+
if (!eligible.length) throw Error("dependencies contain a cycle or missing task");
|
|
35
|
+
eligible.forEach(t => done.add(t.id));
|
|
36
|
+
}
|
|
37
|
+
return s;
|
|
38
|
+
}
|
|
39
|
+
const setupPath = (state: DeclaredWorkState) => join(dirname(state.statePath), "work-setups", `${state.selectedSnapshot.snapshot.digest}.json`);
|
|
40
|
+
|
|
41
|
+
/** Author first, append through P01, then select separately at an explicit owner boundary. */
|
|
42
|
+
export async function recordWorkSetup(cwd: string, value: WorkSetup, mode: "new" | "revise" | "alternative" = "new", expected: DeclaredWorkState | null = null): Promise<RecordedWorkSetup> {
|
|
43
|
+
const setup = workSetup(value), root = join(resolve(cwd), ".pi"), statePath = join(root, "work-current.json");
|
|
44
|
+
await projectProductDirectory(root); await privateDirectory(join(root, "work-setups"));
|
|
45
|
+
return withFileLock(statePath, "work setup", async () => {
|
|
46
|
+
const current = await loadDeclaredWork(statePath);
|
|
47
|
+
if (intentKey(current?.selectedSnapshot ?? null) !== intentKey(expected?.selectedSnapshot ?? null)) throw Error("work selection changed; reopen setup");
|
|
48
|
+
if (mode !== "new" && !current) throw Error("select existing work before recording a revision");
|
|
49
|
+
const original = mode !== "new" ? await loadWorkSetup(current!) : null;
|
|
50
|
+
if (mode !== "new" && (!original || original.setup.tasks.length !== setup.tasks.length || original.setup.tasks.some(t => !setup.tasks.some(n => n.id === t.id && intentKey([...n.dependencies].sort()) === intentKey([...t.dependencies].sort()))))) throw Error("topology changes require a new work setup; no live entity expansion");
|
|
51
|
+
const revisions = current && mode !== "new" ? resolveWorkSnapshotText(await readFile(current.ledgerPath, "utf8"), current.selectedSnapshot) : null;
|
|
52
|
+
const token = hash(intentKey({ setup, mode, from: mode === "new" ? null : current?.selectedSnapshot })).slice(0, 24), prefix = mode === "new" ? `work:${setup.id}:${token.slice(0, 8)}` : current!.scope.id.replace(/:scope$/, "");
|
|
53
|
+
// Preparation is durable before the first append, so an interrupted exact retry retains timestamps/identities.
|
|
54
|
+
const preparedPath = join(root, "work-setups", `pending-${token}.json`);
|
|
55
|
+
const prepared = await readProductJson(preparedPath) as { now: string } | null;
|
|
56
|
+
if (prepared && (Object.keys(prepared).join() !== "now" || !Number.isFinite(Date.parse(prepared.now)))) throw Error("invalid retained setup preparation");
|
|
57
|
+
const now = new Date(prepared?.now ?? Date.now());
|
|
58
|
+
if (!prepared) await writeProductJson(preparedPath, { now: now.toISOString() });
|
|
59
|
+
const events: WorkFrozen<WorkRevisionEvent | WorkSnapshotEvent>[] = [];
|
|
60
|
+
const build = (kind: WorkRevision["kind"], entityId: string, content: unknown, parent: RevisionRef | null, dependencies: RevisionRef[] = [], policy: RevisionRef | null = null): RevisionRef => {
|
|
61
|
+
const prior = revisions && [revisions.scope!, ...revisions.revisions].find(r => r.kind === kind && r.id === entityId);
|
|
62
|
+
const event = buildWorkRevisionEvent({ eventId: `${prefix}:${token}:${kind}:${events.length}`, now, revision: { kind, id: entityId, revision: prior ? prior.revision + 1 : 1, predecessor: prior ? ref(prior) : null, scopeId: `${prefix}:scope`, contentDigest: hash(intentKey(content)), parent, dependencies, ownerId: prior?.ownerId ?? "local-operator", permittedEffects: prior ? [...prior.permittedEffects] : [], policy } });
|
|
63
|
+
events.push(event); return ref(event.payload.revision);
|
|
64
|
+
};
|
|
65
|
+
const scope = mode === "alternative" ? current!.scope : build("scope", `${prefix}:scope`, setup, null);
|
|
66
|
+
const policy = mode === "new" ? build("policy", `${prefix}:policy`, { acceptance: "independent-review-required" }, null) : current!.policy;
|
|
67
|
+
const intent = build("goal", mode === "new" ? `${prefix}:goal` : current!.intent.id, setup, scope);
|
|
68
|
+
const tasks: RecordedWorkSetup["tasks"] = [];
|
|
69
|
+
while (tasks.length < setup.tasks.length) {
|
|
70
|
+
for (const task of setup.tasks) {
|
|
71
|
+
if (tasks.some(t => t.id === task.id) || !task.dependencies.every(d => tasks.some(t => t.id === d))) continue;
|
|
72
|
+
const entityId = original?.tasks.find(t => t.id === task.id)?.obligation.id ?? `${prefix}:${task.id}`;
|
|
73
|
+
const obligation = build("obligation", entityId, task, intent, task.dependencies.map(d => tasks.find(t => t.id === d)!.obligation), policy);
|
|
74
|
+
tasks.push({ id: task.id, obligation });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const ordered = setup.tasks.map(t => tasks.find(x => x.id === t.id)!);
|
|
78
|
+
const snapshot = buildWorkSnapshotEvent({ eventId: `${prefix}:${token}:snapshot`, now, snapshot: { snapshotId: `${prefix}:${token}:selected`, scope, revisions: [policy, intent, ...ordered.map(t => t.obligation)], bindings: ordered.map(t => ({ intent, obligation: t.obligation, artifact: null, policy })) } });
|
|
79
|
+
events.push(snapshot);
|
|
80
|
+
const state: DeclaredWorkState = { version: "pi-daddy-declared-work-v1", id: setup.id, outcomeDigest: hash(setup.outcome.trim()), ledgerPath: join(root, "work.jsonl"), statePath, grantLedgerPath: null, selectedSnapshot: { snapshot: { id: snapshot.payload.snapshot.snapshotId, digest: snapshot.payload.snapshot.digest }, event: { eventId: snapshot.eventId, digest: snapshot.digest } }, scope, intent, obligation: ordered[0].obligation, policy };
|
|
81
|
+
const result: RecordedWorkSetup = { version: "recorded-work-setup-v1", setup, state, tasks: ordered, events };
|
|
82
|
+
const path = setupPath(state), previous = await readProductJson(path);
|
|
83
|
+
if (previous && intentKey(previous) !== intentKey(result)) throw Error("recorded setup identity conflict");
|
|
84
|
+
if (!previous) await writeProductJson(path, result);
|
|
85
|
+
for (const event of events) await appendWorkLedgerEventOnce({ path: state.ledgerPath, grantLedgerPath: null }, event);
|
|
86
|
+
await loadWorkSetup(state); return result;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function loadWorkSetup(state: DeclaredWorkState): Promise<RecordedWorkSetup | null> {
|
|
91
|
+
const value = await readProductJson(setupPath(state)) as RecordedWorkSetup | null;
|
|
92
|
+
if (!value) return null;
|
|
93
|
+
keys(value, ["version", "setup", "state", "tasks", "events"]);
|
|
94
|
+
const setup = workSetup(value.setup);
|
|
95
|
+
if (value.version !== "recorded-work-setup-v1" || intentKey(value.state.selectedSnapshot) !== intentKey(state.selectedSnapshot) || value.state.ledgerPath !== state.ledgerPath || value.state.statePath !== state.statePath || hash(setup.outcome.trim()) !== value.state.outcomeDigest || !Array.isArray(value.tasks) || value.tasks.length !== setup.tasks.length) throw Error("work setup selection mismatch");
|
|
96
|
+
const resolved = resolveWorkSnapshotText(await readFile(state.ledgerPath, "utf8"), state.selectedSnapshot);
|
|
97
|
+
if (resolved.scopeState !== "valid" || !resolved.snapshot || hash(intentKey(setup)) !== resolved.revisions.find(r => r.digest === value.state.intent.digest)?.contentDigest) throw Error("recorded work labels are stale or mismatched");
|
|
98
|
+
if (intentKey(resolved.snapshot.scope) !== intentKey(value.state.scope) || !resolved.snapshot.bindings.some(b => intentKey(b.intent) === intentKey(value.state.intent) && intentKey(b.policy) === intentKey(value.state.policy) && intentKey(b.obligation) === intentKey(state.obligation))) throw Error("work state is not an exact selected binding");
|
|
99
|
+
for (const task of setup.tasks) {
|
|
100
|
+
const binding = value.tasks.find(t => t.id === task.id), revision = resolved.revisions.find(r => r.digest === binding?.obligation.digest);
|
|
101
|
+
if (!binding || !revision || revision.contentDigest !== hash(intentKey(task)) || !resolved.snapshot.bindings.some(b => intentKey(b.obligation) === intentKey(binding.obligation))) throw Error("task configuration is not bound to selected work");
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
/** Caller must own a quiescent session, with no running daily host. No active order is rebound. */
|
|
106
|
+
export async function selectRecordedWork(recorded: RecordedWorkSetup, expected: IntentSelection | null): Promise<DeclaredWorkState> {
|
|
107
|
+
const state = recorded.state;
|
|
108
|
+
return withFileLock(state.statePath, "select work", async () => {
|
|
109
|
+
const current = await loadDeclaredWork(state.statePath);
|
|
110
|
+
if (intentKey(current?.selectedSnapshot ?? null) !== intentKey(expected)) throw Error("selection changed; no work selected");
|
|
111
|
+
if (!await loadWorkSetup(state)) throw Error("recorded setup missing");
|
|
112
|
+
const { statePath: _path, ...stored } = state;
|
|
113
|
+
await writeProductJson(state.statePath, stored, true); return state;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
export async function listWorkSetups(cwd: string): Promise<RecordedWorkSetup[]> {
|
|
117
|
+
const root = join(resolve(cwd), ".pi", "work-setups");
|
|
118
|
+
let files: string[]; try { files = await readdir(root); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return []; throw error; }
|
|
119
|
+
if (files.length > 256) throw Error("work setup history bound exceeded");
|
|
120
|
+
const results: RecordedWorkSetup[] = [];
|
|
121
|
+
for (const file of files.filter(f => /^[a-f0-9]{64}\.json$/.test(f)).sort()) {
|
|
122
|
+
const value = await readProductJson(join(root, file)) as RecordedWorkSetup;
|
|
123
|
+
if (value?.state?.statePath !== join(resolve(cwd), ".pi", "work-current.json") || file !== `${value.state.selectedSnapshot.snapshot.digest}.json`) throw Error("invalid work setup history");
|
|
124
|
+
results.push((await loadWorkSetup(value.state))!);
|
|
125
|
+
}
|
|
126
|
+
return results;
|
|
127
|
+
}
|
|
128
|
+
export async function workSetupForSelection(cwd: string, selection: IntentSelection): Promise<RecordedWorkSetup | null> {
|
|
129
|
+
const path=join(resolve(cwd),".pi","work-setups",`${selection.snapshot.digest}.json`),value=await readProductJson(path) as RecordedWorkSetup|null;
|
|
130
|
+
if(!value)return null;
|
|
131
|
+
if(value.state?.statePath!==join(resolve(cwd),".pi","work-current.json")||intentKey(value.state.selectedSnapshot)!==intentKey(selection))throw Error("recorded selection labels mismatch");
|
|
132
|
+
return loadWorkSetup(value.state);
|
|
133
|
+
}
|
|
134
|
+
export async function workPresentation(state: DeclaredWorkState): Promise<WorkPresentation | null> {
|
|
135
|
+
const setup = await loadWorkSetup(state);
|
|
136
|
+
if (setup) return { outcome: setup.setup.outcome, obligations: setup.tasks.map(t => { const spec = setup.setup.tasks.find(s => s.id === t.id)!; return { digest: t.obligation.digest, outcome: spec.outcome, agent: spec.agent ?? "No-tool agent", dependencies: spec.dependencies }; }) };
|
|
137
|
+
const value = await readProductJson(join(dirname(state.statePath), "work-outcomes", `${state.outcomeDigest}.json`)) as { outcome: string } | null;
|
|
138
|
+
if (!value) return null;
|
|
139
|
+
if (Object.keys(value).join() !== "outcome" || typeof value.outcome !== "string" || hash(value.outcome.trim()) !== state.outcomeDigest) throw Error("declared outcome digest mismatch");
|
|
140
|
+
return { outcome: value.outcome, obligations: [{ digest: state.obligation.digest, outcome: value.outcome }] };
|
|
141
|
+
}
|