@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
package/src/core/theme.ts
CHANGED
|
@@ -37,7 +37,7 @@ export const THEME_TOKENS = [
|
|
|
37
37
|
export type ThemeToken = (typeof THEME_TOKENS)[number];
|
|
38
38
|
|
|
39
39
|
export type ThemeColors = { readonly [K in ThemeToken]: string } & Readonly<Record<string, string>>;
|
|
40
|
-
const PI_THEME_TOKENS: readonly string[] = ["accent","border","borderAccent","borderMuted","success","error","warning","muted","dim","text","thinkingText","scrollbarTrack","scrollbarThumb","searchMatchText","userMessageText","customMessageText","customMessageLabel","toolTitle","toolOutput","mdHeading","mdLink","mdLinkUrl","mdCode","mdCodeBlock","mdCodeBlockBorder","mdQuote","mdQuoteBorder","mdHr","mdListBullet","toolDiffAdded","toolDiffRemoved","toolDiffContext","syntaxComment","syntaxKeyword","syntaxFunction","syntaxVariable","syntaxString","syntaxNumber","syntaxType","syntaxOperator","syntaxPunctuation","thinkingOff","thinkingMinimal","thinkingLow","thinkingMedium","thinkingHigh","thinkingXhigh","thinkingMax","bashMode","selectedBg","searchMatchBg","userMessageBg","customMessageBg","toolPendingBg","toolSuccessBg","toolErrorBg"];
|
|
40
|
+
const PI_THEME_TOKENS: readonly string[] = ["background","accent","border","borderAccent","borderMuted","success","error","warning","muted","dim","text","thinkingText","scrollbarTrack","scrollbarThumb","searchMatchText","userMessageText","customMessageText","customMessageLabel","toolTitle","toolOutput","mdHeading","mdLink","mdLinkUrl","mdCode","mdCodeBlock","mdCodeBlockBorder","mdQuote","mdQuoteBorder","mdHr","mdListBullet","toolDiffAdded","toolDiffRemoved","toolDiffContext","syntaxComment","syntaxKeyword","syntaxFunction","syntaxVariable","syntaxString","syntaxNumber","syntaxType","syntaxOperator","syntaxPunctuation","thinkingOff","thinkingMinimal","thinkingLow","thinkingMedium","thinkingHigh","thinkingXhigh","thinkingMax","bashMode","selectedBg","searchMatchBg","userMessageBg","customMessageBg","toolPendingBg","toolSuccessBg","toolErrorBg"];
|
|
41
41
|
|
|
42
42
|
export interface Theme {
|
|
43
43
|
readonly name: string;
|
|
@@ -50,64 +50,33 @@ export interface ThemeParse {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
* and
|
|
55
|
-
*
|
|
53
|
+
* Built-in appearances own no colours of their own: every token is one of the
|
|
54
|
+
* terminal's 16 slots or its default (empty), and no token paints a
|
|
55
|
+
* background (docs/tasks/refactor-tui-terminal-colors.md). The interactive
|
|
56
|
+
* view derives finer greys from the terminal's reported palette; these
|
|
57
|
+
* tokens are what line mode and an unreporting terminal get.
|
|
58
|
+
*
|
|
59
|
+
* `dark` and `light` differ only in which half of the palette stays legible:
|
|
60
|
+
* bright slots on a dark ground, normal slots on a light one. Muted text is a
|
|
61
|
+
* grey slot, never SGR faint (faint made muted arguments unreadable).
|
|
56
62
|
*/
|
|
57
|
-
const DARK_COLORS: ThemeColors = {
|
|
58
|
-
|
|
59
|
-
"thinkingText": "38;2;128;128;128",
|
|
60
|
-
"toolTitle": "38;2;212;212;212",
|
|
61
|
-
"toolOutput": "38;2;128;128;128",
|
|
62
|
-
"error": "38;2;204;102;102",
|
|
63
|
-
"toolDiffAdded": "38;2;181;189;104",
|
|
64
|
-
"toolDiffRemoved": "38;2;204;102;102",
|
|
65
|
-
"status": "38;2;128;128;128",
|
|
66
|
-
"prompt": "38;2;212;212;212",
|
|
67
|
-
"selectedBg": "48;2;58;58;74",
|
|
68
|
-
"muted": "38;2;128;128;128",
|
|
69
|
-
"warning": "38;2;255;255;0",
|
|
70
|
-
"mdHeading": "38;2;240;198;116",
|
|
71
|
-
"mdCode": "38;2;138;190;183",
|
|
72
|
-
"mdLink": "38;2;129;162;190",
|
|
73
|
-
"userMessageBg": "48;2;52;53;65",
|
|
74
|
-
"toolPendingBg": "48;2;40;40;50",
|
|
75
|
-
"toolSuccessBg": "48;2;40;50;40",
|
|
76
|
-
"toolErrorBg": "48;2;60;40;40",
|
|
77
|
-
"thinkingLow": "38;2;95;135;175",
|
|
78
|
-
"thinkingMedium": "38;2;129;162;190",
|
|
79
|
-
"thinkingHigh": "38;2;178;148;187",
|
|
80
|
-
"thinkingXhigh": "38;2;209;131;232",
|
|
81
|
-
"thinkingMax": "38;2;255;95;255"
|
|
82
|
-
};
|
|
63
|
+
const DARK_COLORS: ThemeColors = terminalTokens({ muted: "37", red: "91", green: "92", yellow: "93", blue: "94", magenta: "95", cyan: "96", rule: "90" });
|
|
64
|
+
const LIGHT_COLORS: ThemeColors = terminalTokens({ muted: "90", red: "31", green: "32", yellow: "33", blue: "34", magenta: "35", cyan: "36", rule: "37" });
|
|
83
65
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"mdHeading": "38;2;154;115;38",
|
|
99
|
-
"mdCode": "38;2;90;128;128",
|
|
100
|
-
"mdLink": "38;2;84;125;167",
|
|
101
|
-
"userMessageBg": "48;2;232;232;232",
|
|
102
|
-
"toolPendingBg": "48;2;232;232;240",
|
|
103
|
-
"toolSuccessBg": "48;2;232;240;232",
|
|
104
|
-
"toolErrorBg": "48;2;240;232;232",
|
|
105
|
-
"thinkingLow": "38;2;84;125;167",
|
|
106
|
-
"thinkingMedium": "38;2;90;128;128",
|
|
107
|
-
"thinkingHigh": "38;2;135;95;135",
|
|
108
|
-
"thinkingXhigh": "38;2;139;0;139",
|
|
109
|
-
"thinkingMax": "38;2;175;0;95"
|
|
110
|
-
};
|
|
66
|
+
function terminalTokens(slot: { muted: string; red: string; green: string; yellow: string; blue: string; magenta: string; cyan: string; rule: string }): ThemeColors {
|
|
67
|
+
return {
|
|
68
|
+
text: "", prompt: "", background: "",
|
|
69
|
+
thinkingText: slot.magenta, toolTitle: "", toolOutput: slot.muted,
|
|
70
|
+
error: slot.red, toolDiffAdded: slot.green, toolDiffRemoved: slot.red,
|
|
71
|
+
status: slot.muted, muted: slot.muted, dim: slot.muted, warning: slot.yellow, success: slot.green, accent: slot.blue,
|
|
72
|
+
mdHeading: slot.blue, mdCode: slot.cyan, mdLink: slot.blue,
|
|
73
|
+
selectedBg: "", userMessageBg: "", toolPendingBg: "", toolSuccessBg: "", toolErrorBg: "",
|
|
74
|
+
border: slot.rule, borderMuted: slot.rule,
|
|
75
|
+
thinkingOff: slot.muted, thinkingMinimal: slot.magenta, thinkingLow: slot.magenta, thinkingMedium: slot.magenta,
|
|
76
|
+
thinkingHigh: slot.magenta, thinkingXhigh: slot.magenta, thinkingMax: slot.magenta,
|
|
77
|
+
syntaxKeyword: slot.magenta, syntaxString: slot.green, syntaxNumber: slot.yellow, syntaxFunction: slot.blue, syntaxComment: slot.muted,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
111
80
|
|
|
112
81
|
export function builtinTheme(name: "dark" | "light"): Theme {
|
|
113
82
|
return name === "light"
|
|
@@ -115,6 +84,15 @@ export function builtinTheme(name: "dark" | "light"): Theme {
|
|
|
115
84
|
: { name: "dark", colors: DARK_COLORS };
|
|
116
85
|
}
|
|
117
86
|
|
|
87
|
+
/**
|
|
88
|
+
* A built-in appearance (dark, light, or system/auto, which reuse them) as
|
|
89
|
+
* opposed to a theme file. Identity, not name: a file may call itself "dark",
|
|
90
|
+
* and parseTheme always copies colours into a new object.
|
|
91
|
+
*/
|
|
92
|
+
export function isBuiltinTheme(theme: Theme): boolean {
|
|
93
|
+
return theme.colors === DARK_COLORS || theme.colors === LIGHT_COLORS;
|
|
94
|
+
}
|
|
95
|
+
|
|
118
96
|
export function parseTheme(json: unknown): ThemeParse {
|
|
119
97
|
const fallback = builtinTheme("dark");
|
|
120
98
|
if (typeof json !== "object" || json === null || Array.isArray(json)) {
|
|
@@ -153,7 +131,7 @@ export function parseTheme(json: unknown): ThemeParse {
|
|
|
153
131
|
diagnostics.push(`invalid colour for ${token}; using built-in`);
|
|
154
132
|
continue;
|
|
155
133
|
}
|
|
156
|
-
colors[token] = token.endsWith("Bg") ? parsed.replace(/^38;/, "48;") : parsed;
|
|
134
|
+
colors[token] = (token.endsWith("Bg") || token === "background") ? parsed.replace(/^38;/, "48;") : parsed;
|
|
157
135
|
}
|
|
158
136
|
|
|
159
137
|
if (!("status" in src) && "dim" in colors) colors.status = colors["dim"]!;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/** O.3: one declarative input contract serves model schemas and runtime admission. */
|
|
2
2
|
export type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue };
|
|
3
3
|
export type ToolDiagnostic = {
|
|
4
|
-
code: "invalid_input" | "version_mismatch" | "not_found" | "ambiguous_edit" | "permission_denied" | "execution_failed"
|
|
4
|
+
code: "invalid_input" | "version_mismatch" | "not_found" | "ambiguous_edit" | "permission_denied" | "execution_failed" |
|
|
5
|
+
"environment_missing" | "transient" | "cancelled" | "timeout" | "unknown_effect" | "command_failed";
|
|
5
6
|
message: string;
|
|
6
7
|
recover?: { tool: string; input: { readonly [key: string]: JsonValue } };
|
|
8
|
+
/** R3: optional only for legacy records; supplied together by current failure owners. */
|
|
9
|
+
execution?: "not_started" | "started" | "unknown";
|
|
10
|
+
effects?: "none" | "possible";
|
|
11
|
+
retry?: "never" | "after_change" | "after_inspection" | "backoff";
|
|
12
|
+
next?: string;
|
|
13
|
+
/** A schema-valid shape with placeholders, never an automatically executable recovery. */
|
|
14
|
+
example?: { readonly [key: string]: JsonValue };
|
|
7
15
|
};
|
|
8
16
|
|
|
9
17
|
export function isJsonValue(value: unknown): value is JsonValue {
|
|
@@ -15,7 +23,17 @@ export function isJsonValue(value: unknown): value is JsonValue {
|
|
|
15
23
|
export function isToolDiagnostic(value: unknown): value is ToolDiagnostic {
|
|
16
24
|
if (value === null || typeof value !== "object") return false;
|
|
17
25
|
const item = value as Record<string, unknown>;
|
|
18
|
-
if (!["invalid_input", "version_mismatch", "not_found", "ambiguous_edit", "permission_denied", "execution_failed"
|
|
26
|
+
if (!["invalid_input", "version_mismatch", "not_found", "ambiguous_edit", "permission_denied", "execution_failed",
|
|
27
|
+
"environment_missing", "transient", "cancelled", "timeout", "unknown_effect", "command_failed"].includes(String(item["code"])) || typeof item["message"] !== "string") return false;
|
|
28
|
+
if (["execution", "effects", "retry", "next"].some(key => item[key] !== undefined)) {
|
|
29
|
+
if (!["not_started", "started", "unknown"].includes(String(item["execution"])) ||
|
|
30
|
+
!["none", "possible"].includes(String(item["effects"])) ||
|
|
31
|
+
!["never", "after_change", "after_inspection", "backoff"].includes(String(item["retry"])) || typeof item["next"] !== "string") return false;
|
|
32
|
+
if (item["retry"] === "backoff" && (item["code"] !== "transient" || item["effects"] !== "none")) return false;
|
|
33
|
+
if (item["code"] === "permission_denied" && (item["retry"] !== "never" || item["recover"] !== undefined)) return false;
|
|
34
|
+
if (item["code"] === "unknown_effect" && item["retry"] !== "after_inspection") return false;
|
|
35
|
+
}
|
|
36
|
+
if (item["example"] !== undefined && (item["example"] === null || typeof item["example"] !== "object" || Array.isArray(item["example"]) || !isJsonValue(item["example"]))) return false;
|
|
19
37
|
const recover = item["recover"];
|
|
20
38
|
if (recover === undefined) return true;
|
|
21
39
|
if (recover === null || typeof recover !== "object") return false;
|
|
@@ -24,6 +42,37 @@ export function isToolDiagnostic(value: unknown): value is ToolDiagnostic {
|
|
|
24
42
|
typeof recovery["input"] === "object" && !Array.isArray(recovery["input"]) && isJsonValue(recovery["input"]);
|
|
25
43
|
}
|
|
26
44
|
|
|
45
|
+
/** Required fields only; examples express a valid shape, not permission or a safe command. */
|
|
46
|
+
export function minimalInput(schema: unknown): JsonValue | undefined {
|
|
47
|
+
if (schema === null || typeof schema !== "object") return undefined;
|
|
48
|
+
const s = schema as Record<string, unknown>;
|
|
49
|
+
if (Array.isArray(s["enum"]) && s["enum"].length && isJsonValue(s["enum"][0])) return s["enum"][0];
|
|
50
|
+
switch (s["type"]) {
|
|
51
|
+
case "string": return "<value>".padEnd(typeof s["minLength"] === "number" ? Math.min(4096, s["minLength"]) : 0, "x");
|
|
52
|
+
case "number": case "integer": return typeof s["minimum"] === "number" ? s["minimum"] : 0;
|
|
53
|
+
case "boolean": return false;
|
|
54
|
+
case "array": return [];
|
|
55
|
+
case "object": {
|
|
56
|
+
const value: { [key: string]: JsonValue } = {};
|
|
57
|
+
const properties = s["properties"];
|
|
58
|
+
for (const key of Array.isArray(s["required"]) ? s["required"] : []) {
|
|
59
|
+
if (typeof key !== "string" || properties === null || typeof properties !== "object") return undefined;
|
|
60
|
+
const item = minimalInput((properties as Record<string, unknown>)[key]);
|
|
61
|
+
if (item === undefined) return undefined;
|
|
62
|
+
value[key] = item;
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
default: return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Diagnostics are projected as tool data; they never become trusted system instructions. */
|
|
71
|
+
export function diagnosticContent(content: string, diagnostic?: ToolDiagnostic): string {
|
|
72
|
+
if (diagnostic?.execution === undefined) return content;
|
|
73
|
+
return content + "\nTool diagnostic: " + JSON.stringify(diagnostic);
|
|
74
|
+
}
|
|
75
|
+
|
|
27
76
|
import { editDistance } from "./text-normalize.ts";
|
|
28
77
|
|
|
29
78
|
/**
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** R11 / D6 / D8: local discovery proposes declarations; only a paired checkpoint installs them. */
|
|
2
|
+
import type { LogEvent, ToolInvocationEvent } from "./events.ts";
|
|
3
|
+
|
|
4
|
+
/** Assumed response bounds, not measured model optima; prevent a directory dump from replacing a lookup. */
|
|
5
|
+
export const MAX_DISCOVERY_RESULTS = 8;
|
|
6
|
+
export const MAX_DISCOVERY_QUERY = 256;
|
|
7
|
+
const MAX_PARAMETER_HINTS = 8;
|
|
8
|
+
const MAX_PURPOSE_CHARS = 320;
|
|
9
|
+
|
|
10
|
+
export interface DiscoverableTool {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly description: string;
|
|
13
|
+
readonly input_schema: { readonly properties: Readonly<Record<string, unknown>>; readonly required: readonly string[] };
|
|
14
|
+
}
|
|
15
|
+
export interface DeclarationRequest { readonly name: string; readonly fingerprint: string }
|
|
16
|
+
export interface DiscoveryMatch {
|
|
17
|
+
name: string; purpose: string; inputs: { name: string; type: string; required: boolean }[]; omitted_inputs: number;
|
|
18
|
+
}
|
|
19
|
+
export function declaredToolNames(tools: readonly unknown[]): string[] {
|
|
20
|
+
return tools.flatMap(tool => tool !== null && typeof tool === "object" && "name" in tool && typeof tool.name === "string" ? [tool.name] : []);
|
|
21
|
+
}
|
|
22
|
+
const compare = (a: string, b: string) => a < b ? -1 : a > b ? 1 : 0;
|
|
23
|
+
function record(raw: unknown): Record<string, unknown> | undefined {
|
|
24
|
+
return raw !== null && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : undefined;
|
|
25
|
+
}
|
|
26
|
+
function parameterType(raw: unknown): string {
|
|
27
|
+
if (raw === null || typeof raw !== "object") return "schema";
|
|
28
|
+
const type = (raw as Record<string, unknown>)["type"];
|
|
29
|
+
if (typeof type === "string") return type.slice(0, 64);
|
|
30
|
+
if (Array.isArray(type) && type.every(item => typeof item === "string")) return type.join("|").slice(0, 64);
|
|
31
|
+
return "schema";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Pure, deterministic matching over the caller's already-selected directory; never perform discovery I/O. */
|
|
35
|
+
export function discoverTools(catalog: readonly DiscoverableTool[], query: string, limit = 5): DiscoveryMatch[] {
|
|
36
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_DISCOVERY_RESULTS) throw new Error("invalid discovery limit");
|
|
37
|
+
const normalized = query.trim().toLowerCase();
|
|
38
|
+
if (!normalized || query.length > MAX_DISCOVERY_QUERY) throw new Error("query must contain 1 to 256 characters");
|
|
39
|
+
const terms = normalized.split(/[^\p{L}\p{N}_-]+/u).filter(Boolean);
|
|
40
|
+
if (terms.length === 0) return [];
|
|
41
|
+
return catalog.map(tool => {
|
|
42
|
+
const name = tool.name.toLowerCase(), text = `${name} ${tool.description.toLowerCase()}`;
|
|
43
|
+
const score = name === normalized ? 100 : terms.every(term => text.includes(term))
|
|
44
|
+
? 1 + terms.filter(term => name.includes(term)).length : 0;
|
|
45
|
+
return { tool, score };
|
|
46
|
+
}).filter(item => item.score > 0).sort((a, b) => b.score - a.score || compare(a.tool.name, b.tool.name))
|
|
47
|
+
.slice(0, limit).map(({ tool }) => {
|
|
48
|
+
const required = new Set(tool.input_schema.required);
|
|
49
|
+
const parameters = Object.entries(tool.input_schema.properties).sort(([a], [b]) =>
|
|
50
|
+
Number(required.has(b)) - Number(required.has(a)) || compare(a, b));
|
|
51
|
+
return { name: tool.name, purpose: tool.description.slice(0, MAX_PURPOSE_CHARS),
|
|
52
|
+
inputs: parameters.slice(0, MAX_PARAMETER_HINTS).map(([name, raw]) => ({ name: name.slice(0, 128), type: parameterType(raw), required: required.has(name) })),
|
|
53
|
+
omitted_inputs: Math.max(0, parameters.length - MAX_PARAMETER_HINTS) };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A model assertion or another extension's data cannot authorize a declaration (D11). */
|
|
58
|
+
export function recordedDeclarations(events: readonly LogEvent[]): DeclarationRequest[] {
|
|
59
|
+
const invocations = new Map<string, ToolInvocationEvent>();
|
|
60
|
+
const requested = new Map<string, { request: DeclarationRequest; user: string | undefined }>();
|
|
61
|
+
let user: string | undefined;
|
|
62
|
+
for (const event of events) {
|
|
63
|
+
if (event.t === "user") { user = event.id; continue; }
|
|
64
|
+
if (event.t === "interrupt") {
|
|
65
|
+
for (const [name, value] of requested) if (value.user === event.user_id) requested.delete(name);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (event.t === "tool_invocation") { invocations.set(event.id, event); continue; }
|
|
69
|
+
if (event.t !== "tool_result" || !event.ok || !event.invocation_ref) continue;
|
|
70
|
+
if (event.outcome !== undefined && event.outcome.status !== "completed") continue;
|
|
71
|
+
const invocation = invocations.get(event.invocation_ref.event);
|
|
72
|
+
if (invocation?.name !== "tool_search" || invocation.call_id !== event.call_id) continue;
|
|
73
|
+
const data = record(event.data);
|
|
74
|
+
if (!data || data["kind"] !== "tool_discovery" || data["version"] !== 1) continue;
|
|
75
|
+
const entries = data["requested"];
|
|
76
|
+
if (!Array.isArray(entries) || entries.length > MAX_DISCOVERY_RESULTS) continue;
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const item = record(entry);
|
|
79
|
+
if (!item) continue;
|
|
80
|
+
const name = item["name"], fingerprint = item["fingerprint"];
|
|
81
|
+
if (typeof name === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(name) && typeof fingerprint === "string" && /^[a-f0-9]{64}$/.test(fingerprint)) {
|
|
82
|
+
requested.set(name, { request: { name, fingerprint }, user });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return [...requested.values()].map(value => value.request);
|
|
87
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** R3 / D11: failure policy follows observed execution facts, never output keywords. */
|
|
2
|
+
import type { ToolDiagnostic } from "./tool-contract.ts";
|
|
3
|
+
|
|
4
|
+
export type FailureFacts = { execution: "not_started" | "started" | "unknown"; effects: "none" | "possible" };
|
|
5
|
+
export const NOT_STARTED: FailureFacts = { execution: "not_started", effects: "none" };
|
|
6
|
+
export const READ_ATTEMPTED: FailureFacts = { execution: "started", effects: "none" };
|
|
7
|
+
export const UNKNOWN_EFFECT: FailureFacts = { execution: "unknown", effects: "possible" };
|
|
8
|
+
|
|
9
|
+
const guidance: Record<ToolDiagnostic["code"], string> = {
|
|
10
|
+
invalid_input: "Correct the named argument using the tool schema; example values are placeholders.",
|
|
11
|
+
version_mismatch: "Read the current resource and derive a new edit from that version.",
|
|
12
|
+
not_found: "Verify the resource path or identifier before changing the request.",
|
|
13
|
+
ambiguous_edit: "Read the indicated range and choose a unique match before editing.",
|
|
14
|
+
permission_denied: "This operation was denied. Do not bypass it through another tool or wrapper; a user policy change is required.",
|
|
15
|
+
execution_failed: "Inspect the failure and change its cause before retrying.",
|
|
16
|
+
environment_missing: "Verify the working directory and required executable in this environment before retrying. If available and permitted, use environment with the exact program; use its python probe for actual Python API support.",
|
|
17
|
+
transient: "Retry with bounded backoff only if no side effect is possible.",
|
|
18
|
+
cancelled: "Respect cancellation; inspect any partial effects before a user-authorized continuation.",
|
|
19
|
+
timeout: "Inspect partial output and effects before deciding whether a new invocation is needed.",
|
|
20
|
+
unknown_effect: "Execution or effects are uncertain. Inspect existing state and output before any replay.",
|
|
21
|
+
command_failed: "Use the actual exit and output to diagnose the command; a nonzero exit alone does not identify its cause.",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function failureDiagnostic(code: ToolDiagnostic["code"], message: string, facts: FailureFacts,
|
|
25
|
+
extra: Pick<ToolDiagnostic, "recover" | "example"> = {}): ToolDiagnostic {
|
|
26
|
+
const retry = code === "permission_denied" || code === "cancelled" ? "never"
|
|
27
|
+
: code === "unknown_effect" || facts.effects === "possible" ? "after_inspection"
|
|
28
|
+
: code === "transient" ? "backoff" : "after_change";
|
|
29
|
+
return { code, message, ...facts, retry, next: guidance[code], ...(code === "permission_denied" ? {} : extra) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** R10: only new advice is scoped; execution facts and historical records are never rewritten. */
|
|
33
|
+
export function scopeFailureGuidance(diagnostic: ToolDiagnostic, tools: readonly string[], recoverAllowed = true): ToolDiagnostic {
|
|
34
|
+
const { recover, ...facts } = diagnostic;
|
|
35
|
+
const next = diagnostic.execution !== undefined && diagnostic.code === "environment_missing" && !tools.includes("environment")
|
|
36
|
+
? { next: "Verify the working directory and required executable in the current executor using permitted observations. If the needed capability is unavailable, report that evidence gap before retrying." } : {};
|
|
37
|
+
return { ...facts, ...next, ...(recover && recoverAllowed && tools.includes(recover.tool) && diagnostic.code !== "permission_denied" ? { recover } : {}) };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Runtime and store throw only facts they own; the outer boundary must retain these facts. */
|
|
41
|
+
export class ToolFault extends Error {
|
|
42
|
+
readonly diagnostic: ToolDiagnostic;
|
|
43
|
+
constructor(code: ToolDiagnostic["code"], message: string, facts: FailureFacts, extra: Pick<ToolDiagnostic, "recover"> = {}) {
|
|
44
|
+
super(message); this.name = "ToolFault";
|
|
45
|
+
this.diagnostic = failureDiagnostic(code, message, facts, extra);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** OS codes are facts; words like "denied" in arbitrary output are not classification evidence. */
|
|
50
|
+
export function failureFromError(error: unknown, facts: FailureFacts): ToolDiagnostic {
|
|
51
|
+
if (error instanceof ToolFault) return error.diagnostic;
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
const os = error !== null && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
54
|
+
const code = os === "ENOENT" || os === "ENOTDIR" ? "not_found"
|
|
55
|
+
: os === "EACCES" || os === "EPERM" ? "permission_denied"
|
|
56
|
+
: os === "EISDIR" ? "invalid_input"
|
|
57
|
+
: ["EAGAIN", "ETIMEDOUT", "ECONNRESET", "EMFILE"].includes(String(os)) ? "transient"
|
|
58
|
+
: facts.effects === "possible" ? "unknown_effect" : "execution_failed";
|
|
59
|
+
return failureDiagnostic(code, message, facts);
|
|
60
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Q.21: the retired orchestration contrast cannot enter new direct-query trials. */
|
|
2
|
+
export const TOOL_MODE_FACTORS = ["querySyntax"] as const;
|
|
3
|
+
export const TOOL_MODE_TASKS = ["tool-small", "tool-batch", "tool-aggregate"] as const;
|
|
4
|
+
export type ToolModeTask = typeof TOOL_MODE_TASKS[number];
|
|
5
|
+
export const isToolModeTask = (task: string): task is ToolModeTask => (TOOL_MODE_TASKS as readonly string[]).includes(task);
|
|
6
|
+
export interface ToolModeEngine { packages: Record<string, string>; assets: Record<string, string> }
|
|
7
|
+
export function validateToolModeEngine(engine: ToolModeEngine | undefined): asserts engine is ToolModeEngine {
|
|
8
|
+
if (!engine || engine.packages["@earendil-works/pi-ai"] !== "0.99.1" || Object.keys(engine.packages).length !== 1 ||
|
|
9
|
+
!engine.assets || !Object.entries(engine.assets).every(([path, sha]) =>
|
|
10
|
+
/^@earendil-works\/pi-ai\/[a-zA-Z0-9_./-]+$/.test(path) && !path.split("/").includes("..") && /^[a-f0-9]{64}$/.test(sha)) ||
|
|
11
|
+
["@earendil-works/pi-ai/package.json", "@earendil-works/pi-ai/dist/api/openai-codex-responses.js"].some(path => !engine.assets[path]))
|
|
12
|
+
throw Error("tool mode engine assets are missing or unsupported");
|
|
13
|
+
}
|
|
14
|
+
export interface ToolModeMetrics {
|
|
15
|
+
elapsedMs: number; queryCalls: number; primitiveCalls: number;
|
|
16
|
+
sourcePaths: string[]; requiredSourcePaths: string[]; sourceComplete: boolean;
|
|
17
|
+
}
|
|
18
|
+
export function isToolModeMetrics(value: unknown): value is ToolModeMetrics {
|
|
19
|
+
if (!value || typeof value !== "object") return false;
|
|
20
|
+
const v = value as ToolModeMetrics;
|
|
21
|
+
const paths = (list: unknown): list is string[] => Array.isArray(list) && list.every(item => typeof item === "string") && new Set(list).size === list.length;
|
|
22
|
+
return Number.isFinite(v.elapsedMs) && v.elapsedMs >= 0 && [v.queryCalls, v.primitiveCalls].every(n => Number.isSafeInteger(n) && n >= 0) &&
|
|
23
|
+
paths(v.sourcePaths) && paths(v.requiredSourcePaths) && v.requiredSourcePaths.length > 0 &&
|
|
24
|
+
v.sourceComplete === v.requiredSourcePaths.every(path => v.sourcePaths.includes(path));
|
|
25
|
+
}
|
package/src/core/transcript.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type TranscriptItem = Identity & (
|
|
|
9
9
|
| { kind: "assistant" | "thinking" | "notice"; text: string }
|
|
10
10
|
| { kind: "custom"; text: string; customType: string; data?: unknown; entry: boolean; timestamp: string }
|
|
11
11
|
| { kind: "tool"; callId: string; name: string; input: unknown; result: { text: string; ok: boolean; data?: unknown; images?: readonly ImageRef[] } | null }
|
|
12
|
+
| { kind: "shell"; command: string; output: string; exit: number; excluded: boolean }
|
|
13
|
+
| { kind: "segment"; reason: string; handoff: string; nextNode?: number; nextModel?: string }
|
|
12
14
|
);
|
|
13
15
|
|
|
14
16
|
export function projectTranscript(events: readonly LogEvent[], entryTypes: ReadonlySet<string> = new Set()): TranscriptItem[] {
|
|
@@ -26,8 +28,7 @@ export function projectTranscript(events: readonly LogEvent[], entryTypes: Reado
|
|
|
26
28
|
users.set(event.id, items.length);
|
|
27
29
|
items.push({ ...identity, kind: "user", text: event.text, interrupted: false, ...(event.images?.length ? { images: event.images } : {}) });
|
|
28
30
|
break;
|
|
29
|
-
case "user_bash": items.push({ ...identity, kind: "
|
|
30
|
-
text: `${event.excludeFromContext ? "!!" : "!"}${event.command}\n${event.content}` }); break;
|
|
31
|
+
case "user_bash": items.push({ ...identity, kind: "shell", command: event.command, output: event.content, exit: event.exit, excluded: event.excludeFromContext }); break;
|
|
31
32
|
case "outcome": items.push({ ...identity, kind: "notice",
|
|
32
33
|
text: `${event.outcome.status} · ${event.outcome.reason} · ${event.reason}` }); break;
|
|
33
34
|
case "assistant":
|
|
@@ -39,6 +40,12 @@ export function projectTranscript(events: readonly LogEvent[], entryTypes: Reado
|
|
|
39
40
|
} else items.push({ id, revision: id, kind: block.type === "text" ? "assistant" : "thinking", text: block.text });
|
|
40
41
|
});
|
|
41
42
|
break;
|
|
43
|
+
case "tool_invocation": {
|
|
44
|
+
if (!event.parent_call_id && !event.origin) break;
|
|
45
|
+
calls.set(event.call_id, items.length);
|
|
46
|
+
items.push({ ...identity, kind: "tool", callId: event.call_id, name: event.name, input: event.input ?? { artifact: event.input_ref.sha256 }, result: null });
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
42
49
|
case "tool_result": {
|
|
43
50
|
const index = calls.get(event.call_id);
|
|
44
51
|
const call = index === undefined ? undefined : items[index];
|
|
@@ -54,13 +61,21 @@ export function projectTranscript(events: readonly LogEvent[], entryTypes: Reado
|
|
|
54
61
|
items.push({ ...identity, kind: "notice", text: "Turn interrupted" });
|
|
55
62
|
break;
|
|
56
63
|
}
|
|
57
|
-
|
|
64
|
+
// A segment boundary is the log's own structure; the view draws it as a divider (D20).
|
|
65
|
+
case "cut": items.push({ ...identity, kind: "segment", reason: event.reason, handoff: event.handoff_ref,
|
|
66
|
+
...(event.next_node === undefined ? {} : { nextNode: event.next_node }), ...(event.next_model === undefined ? {} : { nextModel: event.next_model }) }); break;
|
|
58
67
|
case "rewind": items.push({ ...identity, kind: "notice", text: `Navigated context (${event.mode}) · ${event.prompt}` }); break;
|
|
59
68
|
case "spawn": items.push({ ...identity, kind: "notice", text: `Branch ${event.branch.node} · ${event.purpose}` }); break;
|
|
60
69
|
case "joined": items.push({ ...identity, kind: "notice", text: `Branch ${event.branch.node} finished · ${event.summary}` }); break;
|
|
61
70
|
case "adopt": items.push({ ...identity, kind: "notice", text: `Adopted branch ${event.branch.node}` }); break;
|
|
62
71
|
case "discard": items.push({ ...identity, kind: "notice", text: `Discarded branch ${event.branch.node} · ${event.reason}` }); break;
|
|
63
|
-
case "
|
|
72
|
+
case "skill_loaded": items.push({ ...identity, kind: "notice", text: `Skill ${event.name} · ${event.mode} · ${event.sha256}` }); break;
|
|
73
|
+
case "process_exit": items.push({ ...identity, kind: "notice", text: `Process ${event.session} exited ${event.exit} · output ${event.output.sha256}` }); break;
|
|
74
|
+
case "acceptance_contract": items.push({ ...identity, kind: "notice", text: `Acceptance contract · ${event.contract.id}` }); break;
|
|
75
|
+
case "target_observation": items.push({ ...identity, kind: "notice", text: `Target ${event.observation.target} · ${event.observation.state} · ${event.observation.reason}` }); break;
|
|
76
|
+
case "acceptance_decision": items.push({ ...identity, kind: "notice", text: `User accepted · ${event.criterion}` }); break;
|
|
77
|
+
case "checkpoint": case "workspace_observation":
|
|
78
|
+
case "guidance": case "snapshot": case "claim": case "decision": case "pitfall": case "blocked": case "question": case "next_action": case "work_status": case "work_owner": case "work_dependencies": case "evidence_link":
|
|
64
79
|
// These remain inspectable via /state and /export; they are not chat messages.
|
|
65
80
|
break;
|
|
66
81
|
default: assertNever(event, "projectTranscript");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** R12 / Q.14: runner observations complement, but never replace, gateway billing evidence. */
|
|
2
|
+
export type TrialInterruption = { source: "deadline" | "caller"; phase: "initialization" | "execution"; backendRequests: number };
|
|
3
|
+
export type TrialLifecycle = { version: 1; backendRequests: number; interruption: TrialInterruption | null; cleanup: "complete" | "failed" };
|
|
4
|
+
|
|
5
|
+
export function isTrialLifecycle(value: unknown): value is TrialLifecycle {
|
|
6
|
+
if (!value || typeof value !== "object") return false;
|
|
7
|
+
const v=value as Record<string,unknown>, n=v["backendRequests"], interruption=v["interruption"];
|
|
8
|
+
if (v["version"]!==1 || typeof n!=="number" || !Number.isSafeInteger(n) || n<0 || v["cleanup"]!=="complete" && v["cleanup"]!=="failed") return false;
|
|
9
|
+
if (interruption===null) return true;
|
|
10
|
+
if (!interruption || typeof interruption!=="object") return false;
|
|
11
|
+
const stop=interruption as Record<string,unknown>;
|
|
12
|
+
return (stop["source"]==="deadline" || stop["source"]==="caller") && (stop["phase"]==="initialization" || stop["phase"]==="execution")
|
|
13
|
+
&& stop["backendRequests"]===n && (stop["phase"]!=="initialization" || n===0);
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which screen an interactive session uses. Pure: flag, environment and
|
|
3
|
+
* setting go in, a concrete mode and the reason come out.
|
|
4
|
+
*
|
|
5
|
+
* Fullscreen is the default (OpenCode, grok-build and Pi 1.0 all default to
|
|
6
|
+
* the alternate screen). `auto` is Codex's / grok-build's policy rather than
|
|
7
|
+
* "always", because the alternate screen has known dead ends: Zellij keeps
|
|
8
|
+
* no scrollback for it (zellij#1032, Codex turns it off there), and a dumb
|
|
9
|
+
* terminal cannot address the screen at all. Every route back to native
|
|
10
|
+
* scrollback stays: `--tui-mode regular`, `XENO_TUI_MODE`, the setting.
|
|
11
|
+
*/
|
|
12
|
+
export type TuiModeSetting = "auto" | "regular" | "fullscreen";
|
|
13
|
+
export type TuiMode = "regular" | "fullscreen";
|
|
14
|
+
|
|
15
|
+
export interface TuiModeInput {
|
|
16
|
+
readonly flag?: TuiMode | undefined;
|
|
17
|
+
readonly setting?: TuiModeSetting | undefined;
|
|
18
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveTuiMode(input: TuiModeInput): { mode: TuiMode; reason: string } {
|
|
22
|
+
if (input.flag) return { mode: input.flag, reason: "--tui-mode" };
|
|
23
|
+
const fromEnv = input.env["XENO_TUI_MODE"];
|
|
24
|
+
if (fromEnv === "regular" || fromEnv === "fullscreen") return { mode: fromEnv, reason: "XENO_TUI_MODE" };
|
|
25
|
+
if (input.setting === "regular" || input.setting === "fullscreen") return { mode: input.setting, reason: "settings" };
|
|
26
|
+
if (input.env["TERM"] === "dumb") return { mode: "regular", reason: "TERM=dumb cannot draw a fullscreen UI" };
|
|
27
|
+
if (input.env["ZELLIJ"] !== undefined) return { mode: "regular", reason: "Zellij keeps no scrollback for the alternate screen" };
|
|
28
|
+
return { mode: "fullscreen", reason: "default" };
|
|
29
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether to look for a newer xeno and what to say about it (docs/tasks/feat-update-check.md). Pure.
|
|
3
|
+
*
|
|
4
|
+
* Notify only: xeno names the command for the way it was installed and never
|
|
5
|
+
* runs it. At most one registry request a day, recorded even when it fails,
|
|
6
|
+
* and none at all offline, in CI, when turned off, or from a source checkout
|
|
7
|
+
* (where the right update is `git pull`, not a package manager). Clock,
|
|
8
|
+
* environment and the cached answer are all arguments, so every rule is a
|
|
9
|
+
* plain function of its inputs.
|
|
10
|
+
*/
|
|
11
|
+
import { isUpgrade } from "./version.ts";
|
|
12
|
+
|
|
13
|
+
export const PACKAGE_NAME = "@visual-z/xeno";
|
|
14
|
+
/** One request a day, like update-notifier; Codex uses 20 hours. Assumed. */
|
|
15
|
+
export const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
16
|
+
|
|
17
|
+
export type InstallKind = "bun" | "npm" | "other" | "checkout";
|
|
18
|
+
|
|
19
|
+
export interface UpdateCache {
|
|
20
|
+
/** Epoch ms of the last attempt, successful or not. */
|
|
21
|
+
readonly checkedAt: number;
|
|
22
|
+
/** The newest published version seen, when the last success had one. */
|
|
23
|
+
readonly latest?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UpdateInput {
|
|
27
|
+
readonly current: string;
|
|
28
|
+
readonly now: number;
|
|
29
|
+
readonly cache: UpdateCache | null;
|
|
30
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
31
|
+
readonly offline: boolean;
|
|
32
|
+
/** Settings `updateCheck`; undefined means on. */
|
|
33
|
+
readonly enabled: boolean | undefined;
|
|
34
|
+
readonly install: InstallKind;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UpdatePlan {
|
|
38
|
+
/** Ask the registry now. */
|
|
39
|
+
readonly check: boolean;
|
|
40
|
+
/** What to tell the person, if anything is newer. */
|
|
41
|
+
readonly notice?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function truthy(value: string | undefined): boolean {
|
|
45
|
+
return value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Reasons not to send a request at all, in the order they are checked. */
|
|
49
|
+
export function updateDisabled(input: Pick<UpdateInput, "env" | "offline" | "enabled" | "install">): string | undefined {
|
|
50
|
+
if (input.install === "checkout") return "running from a source checkout";
|
|
51
|
+
if (input.offline || truthy(input.env["XENO_OFFLINE"])) return "offline";
|
|
52
|
+
if (input.enabled === false) return "updateCheck is off";
|
|
53
|
+
if (truthy(input.env["XENO_NO_UPDATE_CHECK"]) || truthy(input.env["NO_UPDATE_NOTIFIER"])) return "turned off by environment";
|
|
54
|
+
if (truthy(input.env["CI"]) || truthy(input.env["CONTINUOUS_INTEGRATION"])) return "running in CI";
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function updatePlan(input: UpdateInput): UpdatePlan {
|
|
59
|
+
if (updateDisabled(input)) return { check: false };
|
|
60
|
+
const due = !input.cache || !Number.isFinite(input.cache.checkedAt) || input.now - input.cache.checkedAt >= CHECK_INTERVAL_MS
|
|
61
|
+
|| input.cache.checkedAt > input.now; // a clock set back must not silence checks for a year
|
|
62
|
+
const latest = input.cache?.latest;
|
|
63
|
+
return { check: due, ...(latest && isUpgrade(latest, input.current) ? { notice: updateNotice(latest, input.current, input.install) } : {}) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function upgradeCommand(install: InstallKind): string | undefined {
|
|
67
|
+
switch (install) {
|
|
68
|
+
case "bun": return `bun add -g ${PACKAGE_NAME}@latest`;
|
|
69
|
+
case "npm": return `npm i -g ${PACKAGE_NAME}@latest`;
|
|
70
|
+
case "other": case "checkout": return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Two lines: what is new, then the command on its own line so it can be copied whole. */
|
|
75
|
+
export function updateNotice(latest: string, current: string, install: InstallKind): string {
|
|
76
|
+
const command = upgradeCommand(install);
|
|
77
|
+
return `xeno ${latest} is available (you have ${current})\n ${command ?? "see docs/install.md to update"}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* How this copy was installed, from where its package root really lives.
|
|
82
|
+
* Detected from the path rather than by running package managers, which
|
|
83
|
+
* costs seconds at startup (OpenCode runs up to seven list commands).
|
|
84
|
+
*/
|
|
85
|
+
export function installKind(packageRoot: string, hasGit: boolean): InstallKind {
|
|
86
|
+
const path = packageRoot.replaceAll("\\", "/");
|
|
87
|
+
if (hasGit || !path.includes("/node_modules/")) return "checkout";
|
|
88
|
+
if (path.includes("/.bun/install/global/")) return "bun";
|
|
89
|
+
if (path.includes("/lib/node_modules/") || /\/npm\/node_modules\//i.test(path)) return "npm";
|
|
90
|
+
return "other";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The registry's dist-tags for the package, honouring npm's registry setting. */
|
|
94
|
+
export function distTagsUrl(registry: string | undefined): string {
|
|
95
|
+
const base = (registry && /^https?:\/\//.test(registry) ? registry : "https://registry.npmjs.org/").replace(/\/+$/, "");
|
|
96
|
+
return `${base}/-/package/${PACKAGE_NAME.replace("/", "%2f")}/dist-tags`;
|
|
97
|
+
}
|