@visual-z/xeno 0.2.1 → 0.3.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 +9 -0
- package/README.md +106 -26
- package/docs/install.md +51 -13
- package/docs/providers.md +11 -0
- package/package.json +5 -4
- package/src/apps/approve.ts +3 -3
- package/src/apps/commands.ts +38 -5
- package/src/apps/completions.ts +73 -19
- package/src/apps/image-draft.ts +46 -0
- package/src/apps/project-trust.ts +2 -42
- package/src/apps/provider-connect.ts +346 -0
- package/src/apps/render.ts +22 -47
- package/src/apps/resources.ts +5 -2
- package/src/apps/shell.ts +298 -1526
- package/src/apps/startup-input.ts +45 -9
- package/src/apps/startup.ts +128 -86
- package/src/apps/ui/app.ts +1383 -0
- package/src/apps/ui/extension-host.ts +194 -0
- package/src/apps/ui/image-cache.ts +78 -0
- package/src/apps/ui/palette.ts +88 -0
- package/src/apps/ui/project.ts +127 -0
- package/src/apps/ui/standalone.ts +146 -0
- package/src/apps/ui/status-model.ts +68 -0
- package/src/apps/ui/surface.ts +84 -0
- package/src/apps/ui/theme.ts +86 -0
- package/src/core/acceptance.ts +107 -0
- package/src/core/action-defaults.ts +47 -295
- package/src/core/benchmark.ts +2 -2
- package/src/core/branch-receipt.ts +24 -0
- package/src/core/checkpoints.ts +64 -0
- package/src/core/churn.ts +2 -0
- package/src/core/command-artifacts.ts +94 -0
- package/src/core/command-contract.ts +35 -0
- package/src/core/completion-review.ts +21 -5
- package/src/core/context-detail.ts +37 -0
- package/src/core/context-policy.ts +74 -1
- package/src/core/continuation.ts +17 -2
- package/src/core/derive.ts +29 -3
- package/src/core/draft-images.ts +56 -0
- package/src/core/environment.ts +145 -0
- package/src/core/evaluation-coverage.ts +74 -0
- package/src/core/event-validation.ts +80 -5
- package/src/core/events.ts +97 -3
- package/src/core/evidence-links.ts +7 -0
- package/src/core/evidence.ts +13 -3
- package/src/core/execution.ts +8 -2
- package/src/core/experiment.ts +133 -0
- package/src/core/export.ts +9 -0
- package/src/core/failure-recovery.ts +173 -0
- package/src/core/file-precondition.ts +72 -0
- package/src/core/handoff.ts +65 -4
- package/src/core/history-query.ts +38 -2
- package/src/core/history.ts +38 -1
- package/src/core/integration-health.ts +34 -0
- package/src/core/keymap.ts +8 -20
- package/src/core/model-picker.ts +64 -0
- package/src/core/native-eval.ts +25 -0
- package/src/core/note-guidance.ts +34 -0
- package/src/core/observation-view.ts +49 -8
- package/src/core/permissions.ts +24 -2
- package/src/core/process-receipt.ts +105 -0
- package/src/core/process-state.ts +37 -0
- package/src/core/project.ts +45 -18
- package/src/core/prompt-policy.ts +37 -0
- package/src/core/query-eval-contract.ts +14 -0
- package/src/core/read-observation.ts +82 -0
- package/src/core/recovery-corpus.ts +17 -0
- package/src/core/recovery-evaluation.ts +60 -0
- package/src/core/recovery-sources.ts +74 -0
- package/src/core/runtime-target.ts +94 -0
- package/src/core/search-observation.ts +97 -0
- package/src/core/session-tree.ts +147 -0
- package/src/core/settings.ts +26 -10
- package/src/core/skills.ts +44 -0
- package/src/core/sql-query.ts +211 -0
- package/src/core/theme.ts +36 -58
- package/src/core/tool-contract.ts +51 -2
- package/src/core/tool-discovery.ts +87 -0
- package/src/core/tool-failure.ts +60 -0
- package/src/core/tool-mode-protocol.ts +25 -0
- package/src/core/transcript.ts +19 -4
- package/src/core/trial-lifecycle.ts +14 -0
- package/src/core/tui-mode.ts +29 -0
- package/src/core/update-check.ts +97 -0
- package/src/core/version.ts +55 -0
- package/src/core/work-state.ts +49 -3
- package/src/core/working-set.ts +91 -0
- package/src/runtime/auth.ts +2 -2
- package/src/runtime/branch-jobs.ts +36 -0
- package/src/runtime/clipboard.ts +15 -4
- package/src/runtime/command-artifacts.ts +64 -0
- package/src/runtime/environment-tools.ts +60 -0
- package/src/runtime/evidence-notes.ts +4 -2
- package/src/runtime/exec-sessions.ts +206 -61
- package/src/runtime/execution-control.ts +1 -0
- package/src/runtime/extension-ui.ts +22 -30
- package/src/runtime/extensions.ts +95 -16
- package/src/runtime/harness-comparison.ts +27 -7
- package/src/runtime/harness-eval.ts +460 -0
- package/src/runtime/history-cache.ts +57 -0
- package/src/runtime/history-tools.ts +62 -12
- package/src/runtime/integrations.ts +210 -0
- package/src/runtime/loop.ts +1021 -268
- package/src/runtime/model-discovery.ts +208 -0
- package/src/runtime/native-eval-fixtures.ts +33 -0
- package/src/runtime/native-eval.ts +143 -0
- package/src/runtime/oauth.ts +1 -1
- package/src/runtime/provider-connect.ts +109 -0
- package/src/runtime/provider-presets.ts +87 -0
- package/src/runtime/query-eval-verifier.ts +28 -0
- package/src/runtime/recovery-coverage.ts +71 -0
- package/src/runtime/recovery-evidence.ts +76 -0
- package/src/runtime/recovery-fixtures.ts +116 -0
- package/src/runtime/runtime-target.ts +149 -0
- package/src/runtime/tool-broker.ts +137 -0
- package/src/runtime/tool-discovery.ts +57 -0
- package/src/runtime/tool-mode-eval.ts +146 -0
- package/src/runtime/tool-queue.ts +77 -0
- package/src/runtime/tools.ts +628 -183
- package/src/runtime/trial-control.ts +45 -0
- package/src/runtime/update-check.ts +47 -0
- package/src/runtime/window-trial.ts +2 -1
- package/src/runtime/working-notes.ts +33 -3
- package/src/runtime/workspace-lifecycle.ts +97 -0
- package/src/store/acceptance.ts +15 -0
- package/src/store/checkpoint-history.ts +43 -0
- package/src/store/command-artifacts.ts +96 -0
- package/src/store/command.ts +65 -37
- package/src/store/file-checkpoints.ts +104 -0
- package/src/store/file-index.ts +13 -1
- package/src/store/git-checkpoints.ts +67 -0
- package/src/store/observations.ts +61 -20
- package/src/store/process-receipts.ts +34 -0
- package/src/store/query-source.ts +13 -8
- package/src/store/recovery.ts +2 -3
- package/src/store/replay.ts +16 -4
- package/src/store/runtime-target.ts +64 -0
- package/src/store/search.ts +171 -132
- package/src/store/session-archive.ts +4 -0
- package/src/store/source-cancellation.ts +6 -0
- package/src/store/update-cache.ts +36 -0
- package/src/store/workspace-observation.ts +113 -0
- package/src/tui/ansi-spans.ts +126 -0
- package/src/tui/style.ts +19 -16
- package/src/tui/ui/atomic-draft.ts +121 -0
- package/src/tui/ui/canvas.ts +96 -0
- package/src/tui/ui/colors.ts +70 -0
- package/src/tui/ui/composer.ts +199 -0
- package/src/tui/ui/dialog.ts +379 -0
- package/src/tui/ui/draft.ts +23 -0
- package/src/tui/ui/edge.ts +186 -0
- package/src/tui/ui/image-size.ts +40 -0
- package/src/tui/ui/ink.ts +210 -0
- package/src/tui/ui/items.ts +40 -0
- package/src/tui/ui/keys.ts +40 -0
- package/src/tui/ui/layout.ts +74 -0
- package/src/tui/ui/list-model.ts +86 -0
- package/src/tui/ui/list-view.ts +172 -0
- package/src/tui/ui/mascot.ts +67 -0
- package/src/tui/ui/messages.ts +518 -0
- package/src/tui/ui/motion.ts +28 -0
- package/src/tui/ui/paths.ts +10 -0
- package/src/tui/ui/pictures.ts +78 -0
- package/src/tui/ui/screen.ts +421 -0
- package/src/tui/ui/status.ts +271 -0
- package/src/tui/ui/styled.ts +85 -0
- package/src/tui/ui/testing.ts +5 -0
- package/src/tui/ui/tool-card.ts +150 -0
- package/src/apps/extension-ui.ts +0 -330
- package/src/apps/resource-browser.ts +0 -75
- package/src/apps/session-browser.ts +0 -113
- package/src/apps/session-picker.ts +0 -55
- package/src/tui/ansi.ts +0 -67
- package/src/tui/autocomplete.ts +0 -29
- package/src/tui/chat-viewport.ts +0 -47
- package/src/tui/complete.ts +0 -87
- package/src/tui/editor.ts +0 -940
- package/src/tui/host.ts +0 -264
- package/src/tui/input.ts +0 -75
- package/src/tui/keys.ts +0 -378
- package/src/tui/markdown.ts +0 -17
- package/src/tui/render.ts +0 -153
- package/src/tui/select.ts +0 -258
- package/src/tui/settings.ts +0 -44
- package/src/tui/statusline.ts +0 -162
- package/src/tui/system-theme.ts +0 -700
- package/src/tui/terminal.ts +0 -30
- package/src/tui/text.ts +0 -52
- package/src/tui/toolview.ts +0 -386
- package/src/tui/transcript.ts +0 -123
- package/src/tui/tree.ts +0 -471
- package/src/vendor/pi-tui/LICENSE.upstream +0 -21
- package/src/vendor/pi-tui/UPSTREAM.json +0 -50
- package/src/vendor/pi-tui/VENDOR.md +0 -25
- package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
- package/src/vendor/pi-tui/autocomplete.ts +0 -860
- package/src/vendor/pi-tui/colors.ts +0 -367
- package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
- package/src/vendor/pi-tui/components/box.ts +0 -166
- package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
- package/src/vendor/pi-tui/components/editor.ts +0 -2472
- package/src/vendor/pi-tui/components/h-stack.ts +0 -44
- package/src/vendor/pi-tui/components/image.ts +0 -127
- package/src/vendor/pi-tui/components/input.ts +0 -494
- package/src/vendor/pi-tui/components/loader.ts +0 -101
- package/src/vendor/pi-tui/components/markdown.ts +0 -1021
- package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
- package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
- package/src/vendor/pi-tui/components/select-list.ts +0 -273
- package/src/vendor/pi-tui/components/settings-list.ts +0 -328
- package/src/vendor/pi-tui/components/spacer.ts +0 -28
- package/src/vendor/pi-tui/components/stack.ts +0 -154
- package/src/vendor/pi-tui/components/text.ts +0 -107
- package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
- package/src/vendor/pi-tui/components/v-stack.ts +0 -33
- package/src/vendor/pi-tui/editor-component.ts +0 -74
- package/src/vendor/pi-tui/fuzzy.ts +0 -138
- package/src/vendor/pi-tui/index.ts +0 -186
- package/src/vendor/pi-tui/keybindings.ts +0 -320
- package/src/vendor/pi-tui/keys.ts +0 -1401
- package/src/vendor/pi-tui/kill-ring.ts +0 -46
- package/src/vendor/pi-tui/latex.ts +0 -1506
- package/src/vendor/pi-tui/layout-node.ts +0 -51
- package/src/vendor/pi-tui/layout.ts +0 -449
- package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
- package/src/vendor/pi-tui/native/clipboard.h +0 -208
- package/src/vendor/pi-tui/native/darwin/README.md +0 -22
- package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
- package/src/vendor/pi-tui/native/linux/README.md +0 -23
- package/src/vendor/pi-tui/native/linux/build.sh +0 -65
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
- package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
- package/src/vendor/pi-tui/native/napi.h +0 -91
- package/src/vendor/pi-tui/native/win32/README.md +0 -31
- package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
- package/src/vendor/pi-tui/native-modifiers.ts +0 -13
- package/src/vendor/pi-tui/native-module-path.ts +0 -31
- package/src/vendor/pi-tui/native-platform.ts +0 -65
- package/src/vendor/pi-tui/oklab.ts +0 -233
- package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
- package/src/vendor/pi-tui/terminal-colors.ts +0 -91
- package/src/vendor/pi-tui/terminal-image.ts +0 -731
- package/src/vendor/pi-tui/terminal.ts +0 -554
- package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
- package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
- package/src/vendor/pi-tui/tui.ts +0 -1493
- package/src/vendor/pi-tui/undo-stack.ts +0 -28
- package/src/vendor/pi-tui/utils.ts +0 -1385
- package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
- package/src/vendor/pi-tui/word-navigation.ts +0 -117
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log → status facts for the sidebar and top bar (docs/tasks/refactor-tui-dock-layout.md). Pure.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is a projection of what the log already records: file
|
|
5
|
+
* counts come from the logged input of edit calls that succeeded, handoff
|
|
6
|
+
* entries from the derived preview (D8), background work from the processes
|
|
7
|
+
* and branches the log observed. Nothing reads model narration (D11).
|
|
8
|
+
*/
|
|
9
|
+
import type { Handoff } from "../../core/handoff.ts";
|
|
10
|
+
import type { TranscriptItem } from "../../core/transcript.ts";
|
|
11
|
+
import { diffCounts } from "../../tui/ui/tool-card.ts";
|
|
12
|
+
import type { FileStatus, StatusModel } from "../../tui/ui/status.ts";
|
|
13
|
+
|
|
14
|
+
/** Files the session touched, most recent first. Counts only for files changed solely by edits (a write's prior content is unknown). */
|
|
15
|
+
export function fileChanges(items: readonly TranscriptItem[]): FileStatus[] {
|
|
16
|
+
const files = new Map<string, { edited: boolean; added: number; removed: number; rewritten: boolean; at: number }>();
|
|
17
|
+
items.forEach((item, at) => {
|
|
18
|
+
if (item.kind !== "tool" || !item.result?.ok) return;
|
|
19
|
+
const input = item.input !== null && typeof item.input === "object" ? item.input as Record<string, unknown> : {};
|
|
20
|
+
const path = typeof input["path"] === "string" ? input["path"] : undefined;
|
|
21
|
+
if (!path || !["read", "edit", "write"].includes(item.name)) return;
|
|
22
|
+
const entry = files.get(path) ?? { edited: false, added: 0, removed: 0, rewritten: false, at };
|
|
23
|
+
entry.at = at;
|
|
24
|
+
if (item.name === "edit") { const counts = diffCounts(item.input); entry.edited = true; entry.added += counts.added; entry.removed += counts.removed; }
|
|
25
|
+
if (item.name === "write") { entry.edited = true; entry.rewritten = true; }
|
|
26
|
+
files.set(path, entry);
|
|
27
|
+
});
|
|
28
|
+
return [...files.entries()].sort((a, b) => b[1].at - a[1].at)
|
|
29
|
+
.map(([path, entry]) => ({ path, edited: entry.edited, added: entry.rewritten ? 0 : entry.added, removed: entry.rewritten ? 0 : entry.removed }));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function firstPrompt(items: readonly TranscriptItem[]): string | null {
|
|
33
|
+
const user = items.find(item => item.kind === "user" && item.text.trim());
|
|
34
|
+
return user?.kind === "user" ? user.text.trim() : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Derived = Pick<StatusModel, "handoff" | "tasks" | "background">;
|
|
38
|
+
|
|
39
|
+
/** The parts of a handoff preview the status surfaces show. Each list keeps its most recent entries last. */
|
|
40
|
+
export function handoffStatus(handoff: Handoff): Derived {
|
|
41
|
+
const background = [
|
|
42
|
+
...handoff.branches.filter(branch => branch.background && branch.resolution === null && !branch.handoff_ref).map(branch => `branch ${branch.node} · ${branch.purpose}`),
|
|
43
|
+
...(handoff.processes ?? []).filter(process => process.observed === "running").map(process => process.command ?? `process ${process.session}`),
|
|
44
|
+
];
|
|
45
|
+
const tasks = (handoff.state.work_items ?? []).filter(item => item.status === "active" || item.status === "blocked")
|
|
46
|
+
.map(item => ({ what: item.what, status: item.status === "blocked" ? "blocked" as const : "active" as const }));
|
|
47
|
+
return {
|
|
48
|
+
handoff: {
|
|
49
|
+
verified: handoff.state.completed.map(item => item.what),
|
|
50
|
+
inFlight: handoff.state.in_flight.map(item => item.what),
|
|
51
|
+
unverified: handoff.state.unverified_claims.map(item => item.what),
|
|
52
|
+
},
|
|
53
|
+
tasks, background,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
58
|
+
|
|
59
|
+
/** "20:13" today, "Oct 5 20:13" on another day, in the given local offset. */
|
|
60
|
+
export function sessionClock(iso: string, now: Date, offsetMinutes = -now.getTimezoneOffset()): string | undefined {
|
|
61
|
+
const at = Date.parse(iso);
|
|
62
|
+
if (!Number.isFinite(at)) return undefined;
|
|
63
|
+
const local = (ms: number) => new Date(ms + offsetMinutes * 60_000);
|
|
64
|
+
const t = local(at), n = local(now.getTime());
|
|
65
|
+
const hhmm = `${String(t.getUTCHours()).padStart(2, "0")}:${String(t.getUTCMinutes()).padStart(2, "0")}`;
|
|
66
|
+
const sameDay = t.getUTCFullYear() === n.getUTCFullYear() && t.getUTCMonth() === n.getUTCMonth() && t.getUTCDate() === n.getUTCDate();
|
|
67
|
+
return sameDay ? hhmm : `${MONTHS[t.getUTCMonth()]} ${t.getUTCDate()} ${hhmm}`;
|
|
68
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What command and turn code may ask of the interactive UI. shell.ts
|
|
3
|
+
* depends on this interface only; apps/ui/app.ts implements it. A null Ui
|
|
4
|
+
* means print/json/line mode, where every branch has a text fallback.
|
|
5
|
+
*/
|
|
6
|
+
import type { ImageRef, UserInput } from "../../core/images.ts";
|
|
7
|
+
import type { Phase, QueueKind } from "../../core/queue.ts";
|
|
8
|
+
import type { PermissionRequest } from "../../core/permissions.ts";
|
|
9
|
+
import type { ApprovalChoice } from "../../runtime/tools.ts";
|
|
10
|
+
import type { LoopEvent } from "../../runtime/loop.ts";
|
|
11
|
+
import type { ExtensionUI } from "../../runtime/extension-ui.ts";
|
|
12
|
+
import type { ClipboardContent } from "../../runtime/clipboard.ts";
|
|
13
|
+
import type { CallbackServer, OAuthCredential, OAuthInteraction } from "../../runtime/oauth.ts";
|
|
14
|
+
|
|
15
|
+
export interface TurnSurface {
|
|
16
|
+
pushDelta(kind: "text" | "thinking", text: string): void;
|
|
17
|
+
discardStream(): void;
|
|
18
|
+
commitStream(): void;
|
|
19
|
+
syncTranscript(): Promise<void>;
|
|
20
|
+
pushProgress?(progress: NonNullable<LoopEvent["progress"]>): void;
|
|
21
|
+
pullQueued?(phase: Phase): Promise<UserInput | null>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Ui extends TurnSurface {
|
|
25
|
+
readonly extensionUi: ExtensionUI;
|
|
26
|
+
readonly signal: AbortSignal | undefined;
|
|
27
|
+
readonly queueLength: number;
|
|
28
|
+
sessionName: string | null;
|
|
29
|
+
oauthEffects: OAuthEffects;
|
|
30
|
+
clipboardWriter: (text: string) => boolean | Promise<boolean>;
|
|
31
|
+
clipboardReader: () => Promise<ClipboardContent>;
|
|
32
|
+
|
|
33
|
+
/** Notices and documents in the message stream. */
|
|
34
|
+
notice(text: string, tone?: "info" | "success" | "warning" | "error"): void;
|
|
35
|
+
showDocument(title: string, text: string): void;
|
|
36
|
+
toast(text: string, tone?: "info" | "success" | "warning" | "error"): void;
|
|
37
|
+
redraw(): void;
|
|
38
|
+
|
|
39
|
+
/** A masked single-line form; resolves undefined when cancelled. The value never reaches a renderable. */
|
|
40
|
+
askSecret(title: string, label: string, hint: string): Promise<string | undefined>;
|
|
41
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
42
|
+
/** A cancellable "waiting" dialog (OAuth browser / device code). Returns a closer. */
|
|
43
|
+
waiting(title: string, lines: readonly string[], onCancel: () => void, pasteLabel?: string, onPaste?: (value: string) => void): () => void;
|
|
44
|
+
|
|
45
|
+
openLogin(method?: "oauth" | "api_key"): void;
|
|
46
|
+
openLogout(): void;
|
|
47
|
+
openModel(): void;
|
|
48
|
+
openScopedModels(): void;
|
|
49
|
+
openThinking(): void;
|
|
50
|
+
openSettings(field?: string): void;
|
|
51
|
+
openResume(): Promise<void>;
|
|
52
|
+
openFork(): Promise<void>;
|
|
53
|
+
openTree(): Promise<void>;
|
|
54
|
+
openResources(): Promise<void>;
|
|
55
|
+
openTrust(): Promise<void>;
|
|
56
|
+
openAttachments(): void;
|
|
57
|
+
|
|
58
|
+
restoreDraft(text: string, images?: readonly ImageRef[]): void;
|
|
59
|
+
refreshResources(): Promise<void>;
|
|
60
|
+
applyConfiguration(): void;
|
|
61
|
+
followTheme(): void;
|
|
62
|
+
beginBash(command: string): void;
|
|
63
|
+
pushBash(chunk: Uint8Array): void;
|
|
64
|
+
ask(request: PermissionRequest): Promise<ApprovalChoice>;
|
|
65
|
+
|
|
66
|
+
runExtensionAction(action: () => void | Promise<void>): void;
|
|
67
|
+
runExtensionCommand(name: string, args: string): void;
|
|
68
|
+
runSessionTransition(action: () => Promise<void>): void;
|
|
69
|
+
deliverExtension(text: string, kind: QueueKind): void;
|
|
70
|
+
bindExtensionRegistry(): void;
|
|
71
|
+
requestShutdown(): void;
|
|
72
|
+
/** Abort the running turn (Esc semantics) without quitting. */
|
|
73
|
+
interrupt(): void;
|
|
74
|
+
/** Plain-text snapshot of the current frame (/debug). */
|
|
75
|
+
snapshot(): { width: number; height: number; lines: string[] };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Injectable OAuth side effects (browser, callback server, device flow, fetch). */
|
|
79
|
+
export interface OAuthEffects {
|
|
80
|
+
fetch: typeof globalThis.fetch;
|
|
81
|
+
openBrowser(url: string): void;
|
|
82
|
+
callback(options: { expectedState: string; host: string; port: number; path: string }): Promise<CallbackServer>;
|
|
83
|
+
device(interaction: OAuthInteraction): Promise<OAuthCredential>;
|
|
84
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core Theme + the terminal's reported palette → the view's colours.
|
|
3
|
+
*
|
|
4
|
+
* Built-in appearances (dark, light, system/auto) take every colour from the
|
|
5
|
+
* terminal (tui/ui/ink.ts); their name only says which way to guess when the
|
|
6
|
+
* terminal does not answer the palette query. A custom theme file is an
|
|
7
|
+
* explicit choice and is drawn with its own colours; it paints a background
|
|
8
|
+
* only when it sets one (refactor-tui-terminal-colors).
|
|
9
|
+
*/
|
|
10
|
+
import type { TerminalColors } from "@opentui/core";
|
|
11
|
+
import { isBuiltinTheme, type Theme } from "../../core/theme.ts";
|
|
12
|
+
import { colorsFromInks, rgba, type UiColors } from "../../tui/ui/colors.ts";
|
|
13
|
+
import { isHex, terminalInks, type InkSet, type InkSpec, type TerminalPalette } from "../../tui/ui/ink.ts";
|
|
14
|
+
|
|
15
|
+
/** OpenTUI's detection result → the 16-slot palette ink.ts expects, or null when incomplete. */
|
|
16
|
+
export function terminalPalette(colors: TerminalColors | null | undefined): TerminalPalette | null {
|
|
17
|
+
if (!colors) return null;
|
|
18
|
+
const slots = colors.palette.slice(0, 16).map(hex => normalizeHex(hex));
|
|
19
|
+
const foreground = normalizeHex(colors.defaultForeground), background = normalizeHex(colors.defaultBackground);
|
|
20
|
+
if (!foreground || !background || slots.length < 16 || slots.some(slot => !slot)) return null;
|
|
21
|
+
const hb = normalizeHex(colors.highlightBackground), hf = normalizeHex(colors.highlightForeground);
|
|
22
|
+
return { foreground, background, slots: slots as string[], ...(hb && hf ? { highlight: { background: hb, foreground: hf } } : {}) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeHex(value: string | null | undefined): string | undefined {
|
|
26
|
+
if (!value) return undefined;
|
|
27
|
+
const hex = value.startsWith("#") ? value : `#${value}`;
|
|
28
|
+
return isHex(hex) ? hex.toLowerCase() : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hint(theme: Theme): "dark" | "light" { return theme.name === "light" ? "light" : "dark"; }
|
|
32
|
+
|
|
33
|
+
/** "38;2;r;g;b" / "48;2;r;g;b" → "#rrggbb"; anything else is undefined. */
|
|
34
|
+
export function sgrHex(value: string | undefined): string | undefined {
|
|
35
|
+
const match = value ? /(?:^|;)[34]8;2;(\d+);(\d+);(\d+)/.exec(value) : null;
|
|
36
|
+
if (!match) return undefined;
|
|
37
|
+
return "#" + [match[1], match[2], match[3]].map(n => Math.max(0, Math.min(255, Number(n))).toString(16).padStart(2, "0")).join("");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function uiColors(theme: Theme, palette: TerminalPalette | null = null): UiColors {
|
|
41
|
+
const terminal = colorsFromInks(terminalInks(palette, hint(theme)));
|
|
42
|
+
if (isBuiltinTheme(theme)) return terminal;
|
|
43
|
+
// A theme file's colours are the user's explicit choice; tokens it leaves
|
|
44
|
+
// out (or sets to the terminal default) still come from the terminal.
|
|
45
|
+
const pick = (...tokens: string[]) => { const hex = tokens.map(token => sgrHex(theme.colors[token])).find(Boolean); return hex ? rgba(hex) : undefined; };
|
|
46
|
+
return {
|
|
47
|
+
background: pick("background") ?? terminal.background,
|
|
48
|
+
text: pick("text") ?? terminal.text,
|
|
49
|
+
muted: pick("muted", "toolOutput") ?? terminal.muted,
|
|
50
|
+
edge: pick("border", "borderMuted") ?? terminal.edge,
|
|
51
|
+
line: pick("borderMuted", "border") ?? terminal.line,
|
|
52
|
+
accent: pick("accent", "mdLink") ?? terminal.accent,
|
|
53
|
+
success: pick("success", "toolDiffAdded") ?? terminal.success,
|
|
54
|
+
error: pick("error", "toolDiffRemoved") ?? terminal.error,
|
|
55
|
+
warning: pick("warning") ?? terminal.warning,
|
|
56
|
+
reasoning: pick("thinkingText") ?? terminal.reasoning,
|
|
57
|
+
code: pick("mdCode") ?? terminal.code,
|
|
58
|
+
selectionBg: pick("selectedBg") ?? terminal.selectionBg,
|
|
59
|
+
selectionFg: terminal.selectionFg,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* SGR tokens for the ANSI renderers (tool details, diffs, code highlighting)
|
|
65
|
+
* from the same terminal-derived inks, so text drawn through ANSI strings
|
|
66
|
+
* matches the view. Custom themes keep their own tokens.
|
|
67
|
+
*/
|
|
68
|
+
export function styleTokens(theme: Theme, palette: TerminalPalette | null): Readonly<Record<string, string>> {
|
|
69
|
+
if (!isBuiltinTheme(theme)) return theme.colors;
|
|
70
|
+
const inks: InkSet = terminalInks(palette, hint(theme));
|
|
71
|
+
const sgr = (spec: InkSpec): string => spec.kind === "default" ? "" : spec.kind === "index"
|
|
72
|
+
? String(spec.index < 8 ? 30 + spec.index : 90 + spec.index - 8)
|
|
73
|
+
: `38;2;${[1, 3, 5].map(i => Number.parseInt(spec.hex.slice(i, i + 2), 16)).join(";")}`;
|
|
74
|
+
const muted = sgr(inks.muted);
|
|
75
|
+
return {
|
|
76
|
+
...theme.colors,
|
|
77
|
+
text: "", prompt: "", toolTitle: "",
|
|
78
|
+
muted, dim: muted, status: muted, toolOutput: muted, thinkingOff: muted, syntaxComment: muted,
|
|
79
|
+
error: sgr(inks.error), toolDiffRemoved: sgr(inks.error), toolDiffAdded: sgr(inks.success), success: sgr(inks.success),
|
|
80
|
+
warning: sgr(inks.warning), syntaxNumber: sgr(inks.warning),
|
|
81
|
+
accent: sgr(inks.accent), mdHeading: sgr(inks.accent), mdLink: sgr(inks.accent), syntaxFunction: sgr(inks.accent),
|
|
82
|
+
mdCode: sgr(inks.code), syntaxString: sgr(inks.success),
|
|
83
|
+
thinkingText: sgr(inks.reasoning), syntaxKeyword: sgr(inks.reasoning),
|
|
84
|
+
border: sgr(inks.line), borderMuted: sgr(inks.line),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** O.11: declared criteria, command evidence and human acceptance are separate. */
|
|
2
|
+
import type { LogEvent } from "./events.ts";
|
|
3
|
+
import type { Evidence } from "./handoff.ts";
|
|
4
|
+
import { evidenceStale } from "./evidence.ts";
|
|
5
|
+
import { changedPaths } from "./checkpoints.ts";
|
|
6
|
+
import { parseRuntimeTarget, type RuntimeTarget, type TargetAcceptance } from "./runtime-target.ts";
|
|
7
|
+
|
|
8
|
+
export type AcceptanceCriterion = { id: string; description: string } &
|
|
9
|
+
({ kind: "command"; command: string } | { kind: "human"; target?: string });
|
|
10
|
+
export type AcceptanceContract = { version: 1; id: string; scope?: "request" | "changes"; criteria: AcceptanceCriterion[];
|
|
11
|
+
source?: { path: string; sha256: string }; targets?: RuntimeTarget[] };
|
|
12
|
+
export type AcceptanceState = {
|
|
13
|
+
contract_id: string; contract_event: string; request: string; scope: "request" | "changes"; changed: boolean;
|
|
14
|
+
criteria: (AcceptanceCriterion & { status: "not_run" | "pass" | "fail" | "stale" | "awaiting_human" | "accepted"; evidence?: Evidence; accepted_event?: string; accepted_binding?: string })[];
|
|
15
|
+
targets?: TargetAcceptance[];
|
|
16
|
+
status: "needs_work" | "awaiting_human" | "verified" | "not_applicable";
|
|
17
|
+
};
|
|
18
|
+
const record = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19
|
+
const label = (value: unknown, max: number) => typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
20
|
+
|
|
21
|
+
export function parseAcceptanceContract(value: unknown): AcceptanceContract {
|
|
22
|
+
if (!record(value) || value["version"] !== 1 || !label(value["id"], 80) ||
|
|
23
|
+
(value["scope"] !== undefined && value["scope"] !== "request" && value["scope"] !== "changes") ||
|
|
24
|
+
!Array.isArray(value["criteria"]) || value["criteria"].length < 1 || value["criteria"].length > 20) throw new Error("invalid acceptance contract v1");
|
|
25
|
+
if (Object.keys(value).some(key => !["version", "id", "scope", "criteria", "source", "targets"].includes(key))) throw new Error("unsupported acceptance contract field");
|
|
26
|
+
const source = value["source"];
|
|
27
|
+
if (source !== undefined && (!record(source) || !label(source["path"], 4096) || typeof source["sha256"] !== "string" || !/^[a-f0-9]{64}$/.test(source["sha256"]))) throw new Error("invalid acceptance source");
|
|
28
|
+
const criteria: AcceptanceCriterion[] = value["criteria"].map((item: unknown) => {
|
|
29
|
+
if (!record(item) || !label(item["id"], 80) || !label(item["description"], 300)) throw new Error("invalid acceptance criterion");
|
|
30
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$/.test(String(item["id"])) || Object.keys(item).some(key => !["id", "description", "kind", "command", "target"].includes(key))) throw new Error("unsupported acceptance criterion field or ID");
|
|
31
|
+
const fields = { id: String(item["id"]), description: String(item["description"]) };
|
|
32
|
+
if (item["kind"] === "human" && item["command"] === undefined && (item["target"] === undefined || label(item["target"], 80))) return { ...fields, kind: "human", ...(item["target"] ? { target: String(item["target"]) } : {}) };
|
|
33
|
+
if (item["kind"] === "command" && item["target"] === undefined && label(item["command"], 500)) return { ...fields, kind: "command", command: String(item["command"]) };
|
|
34
|
+
throw new Error("acceptance criterion must declare a command or human decision");
|
|
35
|
+
});
|
|
36
|
+
if (new Set(criteria.map(item => item.id)).size !== criteria.length) throw new Error("duplicate acceptance criterion IDs");
|
|
37
|
+
const declarations = value["targets"];
|
|
38
|
+
if (declarations !== undefined && (!Array.isArray(declarations) || declarations.length < 1 || declarations.length > 8)) throw Error("targets requires 1..8 declared runtimes");
|
|
39
|
+
const targets = Array.isArray(declarations) ? declarations.map(parseRuntimeTarget) : undefined;
|
|
40
|
+
if (targets && (new Set(targets.map(target => target.id)).size !== targets.length || targets.some(target =>
|
|
41
|
+
!criteria.some(item => item.id === target.build && item.kind === "command") || !criteria.some(item => item.kind === "human" && item.target === target.id)))) throw Error("targets require a build condition and a bound human condition");
|
|
42
|
+
if (criteria.some(item => item.kind === "human" && item.target && !targets?.some(target => target.id === item.target))) throw Error("unknown human acceptance target");
|
|
43
|
+
return { version: 1, id: String(value["id"]), criteria,
|
|
44
|
+
...(targets ? { targets } : {}),
|
|
45
|
+
...(record(source) ? { source: { path: String(source["path"]), sha256: String(source["sha256"]) } } : {}),
|
|
46
|
+
...(value["scope"] ? { scope: value["scope"] as "request" | "changes" } : {}) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function deriveAcceptance(previous: AcceptanceState | undefined, events: readonly LogEvent[]): AcceptanceState | undefined {
|
|
50
|
+
let state = previous ? { ...previous, criteria: previous.criteria.map(item => ({ ...item })), ...(previous.targets ? { targets: previous.targets.map(target => ({ ...target })) } : {}) } : undefined;
|
|
51
|
+
for (const event of events) {
|
|
52
|
+
if (event.t === "acceptance_contract") {
|
|
53
|
+
state = { contract_id: event.contract.id, contract_event: event.id, request: event.request, scope: event.contract.scope ?? "request", changed: false,
|
|
54
|
+
criteria: event.contract.criteria.map(item => ({ ...item, status: item.kind === "human" ? "awaiting_human" : "not_run" })), status: "needs_work" };
|
|
55
|
+
if (event.contract.targets) state.targets = event.contract.targets.map(target => ({ id: target.id, build: target.build, status: "not_run" }));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!state) continue;
|
|
59
|
+
const mutation = event.t === "tool_result" && event.effect?.kind === "edit" && event.effect.sha256_before !== event.effect.sha256_after ||
|
|
60
|
+
event.t === "snapshot" && Object.keys(event.added).length + Object.keys(event.changed).length + event.removed.length > 0 ||
|
|
61
|
+
event.t === "checkpoint" && event.phase === "applied" && changedPaths(event.changes).length > 0 ||
|
|
62
|
+
event.t === "workspace_observation" && event.changed;
|
|
63
|
+
if (mutation) { state.changed = true; for (const target of state.targets ?? []) target.status = "stale"; }
|
|
64
|
+
if (event.t === "target_observation" && event.observation.request === state.request && event.observation.contract_event === state.contract_event) {
|
|
65
|
+
const target = state.targets?.find(item => item.id === event.observation.target), receipt = event.observation;
|
|
66
|
+
if (target) {
|
|
67
|
+
const build = state.criteria.find(item => item.id === target.build);
|
|
68
|
+
target.status = receipt.state === "ready" && (build?.status !== "pass" || build.evidence?.event_id !== receipt.build?.event) ? "stale" : receipt.state;
|
|
69
|
+
target.observation = event.id; target.reason = receipt.reason;
|
|
70
|
+
if (receipt.build) target.build_event = receipt.build.event;
|
|
71
|
+
if (receipt.instance) { target.binding = receipt.instance.id; target.session = receipt.instance.session; target.pid = receipt.instance.pid; target.entry = receipt.instance.entry.path; }
|
|
72
|
+
else { delete target.binding; delete target.session; delete target.pid; delete target.entry; }
|
|
73
|
+
if (receipt.port !== undefined) target.port = receipt.port;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const target of state.targets ?? []) if (event.t === "process_exit" && event.session === target.session ||
|
|
77
|
+
event.t === "tool_result" && record(event.data) && event.data["session_id"] === target.session && event.data["running"] === false) target.status = "stale";
|
|
78
|
+
for (const item of state.criteria) {
|
|
79
|
+
if (item.evidence && evidenceStale([event], item.evidence, item.status === "stale")) item.status = "stale";
|
|
80
|
+
if (mutation && item.kind === "human" && item.status === "accepted") { item.status = "awaiting_human"; delete item.accepted_event; delete item.accepted_binding; }
|
|
81
|
+
if (item.kind === "command" && event.t === "tool_result" && event.effect?.kind === "command" && event.effect.cmd === item.command) {
|
|
82
|
+
item.status = event.effect.exit === 0 && event.ok ? event.effect.workspace_observed === false ? "stale" : "pass" : "fail";
|
|
83
|
+
item.evidence = { command: event.effect.cmd, exit: event.effect.exit, event_id: event.id, at: event.at,
|
|
84
|
+
...(event.effect.workspace_scope ? { workspace_scope: event.effect.workspace_scope } : {}),
|
|
85
|
+
...(event.effect.workspace_sha256 ? { workspace_sha256: event.effect.workspace_sha256 } : {}) };
|
|
86
|
+
for (const target of state.targets ?? []) if (target.build === item.id && target.build_event !== event.id) target.status = "stale";
|
|
87
|
+
}
|
|
88
|
+
if (item.kind === "human" && event.t === "acceptance_decision" && event.request === state.request &&
|
|
89
|
+
event.contract_id === state.contract_id && event.criterion === item.id && event.actor === "user") {
|
|
90
|
+
const target = item.target ? state.targets?.find(target => target.id === item.target) : undefined;
|
|
91
|
+
if (!item.target || target?.status === "ready" && target.binding === event.target_binding && target.observation === event.target_observation) {
|
|
92
|
+
item.status = "accepted"; item.accepted_event = event.id;
|
|
93
|
+
if (event.target_binding) item.accepted_binding = event.target_binding;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const target of state.targets ?? []) if (target.status === "ready" && !state.criteria.some(item => item.id === target.build && item.status === "pass" && item.evidence?.event_id === target.build_event)) target.status = "stale";
|
|
98
|
+
for (const item of state.criteria) if (item.kind === "human" && item.target && item.status === "accepted") {
|
|
99
|
+
const target = state.targets?.find(target => target.id === item.target);
|
|
100
|
+
if (target?.status !== "ready" || target.binding !== item.accepted_binding) { item.status = "awaiting_human"; delete item.accepted_event; delete item.accepted_binding; }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (state) state.status = state.scope === "changes" && !state.changed ? "not_applicable" :
|
|
104
|
+
state.criteria.some(item => item.kind === "command" && item.status !== "pass") || state.targets?.some(target => target.status !== "ready") ? "needs_work" :
|
|
105
|
+
state.criteria.some(item => item.kind === "human" && item.status !== "accepted") ? "awaiting_human" : "verified";
|
|
106
|
+
return state;
|
|
107
|
+
}
|