pi-daddy 0.41.0 → 0.43.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 +37 -0
- package/contracts/ledger-record/v1/fixtures/capability-decision.json +3 -1
- package/contracts/ledger-record/v1/fixtures/child-lifecycle.json +25 -0
- package/contracts/ledger-record/v1/fixtures/episode-cost-gate.json +14 -0
- package/contracts/ledger-record/v1/fixtures/episode-outcome.json +12 -0
- package/contracts/ledger-record/v1/fixtures/ledger-record.jsonl +6 -3
- package/contracts/ledger-record/v1/fixtures/session-config.json +14 -0
- package/contracts/ledger-record/v1/governance-event.schema.json +356 -0
- package/dist/advisors/advisor.d.ts +24 -1
- package/dist/advisors/advisor.d.ts.map +1 -1
- package/dist/advisors/advisor.js +19 -1
- package/dist/advisors/advisor.js.map +1 -1
- package/dist/advisors/decider.d.ts +11 -0
- package/dist/advisors/decider.d.ts.map +1 -1
- package/dist/advisors/decider.js.map +1 -1
- package/dist/advisors/jev.d.ts.map +1 -1
- package/dist/advisors/jev.js +32 -1
- package/dist/advisors/jev.js.map +1 -1
- package/dist/cli.d.ts +7 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +87 -1
- package/dist/cli.js.map +1 -1
- package/dist/executors/activity-session.d.ts +13 -0
- package/dist/executors/activity-session.d.ts.map +1 -1
- package/dist/executors/activity-session.js +44 -2
- package/dist/executors/activity-session.js.map +1 -1
- package/dist/governance/episode-cost-gate.d.ts +31 -0
- package/dist/governance/episode-cost-gate.d.ts.map +1 -0
- package/dist/governance/episode-cost-gate.js +112 -0
- package/dist/governance/episode-cost-gate.js.map +1 -0
- package/dist/governance/ledger-events.d.ts +108 -1
- package/dist/governance/ledger-events.d.ts.map +1 -1
- package/dist/governance/ledger-events.js +64 -0
- package/dist/governance/ledger-events.js.map +1 -1
- package/dist/governance/ledger-v3-validation.d.ts.map +1 -1
- package/dist/governance/ledger-v3-validation.js +124 -0
- package/dist/governance/ledger-v3-validation.js.map +1 -1
- package/dist/governance/ledger.d.ts +8 -2
- package/dist/governance/ledger.d.ts.map +1 -1
- package/dist/governance/ledger.js +18 -2
- package/dist/governance/ledger.js.map +1 -1
- package/dist/kernel/context-handoff.d.ts.map +1 -1
- package/dist/kernel/context-handoff.js +4 -1
- package/dist/kernel/context-handoff.js.map +1 -1
- package/dist/kernel/definitions.d.ts +5 -0
- package/dist/kernel/definitions.d.ts.map +1 -1
- package/dist/kernel/definitions.js +30 -1
- package/dist/kernel/definitions.js.map +1 -1
- package/dist/kernel/delegate-types.d.ts +4 -0
- package/dist/kernel/delegate-types.d.ts.map +1 -1
- package/dist/kernel/delegate-types.js.map +1 -1
- package/dist/kernel/delegate.d.ts.map +1 -1
- package/dist/kernel/delegate.js +20 -5
- package/dist/kernel/delegate.js.map +1 -1
- package/dist/kernel/env-names.d.ts +5 -0
- package/dist/kernel/env-names.d.ts.map +1 -1
- package/dist/kernel/env-names.js +9 -0
- package/dist/kernel/env-names.js.map +1 -1
- package/dist/kernel/grant-env.d.ts +2 -0
- package/dist/kernel/grant-env.d.ts.map +1 -1
- package/dist/kernel/grant-env.js +1 -0
- package/dist/kernel/grant-env.js.map +1 -1
- package/dist/kernel/propagation.d.ts +2 -2
- package/dist/kernel/propagation.d.ts.map +1 -1
- package/dist/kernel/propagation.js +7 -2
- package/dist/kernel/propagation.js.map +1 -1
- package/dist/products/dashboard-cli.d.ts +4 -0
- package/dist/products/dashboard-cli.d.ts.map +1 -1
- package/dist/products/dashboard-cli.js +32 -4
- package/dist/products/dashboard-cli.js.map +1 -1
- package/dist/products/dashboard-handshake.d.ts +8 -0
- package/dist/products/dashboard-handshake.d.ts.map +1 -1
- package/dist/products/dashboard-handshake.js +3 -0
- package/dist/products/dashboard-handshake.js.map +1 -1
- package/dist/products/dashboard-herdr.d.ts +4 -0
- package/dist/products/dashboard-herdr.d.ts.map +1 -1
- package/dist/products/dashboard-herdr.js +10 -2
- package/dist/products/dashboard-herdr.js.map +1 -1
- package/dist/products/dashboard-projection.d.ts.map +1 -1
- package/dist/products/dashboard-projection.js +13 -6
- package/dist/products/dashboard-projection.js.map +1 -1
- package/dist/products/dashboard-render.d.ts +10 -0
- package/dist/products/dashboard-render.d.ts.map +1 -1
- package/dist/products/dashboard-render.js +9 -0
- package/dist/products/dashboard-render.js.map +1 -1
- package/dist/products/dashboard-session-client.d.ts +17 -0
- package/dist/products/dashboard-session-client.d.ts.map +1 -0
- package/dist/products/dashboard-session-client.js +28 -0
- package/dist/products/dashboard-session-client.js.map +1 -0
- package/dist/products/episode-outcomes.d.ts +32 -0
- package/dist/products/episode-outcomes.d.ts.map +1 -0
- package/dist/products/episode-outcomes.js +280 -0
- package/dist/products/episode-outcomes.js.map +1 -0
- package/dist/products/episode-report.d.ts +59 -0
- package/dist/products/episode-report.d.ts.map +1 -0
- package/dist/products/episode-report.js +311 -0
- package/dist/products/episode-report.js.map +1 -0
- package/extensions/chain-ledger.ts +2 -0
- package/extensions/chain-plan.ts +4 -2
- package/extensions/context-shape.ts +19 -4
- package/extensions/dashboard-session-server.ts +86 -0
- package/extensions/definition-runtime.ts +93 -0
- package/extensions/delegate-chain.ts +31 -2
- package/extensions/delegation-ledger.ts +2 -0
- package/extensions/delegation.ts +24 -0
- package/extensions/effort-advice.ts +12 -0
- package/extensions/execute-child.ts +148 -9
- package/extensions/grants-command.ts +32 -2
- package/extensions/grants.ts +23 -1
- package/extensions/pruning-advice.ts +3 -1
- package/extensions/run-delegation.ts +54 -6
- package/extensions/session-model-prompt.ts +179 -0
- package/extensions/session.ts +104 -3
- package/package.json +1 -1
- package/src/advisors/advisor.ts +45 -2
- package/src/advisors/decider.ts +8 -0
- package/src/advisors/jev.ts +34 -1
- package/src/cli.ts +90 -2
- package/src/executors/activity-session.ts +56 -2
- package/src/governance/episode-cost-gate.ts +128 -0
- package/src/governance/ledger-events.ts +173 -1
- package/src/governance/ledger-v3-validation.ts +146 -0
- package/src/governance/ledger.ts +29 -1
- package/src/kernel/context-handoff.ts +5 -1
- package/src/kernel/definitions.ts +32 -1
- package/src/kernel/delegate-types.ts +4 -0
- package/src/kernel/delegate.ts +22 -5
- package/src/kernel/env-names.ts +10 -0
- package/src/kernel/grant-env.ts +3 -0
- package/src/kernel/propagation.ts +7 -0
- package/src/products/dashboard-cli.ts +32 -4
- package/src/products/dashboard-handshake.ts +5 -0
- package/src/products/dashboard-herdr.ts +15 -1
- package/src/products/dashboard-projection.ts +13 -6
- package/src/products/dashboard-render.ts +11 -0
- package/src/products/dashboard-session-client.ts +35 -0
- package/src/products/episode-outcomes.ts +317 -0
- package/src/products/episode-report.ts +372 -0
package/src/governance/ledger.ts
CHANGED
|
@@ -35,7 +35,14 @@ import { assertEpisodeId } from "../kernel/episode-id.ts";
|
|
|
35
35
|
import { assertLedgerV3Wire } from "./ledger-v3-validation.ts";
|
|
36
36
|
|
|
37
37
|
export const LEDGER_VERSION = 3 as const;
|
|
38
|
-
export const LEDGER_EVENT_KINDS = [
|
|
38
|
+
export const LEDGER_EVENT_KINDS = [
|
|
39
|
+
"capability_decision",
|
|
40
|
+
"workspace_lease",
|
|
41
|
+
"child_lifecycle",
|
|
42
|
+
"cost_gate",
|
|
43
|
+
"session_config",
|
|
44
|
+
"episode_outcome",
|
|
45
|
+
] as const;
|
|
39
46
|
export type LedgerEventKind = (typeof LEDGER_EVENT_KINDS)[number];
|
|
40
47
|
export const LEDGER_GATE_OUTCOMES = ["declined", "dismissed", "no-ui", "error"] as const;
|
|
41
48
|
export type LedgerGateOutcome = (typeof LEDGER_GATE_OUTCOMES)[number];
|
|
@@ -144,6 +151,10 @@ export interface GrantRecord extends LedgerEventBase {
|
|
|
144
151
|
* it. Absent for a `tools:`-style delegation, which has no definition.
|
|
145
152
|
*/
|
|
146
153
|
definitionDigest?: DefinitionDigest;
|
|
154
|
+
/** SHA-256 of the complete SKILL.md used by this child. */
|
|
155
|
+
definitionHash?: string;
|
|
156
|
+
/** Installed package version exposed for the definition, when available. */
|
|
157
|
+
definitionPackageVersion?: string;
|
|
147
158
|
/**
|
|
148
159
|
* The context handoff this child RECEIVED (ADR-0078): the mode whose capability survived, and what crossed.
|
|
149
160
|
* Absent means nothing crossed, which is the default and the overwhelming majority of records.
|
|
@@ -232,6 +243,8 @@ export function buildRecord(args: {
|
|
|
232
243
|
humanDenied?: boolean;
|
|
233
244
|
gateOutcome?: PromptOutcomeKind;
|
|
234
245
|
definitionDigest?: DefinitionDigest;
|
|
246
|
+
definitionHash?: string;
|
|
247
|
+
definitionPackageVersion?: string;
|
|
235
248
|
handoff?: GrantRecord["handoff"];
|
|
236
249
|
/** Where the child ran (ADR-0031). Required: the probe's answer survives nowhere else. */
|
|
237
250
|
executor: ExecutorKind;
|
|
@@ -255,6 +268,9 @@ export function buildRecord(args: {
|
|
|
255
268
|
if (args.definitionDigest && !/^[a-f0-9]{64}$/i.test(args.definitionDigest.sha256)) {
|
|
256
269
|
throw new TypeError("definitionDigest.sha256 must be a SHA-256 hex digest");
|
|
257
270
|
}
|
|
271
|
+
if (args.definitionHash && !/^[a-f0-9]{64}$/i.test(args.definitionHash)) {
|
|
272
|
+
throw new TypeError("definitionHash must be a SHA-256 hex digest");
|
|
273
|
+
}
|
|
258
274
|
// R-46: the scalar is a SUMMARY, emitted only when it cannot mislead. `buildRecord` derives it rather
|
|
259
275
|
// than accepting it, so a call site cannot supply one that disagrees with the map beside it.
|
|
260
276
|
const sources = args.approvalSources ?? {};
|
|
@@ -308,6 +324,8 @@ export function buildRecord(args: {
|
|
|
308
324
|
// stops being a signal.
|
|
309
325
|
...(args.gateOutcome && args.gateOutcome !== "granted" ? { gateOutcome: args.gateOutcome } : {}),
|
|
310
326
|
...(args.definitionDigest ? { definitionDigest: args.definitionDigest } : {}),
|
|
327
|
+
...(args.definitionHash ? { definitionHash: args.definitionHash } : {}),
|
|
328
|
+
...(args.definitionPackageVersion ? { definitionPackageVersion: args.definitionPackageVersion } : {}),
|
|
311
329
|
};
|
|
312
330
|
if (args.taskDigest !== undefined) assertLedgerV3Wire(record);
|
|
313
331
|
return record;
|
|
@@ -341,6 +359,10 @@ export function recordKindForEvent(event: { event?: string }): RecordKind {
|
|
|
341
359
|
return "lifecycle";
|
|
342
360
|
case "workspace_lease":
|
|
343
361
|
return "lease";
|
|
362
|
+
case "cost_gate":
|
|
363
|
+
case "session_config":
|
|
364
|
+
case "episode_outcome":
|
|
365
|
+
return "fact";
|
|
344
366
|
case "check_receipt": // retired kind, still imported from pre-format ledgers
|
|
345
367
|
return "check";
|
|
346
368
|
case "workflow_fact": // retired kind, still imported from pre-format ledgers
|
|
@@ -390,8 +412,14 @@ export {
|
|
|
390
412
|
WORKSPACE_LEASE_OUTCOMES,
|
|
391
413
|
WORKSPACE_RECOVERY_VALUES,
|
|
392
414
|
buildChildLifecycleEvent,
|
|
415
|
+
buildEpisodeCostGateEvent,
|
|
416
|
+
buildEpisodeOutcomeEvent,
|
|
417
|
+
buildSessionConfigEvent,
|
|
393
418
|
buildWorkspaceLeaseEvent,
|
|
394
419
|
type CapabilityDecisionEvent,
|
|
420
|
+
type EpisodeCostGateEvent,
|
|
421
|
+
type EpisodeOutcomeEvent,
|
|
422
|
+
type SessionConfigEvent,
|
|
395
423
|
type ChildLifecycleEvent,
|
|
396
424
|
type ChildLifecycleState,
|
|
397
425
|
type ChildProcessSignal,
|
|
@@ -114,7 +114,11 @@ export function parseContextRequest(raw: unknown): { request: ContextRequest } |
|
|
|
114
114
|
if (!Array.isArray(value.files) || value.files.some((path) => typeof path !== "string" || path.length === 0))
|
|
115
115
|
return { refusal: "context.files must be an array of non-empty paths" };
|
|
116
116
|
if (value.files.length > MAX_CONTEXT_FILES)
|
|
117
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
refusal:
|
|
119
|
+
`context.files may name at most ${MAX_CONTEXT_FILES} paths; choose fewer, or omit context when the child ` +
|
|
120
|
+
"can inspect the same workspace",
|
|
121
|
+
};
|
|
118
122
|
request.files = value.files as string[];
|
|
119
123
|
}
|
|
120
124
|
if (value.summary !== undefined) {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { createHash } from "node:crypto";
|
|
23
|
+
import { join } from "node:path";
|
|
23
24
|
import { readBoundedFile } from "./bounded-read.ts";
|
|
24
25
|
import { resolveSkillResources, skillResourceName } from "./skill-resources.ts";
|
|
25
26
|
import { CAPABILITY_NAMESPACE_PREFIXES } from "./capabilities.ts";
|
|
@@ -55,6 +56,10 @@ export function digestDefinition(definition: SkillDefinition): DefinitionDigest
|
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
export function isDefinitionPackageVersion(value: unknown): value is string {
|
|
60
|
+
return typeof value === "string" && /^[0-9A-Za-z][0-9A-Za-z.+_-]{0,127}$/.test(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
export interface SkillDefinition {
|
|
59
64
|
/** From the path, never the frontmatter — see `parseSkillDefinition`. */
|
|
60
65
|
name: string;
|
|
@@ -66,6 +71,10 @@ export interface SkillDefinition {
|
|
|
66
71
|
/** Everything after the frontmatter — the child's system prompt. */
|
|
67
72
|
body: string;
|
|
68
73
|
source: string;
|
|
74
|
+
/** SHA-256 of the complete SKILL.md, matching skill-harness's raw `source_hashes["SKILL.md"]`. */
|
|
75
|
+
sourceHash?: string;
|
|
76
|
+
/** Installed package version when Pi exposes one for this definition. */
|
|
77
|
+
packageVersion?: string;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
export interface DefinitionCeiling {
|
|
@@ -154,6 +163,7 @@ export function parseSkillDefinition(source: string, text: string): SkillDefinit
|
|
|
154
163
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
155
164
|
body: text.slice(match[0].length).trim(),
|
|
156
165
|
source,
|
|
166
|
+
sourceHash: createHash("sha256").update(text, "utf8").digest("hex"),
|
|
157
167
|
};
|
|
158
168
|
}
|
|
159
169
|
|
|
@@ -224,7 +234,8 @@ export async function loadDefinitions(
|
|
|
224
234
|
skipped?: (path: string, reason: string) => void,
|
|
225
235
|
): Promise<Map<string, SkillDefinition>> {
|
|
226
236
|
const definitions = new Map<string, SkillDefinition>();
|
|
227
|
-
|
|
237
|
+
const packageVersions = new Map<string, string | undefined>();
|
|
238
|
+
for (const { path, metadata } of (await resolveSkillResources(cwd)).skills) {
|
|
228
239
|
const read = await readBoundedFile(path, {
|
|
229
240
|
maxBytes: DEFINITION_MAX_BYTES,
|
|
230
241
|
timeoutMs: DEFINITION_READ_TIMEOUT_MS,
|
|
@@ -234,6 +245,26 @@ export async function loadDefinitions(
|
|
|
234
245
|
continue;
|
|
235
246
|
}
|
|
236
247
|
const parsed = parseSkillDefinition(path, read.text);
|
|
248
|
+
if (parsed && metadata.origin === "package" && metadata.baseDir) {
|
|
249
|
+
if (!packageVersions.has(metadata.baseDir)) {
|
|
250
|
+
const manifest = await readBoundedFile(join(metadata.baseDir, "package.json"), {
|
|
251
|
+
maxBytes: DEFINITION_MAX_BYTES,
|
|
252
|
+
timeoutMs: DEFINITION_READ_TIMEOUT_MS,
|
|
253
|
+
});
|
|
254
|
+
let version: string | undefined;
|
|
255
|
+
if (manifest.ok) {
|
|
256
|
+
try {
|
|
257
|
+
const candidate = JSON.parse(manifest.text).version;
|
|
258
|
+
if (isDefinitionPackageVersion(candidate)) version = candidate;
|
|
259
|
+
} catch {
|
|
260
|
+
// A package without readable version metadata still supplies a valid definition; omit the optional stamp.
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
packageVersions.set(metadata.baseDir, version);
|
|
264
|
+
}
|
|
265
|
+
const packageVersion = packageVersions.get(metadata.baseDir);
|
|
266
|
+
if (packageVersion) parsed.packageVersion = packageVersion;
|
|
267
|
+
}
|
|
237
268
|
if (!parsed) skipped?.(path, `${path} has no readable frontmatter with a description`);
|
|
238
269
|
// Shadowing is legitimate — a project override is SUPPOSED to win over a package's copy — but review
|
|
239
270
|
// pointed out it was the one remaining drop with no word said, in the very function being made loud.
|
|
@@ -219,6 +219,10 @@ export interface Delegation {
|
|
|
219
219
|
* identify — and absent on an ADR-0017 authorisation refusal, which is decided before the file is read.
|
|
220
220
|
*/
|
|
221
221
|
definitionDigest?: DefinitionDigest;
|
|
222
|
+
/** SHA-256 of the complete SKILL.md, matching skill-harness source hashes. */
|
|
223
|
+
definitionHash?: string;
|
|
224
|
+
/** Installed package version exposed for the definition, when available. */
|
|
225
|
+
definitionPackageVersion?: string;
|
|
222
226
|
/** Trusted SHA-256 of the exact model-authored task. The task text itself is never stored. */
|
|
223
227
|
taskDigest: string;
|
|
224
228
|
/** Non-authoritative external join metadata, snapshotted at planning time. */
|
package/src/kernel/delegate.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
inheritableGrant,
|
|
28
28
|
workspacePinEnv,
|
|
29
29
|
} from "./propagation.ts";
|
|
30
|
-
import { inheritApprovals, type InheritableApproval } from "./approval.ts";
|
|
30
|
+
import { DELEGATE_SUBJECT, inheritApprovals, type InheritableApproval } from "./approval.ts";
|
|
31
31
|
import { explainDoubledNamespace, suggestForUnknown, unknownCapabilities, type Catalog } from "./catalog.ts";
|
|
32
32
|
import { GovernanceRefusal, refusal, type RefusalCode, type StructuredRefusal } from "./refusals.ts";
|
|
33
33
|
import { contextCapability, isContextCapability, parseContextRequest, type ContextRequest } from "./context-handoff.ts";
|
|
@@ -36,7 +36,7 @@ import { resolveDelegationApproval } from "./delegation-approval.ts";
|
|
|
36
36
|
import type { Delegation, DelegationContext, DelegationRequest } from "./delegate-types.ts";
|
|
37
37
|
import { assertCapabilitiesArePropagatable } from "./capabilities.ts";
|
|
38
38
|
export type { Delegation, DelegationContext, DelegationRequest } from "./delegate-types.ts";
|
|
39
|
-
import { GOVERNANCE_ENV_KEYS } from "./env-names.ts";
|
|
39
|
+
import { ENV_CHILD_DEFINITION, ENV_CHILD_EPISODE, ENV_CHILD_EXECUTION, GOVERNANCE_ENV_KEYS } from "./env-names.ts";
|
|
40
40
|
|
|
41
41
|
export function planDelegation(request: DelegationRequest, ctx: DelegationContext): Delegation {
|
|
42
42
|
const childDepth = ctx.depth + 1;
|
|
@@ -102,6 +102,8 @@ export function planDelegation(request: DelegationRequest, ctx: DelegationContex
|
|
|
102
102
|
let requested: Capability[];
|
|
103
103
|
let systemPrompt: string | undefined;
|
|
104
104
|
let definitionDigest: DefinitionDigest | undefined;
|
|
105
|
+
let definitionHash: string | undefined;
|
|
106
|
+
let definitionPackageVersion: string | undefined;
|
|
105
107
|
/** The definition being spawned, hoisted so the gate below can name its authorising id (ADR-0024). */
|
|
106
108
|
let spawned: SkillDefinition | undefined;
|
|
107
109
|
|
|
@@ -160,7 +162,13 @@ export function planDelegation(request: DelegationRequest, ctx: DelegationContex
|
|
|
160
162
|
// ninth return could forget, there is one, and forgetting it is not expressible. The success return
|
|
161
163
|
// does not spread `empty`, so it names the field explicitly.
|
|
162
164
|
definitionDigest = digestDefinition(definition);
|
|
163
|
-
|
|
165
|
+
definitionHash = definition.sourceHash;
|
|
166
|
+
definitionPackageVersion = definition.packageVersion;
|
|
167
|
+
Object.assign(empty, {
|
|
168
|
+
definitionDigest,
|
|
169
|
+
...(definitionHash ? { definitionHash } : {}),
|
|
170
|
+
...(definitionPackageVersion ? { definitionPackageVersion } : {}),
|
|
171
|
+
});
|
|
164
172
|
|
|
165
173
|
const ceiling = ceilingForDefinition(definition);
|
|
166
174
|
if (ceiling.undeclared) {
|
|
@@ -389,8 +397,15 @@ export function planDelegation(request: DelegationRequest, ctx: DelegationContex
|
|
|
389
397
|
// process boundaries with no shared state.
|
|
390
398
|
if (ctx.fanoutBudget !== undefined) env[ENV_FANOUT] = String(ctx.fanoutBudget);
|
|
391
399
|
if (ctx.childSpawnId) env[ENV_PARENT_ID] = ctx.childSpawnId;
|
|
392
|
-
if (ctx.childExecutionId)
|
|
393
|
-
|
|
400
|
+
if (ctx.childExecutionId) {
|
|
401
|
+
env[ENV_EXECUTION_ID] = ctx.childExecutionId;
|
|
402
|
+
env[ENV_CHILD_EXECUTION] = ctx.childExecutionId;
|
|
403
|
+
}
|
|
404
|
+
if (ctx.episodeId) {
|
|
405
|
+
env[ENV_EPISODE_ID] = ctx.episodeId;
|
|
406
|
+
env[ENV_CHILD_EPISODE] = ctx.episodeId;
|
|
407
|
+
}
|
|
408
|
+
env[ENV_CHILD_DEFINITION] = request.agent ?? DELEGATE_SUBJECT;
|
|
394
409
|
if (ctx.gated.length > 0) env[ENV_GATED] = ctx.gated.join(",");
|
|
395
410
|
// Approvals ride down with the grant, but only ever for what this child actually received — so
|
|
396
411
|
// `approved ⊆ grant` holds at every level (ADR-0010). Written even when empty, so this object states
|
|
@@ -433,5 +448,7 @@ export function planDelegation(request: DelegationRequest, ctx: DelegationContex
|
|
|
433
448
|
...(correlation ? { correlation } : {}),
|
|
434
449
|
...(approvalBinding ? { approvalBinding } : {}),
|
|
435
450
|
...(definitionDigest ? { definitionDigest } : {}),
|
|
451
|
+
...(definitionHash ? { definitionHash } : {}),
|
|
452
|
+
...(definitionPackageVersion ? { definitionPackageVersion } : {}),
|
|
436
453
|
};
|
|
437
454
|
}
|
package/src/kernel/env-names.ts
CHANGED
|
@@ -24,6 +24,14 @@ export const ENV_FANOUT = "PI_DADDY_FANOUT";
|
|
|
24
24
|
export const ENV_PARENT_ID = "PI_DADDY_PARENT_ID";
|
|
25
25
|
export const ENV_EXECUTION_ID = "PI_DADDY_EXECUTION_ID";
|
|
26
26
|
export const ENV_EPISODE_ID = "PI_DADDY_EPISODE_ID";
|
|
27
|
+
export const ENV_EPISODE_COST_CEILING = "PI_DADDY_EPISODE_COST_CEILING";
|
|
28
|
+
|
|
29
|
+
// Child-facing work attribution. These duplicate no authority: they let work produced outside pi-daddy carry the
|
|
30
|
+
// same identities as the ledger records that governed it.
|
|
31
|
+
export const ENV_CHILD_EPISODE = "PI_DADDY_EPISODE";
|
|
32
|
+
export const ENV_CHILD_DEFINITION = "PI_DADDY_DEFINITION";
|
|
33
|
+
export const ENV_CHILD_EXECUTION = "PI_DADDY_EXECUTION";
|
|
34
|
+
export const CHILD_ATTRIBUTION_ENV_KEYS = [ENV_CHILD_EPISODE, ENV_CHILD_DEFINITION, ENV_CHILD_EXECUTION] as const;
|
|
27
35
|
|
|
28
36
|
// Operator preferences a child inherits unchanged. Still governance: they decide executor, deadlines, gates.
|
|
29
37
|
export const ENV_HERDR = "PI_DADDY_HERDR";
|
|
@@ -80,6 +88,8 @@ export const GOVERNANCE_ENV_KEYS: readonly string[] = Object.freeze([
|
|
|
80
88
|
ENV_PARENT_ID,
|
|
81
89
|
ENV_EXECUTION_ID,
|
|
82
90
|
ENV_EPISODE_ID,
|
|
91
|
+
ENV_EPISODE_COST_CEILING,
|
|
92
|
+
...CHILD_ATTRIBUTION_ENV_KEYS,
|
|
83
93
|
ENV_HERDR,
|
|
84
94
|
ENV_HERDR_WORKSPACE,
|
|
85
95
|
ENV_HERDR_KEEP_PANE,
|
package/src/kernel/grant-env.ts
CHANGED
|
@@ -140,6 +140,8 @@ export interface ProjectSettings {
|
|
|
140
140
|
ledger: string;
|
|
141
141
|
/** Gated by default (ADR-0012): closed under subsumption, so gating tool:write also gates tool:bash. */
|
|
142
142
|
gatedByDefault: Capability[];
|
|
143
|
+
/** Cumulative provider-reported USD cost at which the episode asks whether to continue. */
|
|
144
|
+
episodeCostCeiling: number;
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
export function buildProjectSettings(input: GrantEnvInput): ProjectSettings {
|
|
@@ -166,6 +168,7 @@ export function buildProjectSettings(input: GrantEnvInput): ProjectSettings {
|
|
|
166
168
|
cautions: [...input.cautions],
|
|
167
169
|
ledger: PROJECT_FILES.ledger,
|
|
168
170
|
gatedByDefault: ["tool:bash"],
|
|
171
|
+
episodeCostCeiling: 5,
|
|
169
172
|
};
|
|
170
173
|
}
|
|
171
174
|
|
|
@@ -32,6 +32,7 @@ import { WORKSPACE_WILDCARD } from "./resolve.ts";
|
|
|
32
32
|
import { inheritApprovals, type InheritableApproval } from "./approval.ts";
|
|
33
33
|
import { assertCapabilitiesArePropagatable } from "./capabilities.ts";
|
|
34
34
|
import {
|
|
35
|
+
CHILD_ATTRIBUTION_ENV_KEYS,
|
|
35
36
|
ENV_ADVISOR_MODEL,
|
|
36
37
|
ENV_ADVISOR_TASK_EGRESS,
|
|
37
38
|
ENV_ADVISOR_KEY,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
ENV_PARENT_ID,
|
|
42
43
|
ENV_EXECUTION_ID,
|
|
43
44
|
ENV_EPISODE_ID,
|
|
45
|
+
ENV_EPISODE_COST_CEILING,
|
|
44
46
|
ENV_DEPTH,
|
|
45
47
|
ENV_MAX_DEPTH,
|
|
46
48
|
ENV_GATED,
|
|
@@ -54,6 +56,7 @@ export {
|
|
|
54
56
|
ENV_PARENT_ID,
|
|
55
57
|
ENV_EXECUTION_ID,
|
|
56
58
|
ENV_EPISODE_ID,
|
|
59
|
+
ENV_EPISODE_COST_CEILING,
|
|
57
60
|
ENV_DEPTH,
|
|
58
61
|
ENV_MAX_DEPTH,
|
|
59
62
|
ENV_GATED,
|
|
@@ -102,6 +105,9 @@ export const GRANT_ENV_KEYS = [
|
|
|
102
105
|
ENV_PARENT_ID,
|
|
103
106
|
ENV_EXECUTION_ID,
|
|
104
107
|
ENV_EPISODE_ID,
|
|
108
|
+
ENV_EPISODE_COST_CEILING,
|
|
109
|
+
// Child attribution identifies this process itself, so session publication must not clear it. `mergeChildEnv`
|
|
110
|
+
// strips it separately before applying the next child's plan.
|
|
105
111
|
// ADR-0042: a child must never keep its parent's unnarrowed pin, so it is stripped like every other
|
|
106
112
|
// governance value and re-supplied only by the spawn plan.
|
|
107
113
|
ENV_WORKSPACE_PIN,
|
|
@@ -393,6 +399,7 @@ export function workspacePinEnv(
|
|
|
393
399
|
export function mergeChildEnv(parentEnv: NodeJS.ProcessEnv, planEnv: Record<string, string>): NodeJS.ProcessEnv {
|
|
394
400
|
const merged: NodeJS.ProcessEnv = { ...parentEnv };
|
|
395
401
|
for (const key of GRANT_ENV_KEYS) delete merged[key];
|
|
402
|
+
for (const key of CHILD_ATTRIBUTION_ENV_KEYS) delete merged[key];
|
|
396
403
|
return { ...merged, ...planEnv };
|
|
397
404
|
}
|
|
398
405
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `pi-daddy-dashboard` — the
|
|
3
|
+
* `pi-daddy-dashboard` — the view of one ledger or activity timeline plus session model controls.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Ledger and cost views are read-only. When the owning pi session supplies its private endpoint, model edits are
|
|
6
|
+
* sent back to that process and use the same in-memory mutation and audit path as `/grants models`.
|
|
7
7
|
*/
|
|
8
8
|
import { realpathSync } from "node:fs";
|
|
9
9
|
import { readFile } from "node:fs/promises";
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type TimelineFilter,
|
|
23
23
|
} from "./activity-timeline.ts";
|
|
24
24
|
import { createDashboardDisplayControls } from "./dashboard-display-controls.ts";
|
|
25
|
+
import { dashboardSessionRequest, type DashboardSessionSnapshot } from "./dashboard-session-client.ts";
|
|
25
26
|
import { adoptLegacyEnvironment, ENV_LEDGER, legacyEnvironmentWarning } from "../kernel/env-names.ts";
|
|
26
27
|
import { projectLedgerPath } from "../kernel/project-paths.ts";
|
|
27
28
|
|
|
@@ -29,6 +30,8 @@ export const DASHBOARD_PROTOCOL_VERSION = 1 as const;
|
|
|
29
30
|
export const ENV_DASHBOARD_LEDGER = ENV_LEDGER; // one ledger path variable for children and the dashboard (ADR-0076 PR 3b)
|
|
30
31
|
export const ENV_DASHBOARD_PROTOCOL = "PI_DADDY_DASHBOARD_PROTOCOL";
|
|
31
32
|
export const ENV_DASHBOARD_KEY = "PI_DADDY_DASHBOARD_KEY";
|
|
33
|
+
export const ENV_DASHBOARD_SESSION_SOCKET = "PI_DADDY_DASHBOARD_SESSION_SOCKET";
|
|
34
|
+
export const ENV_DASHBOARD_SESSION_TOKEN = "PI_DADDY_DASHBOARD_SESSION_TOKEN";
|
|
32
35
|
export const DASHBOARD_REFRESH_MS = 250;
|
|
33
36
|
|
|
34
37
|
export interface DashboardFrameOptions {
|
|
@@ -43,6 +46,7 @@ export interface DashboardFrameOptions {
|
|
|
43
46
|
activityDetail?: { taskKey: string; field: "prompt" | "final" };
|
|
44
47
|
activityAliases?: ActivityTimelineAliases;
|
|
45
48
|
now?: Date;
|
|
49
|
+
session?: DashboardSessionSnapshot;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
function shellQuote(value: string): string {
|
|
@@ -139,6 +143,8 @@ export async function dashboardFrame(options: DashboardFrameOptions): Promise<st
|
|
|
139
143
|
width: options.width,
|
|
140
144
|
details: options.details,
|
|
141
145
|
history: options.history,
|
|
146
|
+
modelRows: options.session?.rows,
|
|
147
|
+
episodeCost: options.session ? { cost: options.session.cost, ceiling: options.session.ceiling } : undefined,
|
|
142
148
|
});
|
|
143
149
|
return waiting ? `${rendered}\n\nwaiting for ledger ${ledgerPath}` : rendered;
|
|
144
150
|
}
|
|
@@ -175,6 +181,8 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
175
181
|
const rawProtocol = env[ENV_DASHBOARD_PROTOCOL]?.trim();
|
|
176
182
|
const protocol = rawProtocol === undefined || rawProtocol === "" ? undefined : Number(rawProtocol);
|
|
177
183
|
const key = env[ENV_DASHBOARD_KEY]?.trim();
|
|
184
|
+
const sessionSocket = env[ENV_DASHBOARD_SESSION_SOCKET]?.trim();
|
|
185
|
+
const sessionToken = env[ENV_DASHBOARD_SESSION_TOKEN]?.trim();
|
|
178
186
|
process.title = `pi-daddy-dashboard${key ? `:${key.slice(0, 12)}` : ""}`;
|
|
179
187
|
|
|
180
188
|
const display = createDashboardDisplayControls(cli.details, true);
|
|
@@ -182,6 +190,13 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
182
190
|
let notice = "";
|
|
183
191
|
let input: ReturnType<typeof createInterface> | null = null;
|
|
184
192
|
const draw = async (clear: boolean): Promise<void> => {
|
|
193
|
+
let session: DashboardSessionSnapshot | undefined;
|
|
194
|
+
if (sessionSocket && sessionToken)
|
|
195
|
+
try {
|
|
196
|
+
session = await dashboardSessionRequest(sessionSocket, sessionToken, { action: "get" });
|
|
197
|
+
} catch (error) {
|
|
198
|
+
notice ||= `session controls unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
199
|
+
}
|
|
185
200
|
const view = await dashboardFrame({
|
|
186
201
|
cwd,
|
|
187
202
|
ledgerPath,
|
|
@@ -190,6 +205,7 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
190
205
|
width: process.stdout.columns || 80,
|
|
191
206
|
...display.state,
|
|
192
207
|
activityAliases: display.aliases,
|
|
208
|
+
session,
|
|
193
209
|
});
|
|
194
210
|
const frame = notice ? `${notice}\n\n${view}` : view;
|
|
195
211
|
if (frame === previous && !clear) return;
|
|
@@ -217,8 +233,20 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
217
233
|
});
|
|
218
234
|
};
|
|
219
235
|
input.on("line", (line) => {
|
|
236
|
+
const trimmed = line.trim();
|
|
237
|
+
if (trimmed.startsWith("m ") && sessionSocket && sessionToken) {
|
|
238
|
+
void dashboardSessionRequest(sessionSocket, sessionToken, { action: "set", edits: trimmed.slice(2).trim() })
|
|
239
|
+
.then(() => {
|
|
240
|
+
notice = "session model defaults updated";
|
|
241
|
+
})
|
|
242
|
+
.catch((error) => {
|
|
243
|
+
notice = `model edit refused: ${error instanceof Error ? error.message : String(error)}`;
|
|
244
|
+
})
|
|
245
|
+
.finally(redraw);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
220
248
|
// Rule 8: a line the display controls do not understand is said so, never swallowed.
|
|
221
|
-
notice = display.input(line) ? "" : `not a display command: ${JSON.stringify(
|
|
249
|
+
notice = display.input(line) ? "" : `not a display command: ${JSON.stringify(trimmed)}. ${display.prompt()}`;
|
|
222
250
|
redraw();
|
|
223
251
|
});
|
|
224
252
|
const timer = setInterval(redraw, DASHBOARD_REFRESH_MS);
|
|
@@ -74,6 +74,7 @@ export interface DashboardHandshakeInput {
|
|
|
74
74
|
pluginRoot: string;
|
|
75
75
|
preferencePath: string;
|
|
76
76
|
paneStatePath: string;
|
|
77
|
+
sessionEndpoint?: { socketPath: string; token: string };
|
|
77
78
|
ui: DashboardHandshakeUI;
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -101,6 +102,7 @@ export async function offerDashboardHandshake(input: DashboardHandshakeInput): P
|
|
|
101
102
|
statePath: input.paneStatePath,
|
|
102
103
|
pluginRoot: input.pluginRoot,
|
|
103
104
|
allowInactive: true,
|
|
105
|
+
sessionEndpoint: input.sessionEndpoint,
|
|
104
106
|
});
|
|
105
107
|
input.ui.notify(`pi-daddy dashboard ${opened.kind} in pane ${opened.paneId} without changing focus.`, "info");
|
|
106
108
|
return "already-installed";
|
|
@@ -152,6 +154,7 @@ export async function offerDashboardHandshake(input: DashboardHandshakeInput): P
|
|
|
152
154
|
statePath: input.paneStatePath,
|
|
153
155
|
pluginRoot: input.pluginRoot,
|
|
154
156
|
allowInactive: true,
|
|
157
|
+
sessionEndpoint: input.sessionEndpoint,
|
|
155
158
|
});
|
|
156
159
|
input.ui.notify(`pi-daddy dashboard ${opened.kind} in pane ${opened.paneId} without changing focus.`, "info");
|
|
157
160
|
return "installed";
|
|
@@ -172,6 +175,7 @@ export interface DashboardCommandInput {
|
|
|
172
175
|
ledgerPath?: string;
|
|
173
176
|
pluginRoot: string;
|
|
174
177
|
paneStatePath: string;
|
|
178
|
+
sessionEndpoint?: { socketPath: string; token: string };
|
|
175
179
|
}
|
|
176
180
|
|
|
177
181
|
export type DashboardCommandResult =
|
|
@@ -211,6 +215,7 @@ export async function openDashboardCommand(input: DashboardCommandInput): Promis
|
|
|
211
215
|
cwd: input.cwd,
|
|
212
216
|
statePath: input.paneStatePath,
|
|
213
217
|
pluginRoot: input.pluginRoot,
|
|
218
|
+
sessionEndpoint: input.sessionEndpoint,
|
|
214
219
|
});
|
|
215
220
|
}
|
|
216
221
|
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
ENV_DASHBOARD_KEY,
|
|
7
7
|
ENV_DASHBOARD_LEDGER,
|
|
8
8
|
ENV_DASHBOARD_PROTOCOL,
|
|
9
|
+
ENV_DASHBOARD_SESSION_SOCKET,
|
|
10
|
+
ENV_DASHBOARD_SESSION_TOKEN,
|
|
9
11
|
DASHBOARD_PROTOCOL_VERSION,
|
|
10
12
|
} from "./dashboard-cli.ts";
|
|
11
13
|
import { parseReply, type HerdrExec } from "../executors/herdr-cli.ts";
|
|
@@ -291,6 +293,7 @@ export interface DashboardOpenInput {
|
|
|
291
293
|
pluginRoot?: string;
|
|
292
294
|
/** Installation handshake may open an explanatory pane before a ledger exists. */
|
|
293
295
|
allowInactive?: boolean;
|
|
296
|
+
sessionEndpoint?: { socketPath: string; token: string };
|
|
294
297
|
}
|
|
295
298
|
export interface DashboardOpenResult {
|
|
296
299
|
kind: "opened" | "reused";
|
|
@@ -308,7 +311,10 @@ export async function openOrReuseDashboard(input: DashboardOpenInput): Promise<D
|
|
|
308
311
|
const ledgerPath = input.ledgerPath.trim() ? resolve(input.cwd, input.ledgerPath) : "";
|
|
309
312
|
const cwd = resolve(input.cwd);
|
|
310
313
|
const key = createHash("sha256")
|
|
311
|
-
.update(
|
|
314
|
+
.update(
|
|
315
|
+
`${input.host.workspaceId}\0${input.host.tabId}\0${ledgerPath}\0${input.sessionEndpoint?.socketPath ?? ""}`,
|
|
316
|
+
"utf8",
|
|
317
|
+
)
|
|
312
318
|
.digest("hex");
|
|
313
319
|
await mkdir(dirname(input.statePath), { recursive: true });
|
|
314
320
|
|
|
@@ -368,6 +374,14 @@ export async function openOrReuseDashboard(input: DashboardOpenInput): Promise<D
|
|
|
368
374
|
"--cwd",
|
|
369
375
|
cwd,
|
|
370
376
|
...(ledgerPath ? ["--env", `${ENV_DASHBOARD_LEDGER}=${ledgerPath}`] : []),
|
|
377
|
+
...(input.sessionEndpoint
|
|
378
|
+
? [
|
|
379
|
+
"--env",
|
|
380
|
+
`${ENV_DASHBOARD_SESSION_SOCKET}=${input.sessionEndpoint.socketPath}`,
|
|
381
|
+
"--env",
|
|
382
|
+
`${ENV_DASHBOARD_SESSION_TOKEN}=${input.sessionEndpoint.token}`,
|
|
383
|
+
]
|
|
384
|
+
: []),
|
|
371
385
|
"--env",
|
|
372
386
|
`${ENV_DASHBOARD_PROTOCOL}=${DASHBOARD_PROTOCOL_VERSION}`,
|
|
373
387
|
"--env",
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
type LedgerV3Object as ObjectRecord,
|
|
9
9
|
} from "../governance/ledger-v3-validation.ts";
|
|
10
10
|
import { isLedgerCapabilityIdentifier, isLedgerDisplayIdentifier } from "../kernel/ledger-identifiers.ts";
|
|
11
|
+
import { LEDGER_EVENT_KINDS } from "../governance/ledger.ts";
|
|
11
12
|
import { readRecords } from "../governance/record.ts";
|
|
12
|
-
import { isRetiredLedgerEvent } from "../governance/ledger-v3-validation.ts";
|
|
13
13
|
|
|
14
14
|
export type DashboardState =
|
|
15
15
|
"authorised" | "starting" | "running" | "completed" | "failed" | "refused" | "incomplete" | "historical";
|
|
@@ -308,13 +308,20 @@ export function parseDashboardLedger(text: string, options: DashboardProjectionO
|
|
|
308
308
|
return;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
corrupt.push({ line, reason: invalid });
|
|
311
|
+
if (event.ledgerVersion !== 3 || !nonEmpty(event.event)) {
|
|
312
|
+
corrupt.push({ line, reason: validateV3(event) ?? "invalid ledger v3 event" });
|
|
314
313
|
return;
|
|
315
314
|
}
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
if (LEDGER_EVENT_KINDS.includes(event.event as (typeof LEDGER_EVENT_KINDS)[number])) {
|
|
316
|
+
const invalid = validateV3(event);
|
|
317
|
+
if (invalid) {
|
|
318
|
+
corrupt.push({ line, reason: invalid });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// The dashboard renders only delegation-tree events. Other current, retired, or future additive event kinds
|
|
323
|
+
// remain valid ledger history without acquiring tree identity fields they do not have.
|
|
324
|
+
if (!["capability_decision", "child_lifecycle", "workspace_lease"].includes(event.event)) {
|
|
318
325
|
orphanEvents += 1;
|
|
319
326
|
return;
|
|
320
327
|
}
|
|
@@ -8,6 +8,8 @@ export interface DashboardRenderOptions {
|
|
|
8
8
|
history?: boolean;
|
|
9
9
|
completedRoots?: number;
|
|
10
10
|
completedChildren?: number;
|
|
11
|
+
modelRows?: Array<{ definition: string; model: string; thinking: string; source: string }>;
|
|
12
|
+
episodeCost?: { cost: number; ceiling: number };
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
const ACTIVE = new Set<DashboardState>(["authorised", "starting", "running"]);
|
|
@@ -209,6 +211,15 @@ export function renderDashboard(projection: DashboardProjection, options: Dashbo
|
|
|
209
211
|
};
|
|
210
212
|
const index = treeIndex(projection.nodes);
|
|
211
213
|
const lines = [paint("PI-DADDY", 1, resolved.color), ""];
|
|
214
|
+
if (options.modelRows) {
|
|
215
|
+
lines.push(paint("SESSION MODELS", 1, resolved.color), "definition model thinking source");
|
|
216
|
+
for (const row of options.modelRows)
|
|
217
|
+
lines.push(`${clean(row.definition)} ${clean(row.model)} ${clean(row.thinking)} ${clean(row.source)}`);
|
|
218
|
+
lines.push("edit: m <definition> <provider:model> <thinking> | m all <provider:model> <thinking>");
|
|
219
|
+
if (options.episodeCost)
|
|
220
|
+
lines.push(`episode cost $${options.episodeCost.cost} / $${options.episodeCost.ceiling} ceiling`);
|
|
221
|
+
lines.push("");
|
|
222
|
+
}
|
|
212
223
|
|
|
213
224
|
const renderRoots = (roots: DashboardNode[]): void => {
|
|
214
225
|
const selected = selectedRoots(roots, index, resolved.completedRoots);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createConnection } from "node:net";
|
|
2
|
+
|
|
3
|
+
export interface DashboardSessionSnapshot {
|
|
4
|
+
rows: Array<{ definition: string; model: string; thinking: string; source: string }>;
|
|
5
|
+
cost: number;
|
|
6
|
+
ceiling: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function dashboardSessionRequest(
|
|
10
|
+
socketPath: string,
|
|
11
|
+
token: string,
|
|
12
|
+
request: { action: "get" } | { action: "set"; edits: string },
|
|
13
|
+
): Promise<DashboardSessionSnapshot> {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const socket = createConnection(socketPath);
|
|
16
|
+
let output = "";
|
|
17
|
+
socket.setEncoding("utf8");
|
|
18
|
+
socket.setTimeout(2_000, () => socket.destroy(new Error("dashboard session request timed out")));
|
|
19
|
+
socket.once("error", reject);
|
|
20
|
+
socket.on("data", (chunk) => {
|
|
21
|
+
output += chunk;
|
|
22
|
+
if (output.length > 64 * 1024) socket.destroy(new Error("dashboard session response too large"));
|
|
23
|
+
});
|
|
24
|
+
socket.once("end", () => {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(output) as DashboardSessionSnapshot & { ok?: boolean; error?: string };
|
|
27
|
+
if (!parsed.ok) throw new Error(parsed.error ?? "dashboard session request failed");
|
|
28
|
+
resolve(parsed);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
reject(error);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
socket.once("connect", () => socket.write(`${JSON.stringify({ ...request, token })}\n`));
|
|
34
|
+
});
|
|
35
|
+
}
|