@otto-code/protocol 0.8.14 → 0.8.16

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.
@@ -1,28 +1,46 @@
1
- import { type AgentPersonality, type PersonalityRole } from "./messages.js";
2
- export declare function isPersonalityRole(value: string): value is PersonalityRole;
1
+ import { type AgentPersonality, type ProfileRole } from "./messages.js";
2
+ export declare function isProfileRole(value: string): value is ProfileRole;
3
3
  /**
4
4
  * Filter an arbitrary role array (roles ride the wire as plain strings) down to
5
- * the known set, deduped and returned in canonical `PERSONALITY_ROLES` order.
5
+ * the known set, deduped and returned in canonical `PROFILE_ROLES` order.
6
6
  * Retired role names are mapped through `LEGACY_ROLE_ALIASES`; anything else
7
7
  * unknown (e.g. a role from a newer peer) is dropped rather than trusted.
8
8
  */
9
- export declare function normalizePersonalityRoles(roles: readonly string[] | undefined): PersonalityRole[];
10
- export declare function personalityHasRole(personality: Pick<AgentPersonality, "roles">, role: PersonalityRole): boolean;
11
- export type PersonalityRoleTier = "coordinator" | "focused";
12
- interface PersonalityRoleInfo {
13
- tier: PersonalityRoleTier;
9
+ export declare function normalizeProfileRoles(roles: readonly string[] | undefined): ProfileRole[];
10
+ export declare function profileHasRole(personality: Pick<AgentPersonality, "roles">, role: ProfileRole): boolean;
11
+ /**
12
+ * Resolve one roster entry from either identifier a caller might hold.
13
+ *
14
+ * Two kinds of caller reach the roster and they hold different things. A model
15
+ * reads `list_agent_profiles` and passes the display name it saw. Daemon-internal
16
+ * callers (orchestration role resolution, a stored schedule binding) already
17
+ * hold the stable id and must not round-trip through a name: names carry no
18
+ * uniqueness constraint, so a name lookup can land on a different entry than the
19
+ * one the caller meant.
20
+ *
21
+ * Id is checked first because ids are opaque and unique, so an id match is never
22
+ * ambiguous. The exact-name pass comes before the case-insensitive one so an
23
+ * exact match always wins over a differently-cased near-miss.
24
+ */
25
+ export declare function findProfileByRef<T extends {
26
+ id: string;
27
+ name: string;
28
+ }>(roster: readonly T[], ref: string): T | undefined;
29
+ export type ProfileRoleTier = "coordinator" | "focused";
30
+ interface ProfileRoleInfo {
31
+ tier: ProfileRoleTier;
14
32
  guidance: string;
15
33
  }
16
- export declare const PERSONALITY_ROLE_INFO: Readonly<Record<PersonalityRole, PersonalityRoleInfo>>;
34
+ export declare const PROFILE_ROLE_INFO: Readonly<Record<ProfileRole, ProfileRoleInfo>>;
17
35
  /**
18
36
  * A personality may launch/coordinate when it carries at least one coordinator
19
37
  * role. A personality whose roles are entirely focused (researcher, planner,
20
38
  * judger, advisor, coder, designer, writer), or that has no roles at all, is a
21
39
  * "lifter": it should finish its task, not fan out.
22
40
  */
23
- export declare function personalityCanLaunch(personality: Pick<AgentPersonality, "roles">): boolean;
24
- export interface PersonalitySelectionSummary {
25
- tier: PersonalityRoleTier;
41
+ export declare function profileCanLaunch(personality: Pick<AgentPersonality, "roles">): boolean;
42
+ export interface ProfileSelectionSummary {
43
+ tier: ProfileRoleTier;
26
44
  canLaunch: boolean;
27
45
  /** The "why you'd choose me" blurb - each of the personality's roles, joined. */
28
46
  guidance: string;
@@ -30,11 +48,11 @@ export interface PersonalitySelectionSummary {
30
48
  /**
31
49
  * Build the selection decision-aid for a personality from its roles: the tier
32
50
  * (coordinator if any role coordinates), whether it may launch, and a short
33
- * multi-role "why choose me" blurb. Surfaced by list_personalities so a
51
+ * multi-role "why choose me" blurb. Surfaced by list_agent_profiles so a
34
52
  * deciding agent can pick the right teammate from the list alone.
35
53
  */
36
- export declare function summarizePersonalityForSelection(personality: Pick<AgentPersonality, "roles">): PersonalitySelectionSummary;
37
- export declare const OTTO_WORK_VOCABULARY_DIRECTIVE = "Otto work vocabulary: a suggested task is deferred work for the user and does not start work; a chat is an active Otto chat session; a child chat is created by another chat; a personality profile is a reusable provider, model, mode, effort, and behavior template; an orchestration coordinates multiple chats; a schedule starts a background chat when due; a heartbeat sends a reminder or prompt and does not start a chat. Use suggest_task for concrete work to preserve for later, create_chat to start one chat now, and start_orchestration only for managed multi-chat coordination. Use list_personalities, optionally filtered by roles, before choosing a personality profile. Never substitute a harness-native agent-spawn tool for suggest_task, and when a user names an Otto tool exactly, use that exact Otto tool.";
54
+ export declare function summarizeProfileForSelection(personality: Pick<AgentPersonality, "roles">): ProfileSelectionSummary;
55
+ export declare const OTTO_WORK_VOCABULARY_DIRECTIVE = "Otto work vocabulary: a suggested task is deferred work for the user and does not start work; a chat is an active Otto chat session; a child chat is created by another chat; a Personality is a reusable provider, model, mode, effort, and behavior template; an orchestration coordinates multiple chats; a schedule starts a background chat when due; a heartbeat sends a reminder or prompt and does not start a chat. Use suggest_task for concrete work to preserve for later, create_chat to start one chat now, and start_orchestration only for managed multi-chat coordination. Use list_agent_profiles, optionally filtered by roles, before choosing a Personality. Never substitute a harness-native agent-spawn tool for suggest_task, and when a user names an Otto tool exactly, use that exact Otto tool.";
38
56
  export declare const ORCHESTRATOR_METHOD_DIRECTIVE: string;
39
57
  /**
40
58
  * The in-context "role directive" injected into a personality's system prompt at
@@ -45,14 +63,14 @@ export declare const ORCHESTRATOR_METHOD_DIRECTIVE: string;
45
63
  */
46
64
  export declare function composeRoleFocusDirective(roles: readonly string[] | undefined): string | undefined;
47
65
  export type PersonalityUnavailableCode = "provider-missing" | "provider-disabled" | "provider-not-ready" | "model-missing" | "mode-missing";
48
- export interface PersonalityAvailabilityInput {
66
+ export interface ProfileAvailabilityInput {
49
67
  /** Provider snapshot status, or undefined when the provider is absent entirely. */
50
68
  providerStatus: "ready" | "loading" | "error" | "unavailable" | undefined;
51
69
  providerEnabled: boolean | undefined;
52
70
  modelIds: readonly string[] | undefined;
53
71
  modeIds: readonly string[] | undefined;
54
72
  }
55
- export type PersonalityAvailability = {
73
+ export type ProfileAvailability = {
56
74
  available: true;
57
75
  } | {
58
76
  available: false;
@@ -70,10 +88,10 @@ export type PersonalityAvailability = {
70
88
  * means the provider only has to advertise SOME model; the concrete id is
71
89
  * chosen at resolution time, not here.
72
90
  */
73
- export declare function checkPersonalityAvailability(personality: {
91
+ export declare function checkProfileAvailability(personality: {
74
92
  provider: string;
75
93
  model?: string | undefined;
76
94
  modeId?: string | undefined;
77
- }, input: PersonalityAvailabilityInput): PersonalityAvailability;
95
+ }, input: ProfileAvailabilityInput): ProfileAvailability;
78
96
  export {};
79
97
  //# sourceMappingURL=agent-profiles.d.ts.map
@@ -1,9 +1,9 @@
1
- import { PERSONALITY_ROLES } from "./messages.js";
1
+ import { PROFILE_ROLES } from "./messages.js";
2
2
  // Pure, dependency-free personality helpers shared by the daemon (spawn-time
3
3
  // resolution) and the app (picker availability + role filtering). Effort
4
4
  // resolution is NOT here - it needs the model's advertised thinking options and
5
5
  // lives with the daemon's effort resolver; availability does not depend on it.
6
- const ROLE_SET = new Set(PERSONALITY_ROLES);
6
+ const ROLE_SET = new Set(PROFILE_ROLES);
7
7
  // Retired role names, mapped to their canonical replacement. "worker" was split
8
8
  // into "writer" (fast small-text generation) and "coder" (sub-agent coding); a
9
9
  // personality that still carries the old tag resolves to "coder", the closer
@@ -12,32 +12,55 @@ const ROLE_SET = new Set(PERSONALITY_ROLES);
12
12
  const LEGACY_ROLE_ALIASES = {
13
13
  worker: "coder",
14
14
  };
15
- export function isPersonalityRole(value) {
15
+ export function isProfileRole(value) {
16
16
  return ROLE_SET.has(value);
17
17
  }
18
18
  /**
19
19
  * Filter an arbitrary role array (roles ride the wire as plain strings) down to
20
- * the known set, deduped and returned in canonical `PERSONALITY_ROLES` order.
20
+ * the known set, deduped and returned in canonical `PROFILE_ROLES` order.
21
21
  * Retired role names are mapped through `LEGACY_ROLE_ALIASES`; anything else
22
22
  * unknown (e.g. a role from a newer peer) is dropped rather than trusted.
23
23
  */
24
- export function normalizePersonalityRoles(roles) {
24
+ export function normalizeProfileRoles(roles) {
25
25
  if (!roles || roles.length === 0) {
26
26
  return [];
27
27
  }
28
28
  const present = new Set();
29
29
  for (const raw of roles) {
30
- const canonical = LEGACY_ROLE_ALIASES[raw] ?? (isPersonalityRole(raw) ? raw : null);
30
+ const canonical = LEGACY_ROLE_ALIASES[raw] ?? (isProfileRole(raw) ? raw : null);
31
31
  if (canonical) {
32
32
  present.add(canonical);
33
33
  }
34
34
  }
35
- return PERSONALITY_ROLES.filter((role) => present.has(role));
35
+ return PROFILE_ROLES.filter((role) => present.has(role));
36
36
  }
37
- export function personalityHasRole(personality, role) {
38
- return normalizePersonalityRoles(personality.roles).includes(role);
37
+ export function profileHasRole(personality, role) {
38
+ return normalizeProfileRoles(personality.roles).includes(role);
39
39
  }
40
- export const PERSONALITY_ROLE_INFO = {
40
+ /**
41
+ * Resolve one roster entry from either identifier a caller might hold.
42
+ *
43
+ * Two kinds of caller reach the roster and they hold different things. A model
44
+ * reads `list_agent_profiles` and passes the display name it saw. Daemon-internal
45
+ * callers (orchestration role resolution, a stored schedule binding) already
46
+ * hold the stable id and must not round-trip through a name: names carry no
47
+ * uniqueness constraint, so a name lookup can land on a different entry than the
48
+ * one the caller meant.
49
+ *
50
+ * Id is checked first because ids are opaque and unique, so an id match is never
51
+ * ambiguous. The exact-name pass comes before the case-insensitive one so an
52
+ * exact match always wins over a differently-cased near-miss.
53
+ */
54
+ export function findProfileByRef(roster, ref) {
55
+ const trimmed = ref.trim();
56
+ if (!trimmed) {
57
+ return undefined;
58
+ }
59
+ return (roster.find((entry) => entry.id === trimmed) ??
60
+ roster.find((entry) => entry.name === trimmed) ??
61
+ roster.find((entry) => entry.name.toLowerCase() === trimmed.toLowerCase()));
62
+ }
63
+ export const PROFILE_ROLE_INFO = {
41
64
  // ── Surfaces ──────────────────────────────────────────────────────────────
42
65
  chatter: {
43
66
  tier: "coordinator",
@@ -93,22 +116,22 @@ export const PERSONALITY_ROLE_INFO = {
93
116
  * judger, advisor, coder, designer, writer), or that has no roles at all, is a
94
117
  * "lifter": it should finish its task, not fan out.
95
118
  */
96
- export function personalityCanLaunch(personality) {
97
- return normalizePersonalityRoles(personality.roles).some((role) => PERSONALITY_ROLE_INFO[role].tier === "coordinator");
119
+ export function profileCanLaunch(personality) {
120
+ return normalizeProfileRoles(personality.roles).some((role) => PROFILE_ROLE_INFO[role].tier === "coordinator");
98
121
  }
99
122
  /**
100
123
  * Build the selection decision-aid for a personality from its roles: the tier
101
124
  * (coordinator if any role coordinates), whether it may launch, and a short
102
- * multi-role "why choose me" blurb. Surfaced by list_personalities so a
125
+ * multi-role "why choose me" blurb. Surfaced by list_agent_profiles so a
103
126
  * deciding agent can pick the right teammate from the list alone.
104
127
  */
105
- export function summarizePersonalityForSelection(personality) {
106
- const roles = normalizePersonalityRoles(personality.roles);
107
- const canLaunch = roles.some((role) => PERSONALITY_ROLE_INFO[role].tier === "coordinator");
128
+ export function summarizeProfileForSelection(personality) {
129
+ const roles = normalizeProfileRoles(personality.roles);
130
+ const canLaunch = roles.some((role) => PROFILE_ROLE_INFO[role].tier === "coordinator");
108
131
  return {
109
132
  tier: canLaunch ? "coordinator" : "focused",
110
133
  canLaunch,
111
- guidance: roles.map((role) => PERSONALITY_ROLE_INFO[role].guidance).join(" "),
134
+ guidance: roles.map((role) => PROFILE_ROLE_INFO[role].guidance).join(" "),
112
135
  };
113
136
  }
114
137
  // The conductor's standing directive - the distilled `/epic` method taught to
@@ -116,7 +139,7 @@ export function summarizePersonalityForSelection(personality) {
116
139
  // chat, a suggested task, or an orchestration by the task's actual needs rather than
117
140
  // treating orchestration as the default. Kept here as one exported constant so
118
141
  // the wording is testable and shared. See projects/agent-orchestration/agent-orchestration.md.
119
- export const OTTO_WORK_VOCABULARY_DIRECTIVE = "Otto work vocabulary: a suggested task is deferred work for the user and does not start work; a chat is an active Otto chat session; a child chat is created by another chat; a personality profile is a reusable provider, model, mode, effort, and behavior template; an orchestration coordinates multiple chats; a schedule starts a background chat when due; a heartbeat sends a reminder or prompt and does not start a chat. Use suggest_task for concrete work to preserve for later, create_chat to start one chat now, and start_orchestration only for managed multi-chat coordination. Use list_personalities, optionally filtered by roles, before choosing a personality profile. Never substitute a harness-native agent-spawn tool for suggest_task, and when a user names an Otto tool exactly, use that exact Otto tool.";
142
+ export const OTTO_WORK_VOCABULARY_DIRECTIVE = "Otto work vocabulary: a suggested task is deferred work for the user and does not start work; a chat is an active Otto chat session; a child chat is created by another chat; a Personality is a reusable provider, model, mode, effort, and behavior template; an orchestration coordinates multiple chats; a schedule starts a background chat when due; a heartbeat sends a reminder or prompt and does not start a chat. Use suggest_task for concrete work to preserve for later, create_chat to start one chat now, and start_orchestration only for managed multi-chat coordination. Use list_agent_profiles, optionally filtered by roles, before choosing a Personality. Never substitute a harness-native agent-spawn tool for suggest_task, and when a user names an Otto tool exactly, use that exact Otto tool.";
120
143
  export const ORCHESTRATOR_METHOD_DIRECTIVE = "You are the orchestrator - the team's sole conductor. Choose tools because the task needs their specific capability, never because a tool is available or named. " +
121
144
  "Do a small, self-contained task directly. Use create_chat only for an independently executable piece of active work that benefits from its own chat. Use suggest_task only to preserve a concrete, out-of-scope follow-up for later. Use start_orchestration only when the active work needs a declared multi-chat plan with daemon-managed fan-out, gathering, judging, loops, or approval gates. " +
122
145
  "For work that genuinely needs orchestration: (1) if the shape is unclear, dispatch research and planning chats; (2) declare the plan with start_orchestration - phases typed research/plan/implement/design/verify/gate/deliver, fanning out where several angles help and attaching a judger to grade them, looping until enough pass; (3) put a gate before irreversible or costly steps so the user approves; (4) synthesize the passing results into the deliverable. " +
@@ -129,7 +152,7 @@ export const ORCHESTRATOR_METHOD_DIRECTIVE = "You are the orchestrator - the tea
129
152
  * nothing. This is guidance, not a gate - the tools stay available either way.
130
153
  */
131
154
  export function composeRoleFocusDirective(roles) {
132
- const normalized = normalizePersonalityRoles(roles);
155
+ const normalized = normalizeProfileRoles(roles);
133
156
  if (normalized.length === 0) {
134
157
  return undefined;
135
158
  }
@@ -137,10 +160,10 @@ export function composeRoleFocusDirective(roles) {
137
160
  if (normalized.includes("orchestrator")) {
138
161
  return `${ORCHESTRATOR_METHOD_DIRECTIVE} (Your roles: ${roleList}.)`;
139
162
  }
140
- if (normalized.some((role) => PERSONALITY_ROLE_INFO[role].tier === "coordinator")) {
141
- return `You are a coordinator personality (roles: ${roleList}). You front interactive work and may delegate when the task benefits from it: use list_personalities to see who else is available, then either do the work directly, create a child chat for an independent active piece, or hand off genuinely multi-chat work to the team's orchestrator.`;
163
+ if (normalized.some((role) => PROFILE_ROLE_INFO[role].tier === "coordinator")) {
164
+ return `You are a coordinator personality (roles: ${roleList}). You front interactive work and may delegate when the task benefits from it: use list_agent_profiles to see who else is available, then either do the work directly, create a child chat for an independent active piece, or hand off genuinely multi-chat work to the team's orchestrator.`;
142
165
  }
143
- return `You are a focused personality (roles: ${roleList}). Someone is waiting on this specific task - stay on it and finish it. You can still call list_personalities to see the roster, but don't create child chats or start side workflows unless it is genuinely essential to completing this job.`;
166
+ return `You are a focused personality (roles: ${roleList}). Someone is waiting on this specific task - stay on it and finish it. You can still call list_agent_profiles to see the roster, but don't create child chats or start side workflows unless it is genuinely essential to completing this job.`;
144
167
  }
145
168
  /**
146
169
  * Decide whether a personality is usable against a provider's current snapshot.
@@ -153,7 +176,7 @@ export function composeRoleFocusDirective(roles) {
153
176
  * means the provider only has to advertise SOME model; the concrete id is
154
177
  * chosen at resolution time, not here.
155
178
  */
156
- export function checkPersonalityAvailability(personality, input) {
179
+ export function checkProfileAvailability(personality, input) {
157
180
  if (input.providerStatus === undefined) {
158
181
  return {
159
182
  available: false,
@@ -1,4 +1,4 @@
1
- import type { AgentProfile, AgentTeam, PersonalityRole } from "./messages.js";
1
+ import type { AgentProfile, AgentTeam, ProfileRole } from "./messages.js";
2
2
  /**
3
3
  * The dynamic "Team's Scheduler" schedule binding. Stored in the schedule's
4
4
  * `personality` field in place of a personality name; resolved at RUN time to
@@ -48,9 +48,9 @@ export declare function pruneTeamMemberIds(memberIds: readonly string[] | undefi
48
48
  export declare function resolveExclusiveTeamMembers(team: Pick<AgentTeam, "memberIds"> | null | undefined, otherTeams: readonly Pick<AgentTeam, "memberIds">[] | undefined, personalities: readonly AgentProfile[] | undefined): AgentProfile[];
49
49
  /**
50
50
  * The union of all members' roles, normalized and returned in canonical
51
- * `PERSONALITY_ROLES` order - the team card's role-pill strip.
51
+ * `PROFILE_ROLES` order - the team card's role-pill strip.
52
52
  */
53
- export declare function teamRoleUnion(team: Pick<AgentTeam, "memberIds"> | null | undefined, personalities: readonly AgentProfile[] | undefined): PersonalityRole[];
53
+ export declare function teamRoleUnion(team: Pick<AgentTeam, "memberIds"> | null | undefined, personalities: readonly AgentProfile[] | undefined): ProfileRole[];
54
54
  /**
55
55
  * A team prompt only stacks when it has content; a team with an empty or
56
56
  * whitespace prompt is purely organizational (picker scoping, no prompt layer).
@@ -1,9 +1,9 @@
1
- import { normalizePersonalityRoles } from "./agent-profiles.js";
1
+ import { normalizeProfileRoles } from "./agent-profiles.js";
2
2
  // Pure, dependency-free team helpers shared by the daemon (spawn-time active
3
- // team resolution, list_personalities scoping) and the app (team cards,
3
+ // team resolution, list_agent_profiles scoping) and the app (team cards,
4
4
  // pickers, the Active Team switcher). Availability is deliberately NOT here -
5
5
  // a team is never "out of commission"; its members are individually available
6
- // or not, judged by checkPersonalityAvailability per member.
6
+ // or not, judged by checkProfileAvailability per member.
7
7
  /**
8
8
  * The dynamic "Team's Scheduler" schedule binding. Stored in the schedule's
9
9
  * `personality` field in place of a personality name; resolved at RUN time to
@@ -100,11 +100,11 @@ export function resolveExclusiveTeamMembers(team, otherTeams, personalities) {
100
100
  }
101
101
  /**
102
102
  * The union of all members' roles, normalized and returned in canonical
103
- * `PERSONALITY_ROLES` order - the team card's role-pill strip.
103
+ * `PROFILE_ROLES` order - the team card's role-pill strip.
104
104
  */
105
105
  export function teamRoleUnion(team, personalities) {
106
106
  const roles = resolveTeamMembers(team, personalities).flatMap((personality) => personality.roles ?? []);
107
- return normalizePersonalityRoles(roles);
107
+ return normalizeProfileRoles(roles);
108
108
  }
109
109
  /**
110
110
  * A team prompt only stacks when it has content; a team with an empty or
@@ -4,7 +4,7 @@ export const ArtifactStatusSchema = z.enum(["generating", "ready", "error"]);
4
4
  // Two glow colors for the generating spinner, snapshotted from the Agent
5
5
  // Personality the artifact was generated under (BlobLoader glowA/glowB), so its
6
6
  // card spinner renders in the personality's identity. Passthrough for
7
- // forward-compat, mirroring AgentPersonalitySpinnerSchema in messages.ts.
7
+ // forward-compat, mirroring AgentProfileSpinnerSchema in messages.ts.
8
8
  export const ArtifactSpinnerSchema = z
9
9
  .object({
10
10
  glowA: z.string().min(1),
@@ -35,7 +35,7 @@ export const ArtifactMetadataSchema = z.object({
35
35
  // Spinner glow colors of the Agent Personality this artifact was generated
36
36
  // under, snapshotted at create time like the provider/model above. Absent ⇒
37
37
  // the card falls back to the theme's default spinner colors. Purely additive
38
- // (no daemon floor needed). See docs/agent-personalities.md.
38
+ // (no daemon floor needed). See docs/agent-profiles.md.
39
39
  generationSpinner: ArtifactSpinnerSchema.nullable().optional(),
40
40
  // Human name of the Agent Personality that generated (last generated) this
41
41
  // artifact, snapshotted at create/regenerate time like the provider/model
@@ -191,7 +191,7 @@ export const MutableAgentTeamsConfigSchema = z
191
191
  // The host's active team id; null/absent = no team active (exactly legacy
192
192
  // behavior). Host-scoped daemon config rather than device-local: the team
193
193
  // prompt is applied daemon-side at spawn, so headless spawns (MCP
194
- // create_agent, schedule runs) must see it, and a patch from any client
194
+ // create_chat, schedule runs) must see it, and a patch from any client
195
195
  // hot-reloads the switch to every connected client.
196
196
  activeTeamId: z.string().nullable().optional(),
197
197
  })
@@ -5,7 +5,7 @@
5
5
  // restore button) import this one list so the shipped set stays identical on
6
6
  // both sides.
7
7
  //
8
- // Design notes (see docs/agent-personalities.md):
8
+ // Design notes (see docs/agent-profiles.md):
9
9
  // - Ids are STABLE and prefixed `personality_builtin_*`. Restore re-adds only
10
10
  // the builtins whose id is missing, so a user who kept/renamed some never
11
11
  // gets duplicates. Renaming a builtin keeps its id, so it is still "present".