pi-daddy 0.46.0 → 0.47.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 +14 -0
- package/README.md +77 -11
- package/contracts/ledger-record/v1/governance-event.schema.json +67 -3
- package/dist/governance/approval-prompt.d.ts +8 -11
- package/dist/governance/approval-prompt.d.ts.map +1 -1
- package/dist/governance/approval-prompt.js +138 -24
- package/dist/governance/approval-prompt.js.map +1 -1
- package/dist/governance/auto-mode-policy.d.ts +11 -0
- package/dist/governance/auto-mode-policy.d.ts.map +1 -0
- package/dist/governance/auto-mode-policy.js +309 -0
- package/dist/governance/auto-mode-policy.js.map +1 -0
- package/dist/governance/execution-retention.d.ts +3 -0
- package/dist/governance/execution-retention.d.ts.map +1 -1
- package/dist/governance/execution-retention.js +30 -0
- package/dist/governance/execution-retention.js.map +1 -1
- package/dist/governance/ledger-events.d.ts +14 -1
- package/dist/governance/ledger-events.d.ts.map +1 -1
- package/dist/governance/ledger-events.js +13 -0
- package/dist/governance/ledger-events.js.map +1 -1
- package/dist/governance/ledger-report.d.ts.map +1 -1
- package/dist/governance/ledger-report.js +5 -3
- package/dist/governance/ledger-report.js.map +1 -1
- package/dist/governance/ledger-v3-validation.d.ts.map +1 -1
- package/dist/governance/ledger-v3-validation.js +18 -4
- package/dist/governance/ledger-v3-validation.js.map +1 -1
- package/dist/governance/ledger.d.ts +4 -5
- package/dist/governance/ledger.d.ts.map +1 -1
- package/dist/governance/ledger.js +2 -2
- package/dist/governance/ledger.js.map +1 -1
- package/dist/kernel/approval.d.ts +1 -1
- package/dist/kernel/approval.d.ts.map +1 -1
- package/dist/kernel/approval.js +1 -1
- package/dist/kernel/approval.js.map +1 -1
- package/dist/kernel/auto-mode.d.ts +35 -0
- package/dist/kernel/auto-mode.d.ts.map +1 -0
- package/dist/kernel/auto-mode.js +29 -0
- package/dist/kernel/auto-mode.js.map +1 -0
- package/dist/kernel/delegate-types.d.ts +2 -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 +2 -2
- package/dist/kernel/delegate.js.map +1 -1
- package/dist/kernel/env-names.d.ts +2 -0
- package/dist/kernel/env-names.d.ts.map +1 -1
- package/dist/kernel/env-names.js +4 -0
- package/dist/kernel/env-names.js.map +1 -1
- package/dist/kernel/propagation.d.ts +5 -1
- package/dist/kernel/propagation.d.ts.map +1 -1
- package/dist/kernel/propagation.js +8 -2
- package/dist/kernel/propagation.js.map +1 -1
- package/dist/products/dashboard-cli.d.ts +6 -0
- package/dist/products/dashboard-cli.d.ts.map +1 -1
- package/dist/products/dashboard-cli.js +248 -52
- package/dist/products/dashboard-cli.js.map +1 -1
- package/dist/products/dashboard-display-controls.d.ts +2 -1
- package/dist/products/dashboard-display-controls.d.ts.map +1 -1
- package/dist/products/dashboard-display-controls.js +61 -0
- package/dist/products/dashboard-display-controls.js.map +1 -1
- package/dist/products/dashboard-render.d.ts +2 -0
- package/dist/products/dashboard-render.d.ts.map +1 -1
- package/dist/products/dashboard-render.js +2 -2
- package/dist/products/dashboard-render.js.map +1 -1
- package/dist/products/dashboard-screen.d.ts +52 -0
- package/dist/products/dashboard-screen.d.ts.map +1 -0
- package/dist/products/dashboard-screen.js +262 -0
- package/dist/products/dashboard-screen.js.map +1 -0
- package/dist/products/dashboard-session-client.d.ts +34 -2
- package/dist/products/dashboard-session-client.d.ts.map +1 -1
- package/dist/products/dashboard-session-client.js +51 -0
- package/dist/products/dashboard-session-client.js.map +1 -1
- package/dist/products/ecosystem-versions.d.ts +19 -0
- package/dist/products/ecosystem-versions.d.ts.map +1 -0
- package/dist/products/ecosystem-versions.js +2 -0
- package/dist/products/ecosystem-versions.js.map +1 -0
- package/extensions/activity-timeline.ts +94 -31
- package/extensions/approvals.ts +3 -2
- package/extensions/dashboard-session-server.ts +32 -4
- package/extensions/delegate-chain.ts +2 -0
- package/extensions/delegation-ledger.ts +10 -6
- package/extensions/ecosystem-versions.ts +166 -0
- package/extensions/execution-controls.ts +42 -0
- package/extensions/grants-command.ts +37 -3
- package/extensions/grants.ts +29 -2
- package/extensions/reload-environment.ts +16 -1
- package/extensions/run-delegation.ts +49 -3
- package/extensions/session-auto-mode.ts +132 -0
- package/extensions/session.ts +47 -37
- package/package.json +2 -2
- package/src/governance/approval-prompt.ts +144 -29
- package/src/governance/auto-mode-policy.ts +297 -0
- package/src/governance/execution-retention.ts +24 -0
- package/src/governance/ledger-events.ts +22 -1
- package/src/governance/ledger-report.ts +5 -3
- package/src/governance/ledger-v3-validation.ts +18 -4
- package/src/governance/ledger.ts +4 -4
- package/src/kernel/approval.ts +1 -1
- package/src/kernel/auto-mode.ts +56 -0
- package/src/kernel/delegate-types.ts +2 -0
- package/src/kernel/delegate.ts +2 -1
- package/src/kernel/env-names.ts +4 -0
- package/src/kernel/propagation.ts +11 -2
- package/src/products/dashboard-cli.ts +255 -53
- package/src/products/dashboard-display-controls.ts +52 -0
- package/src/products/dashboard-render.ts +2 -2
- package/src/products/dashboard-screen.ts +339 -0
- package/src/products/dashboard-session-client.ts +60 -1
- package/src/products/ecosystem-versions.ts +18 -0
|
@@ -9,11 +9,12 @@ import { realpathSync } from "node:fs";
|
|
|
9
9
|
import { readFile } from "node:fs/promises";
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
|
-
import { createInterface } from "node:readline";
|
|
12
|
+
import { createInterface, emitKeypressEvents, type Key } from "node:readline";
|
|
13
13
|
import { parseDashboardLedger } from "./dashboard-projection.ts";
|
|
14
14
|
import { renderDashboard } from "./dashboard-render.ts";
|
|
15
15
|
import {
|
|
16
16
|
ActivityTimelineAliases,
|
|
17
|
+
activityTaskKey,
|
|
17
18
|
defaultActivityTimelinePath,
|
|
18
19
|
detailForTimeline,
|
|
19
20
|
parseActivityTimeline,
|
|
@@ -22,9 +23,20 @@ import {
|
|
|
22
23
|
type TimelineFilter,
|
|
23
24
|
} from "./activity-timeline.ts";
|
|
24
25
|
import { createDashboardDisplayControls } from "./dashboard-display-controls.ts";
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
createDashboardConnection,
|
|
28
|
+
dashboardSessionRequest,
|
|
29
|
+
type DashboardSessionSnapshot,
|
|
30
|
+
} from "./dashboard-session-client.ts";
|
|
26
31
|
import { adoptLegacyEnvironment, ENV_LEDGER, legacyEnvironmentWarning } from "../kernel/env-names.ts";
|
|
27
32
|
import { projectLedgerPath } from "../kernel/project-paths.ts";
|
|
33
|
+
import {
|
|
34
|
+
activityDashboardItems,
|
|
35
|
+
ledgerDashboardItems,
|
|
36
|
+
renderDashboardScreen,
|
|
37
|
+
type DashboardItem,
|
|
38
|
+
type DashboardScreenState,
|
|
39
|
+
} from "./dashboard-screen.ts";
|
|
28
40
|
|
|
29
41
|
export const DASHBOARD_PROTOCOL_VERSION = 1 as const;
|
|
30
42
|
export const ENV_DASHBOARD_LEDGER = ENV_LEDGER; // one ledger path variable for children and the dashboard (ADR-0076 PR 3b)
|
|
@@ -47,6 +59,11 @@ export interface DashboardFrameOptions {
|
|
|
47
59
|
activityAliases?: ActivityTimelineAliases;
|
|
48
60
|
now?: Date;
|
|
49
61
|
session?: DashboardSessionSnapshot;
|
|
62
|
+
height?: number;
|
|
63
|
+
screen?: DashboardScreenState;
|
|
64
|
+
notice?: string;
|
|
65
|
+
pending?: boolean;
|
|
66
|
+
command?: string;
|
|
50
67
|
}
|
|
51
68
|
|
|
52
69
|
function shellQuote(value: string): string {
|
|
@@ -102,7 +119,106 @@ async function renderTimelineFile(path: string, text: string, options: Dashboard
|
|
|
102
119
|
});
|
|
103
120
|
}
|
|
104
121
|
|
|
122
|
+
function isActivityTimelineText(text: string): boolean {
|
|
123
|
+
try {
|
|
124
|
+
const first = JSON.parse(text.trimStart().split("\n")[0] || "{}");
|
|
125
|
+
return first?.kind === "activity" || first?.version === 1;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function compactFrame(options: DashboardFrameOptions): Promise<string> {
|
|
132
|
+
const path = resolve(options.cwd, options.ledgerPath ?? defaultActivityTimelinePath(options.cwd));
|
|
133
|
+
let items: DashboardItem[] = [],
|
|
134
|
+
problem: string | undefined,
|
|
135
|
+
current: string | undefined,
|
|
136
|
+
currentStatus: string | undefined;
|
|
137
|
+
let content: { title: string; text: string } | undefined;
|
|
138
|
+
const aliases = options.activityAliases ?? new ActivityTimelineAliases();
|
|
139
|
+
if (options.protocol !== undefined && options.protocol !== DASHBOARD_PROTOCOL_VERSION)
|
|
140
|
+
return renderDashboardScreen({
|
|
141
|
+
...options,
|
|
142
|
+
width: options.width ?? 80,
|
|
143
|
+
height: options.height!,
|
|
144
|
+
screen: options.screen!,
|
|
145
|
+
items: [],
|
|
146
|
+
problem: "INCOMPATIBLE dashboard protocol; relink this package.",
|
|
147
|
+
});
|
|
148
|
+
try {
|
|
149
|
+
const text = await readFile(path, "utf8");
|
|
150
|
+
if (!options.ledgerPath || isActivityTimelineText(text)) {
|
|
151
|
+
const timeline = parseActivityTimeline(text);
|
|
152
|
+
items = activityDashboardItems(timeline, aliases, options.now ?? new Date()).map((item) => ({
|
|
153
|
+
...item,
|
|
154
|
+
timelinePath: path,
|
|
155
|
+
}));
|
|
156
|
+
problem = timeline.refusals[0];
|
|
157
|
+
} else {
|
|
158
|
+
const projection = parseDashboardLedger(text, { now: options.now });
|
|
159
|
+
items = ledgerDashboardItems(projection);
|
|
160
|
+
if (projection.corrupt.length) problem = `Ledger unavailable: ${projection.corrupt.length} corrupt line(s)`;
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
problem =
|
|
164
|
+
(error as { code?: string }).code === "ENOENT"
|
|
165
|
+
? "Waiting for local work; open from /grants dashboard."
|
|
166
|
+
: `Evidence unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
167
|
+
}
|
|
168
|
+
// The owner supplies this exact current turn. No sibling timeline or unrelated history is merged.
|
|
169
|
+
const activity = options.session?.activity;
|
|
170
|
+
if (activity) {
|
|
171
|
+
const key = activityTaskKey(activity.rootId, activity.taskId);
|
|
172
|
+
current = `Task ${activity.taskId}`;
|
|
173
|
+
try {
|
|
174
|
+
const timeline = parseActivityTimeline(await readFile(activity.path, "utf8"));
|
|
175
|
+
timeline.tasks = timeline.tasks.filter((task) => activityTaskKey(task.rootId, task.id) === key);
|
|
176
|
+
const item = activityDashboardItems(timeline, aliases, options.now ?? new Date())[0];
|
|
177
|
+
if (item) {
|
|
178
|
+
current = item.label;
|
|
179
|
+
currentStatus = item.status;
|
|
180
|
+
if (!items.some((existing) => existing.key === key)) items.unshift({ ...item, timelinePath: activity.path });
|
|
181
|
+
try {
|
|
182
|
+
current = (await detailForTimeline(activity.path, key, "prompt")).text.slice(0, 600);
|
|
183
|
+
} catch {
|
|
184
|
+
/* Metadata-only/off and unavailable retained content retain the exact task identity. */
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
/* The owner identity remains useful when its local timeline is unavailable. */
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (options.activityDetail) {
|
|
192
|
+
try {
|
|
193
|
+
const requested = aliases.resolve(options.activityDetail.taskKey) ?? options.activityDetail.taskKey;
|
|
194
|
+
const key = items.find((value) => value.taskKey === requested)?.taskKey;
|
|
195
|
+
if (!key) throw Error("unknown task selector");
|
|
196
|
+
options.screen!.selectedKey = key;
|
|
197
|
+
const source = items.find((value) => value.taskKey === key)?.timelinePath;
|
|
198
|
+
if (!source) throw Error("task has no retained activity source");
|
|
199
|
+
content = {
|
|
200
|
+
title: `PRIVATE ${options.activityDetail.field.toUpperCase()}`,
|
|
201
|
+
text: (await detailForTimeline(source, key, options.activityDetail.field)).text,
|
|
202
|
+
};
|
|
203
|
+
} catch (error) {
|
|
204
|
+
problem = `Activity detail unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return renderDashboardScreen({
|
|
208
|
+
...options,
|
|
209
|
+
width: options.width ?? 80,
|
|
210
|
+
height: options.height!,
|
|
211
|
+
screen: options.screen!,
|
|
212
|
+
items,
|
|
213
|
+
problem,
|
|
214
|
+
content,
|
|
215
|
+
current,
|
|
216
|
+
currentStatus,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
105
220
|
export async function dashboardFrame(options: DashboardFrameOptions): Promise<string> {
|
|
221
|
+
if (options.screen && options.height !== undefined) return compactFrame(options);
|
|
106
222
|
if (options.protocol !== undefined && options.protocol !== DASHBOARD_PROTOCOL_VERSION) {
|
|
107
223
|
return incompatibleFrame(options.protocol);
|
|
108
224
|
}
|
|
@@ -136,7 +252,7 @@ export async function dashboardFrame(options: DashboardFrameOptions): Promise<st
|
|
|
136
252
|
].join("\n");
|
|
137
253
|
}
|
|
138
254
|
}
|
|
139
|
-
const rendered = text
|
|
255
|
+
const rendered = isActivityTimelineText(text)
|
|
140
256
|
? await renderTimelineFile(ledgerPath, text, options)
|
|
141
257
|
: renderDashboard(parseDashboardLedger(text, { now: options.now }), {
|
|
142
258
|
color: options.color,
|
|
@@ -186,17 +302,21 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
186
302
|
process.title = `pi-daddy-dashboard${key ? `:${key.slice(0, 12)}` : ""}`;
|
|
187
303
|
|
|
188
304
|
const display = createDashboardDisplayControls(cli.details, true);
|
|
189
|
-
|
|
190
|
-
|
|
305
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !cli.once;
|
|
306
|
+
const connection = createDashboardConnection(
|
|
307
|
+
sessionSocket && sessionToken
|
|
308
|
+
? (action) => dashboardSessionRequest(sessionSocket, sessionToken, action)
|
|
309
|
+
: undefined,
|
|
310
|
+
);
|
|
311
|
+
let previous = "",
|
|
312
|
+
notice = "",
|
|
313
|
+
command: string | undefined;
|
|
191
314
|
let input: ReturnType<typeof createInterface> | null = null;
|
|
315
|
+
let stopped = false,
|
|
316
|
+
drawing = false;
|
|
192
317
|
const draw = async (clear: boolean): Promise<void> => {
|
|
193
|
-
|
|
194
|
-
if (
|
|
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
|
-
}
|
|
318
|
+
await connection.refresh();
|
|
319
|
+
if (stopped) return;
|
|
200
320
|
const view = await dashboardFrame({
|
|
201
321
|
cwd,
|
|
202
322
|
ledgerPath,
|
|
@@ -205,62 +325,144 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
205
325
|
width: process.stdout.columns || 80,
|
|
206
326
|
...display.state,
|
|
207
327
|
activityAliases: display.aliases,
|
|
208
|
-
session,
|
|
328
|
+
session: connection.state.snapshot,
|
|
329
|
+
...(interactive
|
|
330
|
+
? {
|
|
331
|
+
height: process.stdout.rows || 24,
|
|
332
|
+
screen: display.screen,
|
|
333
|
+
notice: connection.state.error || notice,
|
|
334
|
+
pending: connection.state.pending,
|
|
335
|
+
command,
|
|
336
|
+
}
|
|
337
|
+
: {}),
|
|
209
338
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
339
|
+
if (stopped) return; // A read may complete after q/SIGTERM restored the parent terminal.
|
|
340
|
+
const frame = interactive
|
|
341
|
+
? view
|
|
342
|
+
: `${connection.state.error || notice ? `${connection.state.error || notice}\n\n` : ""}${view}`;
|
|
343
|
+
const prompt = display.prompt(),
|
|
344
|
+
draft = input?.line ?? "";
|
|
345
|
+
const rendered = `${frame}${!interactive && input ? `\n\n${prompt}${draft}` : ""}`;
|
|
346
|
+
if (rendered === previous) return;
|
|
347
|
+
previous = rendered;
|
|
215
348
|
process.stdout.write(
|
|
216
|
-
|
|
349
|
+
interactive
|
|
350
|
+
? `\u001b[H${frame}\u001b[J`
|
|
351
|
+
: clear
|
|
352
|
+
? `\u001b]0;PI-DADDY\u0007\u001b[2J\u001b[H${rendered}`
|
|
353
|
+
: `${rendered}\n`,
|
|
217
354
|
);
|
|
218
|
-
|
|
355
|
+
input?.setPrompt(prompt);
|
|
219
356
|
};
|
|
220
|
-
|
|
221
357
|
if (cli.once) {
|
|
222
358
|
await draw(false);
|
|
223
359
|
return;
|
|
224
360
|
}
|
|
225
|
-
input = createInterface({ input: process.stdin, output: process.stdout, terminal: Boolean(process.stdin.isTTY) });
|
|
226
|
-
await draw(true);
|
|
227
|
-
let drawing = false;
|
|
228
361
|
const redraw = (): void => {
|
|
229
|
-
if (drawing) return;
|
|
362
|
+
if (drawing || stopped) return;
|
|
230
363
|
drawing = true;
|
|
231
|
-
void draw(true)
|
|
232
|
-
|
|
233
|
-
|
|
364
|
+
void draw(true)
|
|
365
|
+
.catch((error) => {
|
|
366
|
+
notice = `Dashboard unavailable: ${String(error)}`;
|
|
367
|
+
})
|
|
368
|
+
.finally(() => {
|
|
369
|
+
drawing = false;
|
|
370
|
+
});
|
|
234
371
|
};
|
|
235
|
-
|
|
372
|
+
const runCommand = async (line: string): Promise<void> => {
|
|
236
373
|
const trimmed = line.trim();
|
|
237
|
-
if (trimmed.startsWith("m ")
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
374
|
+
if (trimmed.startsWith("m ")) {
|
|
375
|
+
notice = (await connection.change({ action: "set", edits: trimmed.slice(2).trim() }))
|
|
376
|
+
? "Session models updated"
|
|
377
|
+
: connection.state.error || "Session controls unavailable";
|
|
378
|
+
} else notice = display.input(line) ? "" : `Unknown command: ${trimmed}. Press ? for help.`;
|
|
379
|
+
redraw();
|
|
380
|
+
};
|
|
381
|
+
let stop: () => void = () => {};
|
|
382
|
+
const keypress = (text: string | undefined, key: Key): void => {
|
|
383
|
+
if (key.ctrl && key.name === "c") {
|
|
384
|
+
stop();
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (command !== undefined) {
|
|
388
|
+
if (key.name === "escape") command = undefined;
|
|
389
|
+
else if (key.name === "return") {
|
|
390
|
+
const line = command;
|
|
391
|
+
command = undefined;
|
|
392
|
+
void runCommand(line);
|
|
393
|
+
} else if (key.name === "backspace") command = [...command].slice(0, -1).join("");
|
|
394
|
+
else if (text && !key.ctrl && !key.meta && !/[\p{Cc}\p{Cf}]/u.test(text) && command.length < 2048)
|
|
395
|
+
command += text;
|
|
396
|
+
redraw();
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const action = display.key(
|
|
400
|
+
key.name === "return"
|
|
401
|
+
? "return"
|
|
402
|
+
: ["up", "down", "escape", "tab"].includes(key.name || "")
|
|
403
|
+
? key.name!
|
|
404
|
+
: text || "",
|
|
405
|
+
);
|
|
406
|
+
if (action === "quit") {
|
|
407
|
+
stop();
|
|
246
408
|
return;
|
|
247
409
|
}
|
|
248
|
-
|
|
249
|
-
|
|
410
|
+
if (action === "command") command = "";
|
|
411
|
+
if (action === "auto") {
|
|
412
|
+
const snapshot = connection.state.snapshot;
|
|
413
|
+
if (!snapshot) notice = "Daddy permission controls unavailable";
|
|
414
|
+
else {
|
|
415
|
+
const enabled = !snapshot.auto.enabled;
|
|
416
|
+
void connection.change({ action: "set-auto", enabled }).then((changed) => {
|
|
417
|
+
notice = changed
|
|
418
|
+
? `Daddy permission Auto ${enabled ? "ON" : "OFF"}; ${enabled ? "future prompts auto-approved" : "admitted work continues"}`
|
|
419
|
+
: connection.state.error || "Permission change already pending";
|
|
420
|
+
redraw();
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
250
424
|
redraw();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
process.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
process.
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
425
|
+
};
|
|
426
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
427
|
+
const oldRaw = process.stdin.isRaw;
|
|
428
|
+
try {
|
|
429
|
+
if (interactive) {
|
|
430
|
+
emitKeypressEvents(process.stdin);
|
|
431
|
+
process.stdin.setRawMode(true);
|
|
432
|
+
process.stdin.resume();
|
|
433
|
+
process.stdout.write("\u001b[?1049h\u001b[?25l");
|
|
434
|
+
} else {
|
|
435
|
+
input = createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
436
|
+
input.on("line", (line) => {
|
|
437
|
+
void runCommand(line);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
await new Promise<void>((settle) => {
|
|
441
|
+
stop = () => {
|
|
442
|
+
stopped = true;
|
|
443
|
+
settle();
|
|
444
|
+
};
|
|
445
|
+
process.once("SIGINT", stop);
|
|
446
|
+
process.once("SIGTERM", stop);
|
|
447
|
+
if (interactive) process.stdin.on("keypress", keypress);
|
|
448
|
+
process.on("SIGWINCH", redraw);
|
|
449
|
+
timer = setInterval(redraw, DASHBOARD_REFRESH_MS);
|
|
450
|
+
redraw();
|
|
451
|
+
});
|
|
452
|
+
} finally {
|
|
453
|
+
stopped = true;
|
|
454
|
+
if (timer) clearInterval(timer);
|
|
455
|
+
process.off("SIGINT", stop);
|
|
456
|
+
process.off("SIGTERM", stop);
|
|
457
|
+
process.off("SIGWINCH", redraw);
|
|
458
|
+
input?.close();
|
|
459
|
+
if (interactive) {
|
|
460
|
+
process.stdin.off("keypress", keypress);
|
|
461
|
+
process.stdin.setRawMode(oldRaw || false);
|
|
462
|
+
process.stdin.pause();
|
|
463
|
+
process.stdout.write("\u001b[?25h\u001b[?1049l");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
264
466
|
}
|
|
265
467
|
|
|
266
468
|
const invoked = (() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Local display controls; these never dispatch actions to an owner or change ledger state. */
|
|
2
|
+
import { dashboardScreenState } from "./dashboard-screen.ts";
|
|
2
3
|
import { ActivityTimelineAliases } from "./activity-timeline.ts";
|
|
3
4
|
|
|
4
5
|
export function createDashboardDisplayControls(details: boolean, ledger: boolean) {
|
|
@@ -10,14 +11,63 @@ export function createDashboardDisplayControls(details: boolean, ledger: boolean
|
|
|
10
11
|
} = { details, history: false, ...(ledger ? { filter: "everything" as const } : {}) };
|
|
11
12
|
// Held by the panel process, so a refresh or a newly observed root cannot renumber an already displayed alias.
|
|
12
13
|
const aliases = new ActivityTimelineAliases();
|
|
14
|
+
const screen = dashboardScreenState();
|
|
15
|
+
if (details) screen.view = "details";
|
|
13
16
|
return {
|
|
14
17
|
state,
|
|
18
|
+
screen,
|
|
19
|
+
key(key: string): "auto" | "quit" | "command" | undefined {
|
|
20
|
+
if (key === "a") return "auto";
|
|
21
|
+
if (key === "q" || key === "ctrl-c") return "quit";
|
|
22
|
+
if (key === ":") return "command";
|
|
23
|
+
if (key === "escape") {
|
|
24
|
+
screen.view = screen.view === "version-details" ? "versions" : "main";
|
|
25
|
+
state.activityDetail = undefined;
|
|
26
|
+
screen.offset = 0;
|
|
27
|
+
} else if (key === "?" || key === "m" || key === "v") {
|
|
28
|
+
screen.view = key === "?" ? "help" : key === "v" ? "versions" : "models";
|
|
29
|
+
state.activityDetail = undefined;
|
|
30
|
+
screen.offset = 0;
|
|
31
|
+
} else if (key === "return" || key === "d") {
|
|
32
|
+
screen.view = screen.view === "versions" ? "version-details" : "details";
|
|
33
|
+
screen.offset = 0;
|
|
34
|
+
} else if (["up", "down", "j", "k"].includes(key)) {
|
|
35
|
+
const delta = key === "up" || key === "k" ? -1 : 1;
|
|
36
|
+
if (screen.view === "versions") {
|
|
37
|
+
const rows = screen.versionRows ?? [],
|
|
38
|
+
index = rows.findIndex((row) => row.id === screen.versionId);
|
|
39
|
+
screen.versionId = rows[Math.max(0, Math.min(rows.length - 1, index + delta))]?.id;
|
|
40
|
+
} else if (screen.view !== "main") screen.offset = Math.max(0, screen.offset + delta);
|
|
41
|
+
else {
|
|
42
|
+
const index = Math.max(
|
|
43
|
+
0,
|
|
44
|
+
screen.items.findIndex((item) => item.key === screen.selectedKey),
|
|
45
|
+
);
|
|
46
|
+
screen.selectedKey = screen.items[Math.max(0, Math.min(screen.items.length - 1, index + delta))]?.key;
|
|
47
|
+
state.activityDetail = undefined;
|
|
48
|
+
}
|
|
49
|
+
} else if (key === "tab") {
|
|
50
|
+
const filters = ["everything", "agents", "skills", "needs-you"] as const;
|
|
51
|
+
state.filter = filters[(filters.indexOf(state.filter ?? "everything") + 1) % filters.length];
|
|
52
|
+
screen.view = "main";
|
|
53
|
+
state.activityDetail = undefined;
|
|
54
|
+
} else if (key === "p" || key === "f") {
|
|
55
|
+
const selected = screen.items.find((item) => item.key === screen.selectedKey);
|
|
56
|
+
if (selected?.taskKey) {
|
|
57
|
+
state.activityDetail = { taskKey: selected.taskKey, field: key === "p" ? "prompt" : "final" };
|
|
58
|
+
screen.view = "details";
|
|
59
|
+
screen.offset = 0;
|
|
60
|
+
}
|
|
61
|
+
} else if (key === "h") state.history = !state.history;
|
|
62
|
+
return undefined;
|
|
63
|
+
},
|
|
15
64
|
aliases,
|
|
16
65
|
input(line: string): boolean {
|
|
17
66
|
const input = line.trim(),
|
|
18
67
|
key = input.toLowerCase();
|
|
19
68
|
if (key === "d") {
|
|
20
69
|
state.details = !state.details;
|
|
70
|
+
screen.view = state.details ? "details" : "main";
|
|
21
71
|
return true;
|
|
22
72
|
}
|
|
23
73
|
if (ledger && key === "h") {
|
|
@@ -30,6 +80,8 @@ export function createDashboardDisplayControls(details: boolean, ledger: boolean
|
|
|
30
80
|
}
|
|
31
81
|
const detail = /^(p|f)\s+([^\s]{1,1400})$/i.exec(input);
|
|
32
82
|
if (ledger && detail) {
|
|
83
|
+
screen.view = "details";
|
|
84
|
+
screen.offset = 0;
|
|
33
85
|
state.activityDetail = { field: detail[1]!.toLowerCase() === "p" ? "prompt" : "final", taskKey: detail[2]! };
|
|
34
86
|
return true;
|
|
35
87
|
}
|
|
@@ -38,11 +38,11 @@ const displayIdentifier = (value: string | undefined): string => (isLedgerDispla
|
|
|
38
38
|
const ANSI_SGR = /\u001b\[[0-9;]*m/g;
|
|
39
39
|
const WIDE_CELL = /\p{Extended_Pictographic}|[\u2E80-\u9FFF\uF900-\uFAFF]/u;
|
|
40
40
|
|
|
41
|
-
function cellWidth(value: string): number {
|
|
41
|
+
export function cellWidth(value: string): number {
|
|
42
42
|
return [...value.replace(ANSI_SGR, "")].reduce((sum, char) => sum + (WIDE_CELL.test(char) ? 2 : 1), 0);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function truncate(value: string, width: number): string {
|
|
45
|
+
export function truncate(value: string, width: number): string {
|
|
46
46
|
if (cellWidth(value) <= width) return value;
|
|
47
47
|
const target = Math.max(0, width - 1);
|
|
48
48
|
let cells = 0;
|