@otto-code/protocol 0.5.1 → 0.5.2
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-attention-notification.js +60 -12
- package/dist/agent-teams.d.ts +49 -0
- package/dist/agent-teams.js +99 -0
- package/dist/agent-types.d.ts +20 -0
- package/dist/browser-automation/rpc-schemas.d.ts +3 -3
- package/dist/default-personalities.d.ts +2 -1
- package/dist/default-personalities.js +20 -0
- package/dist/generated/validation/ws-outbound.aot.js +11467 -11053
- package/dist/messages.d.ts +392 -26
- package/dist/messages.js +107 -1
- package/dist/model-tiers.d.ts +34 -0
- package/dist/model-tiers.js +92 -0
- package/dist/provider-manifest.js +11 -0
- package/dist/schedule/rpc-schemas.d.ts +5 -4
- package/dist/schedule/rpc-schemas.js +3 -0
- package/dist/schedule/types.d.ts +1 -1
- package/dist/validation/ws-outbound-schema-metadata.d.ts +50 -4
- package/package.json +1 -1
|
@@ -41,13 +41,59 @@ const buildNotificationPreview = (text) => {
|
|
|
41
41
|
}
|
|
42
42
|
return truncateNotificationText(normalized, NOTIFICATION_PREVIEW_LIMIT);
|
|
43
43
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
// Human-readable labels for common tool names, so a permission notification reads
|
|
45
|
+
// like a request ("Run command: …") instead of dumping the raw tool identifier.
|
|
46
|
+
const TOOL_NAME_LABELS = {
|
|
47
|
+
bash: "Run command",
|
|
48
|
+
exec: "Run command",
|
|
49
|
+
shell: "Run command",
|
|
50
|
+
read: "Read file",
|
|
51
|
+
write: "Write file",
|
|
52
|
+
edit: "Edit file",
|
|
53
|
+
multiedit: "Edit file",
|
|
54
|
+
grep: "Search",
|
|
55
|
+
glob: "Find files",
|
|
56
|
+
webfetch: "Fetch page",
|
|
57
|
+
websearch: "Web search",
|
|
58
|
+
task: "Start subagent",
|
|
59
|
+
external_directory: "Access directory",
|
|
60
|
+
};
|
|
61
|
+
const humanizeToolName = (name) => {
|
|
62
|
+
const trimmed = name.trim();
|
|
63
|
+
return TOOL_NAME_LABELS[trimmed.toLowerCase()] ?? trimmed;
|
|
64
|
+
};
|
|
65
|
+
// Input fields that carry the meaningful, user-legible part of a tool call, in
|
|
66
|
+
// priority order. The first non-empty string wins.
|
|
67
|
+
const PERMISSION_INPUT_DESCRIPTION_KEYS = [
|
|
68
|
+
"command",
|
|
69
|
+
"description",
|
|
70
|
+
"file_path",
|
|
71
|
+
"path",
|
|
72
|
+
"url",
|
|
73
|
+
"pattern",
|
|
74
|
+
"query",
|
|
75
|
+
"prompt",
|
|
76
|
+
];
|
|
77
|
+
// Deterministic field extraction — NOT an AI summary. Picks an existing string
|
|
78
|
+
// field out of the tool's input (by the priority list, then any string field)
|
|
79
|
+
// so the notification can show it verbatim instead of raw JSON.
|
|
80
|
+
const describePermissionInput = (input) => {
|
|
81
|
+
if (!input) {
|
|
49
82
|
return null;
|
|
50
83
|
}
|
|
84
|
+
for (const key of PERMISSION_INPUT_DESCRIPTION_KEYS) {
|
|
85
|
+
const value = input[key];
|
|
86
|
+
if (typeof value === "string" && value.trim()) {
|
|
87
|
+
return value.trim();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Fall back to the first string-valued field rather than raw JSON.
|
|
91
|
+
for (const value of Object.values(input)) {
|
|
92
|
+
if (typeof value === "string" && value.trim()) {
|
|
93
|
+
return value.trim();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
51
97
|
};
|
|
52
98
|
const buildPermissionDetails = (request) => {
|
|
53
99
|
if (!request) {
|
|
@@ -65,15 +111,17 @@ const buildPermissionDetails = (request) => {
|
|
|
65
111
|
if (details.length > 0) {
|
|
66
112
|
return details.join(" - ");
|
|
67
113
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
// No provider-supplied title/description: build a legible line from the tool
|
|
115
|
+
// name and a meaningful input field instead of stringifying the payload.
|
|
116
|
+
const name = request.name?.trim();
|
|
117
|
+
const inputDescription = describePermissionInput(request.input);
|
|
118
|
+
if (name && inputDescription) {
|
|
119
|
+
return `${humanizeToolName(name)}: ${inputDescription}`;
|
|
71
120
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return metadataPreview;
|
|
121
|
+
if (inputDescription) {
|
|
122
|
+
return inputDescription;
|
|
75
123
|
}
|
|
76
|
-
return
|
|
124
|
+
return name ? humanizeToolName(name) : request.kind;
|
|
77
125
|
};
|
|
78
126
|
export function findLatestAssistantMessageFromTimeline(timeline) {
|
|
79
127
|
// Providers may stream assistant content in consecutive chunks.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AgentPersonality, AgentTeam, PersonalityRole } from "./messages.js";
|
|
2
|
+
/**
|
|
3
|
+
* The dynamic "Team's Scheduler" schedule binding. Stored in the schedule's
|
|
4
|
+
* `personality` field in place of a personality name; resolved at RUN time to
|
|
5
|
+
* the active team's first available member carrying the Scheduler role
|
|
6
|
+
* (member order). Deliberately contains "@" — personality names are sanitized
|
|
7
|
+
* to [A-Za-z0-9_-] at the authoring surface, so the sentinel can never collide
|
|
8
|
+
* with a real name. No active team, or no available Scheduler member, is a
|
|
9
|
+
* hard run failure with a named error (same loudness as a bound personality
|
|
10
|
+
* being out of commission).
|
|
11
|
+
*/
|
|
12
|
+
export declare const TEAM_SCHEDULER_PERSONALITY_SENTINEL = "@team-scheduler";
|
|
13
|
+
export interface AgentTeamsConfigView {
|
|
14
|
+
teams?: readonly AgentTeam[] | undefined;
|
|
15
|
+
activeTeamId?: string | null | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare function findAgentTeam(teams: readonly AgentTeam[] | undefined, teamId: string | null | undefined): AgentTeam | null;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the host's active team. A dangling `activeTeamId` (team deleted, or
|
|
20
|
+
* a patch raced a delete) reads as "no team active" — teamlessness is a valid
|
|
21
|
+
* state and must never error. The daemon additionally heals a dangling id back
|
|
22
|
+
* to null on the next config patch; this helper is the read-side tolerance.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getActiveAgentTeam(section: AgentTeamsConfigView | undefined): AgentTeam | null;
|
|
25
|
+
export declare function isTeamMember(team: Pick<AgentTeam, "memberIds"> | null | undefined, personalityId: string | null | undefined): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a team's members against the personality roster, in `memberIds`
|
|
28
|
+
* order, deduped. A member id pointing at a deleted personality is tolerated
|
|
29
|
+
* and ignored (it is pruned opportunistically on the next save of the team,
|
|
30
|
+
* never eagerly cascaded on delete).
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveTeamMembers(team: Pick<AgentTeam, "memberIds"> | null | undefined, personalities: readonly AgentPersonality[] | undefined): AgentPersonality[];
|
|
33
|
+
/**
|
|
34
|
+
* The save-time prune: drop member ids that no longer resolve to a personality
|
|
35
|
+
* (and dedupe), preserving order. Editors call this when persisting a team so
|
|
36
|
+
* dangling ids don't accumulate; readers never require it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function pruneTeamMemberIds(memberIds: readonly string[] | undefined, personalities: readonly AgentPersonality[] | undefined): string[];
|
|
39
|
+
/**
|
|
40
|
+
* The union of all members' roles, normalized and returned in canonical
|
|
41
|
+
* `PERSONALITY_ROLES` order — the team card's role-pill strip.
|
|
42
|
+
*/
|
|
43
|
+
export declare function teamRoleUnion(team: Pick<AgentTeam, "memberIds"> | null | undefined, personalities: readonly AgentPersonality[] | undefined): PersonalityRole[];
|
|
44
|
+
/**
|
|
45
|
+
* A team prompt only stacks when it has content; a team with an empty or
|
|
46
|
+
* whitespace prompt is purely organizational (picker scoping, no prompt layer).
|
|
47
|
+
*/
|
|
48
|
+
export declare function getEffectiveTeamPrompt(team: Pick<AgentTeam, "teamPrompt"> | null | undefined): string | null;
|
|
49
|
+
//# sourceMappingURL=agent-teams.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { normalizePersonalityRoles } from "./agent-personalities.js";
|
|
2
|
+
// Pure, dependency-free team helpers shared by the daemon (spawn-time active
|
|
3
|
+
// team resolution, list_personalities scoping) and the app (team cards,
|
|
4
|
+
// pickers, the Active Team switcher). Availability is deliberately NOT here —
|
|
5
|
+
// a team is never "out of commission"; its members are individually available
|
|
6
|
+
// or not, judged by checkPersonalityAvailability per member.
|
|
7
|
+
/**
|
|
8
|
+
* The dynamic "Team's Scheduler" schedule binding. Stored in the schedule's
|
|
9
|
+
* `personality` field in place of a personality name; resolved at RUN time to
|
|
10
|
+
* the active team's first available member carrying the Scheduler role
|
|
11
|
+
* (member order). Deliberately contains "@" — personality names are sanitized
|
|
12
|
+
* to [A-Za-z0-9_-] at the authoring surface, so the sentinel can never collide
|
|
13
|
+
* with a real name. No active team, or no available Scheduler member, is a
|
|
14
|
+
* hard run failure with a named error (same loudness as a bound personality
|
|
15
|
+
* being out of commission).
|
|
16
|
+
*/
|
|
17
|
+
export const TEAM_SCHEDULER_PERSONALITY_SENTINEL = "@team-scheduler";
|
|
18
|
+
export function findAgentTeam(teams, teamId) {
|
|
19
|
+
if (!teamId) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return teams?.find((team) => team.id === teamId) ?? null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the host's active team. A dangling `activeTeamId` (team deleted, or
|
|
26
|
+
* a patch raced a delete) reads as "no team active" — teamlessness is a valid
|
|
27
|
+
* state and must never error. The daemon additionally heals a dangling id back
|
|
28
|
+
* to null on the next config patch; this helper is the read-side tolerance.
|
|
29
|
+
*/
|
|
30
|
+
export function getActiveAgentTeam(section) {
|
|
31
|
+
return findAgentTeam(section?.teams, section?.activeTeamId);
|
|
32
|
+
}
|
|
33
|
+
export function isTeamMember(team, personalityId) {
|
|
34
|
+
if (!team || !personalityId) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return (team.memberIds ?? []).includes(personalityId);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a team's members against the personality roster, in `memberIds`
|
|
41
|
+
* order, deduped. A member id pointing at a deleted personality is tolerated
|
|
42
|
+
* and ignored (it is pruned opportunistically on the next save of the team,
|
|
43
|
+
* never eagerly cascaded on delete).
|
|
44
|
+
*/
|
|
45
|
+
export function resolveTeamMembers(team, personalities) {
|
|
46
|
+
if (!team || !personalities || personalities.length === 0) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const byId = new Map(personalities.map((personality) => [personality.id, personality]));
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
const members = [];
|
|
52
|
+
for (const memberId of team.memberIds ?? []) {
|
|
53
|
+
if (seen.has(memberId)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
seen.add(memberId);
|
|
57
|
+
const personality = byId.get(memberId);
|
|
58
|
+
if (personality) {
|
|
59
|
+
members.push(personality);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return members;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The save-time prune: drop member ids that no longer resolve to a personality
|
|
66
|
+
* (and dedupe), preserving order. Editors call this when persisting a team so
|
|
67
|
+
* dangling ids don't accumulate; readers never require it.
|
|
68
|
+
*/
|
|
69
|
+
export function pruneTeamMemberIds(memberIds, personalities) {
|
|
70
|
+
if (!memberIds || memberIds.length === 0) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const known = new Set((personalities ?? []).map((personality) => personality.id));
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
return memberIds.filter((memberId) => {
|
|
76
|
+
if (seen.has(memberId) || !known.has(memberId)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
seen.add(memberId);
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The union of all members' roles, normalized and returned in canonical
|
|
85
|
+
* `PERSONALITY_ROLES` order — the team card's role-pill strip.
|
|
86
|
+
*/
|
|
87
|
+
export function teamRoleUnion(team, personalities) {
|
|
88
|
+
const roles = resolveTeamMembers(team, personalities).flatMap((personality) => personality.roles ?? []);
|
|
89
|
+
return normalizePersonalityRoles(roles);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A team prompt only stacks when it has content; a team with an empty or
|
|
93
|
+
* whitespace prompt is purely organizational (picker scoping, no prompt layer).
|
|
94
|
+
*/
|
|
95
|
+
export function getEffectiveTeamPrompt(team) {
|
|
96
|
+
const prompt = team?.teamPrompt?.trim();
|
|
97
|
+
return prompt && prompt.length > 0 ? prompt : null;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=agent-teams.js.map
|
package/dist/agent-types.d.ts
CHANGED
|
@@ -67,6 +67,13 @@ export interface AgentMode {
|
|
|
67
67
|
colorTier?: string;
|
|
68
68
|
}
|
|
69
69
|
export type ProviderStatus = "ready" | "loading" | "error" | "unavailable";
|
|
70
|
+
/**
|
|
71
|
+
* A model's capability tier, used to bind personality "brains" provider-
|
|
72
|
+
* agnostically (deep = flagship reasoning, standard = everyday, fast = cheap/
|
|
73
|
+
* high-volume). Assigned by the daemon at ingest (catalog → name pattern), with
|
|
74
|
+
* a user per-model override winning; see model-tiers.ts.
|
|
75
|
+
*/
|
|
76
|
+
export type ModelTier = "deep" | "standard" | "fast";
|
|
70
77
|
export interface AgentModelDefinition {
|
|
71
78
|
provider: AgentProvider;
|
|
72
79
|
id: string;
|
|
@@ -77,6 +84,19 @@ export interface AgentModelDefinition {
|
|
|
77
84
|
contextWindowMaxTokens?: number;
|
|
78
85
|
thinkingOptions?: AgentSelectOption[];
|
|
79
86
|
defaultThinkingOptionId?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Capability tier, stamped by the daemon when it ingests the provider's model
|
|
89
|
+
* list. Optional: absent from old daemons, and from models we can't classify
|
|
90
|
+
* and the user hasn't tagged. Consumers fall back to their own inference.
|
|
91
|
+
*/
|
|
92
|
+
tier?: ModelTier;
|
|
93
|
+
/**
|
|
94
|
+
* False when this model cannot run the provider's "auto" permission mode
|
|
95
|
+
* (e.g. Claude's classifier-based Auto mode is unsupported on Haiku). Absent
|
|
96
|
+
* = supported or unknown (including old daemons); clients only hide the Auto
|
|
97
|
+
* option on an explicit false.
|
|
98
|
+
*/
|
|
99
|
+
supportsAutoMode?: boolean;
|
|
80
100
|
}
|
|
81
101
|
export interface AgentSelectOption {
|
|
82
102
|
id: string;
|
|
@@ -482,8 +482,8 @@ export declare const BrowserAutomationWaitResultSchema: z.ZodObject<{
|
|
|
482
482
|
command: z.ZodLiteral<"wait">;
|
|
483
483
|
browserId: z.ZodString;
|
|
484
484
|
matched: z.ZodEnum<{
|
|
485
|
-
text: "text";
|
|
486
485
|
url: "url";
|
|
486
|
+
text: "text";
|
|
487
487
|
}>;
|
|
488
488
|
}, z.core.$strip>;
|
|
489
489
|
export declare const BrowserAutomationTypeResultSchema: z.ZodObject<{
|
|
@@ -715,8 +715,8 @@ export declare const BrowserAutomationResultSchema: z.ZodDiscriminatedUnion<[z.Z
|
|
|
715
715
|
command: z.ZodLiteral<"wait">;
|
|
716
716
|
browserId: z.ZodString;
|
|
717
717
|
matched: z.ZodEnum<{
|
|
718
|
-
text: "text";
|
|
719
718
|
url: "url";
|
|
719
|
+
text: "text";
|
|
720
720
|
}>;
|
|
721
721
|
}, z.core.$strip>, z.ZodObject<{
|
|
722
722
|
command: z.ZodLiteral<"type">;
|
|
@@ -1122,8 +1122,8 @@ export declare const BrowserAutomationExecuteResponseSchema: z.ZodObject<{
|
|
|
1122
1122
|
command: z.ZodLiteral<"wait">;
|
|
1123
1123
|
browserId: z.ZodString;
|
|
1124
1124
|
matched: z.ZodEnum<{
|
|
1125
|
-
text: "text";
|
|
1126
1125
|
url: "url";
|
|
1126
|
+
text: "text";
|
|
1127
1127
|
}>;
|
|
1128
1128
|
}, z.core.$strip>, z.ZodObject<{
|
|
1129
1129
|
command: z.ZodLiteral<"type">;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import type { AgentPersonality } from "./messages.js";
|
|
1
|
+
import type { AgentPersonality, AgentTeam } from "./messages.js";
|
|
2
2
|
export declare const DEFAULT_AGENT_PERSONALITIES: readonly AgentPersonality[];
|
|
3
|
+
export declare const DEFAULT_AGENT_TEAMS: readonly AgentTeam[];
|
|
3
4
|
//# sourceMappingURL=default-personalities.d.ts.map
|
|
@@ -127,4 +127,24 @@ export const DEFAULT_AGENT_PERSONALITIES = [
|
|
|
127
127
|
voice: kokoroVoice("am_echo"),
|
|
128
128
|
},
|
|
129
129
|
];
|
|
130
|
+
// The starter Agent Team shipped with Otto: every starter personality grouped
|
|
131
|
+
// under one operating template. Seeded the same first-run/absent-section way
|
|
132
|
+
// as the personalities (see seedDefaultTeamsIfAbsent), and re-addable from the
|
|
133
|
+
// Agent teams card. Deliberately NOT active on first run — activating a
|
|
134
|
+
// prompt-bearing team silently on install would change spawn behavior out
|
|
135
|
+
// from under existing users; the user opts in via the Active Team switcher.
|
|
136
|
+
// The stable `team_builtin_*` id makes restore idempotent, exactly like the
|
|
137
|
+
// personalities' `personality_builtin_*` ids.
|
|
138
|
+
export const DEFAULT_AGENT_TEAMS = [
|
|
139
|
+
{
|
|
140
|
+
id: "team_builtin_otto_crew",
|
|
141
|
+
name: "The Otto Crew",
|
|
142
|
+
avatar: { color: "#4F46E5" },
|
|
143
|
+
teamPrompt: "You are part of the Otto Crew, a coordinated team of specialists working one project " +
|
|
144
|
+
"together under Atlas's lead. Stay in your lane and trust your teammates' lanes: do your " +
|
|
145
|
+
"own role's work well, hand off cleanly with the context the next specialist needs, and " +
|
|
146
|
+
"flag anything you notice outside your remit instead of fixing it yourself.",
|
|
147
|
+
memberIds: DEFAULT_AGENT_PERSONALITIES.map((personality) => personality.id),
|
|
148
|
+
},
|
|
149
|
+
];
|
|
130
150
|
//# sourceMappingURL=default-personalities.js.map
|