@visual-z/xeno 0.2.1 → 0.4.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 +18 -0
- package/README.md +126 -34
- package/docs/app-server.md +84 -0
- package/docs/install.md +51 -13
- package/docs/providers.md +24 -3
- package/package.json +7 -5
- package/src/apps/app-server.ts +28 -0
- package/src/apps/approve.ts +3 -3
- package/src/apps/cli.ts +25 -10
- package/src/apps/commands.ts +45 -8
- package/src/apps/completions.ts +78 -22
- package/src/apps/control-transport.ts +89 -0
- package/src/apps/image-draft.ts +46 -0
- package/src/apps/model-catalog.ts +2 -1
- 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 +333 -1542
- package/src/apps/startup-input.ts +45 -9
- package/src/apps/startup.ts +149 -94
- package/src/apps/ui/app.ts +1553 -0
- package/src/apps/ui/control-model.ts +26 -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 +86 -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-action.ts +20 -0
- package/src/core/completion-review.ts +37 -21
- 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/control-protocol.ts +62 -0
- package/src/core/control-view.ts +42 -0
- 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 +81 -6
- package/src/core/events.ts +99 -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/harness-diagnostics.ts +206 -0
- 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/oauth-fields.ts +44 -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/program-status.ts +31 -0
- package/src/core/project.ts +70 -19
- 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/request-progress.ts +87 -0
- package/src/core/request-state.ts +114 -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 +33 -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-input.ts +6 -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 +11 -4
- package/src/runtime/branch-jobs.ts +36 -0
- package/src/runtime/catalog.ts +13 -3
- package/src/runtime/clipboard.ts +15 -4
- package/src/runtime/command-artifacts.ts +64 -0
- package/src/runtime/control-connection.ts +104 -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 +462 -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 +1088 -282
- package/src/runtime/model-discovery.ts +208 -0
- package/src/runtime/model.ts +9 -0
- package/src/runtime/native-eval-fixtures.ts +33 -0
- package/src/runtime/native-eval.ts +146 -0
- package/src/runtime/oauth.ts +80 -3
- package/src/runtime/pi-provider.ts +169 -12
- package/src/runtime/program-status.ts +38 -0
- package/src/runtime/provider-connect.ts +109 -0
- package/src/runtime/provider-presets.ts +87 -0
- package/src/runtime/provider-request.ts +13 -2
- package/src/runtime/provider.ts +23 -9
- 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/session-control.ts +198 -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 +200 -0
- package/src/tui/ui/control-panel.ts +23 -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 +279 -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
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Startup owns raw input until the renderer can (D20). Keystrokes typed while
|
|
3
|
+
* configuration loads are held as original bytes and handed to the next owner;
|
|
4
|
+
* only Ctrl+C outside a bracketed paste is interpreted here.
|
|
5
|
+
*/
|
|
5
6
|
|
|
6
7
|
type StartupStdin = Pick<NodeJS.ReadStream, "isTTY" | "isRaw" | "setRawMode" | "setEncoding" | "on" | "off" | "resume" | "pause" | "unshift">;
|
|
7
8
|
export interface StartupInput {
|
|
@@ -31,7 +32,9 @@ export function createStartupInput(opts: {
|
|
|
31
32
|
let active = false, closed = false, announced = false;
|
|
32
33
|
let phase = "Reading configuration";
|
|
33
34
|
let pending: string[] = [];
|
|
34
|
-
let
|
|
35
|
+
let pasting = false;
|
|
36
|
+
/** An escape sequence cut across chunks; joined with the next chunk before matching. */
|
|
37
|
+
let partial = "";
|
|
35
38
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
36
39
|
|
|
37
40
|
const release = () => {
|
|
@@ -41,7 +44,7 @@ export function createStartupInput(opts: {
|
|
|
41
44
|
stdin.pause();
|
|
42
45
|
stdin.off("data", receive);
|
|
43
46
|
signals.off("SIGINT", interrupt); signals.off("SIGTERM", terminate); signals.off("SIGHUP", hangup);
|
|
44
|
-
|
|
47
|
+
pasting = false; partial = "";
|
|
45
48
|
stdin.setRawMode(wasRaw);
|
|
46
49
|
};
|
|
47
50
|
const restore = () => { closed = true; release(); pending = []; };
|
|
@@ -56,14 +59,17 @@ export function createStartupInput(opts: {
|
|
|
56
59
|
// Keep original chunks, including partial escapes/pastes, for Pi's own decoder.
|
|
57
60
|
// The temporary parser only recognizes cancellation outside bracketed paste.
|
|
58
61
|
pending.push(data);
|
|
59
|
-
|
|
62
|
+
const joined = partial + data;
|
|
63
|
+
const cut = trailingPartialEscape(joined);
|
|
64
|
+
const whole = joined.slice(0, joined.length - cut.length);
|
|
65
|
+
partial = cut;
|
|
66
|
+
if (interruptsStartup(whole, pasting)) interrupt();
|
|
67
|
+
pasting = pasteState(whole, pasting);
|
|
60
68
|
};
|
|
61
69
|
const announce = () => { if (active) { announced = true; report(` Starting xeno: ${phase} (Ctrl+C to cancel)`); } };
|
|
62
70
|
const capture = () => {
|
|
63
71
|
if (closed || active) return;
|
|
64
72
|
active = true; announced = false;
|
|
65
|
-
parser = new StdinBuffer({ escapeTimeout: resolveEscapeTimeoutMs() });
|
|
66
|
-
parser.on("data", (data: string) => { if (!isKeyRelease(data) && matchesKey(data, "ctrl+c")) interrupt(); });
|
|
67
73
|
stdin.setEncoding("utf8");
|
|
68
74
|
stdin.on("data", receive);
|
|
69
75
|
signals.on("SIGINT", interrupt); signals.on("SIGTERM", terminate); signals.on("SIGHUP", hangup);
|
|
@@ -92,3 +98,33 @@ export function createStartupInput(opts: {
|
|
|
92
98
|
},
|
|
93
99
|
};
|
|
94
100
|
}
|
|
101
|
+
|
|
102
|
+
/** Bracketed paste state after a chunk: a paste that started and did not end yet. */
|
|
103
|
+
export function pasteState(data: string, pasting: boolean): boolean {
|
|
104
|
+
const start = data.lastIndexOf("\x1b[200~"), end = data.lastIndexOf("\x1b[201~");
|
|
105
|
+
if (start < 0 && end < 0) return pasting;
|
|
106
|
+
return start > end;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Ctrl+C as legacy ETX or kitty CSI-u (99;5u press, not a :3 release), outside a paste. */
|
|
110
|
+
export function interruptsStartup(data: string, pasting: boolean): boolean {
|
|
111
|
+
let outside = "";
|
|
112
|
+
let inPaste = pasting;
|
|
113
|
+
for (let i = 0; i < data.length;) {
|
|
114
|
+
if (data.startsWith("\x1b[200~", i)) { inPaste = true; i += 6; continue; }
|
|
115
|
+
if (data.startsWith("\x1b[201~", i)) { inPaste = false; i += 6; continue; }
|
|
116
|
+
if (!inPaste) outside += data[i];
|
|
117
|
+
i++;
|
|
118
|
+
}
|
|
119
|
+
return outside.includes("\x03") || /\x1b\[99;5(?::1)?u/.test(outside);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The unfinished CSI/SS3 at the end of a chunk ("\x1b[99;" waiting for "5u"), or "". */
|
|
123
|
+
export function trailingPartialEscape(data: string): string {
|
|
124
|
+
const at = data.lastIndexOf("\x1b");
|
|
125
|
+
if (at < 0) return "";
|
|
126
|
+
const tail = data.slice(at);
|
|
127
|
+
if (tail === "\x1b") return tail;
|
|
128
|
+
if (/^\x1b\[[0-9;:?]*$/.test(tail) || /^\x1bO$/.test(tail)) return tail;
|
|
129
|
+
return "";
|
|
130
|
+
}
|
package/src/apps/startup.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { release } from "node:os";
|
|
2
|
-
import {
|
|
2
|
+
import { codingWorkingProtocol } from "../core/continuation.ts";
|
|
3
|
+
import { taskGuidance } from "../core/prompt-policy.ts";
|
|
3
4
|
import { environmentSection, type SessionEnvironment } from "../core/environment.ts";
|
|
4
5
|
/**
|
|
5
6
|
* From argv to a session that can run. Credential order, model selection,
|
|
@@ -12,8 +13,10 @@ import { mkdir } from "node:fs/promises";
|
|
|
12
13
|
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
14
|
import { Session, systemClock } from "../runtime/loop.ts";
|
|
14
15
|
import { DEFAULT_PROVIDER, type ProviderConfig } from "../runtime/provider.ts";
|
|
15
|
-
import { createProvider, selectModel, loadCatalog, groupedModels, parseUserCatalog, type CatalogFetch } from "../runtime/catalog.ts";
|
|
16
|
+
import { createProvider, providerSessionId, selectModel, loadCatalog, groupedModels, parseUserCatalog, type CatalogFetch } from "../runtime/catalog.ts";
|
|
16
17
|
import { ExtensionRegistry } from "../runtime/extensions.ts";
|
|
18
|
+
import { DISCOVERY_TOOL_NAME } from "../runtime/tool-discovery.ts";
|
|
19
|
+
import { declaredToolNames } from "../core/tool-discovery.ts";
|
|
17
20
|
import { CATALOG, outputTokenBudget, type ModelSpec } from "../runtime/model.ts";
|
|
18
21
|
import { asThinkingLevel, clampThinkingLevel, supportedThinkingLevels } from "../core/thinking.ts";
|
|
19
22
|
import { modelKey } from "../core/session-position.ts";
|
|
@@ -25,10 +28,11 @@ import {
|
|
|
25
28
|
import { authPath, configDir, readJsonFile } from "../store/config.ts";
|
|
26
29
|
import { loadUiResources, loadTextResources, type UiResources } from "../store/resources.ts";
|
|
27
30
|
import { discoverResources, type ResourceIndex } from "../store/resource-index.ts";
|
|
28
|
-
import { runResourceConfig } from "./
|
|
31
|
+
import { pickProjectTrust, pickStartupSession, runResourceConfig } from "./ui/standalone.ts";
|
|
29
32
|
import { PackageManager, type PackageCommand } from "../runtime/package-manager.ts";
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
33
|
+
import { loadAcceptanceContract } from "../store/acceptance.ts";
|
|
34
|
+
import type { AcceptanceContract } from "../core/acceptance.ts";
|
|
35
|
+
import { appendSkillsToSystem, type Skill } from "../core/skills.ts";
|
|
32
36
|
import { COMMANDS, resolveSlashCatalog, type SlashCatalog } from "./commands.ts";
|
|
33
37
|
import { createStyle, IDENTITY, type Style } from "../tui/style.ts";
|
|
34
38
|
import type { Settings } from "../core/settings.ts";
|
|
@@ -38,23 +42,24 @@ import { readCursor, writeCursor, type Cursor } from "../store/log.ts";
|
|
|
38
42
|
import { resolveLogDir, findSessionByPrefix, listSessions, sessionsDir, type ResolveLogOpts } from "../store/sessions.ts";
|
|
39
43
|
import { recoverCut } from "../store/recovery.ts";
|
|
40
44
|
import { planSessionEntry, restoreSessionModel } from "../core/session-entry.ts";
|
|
41
|
-
import { pickStartupSession } from "./session-picker.ts";
|
|
42
45
|
import { loadContext, assembleSystemPrompt, formatNotice, type LoadedContext } from "../store/context-files.ts";
|
|
43
46
|
import {
|
|
44
47
|
projectTrust,
|
|
45
48
|
} from "../store/trust.ts";
|
|
46
49
|
import { stat } from "node:fs/promises";
|
|
47
50
|
import {
|
|
48
|
-
parseToolList, rulesWithAllowTools, selectTools,
|
|
51
|
+
parseToolList, rulesWithAllowTools, selectTools, isToolProfile,
|
|
52
|
+
type ToolProfile,
|
|
49
53
|
type PermissionRequest, type PermissionRule,
|
|
50
54
|
} from "../core/permissions.ts";
|
|
51
55
|
import {
|
|
52
|
-
TOOL_SCHEMAS, DEFAULT_TOOL_CONTEXT, workspaceRoot,
|
|
56
|
+
TOOL_SCHEMAS, TOOL_DEFINITIONS, modelToolSchema, DEFAULT_TOOL_CONTEXT, workspaceRoot,
|
|
53
57
|
type ApprovalChoice, type ToolContext,
|
|
54
58
|
} from "../runtime/tools.ts";
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
|
|
59
|
+
import { assertNever } from "../core/exhaustive.ts";
|
|
60
|
+
import {
|
|
61
|
+
contextLimits, DEFAULT_CONTEXT_POLICY, isContextPolicy, type ContextLimits, type ContextPolicy,
|
|
62
|
+
} from "../core/context-policy.ts";
|
|
58
63
|
import { colorEnabled, createPalette, describeError, stdinIsTty, stdoutIsTty, type Palette } from "./render.ts";
|
|
59
64
|
import { type ActionId, type Keymap } from "../core/keymap.ts";
|
|
60
65
|
import type { StartupInput } from "./startup-input.ts";
|
|
@@ -62,46 +67,16 @@ import type { StartupInput } from "./startup-input.ts";
|
|
|
62
67
|
export const SYSTEM_PROMPT = systemPromptFor(TOOL_SCHEMAS.map((tool) => tool.name));
|
|
63
68
|
|
|
64
69
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* will never work. The default prompt is left alone when every tool is visible.
|
|
70
|
+
* Tool schemas are the only capability inventory. A branch may inherit this
|
|
71
|
+
* stable policy while narrowing its tools; no parent tool recipe can leak.
|
|
68
72
|
*/
|
|
69
73
|
export function systemPromptFor(toolNames: readonly string[], env?: SessionEnvironment): string {
|
|
70
|
-
const has = (name: string) => toolNames.includes(name);
|
|
71
|
-
const toolsLine = toolNames.length === 0
|
|
72
|
-
? "No tools are available in this session."
|
|
73
|
-
: `Tools: ${toolNames.join(", ")}.` +
|
|
74
|
-
(toolNames.some(name => ["grep", "find", "ls"].includes(name)) ? " Use the available grep/find/ls tools for confined search." : "") +
|
|
75
|
-
(has("bash")
|
|
76
|
-
? " Use bash for verification commands and operations the other tools do not cover."
|
|
77
|
-
: "");
|
|
78
|
-
|
|
79
|
-
// Each line answers a failure class in the Terminal-Bench taxonomy
|
|
80
|
-
// (arXiv 2601.11868): premature termination, no verification, step
|
|
81
|
-
// repetition. The wording of the first and last follows the Codex
|
|
82
|
-
// prompting guide: persist end to end, and end on a change or a blocker.
|
|
83
|
-
const work = [
|
|
84
|
-
"How to work:",
|
|
85
|
-
"- Keep going until the request is fully resolved. When a reasonable assumption lets you proceed, make it and mention it at the end; stop to ask only when blocked on a decision only the user can make.",
|
|
86
|
-
"- Understand before changing: read the relevant code, and for a bug, reproduce it first.",
|
|
87
|
-
"- Put independent reads and searches in one response; they run in parallel.",
|
|
88
|
-
"- After a change, run the project's own tests, build or type check. When a command fails, read the end of its output first; errors and summaries are printed last.",
|
|
89
|
-
"- If the same approach fails twice, change the approach instead of retrying it.",
|
|
90
|
-
"- A claim without a passing command is not evidence. Finish with what changed and the command that verified it, or the specific blocker.",
|
|
91
|
-
];
|
|
92
|
-
if (has("bash")) work.push(
|
|
93
|
-
"- Each bash call is a fresh non-interactive shell with no TTY: use workdir or `cd dir && ...` within one call. Never open pagers or interactive editors. " +
|
|
94
|
-
"Start servers in the background with output redirected (`nohup cmd > app.log 2>&1 &`), then poll the log or port. Pass a larger timeout for long builds and installs.",
|
|
95
|
-
);
|
|
96
|
-
if (has("exec_command")) work.push("- For servers, REPLs and programs that prompt for input, use exec_command (tty=true when the program needs a terminal) and continue with write_stdin.");
|
|
97
|
-
if (has("edit")) work.push("- Prefer edit to rewriting a whole file; copy old_string from read output without the line numbers.");
|
|
98
|
-
|
|
99
74
|
return [
|
|
100
|
-
"You are
|
|
101
|
-
|
|
75
|
+
"You are an agent working in a terminal.",
|
|
76
|
+
"The declared tool schemas define available capabilities, input units and execution lifecycles. Do not assume additional tools or infer permission from a tool's visibility.",
|
|
102
77
|
...(env === undefined ? [] : [environmentSection(env)]),
|
|
103
|
-
|
|
104
|
-
|
|
78
|
+
taskGuidance(),
|
|
79
|
+
codingWorkingProtocol(toolNames),
|
|
105
80
|
"Be concise. Do not narrate what you are about to do; do it, then say what happened.",
|
|
106
81
|
].join("\n\n");
|
|
107
82
|
}
|
|
@@ -128,12 +103,12 @@ export function sessionEnvironment(workspace: string): SessionEnvironment {
|
|
|
128
103
|
* Session policy a CLI coding session differs from a benchmark run in.
|
|
129
104
|
* Benchmarks construct their own SessionConfig and keep the core defaults.
|
|
130
105
|
*/
|
|
131
|
-
export function cliSessionConfig(args: Args): { continuationAfter: number; timeBudgetMs?: number } {
|
|
106
|
+
export function cliSessionConfig(args: Args): { continuationAfter: number; workingSet: true; workingSetVersion: 2; guidanceVersion: 1; strictVerification: true; timeBudgetMs?: number } {
|
|
132
107
|
// Never within a request. A real Kimi K3 run with the reminder at reply six
|
|
133
108
|
// spent half its requests on notes after the work was already verified
|
|
134
109
|
// (fix-cli-continuation-noise). The reminder still fires where it earns its
|
|
135
110
|
// place: a resumed handoff that cannot continue, and boundary preparation.
|
|
136
|
-
return { continuationAfter: Infinity, ...(args.timeBudgetMs === undefined ? {} : { timeBudgetMs: args.timeBudgetMs }) };
|
|
111
|
+
return { continuationAfter: Infinity, workingSet: true, workingSetVersion: 2, guidanceVersion: 1, strictVerification: true, ...(args.timeBudgetMs === undefined ? {} : { timeBudgetMs: args.timeBudgetMs }) };
|
|
137
112
|
}
|
|
138
113
|
|
|
139
114
|
/**
|
|
@@ -151,6 +126,7 @@ export type AskSlot = {
|
|
|
151
126
|
};
|
|
152
127
|
|
|
153
128
|
export interface Args {
|
|
129
|
+
shellCheckpoints?: boolean;
|
|
154
130
|
workspace: string;
|
|
155
131
|
/**
|
|
156
132
|
* `--state-dir <dir>`: where every xeno write goes -- snapshots, tool
|
|
@@ -193,6 +169,9 @@ export interface Args {
|
|
|
193
169
|
systemPrompt: string | null;
|
|
194
170
|
/** `--tools` allowlist. Omitted when the flag was not passed. */
|
|
195
171
|
tools?: string[];
|
|
172
|
+
/** New sessions default to coding; restored context keeps its frozen schemas. */
|
|
173
|
+
toolProfile?: ToolProfile;
|
|
174
|
+
acceptanceFile?: string;
|
|
196
175
|
excludeTools: string[];
|
|
197
176
|
noTools: boolean;
|
|
198
177
|
/** `--no-builtin-tools`. False when the flag was not passed. */
|
|
@@ -245,6 +224,8 @@ export interface Args {
|
|
|
245
224
|
sessionName?: string;
|
|
246
225
|
/** `--mode json`. Empty / omitted when the flag was not passed. */
|
|
247
226
|
mode?: string;
|
|
227
|
+
/** Explicit local adapter endpoint. An owned mode-0700 directory is required. */
|
|
228
|
+
controlSocket?: string;
|
|
248
229
|
/** `--verbose`. Expands the startup header's counts into lists. */
|
|
249
230
|
verbose?: boolean;
|
|
250
231
|
}
|
|
@@ -258,6 +239,7 @@ export interface Args {
|
|
|
258
239
|
* parses; it just cannot be offered as a suggestion.
|
|
259
240
|
*/
|
|
260
241
|
const KNOWN_FLAGS: readonly string[] = [
|
|
242
|
+
"--checkpoint-shell",
|
|
261
243
|
"--workspace", "-w",
|
|
262
244
|
"--path-root",
|
|
263
245
|
"--state-dir",
|
|
@@ -283,6 +265,8 @@ const KNOWN_FLAGS: readonly string[] = [
|
|
|
283
265
|
"--system-prompt",
|
|
284
266
|
"--append-system-prompt",
|
|
285
267
|
"--tools", "-t",
|
|
268
|
+
"--tool-profile",
|
|
269
|
+
"--acceptance-file",
|
|
286
270
|
"--exclude-tools", "-xt",
|
|
287
271
|
"--no-tools", "-nt",
|
|
288
272
|
"--no-builtin-tools",
|
|
@@ -298,6 +282,7 @@ const KNOWN_FLAGS: readonly string[] = [
|
|
|
298
282
|
"--no-prompt-templates",
|
|
299
283
|
"--extension", "-e", "--no-extensions", "-ne", "--extension-flag",
|
|
300
284
|
"--mode",
|
|
285
|
+
"--control-socket",
|
|
301
286
|
"--verbose",
|
|
302
287
|
"--print", "-p",
|
|
303
288
|
"--help", "-h",
|
|
@@ -374,7 +359,8 @@ export function parseArgs(argv: string[], options: { deferExtensions?: boolean;
|
|
|
374
359
|
console.error(`${a} requires a value`);
|
|
375
360
|
process.exit(1);
|
|
376
361
|
}
|
|
377
|
-
if (a === "--
|
|
362
|
+
if (a === "--checkpoint-shell") args.shellCheckpoints = true;
|
|
363
|
+
else if (a === "--workspace" || a === "-w") args.workspace = argv[++i] ?? args.workspace;
|
|
378
364
|
else if (a === "--path-root") args.pathRoot = resolve(argv[++i] ?? "/");
|
|
379
365
|
else if (a === "--state-dir") args.stateDir = resolve(argv[++i] ?? ".");
|
|
380
366
|
else if (a === "--time-budget") {
|
|
@@ -429,6 +415,15 @@ export function parseArgs(argv: string[], options: { deferExtensions?: boolean;
|
|
|
429
415
|
else if (a === "--system-prompt") args.systemPrompt = argv[++i] ?? "";
|
|
430
416
|
else if (a === "--append-system-prompt") args.appendSystemPrompt.push(argv[++i] ?? "");
|
|
431
417
|
else if (a === "--tools" || a === "-t") args.tools = parseToolList(argv[++i] ?? "");
|
|
418
|
+
else if (a === "--acceptance-file") {
|
|
419
|
+
const path = argv[++i]; if (!path || path.startsWith("--")) throw new Error("--acceptance-file requires a JSON path");
|
|
420
|
+
args.acceptanceFile = path;
|
|
421
|
+
}
|
|
422
|
+
else if (a === "--tool-profile") {
|
|
423
|
+
const value = argv[++i] ?? "";
|
|
424
|
+
if (!isToolProfile(value)) throw new Error("unknown tool profile; choose coding, readonly, research, data or full");
|
|
425
|
+
args.toolProfile = value;
|
|
426
|
+
}
|
|
432
427
|
else if (a === "--exclude-tools" || a === "-xt") args.excludeTools = parseToolList(argv[++i] ?? "");
|
|
433
428
|
else if (a === "--no-tools" || a === "-nt") args.noTools = true;
|
|
434
429
|
else if (a === "--no-builtin-tools" || a === "-nbt") args.noBuiltinTools = true;
|
|
@@ -459,6 +454,11 @@ export function parseArgs(argv: string[], options: { deferExtensions?: boolean;
|
|
|
459
454
|
(args.extensionFlags ??= Object.create(null) as Record<string, string | boolean>)[value.slice(0, at)] = value.slice(at + 1) === "true" ? true : value.slice(at + 1) === "false" ? false : value.slice(at + 1);
|
|
460
455
|
}
|
|
461
456
|
else if (a === "--mode") args.mode = argv[++i] ?? "";
|
|
457
|
+
else if (a === "--control-socket") {
|
|
458
|
+
const value = argv[++i];
|
|
459
|
+
if (!value || value.startsWith("-")) throw Error("--control-socket requires a path");
|
|
460
|
+
args.controlSocket = resolve(value);
|
|
461
|
+
}
|
|
462
462
|
else if (a === "--verbose") args.verbose = true;
|
|
463
463
|
else if (a === "-p" || a === "--print") {
|
|
464
464
|
args.print = true;
|
|
@@ -598,16 +598,17 @@ export function formatStartupHeader(input: {
|
|
|
598
598
|
`workspace ${input.workspace}`,
|
|
599
599
|
`session ${input.session}`,
|
|
600
600
|
];
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
601
|
+
const groups: [string, readonly string[]][] = [["agents", input.agents], ["skills", input.skills], ["prompts", input.prompts],
|
|
602
|
+
...(input.tools !== undefined ? [["tools", input.tools] as [string, readonly string[]]] : [])];
|
|
603
|
+
if (input.verbose) {
|
|
604
|
+
for (const [label, items] of groups) {
|
|
605
|
+
lines.push(`${label.padEnd(10)}${items.length}`);
|
|
604
606
|
for (const item of items) lines.push(` ${item}`);
|
|
605
607
|
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
if (input.tools !== undefined) pushCount("tools", input.tools);
|
|
608
|
+
} else {
|
|
609
|
+
// Four count rows were a third of the banner; one row says the same.
|
|
610
|
+
lines.push(`loaded ${groups.map(([label, items]) => `${items.length} ${label}`).join(" · ")}`);
|
|
611
|
+
}
|
|
611
612
|
const keys = formatShortcutLine(input.keymap);
|
|
612
613
|
if (keys.length > 0) lines.push(`keys ${keys}`);
|
|
613
614
|
return lines;
|
|
@@ -720,7 +721,7 @@ export interface App {
|
|
|
720
721
|
blobDir: string;
|
|
721
722
|
system: string;
|
|
722
723
|
providerConfig: ProviderConfig;
|
|
723
|
-
/** May be `{ ok: false }` in an interactive session waiting on /
|
|
724
|
+
/** May be `{ ok: false }` in an interactive session waiting on /provider. */
|
|
724
725
|
credential: Resolved;
|
|
725
726
|
auth: AuthFile;
|
|
726
727
|
effects: ResolveEffects;
|
|
@@ -780,8 +781,8 @@ export function whyNot(spec: ModelSpec, got: Resolved, auth: AuthFile, C: Palett
|
|
|
780
781
|
}
|
|
781
782
|
|
|
782
783
|
/**
|
|
783
|
-
* /
|
|
784
|
-
* cannot answer it, so those runs must still refuse -- naming /
|
|
784
|
+
* /provider only exists inside a live session. A one-shot prompt and a pipe
|
|
785
|
+
* cannot answer it, so those runs must still refuse -- naming /provider there
|
|
785
786
|
* is how this file used to deadlock a first run.
|
|
786
787
|
*/
|
|
787
788
|
/**
|
|
@@ -799,7 +800,7 @@ export function isInteractiveSession(args: Args): boolean {
|
|
|
799
800
|
|
|
800
801
|
/**
|
|
801
802
|
* Script-facing missing-key text. Environment variables and --api-key are
|
|
802
|
-
* the routes that actually work without a tty; /
|
|
803
|
+
* the routes that actually work without a tty; /provider is not one of them.
|
|
803
804
|
*/
|
|
804
805
|
export function describeUnauthenticatedScript(catalog: readonly ModelSpec[] = CATALOG): string {
|
|
805
806
|
const providers = [...new Set(catalog.map((m) => m.provider))];
|
|
@@ -810,13 +811,14 @@ export function describeUnauthenticatedScript(catalog: readonly ModelSpec[] = CA
|
|
|
810
811
|
);
|
|
811
812
|
}
|
|
812
813
|
|
|
813
|
-
/**
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
814
|
+
/**
|
|
815
|
+
* Banner / hint for an interactive session that started without a key.
|
|
816
|
+
* Listing every provider's variable was a 20-line wall that buried the one
|
|
817
|
+
* action that works from here; the interactive routes are named instead.
|
|
818
|
+
*/
|
|
819
|
+
export function describeUnauthenticatedSession(_catalog: readonly ModelSpec[] = CATALOG): string {
|
|
820
|
+
return "no provider connected yet. Type /provider to connect one — a Claude, ChatGPT, Copilot or Grok account, an API key, a gateway or a local server — " +
|
|
821
|
+
`or export a key such as ${["anthropic", "openai", "google", "deepseek"].map(provider => envLabel(provider).split(" or ")[0]).join(", ")}.`;
|
|
820
822
|
}
|
|
821
823
|
|
|
822
824
|
export function retryHandler(io: AppIO): (attempt: number, delayMs: number, error: unknown) => void {
|
|
@@ -833,6 +835,7 @@ export function retryHandler(io: AppIO): (attempt: number, delayMs: number, erro
|
|
|
833
835
|
|
|
834
836
|
export function toolSelectionOf(args: Args) {
|
|
835
837
|
return {
|
|
838
|
+
profile: args.toolProfile ?? "coding",
|
|
836
839
|
...(args.noTools ? { none: true as const } : {}),
|
|
837
840
|
...(args.tools !== undefined ? { only: args.tools } : {}),
|
|
838
841
|
...(args.excludeTools.length > 0 ? { exclude: args.excludeTools } : {}),
|
|
@@ -843,25 +846,40 @@ export function toolSelectionOf(args: Args) {
|
|
|
843
846
|
/**
|
|
844
847
|
* Schema assembly: every tool is tagged with a source here so
|
|
845
848
|
* `--no-builtin-tools` can drop a category without a later cross-layer
|
|
846
|
-
* recut.
|
|
847
|
-
*
|
|
849
|
+
* recut. Optional extension descriptors remain local until selected; their
|
|
850
|
+
* harness-owned discovery entry belongs to that extension-enabled capability set.
|
|
848
851
|
*
|
|
849
852
|
* `source` is stripped before the frozen prefix: the wire schema is what
|
|
850
853
|
* the provider bills, and an extra field there would look like a tool
|
|
851
854
|
* argument.
|
|
852
855
|
*/
|
|
853
|
-
export function
|
|
856
|
+
export function sessionToolCatalog(args: Args, extensions?: ExtensionRegistry, previous: readonly unknown[] = []) {
|
|
854
857
|
const custom = [...extensions?.definitions().values() ?? []].map(({ name, description, input_schema }) => ({ name, description, input_schema, source: "extension" as const }));
|
|
855
|
-
const available = [...
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
858
|
+
const available = [...TOOL_DEFINITIONS.filter(tool => !extensions?.tools.has(tool.name) && (!tool.optional || args.tools?.includes(tool.name))).map((t) => ({ ...t, source: "builtin" as const })), ...custom];
|
|
859
|
+
const selected = selectTools(available, toolSelectionOf(args));
|
|
860
|
+
const deferred = selected.filter(tool => tool.source === "extension" && extensions?.tools.get(tool.name)?.exposure === "deferred")
|
|
861
|
+
.map(({ name, description, input_schema }) => ({ name, description, input_schema }));
|
|
862
|
+
const deferredNames = new Set(deferred.map(tool => tool.name));
|
|
863
|
+
const active = new Set([...declaredToolNames(previous), ...args.tools ?? []]);
|
|
864
|
+
const visible = selected.filter(tool => (!deferredNames.has(tool.name) || active.has(tool.name)) &&
|
|
865
|
+
(tool.name !== DISCOVERY_TOOL_NAME || deferred.length > 0 || args.tools?.includes(DISCOVERY_TOOL_NAME)));
|
|
866
|
+
// A cold open must not reorder an already accepted declaration and invent a checkpoint.
|
|
867
|
+
const order = new Map(declaredToolNames(previous).map((name, index) => [name, index]));
|
|
868
|
+
visible.sort((a, b) => (order.get(a.name) ?? Infinity) - (order.get(b.name) ?? Infinity));
|
|
869
|
+
const builtins = visible.filter(tool => tool.source === "builtin").map(tool => tool.name);
|
|
870
|
+
const schemas = visible.map(tool => tool.source === "builtin" ? modelToolSchema(tool, builtins)
|
|
871
|
+
: { name: tool.name, description: tool.description, input_schema: tool.input_schema });
|
|
872
|
+
// D6: extension authors retain their registration objects and SDK catalog views.
|
|
873
|
+
// Neither may mutate the already frozen provider declaration through an alias.
|
|
874
|
+
return { schemas: JSON.parse(JSON.stringify(schemas)) as typeof schemas,
|
|
875
|
+
deferred: JSON.parse(JSON.stringify(deferred)) as typeof deferred };
|
|
876
|
+
}
|
|
877
|
+
export function visibleSchemas(args: Args, extensions?: ExtensionRegistry, previous: readonly unknown[] = []) {
|
|
878
|
+
return sessionToolCatalog(args, extensions, previous).schemas;
|
|
861
879
|
}
|
|
862
880
|
|
|
863
881
|
export function unknownToolNames(names: readonly string[]): string[] {
|
|
864
|
-
const known = new Set(
|
|
882
|
+
const known = new Set(TOOL_DEFINITIONS.map((t) => t.name));
|
|
865
883
|
return names.filter((n) => !known.has(n));
|
|
866
884
|
}
|
|
867
885
|
|
|
@@ -878,9 +896,11 @@ export function createSessionFor(opts: {
|
|
|
878
896
|
oauth?: boolean;
|
|
879
897
|
effects?: ResolveEffects;
|
|
880
898
|
extensions?: ExtensionRegistry;
|
|
899
|
+
acceptanceContract?: AcceptanceContract;
|
|
900
|
+
skills?: readonly Skill[];
|
|
881
901
|
}): Session {
|
|
882
902
|
const { args, workspace, logDir, blobDir, system, providerConfig, credential, io, askSlot } = opts;
|
|
883
|
-
const schemas =
|
|
903
|
+
const { schemas, deferred } = sessionToolCatalog(args, opts.extensions);
|
|
884
904
|
const names = schemas.map((t) => t.name);
|
|
885
905
|
const sessionRules: PermissionRule[] = [];
|
|
886
906
|
const tools: ToolContext = {
|
|
@@ -888,6 +908,7 @@ export function createSessionFor(opts: {
|
|
|
888
908
|
...DEFAULT_TOOL_CONTEXT,
|
|
889
909
|
...(args.pathRoot === undefined ? {} : { pathRoot: args.pathRoot }),
|
|
890
910
|
allow: names,
|
|
911
|
+
deferredTools: deferred,
|
|
891
912
|
rules: rulesWithAllowTools(args.allowTools),
|
|
892
913
|
sessionRules,
|
|
893
914
|
askSlot,
|
|
@@ -898,9 +919,12 @@ export function createSessionFor(opts: {
|
|
|
898
919
|
logDir,
|
|
899
920
|
frozen: { system, tools: schemas },
|
|
900
921
|
blobDir,
|
|
922
|
+
shellCheckpoints: args.shellCheckpoints === true,
|
|
901
923
|
tools,
|
|
902
924
|
maxTurns: args.maxTurns,
|
|
903
925
|
...cliSessionConfig(args),
|
|
926
|
+
...(opts.skills ? { skills: opts.skills } : {}),
|
|
927
|
+
...(opts.acceptanceContract ? { acceptanceContract: opts.acceptanceContract } : {}),
|
|
904
928
|
// Explicit legacy thresholds still select that experiment unless overridden.
|
|
905
929
|
contextPolicy: args.contextPolicy ?? (args.trigger > 0 ? "segmented" : DEFAULT_CONTEXT_POLICY),
|
|
906
930
|
...(args.trigger > 0
|
|
@@ -917,13 +941,13 @@ export function createSessionFor(opts: {
|
|
|
917
941
|
: {}),
|
|
918
942
|
},
|
|
919
943
|
// Empty key is a placeholder the shell must not send through. Interactive
|
|
920
|
-
// startup reaches this path so /
|
|
944
|
+
// startup reaches this path so /provider exists; run() refuses until a real
|
|
921
945
|
// credential is installed on the session.
|
|
922
946
|
createProvider(
|
|
923
947
|
providerConfig,
|
|
924
948
|
credential.ok ? credential.key : "",
|
|
925
949
|
retryHandler(io),
|
|
926
|
-
{ ...(opts.oauth === true ? { oauth: true } : {}), ...(opts.effects ? { effects: opts.effects } : {}) },
|
|
950
|
+
{ sessionId: providerSessionId(logDir), ...(opts.oauth === true ? { oauth: true } : {}), ...(opts.effects ? { effects: opts.effects } : {}) },
|
|
927
951
|
),
|
|
928
952
|
systemClock(),
|
|
929
953
|
);
|
|
@@ -984,8 +1008,9 @@ export async function loadSessionResources(args: Args, workspace: string, io: Ap
|
|
|
984
1008
|
for (const line of new Set([...loaded.diagnostics, ...text.diagnostics, ...slash.diagnostics])) io.say(io.palette.yellow(` ${line}`));
|
|
985
1009
|
const system = appendSkillsToSystem(assembleSystemPrompt(systemPromptFor(visibleSchemas(args, extensions).map(tool => tool.name), sessionEnvironment(workspace)), loadedContext), text.skills);
|
|
986
1010
|
const ui = toUiState(loaded, colorEnabled());
|
|
1011
|
+
// Also reached on /resume and /reload: `auto` is resolved once per process by the Tui (apps/shell.ts), not here.
|
|
987
1012
|
if (args.tuiMode) ui.settings = { ...ui.settings, tuiMode: args.tuiMode };
|
|
988
|
-
return { loadedContext, slash: { ...slash.catalog, skillCommands: loaded.settings.enableSkillCommands !== false }, ui, index, trusted, system, extensions };
|
|
1013
|
+
return { loadedContext, slash: { ...slash.catalog, skillExecution: true, skillCommands: loaded.settings.enableSkillCommands !== false }, ui, index, trusted, system, extensions };
|
|
989
1014
|
}
|
|
990
1015
|
|
|
991
1016
|
/**
|
|
@@ -1011,7 +1036,7 @@ export async function openSessionFor(opts: OpenSessionOptions): Promise<{
|
|
|
1011
1036
|
: await loadSessionResources(opts.args, workspace, opts.io, workspace === opts.workspace ? opts.projectTrusted : undefined);
|
|
1012
1037
|
const interactive = opts.interactive && !opts.args.print && !opts.args.prompt && opts.args.mode !== "json";
|
|
1013
1038
|
const named = [...opts.args.tools ?? [], ...opts.args.excludeTools, ...opts.args.allowTools];
|
|
1014
|
-
const unknown = unknownToolNames(named).filter(name => !resources.extensions.
|
|
1039
|
+
const unknown = unknownToolNames(named).filter(name => !resources.extensions.definitions().has(name));
|
|
1015
1040
|
if (unknown.length) throw new Error(`unknown tool: ${unknown.join(", ")}`);
|
|
1016
1041
|
const saved = priorCursor === null ? undefined : restoreSessionModel(priorCursor, opts.catalog);
|
|
1017
1042
|
const flags = { ...opts.modelFlags ?? { model: "", provider: "" } };
|
|
@@ -1054,10 +1079,14 @@ export async function openSessionFor(opts: OpenSessionOptions): Promise<{
|
|
|
1054
1079
|
const restoring = saved ?? spec;
|
|
1055
1080
|
const restoringCredential = saved === undefined ? credential : opts.credentialOf(restoring);
|
|
1056
1081
|
const oauth = (model: ModelSpec, got: Resolved) => got.ok && storedIsOAuth(opts.auth, model.provider) && opts.args.apiKey.length === 0;
|
|
1082
|
+
const acceptanceContract = priorCursor === null
|
|
1083
|
+
? await loadAcceptanceContract(workspace, resources.trusted, opts.args.acceptanceFile) : undefined;
|
|
1057
1084
|
const session = createSessionFor({
|
|
1058
1085
|
...opts,
|
|
1059
1086
|
system: resources.system,
|
|
1060
1087
|
extensions: resources.extensions,
|
|
1088
|
+
skills: [...resources.slash.skills.values()],
|
|
1089
|
+
...(acceptanceContract ? { acceptanceContract } : {}),
|
|
1061
1090
|
workspace: priorCursor?.workspace ?? opts.workspace,
|
|
1062
1091
|
blobDir: priorCursor?.blob_dir ?? (priorCursor?.workspace && priorCursor.workspace !== opts.workspace
|
|
1063
1092
|
? statePaths(priorCursor.workspace, opts.args.stateDir).blobs : opts.blobDir),
|
|
@@ -1065,15 +1094,16 @@ export async function openSessionFor(opts: OpenSessionOptions): Promise<{
|
|
|
1065
1094
|
credential: restoringCredential,
|
|
1066
1095
|
...(oauth(restoring, restoringCredential) ? { oauth: true } : {}),
|
|
1067
1096
|
});
|
|
1068
|
-
opts.startupPhase?.("
|
|
1097
|
+
opts.startupPhase?.("Opening session history");
|
|
1069
1098
|
await session.start();
|
|
1099
|
+
resources.slash = { ...resources.slash, skills: new Map((session.config.skills ?? []).map(skill => [skill.name, skill])) };
|
|
1070
1100
|
if (resources.extensions.loaded.length || session.config.frozen.tools.some(tool =>
|
|
1071
1101
|
typeof tool === "object" && tool !== null && "name" in tool && typeof tool.name === "string" && unknownToolNames([tool.name]).length)) {
|
|
1072
|
-
await session.reloadResources({ system: resources.system, tools: visibleSchemas(opts.args, resources.extensions) });
|
|
1102
|
+
await session.reloadResources({ system: resources.system, tools: visibleSchemas(opts.args, resources.extensions, session.config.frozen.tools) });
|
|
1073
1103
|
}
|
|
1074
1104
|
if (restoring.id !== spec.id || restoring.provider !== spec.provider) {
|
|
1075
1105
|
await session.switchModel(createProvider(providerConfig, credential.ok ? credential.key : "", retryHandler(opts.io),
|
|
1076
|
-
{ ...(oauth(spec, credential) ? { oauth: true } : {}), ...(opts.effects ? { effects: opts.effects } : {}) }));
|
|
1106
|
+
{ sessionId: providerSessionId(session.config.logDir), ...(oauth(spec, credential) ? { oauth: true } : {}), ...(opts.effects ? { effects: opts.effects } : {}) }));
|
|
1077
1107
|
opts.io.say(opts.io.palette.yellow(` switched ${restoring.id} → ${spec.id} at a model checkpoint`));
|
|
1078
1108
|
}
|
|
1079
1109
|
resources.ui.settings = { ...resources.ui.settings, thinking: providerConfig.effort,
|
|
@@ -1253,7 +1283,7 @@ export async function startup(args: Args, input?: StartupInput): Promise<App> {
|
|
|
1253
1283
|
if (printMode || jsonMode || !stdinIsTty() || !stdoutIsTty()) {
|
|
1254
1284
|
throw new Error("--resume needs an interactive terminal. Pass -r <id> or --session <id> instead");
|
|
1255
1285
|
}
|
|
1256
|
-
const picked = await withTerminal(handoff => pickStartupSession({ workspace,
|
|
1286
|
+
const picked = await withTerminal(handoff => pickStartupSession({ workspace, theme: ui.theme, say: io.say, keymap: ui.keymap,
|
|
1257
1287
|
...(sessionDir === undefined ? {} : { sessionDir }) }, handoff));
|
|
1258
1288
|
if (picked === null) { io.say(C.dim("No session selected")); process.exit(0); }
|
|
1259
1289
|
sessionOpts = { explicit: picked };
|
|
@@ -1321,6 +1351,21 @@ export async function startup(args: Args, input?: StartupInput): Promise<App> {
|
|
|
1321
1351
|
};
|
|
1322
1352
|
}
|
|
1323
1353
|
|
|
1354
|
+
/** The banner's words for the shared limits; the loop acts at exactly these numbers. */
|
|
1355
|
+
export function boundaryDescription(policy: ContextPolicy, limits: ContextLimits): string {
|
|
1356
|
+
switch (limits.kind) {
|
|
1357
|
+
case "stop":
|
|
1358
|
+
case "open":
|
|
1359
|
+
return "continuous replay; stops at model window";
|
|
1360
|
+
case "window":
|
|
1361
|
+
return `windowed replay; prepare ${limits.prepare}, cut ${limits.trigger}, output reserved`;
|
|
1362
|
+
case "segment":
|
|
1363
|
+
return `${policy}${policy === "windowed" ? " (unknown window; fixed fallback)" : ""}; cut ${limits.trigger}, hard ${limits.hard}`;
|
|
1364
|
+
default:
|
|
1365
|
+
return assertNever(limits, "boundaryDescription");
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1324
1369
|
function printBanner(
|
|
1325
1370
|
args: Args,
|
|
1326
1371
|
io: AppIO,
|
|
@@ -1370,13 +1415,14 @@ function printBanner(
|
|
|
1370
1415
|
const line = formatNotice(notice);
|
|
1371
1416
|
io.say(notice.kind === "skipped" ? C.yellow(` ${line}`) : C.dim(` ${line}`));
|
|
1372
1417
|
}
|
|
1373
|
-
const { triggerTokens: trigger, hardTokens: hard } = { ...DEFAULT_BOUNDARY, ...session.config.boundary };
|
|
1374
1418
|
const policy = session.config.contextPolicy ?? DEFAULT_CONTEXT_POLICY;
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
:
|
|
1378
|
-
:
|
|
1379
|
-
|
|
1419
|
+
const limits = contextLimits({
|
|
1420
|
+
policy,
|
|
1421
|
+
contextWindow: session.provider.config.spec.contextWindow,
|
|
1422
|
+
outputReserve: session.provider.config.maxTokens,
|
|
1423
|
+
...(session.config.boundary ? { boundary: session.config.boundary } : {}),
|
|
1424
|
+
});
|
|
1425
|
+
io.say(C.dim(`boundary ${boundaryDescription(policy, limits)} (estimated tokens)`));
|
|
1380
1426
|
if (priorCursor !== null) {
|
|
1381
1427
|
const events = session.state.events.length;
|
|
1382
1428
|
io.say(
|
|
@@ -1405,6 +1451,9 @@ export function printCliHelp(flags?: ReadonlyMap<string, ExtensionFlag>): void {
|
|
|
1405
1451
|
" [--system-prompt <text-or-file>] [--append-system-prompt <text>]\n" +
|
|
1406
1452
|
" [--no-context-files|-nc]\n" +
|
|
1407
1453
|
" [--tools names] [--exclude-tools names] [--no-tools]\n" +
|
|
1454
|
+
" [--tool-profile coding|readonly|research|data|full]\n" +
|
|
1455
|
+
" [--acceptance-file contract.json]\n" +
|
|
1456
|
+
" [--control-socket /private/directory/control.sock]\n" +
|
|
1408
1457
|
" [--no-builtin-tools]\n" +
|
|
1409
1458
|
" [--allow-tools names] [--approve|--no-approve]\n" +
|
|
1410
1459
|
" [--theme <name|path>] [--use-theme name] [--no-themes] [--thinking level] [--models patterns]\n" +
|
|
@@ -1413,6 +1462,10 @@ export function printCliHelp(flags?: ReadonlyMap<string, ExtensionFlag>): void {
|
|
|
1413
1462
|
" [--extension <path>] [--no-extensions] [--extension-flag name=value]\n" +
|
|
1414
1463
|
" [--mode text|json] [--tui-mode regular|fullscreen] [--verbose]\n" +
|
|
1415
1464
|
" [-p <prompt>] [prompt...]\n\n" +
|
|
1465
|
+
" xeno app-server [session/model flags] runs the experimental JSONL control interface.\n" +
|
|
1466
|
+
" xeno app-server schema prints its versioned request schema without startup.\n" +
|
|
1467
|
+
" --control-socket attaches local adapters to the same live session as the TUI.\n" +
|
|
1468
|
+
" /control opens goal, progress and context panels; XENO_PROGRAM_STATUS=auto|1|0 controls terminal status.\n\n" +
|
|
1416
1469
|
" Starts a new session by default; --fresh is an explicit alias. Nothing is deleted.\n" +
|
|
1417
1470
|
" -c continues the newest (or starts one); -r opens the session picker.\n" +
|
|
1418
1471
|
" --session / -r <id> pick one (unique prefix only). Resume restores its model;\n" +
|
|
@@ -1426,10 +1479,12 @@ export function printCliHelp(flags?: ReadonlyMap<string, ExtensionFlag>): void {
|
|
|
1426
1479
|
" --system-prompt replaces the built-in instructions (text, or a file that exists).\n" +
|
|
1427
1480
|
" --append-system-prompt appends; repeatable. -nc skips AGENTS.md discovery.\n" +
|
|
1428
1481
|
" --tools is a strict allowlist; excluded tools leave the schema.\n" +
|
|
1482
|
+
" New sessions use coding (10 tools); full exposes default built-ins.\n" +
|
|
1429
1483
|
" --no-builtin-tools drops built-ins and keeps any other source; --tools still wins.\n" +
|
|
1430
1484
|
" bash asks before it runs. --allow-tools bash skips the ask for this process.\n" +
|
|
1431
1485
|
" --time-budget <seconds> tells the model, once, when 75% of the run's wall-clock budget is used.\n" +
|
|
1432
1486
|
" --state-dir <dir> keeps snapshots, observations and sessions out of the workspace.\n" +
|
|
1487
|
+
" --checkpoint-shell enables best-effort Git recovery for command changes; ignored files and external effects are excluded.\n" +
|
|
1433
1488
|
" --path-root <dir> lets file tools reach absolute paths under <dir> (e.g. / in a container);\n" +
|
|
1434
1489
|
" relative paths, fingerprints and /back stay in the workspace, so edits outside it are not rewound.\n" +
|
|
1435
1490
|
" --approve loads .xeno/settings.json this run; --no-approve skips it.\n" +
|