@otto-code/protocol 0.5.0

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 (102) hide show
  1. package/README.md +12 -0
  2. package/dist/agent-attention-notification.d.ts +41 -0
  3. package/dist/agent-attention-notification.js +140 -0
  4. package/dist/agent-labels.d.ts +7 -0
  5. package/dist/agent-labels.js +11 -0
  6. package/dist/agent-lifecycle.d.ts +3 -0
  7. package/dist/agent-lifecycle.js +8 -0
  8. package/dist/agent-personalities.d.ts +32 -0
  9. package/dist/agent-personalities.js +69 -0
  10. package/dist/agent-state-bucket.d.ts +14 -0
  11. package/dist/agent-state-bucket.js +41 -0
  12. package/dist/agent-title-limits.d.ts +2 -0
  13. package/dist/agent-title-limits.js +2 -0
  14. package/dist/agent-types.d.ts +479 -0
  15. package/dist/agent-types.js +22 -0
  16. package/dist/artifacts/rpc-schemas.d.ts +396 -0
  17. package/dist/artifacts/rpc-schemas.js +175 -0
  18. package/dist/artifacts/types.d.ts +166 -0
  19. package/dist/artifacts/types.js +73 -0
  20. package/dist/binary-frames/demux.d.ts +11 -0
  21. package/dist/binary-frames/demux.js +23 -0
  22. package/dist/binary-frames/file-transfer.d.ts +50 -0
  23. package/dist/binary-frames/file-transfer.js +107 -0
  24. package/dist/binary-frames/index.d.ts +4 -0
  25. package/dist/binary-frames/index.js +4 -0
  26. package/dist/binary-frames/terminal.d.ts +31 -0
  27. package/dist/binary-frames/terminal.js +99 -0
  28. package/dist/branch-slug.d.ts +14 -0
  29. package/dist/branch-slug.js +49 -0
  30. package/dist/browser-automation/capabilities.d.ts +7 -0
  31. package/dist/browser-automation/capabilities.js +31 -0
  32. package/dist/browser-automation/rpc-schemas.d.ts +1329 -0
  33. package/dist/browser-automation/rpc-schemas.js +555 -0
  34. package/dist/chat/rpc-schemas.d.ts +158 -0
  35. package/dist/chat/rpc-schemas.js +103 -0
  36. package/dist/chat/types.d.ts +30 -0
  37. package/dist/chat/types.js +22 -0
  38. package/dist/client-capabilities.d.ts +8 -0
  39. package/dist/client-capabilities.js +16 -0
  40. package/dist/connection-offer.d.ts +36 -0
  41. package/dist/connection-offer.js +53 -0
  42. package/dist/daemon-endpoints.d.ts +47 -0
  43. package/dist/daemon-endpoints.js +201 -0
  44. package/dist/default-personalities.d.ts +3 -0
  45. package/dist/default-personalities.js +127 -0
  46. package/dist/effort.d.ts +32 -0
  47. package/dist/effort.js +99 -0
  48. package/dist/error-utils.d.ts +11 -0
  49. package/dist/error-utils.js +27 -0
  50. package/dist/generated/validation/ws-outbound.aot.d.ts +2 -0
  51. package/dist/generated/validation/ws-outbound.aot.js +48755 -0
  52. package/dist/git-hosting.d.ts +21 -0
  53. package/dist/git-hosting.js +36 -0
  54. package/dist/git-remote.d.ts +18 -0
  55. package/dist/git-remote.js +84 -0
  56. package/dist/host-connection-schema.d.ts +11 -0
  57. package/dist/host-connection-schema.js +9 -0
  58. package/dist/literal-union.d.ts +2 -0
  59. package/dist/literal-union.js +2 -0
  60. package/dist/loop/rpc-schemas.d.ts +636 -0
  61. package/dist/loop/rpc-schemas.js +163 -0
  62. package/dist/messages.d.ts +38505 -0
  63. package/dist/messages.js +4719 -0
  64. package/dist/otto-config-schema.d.ts +136 -0
  65. package/dist/otto-config-schema.js +96 -0
  66. package/dist/path-utils.d.ts +2 -0
  67. package/dist/path-utils.js +16 -0
  68. package/dist/provider-config.d.ts +266 -0
  69. package/dist/provider-config.js +238 -0
  70. package/dist/provider-icon-names.d.ts +5 -0
  71. package/dist/provider-icon-names.js +52 -0
  72. package/dist/provider-manifest.d.ts +34 -0
  73. package/dist/provider-manifest.js +227 -0
  74. package/dist/schedule/cron-expression.d.ts +13 -0
  75. package/dist/schedule/cron-expression.js +97 -0
  76. package/dist/schedule/rpc-schemas.d.ts +667 -0
  77. package/dist/schedule/rpc-schemas.js +155 -0
  78. package/dist/schedule/types.d.ts +236 -0
  79. package/dist/schedule/types.js +84 -0
  80. package/dist/terminal-activity.d.ts +21 -0
  81. package/dist/terminal-activity.js +25 -0
  82. package/dist/terminal-input-mode.d.ts +26 -0
  83. package/dist/terminal-input-mode.js +151 -0
  84. package/dist/terminal-key-input.d.ts +13 -0
  85. package/dist/terminal-key-input.js +201 -0
  86. package/dist/terminal-profiles.d.ts +6 -0
  87. package/dist/terminal-profiles.js +28 -0
  88. package/dist/terminal-snapshot.d.ts +3 -0
  89. package/dist/terminal-snapshot.js +178 -0
  90. package/dist/terminal-stream-protocol.d.ts +2 -0
  91. package/dist/terminal-stream-protocol.js +2 -0
  92. package/dist/terminal-subscription-key.d.ts +2 -0
  93. package/dist/terminal-subscription-key.js +9 -0
  94. package/dist/tool-call-display.d.ts +11 -0
  95. package/dist/tool-call-display.js +135 -0
  96. package/dist/tool-name-normalization.d.ts +9 -0
  97. package/dist/tool-name-normalization.js +82 -0
  98. package/dist/validation/ws-outbound-schema-metadata.d.ts +7256 -0
  99. package/dist/validation/ws-outbound-schema-metadata.js +3 -0
  100. package/dist/validation/ws-outbound.d.ts +12 -0
  101. package/dist/validation/ws-outbound.js +8 -0
  102. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # @otto-code/protocol
2
+
3
+ Shared Otto protocol schemas, codecs, and wire types.
4
+
5
+ ## Stability
6
+
7
+ This package is public so Otto's published packages can depend on it cleanly.
8
+ It is not a stable public API yet.
9
+
10
+ Schemas, exports, wire helpers, and types may change or disappear in any release
11
+ without advance notice. Use it outside Otto at your own risk until the package
12
+ is explicitly documented as stable.
@@ -0,0 +1,41 @@
1
+ export type AgentAttentionReason = "finished" | "error" | "permission";
2
+ export interface AgentAttentionNotificationData {
3
+ [key: string]: unknown;
4
+ serverId: string;
5
+ agentId: string;
6
+ reason: AgentAttentionReason;
7
+ }
8
+ export interface AgentAttentionNotificationPayload {
9
+ title: string;
10
+ body: string;
11
+ data: AgentAttentionNotificationData;
12
+ }
13
+ interface BuildAgentAttentionNotificationPayloadInput {
14
+ reason: AgentAttentionReason;
15
+ serverId: string;
16
+ agentId: string;
17
+ assistantMessage?: string | null;
18
+ permissionRequest?: NotificationPermissionRequest | null;
19
+ }
20
+ export interface NotificationPermissionRequest {
21
+ id: string;
22
+ provider: string;
23
+ name: string;
24
+ kind: "tool" | "plan" | "question" | "mode" | "other";
25
+ title?: string;
26
+ description?: string;
27
+ input?: Record<string, unknown>;
28
+ metadata?: Record<string, unknown>;
29
+ }
30
+ export type AssistantTimelineItem = {
31
+ type: "assistant_message";
32
+ text: string;
33
+ } | {
34
+ type: string;
35
+ text?: string;
36
+ };
37
+ export declare function findLatestAssistantMessageFromTimeline(timeline: readonly AssistantTimelineItem[]): string | null;
38
+ export declare function findLatestPermissionRequest(pendingPermissions: ReadonlyMap<string, NotificationPermissionRequest>): NotificationPermissionRequest | null;
39
+ export declare function buildAgentAttentionNotificationPayload(input: BuildAgentAttentionNotificationPayloadInput): AgentAttentionNotificationPayload;
40
+ export {};
41
+ //# sourceMappingURL=agent-attention-notification.d.ts.map
@@ -0,0 +1,140 @@
1
+ const NOTIFICATION_PREVIEW_LIMIT = 220;
2
+ const normalizeNotificationText = (text) => text.replace(/\s+/g, " ").trim();
3
+ const truncateNotificationText = (text, limit) => {
4
+ if (text.length <= limit) {
5
+ return text;
6
+ }
7
+ const trimmed = text.slice(0, Math.max(0, limit - 3)).trimEnd();
8
+ return trimmed.length > 0 ? `${trimmed}...` : text.slice(0, limit);
9
+ };
10
+ const stripMarkdownToText = (markdown) => {
11
+ let text = markdown.replace(/\r\n/g, "\n");
12
+ // Strip fenced code markers but keep the code content itself.
13
+ text = text.replace(/^\s*```[^\n]*$/gm, "");
14
+ text = text.replace(/^\s*~~~[^\n]*$/gm, "");
15
+ // Markdown links/images.
16
+ text = text.replace(/!\[([^\]]*)\]\((?:[^()\\]|\\.)*\)/g, "$1");
17
+ text = text.replace(/\[([^\]]+)\]\((?:[^()\\]|\\.)*\)/g, "$1");
18
+ // Structural prefixes.
19
+ text = text.replace(/^\s{0,3}#{1,6}\s+/gm, "");
20
+ text = text.replace(/^\s{0,3}>+\s?/gm, "");
21
+ text = text.replace(/^\s{0,3}(?:[*+-]|\d+\.)\s+/gm, "");
22
+ text = text.replace(/^\s{0,3}(?:[-*_]\s*){3,}$/gm, "");
23
+ // Inline markdown markers.
24
+ text = text.replace(/`([^`]+)`/g, "$1");
25
+ text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
26
+ text = text.replace(/__([^_]+)__/g, "$1");
27
+ text = text.replace(/\*([^*\n]+)\*/g, "$1");
28
+ text = text.replace(/_([^_\n]+)_/g, "$1");
29
+ text = text.replace(/~~([^~]+)~~/g, "$1");
30
+ // Angle-bracketed URL autolinks.
31
+ text = text.replace(/<([^>\n]+)>/g, "$1");
32
+ return text;
33
+ };
34
+ const buildNotificationPreview = (text) => {
35
+ if (!text) {
36
+ return null;
37
+ }
38
+ const normalized = normalizeNotificationText(stripMarkdownToText(text));
39
+ if (!normalized) {
40
+ return null;
41
+ }
42
+ return truncateNotificationText(normalized, NOTIFICATION_PREVIEW_LIMIT);
43
+ };
44
+ const safeStringify = (value) => {
45
+ try {
46
+ return JSON.stringify(value);
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ };
52
+ const buildPermissionDetails = (request) => {
53
+ if (!request) {
54
+ return null;
55
+ }
56
+ const title = request.title?.trim();
57
+ const description = request.description?.trim();
58
+ const details = [];
59
+ if (title) {
60
+ details.push(title);
61
+ }
62
+ if (description && description !== title) {
63
+ details.push(description);
64
+ }
65
+ if (details.length > 0) {
66
+ return details.join(" - ");
67
+ }
68
+ const inputPreview = request.input ? safeStringify(request.input) : null;
69
+ if (inputPreview) {
70
+ return inputPreview;
71
+ }
72
+ const metadataPreview = request.metadata ? safeStringify(request.metadata) : null;
73
+ if (metadataPreview) {
74
+ return metadataPreview;
75
+ }
76
+ return request.name?.trim() || request.kind;
77
+ };
78
+ export function findLatestAssistantMessageFromTimeline(timeline) {
79
+ // Providers may stream assistant content in consecutive chunks.
80
+ const chunks = [];
81
+ for (let i = timeline.length - 1; i >= 0; i -= 1) {
82
+ const item = timeline[i];
83
+ if (item.type !== "assistant_message" || typeof item.text !== "string") {
84
+ if (chunks.length > 0) {
85
+ break;
86
+ }
87
+ continue;
88
+ }
89
+ chunks.push(item.text);
90
+ }
91
+ if (chunks.length === 0) {
92
+ return null;
93
+ }
94
+ return chunks.toReversed().join("");
95
+ }
96
+ export function findLatestPermissionRequest(pendingPermissions) {
97
+ let latest = null;
98
+ for (const request of pendingPermissions.values()) {
99
+ latest = request;
100
+ }
101
+ return latest;
102
+ }
103
+ function resolveAgentAttentionTitle(reason) {
104
+ if (reason === "permission")
105
+ return "Agent needs permission";
106
+ if (reason === "error")
107
+ return "Agent needs attention";
108
+ return "Agent finished";
109
+ }
110
+ function resolveAgentAttentionPreview(input) {
111
+ if (input.reason === "finished") {
112
+ return buildNotificationPreview(input.assistantMessage);
113
+ }
114
+ if (input.reason === "permission") {
115
+ return buildNotificationPreview(buildPermissionDetails(input.permissionRequest));
116
+ }
117
+ return null;
118
+ }
119
+ function resolveAgentAttentionFallbackBody(reason) {
120
+ if (reason === "permission")
121
+ return "Permission requested.";
122
+ if (reason === "error")
123
+ return "Encountered an error.";
124
+ return "Finished working.";
125
+ }
126
+ export function buildAgentAttentionNotificationPayload(input) {
127
+ const title = resolveAgentAttentionTitle(input.reason);
128
+ const preview = resolveAgentAttentionPreview(input);
129
+ const body = preview ?? resolveAgentAttentionFallbackBody(input.reason);
130
+ return {
131
+ title,
132
+ body,
133
+ data: {
134
+ serverId: input.serverId,
135
+ agentId: input.agentId,
136
+ reason: input.reason,
137
+ },
138
+ };
139
+ }
140
+ //# sourceMappingURL=agent-attention-notification.js.map
@@ -0,0 +1,7 @@
1
+ export declare const PARENT_AGENT_ID_LABEL = "otto.parent-agent-id";
2
+ export interface AgentLabelSource {
3
+ labels?: Record<string, unknown> | null;
4
+ }
5
+ export declare function getParentAgentIdFromLabels(labels: Record<string, unknown> | null | undefined): string | null;
6
+ export declare function isDelegatedAgent(agent: AgentLabelSource): boolean;
7
+ //# sourceMappingURL=agent-labels.d.ts.map
@@ -0,0 +1,11 @@
1
+ export const PARENT_AGENT_ID_LABEL = "otto.parent-agent-id";
2
+ export function getParentAgentIdFromLabels(labels) {
3
+ const parentAgentId = labels?.[PARENT_AGENT_ID_LABEL];
4
+ return typeof parentAgentId === "string" && parentAgentId.trim().length > 0
5
+ ? parentAgentId.trim()
6
+ : null;
7
+ }
8
+ export function isDelegatedAgent(agent) {
9
+ return getParentAgentIdFromLabels(agent.labels) !== null;
10
+ }
11
+ //# sourceMappingURL=agent-labels.js.map
@@ -0,0 +1,3 @@
1
+ export declare const AGENT_LIFECYCLE_STATUSES: readonly ["initializing", "idle", "running", "error", "closed"];
2
+ export type AgentLifecycleStatus = (typeof AGENT_LIFECYCLE_STATUSES)[number];
3
+ //# sourceMappingURL=agent-lifecycle.d.ts.map
@@ -0,0 +1,8 @@
1
+ export const AGENT_LIFECYCLE_STATUSES = [
2
+ "initializing",
3
+ "idle",
4
+ "running",
5
+ "error",
6
+ "closed",
7
+ ];
8
+ //# sourceMappingURL=agent-lifecycle.js.map
@@ -0,0 +1,32 @@
1
+ import { type AgentPersonality, type PersonalityRole } from "./messages.js";
2
+ export declare function isPersonalityRole(value: string): value is PersonalityRole;
3
+ /**
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.
6
+ * Unknown roles from a newer peer are dropped rather than trusted.
7
+ */
8
+ export declare function normalizePersonalityRoles(roles: readonly string[] | undefined): PersonalityRole[];
9
+ export declare function personalityHasRole(personality: Pick<AgentPersonality, "roles">, role: PersonalityRole): boolean;
10
+ export type PersonalityUnavailableCode = "provider-missing" | "provider-disabled" | "provider-not-ready" | "model-missing" | "mode-missing";
11
+ export interface PersonalityAvailabilityInput {
12
+ /** Provider snapshot status, or undefined when the provider is absent entirely. */
13
+ providerStatus: "ready" | "loading" | "error" | "unavailable" | undefined;
14
+ providerEnabled: boolean | undefined;
15
+ modelIds: readonly string[] | undefined;
16
+ modeIds: readonly string[] | undefined;
17
+ }
18
+ export type PersonalityAvailability = {
19
+ available: true;
20
+ } | {
21
+ available: false;
22
+ code: PersonalityUnavailableCode;
23
+ reason: string;
24
+ };
25
+ /**
26
+ * Decide whether a personality is usable against a provider's current snapshot.
27
+ * A personality is out of commission the moment any bound setting can't resolve:
28
+ * provider absent/disabled/not-ready, model gone, or an explicit mode missing.
29
+ * The caller grays it out in pickers and hard-fails it in automation.
30
+ */
31
+ export declare function checkPersonalityAvailability(personality: Pick<AgentPersonality, "provider" | "model" | "modeId">, input: PersonalityAvailabilityInput): PersonalityAvailability;
32
+ //# sourceMappingURL=agent-personalities.d.ts.map
@@ -0,0 +1,69 @@
1
+ import { PERSONALITY_ROLES } from "./messages.js";
2
+ // Pure, dependency-free personality helpers shared by the daemon (spawn-time
3
+ // resolution) and the app (picker availability + role filtering). Effort
4
+ // resolution is NOT here — it needs the model's advertised thinking options and
5
+ // lives with the daemon's effort resolver; availability does not depend on it.
6
+ const ROLE_SET = new Set(PERSONALITY_ROLES);
7
+ export function isPersonalityRole(value) {
8
+ return ROLE_SET.has(value);
9
+ }
10
+ /**
11
+ * Filter an arbitrary role array (roles ride the wire as plain strings) down to
12
+ * the known set, deduped and returned in canonical `PERSONALITY_ROLES` order.
13
+ * Unknown roles from a newer peer are dropped rather than trusted.
14
+ */
15
+ export function normalizePersonalityRoles(roles) {
16
+ if (!roles || roles.length === 0) {
17
+ return [];
18
+ }
19
+ const present = new Set(roles.filter(isPersonalityRole));
20
+ return PERSONALITY_ROLES.filter((role) => present.has(role));
21
+ }
22
+ export function personalityHasRole(personality, role) {
23
+ return normalizePersonalityRoles(personality.roles).includes(role);
24
+ }
25
+ /**
26
+ * Decide whether a personality is usable against a provider's current snapshot.
27
+ * A personality is out of commission the moment any bound setting can't resolve:
28
+ * provider absent/disabled/not-ready, model gone, or an explicit mode missing.
29
+ * The caller grays it out in pickers and hard-fails it in automation.
30
+ */
31
+ export function checkPersonalityAvailability(personality, input) {
32
+ if (input.providerStatus === undefined) {
33
+ return {
34
+ available: false,
35
+ code: "provider-missing",
36
+ reason: `Provider "${personality.provider}" is not configured on this host.`,
37
+ };
38
+ }
39
+ if (input.providerEnabled === false) {
40
+ return {
41
+ available: false,
42
+ code: "provider-disabled",
43
+ reason: `Provider "${personality.provider}" is disabled.`,
44
+ };
45
+ }
46
+ if (input.providerStatus !== "ready") {
47
+ return {
48
+ available: false,
49
+ code: "provider-not-ready",
50
+ reason: `Provider "${personality.provider}" is not ready (${input.providerStatus}).`,
51
+ };
52
+ }
53
+ if (!(input.modelIds ?? []).includes(personality.model)) {
54
+ return {
55
+ available: false,
56
+ code: "model-missing",
57
+ reason: `Model "${personality.model}" is not available from "${personality.provider}".`,
58
+ };
59
+ }
60
+ if (personality.modeId !== undefined && !(input.modeIds ?? []).includes(personality.modeId)) {
61
+ return {
62
+ available: false,
63
+ code: "mode-missing",
64
+ reason: `Mode "${personality.modeId}" is not available from "${personality.provider}".`,
65
+ };
66
+ }
67
+ return { available: true };
68
+ }
69
+ //# sourceMappingURL=agent-personalities.js.map
@@ -0,0 +1,14 @@
1
+ import type { AgentLifecycleStatus } from "./agent-lifecycle.js";
2
+ import type { WorkspaceStateBucket } from "./messages.js";
3
+ export type { WorkspaceStateBucket };
4
+ export type AgentAttentionReason = "finished" | "error" | "permission" | null | undefined;
5
+ export interface AgentStateBucketInput {
6
+ status: AgentLifecycleStatus;
7
+ pendingPermissionCount?: number;
8
+ requiresAttention?: boolean;
9
+ attentionReason?: AgentAttentionReason;
10
+ }
11
+ export declare function deriveAgentStateBucket(input: AgentStateBucketInput): WorkspaceStateBucket;
12
+ export declare function getWorkspaceStateBucketPriority(bucket: WorkspaceStateBucket): number;
13
+ export declare function getAgentStatusPriority(input: AgentStateBucketInput): number;
14
+ //# sourceMappingURL=agent-state-bucket.d.ts.map
@@ -0,0 +1,41 @@
1
+ const WORKSPACE_STATE_BUCKET_PRIORITY = {
2
+ needs_input: 0,
3
+ failed: 1,
4
+ running: 2,
5
+ attention: 3,
6
+ done: 4,
7
+ };
8
+ export function deriveAgentStateBucket(input) {
9
+ if ((input.pendingPermissionCount ?? 0) > 0 || input.attentionReason === "permission") {
10
+ return "needs_input";
11
+ }
12
+ if (input.status === "error" || input.attentionReason === "error") {
13
+ return "failed";
14
+ }
15
+ if (input.status === "running") {
16
+ return "running";
17
+ }
18
+ if (input.requiresAttention) {
19
+ return "attention";
20
+ }
21
+ return "done";
22
+ }
23
+ export function getWorkspaceStateBucketPriority(bucket) {
24
+ return WORKSPACE_STATE_BUCKET_PRIORITY[bucket];
25
+ }
26
+ export function getAgentStatusPriority(input) {
27
+ if ((input.pendingPermissionCount ?? 0) > 0 || input.attentionReason === "permission") {
28
+ return 0;
29
+ }
30
+ if (input.status === "error" || input.attentionReason === "error") {
31
+ return 1;
32
+ }
33
+ if (input.status === "running") {
34
+ return 2;
35
+ }
36
+ if (input.status === "initializing") {
37
+ return 3;
38
+ }
39
+ return 4;
40
+ }
41
+ //# sourceMappingURL=agent-state-bucket.js.map
@@ -0,0 +1,2 @@
1
+ export declare const MAX_EXPLICIT_AGENT_TITLE_CHARS = 200;
2
+ //# sourceMappingURL=agent-title-limits.d.ts.map
@@ -0,0 +1,2 @@
1
+ export const MAX_EXPLICIT_AGENT_TITLE_CHARS = 200;
2
+ //# sourceMappingURL=agent-title-limits.js.map