@otto-code/protocol 0.8.12 → 0.8.13
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 +4 -3
- package/dist/agent-personalities.js +13 -20
- 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 +6 -0
- package/dist/daemon-config.js +6 -0
- package/dist/generated/validation/ws-outbound.aot.js +64093 -60274
- package/dist/loop/rpc-schemas.js +1 -0
- package/dist/messages.d.ts +3546 -422
- package/dist/messages.js +445 -13
- 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 +8 -64
- package/dist/schedule/types.d.ts +3 -24
- package/dist/schedule/types.js +1 -11
- 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 +587 -77
- package/package.json +1 -1
|
@@ -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[];
|
|
@@ -1,9 +1,78 @@
|
|
|
1
1
|
import { KNOWN_PROVIDER_ICON_NAMES } from "./provider-icon-names.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 const PROMPT_SENTINEL = "{{{prompt}}}";
|
|
11
|
+
// Prompt forms are taken from each CLI's own `--help`. claude, codex, and pi
|
|
12
|
+
// take the prompt as a trailing positional. opencode's positional is the
|
|
13
|
+
// project directory, so its prompt goes through `--prompt`, written in the
|
|
14
|
+
// `--flag=value` form to keep it one argv entry that can be dropped whole when
|
|
15
|
+
// no prompt is typed.
|
|
2
16
|
export const DEFAULT_TERMINAL_PROFILES = [
|
|
3
|
-
{ id: "claude", name: "Claude Code", command: "claude", icon: "claude" },
|
|
4
|
-
{ id: "codex", name: "Codex", command: "codex", icon: "codex" },
|
|
5
|
-
{
|
|
17
|
+
{ id: "claude", name: "Claude Code", command: "claude", args: [PROMPT_SENTINEL], icon: "claude" },
|
|
18
|
+
{ id: "codex", name: "Codex", command: "codex", args: [PROMPT_SENTINEL], icon: "codex" },
|
|
19
|
+
{
|
|
20
|
+
id: "opencode",
|
|
21
|
+
name: "OpenCode",
|
|
22
|
+
command: "opencode",
|
|
23
|
+
args: [`--prompt=${PROMPT_SENTINEL}`],
|
|
24
|
+
icon: "opencode",
|
|
25
|
+
},
|
|
26
|
+
{ id: "pi", name: "Pi", command: "pi", args: [PROMPT_SENTINEL], icon: "pi" },
|
|
6
27
|
];
|
|
28
|
+
function containsSentinel(value) {
|
|
29
|
+
return value.includes(PROMPT_SENTINEL);
|
|
30
|
+
}
|
|
31
|
+
/** True when the sentinel appears anywhere in `command` or an `args` entry. */
|
|
32
|
+
export function profileTakesPrompt(profile) {
|
|
33
|
+
return containsSentinel(profile.command) || (profile.args ?? []).some(containsSentinel);
|
|
34
|
+
}
|
|
35
|
+
function replaceSentinel(value, prompt) {
|
|
36
|
+
return value.split(PROMPT_SENTINEL).join(prompt);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* An arg that exists only to carry a prompt: the sentinel alone
|
|
40
|
+
* (`{{{prompt}}}`), or as the whole value of an option assignment
|
|
41
|
+
* (`--prompt={{{prompt}}}`). Both are dropped when there is no prompt, because
|
|
42
|
+
* an empty positional and a valueless `--prompt=` are not the same as omitting
|
|
43
|
+
* them. A sentinel embedded in larger text (`echo {{{prompt}}}`) is not
|
|
44
|
+
* prompt-only and still substitutes to empty.
|
|
45
|
+
*
|
|
46
|
+
* This is why an option and its value belong in one entry. Split across two,
|
|
47
|
+
* the option would survive with nothing to carry.
|
|
48
|
+
*/
|
|
49
|
+
function isPromptOnlyArg(arg) {
|
|
50
|
+
if (arg === PROMPT_SENTINEL) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const separator = arg.indexOf("=");
|
|
54
|
+
return separator > 0 && arg.slice(separator + 1) === PROMPT_SENTINEL;
|
|
55
|
+
}
|
|
56
|
+
/** Replaces every sentinel occurrence with `prompt`, dropping prompt-only args when there is none. */
|
|
57
|
+
export function substitutePrompt(profile, prompt) {
|
|
58
|
+
return {
|
|
59
|
+
command: replaceSentinel(profile.command, prompt),
|
|
60
|
+
args: (profile.args ?? []).flatMap((arg) => prompt === "" && isPromptOnlyArg(arg) ? [] : [replaceSentinel(arg, prompt)]),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Human-readable preview of the resolved command, for read-only display. */
|
|
64
|
+
export function formatResolvedCommand(resolved) {
|
|
65
|
+
return [resolved.command, ...resolved.args].join(" ");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* What to spawn for a profile. Every launcher goes through here, so no caller
|
|
69
|
+
* can forward a raw `profile.args` still carrying the sentinel: launchers with
|
|
70
|
+
* nowhere to type (pinned targets, the workspace terminal menu) pass an empty
|
|
71
|
+
* prompt and get the bare command back.
|
|
72
|
+
*/
|
|
73
|
+
export function resolveTerminalProfileLaunch(profile, prompt) {
|
|
74
|
+
return { name: profile.name, ...substitutePrompt(profile, prompt) };
|
|
75
|
+
}
|
|
7
76
|
const WELL_KNOWN_COMMAND_ICONS = new Map(KNOWN_PROVIDER_ICON_NAMES.map((name) => [name, name]));
|
|
8
77
|
function getCommandBaseName(command) {
|
|
9
78
|
const lastSlash = command.lastIndexOf("/");
|
|
@@ -19,10 +88,183 @@ export function guessTerminalProfileIcon(command) {
|
|
|
19
88
|
export function getTerminalProfileIcon(profile) {
|
|
20
89
|
return profile.icon ?? guessTerminalProfileIcon(profile.command);
|
|
21
90
|
}
|
|
91
|
+
// Base command name to the sentinel-bearing args that command wants, derived
|
|
92
|
+
// from the shipped defaults rather than written out again, so adding a default
|
|
93
|
+
// profile later cannot silently miss the adoption below or get the wrong form.
|
|
94
|
+
const PROMPT_ARGS_BY_COMMAND = new Map(DEFAULT_TERMINAL_PROFILES.map((profile) => [
|
|
95
|
+
getCommandBaseName(profile.command),
|
|
96
|
+
(profile.args ?? []).filter(containsSentinel),
|
|
97
|
+
]));
|
|
98
|
+
// Adoption applies only to the interactive, prompt-taking form of each CLI.
|
|
99
|
+
// These are the global options whose values would otherwise look positional
|
|
100
|
+
// while walking argv. Unknown options stay conservative: a following bare arg
|
|
101
|
+
// blocks adoption rather than risking a second prompt or changing a subcommand.
|
|
102
|
+
const GLOBAL_OPTIONS_WITH_VALUES_BY_COMMAND = new Map([
|
|
103
|
+
[
|
|
104
|
+
"claude",
|
|
105
|
+
new Set([
|
|
106
|
+
"--add-dir",
|
|
107
|
+
"--agent",
|
|
108
|
+
"--agents",
|
|
109
|
+
"--allowed-tools",
|
|
110
|
+
"--allowedTools",
|
|
111
|
+
"--append-system-prompt",
|
|
112
|
+
"--betas",
|
|
113
|
+
"--debug-file",
|
|
114
|
+
"--disallowed-tools",
|
|
115
|
+
"--disallowedTools",
|
|
116
|
+
"--effort",
|
|
117
|
+
"--fallback-model",
|
|
118
|
+
"--file",
|
|
119
|
+
"--input-format",
|
|
120
|
+
"--json-schema",
|
|
121
|
+
"--max-budget-usd",
|
|
122
|
+
"--mcp-config",
|
|
123
|
+
"--model",
|
|
124
|
+
"--name",
|
|
125
|
+
"--output-format",
|
|
126
|
+
"--permission-mode",
|
|
127
|
+
"--plugin-dir",
|
|
128
|
+
"--plugin-url",
|
|
129
|
+
"--remote-control-session-name-prefix",
|
|
130
|
+
"--settings",
|
|
131
|
+
"--system-prompt",
|
|
132
|
+
"--system-prompt-file",
|
|
133
|
+
"--tools",
|
|
134
|
+
"-n",
|
|
135
|
+
]),
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
"codex",
|
|
139
|
+
new Set([
|
|
140
|
+
"--add-dir",
|
|
141
|
+
"--ask-for-approval",
|
|
142
|
+
"--cd",
|
|
143
|
+
"--config",
|
|
144
|
+
"--disable",
|
|
145
|
+
"--enable",
|
|
146
|
+
"--image",
|
|
147
|
+
"--local-provider",
|
|
148
|
+
"--model",
|
|
149
|
+
"--profile",
|
|
150
|
+
"--remote",
|
|
151
|
+
"--remote-auth-token-env",
|
|
152
|
+
"--sandbox",
|
|
153
|
+
"-C",
|
|
154
|
+
"-a",
|
|
155
|
+
"-c",
|
|
156
|
+
"-i",
|
|
157
|
+
"-m",
|
|
158
|
+
"-p",
|
|
159
|
+
"-s",
|
|
160
|
+
]),
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
"opencode",
|
|
164
|
+
new Set([
|
|
165
|
+
"--agent",
|
|
166
|
+
"--cors",
|
|
167
|
+
"--hostname",
|
|
168
|
+
"--log-level",
|
|
169
|
+
"--mdns-domain",
|
|
170
|
+
"--model",
|
|
171
|
+
"--port",
|
|
172
|
+
"--prompt",
|
|
173
|
+
"--replay-limit",
|
|
174
|
+
"--session",
|
|
175
|
+
"-m",
|
|
176
|
+
"-s",
|
|
177
|
+
]),
|
|
178
|
+
],
|
|
179
|
+
[
|
|
180
|
+
"pi",
|
|
181
|
+
new Set([
|
|
182
|
+
"--api-key",
|
|
183
|
+
"--append-system-prompt",
|
|
184
|
+
"--exclude-tools",
|
|
185
|
+
"--export",
|
|
186
|
+
"--extension",
|
|
187
|
+
"--fork",
|
|
188
|
+
"--mode",
|
|
189
|
+
"--model",
|
|
190
|
+
"--models",
|
|
191
|
+
"--name",
|
|
192
|
+
"--prompt-template",
|
|
193
|
+
"--provider",
|
|
194
|
+
"--session",
|
|
195
|
+
"--session-dir",
|
|
196
|
+
"--session-id",
|
|
197
|
+
"--skill",
|
|
198
|
+
"--system-prompt",
|
|
199
|
+
"--theme",
|
|
200
|
+
"--thinking",
|
|
201
|
+
"--tools",
|
|
202
|
+
"-e",
|
|
203
|
+
"-n",
|
|
204
|
+
"-t",
|
|
205
|
+
"-xt",
|
|
206
|
+
]),
|
|
207
|
+
],
|
|
208
|
+
]);
|
|
209
|
+
// Profiles predating the sentinel got materialized into user config the first
|
|
210
|
+
// time anyone touched the profile list (host-page.tsx patches the whole list on
|
|
211
|
+
// any add, edit, or reorder). Those users would otherwise be stuck with
|
|
212
|
+
// launch-only versions of the agents we ship, while a fresh install gets prompt
|
|
213
|
+
// support, so a profile still pointing at one of those agents adopts the
|
|
214
|
+
// trailing sentinel on read.
|
|
215
|
+
//
|
|
216
|
+
// Keyed on the command's base name, not the profile id: profiles created
|
|
217
|
+
// through the settings UI get generated ids (`profile_<timestamp>_<random>`),
|
|
218
|
+
// so a real user's Codex profile is never id `codex`. The base name also
|
|
219
|
+
// normalizes `/usr/local/bin/codex` and `codex.cmd` onto the same agent.
|
|
220
|
+
//
|
|
221
|
+
// User args are preserved: `codex --yolo` becomes `codex --yolo {{{prompt}}}`.
|
|
222
|
+
// The appended form comes from the shipped default for that command, so
|
|
223
|
+
// opencode gets `--prompt=` rather than a trailing positional, which it would
|
|
224
|
+
// read as a project directory. A profile that already carries the sentinel
|
|
225
|
+
// anywhere is left alone, and so is one pointing at any other command. Nothing
|
|
226
|
+
// is written back, so this stays a read-time adoption with no version stamp and
|
|
227
|
+
// no daemon-start hook.
|
|
228
|
+
// Any positional means this is not the bare interactive form we can safely
|
|
229
|
+
// adopt. It may be a subcommand (`codex --profile work login`) or an existing
|
|
230
|
+
// prompt/project argument. Option assignments carry their value inline; known
|
|
231
|
+
// split options consume the next argv entry before positional detection.
|
|
232
|
+
function hasPositionalArg(command, args) {
|
|
233
|
+
const optionsWithValues = GLOBAL_OPTIONS_WITH_VALUES_BY_COMMAND.get(command) ?? new Set();
|
|
234
|
+
let expectsOptionValue = false;
|
|
235
|
+
for (const arg of args) {
|
|
236
|
+
if (expectsOptionValue) {
|
|
237
|
+
expectsOptionValue = false;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (arg === "--") {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
if (!arg.startsWith("-")) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
const separator = arg.indexOf("=");
|
|
247
|
+
const option = separator === -1 ? arg : arg.slice(0, separator);
|
|
248
|
+
expectsOptionValue = separator === -1 && optionsWithValues.has(option);
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
function adoptPromptSentinel(profile) {
|
|
253
|
+
const command = getCommandBaseName(profile.command);
|
|
254
|
+
const promptArgs = PROMPT_ARGS_BY_COMMAND.get(command);
|
|
255
|
+
if (!promptArgs || promptArgs.length === 0) {
|
|
256
|
+
return profile;
|
|
257
|
+
}
|
|
258
|
+
const args = profile.args ?? [];
|
|
259
|
+
if (profileTakesPrompt(profile) || hasPositionalArg(command, args)) {
|
|
260
|
+
return profile;
|
|
261
|
+
}
|
|
262
|
+
return { ...profile, args: [...args, ...promptArgs] };
|
|
263
|
+
}
|
|
22
264
|
export function resolveTerminalProfiles(terminalProfiles) {
|
|
23
265
|
if (terminalProfiles === undefined) {
|
|
24
266
|
return DEFAULT_TERMINAL_PROFILES;
|
|
25
267
|
}
|
|
26
|
-
return terminalProfiles;
|
|
268
|
+
return terminalProfiles.map(adoptPromptSentinel);
|
|
27
269
|
}
|
|
28
270
|
//# sourceMappingURL=terminal-profiles.js.map
|
|
@@ -12,8 +12,8 @@ export interface ToolCallDisplayModel {
|
|
|
12
12
|
* shows a tool/action name without a full timeline item to run through
|
|
13
13
|
* {@link buildToolCallDisplayModel} (the visualizer's action labels, sub-agent
|
|
14
14
|
* activity rows). Strips the MCP/Otto namespace, consults the known-tool
|
|
15
|
-
* registry, then title-cases as a fallback - so "
|
|
16
|
-
* "otto.
|
|
15
|
+
* registry, then title-cases as a fallback - so "mcp__otto__suggest_task",
|
|
16
|
+
* "otto.suggest_task", and a bare "suggest_task" all render as "Suggest Task".
|
|
17
17
|
*/
|
|
18
18
|
export declare function getToolDisplayName(name: string): string;
|
|
19
19
|
export declare function buildToolCallDisplayModel(input: ToolCallDisplayInput): ToolCallDisplayModel;
|
|
@@ -11,7 +11,7 @@ function readString(value) {
|
|
|
11
11
|
//
|
|
12
12
|
// Only tools whose bare id does NOT title-case cleanly on its own need an entry
|
|
13
13
|
// here: lowercase compound names ("websearch") a splitter can't segment, or
|
|
14
|
-
// ones we want to word deliberately. Well-formed snake_case ("
|
|
14
|
+
// ones we want to word deliberately. Well-formed snake_case ("suggest_task") and
|
|
15
15
|
// camelCase ("WebSearch") names are handled by the algorithmic humanizer below
|
|
16
16
|
// and do NOT need listing.
|
|
17
17
|
//
|
|
@@ -35,7 +35,7 @@ const KNOWN_TOOL_DISPLAY_NAMES = {
|
|
|
35
35
|
ls: "List Files",
|
|
36
36
|
};
|
|
37
37
|
// Split camelCase / PascalCase and separator-delimited identifiers into words,
|
|
38
|
-
// then Title-Case them: "WebSearch" -> "Web Search", "
|
|
38
|
+
// then Title-Case them: "WebSearch" -> "Web Search", "suggest_task" -> "Suggest
|
|
39
39
|
// Task", "HTTPServer" -> "HTTP Server".
|
|
40
40
|
function titleCaseToolId(value) {
|
|
41
41
|
return value
|
|
@@ -52,8 +52,8 @@ function humanizeToolName(name) {
|
|
|
52
52
|
if (!trimmed) {
|
|
53
53
|
return name;
|
|
54
54
|
}
|
|
55
|
-
// Strip the transport namespace first ("
|
|
56
|
-
// "
|
|
55
|
+
// Strip the transport namespace first ("mcp__otto__suggest_task" ->
|
|
56
|
+
// "suggest_task", "otto.list_chats" -> "list_chats") so both the known-tool
|
|
57
57
|
// lookup and the fallback operate on the bare tool id.
|
|
58
58
|
const leaf = getMcpToolLeafName(trimmed) ?? getOttoToolLeafName(trimmed);
|
|
59
59
|
if (leaf) {
|
|
@@ -66,8 +66,8 @@ function humanizeToolName(name) {
|
|
|
66
66
|
* shows a tool/action name without a full timeline item to run through
|
|
67
67
|
* {@link buildToolCallDisplayModel} (the visualizer's action labels, sub-agent
|
|
68
68
|
* activity rows). Strips the MCP/Otto namespace, consults the known-tool
|
|
69
|
-
* registry, then title-cases as a fallback - so "
|
|
70
|
-
* "otto.
|
|
69
|
+
* registry, then title-cases as a fallback - so "mcp__otto__suggest_task",
|
|
70
|
+
* "otto.suggest_task", and a bare "suggest_task" all render as "Suggest Task".
|
|
71
71
|
*/
|
|
72
72
|
export function getToolDisplayName(name) {
|
|
73
73
|
return humanizeToolName(name);
|