tickmarkr 1.72.0 → 1.74.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/dist/cli/commands/approve.js +21 -3
- package/dist/cli/commands/resume.js +10 -2
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.js +260 -41
- package/dist/cli/commands/ui.d.ts +6 -2
- package/dist/cli/commands/ui.js +2 -4
- package/dist/config/config.d.ts +4 -0
- package/dist/config/config.js +6 -1
- package/dist/drivers/herdr.d.ts +5 -0
- package/dist/drivers/herdr.js +100 -10
- package/dist/drivers/subprocess.d.ts +3 -3
- package/dist/drivers/subprocess.js +10 -5
- package/dist/gates/acceptance.js +19 -1
- package/dist/gates/llm.d.ts +4 -0
- package/dist/gates/llm.js +16 -3
- package/dist/gates/run-gates.d.ts +1 -1
- package/dist/gates/run-gates.js +25 -3
- package/dist/route/preference.d.ts +2 -1
- package/dist/route/preference.js +8 -2
- package/dist/run/daemon.d.ts +1 -0
- package/dist/run/daemon.js +139 -4
- package/dist/run/git.d.ts +2 -0
- package/dist/run/git.js +55 -0
- package/dist/run/journal.d.ts +21 -1
- package/dist/run/journal.js +83 -5
- package/dist/tui/ink/components.d.ts +18 -0
- package/dist/tui/ink/components.js +30 -2
- package/dist/tui/ink/studio-app.d.ts +59 -0
- package/dist/tui/ink/studio-app.js +320 -0
- package/package.json +1 -1
- package/dist/tui/app.d.ts +0 -72
- package/dist/tui/app.js +0 -334
- package/dist/tui/engine.d.ts +0 -52
- package/dist/tui/engine.js +0 -92
- package/dist/tui/frame.d.ts +0 -22
- package/dist/tui/frame.js +0 -56
- package/dist/tui/input.d.ts +0 -41
- package/dist/tui/input.js +0 -100
- package/dist/tui/views/consult-dossier.d.ts +0 -49
- package/dist/tui/views/consult-dossier.js +0 -169
- package/dist/tui/views/diff-modal.d.ts +0 -9
- package/dist/tui/views/diff-modal.js +0 -19
- package/dist/tui/views/fleet-view.d.ts +0 -91
- package/dist/tui/views/fleet-view.js +0 -337
- package/dist/tui/views/preview-view.d.ts +0 -7
- package/dist/tui/views/preview-view.js +0 -80
- package/dist/tui/views/profile-view.d.ts +0 -15
- package/dist/tui/views/profile-view.js +0 -93
- package/dist/tui/views/routing-view.d.ts +0 -48
- package/dist/tui/views/routing-view.js +0 -234
- package/dist/tui/views/runs-view.d.ts +0 -70
- package/dist/tui/views/runs-view.js +0 -387
package/dist/run/git.js
CHANGED
|
@@ -75,6 +75,61 @@ export async function shGitOk(cmd, cwd) {
|
|
|
75
75
|
throw new Error(`command failed (${r.code}): ${cmd}\n${r.stderr || r.stdout}`);
|
|
76
76
|
return r.stdout;
|
|
77
77
|
}
|
|
78
|
+
const NPM_DEPENDENCY_MANIFESTS = ["package.json", "package-lock.json", "npm-shrinkwrap.json"];
|
|
79
|
+
// OBS-126: compare the whole attempt (committed, staged, unstaged, or newly added manifest) with
|
|
80
|
+
// the task's integration-tip baseline. A committed package.json change is invisible to `git status`,
|
|
81
|
+
// while an untracked manifest is invisible to `git diff`, so both views are required.
|
|
82
|
+
export async function npmDependencyManifestChanged(cwd, baselineRef) {
|
|
83
|
+
const paths = NPM_DEPENDENCY_MANIFESTS.map(shq).join(" ");
|
|
84
|
+
const diff = await shGit(`git diff --quiet ${shq(baselineRef)} -- ${paths}`, cwd);
|
|
85
|
+
if (diff.code === 1)
|
|
86
|
+
return true;
|
|
87
|
+
if (diff.code !== 0) {
|
|
88
|
+
throw new Error(`dependency manifest comparison failed (${diff.code}): ${diff.stderr || diff.stdout}`);
|
|
89
|
+
}
|
|
90
|
+
const untracked = await shGit(`git ls-files --others --exclude-standard -- ${paths}`, cwd);
|
|
91
|
+
if (untracked.code !== 0) {
|
|
92
|
+
throw new Error(`dependency manifest comparison failed (${untracked.code}): ${untracked.stderr || untracked.stdout}`);
|
|
93
|
+
}
|
|
94
|
+
return untracked.stdout.trim().length > 0;
|
|
95
|
+
}
|
|
96
|
+
// npm reifies a worktree's node_modules symlink as a private directory, which makes a successful
|
|
97
|
+
// install disappear at worktree cleanup. Build positional specs from the attempt manifest so the
|
|
98
|
+
// daemon can install them at the main repo root (the provisioned link's target) without saving them
|
|
99
|
+
// into that baseline manifest. `--install-links` packs file: dependencies before the attempt is removed.
|
|
100
|
+
export function npmDependencyInstallCommand(cwd) {
|
|
101
|
+
const manifestPath = join(cwd, "package.json");
|
|
102
|
+
const manifest = existsSync(manifestPath)
|
|
103
|
+
? JSON.parse(readFileSync(manifestPath, "utf8"))
|
|
104
|
+
: {};
|
|
105
|
+
const dependencyGroups = [
|
|
106
|
+
manifest.peerDependencies,
|
|
107
|
+
manifest.devDependencies,
|
|
108
|
+
manifest.dependencies,
|
|
109
|
+
manifest.optionalDependencies,
|
|
110
|
+
];
|
|
111
|
+
const dependencies = {};
|
|
112
|
+
for (const group of dependencyGroups) {
|
|
113
|
+
if (!group || typeof group !== "object" || Array.isArray(group))
|
|
114
|
+
continue;
|
|
115
|
+
for (const [name, spec] of Object.entries(group)) {
|
|
116
|
+
if (typeof spec === "string")
|
|
117
|
+
dependencies[name] = spec;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const specs = Object.entries(dependencies).map(([name, rawSpec]) => {
|
|
121
|
+
let spec = rawSpec;
|
|
122
|
+
if (spec.startsWith("file:"))
|
|
123
|
+
spec = `file:${resolve(cwd, spec.slice("file:".length))}`;
|
|
124
|
+
else if (spec.startsWith("./") || spec.startsWith("../"))
|
|
125
|
+
spec = resolve(cwd, spec);
|
|
126
|
+
return shq(`${name}@${spec}`);
|
|
127
|
+
});
|
|
128
|
+
return [
|
|
129
|
+
"npm install --no-save --no-audit --no-fund --package-lock=false --install-links",
|
|
130
|
+
...specs,
|
|
131
|
+
].join(" ");
|
|
132
|
+
}
|
|
78
133
|
export async function gitHead(cwd) {
|
|
79
134
|
return (await shGitOk("git rev-parse HEAD", cwd)).trim();
|
|
80
135
|
}
|
package/dist/run/journal.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type Assignment } from "../adapters/types.js";
|
|
3
3
|
import type { TickmarkrConfig } from "../config/config.js";
|
|
4
|
-
import { type TaskStatus } from "../graph/schema.js";
|
|
4
|
+
import { type GateName, type TaskStatus } from "../graph/schema.js";
|
|
5
5
|
import { type ProfileDiscount, type RoutingProfile } from "../route/profile.js";
|
|
6
6
|
export interface JournalEvent {
|
|
7
7
|
ts: string;
|
|
@@ -9,6 +9,8 @@ export interface JournalEvent {
|
|
|
9
9
|
taskId?: string;
|
|
10
10
|
data: Record<string, unknown>;
|
|
11
11
|
}
|
|
12
|
+
export type TaskPhase = "worker" | "gates" | `gate:${GateName}` | "judge" | "review" | "merge";
|
|
13
|
+
export declare function phaseForGate(gate: GateName): TaskPhase;
|
|
12
14
|
export declare function formatJournalNarration({ event, taskId, data }: JournalEvent): string;
|
|
13
15
|
export interface ResumeState {
|
|
14
16
|
attempts: number;
|
|
@@ -18,6 +20,7 @@ export interface ResumeState {
|
|
|
18
20
|
export declare const CHANNEL_EXCLUSION_KINDS: readonly ["dead-channel"];
|
|
19
21
|
export type ChannelExclusionKind = (typeof CHANNEL_EXCLUSION_KINDS)[number];
|
|
20
22
|
export declare const ATTEMPT_CAP_RELEASE: "attempt-cap";
|
|
23
|
+
export declare const GATE_SATISFIED_RELEASE: "gate-satisfied";
|
|
21
24
|
export declare const PARK_KINDS: readonly ["human-gate", "ladder-exhausted", "attempt-cap", "gate-fail", "quota", "reroute-exhausted", "setup", "stall", "merge-conflict", "tip-moved", "infra", "dispatch"];
|
|
22
25
|
export type ParkKind = (typeof PARK_KINDS)[number];
|
|
23
26
|
export declare const RETRY_MODES: readonly ["resume", "fresh"];
|
|
@@ -89,8 +92,22 @@ export declare const TelemetryRowSchema: z.ZodObject<{
|
|
|
89
92
|
vacuous: "vacuous";
|
|
90
93
|
skipped: "skipped";
|
|
91
94
|
}>>;
|
|
95
|
+
kind: z.ZodOptional<z.ZodLiteral<"judge">>;
|
|
96
|
+
judgeOutcome: z.ZodOptional<z.ZodEnum<{
|
|
97
|
+
parseable: "parseable";
|
|
98
|
+
unparseable: "unparseable";
|
|
99
|
+
}>>;
|
|
92
100
|
}, z.core.$strip>;
|
|
93
101
|
export type TelemetryRow = z.infer<typeof TelemetryRowSchema>;
|
|
102
|
+
export interface JudgeInvocationEvidence {
|
|
103
|
+
taskId: string;
|
|
104
|
+
channel: string;
|
|
105
|
+
outcome: "done" | "failed";
|
|
106
|
+
judgeOutcome: "parseable" | "unparseable";
|
|
107
|
+
durationMs: number;
|
|
108
|
+
transcript?: string;
|
|
109
|
+
}
|
|
110
|
+
export declare function withJudgeInvocationEvidence<T>(invocations: JudgeInvocationEvidence[], persist: () => Promise<T>): Promise<T>;
|
|
94
111
|
export declare const SIGNAL_BASIS: readonly ["proved", "review-agree", "judge-only", "legacy", "vacuous", "skipped"];
|
|
95
112
|
export type SignalBasis = (typeof SIGNAL_BASIS)[number];
|
|
96
113
|
export declare const SIGNAL_QUALITY: Record<SignalBasis, 0 | 0.25 | 0.5 | 0.75 | 1>;
|
|
@@ -143,10 +160,13 @@ export declare class Journal {
|
|
|
143
160
|
}): string | null;
|
|
144
161
|
private get journalPath();
|
|
145
162
|
append(event: string, taskId?: string, data?: Record<string, unknown>): void;
|
|
163
|
+
phaseStart(taskId: string, phase: TaskPhase, data?: Record<string, unknown>): void;
|
|
146
164
|
read(): JournalEvent[];
|
|
147
165
|
replayStatuses(): Map<string, TaskStatus>;
|
|
148
166
|
replayResumeState(): Map<string, ResumeState>;
|
|
167
|
+
replaySatisfiedGates(): Map<string, GateName>;
|
|
149
168
|
replayExcludedChannels(): Set<string>;
|
|
150
169
|
telemetry(row: TelemetryRow): void;
|
|
151
170
|
readTelemetry(): TelemetryRow[];
|
|
171
|
+
readJudgeTelemetry(): TelemetryRow[];
|
|
152
172
|
}
|
package/dist/run/journal.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
2
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { channelKey, TokenUsageSchema } from "../adapters/types.js";
|
|
5
6
|
import { stateDirName, tickmarkrDir } from "../graph/graph.js";
|
|
6
|
-
import { TIERS } from "../graph/schema.js";
|
|
7
|
+
import { GATE_NAMES, TIERS } from "../graph/schema.js";
|
|
7
8
|
import { buildProfile, classify } from "../route/profile.js";
|
|
8
9
|
import { redactSecrets } from "./redact.js";
|
|
10
|
+
export function phaseForGate(gate) {
|
|
11
|
+
if (gate === "acceptance")
|
|
12
|
+
return "judge";
|
|
13
|
+
if (gate === "review")
|
|
14
|
+
return "review";
|
|
15
|
+
return `gate:${gate}`;
|
|
16
|
+
}
|
|
9
17
|
export function formatJournalNarration({ event, taskId, data }) {
|
|
10
18
|
const assignment = data.assignment;
|
|
11
19
|
const direct = [data.summary, data.reason, data.error, data.step, data.action, data.lint, data.branch, data.from]
|
|
@@ -33,6 +41,9 @@ export const CHANNEL_EXCLUSION_KINDS = ["dead-channel"];
|
|
|
33
41
|
// Approve stamps this; replayResumeState zeros the attempt counter (fresh budget) while keeping
|
|
34
42
|
// tried (consult bans / burned channels). Absent on pre-v1.24 events ⇒ inert (corpus outcome-identical).
|
|
35
43
|
export const ATTEMPT_CAP_RELEASE = "attempt-cap";
|
|
44
|
+
// OBS-130: task-approved carries the exact failed gate the operator satisfied. The release tag keeps
|
|
45
|
+
// ordinary humanGate and attempt-cap approvals byte-compatible while making this authority explicit.
|
|
46
|
+
export const GATE_SATISFIED_RELEASE = "gate-satisfied";
|
|
36
47
|
// Fail-closed shape for a dispatched assignment (journal.ts:75-90 posture): a malformed assignment in
|
|
37
48
|
// one dispatch degrades that single task toward today's behavior — counts toward attempts, contributes
|
|
38
49
|
// nothing to tried, poisons only lastAssignment — never crashes resume, never poisons other tasks.
|
|
@@ -131,7 +142,19 @@ export const TelemetryRowSchema = z.object({
|
|
|
131
142
|
// v1.46 additive (T5): gate-signal quality for routing hygiene — OPTIONAL, absent = legacy (0.25 at fold).
|
|
132
143
|
signalQuality: z.union([z.literal(0), z.literal(0.25), z.literal(0.5), z.literal(0.75), z.literal(1)]).optional(),
|
|
133
144
|
signalBasis: z.enum(["proved", "review-agree", "judge-only", "legacy", "vacuous", "skipped"]).optional(),
|
|
145
|
+
// OBS-132 additive judge invocation rows. Absent means the legacy per-task row. Judge rows reuse the
|
|
146
|
+
// required v1.5 envelope so per-run raw readers stay source-compatible, while readAllTelemetry filters
|
|
147
|
+
// them before profile derivation: verdict production must be observable without becoming worker reward.
|
|
148
|
+
kind: z.literal("judge").optional(),
|
|
149
|
+
judgeOutcome: z.enum(["parseable", "unparseable"]).optional(),
|
|
134
150
|
});
|
|
151
|
+
const judgePersistence = new AsyncLocalStorage();
|
|
152
|
+
// run-gates scopes this context around the existing daemon onGate callback. Journal.append therefore
|
|
153
|
+
// remains the sole persistence boundary: it can enrich the existing judge-retry row and write invocation
|
|
154
|
+
// telemetry without requiring a parallel daemon callback or changing healthy gate-result payloads.
|
|
155
|
+
export function withJudgeInvocationEvidence(invocations, persist) {
|
|
156
|
+
return judgePersistence.run({ invocations, written: false }, persist);
|
|
157
|
+
}
|
|
135
158
|
// v1.46 T5 (Sol signal telemetry): gate-result rows carry explicit signalQuality so future defect windows
|
|
136
159
|
// are identifiable without forensics. Basis is the provenance claim; quality is the dyadic h-fold weight.
|
|
137
160
|
export const SIGNAL_BASIS = ["proved", "review-agree", "judge-only", "legacy", "vacuous", "skipped"];
|
|
@@ -249,7 +272,7 @@ export function readAllTelemetry(repoRoot, lastK, opts = {}) {
|
|
|
249
272
|
for (const runId of runIds) {
|
|
250
273
|
for (const raw of readJsonl(join(dir, runId, "telemetry.jsonl"))) {
|
|
251
274
|
const r = TelemetryRowSchema.safeParse(raw);
|
|
252
|
-
if (r.success)
|
|
275
|
+
if (r.success && r.data.kind !== "judge")
|
|
253
276
|
out.push({ ...r.data, runId });
|
|
254
277
|
}
|
|
255
278
|
}
|
|
@@ -348,7 +371,18 @@ export class Journal {
|
|
|
348
371
|
return join(this.dir, "journal.jsonl");
|
|
349
372
|
}
|
|
350
373
|
append(event, taskId, data = {}) {
|
|
351
|
-
const
|
|
374
|
+
const evidence = judgePersistence.getStore();
|
|
375
|
+
const failed = evidence?.invocations.filter((invocation) => invocation.transcript !== undefined) ?? [];
|
|
376
|
+
const persistedData = event === "judge-retry" && failed.length > 0
|
|
377
|
+
? {
|
|
378
|
+
...data,
|
|
379
|
+
transcript: failed[0].transcript,
|
|
380
|
+
...(failed[1] ? { retryTranscript: failed[1].transcript } : {}),
|
|
381
|
+
}
|
|
382
|
+
: data;
|
|
383
|
+
const row = {
|
|
384
|
+
ts: new Date().toISOString(), event, ...(taskId ? { taskId } : {}), data: persistedData,
|
|
385
|
+
};
|
|
352
386
|
// T3 secret redaction: only the persisted bytes are masked — the caller's data stays untouched in
|
|
353
387
|
// memory. The narrator receives the persisted (masked) row so a pane sink never shows a credential.
|
|
354
388
|
const line = redactSecrets(JSON.stringify(row));
|
|
@@ -359,6 +393,29 @@ export class Journal {
|
|
|
359
393
|
catch {
|
|
360
394
|
// narration is observational; a broken sink must not affect the journal or run
|
|
361
395
|
}
|
|
396
|
+
if (evidence && !evidence.written && (event === "judge-retry" || event === "gate-result")) {
|
|
397
|
+
evidence.written = true;
|
|
398
|
+
for (const invocation of evidence.invocations) {
|
|
399
|
+
const colon = invocation.channel.indexOf(":");
|
|
400
|
+
const adapter = colon === -1 ? invocation.channel : invocation.channel.slice(0, colon);
|
|
401
|
+
const model = colon === -1 ? "" : invocation.channel.slice(colon + 1);
|
|
402
|
+
this.telemetry({
|
|
403
|
+
kind: "judge",
|
|
404
|
+
taskId: invocation.taskId,
|
|
405
|
+
shape: "judge",
|
|
406
|
+
adapter,
|
|
407
|
+
model,
|
|
408
|
+
channel: invocation.channel,
|
|
409
|
+
attempts: 1,
|
|
410
|
+
outcome: invocation.outcome,
|
|
411
|
+
durationMs: invocation.durationMs,
|
|
412
|
+
judgeOutcome: invocation.judgeOutcome,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
phaseStart(taskId, phase, data = {}) {
|
|
418
|
+
this.append("phase-start", taskId, { ...data, phase });
|
|
362
419
|
}
|
|
363
420
|
read() {
|
|
364
421
|
return readJsonl(this.journalPath);
|
|
@@ -461,6 +518,20 @@ export class Journal {
|
|
|
461
518
|
}
|
|
462
519
|
return m;
|
|
463
520
|
}
|
|
521
|
+
// OBS-130: gate satisfaction is authority, not an inferred daemon state. Only an explicit
|
|
522
|
+
// task-approved event with the typed release marker and a known gate enters this fold. A daemon-made
|
|
523
|
+
// gate-satisfied event, a prior pass/fail result, malformed data, or another task's approval is inert.
|
|
524
|
+
replaySatisfiedGates() {
|
|
525
|
+
const satisfied = new Map();
|
|
526
|
+
for (const e of this.read()) {
|
|
527
|
+
if (e.event !== "task-approved" || !e.taskId || e.data.release !== GATE_SATISFIED_RELEASE)
|
|
528
|
+
continue;
|
|
529
|
+
if (typeof e.data.gate === "string" && GATE_NAMES.includes(e.data.gate)) {
|
|
530
|
+
satisfied.set(e.taskId, e.data.gate);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return satisfied;
|
|
534
|
+
}
|
|
464
535
|
// v1.71 OBS-119: run-wide exclusion fold — same replay discipline as replayResumeState().
|
|
465
536
|
// channel-exclusion is the typed event; dead-channel-failover.from is the pre-v1.71 compat seam.
|
|
466
537
|
replayExcludedChannels() {
|
|
@@ -479,8 +550,15 @@ export class Journal {
|
|
|
479
550
|
// T3 secret redaction: same persistence seam as append — credential-free rows are byte-identical.
|
|
480
551
|
appendFileSync(join(this.dir, "telemetry.jsonl"), redactSecrets(JSON.stringify(row)) + "\n");
|
|
481
552
|
}
|
|
482
|
-
// Per-run
|
|
553
|
+
// Per-run task rows stay byte-compatible for legacy readers (report and task-level test helpers).
|
|
554
|
+
// Judge rows share telemetry.jsonl but have a dedicated reader so their earlier gate-time ordering
|
|
555
|
+
// cannot make a find(taskId) caller mistake invocation evidence for the later terminal task row.
|
|
483
556
|
readTelemetry() {
|
|
484
|
-
return readJsonl(join(this.dir, "telemetry.jsonl"))
|
|
557
|
+
return readJsonl(join(this.dir, "telemetry.jsonl"))
|
|
558
|
+
.filter((row) => !(row && typeof row === "object" && "kind" in row && row.kind === "judge"));
|
|
559
|
+
}
|
|
560
|
+
readJudgeTelemetry() {
|
|
561
|
+
return readJsonl(join(this.dir, "telemetry.jsonl"))
|
|
562
|
+
.filter((row) => row && typeof row === "object" && "kind" in row && row.kind === "judge");
|
|
485
563
|
}
|
|
486
564
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { type SaveTarget } from "../save.js";
|
|
2
3
|
export type FleetListRow = {
|
|
3
4
|
id: string;
|
|
4
5
|
content: ReactNode;
|
|
5
6
|
};
|
|
7
|
+
export declare function TextLines({ lines }: {
|
|
8
|
+
lines: string[];
|
|
9
|
+
}): import("react").JSX.Element;
|
|
6
10
|
export declare function ToggleMark({ active }: {
|
|
7
11
|
active: boolean;
|
|
8
12
|
}): import("react").JSX.Element;
|
|
@@ -18,3 +22,17 @@ export declare function FleetReviewScreen({ title, legend, diff, }: {
|
|
|
18
22
|
legend: string;
|
|
19
23
|
diff: string;
|
|
20
24
|
}): import("react").JSX.Element;
|
|
25
|
+
export type DiffConfirmationProps = {
|
|
26
|
+
target: SaveTarget;
|
|
27
|
+
targetPath: string;
|
|
28
|
+
diff: string;
|
|
29
|
+
liveRun: boolean;
|
|
30
|
+
};
|
|
31
|
+
export declare function DiffConfirmationScreen(props: DiffConfirmationProps): import("react").JSX.Element;
|
|
32
|
+
export declare function StudioScreen({ tabs, active, lines, status, children, }: {
|
|
33
|
+
tabs: string[];
|
|
34
|
+
active: number;
|
|
35
|
+
lines: string[];
|
|
36
|
+
status?: string;
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
@@ -1,14 +1,42 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { GLYPHS } from "../../brand.js";
|
|
4
|
+
import { RELOAD_GUARD_NOTICE } from "../save.js";
|
|
5
|
+
function keyedLines(lines) {
|
|
6
|
+
const occurrences = new Map();
|
|
7
|
+
return lines.map((text) => {
|
|
8
|
+
const occurrence = occurrences.get(text) ?? 0;
|
|
9
|
+
occurrences.set(text, occurrence + 1);
|
|
10
|
+
return { id: `${text}:${occurrence}`, text };
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function TextLines({ lines }) {
|
|
14
|
+
return (_jsx(_Fragment, { children: keyedLines(lines).map(({ id, text }) => _jsx(Text, { children: text || " " }, id)) }));
|
|
15
|
+
}
|
|
4
16
|
export function ToggleMark({ active }) {
|
|
5
17
|
return active
|
|
6
18
|
? _jsx(Text, { color: "ansi256(41)", children: GLYPHS.toggleActive })
|
|
7
19
|
: _jsx(Text, { dimColor: true, children: GLYPHS.toggleInactive });
|
|
8
20
|
}
|
|
9
21
|
export function FleetListScreen({ title, legend, rows, cursor, details = [], }) {
|
|
10
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: title }), _jsx(Text, { dimColor: true, children: legend }), rows.map((row, index) => (_jsxs(Text, { bold: index === cursor, children: [index === cursor ? `${GLYPHS.pointer} ` : " ", row.content] }, row.id))),
|
|
22
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: title }), _jsx(Text, { dimColor: true, children: legend }), rows.map((row, index) => (_jsxs(Text, { bold: index === cursor, children: [index === cursor ? `${GLYPHS.pointer} ` : " ", row.content] }, row.id))), _jsx(TextLines, { lines: details })] }));
|
|
11
23
|
}
|
|
12
24
|
export function FleetReviewScreen({ title, legend, diff, }) {
|
|
13
25
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: title }), _jsx(Text, { dimColor: true, children: legend }), _jsx(Text, { children: diff })] }));
|
|
14
26
|
}
|
|
27
|
+
export function DiffConfirmationScreen(props) {
|
|
28
|
+
return _jsx(TextLines, { lines: [
|
|
29
|
+
`Save overlay — ${props.targetPath} (${props.target === "repo" ? "repository" : "global"} target)`,
|
|
30
|
+
"t toggle target · y confirm · n/esc cancel",
|
|
31
|
+
...(props.liveRun ? [RELOAD_GUARD_NOTICE] : []),
|
|
32
|
+
"─ diff ─",
|
|
33
|
+
props.diff || "(no overlay changes)",
|
|
34
|
+
] });
|
|
35
|
+
}
|
|
36
|
+
export function StudioScreen({ tabs, active, lines, status = "no staged changes", children, }) {
|
|
37
|
+
const tabBar = [
|
|
38
|
+
"tickmarkr ui ──",
|
|
39
|
+
...tabs.map((label, index) => `[${index + 1}]${index === active ? "*" : ""}${label}`),
|
|
40
|
+
].join(" ");
|
|
41
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: tabBar }), children ?? _jsx(TextLines, { lines: lines }), _jsx(Text, { dimColor: true, children: status })] }));
|
|
42
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type RunGraph } from "../../graph/schema.js";
|
|
2
|
+
import { type JournalEvent } from "../../run/journal.js";
|
|
3
|
+
import { FleetStaging } from "../staging.js";
|
|
4
|
+
export type StudioInkView = {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
render(props: {
|
|
8
|
+
cols: number;
|
|
9
|
+
rows: number;
|
|
10
|
+
}): string[];
|
|
11
|
+
key?(name: string): void;
|
|
12
|
+
};
|
|
13
|
+
export type StudioInkIO = {
|
|
14
|
+
input: NodeJS.ReadStream;
|
|
15
|
+
output: NodeJS.WriteStream;
|
|
16
|
+
views?: StudioInkView[];
|
|
17
|
+
repoRoot?: string;
|
|
18
|
+
globalDir?: string;
|
|
19
|
+
staging?: FleetStaging;
|
|
20
|
+
runsData?: RunsCockpitData;
|
|
21
|
+
clock?: () => number;
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type RunsCockpitData = {
|
|
25
|
+
runId?: string;
|
|
26
|
+
events: JournalEvent[];
|
|
27
|
+
graph: RunGraph;
|
|
28
|
+
prompts?: Record<string, string[]>;
|
|
29
|
+
};
|
|
30
|
+
type DossierVerdict = {
|
|
31
|
+
action: "retry" | "reroute" | "decompose" | "human";
|
|
32
|
+
reason?: string;
|
|
33
|
+
prompt?: string;
|
|
34
|
+
};
|
|
35
|
+
type ConsultDossierData = {
|
|
36
|
+
taskId: string;
|
|
37
|
+
verdicts: DossierVerdict[];
|
|
38
|
+
};
|
|
39
|
+
type RunsCockpitTask = {
|
|
40
|
+
id: string;
|
|
41
|
+
status: string;
|
|
42
|
+
phase?: string;
|
|
43
|
+
elapsed?: string;
|
|
44
|
+
consultCount: number;
|
|
45
|
+
};
|
|
46
|
+
export declare function foldConsultDossier(data: RunsCockpitData, taskId: string): ConsultDossierData;
|
|
47
|
+
export declare function buildRunsCockpitTasks(data: RunsCockpitData, nowMs: number): RunsCockpitTask[];
|
|
48
|
+
export declare function StudioApp({ views: suppliedViews, repoRoot, globalDir, staging: suppliedStaging, runsData: suppliedRunsData, clock, columns, rows, }: {
|
|
49
|
+
views?: StudioInkView[];
|
|
50
|
+
repoRoot?: string;
|
|
51
|
+
globalDir?: string;
|
|
52
|
+
staging?: FleetStaging;
|
|
53
|
+
runsData?: RunsCockpitData;
|
|
54
|
+
clock?: () => number;
|
|
55
|
+
columns?: number;
|
|
56
|
+
rows?: number;
|
|
57
|
+
}): import("react").JSX.Element;
|
|
58
|
+
export declare function runStudioInk({ input, output, views, repoRoot, globalDir, staging, runsData, clock, debug, }: StudioInkIO): Promise<void>;
|
|
59
|
+
export {};
|