@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.
Files changed (43) hide show
  1. package/dist/agent-labels.d.ts +3 -0
  2. package/dist/agent-labels.js +10 -0
  3. package/dist/{agent-personalities.d.ts → agent-profiles.d.ts} +13 -3
  4. package/dist/{agent-personalities.js → agent-profiles.js} +27 -11
  5. package/dist/agent-teams.d.ts +5 -5
  6. package/dist/agent-teams.js +1 -1
  7. package/dist/agent-types.d.ts +24 -4
  8. package/dist/binary-frames/terminal.d.ts +4 -0
  9. package/dist/binary-frames/terminal.js +1 -0
  10. package/dist/brain.d.ts +91 -0
  11. package/dist/brain.js +19 -1
  12. package/dist/chat/rpc-schemas.js +1 -0
  13. package/dist/chat/types.js +1 -0
  14. package/dist/client-capabilities.d.ts +1 -0
  15. package/dist/client-capabilities.js +4 -0
  16. package/dist/daemon-config.d.ts +10 -0
  17. package/dist/daemon-config.js +6 -0
  18. package/dist/default-personalities.d.ts +2 -2
  19. package/dist/default-personalities.js +2 -2
  20. package/dist/generated/validation/ws-outbound.aot.js +65204 -60319
  21. package/dist/loop/rpc-schemas.js +1 -0
  22. package/dist/messages.d.ts +4146 -316
  23. package/dist/messages.js +483 -2
  24. package/dist/personality-schemas.d.ts +135 -0
  25. package/dist/personality-schemas.js +63 -0
  26. package/dist/provider-manifest.js +7 -0
  27. package/dist/provider-snapshot-codec.d.ts +18 -0
  28. package/dist/provider-snapshot-codec.js +71 -0
  29. package/dist/schedule/rpc-schemas.d.ts +16 -8
  30. package/dist/schedule/types.d.ts +6 -3
  31. package/dist/schedule/types.js +8 -1
  32. package/dist/search/text-match.d.ts +55 -0
  33. package/dist/search/text-match.js +262 -0
  34. package/dist/suggested-tasks.js +1 -1
  35. package/dist/terminal-input-mode.d.ts +4 -0
  36. package/dist/terminal-input-mode.js +35 -6
  37. package/dist/terminal-key-input.js +15 -6
  38. package/dist/terminal-profiles.d.ts +35 -0
  39. package/dist/terminal-profiles.js +246 -4
  40. package/dist/tool-call-display.d.ts +2 -2
  41. package/dist/tool-call-display.js +6 -6
  42. package/dist/validation/ws-outbound-schema-metadata.d.ts +677 -28
  43. package/package.json +1 -1
@@ -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
- { id: "opencode", name: "OpenCode", command: "opencode", icon: "opencode" },
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 "mcp__otto__spawn_task",
16
- * "otto.spawn_task", and a bare "spawn_task" all render as "Spawn Task".
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 ("spawn_task") and
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", "spawn_task" -> "Spawn
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 ("mcp__otto__spawn_task" ->
56
- // "spawn_task", "otto.list_agents" -> "list_agents") so both the known-tool
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 "mcp__otto__spawn_task",
70
- * "otto.spawn_task", and a bare "spawn_task" all render as "Spawn Task".
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);