@visual-z/xeno 0.8.4 → 0.9.1
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 +19 -0
- package/README.md +31 -746
- package/bin/xeno.exe +26 -0
- package/install.cjs +88 -0
- package/package.json +18 -48
- package/docs/app-server.md +0 -115
- package/docs/foundations.md +0 -88
- package/docs/install.md +0 -244
- package/docs/providers.md +0 -172
- package/docs/research/foundation-clients-2026-10-09.md +0 -27
- package/docs/research/foundation-editing-2026-10-09.md +0 -56
- package/docs/research/foundation-network-2026-10-09.md +0 -37
- package/docs/research/foundation-workflow-2026-10-09.md +0 -37
- package/src/apps/acp-server.ts +0 -41
- package/src/apps/agent-profiles.ts +0 -19
- package/src/apps/app-server.ts +0 -30
- package/src/apps/approve.ts +0 -49
- package/src/apps/cli.ts +0 -99
- package/src/apps/commands.ts +0 -319
- package/src/apps/completions.ts +0 -87
- package/src/apps/control-transport.ts +0 -90
- package/src/apps/export-cli.ts +0 -24
- package/src/apps/goal-flow.ts +0 -70
- package/src/apps/image-draft.ts +0 -46
- package/src/apps/input.ts +0 -312
- package/src/apps/model-catalog.ts +0 -69
- package/src/apps/preferences.ts +0 -20
- package/src/apps/project-trust.ts +0 -9
- package/src/apps/provider-connect.ts +0 -346
- package/src/apps/question-ui.ts +0 -27
- package/src/apps/render.ts +0 -181
- package/src/apps/resources.ts +0 -66
- package/src/apps/review.ts +0 -109
- package/src/apps/session-hub.ts +0 -96
- package/src/apps/session-lifecycle.ts +0 -64
- package/src/apps/shell.ts +0 -2071
- package/src/apps/startup-input.ts +0 -135
- package/src/apps/startup.ts +0 -1677
- package/src/apps/ui/app.ts +0 -1818
- package/src/apps/ui/control-model.ts +0 -43
- package/src/apps/ui/extension-host.ts +0 -194
- package/src/apps/ui/image-cache.ts +0 -121
- package/src/apps/ui/palette.ts +0 -88
- package/src/apps/ui/project.ts +0 -127
- package/src/apps/ui/standalone.ts +0 -148
- package/src/apps/ui/status-model.ts +0 -68
- package/src/apps/ui/surface.ts +0 -91
- package/src/apps/ui/theme.ts +0 -86
- package/src/apps/update-check.ts +0 -112
- package/src/apps/workflow.ts +0 -58
- package/src/core/acceptance.ts +0 -139
- package/src/core/action-defaults.ts +0 -58
- package/src/core/activity-status.ts +0 -52
- package/src/core/agent-profile.ts +0 -130
- package/src/core/attachment-input.ts +0 -17
- package/src/core/blind-checks.ts +0 -72
- package/src/core/boundary.ts +0 -174
- package/src/core/branch-receipt.ts +0 -26
- package/src/core/catalog-merge.ts +0 -19
- package/src/core/checkpoints.ts +0 -85
- package/src/core/churn.ts +0 -61
- package/src/core/command-artifacts.ts +0 -94
- package/src/core/command-contract.ts +0 -151
- package/src/core/completion-action.ts +0 -20
- package/src/core/completion-review.ts +0 -98
- package/src/core/context-detail.ts +0 -37
- package/src/core/context-policy.ts +0 -133
- package/src/core/context-window.ts +0 -54
- package/src/core/continuation.ts +0 -130
- package/src/core/control-protocol.ts +0 -69
- package/src/core/control-view.ts +0 -39
- package/src/core/cycle.ts +0 -17
- package/src/core/data-query.ts +0 -201
- package/src/core/deadline.ts +0 -40
- package/src/core/derive.ts +0 -516
- package/src/core/draft-images.ts +0 -56
- package/src/core/edit-plan.ts +0 -200
- package/src/core/environment.ts +0 -181
- package/src/core/event-validation.ts +0 -220
- package/src/core/events.ts +0 -670
- package/src/core/evidence-ladder.ts +0 -62
- package/src/core/evidence-links.ts +0 -68
- package/src/core/evidence.ts +0 -240
- package/src/core/execution.ts +0 -18
- package/src/core/exhaustive.ts +0 -18
- package/src/core/export.ts +0 -179
- package/src/core/extension-flags.ts +0 -14
- package/src/core/external-effects.ts +0 -488
- package/src/core/failure-recovery.ts +0 -173
- package/src/core/file-precondition.ts +0 -78
- package/src/core/file-references.ts +0 -35
- package/src/core/frontmatter.ts +0 -213
- package/src/core/git-status.ts +0 -29
- package/src/core/goal-view.ts +0 -64
- package/src/core/goal.ts +0 -200
- package/src/core/handoff.ts +0 -479
- package/src/core/history-query.ts +0 -139
- package/src/core/history.ts +0 -237
- package/src/core/image-limits.ts +0 -72
- package/src/core/images.ts +0 -46
- package/src/core/integration-health.ts +0 -34
- package/src/core/json-event.ts +0 -131
- package/src/core/json-pointer.ts +0 -46
- package/src/core/keymap.ts +0 -153
- package/src/core/lsp-framing.ts +0 -34
- package/src/core/mcp-config.ts +0 -68
- package/src/core/model-picker.ts +0 -64
- package/src/core/model-preferences.ts +0 -28
- package/src/core/model-replay.ts +0 -22
- package/src/core/model-scope.ts +0 -53
- package/src/core/note-guidance.ts +0 -34
- package/src/core/oauth-fields.ts +0 -44
- package/src/core/observation-view.ts +0 -152
- package/src/core/outcome.ts +0 -44
- package/src/core/overflow.ts +0 -59
- package/src/core/packages.ts +0 -47
- package/src/core/patch.ts +0 -130
- package/src/core/path-suggestions.ts +0 -59
- package/src/core/permissions.ts +0 -410
- package/src/core/process-receipt.ts +0 -105
- package/src/core/process-state.ts +0 -37
- package/src/core/program-status.ts +0 -40
- package/src/core/progress.ts +0 -150
- package/src/core/project.ts +0 -431
- package/src/core/prompt-policy.ts +0 -46
- package/src/core/prompts.ts +0 -111
- package/src/core/question.ts +0 -94
- package/src/core/queue.ts +0 -78
- package/src/core/read-bounds.ts +0 -24
- package/src/core/read-observation.ts +0 -106
- package/src/core/recovery-sources.ts +0 -74
- package/src/core/regex-dialect.ts +0 -25
- package/src/core/request-budget.ts +0 -23
- package/src/core/request-progress.ts +0 -87
- package/src/core/request-state.ts +0 -273
- package/src/core/resource-selection.ts +0 -36
- package/src/core/response-recovery.ts +0 -59
- package/src/core/resume.ts +0 -59
- package/src/core/review.ts +0 -68
- package/src/core/ripgrep-release.ts +0 -64
- package/src/core/runtime-target.ts +0 -94
- package/src/core/scheduling.ts +0 -8
- package/src/core/search-observation.ts +0 -97
- package/src/core/secrets.ts +0 -188
- package/src/core/session-archive.ts +0 -4
- package/src/core/session-browser.ts +0 -82
- package/src/core/session-entry.ts +0 -61
- package/src/core/session-exit.ts +0 -42
- package/src/core/session-format.ts +0 -169
- package/src/core/session-position.ts +0 -26
- package/src/core/session-tree.ts +0 -147
- package/src/core/settings.ts +0 -192
- package/src/core/skills.ts +0 -165
- package/src/core/sql-query.ts +0 -211
- package/src/core/stall.ts +0 -128
- package/src/core/stop-report.ts +0 -68
- package/src/core/text-normalize.ts +0 -81
- package/src/core/theme.ts +0 -166
- package/src/core/thinking.ts +0 -26
- package/src/core/tokens.ts +0 -151
- package/src/core/tool-contract.ts +0 -153
- package/src/core/tool-discovery.ts +0 -87
- package/src/core/tool-failure.ts +0 -81
- package/src/core/tool-input.ts +0 -44
- package/src/core/transcript.ts +0 -108
- package/src/core/tui-mode.ts +0 -29
- package/src/core/update-check.ts +0 -157
- package/src/core/version.ts +0 -55
- package/src/core/web-content.ts +0 -80
- package/src/core/work-state.ts +0 -154
- package/src/core/workflow-mode.ts +0 -34
- package/src/core/working-set.ts +0 -119
- package/src/core/yaml.ts +0 -282
- package/src/runtime/acp-agent.ts +0 -289
- package/src/runtime/agent-profiles.ts +0 -21
- package/src/runtime/auth.ts +0 -367
- package/src/runtime/blind-check.ts +0 -36
- package/src/runtime/branch-jobs.ts +0 -36
- package/src/runtime/catalog.ts +0 -285
- package/src/runtime/clipboard.ts +0 -125
- package/src/runtime/command-artifacts.ts +0 -64
- package/src/runtime/connection-reuse.ts +0 -40
- package/src/runtime/control-client.ts +0 -149
- package/src/runtime/control-connection.ts +0 -139
- package/src/runtime/counter-check.ts +0 -74
- package/src/runtime/environment-tools.ts +0 -60
- package/src/runtime/evidence-notes.ts +0 -35
- package/src/runtime/exec-sessions.ts +0 -456
- package/src/runtime/execution-control.ts +0 -40
- package/src/runtime/extension-loader.ts +0 -37
- package/src/runtime/extension-ui.ts +0 -47
- package/src/runtime/extensions.ts +0 -370
- package/src/runtime/formatter-tool.ts +0 -38
- package/src/runtime/formatter.ts +0 -50
- package/src/runtime/foundation.ts +0 -116
- package/src/runtime/history-cache.ts +0 -57
- package/src/runtime/history-tools.ts +0 -168
- package/src/runtime/images.ts +0 -80
- package/src/runtime/integrations.ts +0 -216
- package/src/runtime/loop.ts +0 -3108
- package/src/runtime/lsp-client.ts +0 -201
- package/src/runtime/mcp-client.ts +0 -264
- package/src/runtime/mcp-oauth.ts +0 -168
- package/src/runtime/mcp-schema.ts +0 -51
- package/src/runtime/model-config.ts +0 -232
- package/src/runtime/model-data.LICENSE +0 -27
- package/src/runtime/model-data.ts +0 -21
- package/src/runtime/model-discovery.ts +0 -208
- package/src/runtime/model-replay.ts +0 -10
- package/src/runtime/model-scope.ts +0 -20
- package/src/runtime/model-sources.ts +0 -151
- package/src/runtime/model.ts +0 -686
- package/src/runtime/models-fetch.ts +0 -57
- package/src/runtime/oauth-device.ts +0 -86
- package/src/runtime/oauth.ts +0 -853
- package/src/runtime/open-image.ts +0 -42
- package/src/runtime/openai-responses.ts +0 -304
- package/src/runtime/openai.ts +0 -697
- package/src/runtime/output-schema.ts +0 -26
- package/src/runtime/package-manager.ts +0 -125
- package/src/runtime/patch-tool.ts +0 -38
- package/src/runtime/permission-resources.ts +0 -18
- package/src/runtime/pi-provider.ts +0 -423
- package/src/runtime/program-status.ts +0 -38
- package/src/runtime/progress.ts +0 -26
- package/src/runtime/provider-access.ts +0 -21
- package/src/runtime/provider-connect.ts +0 -109
- package/src/runtime/provider-presets.ts +0 -98
- package/src/runtime/provider-protocols.ts +0 -29
- package/src/runtime/provider-request.ts +0 -87
- package/src/runtime/provider-tool-policy.ts +0 -33
- package/src/runtime/provider.ts +0 -580
- package/src/runtime/question-tool.ts +0 -62
- package/src/runtime/retry.ts +0 -447
- package/src/runtime/review-tool.ts +0 -48
- package/src/runtime/rich-text.ts +0 -36
- package/src/runtime/runtime-target.ts +0 -149
- package/src/runtime/secrets.ts +0 -79
- package/src/runtime/self-update.ts +0 -74
- package/src/runtime/session-control.ts +0 -265
- package/src/runtime/session-hub.ts +0 -85
- package/src/runtime/stream-idle.ts +0 -80
- package/src/runtime/tool-broker.ts +0 -137
- package/src/runtime/tool-discovery.ts +0 -58
- package/src/runtime/tool-queue.ts +0 -77
- package/src/runtime/tools.ts +0 -1820
- package/src/runtime/turn-effects.ts +0 -31
- package/src/runtime/update-check.ts +0 -48
- package/src/runtime/usage.ts +0 -253
- package/src/runtime/version.ts +0 -12
- package/src/runtime/web-http.ts +0 -56
- package/src/runtime/web-tools.ts +0 -88
- package/src/runtime/working-notes.ts +0 -60
- package/src/runtime/workspace-lifecycle.ts +0 -141
- package/src/store/acceptance.ts +0 -15
- package/src/store/agent-profile-files.ts +0 -13
- package/src/store/atomic.ts +0 -31
- package/src/store/auth-store.ts +0 -50
- package/src/store/blind-checks.ts +0 -32
- package/src/store/blobs.ts +0 -143
- package/src/store/checkpoint-history.ts +0 -32
- package/src/store/command-artifacts.ts +0 -96
- package/src/store/command.ts +0 -111
- package/src/store/config.ts +0 -69
- package/src/store/context-files.ts +0 -436
- package/src/store/file-checkpoints.ts +0 -108
- package/src/store/file-index.ts +0 -174
- package/src/store/foundation-config.ts +0 -24
- package/src/store/git-checkpoints.ts +0 -67
- package/src/store/git-status.ts +0 -15
- package/src/store/history-search.ts +0 -19
- package/src/store/log.ts +0 -725
- package/src/store/lsp-document.ts +0 -30
- package/src/store/mcp-credentials.ts +0 -85
- package/src/store/models.ts +0 -60
- package/src/store/observations.ts +0 -224
- package/src/store/packages.ts +0 -98
- package/src/store/patch.ts +0 -92
- package/src/store/permission-paths.ts +0 -34
- package/src/store/process-receipts.ts +0 -34
- package/src/store/query-source.ts +0 -49
- package/src/store/recovery.ts +0 -44
- package/src/store/replay.ts +0 -76
- package/src/store/resource-index.ts +0 -170
- package/src/store/resources.ts +0 -285
- package/src/store/restore.ts +0 -110
- package/src/store/review.ts +0 -109
- package/src/store/runtime-target.ts +0 -64
- package/src/store/search.ts +0 -728
- package/src/store/secrets.ts +0 -44
- package/src/store/serial.ts +0 -13
- package/src/store/session-archive.ts +0 -165
- package/src/store/session-browser.ts +0 -68
- package/src/store/session-position.ts +0 -27
- package/src/store/sessions.ts +0 -525
- package/src/store/source-cancellation.ts +0 -6
- package/src/store/tool-binaries.ts +0 -90
- package/src/store/trust.ts +0 -82
- package/src/store/update-cache.ts +0 -36
- package/src/store/user-settings.ts +0 -30
- package/src/store/workspace-clone.ts +0 -42
- package/src/store/workspace-observation.ts +0 -113
- package/src/store/workspace.ts +0 -115
- package/src/store/worktree.ts +0 -278
- package/src/tui/ansi-spans.ts +0 -126
- package/src/tui/diff.ts +0 -160
- package/src/tui/exhaustive.ts +0 -14
- package/src/tui/fuzzy.ts +0 -75
- package/src/tui/style.ts +0 -208
- package/src/tui/ui/atomic-draft.ts +0 -121
- package/src/tui/ui/branch-panel.ts +0 -47
- package/src/tui/ui/canvas.ts +0 -96
- package/src/tui/ui/colors.ts +0 -70
- package/src/tui/ui/composer.ts +0 -199
- package/src/tui/ui/control-panel.ts +0 -25
- package/src/tui/ui/control-sidebar.ts +0 -49
- package/src/tui/ui/dialog.ts +0 -398
- package/src/tui/ui/draft.ts +0 -23
- package/src/tui/ui/duration.ts +0 -16
- package/src/tui/ui/edge.ts +0 -186
- package/src/tui/ui/image-size.ts +0 -44
- package/src/tui/ui/ink.ts +0 -210
- package/src/tui/ui/items.ts +0 -42
- package/src/tui/ui/keys.ts +0 -40
- package/src/tui/ui/layout.ts +0 -74
- package/src/tui/ui/list-model.ts +0 -86
- package/src/tui/ui/list-view.ts +0 -172
- package/src/tui/ui/mascot.ts +0 -67
- package/src/tui/ui/messages.ts +0 -518
- package/src/tui/ui/motion.ts +0 -28
- package/src/tui/ui/paths.ts +0 -10
- package/src/tui/ui/pictures.ts +0 -108
- package/src/tui/ui/prompt-history.ts +0 -60
- package/src/tui/ui/screen.ts +0 -579
- package/src/tui/ui/status.ts +0 -403
- package/src/tui/ui/styled.ts +0 -85
- package/src/tui/ui/tool-card.ts +0 -150
- package/src/tui/width.ts +0 -567
- package/src/vendor/pi-rich/LICENSE.upstream +0 -21
- package/src/vendor/pi-rich/UPSTREAM.json +0 -17
- package/src/vendor/pi-rich/clipboard-command.ts +0 -44
- package/src/vendor/pi-rich/exif-orientation.ts +0 -182
- package/src/vendor/pi-rich/html.ts +0 -51
- package/src/vendor/pi-rich/image-convert.ts +0 -49
- package/src/vendor/pi-rich/image-resize-core.ts +0 -164
- package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
- package/src/vendor/pi-rich/image-resize.ts +0 -123
- package/src/vendor/pi-rich/mime.ts +0 -116
- package/src/vendor/pi-rich/photon.ts +0 -139
- package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pickers that run before a session exists (startup session choice, project
|
|
3
|
-
* trust, `xeno config`). Each borrows the terminal for one dialog on its own
|
|
4
|
-
* renderer and gives it back; none of them creates a durable session.
|
|
5
|
-
* Same dialog component as the session UI, so keys and look match.
|
|
6
|
-
*/
|
|
7
|
-
import { BoxRenderable, createCliRenderer, type CliRenderer } from "@opentui/core";
|
|
8
|
-
import { defaultKeymap, type Keymap } from "../../core/keymap.ts";
|
|
9
|
-
import { nextResourceOverride } from "../../core/resource-selection.ts";
|
|
10
|
-
import { selectSessions } from "../../core/session-browser.ts";
|
|
11
|
-
import { builtinTheme, type Theme } from "../../core/theme.ts";
|
|
12
|
-
import { projectTrust } from "../../store/trust.ts";
|
|
13
|
-
import { discoverResources, setResourceOverride, type ResourceOptions } from "../../store/resource-index.ts";
|
|
14
|
-
import { loadUiResources } from "../../store/resources.ts";
|
|
15
|
-
import { registerSessionRoot, scanSessionRoots, sessionRoots } from "../../store/session-browser.ts";
|
|
16
|
-
import { sessionsDir, type ListedSession } from "../../store/sessions.ts";
|
|
17
|
-
import { fuzzyMatch } from "../../tui/fuzzy.ts";
|
|
18
|
-
import { DialogHost, type DialogSpec } from "../../tui/ui/dialog.ts";
|
|
19
|
-
import { keyPress } from "../../tui/ui/keys.ts";
|
|
20
|
-
import type { ListItem } from "../../tui/ui/list-model.ts";
|
|
21
|
-
import { Canvas } from "../../tui/ui/canvas.ts";
|
|
22
|
-
import { edgeLine } from "../../tui/ui/edge.ts";
|
|
23
|
-
import { applyTrustChoice, type TrustChoice } from "../project-trust.ts";
|
|
24
|
-
import { uiColors } from "./theme.ts";
|
|
25
|
-
import { trustItems } from "./app.ts";
|
|
26
|
-
|
|
27
|
-
interface Standalone { renderer: CliRenderer; dialogs: DialogHost; close(): void }
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* A one-dialog screen: a heading row on top and the same input box the
|
|
31
|
-
* session uses, pinned to the bottom, with the dialog's title and keys on its
|
|
32
|
-
* top edge (refactor-tui-dock-layout). Nothing is painted behind it.
|
|
33
|
-
*/
|
|
34
|
-
async function open(theme: Theme, keymap: Keymap, heading: string, handoff?: () => void, renderer?: CliRenderer): Promise<Standalone> {
|
|
35
|
-
handoff?.();
|
|
36
|
-
const r = renderer ?? await createCliRenderer({ screenMode: "alternate-screen", exitOnCtrlC: false, exitSignals: [], consoleMode: "disabled", openConsoleOnError: false,
|
|
37
|
-
enableMouseMovement: false, gatherStats: false, memorySnapshotInterval: 0, maxFps: 60 });
|
|
38
|
-
// The terminal's palette is not known yet on this short-lived renderer; the
|
|
39
|
-
// theme's hint picks safe slots. The background is still never painted.
|
|
40
|
-
const colors = uiColors(theme);
|
|
41
|
-
r.setBackgroundColor(colors.background);
|
|
42
|
-
const root = new BoxRenderable(r, { id: "standalone", flexDirection: "column", width: "100%", height: "100%" });
|
|
43
|
-
const top = new Canvas(r, { id: "standalone-heading", height: 1, flexShrink: 0, marginLeft: 1, marginRight: 1, colors });
|
|
44
|
-
top.setRows([[{ text: "xeno", role: "text", bold: true }, { text: ` ${heading}`, role: "muted" }]]);
|
|
45
|
-
const spacer = new BoxRenderable(r, { id: "standalone-space", flexGrow: 1 });
|
|
46
|
-
const box = new BoxRenderable(r, { id: "standalone-box", flexDirection: "column", flexShrink: 0, marginLeft: 1, marginRight: 1 });
|
|
47
|
-
const topEdge = new Canvas(r, { id: "standalone-top", height: 1, flexShrink: 0, colors });
|
|
48
|
-
const body = new BoxRenderable(r, { id: "standalone-body", flexDirection: "column", flexShrink: 0, border: ["left", "right"], borderStyle: "rounded", borderColor: colors.edge, paddingLeft: 1, paddingRight: 1 });
|
|
49
|
-
const slot = new BoxRenderable(r, { id: "dialog", flexDirection: "column", flexShrink: 0, visible: false });
|
|
50
|
-
const bottomEdge = new Canvas(r, { id: "standalone-bottom", height: 1, flexShrink: 0, colors });
|
|
51
|
-
body.add(slot); box.add(topEdge); box.add(body); box.add(bottomEdge);
|
|
52
|
-
root.add(top); root.add(spacer); root.add(box);
|
|
53
|
-
r.root.add(root);
|
|
54
|
-
const dialogs = new DialogHost(r, colors, () => keymap.byAction, slot, () => Math.max(1, r.height - 4));
|
|
55
|
-
const paint = () => {
|
|
56
|
-
const width = Math.max(2, r.width - 2);
|
|
57
|
-
topEdge.setRows([edgeLine({ width, corners: ["╭", "╮"], left: dialogs.title ? [{ segs: [{ text: dialogs.title, role: "text", bold: true }], priority: 0, min: 8 }] : [],
|
|
58
|
-
right: dialogs.edgeHint ? [{ segs: [{ text: dialogs.edgeHint, role: "muted" }], priority: 3, min: 10 }] : [] })]);
|
|
59
|
-
bottomEdge.setRows([edgeLine({ width, corners: ["╰", "╯"] })]);
|
|
60
|
-
};
|
|
61
|
-
dialogs.onChrome = paint;
|
|
62
|
-
r.on("resize", () => { dialogs.layout(); paint(); });
|
|
63
|
-
paint();
|
|
64
|
-
r.keyInput.on("keypress", event => { if (dialogs.key(keyPress(event))) event.preventDefault(); });
|
|
65
|
-
r.keyInput.on("paste", event => { if (dialogs.paste(new TextDecoder().decode(event.bytes)) === false) event.preventDefault(); });
|
|
66
|
-
return { renderer: r, dialogs, close: () => { dialogs.dispose(); if (!renderer) r.destroy(); } };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Show one dialog; resolves with the picked value, or null when it is closed without a choice. */
|
|
70
|
-
function ask(host: Standalone, spec: (resolve: (value: string | null) => void) => DialogSpec): Promise<string | null> {
|
|
71
|
-
return new Promise(resolve => {
|
|
72
|
-
let settled = false;
|
|
73
|
-
const done = (value: string | null) => { if (!settled) { settled = true; resolve(value); } };
|
|
74
|
-
// A pick empties the stack before its callback runs; let the callback answer first.
|
|
75
|
-
host.dialogs.onClosed = () => queueMicrotask(() => done(null));
|
|
76
|
-
host.dialogs.push(spec(done));
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function pickProjectTrust(workspace: string, handoff?: () => void, renderer?: CliRenderer): Promise<boolean> {
|
|
81
|
-
const decision = await projectTrust(workspace);
|
|
82
|
-
const ui = await loadUiResources({ workspace, trusted: false });
|
|
83
|
-
const host = await open(ui.theme, ui.keymap, "project trust", handoff, renderer);
|
|
84
|
-
try {
|
|
85
|
-
const choice = await ask(host, resolve => ({ kind: "list", title: "Trust this project?", search: false, items: trustItems(workspace),
|
|
86
|
-
intro: [workspace, "This project has its own .xeno configuration (settings, skills, extensions). Tool permissions are separate."],
|
|
87
|
-
...(decision.saved ? { selected: decision.saved.trusted ? decision.saved.path === workspace ? "here" : "parent" : "never" } : {}),
|
|
88
|
-
onPick: item => resolve(item.value) }));
|
|
89
|
-
return choice === null ? false : applyTrustChoice(workspace, choice as TrustChoice);
|
|
90
|
-
} finally { host.close(); }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
type Session = Extract<ListedSession, { ok: true }>;
|
|
94
|
-
|
|
95
|
-
export async function pickStartupSession(opts: { workspace: string; sessionDir?: string; say: (line: string) => void; keymap?: Keymap; theme?: Theme },
|
|
96
|
-
handoff?: () => void, renderer?: CliRenderer): Promise<string | null> {
|
|
97
|
-
const root = { path: sessionsDir(opts.workspace, opts.sessionDir), workspace: opts.workspace };
|
|
98
|
-
await registerSessionRoot(root).catch(error => opts.say(String(error)));
|
|
99
|
-
const host = await open(opts.theme ?? builtinTheme("dark"), opts.keymap ?? defaultKeymap(), "resume a session", handoff, renderer);
|
|
100
|
-
const scan = new AbortController();
|
|
101
|
-
let rows: Session[] = [];
|
|
102
|
-
let scope: "current" | "all" = "current";
|
|
103
|
-
const items = (query = ""): ListItem[] => selectSessions(rows, query, "threaded", false, (q, text) => { const score = fuzzyMatch(text, q); return { matches: score !== null, score: score ?? Infinity }; })
|
|
104
|
-
.map(row => ({ value: row.path, label: " ".repeat(Math.min(8, row.depth)) + (row.depth ? "↳ " : "") + (row.name?.trim() || row.text?.split("\n").find(Boolean)?.slice(0, 90) || row.id),
|
|
105
|
-
description: [row.updated_at?.slice(0, 16).replace("T", " ") ?? "", row.forked_at ? `forked ${row.forked_at.slice(0, 16).replace("T", " ")}` : "",
|
|
106
|
-
row.model ?? "", scope === "all" ? row.workspace ?? "" : ""].filter(Boolean).join(" · ") }));
|
|
107
|
-
try {
|
|
108
|
-
let spec: DialogSpec & { kind: "list" } | undefined;
|
|
109
|
-
const load = async () => {
|
|
110
|
-
rows = [];
|
|
111
|
-
for await (const batch of scanSessionRoots(scope === "current" ? [root] : await sessionRoots(root), scan.signal)) {
|
|
112
|
-
// A session left without a word is not something to resume (fix-skip-empty-sessions).
|
|
113
|
-
rows.push(...batch.sessions.filter((row): row is Session => row.ok && row.conversation !== false));
|
|
114
|
-
if (spec && host.dialogs.top === spec) host.dialogs.list?.setItems(items(host.dialogs.list.query));
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
const picked = ask(host, resolve => (spec = { kind: "list", title: "Resume · this folder", items: [], rows: 16, empty: "Loading sessions…", placeholder: "search sessions",
|
|
118
|
-
actions: [{ key: "tab", label: "all workspaces", run: async () => { scope = scope === "current" ? "all" : "current"; spec!.title = `Resume · ${scope === "current" ? "this folder" : "all workspaces"}`; await load(); } }],
|
|
119
|
-
onPick: item => resolve(item.value) }));
|
|
120
|
-
await load();
|
|
121
|
-
if (!rows.length && scope === "current" && (await sessionRoots(root)).length === 1) { opts.say("No sessions to resume"); host.dialogs.clear(); return null; }
|
|
122
|
-
return await picked;
|
|
123
|
-
} finally { scan.abort(); host.close(); }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export async function runResourceConfig(options: ResourceOptions, handoff?: () => void, renderer?: CliRenderer): Promise<void> {
|
|
127
|
-
let index = await discoverResources(options);
|
|
128
|
-
const ui = await loadUiResources(options, index);
|
|
129
|
-
const host = await open(ui.theme, ui.keymap, "resources", handoff, renderer);
|
|
130
|
-
let scope: "user" | "project" = options.local ? "project" : "user";
|
|
131
|
-
const items = (): ListItem[] => index.entries.map(entry => ({ value: `${entry.kind}:${entry.path}`, label: `${entry.enabled ? "◉" : "○"} ${entry.name}`, section: entry.kind,
|
|
132
|
-
description: `${entry.scope}${entry.trusted ? "" : " · untrusted"}${scope === "project" ? ` · ${entry.override}` : ""}`, keywords: entry.path }));
|
|
133
|
-
try {
|
|
134
|
-
let spec: DialogSpec & { kind: "list" } | undefined;
|
|
135
|
-
await ask(host, () => (spec = { kind: "list", title: `Resources · ${scope === "user" ? "global" : "project"}`, items: items(), rows: 16, empty: "No resources found",
|
|
136
|
-
hint: "space toggle · tab scope · esc close",
|
|
137
|
-
actions: [
|
|
138
|
-
{ key: "tab", label: "scope", run: () => { scope = scope === "user" ? "project" : "user"; spec!.title = `Resources · ${scope === "user" ? "global" : "project"}`; host.dialogs.list?.setItems(items()); } },
|
|
139
|
-
{ key: "space", label: "toggle", run: async item => {
|
|
140
|
-
const entry = index.entries.find(row => `${row.kind}:${row.path}` === item?.value);
|
|
141
|
-
if (!entry) return;
|
|
142
|
-
await setResourceOverride(index, entry, scope, scope === "project" ? nextResourceOverride(entry.override) : entry.inherited ? "skip" : "load");
|
|
143
|
-
index = await discoverResources(options);
|
|
144
|
-
host.dialogs.list?.setItems(items(), item?.value);
|
|
145
|
-
} },
|
|
146
|
-
] }));
|
|
147
|
-
} finally { host.close(); }
|
|
148
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
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
|
-
}
|
package/src/apps/ui/surface.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
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
|
-
observeEvent?(event: LoopEvent): void;
|
|
23
|
-
/** A plain-text notice in the message stream (the interactive app); line mode prints instead. */
|
|
24
|
-
notice?(text: string, tone?: "info" | "success" | "warning" | "error"): void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface Ui extends TurnSurface {
|
|
28
|
-
readonly extensionUi: ExtensionUI;
|
|
29
|
-
readonly signal: AbortSignal | undefined;
|
|
30
|
-
readonly queueLength: number;
|
|
31
|
-
sessionName: string | null;
|
|
32
|
-
oauthEffects: OAuthEffects;
|
|
33
|
-
clipboardWriter: (text: string) => boolean | Promise<boolean>;
|
|
34
|
-
clipboardReader: () => Promise<ClipboardContent>;
|
|
35
|
-
|
|
36
|
-
/** Notices and documents in the message stream. */
|
|
37
|
-
notice(text: string, tone?: "info" | "success" | "warning" | "error"): void;
|
|
38
|
-
showDocument(title: string, text: string): void;
|
|
39
|
-
checkForUpdate(): void;
|
|
40
|
-
toast(text: string, tone?: "info" | "success" | "warning" | "error"): void;
|
|
41
|
-
redraw(): void;
|
|
42
|
-
|
|
43
|
-
/** A masked single-line form; resolves undefined when cancelled. The value never reaches a renderable. */
|
|
44
|
-
askSecret(title: string, label: string, hint: string): Promise<string | undefined>;
|
|
45
|
-
confirm(title: string, message: string): Promise<boolean>;
|
|
46
|
-
/** A cancellable "waiting" dialog (OAuth browser / device code). Returns a closer. */
|
|
47
|
-
waiting(title: string, lines: readonly string[], onCancel: () => void, pasteLabel?: string, onPaste?: (value: string) => void): () => void;
|
|
48
|
-
|
|
49
|
-
openLogin(method?: "oauth" | "api_key"): void;
|
|
50
|
-
openLogout(): void;
|
|
51
|
-
openModel(): void;
|
|
52
|
-
openScopedModels(): void;
|
|
53
|
-
openThinking(): void;
|
|
54
|
-
openSettings(field?: string): void;
|
|
55
|
-
openResume(): Promise<void>;
|
|
56
|
-
openFork(): Promise<void>;
|
|
57
|
-
openTree(): Promise<void>;
|
|
58
|
-
openResources(): Promise<void>;
|
|
59
|
-
openControl(tab?: "goal" | "progress" | "context"): Promise<void>;
|
|
60
|
-
/** S.5: draft criteria for an objective, ask the user which to accept, then work toward them. */
|
|
61
|
-
startGoal(objective: string): void;
|
|
62
|
-
openTrust(): Promise<void>;
|
|
63
|
-
openAttachments(): void;
|
|
64
|
-
|
|
65
|
-
restoreDraft(text: string, images?: readonly ImageRef[]): void;
|
|
66
|
-
refreshResources(): Promise<void>;
|
|
67
|
-
applyConfiguration(): void;
|
|
68
|
-
followTheme(): void;
|
|
69
|
-
beginBash(command: string): void;
|
|
70
|
-
pushBash(chunk: Uint8Array): void;
|
|
71
|
-
ask(request: PermissionRequest): Promise<ApprovalChoice>;
|
|
72
|
-
|
|
73
|
-
runExtensionAction(action: () => void | Promise<void>): void;
|
|
74
|
-
runExtensionCommand(name: string, args: string): void;
|
|
75
|
-
runSessionTransition(action: () => Promise<void>): void;
|
|
76
|
-
deliverExtension(text: string, kind: QueueKind): void;
|
|
77
|
-
bindExtensionRegistry(): void;
|
|
78
|
-
requestShutdown(): void;
|
|
79
|
-
/** Abort the running turn (Esc semantics) without quitting. */
|
|
80
|
-
interrupt(): void;
|
|
81
|
-
/** Plain-text snapshot of the current frame (/debug). */
|
|
82
|
-
snapshot(): { width: number; height: number; lines: string[] };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Injectable OAuth side effects (browser, callback server, device flow, fetch). */
|
|
86
|
-
export interface OAuthEffects {
|
|
87
|
-
fetch: typeof globalThis.fetch;
|
|
88
|
-
openBrowser(url: string): void;
|
|
89
|
-
callback(options: { expectedState: string; host: string; port: number; path: string }): Promise<CallbackServer>;
|
|
90
|
-
device(interaction: OAuthInteraction): Promise<OAuthCredential>;
|
|
91
|
-
}
|
package/src/apps/ui/theme.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
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
|
-
}
|
package/src/apps/update-check.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/** Shared lifecycle for automatic hints and explicit checks; policy remains in core (D16). */
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
import { installArgv, installKind, installResult, manualUpdateResult, updateDisabled, updatePlan, type UpdateInput } from "../core/update-check.ts";
|
|
4
|
-
import { packageLocation, readUpdateCache, writeUpdateCache } from "../store/update-cache.ts";
|
|
5
|
-
import { fetchLatestVersion, UPDATE_TIMEOUT_MS } from "../runtime/update-check.ts";
|
|
6
|
-
import { INSTALL_TIMEOUT_MS, runInstall } from "../runtime/self-update.ts";
|
|
7
|
-
import { XENO_VERSION } from "../runtime/version.ts";
|
|
8
|
-
|
|
9
|
-
export interface UpdateOptions {
|
|
10
|
-
readonly offline?: boolean;
|
|
11
|
-
readonly enabled?: boolean | undefined;
|
|
12
|
-
/** Install a newer release after the check: `/update` does, `xeno update --check` never does. */
|
|
13
|
-
readonly install?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type ManualUpdateActivity =
|
|
17
|
-
| { readonly state: "checking"; readonly current: string; readonly timeoutMs: number }
|
|
18
|
-
| { readonly state: "installing"; readonly version: string; readonly timeoutMs: number; readonly startedAt: number; readonly detail?: string }
|
|
19
|
-
| { readonly state: "idle" };
|
|
20
|
-
|
|
21
|
-
type ManualUpdateReporter = (text: string, tone?: "info" | "error") => void;
|
|
22
|
-
|
|
23
|
-
export class UpdateChecks {
|
|
24
|
-
private readonly controller = new AbortController();
|
|
25
|
-
private pending: Promise<string | null> | undefined;
|
|
26
|
-
private manualTask: Promise<number> | undefined;
|
|
27
|
-
/** The release this process already installed; the running code stays old until a restart. */
|
|
28
|
-
private installed: string | undefined;
|
|
29
|
-
|
|
30
|
-
async prepare(options: UpdateOptions, manual = false) {
|
|
31
|
-
const location = packageLocation();
|
|
32
|
-
const input: UpdateInput = { current: XENO_VERSION, now: Date.now(), cache: await readUpdateCache(), env: process.env,
|
|
33
|
-
offline: options.offline === true, enabled: options.enabled, install: installKind(location.root, location.git), manual };
|
|
34
|
-
return { input, plan: updatePlan(input), disabled: updateDisabled(input) };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** One request and one cache write even when a manual check joins startup's request. */
|
|
38
|
-
refresh(input: UpdateInput): Promise<string | null> {
|
|
39
|
-
if (this.pending) return this.pending;
|
|
40
|
-
const pending = (async () => {
|
|
41
|
-
const latest = await fetchLatestVersion(input.env["npm_config_registry"], { signal: this.controller.signal });
|
|
42
|
-
if (!this.controller.signal.aborted) {
|
|
43
|
-
const known = latest ?? input.cache?.latest;
|
|
44
|
-
await writeUpdateCache({ checkedAt: Date.now(), ...(known ? { latest: known } : {}) });
|
|
45
|
-
}
|
|
46
|
-
return latest;
|
|
47
|
-
})().finally(() => { if (this.pending === pending) this.pending = undefined; });
|
|
48
|
-
this.pending = pending;
|
|
49
|
-
return pending;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
manual(options: UpdateOptions, say: ManualUpdateReporter, onActivity?: (activity: ManualUpdateActivity) => void): Promise<number> {
|
|
53
|
-
if (this.manualTask) return this.manualTask;
|
|
54
|
-
const task = this.runManual(options, say, onActivity).finally(() => { if (this.manualTask === task) this.manualTask = undefined; });
|
|
55
|
-
this.manualTask = task;
|
|
56
|
-
return task;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private async runManual(options: UpdateOptions, say: ManualUpdateReporter, onActivity?: (activity: ManualUpdateActivity) => void): Promise<number> {
|
|
60
|
-
if (options.install && this.installed) {
|
|
61
|
-
say(`xeno ${this.installed} is already installed. Restart xeno to use it.`);
|
|
62
|
-
return 0;
|
|
63
|
-
}
|
|
64
|
-
const { input, plan, disabled } = await this.prepare(options, true);
|
|
65
|
-
if (this.controller.signal.aborted) return 130;
|
|
66
|
-
if (!plan.check) {
|
|
67
|
-
say(input.install === "checkout" ? `Source checkout: xeno ${input.current}. Use git pull to update this checkout.`
|
|
68
|
-
: `Update check skipped: ${disabled ?? "check unavailable"} (xeno ${input.current}).`);
|
|
69
|
-
return input.install === "checkout" ? 0 : 1;
|
|
70
|
-
}
|
|
71
|
-
if (onActivity) onActivity({ state: "checking", current: input.current, timeoutMs: UPDATE_TIMEOUT_MS });
|
|
72
|
-
else say(`Checking for xeno updates (current ${input.current})...`);
|
|
73
|
-
try {
|
|
74
|
-
const latest = await this.refresh(input);
|
|
75
|
-
if (this.controller.signal.aborted) return 130;
|
|
76
|
-
const result = manualUpdateResult(latest, input.current, input.install);
|
|
77
|
-
const argv = options.install && result.status === "available" && latest ? installArgv(input.install, latest) : undefined;
|
|
78
|
-
if (!argv || !latest) {
|
|
79
|
-
say(result.text, result.status === "failed" ? "error" : "info");
|
|
80
|
-
return result.status === "failed" ? 1 : 0;
|
|
81
|
-
}
|
|
82
|
-
return await this.install(argv, latest, input.current, say, onActivity);
|
|
83
|
-
} finally {
|
|
84
|
-
if (onActivity) onActivity({ state: "idle" });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private async install(argv: readonly string[], latest: string, current: string, say: ManualUpdateReporter, onActivity?: (activity: ManualUpdateActivity) => void): Promise<number> {
|
|
89
|
-
const startedAt = Date.now();
|
|
90
|
-
if (onActivity) onActivity({ state: "installing", version: latest, timeoutMs: INSTALL_TIMEOUT_MS, startedAt });
|
|
91
|
-
else say(`Installing xeno ${latest} with ${argv.join(" ")}...`);
|
|
92
|
-
const outcome = await runInstall(argv, {
|
|
93
|
-
signal: this.controller.signal, timeoutMs: INSTALL_TIMEOUT_MS, writableDir: dirname(packageLocation().root),
|
|
94
|
-
...(onActivity ? { onLine: (detail: string) => onActivity({ state: "installing", version: latest, timeoutMs: INSTALL_TIMEOUT_MS, startedAt, detail }) } : {}),
|
|
95
|
-
});
|
|
96
|
-
if (outcome.kind === "cancelled") return 130;
|
|
97
|
-
const result = installResult(outcome, argv, current, latest);
|
|
98
|
-
if (result.ok) this.installed = latest;
|
|
99
|
-
say(result.text, result.ok ? "info" : "error");
|
|
100
|
-
return result.ok ? 0 : 1;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
cancel(): void { this.controller.abort(); }
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** A standalone command has no UI lifecycle to own cancellation. */
|
|
107
|
-
export async function runManualUpdate(options: UpdateOptions, say: (text: string) => void): Promise<number> {
|
|
108
|
-
const checks = new UpdateChecks(), cancel = () => checks.cancel();
|
|
109
|
-
process.once("SIGINT", cancel); process.once("SIGTERM", cancel);
|
|
110
|
-
try { return await checks.manual(options, say); }
|
|
111
|
-
finally { checks.cancel(); process.off("SIGINT", cancel); process.off("SIGTERM", cancel); }
|
|
112
|
-
}
|
package/src/apps/workflow.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/** D5/D6: the app changes capabilities only through the live session's checkpoint owner. */
|
|
2
|
-
import { isWorkflowMode, workflowInstructions, type WorkflowMode } from "../core/workflow-mode.ts";
|
|
3
|
-
import { sessionControl } from "../runtime/session-control.ts";
|
|
4
|
-
import { sessionToolCatalog, type App } from "./startup.ts";
|
|
5
|
-
|
|
6
|
-
export type WorkflowApp = Pick<App, "args" | "session" | "extensions" | "system">;
|
|
7
|
-
|
|
8
|
-
export async function setAppWorkflow(app: WorkflowApp, mode: WorkflowMode): Promise<boolean> {
|
|
9
|
-
if (!isWorkflowMode(mode)) throw Error("workflow mode must be build or plan");
|
|
10
|
-
const session = app.session, previousMode = session.config.tools.workflowMode;
|
|
11
|
-
const current = previousMode ?? app.args.workflow ?? "build";
|
|
12
|
-
return sessionControl(session).withIdleChange(async () => {
|
|
13
|
-
const previousProfile = session.config.agentProfile, name = previousProfile?.name ?? app.args.agent;
|
|
14
|
-
const nextProfile = name ? { name, workflowExplicit: true } : undefined;
|
|
15
|
-
const args = structuredClone(app.args), previousScope = session.config.workflowToolProfile;
|
|
16
|
-
const promote = mode === "plan" && args.tools === undefined && !args.noTools && args.toolProfile === undefined;
|
|
17
|
-
if (promote) args.toolProfile = "foundation";
|
|
18
|
-
const nextScope = args.toolProfile === "foundation" ? "foundation" as const : undefined;
|
|
19
|
-
const metadataChanged = JSON.stringify(previousProfile) !== JSON.stringify(nextProfile) || previousScope !== nextScope;
|
|
20
|
-
if (mode === current && !metadataChanged && !promote) {
|
|
21
|
-
app.args.workflow = mode; app.args.cliFlags = [...new Set([...app.args.cliFlags ?? [], "--workflow"])];
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
const oldInstruction = workflowInstructions(current), nextInstruction = workflowInstructions(mode);
|
|
25
|
-
// Replace only a complete harness paragraph: user context and all other frozen bytes survive.
|
|
26
|
-
const paragraphs = session.config.frozen.system.split("\n\n");
|
|
27
|
-
const index = paragraphs.indexOf(oldInstruction);
|
|
28
|
-
if (index >= 0) paragraphs[index] = nextInstruction; else paragraphs.push(nextInstruction);
|
|
29
|
-
const system = mode === current ? session.config.frozen.system : paragraphs.join("\n\n");
|
|
30
|
-
const catalog = sessionToolCatalog({ ...args, workflow: mode }, app.extensions, session.config.frozen.tools);
|
|
31
|
-
// The persisted checkpoint owns the admission mode as well as declarations, including cold restore.
|
|
32
|
-
session.config.tools.workflowMode = mode;
|
|
33
|
-
if (nextProfile) session.config.agentProfile = nextProfile;
|
|
34
|
-
if (nextScope) session.config.workflowToolProfile = nextScope; else delete session.config.workflowToolProfile;
|
|
35
|
-
try {
|
|
36
|
-
const changed = await session.reloadResources(mode === current && !promote ? session.config.frozen : { ...session.config.frozen, system, tools: catalog.schemas }, undefined, undefined, metadataChanged);
|
|
37
|
-
if (mode !== current || promote) session.config.tools.deferredTools = catalog.deferred;
|
|
38
|
-
args.workflow = mode; args.cliFlags = [...new Set([...args.cliFlags ?? [], "--workflow"])]; app.args = args; app.system = system;
|
|
39
|
-
return changed;
|
|
40
|
-
} catch (error) {
|
|
41
|
-
if (previousMode === undefined) delete session.config.tools.workflowMode;
|
|
42
|
-
else session.config.tools.workflowMode = previousMode;
|
|
43
|
-
if (previousProfile === undefined) delete session.config.agentProfile;
|
|
44
|
-
else session.config.agentProfile = previousProfile;
|
|
45
|
-
if (previousScope === undefined) delete session.config.workflowToolProfile;
|
|
46
|
-
else session.config.workflowToolProfile = previousScope;
|
|
47
|
-
throw error;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function bindAppWorkflow(app: WorkflowApp): void {
|
|
53
|
-
const session = app.session;
|
|
54
|
-
sessionControl(session).configureWorkflow = async mode => {
|
|
55
|
-
if (app.session !== session) throw Error("this session is no longer attached to the app");
|
|
56
|
-
await setAppWorkflow(app, mode);
|
|
57
|
-
};
|
|
58
|
-
}
|