pi-daddy 0.26.0 → 0.27.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.
- package/CHANGELOG.md +32 -0
- package/PRODUCT-GUIDE.md +125 -0
- package/README.md +16 -1
- package/REQUIREMENTS.md +41 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +35 -7
- 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/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/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/measured-order.d.ts.map +1 -1
- package/dist/measured-order.js +63 -5
- package/dist/measured-order.js.map +1 -1
- 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/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/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/cli.ts +26 -7
- 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/index.ts +7 -1
- package/src/learning-connection.ts +70 -0
- package/src/measured-order.ts +8 -3
- package/src/model-preflight.ts +9 -0
- package/src/product-files.ts +34 -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,132 @@
|
|
|
1
|
+
import { randomUUID, createHash } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { supportedModelEfforts as getSupportedThinkingLevels } from "../src/model-preflight.ts";
|
|
5
|
+
import { workSetup, recordWorkSetup, selectRecordedWork, loadWorkSetup, listWorkSetups, type WorkSetup, type WorkTaskSetup } from "../src/work-setup.ts";
|
|
6
|
+
import { loadDeclaredWork } from "../src/work-command.ts";
|
|
7
|
+
import { intentKey } from "../src/intent-control.ts";
|
|
8
|
+
import { ordinaryChildrenFor } from "../src/ordinary-children.ts";
|
|
9
|
+
import { inspectWorkRun, type WorkRunInitial, type WorkRunResult } from "../src/work-run.ts";
|
|
10
|
+
import type { ControlBinding } from "../src/control-journal.ts";
|
|
11
|
+
import { readProductJson } from "../src/product-files.ts";
|
|
12
|
+
import { dashboardActionFeedback } from "../src/dashboard-cli.ts";
|
|
13
|
+
import { runSelectedWork, showWorkResults } from "./work-run-session.ts";
|
|
14
|
+
import type { GrantsSession } from "./session.ts";
|
|
15
|
+
import type { createDailyDashboardSession } from "./daily-dashboard-session.ts";
|
|
16
|
+
|
|
17
|
+
const cancelled = () => Error("Work setup cancelled; nothing selected or launched");
|
|
18
|
+
async function ask(ctx: ExtensionCommandContext, title: string, initial = ""): Promise<string> {
|
|
19
|
+
const value = await ctx.ui.editor(title, initial); if (!value?.trim()) throw cancelled(); return value.trim();
|
|
20
|
+
}
|
|
21
|
+
async function profile(ctx: ExtensionCommandContext, session: GrantsSession, previous?: WorkTaskSetup) {
|
|
22
|
+
const agents = ["No tools (text-only)", ...session.definitions.keys()];
|
|
23
|
+
const agent = await ctx.ui.select("Agent definition — existing grants still apply", agents);
|
|
24
|
+
if (!agent) throw cancelled();
|
|
25
|
+
const available = ctx.scopedModels?.length ? ctx.scopedModels.map(entry => entry.model) : ctx.modelRegistry.getAvailable();
|
|
26
|
+
if(!available.length)throw Error("No available Pi models. Configure a model in Pi, then reopen work setup; no fallback is chosen.");
|
|
27
|
+
const labels = available.map(model => `${model.provider}/${model.id}`);
|
|
28
|
+
const selected = await ctx.ui.select("Model — no silent fallback", labels);
|
|
29
|
+
const model = available[labels.indexOf(selected ?? "")]; if (!model) throw cancelled();
|
|
30
|
+
const thinking = await ctx.ui.select("Requested effort (not proof of provider-internal reasoning)", getSupportedThinkingLevels(model));
|
|
31
|
+
if (!thinking) throw cancelled();
|
|
32
|
+
return { agent: agent === agents[0] ? null : agent, model: `${model.provider}/${model.id}`, thinking: thinking as WorkTaskSetup["thinking"] };
|
|
33
|
+
}
|
|
34
|
+
async function author(ctx: ExtensionCommandContext, session: GrantsSession): Promise<WorkSetup> {
|
|
35
|
+
const outcome = await ask(ctx, "What outcome should this work produce?");
|
|
36
|
+
const tasks: WorkTaskSetup[] = [];
|
|
37
|
+
do {
|
|
38
|
+
const outcome = await ask(ctx, `Task ${tasks.length + 1}: outcome / instructions`);
|
|
39
|
+
const selected = await profile(ctx, session);
|
|
40
|
+
const dependencies: string[] = [];
|
|
41
|
+
while (tasks.length) {
|
|
42
|
+
const candidates = tasks.filter(t => !dependencies.includes(t.id));
|
|
43
|
+
const labels = ["Dependencies complete", ...candidates.map((t, i) => `${i + 1}. ${t.outcome}`)];
|
|
44
|
+
const choice = await ctx.ui.select("Wait for which prior tasks? Failed dependencies block only their branch", labels);
|
|
45
|
+
if (!choice) throw cancelled(); if (choice === labels[0]) break;
|
|
46
|
+
dependencies.push(candidates[labels.indexOf(choice) - 1].id);
|
|
47
|
+
}
|
|
48
|
+
tasks.push({ id: `task${tasks.length + 1}`, outcome, ...selected, dependencies });
|
|
49
|
+
} while (tasks.length < 8 && await ctx.ui.confirm("Add another task?", "Independent tasks can run in parallel; dependent tasks receive predecessor results."));
|
|
50
|
+
const width = await ctx.ui.select("Maximum parallel agents", Array.from({ length: tasks.length }, (_, i) => String(i + 1)));
|
|
51
|
+
if (!width) throw cancelled();
|
|
52
|
+
return workSetup({ version: "work-setup-v1", id: `work-${randomUUID().slice(0, 8)}`, outcome, tasks, maxParallel: Number(width) });
|
|
53
|
+
}
|
|
54
|
+
async function revise(ctx: ExtensionCommandContext, session: GrantsSession, previous: WorkSetup): Promise<WorkSetup> {
|
|
55
|
+
const setup = structuredClone(previous);
|
|
56
|
+
while (true) {
|
|
57
|
+
const labels = ["Save revision", "Overall outcome", "Parallel limit", ...setup.tasks.map((t,i) => `${i+1}. ${t.outcome}`)];
|
|
58
|
+
const choice = await ctx.ui.select("Revise scope/configuration (topology and permissions stay fixed)", labels);
|
|
59
|
+
if (!choice) throw cancelled();
|
|
60
|
+
const index = labels.indexOf(choice);
|
|
61
|
+
if (index === 0) return workSetup(setup);
|
|
62
|
+
if (index === 1) setup.outcome = await ask(ctx, "Revised outcome", setup.outcome);
|
|
63
|
+
else if (index === 2) { const width = await ctx.ui.select("Parallel limit", Array.from({ length: setup.tasks.length }, (_, i) => String(i + 1))); if (!width) throw cancelled(); setup.maxParallel = Number(width); }
|
|
64
|
+
else { const task = setup.tasks[index - 3]; task.outcome = await ask(ctx, "Task outcome", task.outcome); Object.assign(task, await profile(ctx, session, task)); }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createWorkSession(session: GrantsSession, host: ReturnType<typeof createDailyDashboardSession>, rebind: () => void) {
|
|
69
|
+
let pending: Promise<void> | null = null, owned: Promise<WorkRunResult> | null = null;
|
|
70
|
+
const shutdown = new AbortController();
|
|
71
|
+
const run = async (verb: string, ctx: ExtensionCommandContext) => {
|
|
72
|
+
if (!ctx.hasUI) throw Error("Use /grants work in interactive Pi for setup and explicit run confirmation");
|
|
73
|
+
const current = await loadDeclaredWork(join(ctx.cwd, ".pi", "work-current.json"));
|
|
74
|
+
if (current) { session.declaredWork = current; rebind(); }
|
|
75
|
+
const selected = current ? await loadWorkSetup(current) : null;
|
|
76
|
+
const menu = ["Run selected work", "Declare new work", "Revise scope / configuration", "Record alternative", "Select saved work", "Select task for ordinary delegation", "Change priority", "Inspect results"];
|
|
77
|
+
const choice = verb ? ({ run: menu[0], new: menu[1], results: menu[7] } as Record<string, string>)[verb] : await ctx.ui.select(current ? selected?.setup.outcome ?? `Declared work: ${current.id}` : "No work selected", menu);
|
|
78
|
+
if (!choice) { if (verb) throw Error("Use /grants work, work new, work run or work results"); return; }
|
|
79
|
+
const guard = () => { if (!ctx.isIdle() || !ordinaryChildrenFor(session).quiescent()) throw Error("Wait for the original active work to settle before changing setup"); };
|
|
80
|
+
if (choice === menu[0]) {
|
|
81
|
+
if (!selected) throw Error("Declare multi-task work first; existing ordinary delegation remains available");
|
|
82
|
+
let priorities: string[] | undefined;
|
|
83
|
+
if (host.running) {
|
|
84
|
+
const frame = await host.frame(), controls = frame.controls as { intent?: { priorities: { obligation: { digest: string } }[] } };
|
|
85
|
+
priorities = controls.intent?.priorities.map(p => selected.tasks.find(t => t.obligation.digest === p.obligation.digest)?.id ?? "");
|
|
86
|
+
}
|
|
87
|
+
const result = await runSelectedWork(session, ctx, selected, priorities, { signal: shutdown.signal, started: promise => { owned = promise; } });
|
|
88
|
+
if (shutdown.signal.aborted) return;
|
|
89
|
+
if (result) { ctx.ui.notify(`${result.tasks.filter(t => t.state === "finished").length} finished; ${result.tasks.filter(t => t.state !== "finished").length} failed/blocked. No acceptance inferred.`, "info"); await showWorkResults(ctx, result); }
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (choice === menu[7]) {
|
|
93
|
+
const binding = await readProductJson(join(ctx.cwd, ".pi", "work-last-run.json")) as ControlBinding<WorkRunInitial> | null;
|
|
94
|
+
if (!binding) throw Error("No bounded run result is retained for this project yet");
|
|
95
|
+
await showWorkResults(ctx, await inspectWorkRun(binding)); return;
|
|
96
|
+
}
|
|
97
|
+
if (choice === menu[6]) {
|
|
98
|
+
if (!host.running) throw Error("Start /grants host to change priority through the current dispatch owner");
|
|
99
|
+
const frame = await host.frame(), actions = frame.actions.filter(a => a.key.startsWith("priority-"));
|
|
100
|
+
const labels = actions.map((a, i) => `${i + 1}. ${a.label}`), picked = await ctx.ui.select("Next-work priority (does not rearrange active children)", labels);
|
|
101
|
+
const action = actions[labels.indexOf(picked ?? "")]; if (action) ctx.ui.notify(dashboardActionFeedback(action.label, await host.choose(action.key,{tip:frame.tip,requestDigest:action.requestDigest})), "info"); return;
|
|
102
|
+
}
|
|
103
|
+
guard();
|
|
104
|
+
if (choice === menu[5]) {
|
|
105
|
+
if (!selected || !current) throw Error("No multi-task setup selected");
|
|
106
|
+
const labels = selected.setup.tasks.map((t, i) => `${i + 1}. ${t.outcome}`), picked = await ctx.ui.select("Ordinary delegate binds which task?", labels), task = selected.tasks[labels.indexOf(picked ?? "")];
|
|
107
|
+
if (!task) return;
|
|
108
|
+
guard(); session.declaredWork = await selectRecordedWork({ ...selected, state: { ...selected.state, obligation: task.obligation } }, current.selectedSnapshot); rebind();
|
|
109
|
+
ctx.ui.notify("Task selected for subsequent ordinary delegation. Existing attempts were not rebound.", "info"); return;
|
|
110
|
+
}
|
|
111
|
+
if (choice === menu[4]) {
|
|
112
|
+
const setups = await listWorkSetups(ctx.cwd), labels = setups.map((s, i) => `${i + 1}. ${s.setup.outcome}`), picked = await ctx.ui.select("Select saved setup (existing work stays pinned)", labels), target = setups[labels.indexOf(picked ?? "")];
|
|
113
|
+
if (!target) return;
|
|
114
|
+
guard();
|
|
115
|
+
if (host.running) { if (!await ctx.ui.confirm("Stop the current host and select saved work?", "The old host's evidence remains intact. Start /grants host again for the selected setup.")) return; await host.run("stop"); }
|
|
116
|
+
session.declaredWork = await selectRecordedWork(target, current?.selectedSnapshot ?? null); rebind(); return;
|
|
117
|
+
}
|
|
118
|
+
const mode = choice === menu[1] ? "new" : choice === menu[2] ? "revise" : "alternative";
|
|
119
|
+
if (mode !== "new" && !selected) throw Error("Existing single-task work remains supported; declare a new multi-task setup to use revisions");
|
|
120
|
+
const draft = mode === "new" ? await author(ctx, session) : await revise(ctx, session, selected!.setup);
|
|
121
|
+
if (!await ctx.ui.confirm("Save this work setup?", `${draft.outcome}\n${draft.tasks.length} obligations; up to ${draft.maxParallel} parallel. Outcome/instruction text is retained in private project files, not governance ledgers. Nothing runs now.`)) return;
|
|
122
|
+
guard();
|
|
123
|
+
if (mode === "new" && host.running) { if (!await ctx.ui.confirm("Stop current host?", "A new topology needs a new host binding. Old evidence is preserved; no running work changes.")) return; await host.run("stop"); }
|
|
124
|
+
const recorded = await recordWorkSetup(ctx.cwd, draft, mode, current);
|
|
125
|
+
if (host.running) {
|
|
126
|
+
const prefix = mode === "revise" ? "scope" : "alternative", digest = createHash("sha256").update(intentKey(recorded.state.selectedSnapshot)).digest("hex").slice(0, 12);
|
|
127
|
+
ctx.ui.notify(dashboardActionFeedback("Select recorded work", await host.choose(`${prefix}-${digest}`)), "info");
|
|
128
|
+
} else { session.declaredWork = await selectRecordedWork(recorded, current?.selectedSnapshot ?? null); rebind(); }
|
|
129
|
+
ctx.ui.notify("Work saved. /grants work run starts it explicitly; /grants host connects the panel.", "info");
|
|
130
|
+
};
|
|
131
|
+
return { async close() { shutdown.abort(); if (owned) await owned.catch(() => undefined); }, async run(verb: string, ctx: ExtensionCommandContext) { shutdown.signal.throwIfAborted(); if (pending) throw Error("A work setup/run command is already active"); pending = run(verb, ctx); try { await pending; } finally { pending = null; owned = null; } } };
|
|
132
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-daddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Capability governance for pi sub-agents: spawn Agent Skills (SKILL.md) definitions whose allowed-tools becomes a grant that can only narrow going down a delegation tree, enforced by pi's own --tools allowlist, with an append-only ledger.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -37,6 +37,12 @@
|
|
|
37
37
|
"types": "./dist/ledger.d.ts",
|
|
38
38
|
"default": "./dist/ledger.js"
|
|
39
39
|
},
|
|
40
|
+
"./work-setup": { "types": "./dist/work-setup.d.ts", "default": "./dist/work-setup.js" },
|
|
41
|
+
"./work-command": { "types": "./dist/work-command.d.ts", "default": "./dist/work-command.js" },
|
|
42
|
+
"./work-run": { "types": "./dist/work-run.d.ts", "default": "./dist/work-run.js" },
|
|
43
|
+
"./work-policy-registry": { "types": "./dist/work-policy-registry.d.ts", "default": "./dist/work-policy-registry.js" },
|
|
44
|
+
"./factory-registry": { "types": "./dist/factory-registry.d.ts", "default": "./dist/factory-registry.js" },
|
|
45
|
+
"./learning-connection": { "types": "./dist/learning-connection.d.ts", "default": "./dist/learning-connection.js" },
|
|
40
46
|
"./work-ledger": {
|
|
41
47
|
"types": "./dist/work-ledger.d.ts",
|
|
42
48
|
"default": "./dist/work-ledger.js"
|
|
@@ -241,6 +247,8 @@
|
|
|
241
247
|
"THIRD_PARTY_LICENSES",
|
|
242
248
|
"THIRD_PARTY_NOTICES.md",
|
|
243
249
|
"README.md",
|
|
250
|
+
"PRODUCT-GUIDE.md",
|
|
251
|
+
"REQUIREMENTS.md",
|
|
244
252
|
"CHANGELOG.md",
|
|
245
253
|
"LICENSE"
|
|
246
254
|
],
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `pi-daddy` —
|
|
3
|
+
* `pi-daddy` — scaffolding, read-only work inspection and the installed product guide.
|
|
4
4
|
*
|
|
5
5
|
* Thin on purpose: argv in, `discoverSkillPackages` + `planInit` + `applyInit`, report out. Every decision
|
|
6
6
|
* lives in `./init.ts`, `./grant-env.ts` and `./skill-packages.ts` as functions that touch no argv and print
|
|
@@ -21,7 +21,9 @@ import { UnsafeGrantError } from "./grant-env.ts";
|
|
|
21
21
|
import { applyInit, countDeclaring, planInit, type InitPlan } from "./init.ts";
|
|
22
22
|
import { registeredWorkspaceIds } from "./workspace.ts";
|
|
23
23
|
import { discoverSkillPackages, skillPackageRoots, type RefusedSkill, type SkillPackage } from "./skill-packages.ts";
|
|
24
|
-
import { declareWork } from "./work-command.ts";
|
|
24
|
+
import { declareWork, loadDeclaredWork } from "./work-command.ts";
|
|
25
|
+
import { listWorkSetups, workPresentation } from "./work-setup.ts";
|
|
26
|
+
import { panelText } from "./daily-panel.ts";
|
|
25
27
|
|
|
26
28
|
const USAGE = `pi-daddy — capability governance for pi sub-agents
|
|
27
29
|
|
|
@@ -30,6 +32,11 @@ Usage:
|
|
|
30
32
|
packages that declare skills (package.json "pi": {"skills": …})
|
|
31
33
|
pi-daddy work add --id <id> --outcome <text> [--dir <path>]
|
|
32
34
|
declare one current obligation for ordinary delegation
|
|
35
|
+
pi-daddy work list | work show [--dir <path>]
|
|
36
|
+
inspect saved setups or selected outcome (no model calls)
|
|
37
|
+
In Pi: /grants work guided multi-task/model/effort/dependency setup and run
|
|
38
|
+
/grants learning retained review, trust, adoption and later outcomes
|
|
39
|
+
pi-daddy guide | current installed product guide / current requirement register
|
|
33
40
|
pi-daddy --help | --version
|
|
34
41
|
|
|
35
42
|
init copies each declared SKILL.md into .pi/skills/ and writes a grant naming exactly what those files
|
|
@@ -41,7 +48,7 @@ placeholder and stays unspawnable until you fill it in. Capabilities that can ch
|
|
|
41
48
|
It never rewrites .pi/grants.env — delete that file if you want it regenerated.`;
|
|
42
49
|
|
|
43
50
|
export interface ParsedArgs {
|
|
44
|
-
command: "init" | "work-add" | "help" | "version";
|
|
51
|
+
command: "init" | "work-add" | "work-list" | "work-show" | "guide" | "current" | "help" | "version";
|
|
45
52
|
dir?: string;
|
|
46
53
|
force: boolean;
|
|
47
54
|
id?: string;
|
|
@@ -64,10 +71,12 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
64
71
|
if (args.includes("--version") || args.includes("-v")) return { command: "version", force: false, errors: [] };
|
|
65
72
|
|
|
66
73
|
const [command, ...tail] = args;
|
|
74
|
+
if(command==="guide"||command==="current")return{command,force:false,errors:tail.length?[`${command} takes no arguments`]:[]};
|
|
67
75
|
if (command !== "init" && command !== "work") return { command: "help", force: false, errors: [`unknown command "${command}"`] };
|
|
68
76
|
const work = command === "work";
|
|
69
|
-
const
|
|
70
|
-
|
|
77
|
+
const workVerb = tail[0];
|
|
78
|
+
const rest = work ? tail.slice(1) : tail;
|
|
79
|
+
if (work && !["add", "list", "show"].includes(workVerb)) return { command: "work-add", force: false, errors: ["work needs add, list or show; /grants work opens guided setup in Pi"] };
|
|
71
80
|
|
|
72
81
|
const errors: string[] = [];
|
|
73
82
|
let dir: string | undefined;
|
|
@@ -77,11 +86,11 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
77
86
|
const arg = rest[i];
|
|
78
87
|
if (arg === "--force" && !work) {
|
|
79
88
|
force = true;
|
|
80
|
-
} else if (arg === "--id" && work) {
|
|
89
|
+
} else if (arg === "--id" && work && workVerb === "add") {
|
|
81
90
|
const value = rest[i + 1];
|
|
82
91
|
if (value === undefined || value.startsWith("-")) errors.push("--id needs a value");
|
|
83
92
|
else { id = value; i += 1; }
|
|
84
|
-
} else if (arg === "--outcome" && work) {
|
|
93
|
+
} else if (arg === "--outcome" && work && workVerb === "add") {
|
|
85
94
|
const value = rest[i + 1];
|
|
86
95
|
if (value === undefined || value.startsWith("-")) errors.push("--outcome needs text");
|
|
87
96
|
else { outcome = value; i += 1; }
|
|
@@ -104,6 +113,7 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
115
|
|
|
116
|
+
if (work && workVerb !== "add") return { command: workVerb === "list" ? "work-list" : "work-show", force: false, errors, ...(dir ? { dir } : {}) };
|
|
107
117
|
if (work) {
|
|
108
118
|
if (!id && !errors.some(error => error.startsWith("--id"))) errors.push("--id needs a value");
|
|
109
119
|
if (!outcome && !errors.some(error => error.startsWith("--outcome"))) errors.push("--outcome needs text");
|
|
@@ -282,6 +292,15 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
282
292
|
console.log(USAGE);
|
|
283
293
|
return 0;
|
|
284
294
|
}
|
|
295
|
+
if(parsed.command==="guide"||parsed.command==="current"){
|
|
296
|
+
console.log(await readFile(new URL(parsed.command==="guide"?"../PRODUCT-GUIDE.md":"../REQUIREMENTS.md",import.meta.url),"utf8"));return 0;
|
|
297
|
+
}
|
|
298
|
+
if (parsed.command === "work-list" || parsed.command === "work-show") {
|
|
299
|
+
const cwd=resolvePath(parsed.dir??process.cwd());
|
|
300
|
+
if(parsed.command==="work-list") {const setups=await listWorkSetups(cwd);console.log(setups.map((s,i)=>`${i+1}. ${panelText(s.setup.outcome)} — ${s.setup.tasks.length} tasks; up to ${s.setup.maxParallel} parallel`).join("\n")||"No saved multi-task setups. In Pi: /grants work");}
|
|
301
|
+
else {const state=await loadDeclaredWork(resolvePath(cwd,".pi/work-current.json")),view=state?await workPresentation(state):null;console.log(view?[panelText(view.outcome),...view.obligations.map(o=>`- ${panelText(o.outcome)}`)].join("\n"):state?"Selected legacy work; outcome text unavailable":"No work selected. In Pi: /grants work");}
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
285
304
|
if (parsed.command === "work-add") {
|
|
286
305
|
const declared = await declareWork({
|
|
287
306
|
cwd: resolvePath(parsed.dir ?? process.cwd()),
|
|
@@ -14,12 +14,16 @@ import { bindWorkIntent } from "./intent-application.ts";
|
|
|
14
14
|
import { intentKey, intentRequest, intentRequestDigest, type IntentPriority, type IntentRequest, type IntentSelection } from "./intent-control.ts";
|
|
15
15
|
import { parseWorkLedgerText, type WorkRevisionEvent, type WorkSnapshotEvent } from "./work-ledger.ts";
|
|
16
16
|
import { resolveWorkSnapshotText } from "./work-ledger-snapshot.ts";
|
|
17
|
+
import { workPresentation, workSetupForSelection } from "./work-setup.ts";
|
|
18
|
+
import { createDailyViewReader } from "./daily-view.ts";
|
|
19
|
+
import { learningHarness, learningScopeDigest, type LearningConnection } from "./learning-connection.ts";
|
|
17
20
|
|
|
18
21
|
export interface DailyIntentAction {key:string;label:string;action:"revise-scope"|"reprioritize"|"select-alternative";fromSelection:IntentSelection;events:(WorkRevisionEvent|WorkSnapshotEvent)[];selection:IntentSelection;priorities:IntentPriority[]}
|
|
19
|
-
export interface DailyDashboardHostInput {id:string;cwd:string;directory:string;socketPath?:string;declared:DeclaredWorkState;ordinary:OrdinaryChildren;harness:DashboardHarness;author:string;priorities?:IntentPriority[];intentActions?:DailyIntentAction[];presence?:()=>{present:boolean;closing:boolean;evidenceDigest:string;expiresAt:number}|null;onDeclaredWorkChanged?:(state:DeclaredWorkState)=>void|Promise<void>}
|
|
22
|
+
export interface DailyDashboardHostInput {id:string;cwd:string;directory:string;socketPath?:string;declared:DeclaredWorkState;ordinary:OrdinaryChildren;harness:DashboardHarness;author:string;learning?:LearningConnection;priorities?:IntentPriority[];intentActions?:DailyIntentAction[];presence?:()=>{present:boolean;closing:boolean;evidenceDigest:string;expiresAt:number}|null;onDeclaredWorkChanged?:(state:DeclaredWorkState)=>void|Promise<void>}
|
|
20
23
|
const idPattern=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
21
24
|
const hash=(value:string)=>createHash("sha256").update(value).digest("hex");
|
|
22
25
|
const same=(a:unknown,b:unknown)=>intentKey(a)===intentKey(b);
|
|
26
|
+
const friendlyLabel=(value:string)=>{let chars=[...value.replace(/[\p{Cc}\p{Cf}]/gu," ")];while(Buffer.byteLength(chars.join(""))>112)chars.pop();return chars.join("")+(chars.length<[...value].length?"...":"");};
|
|
23
27
|
/** Recorded P01 snapshots become labelled proposals; choosing a label still traverses intent validation and exact host/CAS authority. */
|
|
24
28
|
export function discoverDailyIntentActions(text:string,selection:IntentSelection,priorities:readonly IntentPriority[]):DailyIntentAction[]{
|
|
25
29
|
const parsed=parseWorkLedgerText(text);if(!parsed.complete||parsed.events.length>512)throw Error("bounded complete recorded proposal ledger required");const label=(value:string)=>value.length<=80?value:`${value.slice(0,67)}…${hash(value).slice(0,12)}`;
|
|
@@ -41,48 +45,61 @@ export async function startDailyDashboardHost(input:DailyDashboardHostInput){
|
|
|
41
45
|
if(!idPattern.test(input.id)||!isAbsolute(input.cwd)||resolve(input.cwd)!==input.cwd||!isAbsolute(input.directory)||resolve(input.directory)!==input.directory||!input.author.trim()||input.author.length>256)throw Error("explicit bounded daily host configuration required");
|
|
42
46
|
const harnessDigest=loadedDashboardHarnessDigest(input.harness);if(!harnessDigest)throw Error("loaded skill-harness bridge required");
|
|
43
47
|
if(input.declared.ledgerPath!==join(input.cwd,".pi","work.jsonl"))throw Error("current declared work must belong to this host project");
|
|
44
|
-
|
|
48
|
+
const learning=input.learning?learningHarness(input.harness).openLearningWorkspace(input.learning.directory):null,learningConfig=learning?.configuration();
|
|
49
|
+
if(input.learning&&(!learningConfig||intentKey(learningConfig)!==intentKey(input.learning.configuration)||learningConfig.scopeDigest!==learningScopeDigest(input.declared)||learningConfig.author!==input.author||intentKey(input.learning.selection)!==intentKey(input.declared.selectedSnapshot)))throw Error("learning scope/archive/population/author mismatch");
|
|
50
|
+
const learnedTrust=learning?.dashboardBinding();
|
|
51
|
+
await mkdir(input.directory,{mode:0o700});const archiveRoot=learningConfig?.archiveRoot??join(input.directory,"archive");if(!learningConfig)await mkdir(archiveRoot,{mode:0o700});
|
|
45
52
|
const reference=input.harness.retainArchiveSource(archiveRoot,{sourceId:`host-${input.id}-bootstrap`,parser:{id:"daily-host-bootstrap",version:"1"},retention:"exact",bytes:Buffer.from("explicit daily host bootstrap")});
|
|
46
|
-
const component={kind:"detector",id:"coverage_gap",version:"work-signals-v1:declared-work-v1",population
|
|
53
|
+
const component={kind:"detector",id:"coverage_gap",version:"work-signals-v1:declared-work-v1",population:learningConfig?.population??`daily:${input.id}`};
|
|
47
54
|
const trustInput={archiveRoot,component,seed:hash(`daily-host\0${input.id}`),maxUnflagged:0,cohort:[{incidentId:"bootstrap",manifestId:reference.manifestId,flagged:true,split:"calibration"}],exposure:null};
|
|
48
|
-
const trustPolicyId=input.harness.trustPolicyDigest(trustInput),trustDirectory=join(input.directory,"trust");
|
|
55
|
+
const trustPolicyId=learnedTrust?.trustPolicyId??input.harness.trustPolicyDigest(trustInput),trustDirectory=learnedTrust?.trustDirectory??join(input.directory,"trust");
|
|
56
|
+
if(Boolean(learnedTrust?.trustDirectory)!==Boolean(learnedTrust?.trustPolicyId))throw Error("incomplete original learning trust binding");
|
|
57
|
+
if(!learnedTrust?.trustDirectory)input.harness.createTrustLifecycle(trustDirectory,trustInput,[trustPolicyId]);
|
|
49
58
|
const workRelative="work.jsonl",factsRelative="facts.jsonl",workMirror=join(input.directory,workRelative);await writeFile(workMirror,await readFile(input.declared.ledgerPath),{mode:0o600,flag:"wx"});
|
|
50
59
|
const refreshWork=async()=>{const temporary=`${workMirror}.${process.pid}.${randomUUID()}.tmp`;try{await writeFile(temporary,await readFile(input.declared.ledgerPath),{mode:0o600,flag:"wx"});await rename(temporary,workMirror);}finally{await rm(temporary,{force:true});}};
|
|
51
|
-
const facts={scopeDigest:input.declared.selectedSnapshot.snapshot.digest,version:"declared-work-v1",population
|
|
60
|
+
const facts={scopeDigest:input.declared.selectedSnapshot.snapshot.digest,version:"declared-work-v1",population:learningConfig?.population??`daily:${input.id}`,expectedWaits:[],checkpoints:[],violations:[],priorAccepted:[]};await writeFile(join(input.directory,factsRelative),JSON.stringify(facts)+"\n",{mode:0o600,flag:"wx"});
|
|
52
61
|
const policy={version:"archive-policy-v2",id:`daily-${input.id}`,revision:"1",sourceRoot:input.directory,archiveRoot,maxBytes:8*1024*1024,retention:"exact",expiresAt:new Date(Date.now()+24*60*60*1000).toISOString(),sources:[{id:"work",path:workRelative,parser:{id:"pi-daddy-work-ledger",version:"4"},contentPolicy:"manifest-only"},{id:"facts",path:factsRelative,parser:{id:"pi-daddy-work-signal-facts",version:"1"},contentPolicy:"manifest-only"}]};
|
|
53
62
|
const policyPath=join(input.directory,"archive-policy.json"),policyBytes=JSON.stringify(policy);await writeFile(policyPath,policyBytes,{mode:0o600,flag:"wx"});
|
|
54
|
-
const authorityDigest=dataDigest({kind:"daily-dashboard-host",id:input.id,cwd:input.cwd}),initialPriorities=input.priorities??
|
|
63
|
+
const authorityDigest=dataDigest({kind:"daily-dashboard-host",id:input.id,cwd:input.cwd}),initialPriorities=input.priorities??resolveWorkSnapshotText(await readFile(input.declared.ledgerPath,"utf8"),input.declared.selectedSnapshot).snapshot!.bindings.map((binding,rank)=>({obligation:binding.obligation,rank}));
|
|
55
64
|
const workIntent=await bindWorkIntent({path:input.declared.ledgerPath,grantLedgerPath:input.declared.grantLedgerPath,selection:input.declared.selectedSnapshot,priorities:initialPriorities});
|
|
56
65
|
const budget=await createIntentBudget({directory:join(input.directory,"budget"),authorityDigest,limits:{maxAttempts:6,maxInputBytes:65536,maxConcurrent:3}},workIntent);
|
|
57
|
-
const config:DashboardHostConfig={version:"producer-dashboard-host-v1",trustDirectory,trustPolicyId,archiveRoot,scope:`daily:${input.id}`,author:input.author,policyPath,policySha256:hash(policyBytes),sources:[{id:"work",kind:"work"},{id:"facts",kind:"facts"}],selection:input.declared.selectedSnapshot,cases:null,blind:null,budgetDigest:resourceBindingDigest(budget),experimentDigest:null,harnessArtifactDigest:harnessDigest,ordinaryDigest:input.ordinary.bindingDigest};
|
|
66
|
+
const config:DashboardHostConfig={version:"producer-dashboard-host-v1",...(learnedTrust?.trustDirectory?{journalDirectory:join(input.directory,"producer-host")} : {}),trustDirectory,trustPolicyId,archiveRoot,scope:`daily:${input.id}`,author:input.author,policyPath,policySha256:hash(policyBytes),sources:[{id:"work",kind:"work"},{id:"facts",kind:"facts"}],selection:input.declared.selectedSnapshot,cases:null,blind:null,budgetDigest:resourceBindingDigest(budget),experimentDigest:null,harnessArtifactDigest:harnessDigest,ordinaryDigest:input.ordinary.bindingDigest};
|
|
58
67
|
const proposals=input.intentActions??[];if(proposals.length>8||new Set(proposals.map(x=>x.key)).size!==proposals.length||proposals.some(x=>!idPattern.test(x.key)||typeof x.label!=="string"||!x.label.trim()||Buffer.byteLength(x.label)>120))throw Error("bounded friendly intent actions required");
|
|
59
68
|
const requestDigests=new Set<string>(),dispatchDigests=new Set<string>(),ordinaryDigests=new Set<string>();let host:ReturnType<typeof createDashboardHost>,factsResultManifest:string|null=null,workSelection:IntentSelection=input.declared.selectedSnapshot,declaredState=input.declared,reboundSnapshotId:string|null=null;
|
|
60
69
|
const authority=():DashboardHostAuthority=>({hostDigests:[dashboardHostDigest(config)],requestDigests:[...requestDigests],workContext:{selectedSnapshot:workSelection,authority:null},dispatch:{authorityDigest,requestDigests:[...dispatchDigests]},experiment:null,ordinary:{bindingDigest:input.ordinary.bindingDigest,requestDigests:[...ordinaryDigests]}});
|
|
61
70
|
const request=(context:{hostDigest:string;selectionDigest:string;tip:string},operation:DashboardHostRequest["operation"],payload:unknown,requestId:string):DashboardHostRequest=>({version:"1.0",requestId,hostDigest:context.hostDigest,selectionDigest:context.selectionDigest,expectedTip:context.tip,operation,payload});
|
|
62
|
-
|
|
71
|
+
const dailyReader=createDailyViewReader();
|
|
72
|
+
host=createDashboardHost({harness:input.harness,config,budget,ordinary:input.ordinary,authority,learningView:learning?()=>learningScopeDigest(declaredState)===learningConfig!.scopeDigest?learning.inspect(Date.now()):{state:"deferred",reason:"Selected work changed; reconnect learning explicitly"}:undefined,afterObservation:learning?observation=>{if(observation.cases&&learningScopeDigest(declaredState)===learningConfig!.scopeDigest)learning.bindCases({name:`work-${observation.cases.batchId.slice(0,20)}`,title:"Retained daily work",version:3,batchId:observation.cases.batchId});}:undefined,presentation:()=>workPresentation(declaredState),currentWork:()=>dailyReader({workLedgerPath:declaredState.ledgerPath,workContext:{selectedSnapshot:workSelection,authority:null}}),beforeObservation:sourceId=>sourceId==="work"?refreshWork():Promise.resolve(),humanActions:async context=>{
|
|
63
73
|
const state=await openResourceBudget(budget).controls(null).inspect(),action=state.paused?"resume-dispatch":"pause-dispatch",key=state.paused?"resume-dispatch":"pause-new-dispatch";
|
|
64
74
|
const native:DispatchRequest={version:"1.0",requestId:`${action}:${state.revision}`,bindingDigest:config.budgetDigest,expectedRevision:state.revision,action,targetExecutionId:null};dispatchDigests.add(dispatchRequestDigest(native));
|
|
65
75
|
const hostRequest=request(context,"dispatch",native,`host-${action}:${state.revision}`);requestDigests.add(dashboardHostRequestDigest(hostRequest));
|
|
66
76
|
const previous=context.observations.find(x=>x.sourceId==="work")?.checkpointId??null,refresh=request(context,"observe",{sourceId:"work",previousCheckpointId:previous,facts:context.observations.find(x=>x.sourceId==="facts")?.checkpointId?factsResultManifest:null},`refresh-work:${state.revision}:${context.tip.slice(0,12)}`);requestDigests.add(dashboardHostRequestDigest(refresh));
|
|
67
77
|
const intentState=await openResourceBudget(budget).intentControls(null).inspect(),automatic=discoverDailyIntentActions(await readFile(input.declared.ledgerPath,"utf8"),intentState.selection,intentState.priorities);
|
|
68
78
|
const candidates=[...proposals,...automatic].filter((p,index,all)=>all.findIndex(x=>x.key===p.key)===index).filter(p=>p.action==="reprioritize"||Boolean(input.onDeclaredWorkChanged)).slice(0,8);
|
|
69
|
-
const
|
|
79
|
+
const labels=await workPresentation(declaredState);
|
|
80
|
+
const intents=await Promise.all((input.ordinary.quiescent()?candidates:[]).filter(p=>intentKey(p.fromSelection)===intentKey(intentState.selection)).map(async p=>{
|
|
70
81
|
const native=intentRequest({version:"intent-request-v1",requestId:`${p.key}:${intentState.revision}`,bindingDigest:config.budgetDigest,expectedRevision:intentState.revision,expectedSelection:intentState.selection,action:p.action,events:p.events,selection:p.selection,priorities:p.priorities});dispatchDigests.add(intentRequestDigest(native));
|
|
71
|
-
const proposed=request(context,"intent",native,`host-intent:${p.key}:${intentState.revision}`);requestDigests.add(dashboardHostRequestDigest(proposed));
|
|
72
|
-
|
|
82
|
+
const proposed=request(context,"intent",native,`host-intent:${p.key}:${intentState.revision}`);requestDigests.add(dashboardHostRequestDigest(proposed));const outcome=p.action==="reprioritize"?labels?.obligations.find(o=>o.digest===p.priorities[0]?.obligation.digest)?.outcome:(await workSetupForSelection(input.cwd,p.selection))?.setup.outcome;
|
|
83
|
+
return {key:p.key,label:outcome?friendlyLabel(p.action==="reprioritize"?`Put ${outcome} first`:`Use ${outcome}`):p.label,request:proposed};
|
|
84
|
+
}));
|
|
73
85
|
const ordinary=input.ordinary.inspect() as {revision:number;children:{target:OrdinaryTarget;state:string;abortRequested:boolean}[]},accounting=await openResourceBudget(budget).inspect(),presence=input.presence?.();
|
|
74
86
|
const presentation:{key:string;label:string;request:DashboardHostRequest}[]=[];
|
|
75
87
|
if(context.preparedPresentationDigest){const shown=request(context,"presented",{frameDigest:context.preparedPresentationDigest},`host-presented:${state.revision}:${context.tip.slice(0,12)}`);requestDigests.add(dashboardHostRequestDigest(shown));presentation.push({key:"acknowledge-case-cards",label:"Acknowledge these case cards are visible",request:shown});}
|
|
76
|
-
else if(presence?.present===true&&presence.closing===true&&presence.expiresAt>Date.now()&&state.paused&&accounting.active===0&&!ordinary.children.some(row=>row.state==="active")){const present=request(context,"present",{userPresent:true,closing:true,evidenceDigest:presence.evidenceDigest,dispatchRevision:state.revision},`host-present:${state.revision}:${context.tip.slice(0,12)}`);requestDigests.add(dashboardHostRequestDigest(present));presentation.push({key:"prepare-case-cards",label:"Prepare up to five retained case cards",request:present});}
|
|
88
|
+
else if((!learningConfig||learningScopeDigest(declaredState)===learningConfig.scopeDigest)&&presence?.present===true&&presence.closing===true&&presence.expiresAt>Date.now()&&state.paused&&accounting.active===0&&!ordinary.children.some(row=>row.state==="active")){const present=request(context,"present",{userPresent:true,closing:true,evidenceDigest:presence.evidenceDigest,dispatchRevision:state.revision},`host-present:${state.revision}:${context.tip.slice(0,12)}`);requestDigests.add(dashboardHostRequestDigest(present));presentation.push({key:"prepare-case-cards",label:"Prepare up to five retained case cards",request:present});}
|
|
77
89
|
const cancellations=ordinary.children.filter(row=>row.state==="active"&&!row.abortRequested).sort((a,b)=>a.target.executionId.localeCompare(b.target.executionId)).slice(0,8).map(row=>{
|
|
78
90
|
const native=ordinaryCancellation({version:"ordinary-cancel-v1",requestId:`cancel:${row.target.executionId.slice(5)}`,bindingDigest:input.ordinary.bindingDigest,expectedRevision:ordinary.revision,target:row.target});ordinaryDigests.add(ordinaryCancellationDigest(native));
|
|
79
91
|
const cancel=request(context,"ordinary-cancel",native,`host-cancel:${row.target.executionId.slice(5)}:${ordinary.revision}`);requestDigests.add(dashboardHostRequestDigest(cancel));
|
|
80
|
-
return {key:`cancel-${row.target.executionId.replace(":","-")}-${ordinary.revision.toString(36)}`,label:`
|
|
92
|
+
return {key:`cancel-${row.target.executionId.replace(":","-")}-${ordinary.revision.toString(36)}`,label:`Stop active agent ${ordinary.children.filter(r=>r.state==="active").indexOf(row)+1}`,request:cancel};
|
|
81
93
|
});
|
|
82
|
-
return [{key,label:state.paused?"Resume new
|
|
94
|
+
return [{key,label:state.paused?"Resume new work":"Pause new work (active agents continue)",request:hostRequest},{key:"refresh-current-work",label:reboundSnapshotId?"Retain current work and attempts (new scope)":"Retain current work and attempts",request:refresh},...intents,...presentation,...cancellations];
|
|
83
95
|
},afterIntentApplied:async selection=>{const rebound=await rebindDeclaredWork(declaredState,selection);declaredState=rebound;workSelection=selection;reboundSnapshotId=selection.snapshot.id;await input.onDeclaredWorkChanged?.(rebound);},presence:input.presence});
|
|
84
96
|
const before=await host.frame(),observeFacts=request({hostDigest:host.hostDigest,selectionDigest:before.selectionDigest,tip:before.tip},"observe",{sourceId:"facts",previousCheckpointId:null,facts:null},"observe-current-facts");requestDigests.add(dashboardHostRequestDigest(observeFacts));const factsResult=await host.action(observeFacts) as {result:{sourceManifestId:string}};factsResultManifest=factsResult.result.sourceManifestId;
|
|
85
97
|
const afterFacts=await host.frame(),observeWork=request({hostDigest:host.hostDigest,selectionDigest:afterFacts.selectionDigest,tip:afterFacts.tip},"observe",{sourceId:"work",previousCheckpointId:null,facts:factsResult.result.sourceManifestId},"observe-current-work");requestDigests.add(dashboardHostRequestDigest(observeWork));await host.action(observeWork);
|
|
86
98
|
const socketPath=input.socketPath??join(input.directory,"dashboard.sock"),server=await serveDashboardHost(socketPath,host);
|
|
87
|
-
let closed=false;
|
|
99
|
+
let closed=false;const disconnect=async()=>{if(closed)return;closed=true;await server.close();};
|
|
100
|
+
return Object.freeze({host,socketPath,directory:input.directory,
|
|
101
|
+
async close(){if(closed)return;await host.stop();await disconnect();},
|
|
102
|
+
/** Final Pi owner-session disposal, never a manual host replacement or implicit resume. */
|
|
103
|
+
async endSession(){host.endSession();await disconnect();}
|
|
104
|
+
});
|
|
88
105
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { DailyAttempt, DailyView } from "./daily-view.ts";
|
|
2
|
+
import { renderDailyDetails } from "./daily-view-render.ts";
|
|
3
|
+
|
|
4
|
+
export interface WorkPresentation {
|
|
5
|
+
outcome: string;
|
|
6
|
+
obligations: { digest: string; outcome: string; agent?: string; dependencies?: string[] }[];
|
|
7
|
+
}
|
|
8
|
+
export interface PanelAction { key: string; label: string; operation?: string; requestDigest?: string }
|
|
9
|
+
export interface PanelOptions {
|
|
10
|
+
details?: boolean;
|
|
11
|
+
presentation?: WorkPresentation | null;
|
|
12
|
+
connected?: boolean;
|
|
13
|
+
error?: string | null;
|
|
14
|
+
controls?: unknown;
|
|
15
|
+
control?: string;
|
|
16
|
+
actions?: readonly { choice: string; label: string }[];
|
|
17
|
+
learning?: string[];
|
|
18
|
+
}
|
|
19
|
+
export const panelText = (value: unknown): string => String(value ?? "unknown").replace(/[\p{Cc}\p{Cf}]/gu, " ").replace(/\s+/g, " ").trim();
|
|
20
|
+
/** Conservative terminal-cell bound: non-ASCII glyphs take two cells; no ANSI/control bytes survive. */
|
|
21
|
+
export function panelLines(lines: readonly string[], width = 80): string {
|
|
22
|
+
const columns = Number.isFinite(width) ? Math.max(12, Math.min(240, Math.floor(width))) : 80;
|
|
23
|
+
return lines.flatMap(line => {
|
|
24
|
+
let row = "", cells = 0; const rows: string[] = [];
|
|
25
|
+
for (const char of panelText(line)) {
|
|
26
|
+
const size = char.codePointAt(0)! > 127 ? 2 : 1;
|
|
27
|
+
if (cells + size > columns) { rows.push(row); row = ""; cells = 0; }
|
|
28
|
+
row += char; cells += size;
|
|
29
|
+
}
|
|
30
|
+
rows.push(row); return rows;
|
|
31
|
+
}).join("\n");
|
|
32
|
+
}
|
|
33
|
+
export function learningSummary(value: unknown): string[] {
|
|
34
|
+
if (!value || typeof value !== "object") return ["Not connected — /grants learning"];
|
|
35
|
+
const v=value as {state?:string;reason?:string;cases?:unknown[];comparisons?:{state?:string;reason?:string;title?:string}[];trust?:{automaticExposure?:{mode?:string;reason?:string};reason?:string}};
|
|
36
|
+
if(v.state==="deferred")return [`Deferred: ${v.reason??"evidence unavailable"}`,"/grants learning to reconnect"];
|
|
37
|
+
if(!v.cases||!v.comparisons)return ["Retained learning linked — /grants learning"];
|
|
38
|
+
const reasons=v.comparisons.map(c=>`${c.title??"Comparison"}: ${c.reason??c.state??"not ready"}`);
|
|
39
|
+
return [`${v.cases.length} case batch(es); ${v.comparisons.length} comparison(s)`,...reasons.slice(0,2),`Automatic questions: ${v.trust?.automaticExposure?.mode??"silent"} — ${v.trust?.automaticExposure?.reason??v.trust?.reason??"not configured"}`,"/grants learning opens deliberate review; not earned exposure"];
|
|
40
|
+
}
|
|
41
|
+
const finished = (a: DailyAttempt) => ["completed", "failed"].includes(a.intentRuntime) || a.archive?.runtime === "terminal";
|
|
42
|
+
|
|
43
|
+
/** Ordinary presentation is not another source of authority. All history remains under Details. */
|
|
44
|
+
export function renderDailyPanel(view: DailyView, width = 80, options: PanelOptions = {}): string {
|
|
45
|
+
const controls = options.controls as { ordinary?: { admission?: string; children?: { target: { executionId: string }; state: string; abortRequested: boolean }[] }; dispatch?: { paused?: boolean } } | undefined;
|
|
46
|
+
const live = controls?.ordinary?.children ?? [];
|
|
47
|
+
const rows = new Map(view.attempts.map(a => [a.executionId, a]));
|
|
48
|
+
const activeIds = options.connected && controls?.ordinary ? live.filter(r => r.state === "active").map(r => r.target.executionId) : view.attempts.filter(a => !finished(a)).map(a => a.executionId);
|
|
49
|
+
const complete = view.attempts.filter(finished);
|
|
50
|
+
const paused = controls?.dispatch?.paused || controls?.ordinary?.admission === "held-by-original-owner";
|
|
51
|
+
const failed=complete.filter(a=>a.intentRuntime==="failed"||a.archive?.outcome?.failed);
|
|
52
|
+
const unhealthy = options.error || options.control === "failed" || options.control === "unknown" || live.some(row=>row.state==="unknown");
|
|
53
|
+
const short=(value:string,rows=1)=>{const limit=Math.max(12,width-4)*rows;return [...panelText(value)].length>limit?[...panelText(value)].slice(0,limit-3).join("")+"...":value;};
|
|
54
|
+
const status = unhealthy || (!activeIds.length&&failed.length) ? "Needs you" : activeIds.length ? "Running" : paused ? "Waiting" : !view.obligations.length ? "Waiting" : view.obligations.every(o => o.attempts.some(id => rows.get(id) && finished(rows.get(id)!))) ? "Finished" : "Waiting";
|
|
55
|
+
const lines = ["PI-DADDY", options.presentation?.outcome ? short(options.presentation.outcome,2) : (view.scope ? "Selected work — outcome text unavailable" : "No work selected"), "", status];
|
|
56
|
+
if (unhealthy) lines.push("Control or observation needs attention. Open Details; no action was retried.");
|
|
57
|
+
else if (!activeIds.length&&failed.length) lines.push(`${failed.length} attempt(s) failed. Inspect results in /grants work.`);
|
|
58
|
+
else if (activeIds.length) lines.push(`${activeIds.length} active agent${activeIds.length === 1 ? "" : "s"}${paused ? "; new work paused" : ""}`);
|
|
59
|
+
else if (paused) lines.push("New work is paused. Running work is not cancelled by pausing.");
|
|
60
|
+
else if (status === "Finished") lines.push("Attempts finished. Review results; completion is not acceptance.");
|
|
61
|
+
else lines.push(view.obligations.length ? "Ready for an explicit run in /grants work. No automatic dispatch." : "Declare work with /grants work.");
|
|
62
|
+
for (const id of activeIds.slice(0, 8)) {
|
|
63
|
+
const a = rows.get(id), obligation = view.obligations.find(o => o.attempts.includes(id));
|
|
64
|
+
const label = options.presentation?.obligations.find(o => o.digest === obligation?.obligation.digest);
|
|
65
|
+
const original = live.find(row => row.target.executionId === id);
|
|
66
|
+
lines.push(` ${activeIds.indexOf(id)+1}. ${label?.agent || "Agent"} — ${original?.abortRequested ? "Stopping (awaiting settlement)" : options.connected ? "Running" : "Last observed active; freshness unknown"}`,
|
|
67
|
+
` ${short(label?.outcome || "Attempt details pending capture")}`,
|
|
68
|
+
` ${a?.modelId || "model unknown"} / effort ${a?.effortId || "unknown"}`);
|
|
69
|
+
}
|
|
70
|
+
if (activeIds.length > 8) lines.push(` ${activeIds.length - 8} more active agents in Details`);
|
|
71
|
+
if (complete.length) lines.push(`Finished attempts: ${complete.length} (collapsed; Details)`);
|
|
72
|
+
if (view.obligations.length > 1) lines.push(`Work: ${view.obligations.length} obligations; /grants work to select or change setup`);
|
|
73
|
+
if (!options.connected) lines.push("Snapshot only — live connection unavailable. /grants host to connect.");
|
|
74
|
+
if (view.continuity === "reconnected-gap") lines.push("Reconnected — earlier observation gap retained.");
|
|
75
|
+
if (options.learning?.length) lines.push("", "Learning", ...options.learning);
|
|
76
|
+
if (options.actions) lines.push("", "Available actions", ...(options.actions.length ? options.actions.map(a => `${a.choice} ${short(a.label,2)}`) : ["None currently authorized"]));
|
|
77
|
+
lines.push("", "d Details / history", "Pi coordinates work; skill-harness retains learning evidence.");
|
|
78
|
+
const summary = panelLines(lines, width);
|
|
79
|
+
return options.details ? `${summary}\n\n${panelLines([options.presentation?.outcome??"",...(options.presentation?.obligations.map(o=>o.outcome)??[])],width)}\n${renderDailyDetails(view, width)}${options.error ? `\n${panelLines([options.error], width)}` : ""}` : summary;
|
|
80
|
+
}
|
package/src/daily-view-render.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { DailyView } from "./daily-view.ts";
|
|
2
|
+
export { renderDailyPanel as renderDailyView } from "./daily-panel.ts";
|
|
2
3
|
const clean = (value: unknown) => String(value ?? "unknown").replace(/[\p{Cc}\p{Cf}]/gu, " ").replace(/\s+/g, " ").trim();
|
|
3
4
|
/** Text-only, accessible labels. No command links, control actions, model calls or terminal input. */
|
|
4
|
-
export function
|
|
5
|
+
export function renderDailyDetails(view: DailyView, width = 100): string {
|
|
5
6
|
const lines = ["PI-DADDY — READ-ONLY DAILY VIEW", view.narrative,
|
|
6
7
|
`Scope: ${view.scope ? `${view.scope.id} revision ${view.scope.revision} digest ${view.scope.digest}` : "UNRESOLVED — select an exact P01 snapshot"}`,
|
|
7
8
|
`Acceptance authority: ${view.authority}; coverage: PARTIAL; freshness: SNAPSHOT / UNKNOWN`,
|
package/src/daily-view.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface DailyAttempt {
|
|
|
18
18
|
executionId: string; logicalChildId: string | null; intentRuntime: string;
|
|
19
19
|
archive: ArchiveExecution | null; sourceAvailability: Array<{ sha256: string; state: "available" | "missing" | "mismatch" | "error" }>;
|
|
20
20
|
issues: string[]; activeBranch: null;
|
|
21
|
+
/** Observed launch configuration, not a claim about provider-internal reasoning. */
|
|
22
|
+
modelId?: string | null; effortId?: string | null;
|
|
21
23
|
}
|
|
22
24
|
export interface DailyObligation {
|
|
23
25
|
key: string; intent: RevisionRef; obligation: RevisionRef; policy: RevisionRef;
|
|
@@ -41,6 +43,10 @@ function contextCopy(value: DailyViewOptions["workContext"]): WorkProjectionCont
|
|
|
41
43
|
if (projectWorkLedger("", copied).errors.some(e => e.code === "WORK_CONTEXT_INVALID")) throw new TypeError("invalid independent P01 context");
|
|
42
44
|
return copied;
|
|
43
45
|
}
|
|
46
|
+
function observedLabel(labels: readonly { modelId: string | null; effortId: string | null }[], key: "modelId" | "effortId"): string | null {
|
|
47
|
+
const values = [...new Set(labels.map(label => label[key]))];
|
|
48
|
+
return values.length === 1 ? values[0] : null;
|
|
49
|
+
}
|
|
44
50
|
const readers = new WeakSet<object>();
|
|
45
51
|
/** Only actual factory readers may carry continuity state through the dashboard; not arbitrary callbacks. */
|
|
46
52
|
export function isDailyViewReader(value: unknown): value is ReturnType<typeof createDailyViewReader> {
|
|
@@ -107,6 +113,8 @@ export function createDailyViewReader() {
|
|
|
107
113
|
for (const attempt of work.runtime?.attempts ?? []) attempts.set(attempt.executionId, {
|
|
108
114
|
executionId: attempt.executionId, logicalChildId: attempt.childId, intentRuntime: attempt.state, archive: null,
|
|
109
115
|
sourceAvailability: [], issues: attempt.problems.map(p => `P01:${p.code}`), activeBranch: null,
|
|
116
|
+
...(observedLabel(attempt.observedLabels, "modelId")!==null?{modelId:observedLabel(attempt.observedLabels,"modelId")}:{}),
|
|
117
|
+
...(observedLabel(attempt.observedLabels, "effortId")!==null?{effortId:observedLabel(attempt.observedLabels,"effortId")}:{}),
|
|
110
118
|
});
|
|
111
119
|
for (const entry of archive) {
|
|
112
120
|
const a = attempts.get(entry.executionId) ?? { executionId: entry.executionId, logicalChildId: null, intentRuntime: "unknown",
|