@otto-code/protocol 0.8.12 → 0.8.14
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-labels.d.ts +3 -0
- package/dist/agent-labels.js +10 -0
- package/dist/{agent-personalities.d.ts → agent-profiles.d.ts} +13 -3
- package/dist/{agent-personalities.js → agent-profiles.js} +27 -11
- package/dist/agent-teams.d.ts +5 -5
- package/dist/agent-teams.js +1 -1
- package/dist/agent-types.d.ts +24 -4
- package/dist/binary-frames/terminal.d.ts +4 -0
- package/dist/binary-frames/terminal.js +1 -0
- package/dist/brain.d.ts +91 -0
- package/dist/brain.js +19 -1
- package/dist/chat/rpc-schemas.js +1 -0
- package/dist/chat/types.js +1 -0
- package/dist/client-capabilities.d.ts +1 -0
- package/dist/client-capabilities.js +4 -0
- package/dist/daemon-config.d.ts +10 -0
- package/dist/daemon-config.js +6 -0
- package/dist/default-personalities.d.ts +2 -2
- package/dist/default-personalities.js +2 -2
- package/dist/generated/validation/ws-outbound.aot.js +65204 -60319
- package/dist/loop/rpc-schemas.js +1 -0
- package/dist/messages.d.ts +4146 -316
- package/dist/messages.js +483 -2
- package/dist/personality-schemas.d.ts +135 -0
- package/dist/personality-schemas.js +63 -0
- package/dist/provider-manifest.js +7 -0
- package/dist/provider-snapshot-codec.d.ts +18 -0
- package/dist/provider-snapshot-codec.js +71 -0
- package/dist/schedule/rpc-schemas.d.ts +16 -8
- package/dist/schedule/types.d.ts +6 -3
- package/dist/schedule/types.js +8 -1
- package/dist/search/text-match.d.ts +55 -0
- package/dist/search/text-match.js +262 -0
- package/dist/suggested-tasks.js +1 -1
- package/dist/terminal-input-mode.d.ts +4 -0
- package/dist/terminal-input-mode.js +35 -6
- package/dist/terminal-key-input.js +15 -6
- package/dist/terminal-profiles.d.ts +35 -0
- package/dist/terminal-profiles.js +246 -4
- package/dist/tool-call-display.d.ts +2 -2
- package/dist/tool-call-display.js +6 -6
- package/dist/validation/ws-outbound-schema-metadata.d.ts +677 -28
- package/package.json +1 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranked text matching shared by the app's pickers and the daemon's history
|
|
3
|
+
* search. A match is a tier plus the offset it was found at; lower is better on
|
|
4
|
+
* both, so callers sort ascending and never have to invent a scale.
|
|
5
|
+
*
|
|
6
|
+
* Typo tolerance is opt-in via `fuzzy`. The pickers leave it off — a combobox
|
|
7
|
+
* over a known list wants exact narrowing — while history search turns it on
|
|
8
|
+
* because the user is recalling a title from memory.
|
|
9
|
+
*/
|
|
10
|
+
/** Exact tiers, best to worst. The fuzzy tier always sorts after all of them. */
|
|
11
|
+
const TIER_EXACT = 0;
|
|
12
|
+
const TIER_WHOLE_WORD = 1;
|
|
13
|
+
const TIER_PREFIX = 2;
|
|
14
|
+
const TIER_WORD_START = 3;
|
|
15
|
+
const TIER_SUBSTRING = 4;
|
|
16
|
+
const TIER_SUBSEQUENCE = 5;
|
|
17
|
+
const TIER_FUZZY = 6;
|
|
18
|
+
function isWordBoundaryChar(ch) {
|
|
19
|
+
if (ch === undefined)
|
|
20
|
+
return true;
|
|
21
|
+
return !/[a-z0-9]/.test(ch);
|
|
22
|
+
}
|
|
23
|
+
function scoreSubstringMatch(query, text) {
|
|
24
|
+
let best = null;
|
|
25
|
+
let pos = 0;
|
|
26
|
+
while (pos <= text.length - query.length) {
|
|
27
|
+
const found = text.indexOf(query, pos);
|
|
28
|
+
if (found === -1)
|
|
29
|
+
break;
|
|
30
|
+
const before = found > 0 ? text[found - 1] : undefined;
|
|
31
|
+
const after = text[found + query.length];
|
|
32
|
+
const startsAtBoundary = found === 0 || isWordBoundaryChar(before);
|
|
33
|
+
const endsAtBoundary = after === undefined || isWordBoundaryChar(after);
|
|
34
|
+
let tier;
|
|
35
|
+
if (startsAtBoundary && endsAtBoundary) {
|
|
36
|
+
tier = TIER_WHOLE_WORD;
|
|
37
|
+
}
|
|
38
|
+
else if (found === 0) {
|
|
39
|
+
tier = TIER_PREFIX;
|
|
40
|
+
}
|
|
41
|
+
else if (startsAtBoundary) {
|
|
42
|
+
tier = TIER_WORD_START;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
tier = TIER_SUBSTRING;
|
|
46
|
+
}
|
|
47
|
+
if (!best || tier < best.tier || (tier === best.tier && found < best.offset)) {
|
|
48
|
+
best = { tier, offset: found };
|
|
49
|
+
}
|
|
50
|
+
pos = found + 1;
|
|
51
|
+
}
|
|
52
|
+
return best;
|
|
53
|
+
}
|
|
54
|
+
function scoreSubsequenceMatch(query, text) {
|
|
55
|
+
let queryIndex = 0;
|
|
56
|
+
let firstIndex = -1;
|
|
57
|
+
let lastIndex = -1;
|
|
58
|
+
for (let textIndex = 0; textIndex < text.length && queryIndex < query.length; textIndex += 1) {
|
|
59
|
+
if (text[textIndex] !== query[queryIndex])
|
|
60
|
+
continue;
|
|
61
|
+
if (firstIndex === -1)
|
|
62
|
+
firstIndex = textIndex;
|
|
63
|
+
lastIndex = textIndex;
|
|
64
|
+
queryIndex += 1;
|
|
65
|
+
}
|
|
66
|
+
if (queryIndex !== query.length || firstIndex === -1)
|
|
67
|
+
return null;
|
|
68
|
+
return { tier: TIER_SUBSEQUENCE, offset: firstIndex, spread: lastIndex - firstIndex + 1 };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Damerau-Levenshtein distance, abandoned as soon as every cell in a row is
|
|
72
|
+
* over budget. Bounding it is what keeps the fuzzy tier affordable to run
|
|
73
|
+
* against every word of every candidate.
|
|
74
|
+
*/
|
|
75
|
+
function boundedEditDistance(query, word, budget) {
|
|
76
|
+
if (Math.abs(query.length - word.length) > budget)
|
|
77
|
+
return null;
|
|
78
|
+
let twoRowsBack = [];
|
|
79
|
+
let previousRow = Array.from({ length: word.length + 1 }, (_, index) => index);
|
|
80
|
+
for (let queryIndex = 1; queryIndex <= query.length; queryIndex += 1) {
|
|
81
|
+
const currentRow = [queryIndex];
|
|
82
|
+
let rowBest = queryIndex;
|
|
83
|
+
for (let wordIndex = 1; wordIndex <= word.length; wordIndex += 1) {
|
|
84
|
+
const substitutionCost = query[queryIndex - 1] === word[wordIndex - 1] ? 0 : 1;
|
|
85
|
+
let cost = Math.min(currentRow[wordIndex - 1] + 1, previousRow[wordIndex] + 1, previousRow[wordIndex - 1] + substitutionCost);
|
|
86
|
+
const isTransposition = queryIndex > 1 &&
|
|
87
|
+
wordIndex > 1 &&
|
|
88
|
+
query[queryIndex - 1] === word[wordIndex - 2] &&
|
|
89
|
+
query[queryIndex - 2] === word[wordIndex - 1];
|
|
90
|
+
if (isTransposition) {
|
|
91
|
+
cost = Math.min(cost, twoRowsBack[wordIndex - 2] + 1);
|
|
92
|
+
}
|
|
93
|
+
currentRow.push(cost);
|
|
94
|
+
rowBest = Math.min(rowBest, cost);
|
|
95
|
+
}
|
|
96
|
+
if (rowBest > budget)
|
|
97
|
+
return null;
|
|
98
|
+
twoRowsBack = previousRow;
|
|
99
|
+
previousRow = currentRow;
|
|
100
|
+
}
|
|
101
|
+
const distance = previousRow[word.length];
|
|
102
|
+
return distance <= budget ? distance : null;
|
|
103
|
+
}
|
|
104
|
+
function transpositionDistance(query, word) {
|
|
105
|
+
return isAdjacentTransposition(query, word) ? 1 : null;
|
|
106
|
+
}
|
|
107
|
+
/** True when the two differ only by one swap of neighbouring characters. */
|
|
108
|
+
function isAdjacentTransposition(query, word) {
|
|
109
|
+
if (query.length !== word.length)
|
|
110
|
+
return false;
|
|
111
|
+
let index = 0;
|
|
112
|
+
while (index < query.length && query[index] === word[index])
|
|
113
|
+
index += 1;
|
|
114
|
+
if (index >= query.length - 1)
|
|
115
|
+
return false;
|
|
116
|
+
if (query[index] !== word[index + 1] || query[index + 1] !== word[index])
|
|
117
|
+
return false;
|
|
118
|
+
return query.slice(index + 2) === word.slice(index + 2);
|
|
119
|
+
}
|
|
120
|
+
export function fuzzyPolicyForToken(token) {
|
|
121
|
+
if (token.length <= 3)
|
|
122
|
+
return null;
|
|
123
|
+
if (token.length === 4)
|
|
124
|
+
return { maxEdits: 1, transpositionsOnly: true };
|
|
125
|
+
if (token.length <= 7)
|
|
126
|
+
return { maxEdits: 1, transpositionsOnly: false };
|
|
127
|
+
return { maxEdits: 2, transpositionsOnly: false };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Words are what people mistype, so the fuzzy tier compares the query against
|
|
131
|
+
* each word rather than against the whole string — otherwise a long title's
|
|
132
|
+
* length difference alone would blow the budget.
|
|
133
|
+
*/
|
|
134
|
+
function scoreFuzzyMatch(query, text, policy) {
|
|
135
|
+
if (policy.maxEdits <= 0 || query.length <= policy.maxEdits)
|
|
136
|
+
return null;
|
|
137
|
+
let best = null;
|
|
138
|
+
const wordPattern = /[a-z0-9]+/g;
|
|
139
|
+
let word = wordPattern.exec(text);
|
|
140
|
+
while (word !== null) {
|
|
141
|
+
// Compare against the whole word and against its leading slices, so a typo
|
|
142
|
+
// in a prefix ("confug" for "configuration") still lands — the length gap
|
|
143
|
+
// to the full word would otherwise blow the budget on its own.
|
|
144
|
+
const candidates = new Set([
|
|
145
|
+
word[0],
|
|
146
|
+
word[0].slice(0, query.length),
|
|
147
|
+
word[0].slice(0, query.length + policy.maxEdits),
|
|
148
|
+
]);
|
|
149
|
+
for (const candidate of candidates) {
|
|
150
|
+
const distance = policy.transpositionsOnly
|
|
151
|
+
? transpositionDistance(query, candidate)
|
|
152
|
+
: boundedEditDistance(query, candidate, policy.maxEdits);
|
|
153
|
+
if (distance === null)
|
|
154
|
+
continue;
|
|
155
|
+
const score = { tier: TIER_FUZZY, offset: word.index, spread: distance };
|
|
156
|
+
if (!best || compareMatchScores(score, best) < 0) {
|
|
157
|
+
best = score;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
word = wordPattern.exec(text);
|
|
161
|
+
}
|
|
162
|
+
return best;
|
|
163
|
+
}
|
|
164
|
+
export function scoreMatch(query, text, options = {}) {
|
|
165
|
+
if (!query)
|
|
166
|
+
return { tier: TIER_EXACT, offset: 0 };
|
|
167
|
+
const q = query.toLowerCase();
|
|
168
|
+
const t = text.toLowerCase();
|
|
169
|
+
if (t === q)
|
|
170
|
+
return { tier: TIER_EXACT, offset: 0 };
|
|
171
|
+
const exact = scoreSubstringMatch(q, t) ?? scoreSubsequenceMatch(q, t);
|
|
172
|
+
if (exact)
|
|
173
|
+
return exact;
|
|
174
|
+
const fuzzy = options.fuzzy;
|
|
175
|
+
return fuzzy ? scoreFuzzyMatch(q, t, fuzzy) : null;
|
|
176
|
+
}
|
|
177
|
+
function mergeAdjacentRanges(indices) {
|
|
178
|
+
const ranges = [];
|
|
179
|
+
for (const index of indices) {
|
|
180
|
+
const last = ranges.at(-1);
|
|
181
|
+
if (last && last.start + last.length === index) {
|
|
182
|
+
last.length += 1;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
ranges.push({ start: index, length: 1 });
|
|
186
|
+
}
|
|
187
|
+
return ranges;
|
|
188
|
+
}
|
|
189
|
+
function wordRangeAt(text, offset) {
|
|
190
|
+
let end = offset;
|
|
191
|
+
while (end < text.length && /[a-z0-9]/.test(text[end]))
|
|
192
|
+
end += 1;
|
|
193
|
+
return { start: offset, length: Math.max(end - offset, 1) };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Where a score's match actually landed, so a caller can mark it. Derived from
|
|
197
|
+
* a score rather than produced alongside one: ranking touches every candidate
|
|
198
|
+
* and needs no ranges, while only the handful of rows that get rendered do.
|
|
199
|
+
*
|
|
200
|
+
* The tier decides the shape. A substring hit is one span; a subsequence hit is
|
|
201
|
+
* the scattered characters it walked; a typo hit marks the whole word, because
|
|
202
|
+
* the characters the user got wrong are not in the text to point at.
|
|
203
|
+
*/
|
|
204
|
+
export function matchRanges(query, text, score) {
|
|
205
|
+
if (!query)
|
|
206
|
+
return [];
|
|
207
|
+
const q = query.toLowerCase();
|
|
208
|
+
const t = text.toLowerCase();
|
|
209
|
+
if (score.tier === TIER_EXACT)
|
|
210
|
+
return [{ start: 0, length: text.length }];
|
|
211
|
+
if (score.tier === TIER_FUZZY)
|
|
212
|
+
return [wordRangeAt(t, score.offset)];
|
|
213
|
+
if (score.tier === TIER_SUBSEQUENCE) {
|
|
214
|
+
const indices = [];
|
|
215
|
+
let queryIndex = 0;
|
|
216
|
+
for (let textIndex = 0; textIndex < t.length && queryIndex < q.length; textIndex += 1) {
|
|
217
|
+
if (t[textIndex] !== q[queryIndex])
|
|
218
|
+
continue;
|
|
219
|
+
indices.push(textIndex);
|
|
220
|
+
queryIndex += 1;
|
|
221
|
+
}
|
|
222
|
+
return mergeAdjacentRanges(indices);
|
|
223
|
+
}
|
|
224
|
+
return [{ start: score.offset, length: q.length }];
|
|
225
|
+
}
|
|
226
|
+
export function compareMatchScores(a, b) {
|
|
227
|
+
if (a.tier !== b.tier)
|
|
228
|
+
return a.tier - b.tier;
|
|
229
|
+
if (a.offset !== b.offset)
|
|
230
|
+
return a.offset - b.offset;
|
|
231
|
+
return (a.spread ?? 0) - (b.spread ?? 0);
|
|
232
|
+
}
|
|
233
|
+
export function tokenizeQuery(query) {
|
|
234
|
+
return query
|
|
235
|
+
.trim()
|
|
236
|
+
.toLowerCase()
|
|
237
|
+
.split(/\s+/)
|
|
238
|
+
.filter((token) => token.length > 0);
|
|
239
|
+
}
|
|
240
|
+
export function scoreTextFields(query, fields, options = {}) {
|
|
241
|
+
const tokens = tokenizeQuery(query);
|
|
242
|
+
if (tokens.length === 0)
|
|
243
|
+
return { tier: TIER_EXACT, offset: 0, spread: 0 };
|
|
244
|
+
const aggregate = { tier: TIER_EXACT, offset: 0, spread: 0 };
|
|
245
|
+
for (const token of tokens) {
|
|
246
|
+
const fuzzy = options.typoTolerant ? fuzzyPolicyForToken(token) : null;
|
|
247
|
+
let best = null;
|
|
248
|
+
for (const field of fields) {
|
|
249
|
+
const score = scoreMatch(token, field, { fuzzy });
|
|
250
|
+
if (score && (!best || compareMatchScores(score, best) < 0)) {
|
|
251
|
+
best = score;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!best)
|
|
255
|
+
return null;
|
|
256
|
+
aggregate.tier += best.tier;
|
|
257
|
+
aggregate.offset += best.offset;
|
|
258
|
+
aggregate.spread = (aggregate.spread ?? 0) + (best.spread ?? token.length);
|
|
259
|
+
}
|
|
260
|
+
return aggregate;
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=text-match.js.map
|
package/dist/suggested-tasks.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
/**
|
|
3
3
|
* Otto suggested-task wire schemas: the tasks.suggested.* start and dismiss RPCs and the suggested-task payloads. Fork-only capability, so it owns its schemas; messages.ts re-exports them.
|
|
4
4
|
*/
|
|
5
|
-
// A suggested task
|
|
5
|
+
// A suggested task a chat surfaced via the `suggest_task` tool (Claude Desktop
|
|
6
6
|
// parity). Renders as a chip in the parent agent's session; the user starts it
|
|
7
7
|
// (new worktree / local / this session) or dismisses it. The `prompt` is
|
|
8
8
|
// deliberately NOT part of this wire shape - it stays server-side and is only
|
|
@@ -5,6 +5,8 @@ export interface TerminalInputModeFeedResult {
|
|
|
5
5
|
export interface TerminalInputModeState {
|
|
6
6
|
kittyKeyboardFlags: number;
|
|
7
7
|
win32InputMode: boolean;
|
|
8
|
+
applicationCursorKeys?: boolean;
|
|
9
|
+
bracketedPaste?: boolean;
|
|
8
10
|
}
|
|
9
11
|
export declare const DEFAULT_TERMINAL_INPUT_MODE_STATE: TerminalInputModeState;
|
|
10
12
|
export declare function terminalInputModeSupportsModifiedEnter(state: TerminalInputModeState): boolean;
|
|
@@ -12,6 +14,8 @@ export declare function terminalInputModeStatesEqual(left: TerminalInputModeStat
|
|
|
12
14
|
export declare class TerminalInputModeTracker {
|
|
13
15
|
private kittyKeyboardFlags;
|
|
14
16
|
private win32InputMode;
|
|
17
|
+
private applicationCursorKeys;
|
|
18
|
+
private bracketedPaste;
|
|
15
19
|
private readonly kittyKeyboardStack;
|
|
16
20
|
private pending;
|
|
17
21
|
feed(data: string): TerminalInputModeFeedResult;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export const DEFAULT_TERMINAL_INPUT_MODE_STATE = {
|
|
2
2
|
kittyKeyboardFlags: 0,
|
|
3
3
|
win32InputMode: false,
|
|
4
|
+
applicationCursorKeys: false,
|
|
5
|
+
bracketedPaste: false,
|
|
4
6
|
};
|
|
5
7
|
const ESC = String.fromCharCode(0x1b);
|
|
8
|
+
const APPLICATION_CURSOR_KEYS_MODE = 1;
|
|
6
9
|
const WIN32_INPUT_MODE = 9001;
|
|
10
|
+
const BRACKETED_PASTE_MODE = 2004;
|
|
7
11
|
const CSI_INPUT_MODE_SEQUENCE = new RegExp(`${ESC}\\[(?:([<>=?]?)([0-9;]*)u|\\?([0-9;]*)([hl]))`, "g");
|
|
8
12
|
const INCOMPLETE_CSI_INPUT_MODE_SEQUENCE = new RegExp(`${ESC}\\[[<>=?]?[0-9;]*$`);
|
|
9
13
|
function parseFirstParam(params) {
|
|
@@ -34,12 +38,16 @@ export function terminalInputModeSupportsModifiedEnter(state) {
|
|
|
34
38
|
}
|
|
35
39
|
export function terminalInputModeStatesEqual(left, right) {
|
|
36
40
|
return (left.kittyKeyboardFlags === right.kittyKeyboardFlags &&
|
|
37
|
-
left.win32InputMode === right.win32InputMode
|
|
41
|
+
left.win32InputMode === right.win32InputMode &&
|
|
42
|
+
Boolean(left.applicationCursorKeys) === Boolean(right.applicationCursorKeys) &&
|
|
43
|
+
Boolean(left.bracketedPaste) === Boolean(right.bracketedPaste));
|
|
38
44
|
}
|
|
39
45
|
export class TerminalInputModeTracker {
|
|
40
46
|
constructor() {
|
|
41
47
|
this.kittyKeyboardFlags = 0;
|
|
42
48
|
this.win32InputMode = false;
|
|
49
|
+
this.applicationCursorKeys = false;
|
|
50
|
+
this.bracketedPaste = false;
|
|
43
51
|
this.kittyKeyboardStack = [];
|
|
44
52
|
this.pending = "";
|
|
45
53
|
}
|
|
@@ -80,6 +88,8 @@ export class TerminalInputModeTracker {
|
|
|
80
88
|
reset() {
|
|
81
89
|
this.kittyKeyboardFlags = 0;
|
|
82
90
|
this.win32InputMode = false;
|
|
91
|
+
this.applicationCursorKeys = false;
|
|
92
|
+
this.bracketedPaste = false;
|
|
83
93
|
this.kittyKeyboardStack.length = 0;
|
|
84
94
|
this.pending = "";
|
|
85
95
|
}
|
|
@@ -87,6 +97,8 @@ export class TerminalInputModeTracker {
|
|
|
87
97
|
return {
|
|
88
98
|
kittyKeyboardFlags: this.kittyKeyboardFlags,
|
|
89
99
|
win32InputMode: this.win32InputMode,
|
|
100
|
+
applicationCursorKeys: this.applicationCursorKeys,
|
|
101
|
+
bracketedPaste: this.bracketedPaste,
|
|
90
102
|
};
|
|
91
103
|
}
|
|
92
104
|
getKittyKeyboardFlags() {
|
|
@@ -103,6 +115,12 @@ export class TerminalInputModeTracker {
|
|
|
103
115
|
if (this.win32InputMode) {
|
|
104
116
|
parts.push("\x1b[?9001h");
|
|
105
117
|
}
|
|
118
|
+
if (this.applicationCursorKeys) {
|
|
119
|
+
parts.push("\x1b[?1h");
|
|
120
|
+
}
|
|
121
|
+
if (this.bracketedPaste) {
|
|
122
|
+
parts.push("\x1b[?2004h");
|
|
123
|
+
}
|
|
106
124
|
return parts.join("");
|
|
107
125
|
}
|
|
108
126
|
applyKittyKeyboardSequence(prefix, params) {
|
|
@@ -140,12 +158,23 @@ export class TerminalInputModeTracker {
|
|
|
140
158
|
}
|
|
141
159
|
applyPrivateModeSequence(params, final) {
|
|
142
160
|
const modes = parsePrivateModeParams(params);
|
|
143
|
-
|
|
144
|
-
|
|
161
|
+
let changed = false;
|
|
162
|
+
if (modes.has(WIN32_INPUT_MODE)) {
|
|
163
|
+
const previous = this.win32InputMode;
|
|
164
|
+
this.win32InputMode = final === "h";
|
|
165
|
+
changed = this.win32InputMode !== previous || changed;
|
|
166
|
+
}
|
|
167
|
+
if (modes.has(APPLICATION_CURSOR_KEYS_MODE)) {
|
|
168
|
+
const previous = this.applicationCursorKeys;
|
|
169
|
+
this.applicationCursorKeys = final === "h";
|
|
170
|
+
changed = this.applicationCursorKeys !== previous || changed;
|
|
171
|
+
}
|
|
172
|
+
if (modes.has(BRACKETED_PASTE_MODE)) {
|
|
173
|
+
const previous = this.bracketedPaste;
|
|
174
|
+
this.bracketedPaste = final === "h";
|
|
175
|
+
changed = this.bracketedPaste !== previous || changed;
|
|
145
176
|
}
|
|
146
|
-
|
|
147
|
-
this.win32InputMode = final === "h";
|
|
148
|
-
return this.win32InputMode !== previous;
|
|
177
|
+
return changed;
|
|
149
178
|
}
|
|
150
179
|
}
|
|
151
180
|
//# sourceMappingURL=terminal-input-mode.js.map
|
|
@@ -99,6 +99,9 @@ function csiWithModifier(finalByte, input) {
|
|
|
99
99
|
const mod = modifierParam(input);
|
|
100
100
|
return mod === 1 ? `\x1b[${finalByte}` : `\x1b[1;${mod}${finalByte}`;
|
|
101
101
|
}
|
|
102
|
+
function ss3WithModifier(finalByte, input) {
|
|
103
|
+
return modifierParam(input) === 1 ? `\x1bO${finalByte}` : csiWithModifier(finalByte, input);
|
|
104
|
+
}
|
|
102
105
|
function csiTilde(base, input) {
|
|
103
106
|
const mod = modifierParam(input);
|
|
104
107
|
return mod === 1 ? `\x1b[${base}~` : `\x1b[${base};${mod}~`;
|
|
@@ -133,16 +136,22 @@ function encodeFunctionKey(key, input) {
|
|
|
133
136
|
return null;
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
|
-
function
|
|
139
|
+
function encodeArrowKey(finalByte, input, options) {
|
|
140
|
+
if (options.inputMode?.applicationCursorKeys) {
|
|
141
|
+
return ss3WithModifier(finalByte, input);
|
|
142
|
+
}
|
|
143
|
+
return csiWithModifier(finalByte, input);
|
|
144
|
+
}
|
|
145
|
+
function encodeNavigationKey(key, input, options) {
|
|
137
146
|
switch (key) {
|
|
138
147
|
case "ArrowUp":
|
|
139
|
-
return
|
|
148
|
+
return encodeArrowKey("A", input, options);
|
|
140
149
|
case "ArrowDown":
|
|
141
|
-
return
|
|
150
|
+
return encodeArrowKey("B", input, options);
|
|
142
151
|
case "ArrowRight":
|
|
143
|
-
return
|
|
152
|
+
return encodeArrowKey("C", input, options);
|
|
144
153
|
case "ArrowLeft":
|
|
145
|
-
return
|
|
154
|
+
return encodeArrowKey("D", input, options);
|
|
146
155
|
case "Home":
|
|
147
156
|
return csiWithModifier("H", input);
|
|
148
157
|
case "End":
|
|
@@ -190,7 +199,7 @@ export function encodeTerminalKeyInput(input, options = {}) {
|
|
|
190
199
|
default:
|
|
191
200
|
break;
|
|
192
201
|
}
|
|
193
|
-
const nav = encodeNavigationKey(key, input);
|
|
202
|
+
const nav = encodeNavigationKey(key, input, options);
|
|
194
203
|
if (nav !== null)
|
|
195
204
|
return nav;
|
|
196
205
|
const fn = encodeFunctionKey(key, input);
|
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
import type { TerminalProfile } from "./messages.js";
|
|
2
|
+
/**
|
|
3
|
+
* Marks where a typed prompt goes inside a profile's `command` or `args`. A
|
|
4
|
+
* profile carrying it accepts a prompt; one without it launches as-is.
|
|
5
|
+
*
|
|
6
|
+
* Substitution happens client-side before `create_terminal_request` is sent, so
|
|
7
|
+
* this is profile-format vocabulary rather than anything on the wire. It lives
|
|
8
|
+
* here because this module owns what a `TerminalProfile` means.
|
|
9
|
+
*/
|
|
10
|
+
export declare const PROMPT_SENTINEL = "{{{prompt}}}";
|
|
2
11
|
export declare const DEFAULT_TERMINAL_PROFILES: readonly TerminalProfile[];
|
|
12
|
+
export interface SubstitutableCommand {
|
|
13
|
+
command: string;
|
|
14
|
+
args?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface ResolvedCommand {
|
|
17
|
+
command: string;
|
|
18
|
+
args: string[];
|
|
19
|
+
}
|
|
20
|
+
/** True when the sentinel appears anywhere in `command` or an `args` entry. */
|
|
21
|
+
export declare function profileTakesPrompt(profile: SubstitutableCommand): boolean;
|
|
22
|
+
/** Replaces every sentinel occurrence with `prompt`, dropping prompt-only args when there is none. */
|
|
23
|
+
export declare function substitutePrompt(profile: SubstitutableCommand, prompt: string): ResolvedCommand;
|
|
24
|
+
/** Human-readable preview of the resolved command, for read-only display. */
|
|
25
|
+
export declare function formatResolvedCommand(resolved: ResolvedCommand): string;
|
|
26
|
+
export interface TerminalProfileLaunch {
|
|
27
|
+
name: string;
|
|
28
|
+
command: string;
|
|
29
|
+
args: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* What to spawn for a profile. Every launcher goes through here, so no caller
|
|
33
|
+
* can forward a raw `profile.args` still carrying the sentinel: launchers with
|
|
34
|
+
* nowhere to type (pinned targets, the workspace terminal menu) pass an empty
|
|
35
|
+
* prompt and get the bare command back.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveTerminalProfileLaunch(profile: TerminalProfile, prompt: string): TerminalProfileLaunch;
|
|
3
38
|
export declare function guessTerminalProfileIcon(command: string): string | undefined;
|
|
4
39
|
export declare function getTerminalProfileIcon(profile: TerminalProfile): string | undefined;
|
|
5
40
|
export declare function resolveTerminalProfiles(terminalProfiles: TerminalProfile[] | undefined): readonly TerminalProfile[];
|