@otto-code/protocol 0.6.1 → 0.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-types.d.ts +30 -0
- package/dist/browser-automation/capabilities.d.ts +1 -1
- package/dist/browser-automation/rpc-schemas.d.ts +199 -1
- package/dist/browser-automation/rpc-schemas.js +71 -0
- package/dist/generated/validation/ws-outbound.aot.js +29483 -28473
- package/dist/messages.d.ts +491 -7
- package/dist/messages.js +127 -5
- package/dist/observed-subagent-title.d.ts +26 -0
- package/dist/observed-subagent-title.js +63 -0
- package/dist/tool-call-display.d.ts +9 -0
- package/dist/tool-call-display.js +60 -15
- package/dist/tool-name-normalization.d.ts +16 -0
- package/dist/tool-name-normalization.js +26 -0
- package/dist/validation/ws-outbound-schema-metadata.d.ts +66 -0
- package/package.json +1 -1
package/dist/messages.js
CHANGED
|
@@ -177,6 +177,23 @@ const AgentPersonalityVoiceSchema = z
|
|
|
177
177
|
name: z.string().min(1),
|
|
178
178
|
})
|
|
179
179
|
.passthrough();
|
|
180
|
+
// The three Visualizer lifecycle moments a personality voice-cue line can
|
|
181
|
+
// belong to. Protocol owns this vocabulary — the daemon's cue generator, the
|
|
182
|
+
// personality editor, and the Visualizer playback hook all import it from here.
|
|
183
|
+
export const CUE_MOMENTS = ["join", "thinking", "done"];
|
|
184
|
+
// Pre-generated (and user-editable) spoken "voice cue" lines for the personality
|
|
185
|
+
// — a few short variations for each of three Visualizer moments (its node joins
|
|
186
|
+
// the graph, first starts thinking, completes). Stored on the personality so
|
|
187
|
+
// they're deterministic and hand-tunable in the editor; the Visualizer reads
|
|
188
|
+
// them directly (no runtime generation). All groups optional/loose — a
|
|
189
|
+
// personality may have none, or only some. See docs/visualizer.md "Voice cues".
|
|
190
|
+
const AgentPersonalityVoiceCuesSchema = z
|
|
191
|
+
.object({
|
|
192
|
+
join: z.array(z.string()).optional(),
|
|
193
|
+
thinking: z.array(z.string()).optional(),
|
|
194
|
+
done: z.array(z.string()).optional(),
|
|
195
|
+
})
|
|
196
|
+
.passthrough();
|
|
180
197
|
// A named, reusable agent template stored per-host. `id` is the stable identity
|
|
181
198
|
// everything binds to; `name` is a freely-renamable label. Effort and roles are
|
|
182
199
|
// plain strings on the wire (like speech engine/model ids) so the daemon can
|
|
@@ -199,6 +216,7 @@ export const AgentPersonalitySchema = z
|
|
|
199
216
|
roles: z.array(z.string().min(1)).optional(),
|
|
200
217
|
spinner: AgentPersonalitySpinnerSchema.optional(),
|
|
201
218
|
voice: AgentPersonalityVoiceSchema.optional(),
|
|
219
|
+
voiceCues: AgentPersonalityVoiceCuesSchema.optional(),
|
|
202
220
|
})
|
|
203
221
|
.passthrough();
|
|
204
222
|
const MutableAgentPersonalitiesConfigSchema = z
|
|
@@ -367,6 +385,22 @@ const AgentProviderNoticeSchema = z.discriminatedUnion("type", [
|
|
|
367
385
|
z.object({ type: z.literal("warning"), message: z.string() }),
|
|
368
386
|
z.object({ type: z.literal("error"), message: z.string() }),
|
|
369
387
|
]);
|
|
388
|
+
// Provider-reported plan rate-limit status (e.g. Claude claude.ai plan
|
|
389
|
+
// windows), pushed on the agent stream when it changes. Presentation-only:
|
|
390
|
+
// the app decides whether to show it (rateLimitWarningsEnabled setting).
|
|
391
|
+
export const AgentRateLimitInfoSchema = z.object({
|
|
392
|
+
status: z.enum(["allowed", "warning", "rejected"]),
|
|
393
|
+
// Percentage of the limit window used, 0-100. Absent when the provider
|
|
394
|
+
// does not report it (Claude only includes it near the limit).
|
|
395
|
+
utilizationPercent: z.number().optional(),
|
|
396
|
+
// Provider-reported window identifier, e.g. "five_hour" | "seven_day".
|
|
397
|
+
// Open set — display code falls back to a generic label for unknown values.
|
|
398
|
+
limitType: z.string().optional(),
|
|
399
|
+
// ISO 8601 timestamp when the window resets.
|
|
400
|
+
resetsAt: z.string().optional(),
|
|
401
|
+
// True when usage is currently drawing from overage/extra usage credits.
|
|
402
|
+
isUsingOverage: z.boolean().optional(),
|
|
403
|
+
});
|
|
370
404
|
export const AgentFeatureToggleSchema = z.object({
|
|
371
405
|
type: z.literal("toggle"),
|
|
372
406
|
id: z.string(),
|
|
@@ -436,6 +470,13 @@ const AgentCapabilityFlagsSchema = z
|
|
|
436
470
|
supportsRewindBoth: z.boolean().optional().default(false),
|
|
437
471
|
})
|
|
438
472
|
.catchall(z.boolean());
|
|
473
|
+
const ContextCompositionSchema = z.object({
|
|
474
|
+
systemPrompt: z.number().optional(),
|
|
475
|
+
userMessages: z.number().optional(),
|
|
476
|
+
toolResults: z.number().optional(),
|
|
477
|
+
reasoning: z.number().optional(),
|
|
478
|
+
subagentResults: z.number().optional(),
|
|
479
|
+
});
|
|
439
480
|
const AgentUsageSchema = z.object({
|
|
440
481
|
inputTokens: z.number().optional(),
|
|
441
482
|
cachedInputTokens: z.number().optional(),
|
|
@@ -443,6 +484,9 @@ const AgentUsageSchema = z.object({
|
|
|
443
484
|
totalCostUsd: z.number().optional(),
|
|
444
485
|
contextWindowMaxTokens: z.number().optional(),
|
|
445
486
|
contextWindowUsedTokens: z.number().optional(),
|
|
487
|
+
// Provider-graded context breakdown for the visualizer ring/bar; absent ⇒
|
|
488
|
+
// occupancy only (pre-composition behavior). See ContextComposition.
|
|
489
|
+
contextComposition: ContextCompositionSchema.optional(),
|
|
446
490
|
});
|
|
447
491
|
const AgentSessionConfigSchema = z.object({
|
|
448
492
|
provider: AgentProviderSchema,
|
|
@@ -754,6 +798,22 @@ export const AgentStreamEventPayloadSchema = z.discriminatedUnion("type", [
|
|
|
754
798
|
})
|
|
755
799
|
.optional(),
|
|
756
800
|
}),
|
|
801
|
+
// Predicted next-user-prompt suggestion emitted after a turn. Transient: the
|
|
802
|
+
// app shows the latest as composer ghost text (Tab to accept) and clears it on
|
|
803
|
+
// the next turn_started. COMPAT(promptSuggestions): added in v0.6.3.
|
|
804
|
+
z.object({
|
|
805
|
+
type: z.literal("prompt_suggestion"),
|
|
806
|
+
provider: AgentProviderSchema,
|
|
807
|
+
suggestion: z.string(),
|
|
808
|
+
}),
|
|
809
|
+
// Provider-reported plan rate-limit status (Claude claude.ai plan windows).
|
|
810
|
+
// Transient: the app shows a suppressible warning strip near the composer.
|
|
811
|
+
// Deduped provider-side. COMPAT(rateLimitEvents): added in v0.6.3.
|
|
812
|
+
z.object({
|
|
813
|
+
type: z.literal("rate_limit_updated"),
|
|
814
|
+
provider: AgentProviderSchema,
|
|
815
|
+
info: AgentRateLimitInfoSchema,
|
|
816
|
+
}),
|
|
757
817
|
]);
|
|
758
818
|
const AgentPersistenceHandleSchema = z
|
|
759
819
|
.object({
|
|
@@ -1229,6 +1289,32 @@ export const SpeechTtsPreviewRequestSchema = z.object({
|
|
|
1229
1289
|
.passthrough()
|
|
1230
1290
|
.optional(),
|
|
1231
1291
|
});
|
|
1292
|
+
// COMPAT(visualizerVoiceCues): added in v0.6.3; gate lives in
|
|
1293
|
+
// features.visualizerVoiceCues. Author short spoken "cue" lines for a
|
|
1294
|
+
// personality — a handful of variations each for three Visualizer moments
|
|
1295
|
+
// (join / thinking / done) — via the Writer mini-task chain, flavored by the
|
|
1296
|
+
// persona's `name` + `prompt`. The persona is passed inline (not a stored id)
|
|
1297
|
+
// so the personality editor can generate for an unsaved draft too; the result
|
|
1298
|
+
// is stored on the personality (`voiceCues`) and edited there, so this is an
|
|
1299
|
+
// editor-time action, not a runtime one. `cwd` scopes provider resolution to a
|
|
1300
|
+
// workspace; omitted falls back to any resolvable one.
|
|
1301
|
+
export const VisualizerVoiceCuesGenerateRequestSchema = z.object({
|
|
1302
|
+
type: z.literal("visualizer.voiceCues.generate.request"),
|
|
1303
|
+
requestId: z.string(),
|
|
1304
|
+
name: z.string(),
|
|
1305
|
+
prompt: z.string().optional(),
|
|
1306
|
+
cwd: z.string().optional(),
|
|
1307
|
+
// The persona's roles (e.g. "researcher", "coder") so the writer can flavor
|
|
1308
|
+
// the lines to what the agent does. Permissive strings to match the stored
|
|
1309
|
+
// personality shape (forward-compatible with roles this daemon predates).
|
|
1310
|
+
roles: z.array(z.string().min(1)).optional(),
|
|
1311
|
+
// When present, author only this one moment's lines (a focused single-moment
|
|
1312
|
+
// prompt) and return only that group. The editor issues one request per
|
|
1313
|
+
// moment so it can show generation progress and keep the moments distinct.
|
|
1314
|
+
// Omitted → author all three at once (the original all-in-one path, still
|
|
1315
|
+
// used by older clients).
|
|
1316
|
+
moment: z.enum(CUE_MOMENTS).optional(),
|
|
1317
|
+
});
|
|
1232
1318
|
export const AgentPersonalitiesGetStatsRequestSchema = z.object({
|
|
1233
1319
|
type: z.literal("agentPersonalities.get_stats.request"),
|
|
1234
1320
|
requestId: z.string(),
|
|
@@ -1620,11 +1706,22 @@ const SuggestedTaskActionResponsePayloadSchema = z.object({
|
|
|
1620
1706
|
error: z.string().nullable(),
|
|
1621
1707
|
});
|
|
1622
1708
|
// Start one or more suggested tasks, applying the SAME mode to each — no
|
|
1623
|
-
// combining. `
|
|
1624
|
-
// a
|
|
1625
|
-
//
|
|
1626
|
-
//
|
|
1627
|
-
|
|
1709
|
+
// combining. Four modes, only `subagent` links the new agent to the parent:
|
|
1710
|
+
// - `new_chat`: a fresh independent agent in its own tab, same repo/cwd, NO
|
|
1711
|
+
// parent link — survives the parent's cancel/archive.
|
|
1712
|
+
// - `subagent`: a bound child agent that shows in the parent's Subagents
|
|
1713
|
+
// track and archive-cascades with it.
|
|
1714
|
+
// - `worktree`: an independent agent on a new git worktree (auto branch-off),
|
|
1715
|
+
// isolated workspace — also unlinked from the parent.
|
|
1716
|
+
// - `in_session`: steers the parent agent with the task prompt (no new agent).
|
|
1717
|
+
// The daemon resolves the parent agent's brain (provider/model/personality) so a
|
|
1718
|
+
// started task continues the suggesting agent.
|
|
1719
|
+
export const TasksSuggestedStartModeSchema = z.enum([
|
|
1720
|
+
"new_chat",
|
|
1721
|
+
"subagent",
|
|
1722
|
+
"worktree",
|
|
1723
|
+
"in_session",
|
|
1724
|
+
]);
|
|
1628
1725
|
export const TasksSuggestedStartRequestMessageSchema = z.object({
|
|
1629
1726
|
type: z.literal("tasks.suggested.start.request"),
|
|
1630
1727
|
parentAgentId: z.string(),
|
|
@@ -2608,6 +2705,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
|
|
2608
2705
|
SetDaemonConfigRequestMessageSchema,
|
|
2609
2706
|
SpeechSettingsGetOptionsRequestSchema,
|
|
2610
2707
|
SpeechTtsPreviewRequestSchema,
|
|
2708
|
+
VisualizerVoiceCuesGenerateRequestSchema,
|
|
2611
2709
|
AgentPersonalitiesGetStatsRequestSchema,
|
|
2612
2710
|
ReadProjectConfigRequestMessageSchema,
|
|
2613
2711
|
WriteProjectConfigRequestMessageSchema,
|
|
@@ -2956,6 +3054,8 @@ export const ServerInfoStatusPayloadSchema = z
|
|
|
2956
3054
|
agentPersonalities: z.boolean().optional(),
|
|
2957
3055
|
// COMPAT(ttsPreview): added in v0.4.7, drop the gate when daemon floor >= v0.4.7.
|
|
2958
3056
|
ttsPreview: z.boolean().optional(),
|
|
3057
|
+
// COMPAT(visualizerVoiceCues): added in v0.6.3, drop the gate when daemon floor >= v0.6.3.
|
|
3058
|
+
visualizerVoiceCues: z.boolean().optional(),
|
|
2959
3059
|
// COMPAT(setAgentPersonality): added in v0.5.0, drop the gate when daemon floor >= v0.5.0.
|
|
2960
3060
|
setAgentPersonality: z.boolean().optional(),
|
|
2961
3061
|
// COMPAT(checkoutGitCommit): added in v0.5.1, drop the gate when daemon floor >= v0.5.1.
|
|
@@ -2984,6 +3084,15 @@ export const ServerInfoStatusPayloadSchema = z
|
|
|
2984
3084
|
// permissions). The client gates this behind an "edit anyway" warning;
|
|
2985
3085
|
// an old daemon leaves the flag unset and out-of-project files are not offered.
|
|
2986
3086
|
fileOutsideWorkspace: z.boolean().optional(),
|
|
3087
|
+
// COMPAT(promptSuggestions): added in v0.6.3, drop the gate when daemon floor >= v0.6.3.
|
|
3088
|
+
// Set when the daemon emits agent_stream `prompt_suggestion` events (native
|
|
3089
|
+
// Claude next-prompt predictions). The client gates the Settings toggle on
|
|
3090
|
+
// this; suggestions already degrade silently on an old daemon (no event).
|
|
3091
|
+
promptSuggestions: z.boolean().optional(),
|
|
3092
|
+
// COMPAT(rateLimitEvents): added in v0.6.3, drop the gate when daemon floor >= v0.6.3.
|
|
3093
|
+
// Set when the daemon emits agent_stream `rate_limit_updated` events (Claude
|
|
3094
|
+
// plan rate-limit status). Warnings degrade silently on an old daemon (no event).
|
|
3095
|
+
rateLimitEvents: z.boolean().optional(),
|
|
2987
3096
|
})
|
|
2988
3097
|
.optional(),
|
|
2989
3098
|
})
|
|
@@ -3666,6 +3775,18 @@ export const SpeechTtsPreviewResponseSchema = z.object({
|
|
|
3666
3775
|
})
|
|
3667
3776
|
.passthrough(),
|
|
3668
3777
|
});
|
|
3778
|
+
export const VisualizerVoiceCuesGenerateResponseSchema = z.object({
|
|
3779
|
+
type: z.literal("visualizer.voiceCues.generate.response"),
|
|
3780
|
+
payload: z
|
|
3781
|
+
.object({
|
|
3782
|
+
requestId: z.string(),
|
|
3783
|
+
// Absent when generation failed (see error) or no writer/provider
|
|
3784
|
+
// resolves on this host. Reuses the stored-cues shape.
|
|
3785
|
+
cues: AgentPersonalityVoiceCuesSchema.optional(),
|
|
3786
|
+
error: z.string().optional(),
|
|
3787
|
+
})
|
|
3788
|
+
.passthrough(),
|
|
3789
|
+
});
|
|
3669
3790
|
export const AgentPersonalitiesGetStatsResponseSchema = z.object({
|
|
3670
3791
|
type: z.literal("agentPersonalities.get_stats.response"),
|
|
3671
3792
|
payload: z
|
|
@@ -5141,6 +5262,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
|
|
5141
5262
|
SetDaemonConfigResponseMessageSchema,
|
|
5142
5263
|
SpeechSettingsGetOptionsResponseSchema,
|
|
5143
5264
|
SpeechTtsPreviewResponseSchema,
|
|
5265
|
+
VisualizerVoiceCuesGenerateResponseSchema,
|
|
5144
5266
|
AgentPersonalitiesGetStatsResponseSchema,
|
|
5145
5267
|
ReadProjectConfigResponseMessageSchema,
|
|
5146
5268
|
WriteProjectConfigResponseMessageSchema,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Hard cap so no provider summary can produce a wall-of-text label. */
|
|
2
|
+
export declare const OBSERVED_SUBAGENT_TITLE_MAX = 60;
|
|
3
|
+
export declare function normalizeObservedTitleSource(value: string | undefined | null): string | null;
|
|
4
|
+
export declare function normalizeObservedSubagentType(value: string | undefined | null): string | null;
|
|
5
|
+
/**
|
|
6
|
+
* Derive the frozen row name for an observed subagent. Prefer the stable
|
|
7
|
+
* `subAgentType` (e.g. "code-explorer") over the description — a
|
|
8
|
+
* `task_progress` description is the ever-changing AI summary, which must
|
|
9
|
+
* never become the label. Generic catch-all types ("general-purpose") are
|
|
10
|
+
* skipped in favor of the description. Callers freeze the result at the first
|
|
11
|
+
* named update.
|
|
12
|
+
*/
|
|
13
|
+
export declare function deriveObservedSubagentTitle(update: {
|
|
14
|
+
subAgentType?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}): string;
|
|
17
|
+
/**
|
|
18
|
+
* True when this update carries a real name source we can freeze the title on.
|
|
19
|
+
* A generic catch-all type alone doesn't count — freezing "general-purpose"
|
|
20
|
+
* would lock out the description a later update may carry.
|
|
21
|
+
*/
|
|
22
|
+
export declare function observedUpdateHasTitleSource(update: {
|
|
23
|
+
subAgentType?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
}): boolean;
|
|
26
|
+
//# sourceMappingURL=observed-subagent-title.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Shared naming rule for provider-managed (observed) subagents. This is the
|
|
2
|
+
// single source of truth for how an observed subagent is titled — the daemon
|
|
3
|
+
// uses it to freeze the track-row label (agent-projections.ts), and the app's
|
|
4
|
+
// visualizer uses it so page-side child labels (subagent_dispatch/return
|
|
5
|
+
// particles ride an edge keyed by child NAME) resolve to exactly the same
|
|
6
|
+
// string as the child node the daemon-titled agent row spawned. If the two
|
|
7
|
+
// ever diverge, the dispatch/return visuals silently stop rendering.
|
|
8
|
+
// See docs/agent-lifecycle.md (Item 4) and docs/visualizer.md.
|
|
9
|
+
/** Hard cap so no provider summary can produce a wall-of-text label. */
|
|
10
|
+
export const OBSERVED_SUBAGENT_TITLE_MAX = 60;
|
|
11
|
+
/**
|
|
12
|
+
* Catch-all subagent types that make lousy row labels — Claude's default Task
|
|
13
|
+
* runs as "general-purpose". These never become the title; the task
|
|
14
|
+
* description names the row instead (user-locked).
|
|
15
|
+
*/
|
|
16
|
+
const GENERIC_OBSERVED_SUBAGENT_TYPES = new Set([
|
|
17
|
+
"general-purpose",
|
|
18
|
+
"general",
|
|
19
|
+
"task",
|
|
20
|
+
"agent",
|
|
21
|
+
"subagent",
|
|
22
|
+
]);
|
|
23
|
+
export function normalizeObservedTitleSource(value) {
|
|
24
|
+
if (typeof value !== "string") {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
28
|
+
return collapsed.length > 0 ? collapsed : null;
|
|
29
|
+
}
|
|
30
|
+
export function normalizeObservedSubagentType(value) {
|
|
31
|
+
const normalized = normalizeObservedTitleSource(value);
|
|
32
|
+
if (normalized === null || GENERIC_OBSERVED_SUBAGENT_TYPES.has(normalized.toLowerCase())) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Derive the frozen row name for an observed subagent. Prefer the stable
|
|
39
|
+
* `subAgentType` (e.g. "code-explorer") over the description — a
|
|
40
|
+
* `task_progress` description is the ever-changing AI summary, which must
|
|
41
|
+
* never become the label. Generic catch-all types ("general-purpose") are
|
|
42
|
+
* skipped in favor of the description. Callers freeze the result at the first
|
|
43
|
+
* named update.
|
|
44
|
+
*/
|
|
45
|
+
export function deriveObservedSubagentTitle(update) {
|
|
46
|
+
const base = normalizeObservedSubagentType(update.subAgentType) ??
|
|
47
|
+
normalizeObservedTitleSource(update.description) ??
|
|
48
|
+
"Subagent";
|
|
49
|
+
if (base.length <= OBSERVED_SUBAGENT_TITLE_MAX) {
|
|
50
|
+
return base;
|
|
51
|
+
}
|
|
52
|
+
return `${base.slice(0, OBSERVED_SUBAGENT_TITLE_MAX - 1).trimEnd()}…`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* True when this update carries a real name source we can freeze the title on.
|
|
56
|
+
* A generic catch-all type alone doesn't count — freezing "general-purpose"
|
|
57
|
+
* would lock out the description a later update may carry.
|
|
58
|
+
*/
|
|
59
|
+
export function observedUpdateHasTitleSource(update) {
|
|
60
|
+
return (normalizeObservedSubagentType(update.subAgentType) !== null ||
|
|
61
|
+
normalizeObservedTitleSource(update.description) !== null);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=observed-subagent-title.js.map
|
|
@@ -7,5 +7,14 @@ export interface ToolCallDisplayModel {
|
|
|
7
7
|
summary?: string;
|
|
8
8
|
errorText?: string;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Friendly display name for a bare tool identifier, used at every surface that
|
|
12
|
+
* shows a tool/action name without a full timeline item to run through
|
|
13
|
+
* {@link buildToolCallDisplayModel} (the visualizer's action labels, sub-agent
|
|
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".
|
|
17
|
+
*/
|
|
18
|
+
export declare function getToolDisplayName(name: string): string;
|
|
10
19
|
export declare function buildToolCallDisplayModel(input: ToolCallDisplayInput): ToolCallDisplayModel;
|
|
11
20
|
//# sourceMappingURL=tool-call-display.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getMcpToolLeafName, getOttoToolLeafName } from "./tool-name-normalization.js";
|
|
2
2
|
import { stripCwdPrefix } from "./path-utils.js";
|
|
3
3
|
function isRecord(value) {
|
|
4
4
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -6,26 +6,71 @@ function isRecord(value) {
|
|
|
6
6
|
function readString(value) {
|
|
7
7
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
8
8
|
}
|
|
9
|
+
// Curated display names — the registry of tools we explicitly "know about".
|
|
10
|
+
// Keyed by the lowercased leaf name (transport namespace already stripped).
|
|
11
|
+
//
|
|
12
|
+
// Only tools whose bare id does NOT title-case cleanly on its own need an entry
|
|
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
|
|
15
|
+
// camelCase ("WebSearch") names are handled by the algorithmic humanizer below
|
|
16
|
+
// and do NOT need listing.
|
|
17
|
+
//
|
|
18
|
+
// Anything not here falls through to the humanizer and still renders readably —
|
|
19
|
+
// so an unmapped tool shows up looking slightly generic (e.g. a stray provider
|
|
20
|
+
// tool), which is the cue to add it here. Extend this map to teach Otto a tool.
|
|
21
|
+
const KNOWN_TOOL_DISPLAY_NAMES = {
|
|
22
|
+
websearch: "Web Search",
|
|
23
|
+
web_search: "Web Search",
|
|
24
|
+
webfetch: "Web Fetch",
|
|
25
|
+
web_fetch: "Web Fetch",
|
|
26
|
+
todowrite: "Update Todos",
|
|
27
|
+
todoread: "Read Todos",
|
|
28
|
+
multiedit: "Multi Edit",
|
|
29
|
+
notebookedit: "Edit Notebook",
|
|
30
|
+
notebookread: "Read Notebook",
|
|
31
|
+
bashoutput: "Bash Output",
|
|
32
|
+
killshell: "Kill Shell",
|
|
33
|
+
exitplanmode: "Exit Plan Mode",
|
|
34
|
+
applypatch: "Apply Patch",
|
|
35
|
+
ls: "List Files",
|
|
36
|
+
};
|
|
37
|
+
// Split camelCase / PascalCase and separator-delimited identifiers into words,
|
|
38
|
+
// then Title-Case them: "WebSearch" -> "Web Search", "spawn_task" -> "Spawn
|
|
39
|
+
// Task", "HTTPServer" -> "HTTP Server".
|
|
40
|
+
function titleCaseToolId(value) {
|
|
41
|
+
return value
|
|
42
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
43
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
44
|
+
.replace(/[._-]+/g, " ")
|
|
45
|
+
.split(" ")
|
|
46
|
+
.filter((segment) => segment.length > 0)
|
|
47
|
+
.map((segment) => `${segment[0]?.toUpperCase() ?? ""}${segment.slice(1)}`)
|
|
48
|
+
.join(" ");
|
|
49
|
+
}
|
|
9
50
|
function humanizeToolName(name) {
|
|
10
51
|
const trimmed = name.trim();
|
|
11
52
|
if (!trimmed) {
|
|
12
53
|
return name;
|
|
13
54
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (/[:./]/.test(trimmed) || trimmed.includes("__")) {
|
|
21
|
-
return trimmed;
|
|
55
|
+
// Strip the transport namespace first ("mcp__otto__spawn_task" ->
|
|
56
|
+
// "spawn_task", "otto.list_agents" -> "list_agents") so both the known-tool
|
|
57
|
+
// lookup and the fallback operate on the bare tool id.
|
|
58
|
+
const leaf = getMcpToolLeafName(trimmed) ?? getOttoToolLeafName(trimmed);
|
|
59
|
+
if (leaf) {
|
|
60
|
+
return humanizeToolName(leaf);
|
|
22
61
|
}
|
|
23
|
-
return trimmed
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
62
|
+
return KNOWN_TOOL_DISPLAY_NAMES[trimmed.toLowerCase()] ?? titleCaseToolId(trimmed);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Friendly display name for a bare tool identifier, used at every surface that
|
|
66
|
+
* shows a tool/action name without a full timeline item to run through
|
|
67
|
+
* {@link buildToolCallDisplayModel} (the visualizer's action labels, sub-agent
|
|
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".
|
|
71
|
+
*/
|
|
72
|
+
export function getToolDisplayName(name) {
|
|
73
|
+
return humanizeToolName(name);
|
|
29
74
|
}
|
|
30
75
|
function formatErrorText(error) {
|
|
31
76
|
if (error === null || error === undefined) {
|
|
@@ -5,5 +5,21 @@ export declare function isSpeakToolName(name: string): boolean;
|
|
|
5
5
|
export declare function isLikelyNamespacedToolName(name: string): boolean;
|
|
6
6
|
export declare function isOttoToolName(name: string): boolean;
|
|
7
7
|
export declare function getOttoToolLeafName(name: string): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Strip a leading MCP namespace for DISPLAY, returning the bare tool id.
|
|
10
|
+
*
|
|
11
|
+
* Tools hosted over MCP arrive namespaced as `mcp__<server>__<tool>` (Claude
|
|
12
|
+
* Code format). The `mcp__<server>__` part is transport plumbing that means
|
|
13
|
+
* nothing to a reader — "Create Issue", not "mcp__linear__create_issue". This
|
|
14
|
+
* generalizes {@link getOttoToolLeafName} to ANY server so every MCP tool reads
|
|
15
|
+
* cleanly, not just Otto's own.
|
|
16
|
+
*
|
|
17
|
+
* Returns `null` when the name carries no `mcp__…__` namespace, so callers keep
|
|
18
|
+
* plain tool names (`Read`, `bash`) and dotted/other forms untouched. Case is
|
|
19
|
+
* preserved — MCP tool ids are already snake_case and callers humanize after.
|
|
20
|
+
* `speak` is never treated as namespaced (it renders as a chat bubble, not an
|
|
21
|
+
* action row).
|
|
22
|
+
*/
|
|
23
|
+
export declare function getMcpToolLeafName(name: string): string | null;
|
|
8
24
|
export declare function isLikelyExternalToolName(name: string): boolean;
|
|
9
25
|
//# sourceMappingURL=tool-name-normalization.d.ts.map
|
|
@@ -69,6 +69,32 @@ export function getOttoToolLeafName(name) {
|
|
|
69
69
|
}
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Strip a leading MCP namespace for DISPLAY, returning the bare tool id.
|
|
74
|
+
*
|
|
75
|
+
* Tools hosted over MCP arrive namespaced as `mcp__<server>__<tool>` (Claude
|
|
76
|
+
* Code format). The `mcp__<server>__` part is transport plumbing that means
|
|
77
|
+
* nothing to a reader — "Create Issue", not "mcp__linear__create_issue". This
|
|
78
|
+
* generalizes {@link getOttoToolLeafName} to ANY server so every MCP tool reads
|
|
79
|
+
* cleanly, not just Otto's own.
|
|
80
|
+
*
|
|
81
|
+
* Returns `null` when the name carries no `mcp__…__` namespace, so callers keep
|
|
82
|
+
* plain tool names (`Read`, `bash`) and dotted/other forms untouched. Case is
|
|
83
|
+
* preserved — MCP tool ids are already snake_case and callers humanize after.
|
|
84
|
+
* `speak` is never treated as namespaced (it renders as a chat bubble, not an
|
|
85
|
+
* action row).
|
|
86
|
+
*/
|
|
87
|
+
export function getMcpToolLeafName(name) {
|
|
88
|
+
const trimmed = name.trim();
|
|
89
|
+
if (!trimmed || isSpeakToolName(trimmed) || !trimmed.includes("__")) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const segments = trimmed.split("__").filter((segment) => segment.length > 0);
|
|
93
|
+
if (segments.length >= 3 && segments[0].toLowerCase() === "mcp") {
|
|
94
|
+
return segments.slice(2).join("__");
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
72
98
|
export function isLikelyExternalToolName(name) {
|
|
73
99
|
const normalized = normalizeToolName(name);
|
|
74
100
|
if (!normalized) {
|
|
@@ -183,6 +183,33 @@ export declare const WSOutboundMessageSchema: {
|
|
|
183
183
|
args: import("zod").ZodObject<{
|
|
184
184
|
browserId: import("zod").ZodString;
|
|
185
185
|
}, import("zod/v4/core").$strict>;
|
|
186
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
187
|
+
command: import("zod").ZodLiteral<"focus_tab">;
|
|
188
|
+
args: import("zod").ZodObject<{
|
|
189
|
+
browserId: import("zod").ZodString;
|
|
190
|
+
}, import("zod/v4/core").$strict>;
|
|
191
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
192
|
+
command: import("zod").ZodLiteral<"page_text">;
|
|
193
|
+
args: import("zod").ZodObject<{
|
|
194
|
+
browserId: import("zod").ZodString;
|
|
195
|
+
maxChars: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
196
|
+
}, import("zod/v4/core").$strict>;
|
|
197
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
198
|
+
command: import("zod").ZodLiteral<"set_color_scheme">;
|
|
199
|
+
args: import("zod").ZodObject<{
|
|
200
|
+
browserId: import("zod").ZodString;
|
|
201
|
+
colorScheme: import("zod").ZodEnum<{
|
|
202
|
+
auto: "auto";
|
|
203
|
+
light: "light";
|
|
204
|
+
dark: "dark";
|
|
205
|
+
}>;
|
|
206
|
+
}, import("zod/v4/core").$strict>;
|
|
207
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
208
|
+
command: import("zod").ZodLiteral<"screenshot_element">;
|
|
209
|
+
args: import("zod").ZodObject<{
|
|
210
|
+
browserId: import("zod").ZodString;
|
|
211
|
+
ref: import("zod").ZodString;
|
|
212
|
+
}, import("zod/v4/core").$strict>;
|
|
186
213
|
}, import("zod/v4/core").$strip>], "command">;
|
|
187
214
|
}, import("zod/v4/core").$strict>, import("zod").ZodObject<{
|
|
188
215
|
type: import("zod").ZodLiteral<"activity_log">;
|
|
@@ -1073,6 +1100,24 @@ export declare const WSOutboundMessageSchema: {
|
|
|
1073
1100
|
}>;
|
|
1074
1101
|
}, import("zod/v4/core").$strip>;
|
|
1075
1102
|
}, import("zod/v4/core").$strip>>;
|
|
1103
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
1104
|
+
type: import("zod").ZodLiteral<"prompt_suggestion">;
|
|
1105
|
+
provider: import("zod").ZodString;
|
|
1106
|
+
suggestion: import("zod").ZodString;
|
|
1107
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
1108
|
+
type: import("zod").ZodLiteral<"rate_limit_updated">;
|
|
1109
|
+
provider: import("zod").ZodString;
|
|
1110
|
+
info: import("zod").ZodObject<{
|
|
1111
|
+
status: import("zod").ZodEnum<{
|
|
1112
|
+
warning: "warning";
|
|
1113
|
+
allowed: "allowed";
|
|
1114
|
+
rejected: "rejected";
|
|
1115
|
+
}>;
|
|
1116
|
+
utilizationPercent: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
1117
|
+
limitType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
1118
|
+
resetsAt: import("zod").ZodOptional<import("zod").ZodString>;
|
|
1119
|
+
isUsingOverage: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
1120
|
+
}, import("zod/v4/core").$strip>;
|
|
1076
1121
|
}, import("zod/v4/core").$strip>], "type">;
|
|
1077
1122
|
timestamp: import("zod").ZodString;
|
|
1078
1123
|
seq: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
@@ -3446,6 +3491,11 @@ export declare const WSOutboundMessageSchema: {
|
|
|
3446
3491
|
model: import("zod").ZodString;
|
|
3447
3492
|
name: import("zod").ZodString;
|
|
3448
3493
|
}, import("zod/v4/core").$loose>>;
|
|
3494
|
+
voiceCues: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
3495
|
+
join: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
3496
|
+
thinking: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
3497
|
+
done: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
3498
|
+
}, import("zod/v4/core").$loose>>;
|
|
3449
3499
|
}, import("zod/v4/core").$loose>>>;
|
|
3450
3500
|
}, import("zod/v4/core").$loose>>;
|
|
3451
3501
|
agentTeams: import("zod").ZodDefault<import("zod").ZodObject<{
|
|
@@ -3570,6 +3620,11 @@ export declare const WSOutboundMessageSchema: {
|
|
|
3570
3620
|
model: import("zod").ZodString;
|
|
3571
3621
|
name: import("zod").ZodString;
|
|
3572
3622
|
}, import("zod/v4/core").$loose>>;
|
|
3623
|
+
voiceCues: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
3624
|
+
join: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
3625
|
+
thinking: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
3626
|
+
done: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
3627
|
+
}, import("zod/v4/core").$loose>>;
|
|
3573
3628
|
}, import("zod/v4/core").$loose>>>;
|
|
3574
3629
|
}, import("zod/v4/core").$loose>>;
|
|
3575
3630
|
agentTeams: import("zod").ZodDefault<import("zod").ZodObject<{
|
|
@@ -3637,6 +3692,17 @@ export declare const WSOutboundMessageSchema: {
|
|
|
3637
3692
|
format: import("zod").ZodOptional<import("zod").ZodString>;
|
|
3638
3693
|
error: import("zod").ZodOptional<import("zod").ZodString>;
|
|
3639
3694
|
}, import("zod/v4/core").$loose>;
|
|
3695
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
3696
|
+
type: import("zod").ZodLiteral<"visualizer.voiceCues.generate.response">;
|
|
3697
|
+
payload: import("zod").ZodObject<{
|
|
3698
|
+
requestId: import("zod").ZodString;
|
|
3699
|
+
cues: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
3700
|
+
join: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
3701
|
+
thinking: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
3702
|
+
done: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
3703
|
+
}, import("zod/v4/core").$loose>>;
|
|
3704
|
+
error: import("zod").ZodOptional<import("zod").ZodString>;
|
|
3705
|
+
}, import("zod/v4/core").$loose>;
|
|
3640
3706
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
3641
3707
|
type: import("zod").ZodLiteral<"agentPersonalities.get_stats.response">;
|
|
3642
3708
|
payload: import("zod").ZodObject<{
|