@visual-z/xeno 0.2.0 → 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 +15 -0
- package/README.md +106 -26
- package/docs/install.md +52 -9
- package/docs/providers.md +11 -0
- package/package.json +5 -4
- package/src/apps/approve.ts +3 -3
- package/src/apps/cli.ts +10 -11
- 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 +130 -0
- package/src/apps/startup.ts +143 -115
- 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/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,
|
|
@@ -14,6 +15,8 @@ import { Session, systemClock } from "../runtime/loop.ts";
|
|
|
14
15
|
import { DEFAULT_PROVIDER, type ProviderConfig } from "../runtime/provider.ts";
|
|
15
16
|
import { createProvider, 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,69 +42,41 @@ 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";
|
|
65
|
+
import type { StartupInput } from "./startup-input.ts";
|
|
60
66
|
|
|
61
67
|
export const SYSTEM_PROMPT = systemPromptFor(TOOL_SCHEMAS.map((tool) => tool.name));
|
|
62
68
|
|
|
63
69
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* 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.
|
|
67
72
|
*/
|
|
68
73
|
export function systemPromptFor(toolNames: readonly string[], env?: SessionEnvironment): string {
|
|
69
|
-
const has = (name: string) => toolNames.includes(name);
|
|
70
|
-
const toolsLine = toolNames.length === 0
|
|
71
|
-
? "No tools are available in this session."
|
|
72
|
-
: `Tools: ${toolNames.join(", ")}.` +
|
|
73
|
-
(toolNames.some(name => ["grep", "find", "ls"].includes(name)) ? " Use the available grep/find/ls tools for confined search." : "") +
|
|
74
|
-
(has("bash")
|
|
75
|
-
? " Use bash for verification commands and operations the other tools do not cover."
|
|
76
|
-
: "");
|
|
77
|
-
|
|
78
|
-
// Each line answers a failure class in the Terminal-Bench taxonomy
|
|
79
|
-
// (arXiv 2601.11868): premature termination, no verification, step
|
|
80
|
-
// repetition. The wording of the first and last follows the Codex
|
|
81
|
-
// prompting guide: persist end to end, and end on a change or a blocker.
|
|
82
|
-
const work = [
|
|
83
|
-
"How to work:",
|
|
84
|
-
"- 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.",
|
|
85
|
-
"- Understand before changing: read the relevant code, and for a bug, reproduce it first.",
|
|
86
|
-
"- Put independent reads and searches in one response; they run in parallel.",
|
|
87
|
-
"- 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.",
|
|
88
|
-
"- If the same approach fails twice, change the approach instead of retrying it.",
|
|
89
|
-
"- A claim without a passing command is not evidence. Finish with what changed and the command that verified it, or the specific blocker.",
|
|
90
|
-
];
|
|
91
|
-
if (has("bash")) work.push(
|
|
92
|
-
"- 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. " +
|
|
93
|
-
"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.",
|
|
94
|
-
);
|
|
95
|
-
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.");
|
|
96
|
-
if (has("edit")) work.push("- Prefer edit to rewriting a whole file; copy old_string from read output without the line numbers.");
|
|
97
|
-
|
|
98
74
|
return [
|
|
99
|
-
"You are
|
|
100
|
-
|
|
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.",
|
|
101
77
|
...(env === undefined ? [] : [environmentSection(env)]),
|
|
102
|
-
|
|
103
|
-
|
|
78
|
+
taskGuidance(),
|
|
79
|
+
codingWorkingProtocol(toolNames),
|
|
104
80
|
"Be concise. Do not narrate what you are about to do; do it, then say what happened.",
|
|
105
81
|
].join("\n\n");
|
|
106
82
|
}
|
|
@@ -127,12 +103,12 @@ export function sessionEnvironment(workspace: string): SessionEnvironment {
|
|
|
127
103
|
* Session policy a CLI coding session differs from a benchmark run in.
|
|
128
104
|
* Benchmarks construct their own SessionConfig and keep the core defaults.
|
|
129
105
|
*/
|
|
130
|
-
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 } {
|
|
131
107
|
// Never within a request. A real Kimi K3 run with the reminder at reply six
|
|
132
108
|
// spent half its requests on notes after the work was already verified
|
|
133
109
|
// (fix-cli-continuation-noise). The reminder still fires where it earns its
|
|
134
110
|
// place: a resumed handoff that cannot continue, and boundary preparation.
|
|
135
|
-
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 }) };
|
|
136
112
|
}
|
|
137
113
|
|
|
138
114
|
/**
|
|
@@ -150,6 +126,7 @@ export type AskSlot = {
|
|
|
150
126
|
};
|
|
151
127
|
|
|
152
128
|
export interface Args {
|
|
129
|
+
shellCheckpoints?: boolean;
|
|
153
130
|
workspace: string;
|
|
154
131
|
/**
|
|
155
132
|
* `--state-dir <dir>`: where every xeno write goes -- snapshots, tool
|
|
@@ -192,6 +169,9 @@ export interface Args {
|
|
|
192
169
|
systemPrompt: string | null;
|
|
193
170
|
/** `--tools` allowlist. Omitted when the flag was not passed. */
|
|
194
171
|
tools?: string[];
|
|
172
|
+
/** New sessions default to coding; restored context keeps its frozen schemas. */
|
|
173
|
+
toolProfile?: ToolProfile;
|
|
174
|
+
acceptanceFile?: string;
|
|
195
175
|
excludeTools: string[];
|
|
196
176
|
noTools: boolean;
|
|
197
177
|
/** `--no-builtin-tools`. False when the flag was not passed. */
|
|
@@ -257,6 +237,7 @@ export interface Args {
|
|
|
257
237
|
* parses; it just cannot be offered as a suggestion.
|
|
258
238
|
*/
|
|
259
239
|
const KNOWN_FLAGS: readonly string[] = [
|
|
240
|
+
"--checkpoint-shell",
|
|
260
241
|
"--workspace", "-w",
|
|
261
242
|
"--path-root",
|
|
262
243
|
"--state-dir",
|
|
@@ -282,6 +263,8 @@ const KNOWN_FLAGS: readonly string[] = [
|
|
|
282
263
|
"--system-prompt",
|
|
283
264
|
"--append-system-prompt",
|
|
284
265
|
"--tools", "-t",
|
|
266
|
+
"--tool-profile",
|
|
267
|
+
"--acceptance-file",
|
|
285
268
|
"--exclude-tools", "-xt",
|
|
286
269
|
"--no-tools", "-nt",
|
|
287
270
|
"--no-builtin-tools",
|
|
@@ -346,32 +329,6 @@ function rejectUnknownFlag(flag: string): never {
|
|
|
346
329
|
process.exit(1);
|
|
347
330
|
}
|
|
348
331
|
|
|
349
|
-
/**
|
|
350
|
-
* Stop the terminal echoing before startup does anything slow.
|
|
351
|
-
*
|
|
352
|
-
* Reading auth, scanning context files and printing the header all happen
|
|
353
|
-
* before the TUI attaches. In cooked mode the tty echoes whatever is typed
|
|
354
|
-
* during that window, so keystrokes land *between* the banner's own lines
|
|
355
|
-
* and look like output the program produced. Raw mode is the TUI's anyway;
|
|
356
|
-
* taking it at the first instruction just closes the gap.
|
|
357
|
-
*
|
|
358
|
-
* `stdin` is a parameter so the decision is testable without a real tty.
|
|
359
|
-
* Returns an undo so a caller that bails out can put the terminal back.
|
|
360
|
-
*/
|
|
361
|
-
export function silenceTtyEarly(opts: {
|
|
362
|
-
interactive: boolean;
|
|
363
|
-
stdin?: Pick<NodeJS.ReadStream, "isTTY" | "setRawMode" | "resume">;
|
|
364
|
-
}): () => void {
|
|
365
|
-
const stdin = opts.stdin ?? process.stdin;
|
|
366
|
-
if (!opts.interactive || stdin.isTTY !== true) return () => {};
|
|
367
|
-
stdin.setRawMode(true);
|
|
368
|
-
// Leave input buffered until ProcessTerminal installs its listener. Resuming
|
|
369
|
-
// here drops keystrokes typed while startup or transcript loading awaits I/O.
|
|
370
|
-
return () => {
|
|
371
|
-
if (stdin.isTTY === true) stdin.setRawMode(false);
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
332
|
export function parseArgs(argv: string[], options: { deferExtensions?: boolean; flags?: ReadonlyMap<string, ExtensionFlag> } = {}): Args {
|
|
376
333
|
let selectedTheme: string | undefined;
|
|
377
334
|
const args: Args = {
|
|
@@ -399,7 +356,8 @@ export function parseArgs(argv: string[], options: { deferExtensions?: boolean;
|
|
|
399
356
|
console.error(`${a} requires a value`);
|
|
400
357
|
process.exit(1);
|
|
401
358
|
}
|
|
402
|
-
if (a === "--
|
|
359
|
+
if (a === "--checkpoint-shell") args.shellCheckpoints = true;
|
|
360
|
+
else if (a === "--workspace" || a === "-w") args.workspace = argv[++i] ?? args.workspace;
|
|
403
361
|
else if (a === "--path-root") args.pathRoot = resolve(argv[++i] ?? "/");
|
|
404
362
|
else if (a === "--state-dir") args.stateDir = resolve(argv[++i] ?? ".");
|
|
405
363
|
else if (a === "--time-budget") {
|
|
@@ -454,6 +412,15 @@ export function parseArgs(argv: string[], options: { deferExtensions?: boolean;
|
|
|
454
412
|
else if (a === "--system-prompt") args.systemPrompt = argv[++i] ?? "";
|
|
455
413
|
else if (a === "--append-system-prompt") args.appendSystemPrompt.push(argv[++i] ?? "");
|
|
456
414
|
else if (a === "--tools" || a === "-t") args.tools = parseToolList(argv[++i] ?? "");
|
|
415
|
+
else if (a === "--acceptance-file") {
|
|
416
|
+
const path = argv[++i]; if (!path || path.startsWith("--")) throw new Error("--acceptance-file requires a JSON path");
|
|
417
|
+
args.acceptanceFile = path;
|
|
418
|
+
}
|
|
419
|
+
else if (a === "--tool-profile") {
|
|
420
|
+
const value = argv[++i] ?? "";
|
|
421
|
+
if (!isToolProfile(value)) throw new Error("unknown tool profile; choose coding, readonly, research, data or full");
|
|
422
|
+
args.toolProfile = value;
|
|
423
|
+
}
|
|
457
424
|
else if (a === "--exclude-tools" || a === "-xt") args.excludeTools = parseToolList(argv[++i] ?? "");
|
|
458
425
|
else if (a === "--no-tools" || a === "-nt") args.noTools = true;
|
|
459
426
|
else if (a === "--no-builtin-tools" || a === "-nbt") args.noBuiltinTools = true;
|
|
@@ -623,16 +590,17 @@ export function formatStartupHeader(input: {
|
|
|
623
590
|
`workspace ${input.workspace}`,
|
|
624
591
|
`session ${input.session}`,
|
|
625
592
|
];
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
593
|
+
const groups: [string, readonly string[]][] = [["agents", input.agents], ["skills", input.skills], ["prompts", input.prompts],
|
|
594
|
+
...(input.tools !== undefined ? [["tools", input.tools] as [string, readonly string[]]] : [])];
|
|
595
|
+
if (input.verbose) {
|
|
596
|
+
for (const [label, items] of groups) {
|
|
597
|
+
lines.push(`${label.padEnd(10)}${items.length}`);
|
|
629
598
|
for (const item of items) lines.push(` ${item}`);
|
|
630
599
|
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if (input.tools !== undefined) pushCount("tools", input.tools);
|
|
600
|
+
} else {
|
|
601
|
+
// Four count rows were a third of the banner; one row says the same.
|
|
602
|
+
lines.push(`loaded ${groups.map(([label, items]) => `${items.length} ${label}`).join(" · ")}`);
|
|
603
|
+
}
|
|
636
604
|
const keys = formatShortcutLine(input.keymap);
|
|
637
605
|
if (keys.length > 0) lines.push(`keys ${keys}`);
|
|
638
606
|
return lines;
|
|
@@ -835,13 +803,14 @@ export function describeUnauthenticatedScript(catalog: readonly ModelSpec[] = CA
|
|
|
835
803
|
);
|
|
836
804
|
}
|
|
837
805
|
|
|
838
|
-
/**
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
806
|
+
/**
|
|
807
|
+
* Banner / hint for an interactive session that started without a key.
|
|
808
|
+
* Listing every provider's variable was a 20-line wall that buried the one
|
|
809
|
+
* action that works from here; the interactive routes are named instead.
|
|
810
|
+
*/
|
|
811
|
+
export function describeUnauthenticatedSession(_catalog: readonly ModelSpec[] = CATALOG): string {
|
|
812
|
+
return "no provider connected yet. Type /login to connect one — a Claude, ChatGPT or Copilot subscription, an API key, a gateway or a local server — " +
|
|
813
|
+
`or export a key such as ${["anthropic", "openai", "google", "deepseek"].map(provider => envLabel(provider).split(" or ")[0]).join(", ")}.`;
|
|
845
814
|
}
|
|
846
815
|
|
|
847
816
|
export function retryHandler(io: AppIO): (attempt: number, delayMs: number, error: unknown) => void {
|
|
@@ -858,6 +827,7 @@ export function retryHandler(io: AppIO): (attempt: number, delayMs: number, erro
|
|
|
858
827
|
|
|
859
828
|
export function toolSelectionOf(args: Args) {
|
|
860
829
|
return {
|
|
830
|
+
profile: args.toolProfile ?? "coding",
|
|
861
831
|
...(args.noTools ? { none: true as const } : {}),
|
|
862
832
|
...(args.tools !== undefined ? { only: args.tools } : {}),
|
|
863
833
|
...(args.excludeTools.length > 0 ? { exclude: args.excludeTools } : {}),
|
|
@@ -868,25 +838,40 @@ export function toolSelectionOf(args: Args) {
|
|
|
868
838
|
/**
|
|
869
839
|
* Schema assembly: every tool is tagged with a source here so
|
|
870
840
|
* `--no-builtin-tools` can drop a category without a later cross-layer
|
|
871
|
-
* recut.
|
|
872
|
-
*
|
|
841
|
+
* recut. Optional extension descriptors remain local until selected; their
|
|
842
|
+
* harness-owned discovery entry belongs to that extension-enabled capability set.
|
|
873
843
|
*
|
|
874
844
|
* `source` is stripped before the frozen prefix: the wire schema is what
|
|
875
845
|
* the provider bills, and an extra field there would look like a tool
|
|
876
846
|
* argument.
|
|
877
847
|
*/
|
|
878
|
-
export function
|
|
848
|
+
export function sessionToolCatalog(args: Args, extensions?: ExtensionRegistry, previous: readonly unknown[] = []) {
|
|
879
849
|
const custom = [...extensions?.definitions().values() ?? []].map(({ name, description, input_schema }) => ({ name, description, input_schema, source: "extension" as const }));
|
|
880
|
-
const available = [...
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
850
|
+
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];
|
|
851
|
+
const selected = selectTools(available, toolSelectionOf(args));
|
|
852
|
+
const deferred = selected.filter(tool => tool.source === "extension" && extensions?.tools.get(tool.name)?.exposure === "deferred")
|
|
853
|
+
.map(({ name, description, input_schema }) => ({ name, description, input_schema }));
|
|
854
|
+
const deferredNames = new Set(deferred.map(tool => tool.name));
|
|
855
|
+
const active = new Set([...declaredToolNames(previous), ...args.tools ?? []]);
|
|
856
|
+
const visible = selected.filter(tool => (!deferredNames.has(tool.name) || active.has(tool.name)) &&
|
|
857
|
+
(tool.name !== DISCOVERY_TOOL_NAME || deferred.length > 0 || args.tools?.includes(DISCOVERY_TOOL_NAME)));
|
|
858
|
+
// A cold open must not reorder an already accepted declaration and invent a checkpoint.
|
|
859
|
+
const order = new Map(declaredToolNames(previous).map((name, index) => [name, index]));
|
|
860
|
+
visible.sort((a, b) => (order.get(a.name) ?? Infinity) - (order.get(b.name) ?? Infinity));
|
|
861
|
+
const builtins = visible.filter(tool => tool.source === "builtin").map(tool => tool.name);
|
|
862
|
+
const schemas = visible.map(tool => tool.source === "builtin" ? modelToolSchema(tool, builtins)
|
|
863
|
+
: { name: tool.name, description: tool.description, input_schema: tool.input_schema });
|
|
864
|
+
// D6: extension authors retain their registration objects and SDK catalog views.
|
|
865
|
+
// Neither may mutate the already frozen provider declaration through an alias.
|
|
866
|
+
return { schemas: JSON.parse(JSON.stringify(schemas)) as typeof schemas,
|
|
867
|
+
deferred: JSON.parse(JSON.stringify(deferred)) as typeof deferred };
|
|
868
|
+
}
|
|
869
|
+
export function visibleSchemas(args: Args, extensions?: ExtensionRegistry, previous: readonly unknown[] = []) {
|
|
870
|
+
return sessionToolCatalog(args, extensions, previous).schemas;
|
|
886
871
|
}
|
|
887
872
|
|
|
888
873
|
export function unknownToolNames(names: readonly string[]): string[] {
|
|
889
|
-
const known = new Set(
|
|
874
|
+
const known = new Set(TOOL_DEFINITIONS.map((t) => t.name));
|
|
890
875
|
return names.filter((n) => !known.has(n));
|
|
891
876
|
}
|
|
892
877
|
|
|
@@ -903,9 +888,11 @@ export function createSessionFor(opts: {
|
|
|
903
888
|
oauth?: boolean;
|
|
904
889
|
effects?: ResolveEffects;
|
|
905
890
|
extensions?: ExtensionRegistry;
|
|
891
|
+
acceptanceContract?: AcceptanceContract;
|
|
892
|
+
skills?: readonly Skill[];
|
|
906
893
|
}): Session {
|
|
907
894
|
const { args, workspace, logDir, blobDir, system, providerConfig, credential, io, askSlot } = opts;
|
|
908
|
-
const schemas =
|
|
895
|
+
const { schemas, deferred } = sessionToolCatalog(args, opts.extensions);
|
|
909
896
|
const names = schemas.map((t) => t.name);
|
|
910
897
|
const sessionRules: PermissionRule[] = [];
|
|
911
898
|
const tools: ToolContext = {
|
|
@@ -913,6 +900,7 @@ export function createSessionFor(opts: {
|
|
|
913
900
|
...DEFAULT_TOOL_CONTEXT,
|
|
914
901
|
...(args.pathRoot === undefined ? {} : { pathRoot: args.pathRoot }),
|
|
915
902
|
allow: names,
|
|
903
|
+
deferredTools: deferred,
|
|
916
904
|
rules: rulesWithAllowTools(args.allowTools),
|
|
917
905
|
sessionRules,
|
|
918
906
|
askSlot,
|
|
@@ -923,9 +911,12 @@ export function createSessionFor(opts: {
|
|
|
923
911
|
logDir,
|
|
924
912
|
frozen: { system, tools: schemas },
|
|
925
913
|
blobDir,
|
|
914
|
+
shellCheckpoints: args.shellCheckpoints === true,
|
|
926
915
|
tools,
|
|
927
916
|
maxTurns: args.maxTurns,
|
|
928
917
|
...cliSessionConfig(args),
|
|
918
|
+
...(opts.skills ? { skills: opts.skills } : {}),
|
|
919
|
+
...(opts.acceptanceContract ? { acceptanceContract: opts.acceptanceContract } : {}),
|
|
929
920
|
// Explicit legacy thresholds still select that experiment unless overridden.
|
|
930
921
|
contextPolicy: args.contextPolicy ?? (args.trigger > 0 ? "segmented" : DEFAULT_CONTEXT_POLICY),
|
|
931
922
|
...(args.trigger > 0
|
|
@@ -961,6 +952,7 @@ export function sessionDirFor(args: Args, workspace: string): string | undefined
|
|
|
961
952
|
}
|
|
962
953
|
|
|
963
954
|
type OpenSessionOptions = Omit<Parameters<typeof createSessionFor>[0], "providerConfig" | "credential" | "oauth" | "system"> & {
|
|
955
|
+
startupPhase?: (name: string) => void;
|
|
964
956
|
catalog: readonly ModelSpec[];
|
|
965
957
|
auth: AuthFile;
|
|
966
958
|
credentialOf: (spec: ModelSpec) => Resolved;
|
|
@@ -1008,8 +1000,9 @@ export async function loadSessionResources(args: Args, workspace: string, io: Ap
|
|
|
1008
1000
|
for (const line of new Set([...loaded.diagnostics, ...text.diagnostics, ...slash.diagnostics])) io.say(io.palette.yellow(` ${line}`));
|
|
1009
1001
|
const system = appendSkillsToSystem(assembleSystemPrompt(systemPromptFor(visibleSchemas(args, extensions).map(tool => tool.name), sessionEnvironment(workspace)), loadedContext), text.skills);
|
|
1010
1002
|
const ui = toUiState(loaded, colorEnabled());
|
|
1003
|
+
// Also reached on /resume and /reload: `auto` is resolved once per process by the Tui (apps/shell.ts), not here.
|
|
1011
1004
|
if (args.tuiMode) ui.settings = { ...ui.settings, tuiMode: args.tuiMode };
|
|
1012
|
-
return { loadedContext, slash: { ...slash.catalog, skillCommands: loaded.settings.enableSkillCommands !== false }, ui, index, trusted, system, extensions };
|
|
1005
|
+
return { loadedContext, slash: { ...slash.catalog, skillExecution: true, skillCommands: loaded.settings.enableSkillCommands !== false }, ui, index, trusted, system, extensions };
|
|
1013
1006
|
}
|
|
1014
1007
|
|
|
1015
1008
|
/**
|
|
@@ -1021,6 +1014,7 @@ export async function openSessionFor(opts: OpenSessionOptions): Promise<{
|
|
|
1021
1014
|
session: Session; priorCursor: Cursor | null; providerConfig: ProviderConfig; credential: Resolved;
|
|
1022
1015
|
workspace: string; blobDir: string; system: string; resources: Awaited<ReturnType<typeof loadSessionResources>>;
|
|
1023
1016
|
}> {
|
|
1017
|
+
opts.startupPhase?.("Reading session history");
|
|
1024
1018
|
const recorded = await readCursor(opts.logDir);
|
|
1025
1019
|
const priorCursor = recorded === null ? null : await recoverCut(opts.logDir, recorded);
|
|
1026
1020
|
const workspace = priorCursor?.workspace ?? opts.workspace;
|
|
@@ -1029,11 +1023,12 @@ export async function openSessionFor(opts: OpenSessionOptions): Promise<{
|
|
|
1029
1023
|
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) throw new Error("saved workspace is outside --path-root");
|
|
1030
1024
|
}
|
|
1031
1025
|
if (opts.preparedResources && opts.preparedResources.workspace !== workspace) await opts.preparedResources.value.extensions.dispose();
|
|
1026
|
+
opts.startupPhase?.("Loading session resources");
|
|
1032
1027
|
const resources = opts.preparedResources?.workspace === workspace ? opts.preparedResources.value
|
|
1033
1028
|
: await loadSessionResources(opts.args, workspace, opts.io, workspace === opts.workspace ? opts.projectTrusted : undefined);
|
|
1034
1029
|
const interactive = opts.interactive && !opts.args.print && !opts.args.prompt && opts.args.mode !== "json";
|
|
1035
1030
|
const named = [...opts.args.tools ?? [], ...opts.args.excludeTools, ...opts.args.allowTools];
|
|
1036
|
-
const unknown = unknownToolNames(named).filter(name => !resources.extensions.
|
|
1031
|
+
const unknown = unknownToolNames(named).filter(name => !resources.extensions.definitions().has(name));
|
|
1037
1032
|
if (unknown.length) throw new Error(`unknown tool: ${unknown.join(", ")}`);
|
|
1038
1033
|
const saved = priorCursor === null ? undefined : restoreSessionModel(priorCursor, opts.catalog);
|
|
1039
1034
|
const flags = { ...opts.modelFlags ?? { model: "", provider: "" } };
|
|
@@ -1042,6 +1037,7 @@ export async function openSessionFor(opts: OpenSessionOptions): Promise<{
|
|
|
1042
1037
|
const explicit = flags.model.length > 0 || flags.provider.length > 0;
|
|
1043
1038
|
const settings = opts.settings ?? {};
|
|
1044
1039
|
const preferred = saved ?? opts.fallback?.spec;
|
|
1040
|
+
opts.startupPhase?.("Resolving model credentials");
|
|
1045
1041
|
const selected = !explicit && preferred !== undefined
|
|
1046
1042
|
? { ok: true as const, spec: preferred }
|
|
1047
1043
|
: selectModel(settings, flags, (spec) => opts.credentialOf(spec).ok, opts.catalog);
|
|
@@ -1075,10 +1071,14 @@ export async function openSessionFor(opts: OpenSessionOptions): Promise<{
|
|
|
1075
1071
|
const restoring = saved ?? spec;
|
|
1076
1072
|
const restoringCredential = saved === undefined ? credential : opts.credentialOf(restoring);
|
|
1077
1073
|
const oauth = (model: ModelSpec, got: Resolved) => got.ok && storedIsOAuth(opts.auth, model.provider) && opts.args.apiKey.length === 0;
|
|
1074
|
+
const acceptanceContract = priorCursor === null
|
|
1075
|
+
? await loadAcceptanceContract(workspace, resources.trusted, opts.args.acceptanceFile) : undefined;
|
|
1078
1076
|
const session = createSessionFor({
|
|
1079
1077
|
...opts,
|
|
1080
1078
|
system: resources.system,
|
|
1081
1079
|
extensions: resources.extensions,
|
|
1080
|
+
skills: [...resources.slash.skills.values()],
|
|
1081
|
+
...(acceptanceContract ? { acceptanceContract } : {}),
|
|
1082
1082
|
workspace: priorCursor?.workspace ?? opts.workspace,
|
|
1083
1083
|
blobDir: priorCursor?.blob_dir ?? (priorCursor?.workspace && priorCursor.workspace !== opts.workspace
|
|
1084
1084
|
? statePaths(priorCursor.workspace, opts.args.stateDir).blobs : opts.blobDir),
|
|
@@ -1086,10 +1086,12 @@ export async function openSessionFor(opts: OpenSessionOptions): Promise<{
|
|
|
1086
1086
|
credential: restoringCredential,
|
|
1087
1087
|
...(oauth(restoring, restoringCredential) ? { oauth: true } : {}),
|
|
1088
1088
|
});
|
|
1089
|
+
opts.startupPhase?.("Opening session history");
|
|
1089
1090
|
await session.start();
|
|
1091
|
+
resources.slash = { ...resources.slash, skills: new Map((session.config.skills ?? []).map(skill => [skill.name, skill])) };
|
|
1090
1092
|
if (resources.extensions.loaded.length || session.config.frozen.tools.some(tool =>
|
|
1091
1093
|
typeof tool === "object" && tool !== null && "name" in tool && typeof tool.name === "string" && unknownToolNames([tool.name]).length)) {
|
|
1092
|
-
await session.reloadResources({ system: resources.system, tools: visibleSchemas(opts.args, resources.extensions) });
|
|
1094
|
+
await session.reloadResources({ system: resources.system, tools: visibleSchemas(opts.args, resources.extensions, session.config.frozen.tools) });
|
|
1093
1095
|
}
|
|
1094
1096
|
if (restoring.id !== spec.id || restoring.provider !== spec.provider) {
|
|
1095
1097
|
await session.switchModel(createProvider(providerConfig, credential.ok ? credential.key : "", retryHandler(opts.io),
|
|
@@ -1103,7 +1105,8 @@ export async function openSessionFor(opts: OpenSessionOptions): Promise<{
|
|
|
1103
1105
|
blobDir: session.config.blobDir ?? opts.blobDir, system: session.config.frozen.system, resources };
|
|
1104
1106
|
}
|
|
1105
1107
|
|
|
1106
|
-
export async function startup(args: Args): Promise<App> {
|
|
1108
|
+
export async function startup(args: Args, input?: StartupInput): Promise<App> {
|
|
1109
|
+
const withTerminal = <T>(run: (handoff: () => void) => Promise<T>): Promise<T> => input ? input.runWithTerminal(run) : run(() => {});
|
|
1107
1110
|
if (args.extensionArgv && (args.listModels || args.modelsCommand || args.configCommand)) rejectUnknownFlag("--" + Object.keys(args.extensionInputs ?? {})[0]);
|
|
1108
1111
|
const C = createPalette(colorEnabled());
|
|
1109
1112
|
let printMode = isPrintMode(args);
|
|
@@ -1158,6 +1161,7 @@ export async function startup(args: Args): Promise<App> {
|
|
|
1158
1161
|
}
|
|
1159
1162
|
|
|
1160
1163
|
// File only. Passing fetch here would make the test of D16 un-runnable.
|
|
1164
|
+
input?.phase("Loading model catalog");
|
|
1161
1165
|
const loaded = loadCatalog(await readModelSources());
|
|
1162
1166
|
for (const warning of loaded.warnings) io.say(C.yellow(` ${warning}`));
|
|
1163
1167
|
const catalog = loaded.models;
|
|
@@ -1187,6 +1191,7 @@ export async function startup(args: Args): Promise<App> {
|
|
|
1187
1191
|
* It has to happen before model selection so a trusted `.xeno/settings.json`
|
|
1188
1192
|
* can override the default model. Trust does not affect tools.
|
|
1189
1193
|
*/
|
|
1194
|
+
input?.phase("Opening workspace");
|
|
1190
1195
|
let workspace: string;
|
|
1191
1196
|
try {
|
|
1192
1197
|
workspace = await workspaceRoot(args.workspace);
|
|
@@ -1206,6 +1211,7 @@ export async function startup(args: Args): Promise<App> {
|
|
|
1206
1211
|
}
|
|
1207
1212
|
}
|
|
1208
1213
|
|
|
1214
|
+
input?.phase("Preparing extensions and project settings");
|
|
1209
1215
|
await prepareExtensionPackages(args,workspace);
|
|
1210
1216
|
const trust = await projectTrust(workspace, args.approve);
|
|
1211
1217
|
let loadProjectSettings = trust.trusted;
|
|
@@ -1213,11 +1219,11 @@ export async function startup(args: Args): Promise<App> {
|
|
|
1213
1219
|
if (!stdinIsTty() || !stdoutIsTty()) {
|
|
1214
1220
|
const index = await discoverResources({ ...args, workspace, trusted: loadProjectSettings });
|
|
1215
1221
|
for (const entry of index.entries) io.say(`${entry.enabled ? "[x]" : "[ ]"} ${entry.kind} ${entry.scope}${entry.package?" · "+entry.package.source:""} ${entry.path}`);
|
|
1216
|
-
} else await runResourceConfig({ ...args, workspace, trusted: loadProjectSettings });
|
|
1222
|
+
} else await withTerminal(handoff => runResourceConfig({ ...args, workspace, trusted: loadProjectSettings }, handoff));
|
|
1217
1223
|
process.exit(0);
|
|
1218
1224
|
}
|
|
1219
1225
|
const hasProjectResources = (await Promise.all(["settings.json", "keybindings.json", "skills", "prompts", "themes", "extensions"].map(name => stat(join(workspace, ".xeno", name)).then(() => true, () => false)))).some(Boolean);
|
|
1220
|
-
if (!args.help && hasProjectResources && args.approve === undefined && !trust.saved && trust.policy === "ask" && isInteractiveSession(args)) loadProjectSettings = await pickProjectTrust(workspace);
|
|
1226
|
+
if (!args.help && hasProjectResources && args.approve === undefined && !trust.saved && trust.policy === "ask" && isInteractiveSession(args)) loadProjectSettings = await withTerminal(handoff => pickProjectTrust(workspace, handoff));
|
|
1221
1227
|
if (hasProjectResources && !loadProjectSettings) io.say(C.dim(" Project resources not loaded (project is not trusted). /trust changes resource trust; tool permissions are separate."));
|
|
1222
1228
|
|
|
1223
1229
|
// Resolve the resource owner before factories run. Parsing flags must not load a different cwd first.
|
|
@@ -1231,6 +1237,7 @@ export async function startup(args: Args): Promise<App> {
|
|
|
1231
1237
|
else if (args.continueLatest) resourceLog = (await listSessions(workspace, sessionDirFor(args, workspace), Infinity)).sessions.find(session => session.ok)?.path;
|
|
1232
1238
|
if (resourceLog) resourceWorkspace = (await readCursor(resourceLog))?.workspace ?? workspace;
|
|
1233
1239
|
}
|
|
1240
|
+
input?.phase("Loading session resources");
|
|
1234
1241
|
const preparedResources = prepareFlags
|
|
1235
1242
|
? { workspace: resourceWorkspace, value: await loadSessionResources(args, resourceWorkspace, io, resourceWorkspace === workspace ? loadProjectSettings : undefined) } : undefined;
|
|
1236
1243
|
if (args.help) { printCliHelp(preparedResources?.value.extensions.flags); await preparedResources?.value.extensions.dispose(); process.exit(0); }
|
|
@@ -1268,8 +1275,8 @@ export async function startup(args: Args): Promise<App> {
|
|
|
1268
1275
|
if (printMode || jsonMode || !stdinIsTty() || !stdoutIsTty()) {
|
|
1269
1276
|
throw new Error("--resume needs an interactive terminal. Pass -r <id> or --session <id> instead");
|
|
1270
1277
|
}
|
|
1271
|
-
const picked = await pickStartupSession({ workspace,
|
|
1272
|
-
...(sessionDir === undefined ? {} : { sessionDir }) });
|
|
1278
|
+
const picked = await withTerminal(handoff => pickStartupSession({ workspace, theme: ui.theme, say: io.say, keymap: ui.keymap,
|
|
1279
|
+
...(sessionDir === undefined ? {} : { sessionDir }) }, handoff));
|
|
1273
1280
|
if (picked === null) { io.say(C.dim("No session selected")); process.exit(0); }
|
|
1274
1281
|
sessionOpts = { explicit: picked };
|
|
1275
1282
|
}
|
|
@@ -1301,6 +1308,7 @@ export async function startup(args: Args): Promise<App> {
|
|
|
1301
1308
|
settings: mergedSettings, modelFlags: { model: args.model, provider: args.provider }, projectTrusted: loadProjectSettings,
|
|
1302
1309
|
...(preparedResources ? { preparedResources } : {}),
|
|
1303
1310
|
interactive: isInteractiveSession(args),
|
|
1311
|
+
...(input ? { startupPhase: input.phase } : {}),
|
|
1304
1312
|
});
|
|
1305
1313
|
} catch (err) {
|
|
1306
1314
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -1335,6 +1343,21 @@ export async function startup(args: Args): Promise<App> {
|
|
|
1335
1343
|
};
|
|
1336
1344
|
}
|
|
1337
1345
|
|
|
1346
|
+
/** The banner's words for the shared limits; the loop acts at exactly these numbers. */
|
|
1347
|
+
export function boundaryDescription(policy: ContextPolicy, limits: ContextLimits): string {
|
|
1348
|
+
switch (limits.kind) {
|
|
1349
|
+
case "stop":
|
|
1350
|
+
case "open":
|
|
1351
|
+
return "continuous replay; stops at model window";
|
|
1352
|
+
case "window":
|
|
1353
|
+
return `windowed replay; prepare ${limits.prepare}, cut ${limits.trigger}, output reserved`;
|
|
1354
|
+
case "segment":
|
|
1355
|
+
return `${policy}${policy === "windowed" ? " (unknown window; fixed fallback)" : ""}; cut ${limits.trigger}, hard ${limits.hard}`;
|
|
1356
|
+
default:
|
|
1357
|
+
return assertNever(limits, "boundaryDescription");
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1338
1361
|
function printBanner(
|
|
1339
1362
|
args: Args,
|
|
1340
1363
|
io: AppIO,
|
|
@@ -1384,13 +1407,14 @@ function printBanner(
|
|
|
1384
1407
|
const line = formatNotice(notice);
|
|
1385
1408
|
io.say(notice.kind === "skipped" ? C.yellow(` ${line}`) : C.dim(` ${line}`));
|
|
1386
1409
|
}
|
|
1387
|
-
const { triggerTokens: trigger, hardTokens: hard } = { ...DEFAULT_BOUNDARY, ...session.config.boundary };
|
|
1388
1410
|
const policy = session.config.contextPolicy ?? DEFAULT_CONTEXT_POLICY;
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
:
|
|
1392
|
-
:
|
|
1393
|
-
|
|
1411
|
+
const limits = contextLimits({
|
|
1412
|
+
policy,
|
|
1413
|
+
contextWindow: session.provider.config.spec.contextWindow,
|
|
1414
|
+
outputReserve: session.provider.config.maxTokens,
|
|
1415
|
+
...(session.config.boundary ? { boundary: session.config.boundary } : {}),
|
|
1416
|
+
});
|
|
1417
|
+
io.say(C.dim(`boundary ${boundaryDescription(policy, limits)} (estimated tokens)`));
|
|
1394
1418
|
if (priorCursor !== null) {
|
|
1395
1419
|
const events = session.state.events.length;
|
|
1396
1420
|
io.say(
|
|
@@ -1419,6 +1443,8 @@ export function printCliHelp(flags?: ReadonlyMap<string, ExtensionFlag>): void {
|
|
|
1419
1443
|
" [--system-prompt <text-or-file>] [--append-system-prompt <text>]\n" +
|
|
1420
1444
|
" [--no-context-files|-nc]\n" +
|
|
1421
1445
|
" [--tools names] [--exclude-tools names] [--no-tools]\n" +
|
|
1446
|
+
" [--tool-profile coding|readonly|research|data|full]\n" +
|
|
1447
|
+
" [--acceptance-file contract.json]\n" +
|
|
1422
1448
|
" [--no-builtin-tools]\n" +
|
|
1423
1449
|
" [--allow-tools names] [--approve|--no-approve]\n" +
|
|
1424
1450
|
" [--theme <name|path>] [--use-theme name] [--no-themes] [--thinking level] [--models patterns]\n" +
|
|
@@ -1440,10 +1466,12 @@ export function printCliHelp(flags?: ReadonlyMap<string, ExtensionFlag>): void {
|
|
|
1440
1466
|
" --system-prompt replaces the built-in instructions (text, or a file that exists).\n" +
|
|
1441
1467
|
" --append-system-prompt appends; repeatable. -nc skips AGENTS.md discovery.\n" +
|
|
1442
1468
|
" --tools is a strict allowlist; excluded tools leave the schema.\n" +
|
|
1469
|
+
" New sessions use coding (10 tools); full exposes default built-ins.\n" +
|
|
1443
1470
|
" --no-builtin-tools drops built-ins and keeps any other source; --tools still wins.\n" +
|
|
1444
1471
|
" bash asks before it runs. --allow-tools bash skips the ask for this process.\n" +
|
|
1445
1472
|
" --time-budget <seconds> tells the model, once, when 75% of the run's wall-clock budget is used.\n" +
|
|
1446
1473
|
" --state-dir <dir> keeps snapshots, observations and sessions out of the workspace.\n" +
|
|
1474
|
+
" --checkpoint-shell enables best-effort Git recovery for command changes; ignored files and external effects are excluded.\n" +
|
|
1447
1475
|
" --path-root <dir> lets file tools reach absolute paths under <dir> (e.g. / in a container);\n" +
|
|
1448
1476
|
" relative paths, fingerprints and /back stay in the workspace, so edits outside it are not rewound.\n" +
|
|
1449
1477
|
" --approve loads .xeno/settings.json this run; --no-approve skips it.\n" +
|