pi-daddy 0.48.3 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +53 -0
  3. package/dist/cli.d.ts +3 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +18 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/kernel/selected-definitions.d.ts.map +1 -1
  12. package/dist/kernel/selected-definitions.js +6 -3
  13. package/dist/kernel/selected-definitions.js.map +1 -1
  14. package/dist/products/dashboard-cli.d.ts.map +1 -1
  15. package/dist/products/dashboard-cli.js +46 -1
  16. package/dist/products/dashboard-cli.js.map +1 -1
  17. package/dist/products/dashboard-display-controls.d.ts +1 -1
  18. package/dist/products/dashboard-display-controls.d.ts.map +1 -1
  19. package/dist/products/dashboard-display-controls.js +11 -3
  20. package/dist/products/dashboard-display-controls.js.map +1 -1
  21. package/dist/products/dashboard-screen.d.ts +3 -1
  22. package/dist/products/dashboard-screen.d.ts.map +1 -1
  23. package/dist/products/dashboard-screen.js +21 -8
  24. package/dist/products/dashboard-screen.js.map +1 -1
  25. package/dist/products/dashboard-session-client.d.ts +9 -0
  26. package/dist/products/dashboard-session-client.d.ts.map +1 -1
  27. package/dist/products/dashboard-session-client.js.map +1 -1
  28. package/dist/products/dashboard-settings.d.ts +28 -0
  29. package/dist/products/dashboard-settings.d.ts.map +1 -0
  30. package/dist/products/dashboard-settings.js +42 -0
  31. package/dist/products/dashboard-settings.js.map +1 -0
  32. package/dist/products/diagnostic-export.d.ts +23 -0
  33. package/dist/products/diagnostic-export.d.ts.map +1 -0
  34. package/dist/products/diagnostic-export.js +264 -0
  35. package/dist/products/diagnostic-export.js.map +1 -0
  36. package/dist/products/diagnostic-filter.d.ts +7 -0
  37. package/dist/products/diagnostic-filter.d.ts.map +1 -0
  38. package/dist/products/diagnostic-filter.js +135 -0
  39. package/dist/products/diagnostic-filter.js.map +1 -0
  40. package/extensions/dashboard-session-server.ts +17 -2
  41. package/extensions/dashboard-settings.ts +149 -0
  42. package/extensions/delegation-operations.ts +81 -2
  43. package/extensions/execute-child.ts +9 -11
  44. package/extensions/execution-controls.ts +7 -2
  45. package/extensions/grants-command.ts +3 -1
  46. package/extensions/grants.ts +3 -0
  47. package/extensions/reload-environment.ts +3 -0
  48. package/extensions/run-delegation.ts +3 -1
  49. package/extensions/runtime-snapshot.ts +9 -1
  50. package/package.json +1 -1
  51. package/src/cli.ts +24 -1
  52. package/src/index.ts +2 -0
  53. package/src/kernel/selected-definitions.ts +5 -3
  54. package/src/products/dashboard-cli.ts +38 -1
  55. package/src/products/dashboard-display-controls.ts +10 -4
  56. package/src/products/dashboard-screen.ts +21 -10
  57. package/src/products/dashboard-session-client.ts +7 -1
  58. package/src/products/dashboard-settings.ts +72 -0
  59. package/src/products/diagnostic-export.ts +288 -0
  60. package/src/products/diagnostic-filter.ts +140 -0
@@ -5,6 +5,7 @@ import { openRuntimeSettlement, settlementHash } from "../src/governance/runtime
5
5
  import { reconcileDelegationCapacity } from "./session-capacity.ts";
6
6
  import { agentDir } from "../src/kernel/project-paths.ts";
7
7
  import type { GrantsSession } from "./session.ts";
8
+ import { bindOperationFinals, operationFinal } from "./delegation-operations.ts";
8
9
  export const OPERATION_STATUS_EVENT = "pi-daddy:operation-status:v1";
9
10
  export const RUNTIME_SNAPSHOT_EVENT = "pi-daddy:runtime-snapshot:v1";
10
11
  export function registerRuntimeSnapshot(
@@ -99,6 +100,7 @@ export function registerRuntimeSnapshot(
99
100
  sessionId?: string;
100
101
  cwd?: string;
101
102
  operationId?: string;
103
+ includeFinal?: boolean;
102
104
  reply?: (value: unknown) => void;
103
105
  };
104
106
  if (
@@ -139,7 +141,12 @@ export function registerRuntimeSnapshot(
139
141
  lifecycle.runtimeSnapshotGeneration !== generation
140
142
  )
141
143
  throw Error("operation owner changed during read");
142
- request.reply!({ ...envelope, qualified: true, operation });
144
+ request.reply!({
145
+ ...envelope,
146
+ qualified: true,
147
+ operation,
148
+ ...(request.includeFinal === true && operation ? { final: operationFinal(session, operation) } : {}),
149
+ });
143
150
  } catch (error) {
144
151
  request.reply!({ ...envelope, qualified: false, operation: null, reason: String(error) });
145
152
  }
@@ -172,6 +179,7 @@ export function registerRuntimeSnapshot(
172
179
  }
173
180
  if (sequence !== bindSequence || session.reloadLifecycle !== lifecycle) return;
174
181
  lifecycle.runtimeSettlement = runtime;
182
+ bindOperationFinals(session, sessionId, cwd);
175
183
  bound = { sessionId, cwd, lifecycle: session.reloadLifecycle };
176
184
  } catch (error) {
177
185
  if (sequence === bindSequence && session.reloadLifecycle === lifecycle) problem = String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-daddy",
3
- "version": "0.48.3",
3
+ "version": "0.49.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",
package/src/cli.ts CHANGED
@@ -50,6 +50,7 @@ Usage:
50
50
  pi-daddy report [--since <date>] [--definition <name>] [--model <id>]
51
51
  [--group-by definition|model|thinking] [--json]
52
52
  pi-daddy outcomes append changed episode outcomes from Git and CI
53
+ pi-daddy diagnostics export <selection.json> <new-directory> private filtered local evidence
53
54
  pi-daddy --help | --version
54
55
 
55
56
  init references skills already enabled in Pi at their installed or local paths. Legacy unregistered npm
@@ -61,8 +62,11 @@ stays unspawnable. Capabilities that can change your machine
61
62
  It never rewrites ${SETTINGS_REL} — delete that file if you want it regenerated.`;
62
63
 
63
64
  export interface ParsedArgs {
64
- command: "init" | "ledger-repair" | "ledger-import" | "report" | "outcomes" | "help" | "version";
65
+ command:
66
+ "init" | "ledger-repair" | "ledger-import" | "report" | "outcomes" | "diagnostics-export" | "help" | "version";
65
67
  importTarget?: string;
68
+ selectionPath?: string;
69
+ exportDirectory?: string;
66
70
  /** `ledger repair <path>`: the ledger file; `yes` applies, otherwise preview only. */
67
71
  ledgerPath?: string;
68
72
  yes?: boolean;
@@ -91,6 +95,19 @@ export function parseArgs(argv: string[]): ParsedArgs {
91
95
  if (args.includes("--version") || args.includes("-v")) return { command: "version", force: false, errors: [] };
92
96
 
93
97
  const [command, ...tail] = args;
98
+ if (command === "diagnostics") {
99
+ const [verb, selectionPath, exportDirectory, ...extra] = tail;
100
+ if (
101
+ verb !== "export" ||
102
+ !selectionPath ||
103
+ !exportDirectory ||
104
+ selectionPath.startsWith("-") ||
105
+ exportDirectory.startsWith("-") ||
106
+ extra.length
107
+ )
108
+ return { command: "help", force: false, errors: ["diagnostics needs: export <selection.json> <new-directory>"] };
109
+ return { command: "diagnostics-export", force: false, errors: [], selectionPath, exportDirectory };
110
+ }
94
111
  if (command === "ledger") {
95
112
  // ADR-0076 PR 3d: `ledger repair <path> [--yes]` previews, --yes truncates; `ledger import <source> <target>`.
96
113
  const [verb, target, ...flags] = tail;
@@ -388,6 +405,12 @@ export async function main(
388
405
  console.log(USAGE);
389
406
  return 0;
390
407
  }
408
+ if (parsed.command === "diagnostics-export") {
409
+ const { exportDiagnostics } = await import("./products/diagnostic-export.ts");
410
+ const result = await exportDiagnostics(parsed.selectionPath!, parsed.exportDirectory!);
411
+ console.log(JSON.stringify(result));
412
+ return result.missing ? 1 : 0;
413
+ }
391
414
  if (parsed.command === "outcomes") {
392
415
  const cwd = process.cwd();
393
416
  const result = await updateEpisodeOutcomes({
package/src/index.ts CHANGED
@@ -191,3 +191,5 @@ export {
191
191
  type WorkspacePins,
192
192
  type ParsedPin,
193
193
  } from "./kernel/workspace-pin.ts";
194
+
195
+ export { exportDiagnostics, parseDiagnosticSelection, type DiagnosticSelection } from "./products/diagnostic-export.ts";
@@ -12,7 +12,8 @@ export interface SelectedCommand {
12
12
  name: string;
13
13
  sourceInfo?: { path?: string };
14
14
  }
15
- const phases = ["plan", "build", "review", "debug", "investigate"];
15
+ const requiredPhases = ["plan", "build", "review", "debug", "investigate"];
16
+ const phases = [...requiredPhases, "test-review"];
16
17
  const hash = (value: string) => createHash("sha256").update(value, "utf8").digest("hex");
17
18
  const object = (x: unknown): x is Record<string, unknown> => !!x && typeof x === "object" && !Array.isArray(x);
18
19
  const exact = (x: Record<string, unknown>, keys: string[]) =>
@@ -57,10 +58,10 @@ async function principalBinding(
57
58
  manifest.version !== 1 ||
58
59
  manifest.package !== "principal-pi-skills" ||
59
60
  !object(manifest.bindings) ||
60
- !exact(manifest.bindings, phases)
61
+ !exact(manifest.bindings, Object.hasOwn(manifest.bindings, "test-review") ? phases : requiredPhases)
61
62
  )
62
63
  throw Error("invalid Principal binding manifest");
63
- for (const name of phases) {
64
+ for (const name of Object.keys(manifest.bindings)) {
64
65
  const row = manifest.bindings[name];
65
66
  if (
66
67
  !object(row) ||
@@ -74,6 +75,7 @@ async function principalBinding(
74
75
  )
75
76
  throw Error("invalid Principal binding row");
76
77
  }
78
+ if (!Object.hasOwn(manifest.bindings, phase)) throw Error("selected Principal phase has no binding row");
77
79
  const row = manifest.bindings[phase] as Record<string, string>;
78
80
  const agentPath = join(root, row.agent);
79
81
  if (
@@ -376,6 +376,17 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
376
376
  notice = (await connection.change({ action: "set", edits: trimmed.slice(2).trim() }))
377
377
  ? "Session models updated"
378
378
  : connection.state.error || "Session controls unavailable";
379
+ } else if (trimmed.startsWith("limit ")) {
380
+ const edit = /^limit (wall|idle) ([0-9]+)$/.exec(trimmed);
381
+ if (!edit) notice = "Use limit wall <seconds> or limit idle <seconds>; 0 restores default";
382
+ else
383
+ notice = (await connection.change({
384
+ action: "set-limit",
385
+ key: edit[1] as "wall" | "idle",
386
+ seconds: Number(edit[2]),
387
+ }))
388
+ ? "Session limit updated for future children; running children unchanged"
389
+ : connection.state.error || "Settings unavailable";
379
390
  } else notice = display.input(line) ? "" : `Unknown command: ${trimmed}. Press ? for help.`;
380
391
  redraw();
381
392
  };
@@ -397,7 +408,7 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
397
408
  redraw();
398
409
  return;
399
410
  }
400
- const action = display.key(
411
+ let action = display.key(
401
412
  key.name === "return"
402
413
  ? "return"
403
414
  : ["up", "down", "escape", "tab"].includes(key.name || "")
@@ -409,6 +420,32 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
409
420
  return;
410
421
  }
411
422
  if (action === "command") command = "";
423
+ if (action === "setting") {
424
+ const selected = display.screen.settingKey ?? "auto";
425
+ const settings = connection.state.snapshot?.settings;
426
+ if (!settings?.editable) notice = "Settings can only be changed in the connected owning parent";
427
+ else if (selected === "auto") action = "auto";
428
+ else if (selected === "wall" || selected === "idle") command = `limit ${selected} `;
429
+ else if (selected === "descendants") notice = "Startup only: set PI_DADDY_FANOUT before restarting Pi";
430
+ else if (selected === "depth") notice = "Startup only: set PI_DADDY_MAX_DEPTH before restarting Pi";
431
+ else if (selected === "perCall") notice = "Children per call is a fixed safety bound";
432
+ else if (!settings.jev.available)
433
+ notice = "JEV controls unavailable; load skill-harness in the parent Pi session";
434
+ else {
435
+ void connection
436
+ .change({ action: "set-jev", enabled: !(settings.jev.enabled || settings.jev.pending) })
437
+ .then((changed) => {
438
+ const jev = connection.state.snapshot?.settings?.jev;
439
+ notice = !changed
440
+ ? connection.state.error || "JEV change already pending"
441
+ : jev?.pending
442
+ ? "Complete JEV paid-call and LoRA-storage choices in the parent Pi session"
443
+ : jev?.error ||
444
+ `JEV ${jev?.enabled ? "enabled" : "disabled"}; ${jev?.availability ?? "status unknown"}`;
445
+ redraw();
446
+ });
447
+ }
448
+ }
412
449
  if (action === "auto") {
413
450
  const snapshot = connection.state.snapshot;
414
451
  if (!snapshot) notice = "Daddy permission controls unavailable";
@@ -1,3 +1,4 @@
1
+ import { DASHBOARD_SETTING_KEYS } from "./dashboard-settings.ts";
1
2
  /** Local display controls; these never dispatch actions to an owner or change ledger state. */
2
3
  import { dashboardScreenState } from "./dashboard-screen.ts";
3
4
  import { ActivityTimelineAliases } from "./activity-timeline.ts";
@@ -16,16 +17,17 @@ export function createDashboardDisplayControls(details: boolean, ledger: boolean
16
17
  return {
17
18
  state,
18
19
  screen,
19
- key(key: string): "auto" | "quit" | "command" | undefined {
20
+ key(key: string): "auto" | "quit" | "command" | "setting" | undefined {
20
21
  if (key === "a") return "auto";
21
22
  if (key === "q" || key === "ctrl-c") return "quit";
22
23
  if (key === ":") return "command";
24
+ if (screen.view === "settings" && (key === "return" || key === "d")) return "setting";
23
25
  if (key === "escape") {
24
26
  screen.view = screen.view === "version-details" ? "versions" : "main";
25
27
  state.activityDetail = undefined;
26
28
  screen.offset = 0;
27
- } else if (key === "?" || key === "m" || key === "v") {
28
- screen.view = key === "?" ? "help" : key === "v" ? "versions" : "models";
29
+ } else if (key === "?" || key === "m" || key === "v" || key === "s") {
30
+ screen.view = key === "?" ? "help" : key === "v" ? "versions" : key === "s" ? "settings" : "models";
29
31
  state.activityDetail = undefined;
30
32
  screen.offset = 0;
31
33
  } else if (key === "return" || key === "d") {
@@ -33,7 +35,11 @@ export function createDashboardDisplayControls(details: boolean, ledger: boolean
33
35
  screen.offset = 0;
34
36
  } else if (["up", "down", "j", "k"].includes(key)) {
35
37
  const delta = key === "up" || key === "k" ? -1 : 1;
36
- if (screen.view === "versions") {
38
+ if (screen.view === "settings") {
39
+ const index = DASHBOARD_SETTING_KEYS.indexOf(screen.settingKey ?? "auto");
40
+ screen.settingKey =
41
+ DASHBOARD_SETTING_KEYS[Math.max(0, Math.min(DASHBOARD_SETTING_KEYS.length - 1, index + delta))];
42
+ } else if (screen.view === "versions") {
37
43
  const rows = screen.versionRows ?? [],
38
44
  index = rows.findIndex((row) => row.id === screen.versionId);
39
45
  screen.versionId = rows[Math.max(0, Math.min(rows.length - 1, index + delta))]?.id;
@@ -1,3 +1,4 @@
1
+ import { dashboardSettingsLines, type DashboardSettingKey } from "./dashboard-settings.ts";
1
2
  /** Height-bounded, task-first view. Permission state is always supplied by the owner. */
2
3
  import type { EcosystemVersionRow } from "./ecosystem-versions.ts";
3
4
  import type { DashboardProjection } from "./dashboard-projection.ts";
@@ -19,7 +20,8 @@ export interface DashboardItem {
19
20
  agent?: boolean;
20
21
  }
21
22
  export interface DashboardScreenState {
22
- view: "main" | "details" | "models" | "help" | "versions" | "version-details";
23
+ view: "main" | "details" | "models" | "help" | "versions" | "version-details" | "settings";
24
+ settingKey?: DashboardSettingKey;
23
25
  versionId?: EcosystemVersionRow["id"];
24
26
  versionRows?: EcosystemVersionRow[];
25
27
  selectedKey?: string;
@@ -204,12 +206,14 @@ export function renderDashboardScreen(options: DashboardScreenOptions): string {
204
206
  const footer =
205
207
  options.command !== undefined
206
208
  ? [`: ${clean(options.command)}`]
207
- : screen.view === "main" || screen.view === "versions"
208
- ? ["↑↓ select · Enter details · a Auto", "m models · v versions · Tab filter · ?"]
209
- : [
210
- "↑↓ scroll · Esc back · a Auto",
211
- screen.view === "details" ? "p prompt · f final · : command · ? help" : ": command · ? help",
212
- ];
209
+ : screen.view === "settings"
210
+ ? ["↑↓ select · Enter toggle/edit · Esc back", "0 restores a default limit · s settings"]
211
+ : screen.view === "main" || screen.view === "versions"
212
+ ? ["↑↓ select · Enter details · a Auto", "m models · v versions · s settings · ?"]
213
+ : [
214
+ "↑↓ scroll · Esc back · a Auto",
215
+ screen.view === "details" ? "p prompt · f final · : command · ? help" : ": command · ? help",
216
+ ];
213
217
  const fixed = [
214
218
  header,
215
219
  session
@@ -223,7 +227,7 @@ export function renderDashboardScreen(options: DashboardScreenOptions): string {
223
227
  "DASHBOARD KEYS",
224
228
  "↑/↓ or j/k: select; Enter/d: details",
225
229
  "a: toggle Daddy permission Auto",
226
- "m: session models; v: ecosystem versions",
230
+ "m: session models; v: ecosystem versions; s: settings",
227
231
  "h: show/hide finished history",
228
232
  "Tab: Everything / Agents / Skills / Needs-you",
229
233
  "p/f: selected retained prompt/final",
@@ -234,7 +238,9 @@ export function renderDashboardScreen(options: DashboardScreenOptions): string {
234
238
  "Auto does not enable JEV or change LoRA consent.",
235
239
  "Finished means observed completion, not acceptance.",
236
240
  );
237
- else if (screen.view === "versions" || screen.view === "version-details") {
241
+ else if (screen.view === "settings") {
242
+ body.push(...dashboardSettingsLines(session?.settings, session?.auto.enabled, screen.settingKey ?? "auto"));
243
+ } else if (screen.view === "versions" || screen.view === "version-details") {
238
244
  body.push("VERSIONS");
239
245
  if (!session?.versions) body.push("Owner has not reported versions.");
240
246
  else if (screen.view === "versions") {
@@ -323,6 +329,11 @@ export function renderDashboardScreen(options: DashboardScreenOptions): string {
323
329
  if (row < screen.offset) screen.offset = row;
324
330
  else if (row + 1 >= screen.offset + room) screen.offset = Math.max(0, row + 2 - room);
325
331
  }
332
+ if (screen.view === "settings" && room > 0) {
333
+ const row = detailBody.findIndex((line) => line.startsWith(">"));
334
+ if (row < screen.offset) screen.offset = Math.max(0, row);
335
+ else if (row >= screen.offset + room) screen.offset = Math.max(0, row - room + 1);
336
+ }
326
337
  const offset =
327
338
  screen.view === "main" ? 0 : Math.max(0, Math.min(screen.offset, Math.max(0, detailBody.length - room)));
328
339
  screen.offset = offset;
@@ -337,7 +348,7 @@ export function renderDashboardScreen(options: DashboardScreenOptions): string {
337
348
  const safe = truncate(dashboardText(line), width);
338
349
  if (!options.color) return safe;
339
350
  const section =
340
- /^(CURRENT REQUEST|LATEST REQUEST|CURRENT WORK|NEEDS YOU|NEEDS ATTENTION|WORK IN PROGRESS|HISTORY|VERSIONS|SESSION MODELS|DASHBOARD KEYS|MANAGE)/.test(
351
+ /^(CURRENT REQUEST|LATEST REQUEST|CURRENT WORK|NEEDS YOU|NEEDS ATTENTION|WORK IN PROGRESS|HISTORY|VERSIONS|SETTINGS|STARTUP LIMITS|SESSION MODELS|DASHBOARD KEYS|MANAGE)/.test(
341
352
  safe,
342
353
  );
343
354
  const code =
@@ -1,3 +1,4 @@
1
+ import type { DashboardSettingsSnapshot } from "./dashboard-settings.ts";
1
2
  import type { EcosystemVersions } from "./ecosystem-versions.ts";
2
3
  import { createConnection } from "node:net";
3
4
 
@@ -6,12 +7,17 @@ export interface DashboardSessionSnapshot {
6
7
  cost: number | null;
7
8
  activity?: { rootId: string; path: string; taskId: string };
8
9
  versions?: EcosystemVersions;
10
+ settings?: DashboardSettingsSnapshot;
9
11
  auto: { enabled: boolean; source: "default" | "environment" | "session" };
10
12
  pendingApprovals: Array<{ id: string; subject: string; capability: string }>;
11
13
  }
12
14
 
13
15
  export type DashboardSessionAction =
14
- { action: "get" } | { action: "set"; edits: string } | { action: "set-auto"; enabled: boolean };
16
+ | { action: "get" }
17
+ | { action: "set"; edits: string }
18
+ | { action: "set-auto"; enabled: boolean }
19
+ | { action: "set-jev"; enabled: boolean }
20
+ | { action: "set-limit"; key: "wall" | "idle"; seconds: number };
15
21
 
16
22
  export async function dashboardSessionRequest(
17
23
  socketPath: string,
@@ -0,0 +1,72 @@
1
+ /** Settings are owner observations; rendering never grants consent or changes execution. */
2
+ export type DashboardSettingKey = "auto" | "jev" | "wall" | "idle" | "descendants" | "depth" | "perCall";
3
+ export const DASHBOARD_SETTING_KEYS: DashboardSettingKey[] = [
4
+ "auto",
5
+ "jev",
6
+ "wall",
7
+ "idle",
8
+ "descendants",
9
+ "depth",
10
+ "perCall",
11
+ ];
12
+ export interface DashboardJevState {
13
+ available: boolean;
14
+ enabled: boolean;
15
+ pending: boolean;
16
+ mode?: string;
17
+ storage?: string | null;
18
+ remaining?: number;
19
+ availability?: string;
20
+ providerReadiness?: string;
21
+ error?: string;
22
+ }
23
+ export interface DashboardSettingsSnapshot {
24
+ editable: boolean;
25
+ wallSeconds: number;
26
+ idleSeconds: number;
27
+ wallSource: "session" | "startup";
28
+ idleSource: "session" | "startup";
29
+ descendants: number;
30
+ reserved: number;
31
+ maxDepth: number;
32
+ perCall: number;
33
+ jev: DashboardJevState;
34
+ }
35
+ export function dashboardSettingsLines(
36
+ settings: DashboardSettingsSnapshot | undefined,
37
+ auto: boolean | undefined,
38
+ selected: DashboardSettingKey,
39
+ ): string[] {
40
+ if (!settings) return ["SETTINGS", "Owner settings unavailable. Reconnect from /grants dashboard."];
41
+ const row = (key: DashboardSettingKey, label: string) => `${key === selected ? ">" : " "} ${label}`;
42
+ const jev = settings.jev;
43
+ return [
44
+ "SETTINGS · current session",
45
+ row("auto", `Permission Auto: ${auto ? "ON" : "OFF"}`),
46
+ row(
47
+ "jev",
48
+ `JEV: ${jev.pending ? "waiting for consent" : !jev.available ? "unavailable" : jev.enabled ? "ON" : "OFF"}`,
49
+ ),
50
+ ...(jev.available
51
+ ? [
52
+ ` ${jev.availability ?? "unknown"} · ${jev.remaining ?? 0} paid calls left`,
53
+ ` Key: ${jev.providerReadiness ?? "unknown"}; storage: ${jev.storage ?? "not granted"}`,
54
+ ]
55
+ : [" Load skill-harness to connect JEV controls."]),
56
+ ...(jev.error ? [` ${jev.error}`] : []),
57
+ row("wall", `Child wall: ${settings.wallSeconds}s (${settings.wallSource})`),
58
+ row("idle", `Child idle: ${settings.idleSeconds}s (${settings.idleSource})`),
59
+ settings.editable
60
+ ? "Edits apply to future children from this parent."
61
+ : "Read-only: change settings in the owning parent.",
62
+ "Running children keep their captured limits.",
63
+ "JEV ON asks paid-call and LoRA-storage consent in Pi.",
64
+ "Auto never supplies that consent. No model token budget.",
65
+ "",
66
+ "STARTUP LIMITS · restart to change",
67
+ row("descendants", `Active descendants: ${settings.descendants} (${settings.reserved} reserved) · PI_DADDY_FANOUT`),
68
+ row("depth", `Maximum depth: ${settings.maxDepth} · PI_DADDY_MAX_DEPTH`),
69
+ row("perCall", `Children per call: ${settings.perCall} (fixed safety bound)`),
70
+ "s settings · m models · v versions",
71
+ ];
72
+ }