@otto-code/protocol 0.8.9 → 0.8.12

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 (60) hide show
  1. package/dist/agent-queue.d.ts +87 -0
  2. package/dist/agent-queue.js +106 -0
  3. package/dist/brain.d.ts +2321 -0
  4. package/dist/brain.js +1082 -0
  5. package/dist/client-capabilities.d.ts +2 -0
  6. package/dist/client-capabilities.js +10 -0
  7. package/dist/code-intelligence.d.ts +917 -0
  8. package/dist/code-intelligence.js +699 -0
  9. package/dist/communications.d.ts +1106 -0
  10. package/dist/communications.js +384 -0
  11. package/dist/context.d.ts +787 -0
  12. package/dist/context.js +295 -0
  13. package/dist/daemon-config.d.ts +377 -0
  14. package/dist/daemon-config.js +395 -0
  15. package/dist/file-operations.d.ts +380 -0
  16. package/dist/file-operations.js +282 -0
  17. package/dist/generated/validation/ws-outbound.aot.js +54927 -48878
  18. package/dist/git-hosting.d.ts +117 -0
  19. package/dist/git-hosting.js +109 -0
  20. package/dist/git-operations.d.ts +255 -0
  21. package/dist/git-operations.js +221 -0
  22. package/dist/integration-authorization.d.ts +195 -0
  23. package/dist/integration-authorization.js +125 -0
  24. package/dist/kanban.d.ts +341 -0
  25. package/dist/kanban.js +273 -0
  26. package/dist/loop/rpc-schemas.d.ts +6 -6
  27. package/dist/meetings.d.ts +95 -0
  28. package/dist/meetings.js +57 -0
  29. package/dist/messages.d.ts +21054 -24042
  30. package/dist/messages.js +4355 -8731
  31. package/dist/orchestration.d.ts +726 -0
  32. package/dist/orchestration.js +232 -0
  33. package/dist/personality-schemas.d.ts +221 -0
  34. package/dist/personality-schemas.js +340 -0
  35. package/dist/preview.d.ts +140 -0
  36. package/dist/preview.js +98 -0
  37. package/dist/project-knowledge.d.ts +740 -0
  38. package/dist/project-knowledge.js +229 -0
  39. package/dist/project-links.d.ts +102 -0
  40. package/dist/project-links.js +62 -0
  41. package/dist/provider-config.d.ts +87 -2
  42. package/dist/provider-config.js +110 -0
  43. package/dist/refine.d.ts +93 -0
  44. package/dist/refine.js +78 -0
  45. package/dist/schedule/rpc-schemas.d.ts +47 -47
  46. package/dist/schedule/types.d.ts +13 -13
  47. package/dist/speech.d.ts +180 -0
  48. package/dist/speech.js +177 -0
  49. package/dist/storage.d.ts +79 -0
  50. package/dist/storage.js +107 -0
  51. package/dist/suggested-tasks.d.ts +106 -0
  52. package/dist/suggested-tasks.js +81 -0
  53. package/dist/terminal-compatibility.d.ts +55 -0
  54. package/dist/terminal-compatibility.js +35 -0
  55. package/dist/usage-stats.d.ts +255 -0
  56. package/dist/usage-stats.js +162 -0
  57. package/dist/validation/ws-outbound-schema-metadata.d.ts +1404 -274
  58. package/dist/worktree-ops.d.ts +81 -0
  59. package/dist/worktree-ops.js +88 -0
  60. package/package.json +1 -1
@@ -0,0 +1,229 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Otto project-knowledge wire schemas: the project.knowledge.* RPCs and the delivery, reference and progress payloads they carry. Fork-only capability, so it owns its schemas; messages.ts re-exports them.
4
+ */
5
+ // Repo-owned project knowledge is canonical Markdown under `.otto/knowledge`,
6
+ // with daemon-owned writes so worktrees resolve to one store and every truth
7
+ // change retains its timeline evidence.
8
+ export const ProjectKnowledgeKindSchema = z.enum([
9
+ "decision",
10
+ "constraint",
11
+ "requirement",
12
+ "architecture",
13
+ "finding",
14
+ "project",
15
+ "reference",
16
+ ]);
17
+ export const ProjectKnowledgeStatusSchema = z.enum(["proposed", "confirmed", "superseded"]);
18
+ export const ProjectDeliveryStatusSchema = z.enum([
19
+ "charter",
20
+ "in_build",
21
+ "partial",
22
+ "blocked",
23
+ "complete",
24
+ "reference",
25
+ "deferred",
26
+ "cancelled",
27
+ ]);
28
+ export const ProjectReferenceDispositionSchema = z.enum([
29
+ "unevaluated",
30
+ "read",
31
+ "adopted",
32
+ "rejected",
33
+ "dependency",
34
+ ]);
35
+ export const ProjectProgressSchema = z.object({
36
+ completed: z.number().int().nonnegative(),
37
+ total: z.number().int().positive(),
38
+ unit: z.string().min(1).max(48),
39
+ });
40
+ export const ProjectKnowledgeRecordSchema = z.object({
41
+ id: z.string(),
42
+ kind: ProjectKnowledgeKindSchema,
43
+ title: z.string(),
44
+ statement: z.string(),
45
+ statementDigest: z.string().optional(),
46
+ evidence: z.string().optional(),
47
+ tags: z.array(z.string()),
48
+ status: ProjectKnowledgeStatusSchema,
49
+ deliveryStatus: ProjectDeliveryStatusSchema.optional(),
50
+ progress: ProjectProgressSchema.optional(),
51
+ referenceDisposition: ProjectReferenceDispositionSchema.optional(),
52
+ sourceUrl: z.string().optional(),
53
+ createdAt: z.string(),
54
+ updatedAt: z.string(),
55
+ provenance: z
56
+ .array(z.object({
57
+ text: z.string(),
58
+ recordedAt: z.string(),
59
+ source: z.string().optional(),
60
+ kind: z.string().optional(),
61
+ affects: z.array(z.string()).optional(),
62
+ }))
63
+ .optional(),
64
+ path: z.string().optional(),
65
+ });
66
+ /** Review health, not a persisted project-knowledge finding record. */
67
+ export const ProjectKnowledgeHealthSchema = z.object({
68
+ kind: z.enum(["stale", "overlapping_tags", "overlapping_statement"]),
69
+ recordId: z.string(),
70
+ relatedRecordId: z.string().optional(),
71
+ tagOverlap: z.enum(["complete", "partial"]).optional(),
72
+ sharedTags: z.array(z.string()).optional(),
73
+ message: z.string(),
74
+ });
75
+ export const ProjectKnowledgeRootPageSchema = z.object({
76
+ slug: z.string(),
77
+ title: z.string(),
78
+ path: z.string(),
79
+ body: z.string(),
80
+ });
81
+ export const ProjectKnowledgeListRequestMessageSchema = z.object({
82
+ type: z.literal("project.knowledge.list.request"),
83
+ requestId: z.string(),
84
+ workspaceId: z.string(),
85
+ });
86
+ export const ProjectKnowledgeListResponseMessageSchema = z.object({
87
+ type: z.literal("project.knowledge.list.response"),
88
+ payload: z.object({
89
+ requestId: z.string(),
90
+ records: z.array(ProjectKnowledgeRecordSchema),
91
+ rootPages: z.array(ProjectKnowledgeRootPageSchema).optional(),
92
+ findings: z.array(ProjectKnowledgeHealthSchema),
93
+ brief: z.string(),
94
+ briefTokens: z.number(),
95
+ includedIds: z.array(z.string()),
96
+ omittedCount: z.number(),
97
+ }),
98
+ });
99
+ export const ProjectKnowledgeGetRequestMessageSchema = z.object({
100
+ type: z.literal("project.knowledge.get.request"),
101
+ requestId: z.string(),
102
+ workspaceId: z.string(),
103
+ id: z.string(),
104
+ });
105
+ export const ProjectKnowledgeGetResponseMessageSchema = z.object({
106
+ type: z.literal("project.knowledge.get.response"),
107
+ payload: z.object({ requestId: z.string(), record: ProjectKnowledgeRecordSchema.nullable() }),
108
+ });
109
+ export const ProjectKnowledgeCreateRequestMessageSchema = z.object({
110
+ type: z.literal("project.knowledge.create.request"),
111
+ requestId: z.string(),
112
+ workspaceId: z.string(),
113
+ id: z.string().optional(),
114
+ kind: ProjectKnowledgeKindSchema,
115
+ title: z.string().max(160),
116
+ statement: z.string(),
117
+ evidence: z.string().optional(),
118
+ tags: z.array(z.string().max(48)).max(32).optional(),
119
+ affects: z.array(z.string()).optional(),
120
+ status: ProjectKnowledgeStatusSchema.optional(),
121
+ deliveryStatus: ProjectDeliveryStatusSchema.optional(),
122
+ progress: ProjectProgressSchema.optional(),
123
+ referenceDisposition: ProjectReferenceDispositionSchema.optional(),
124
+ sourceUrl: z.string().optional(),
125
+ });
126
+ export const ProjectKnowledgeCreateResponseMessageSchema = z.object({
127
+ type: z.literal("project.knowledge.create.response"),
128
+ payload: z.object({ requestId: z.string(), record: ProjectKnowledgeRecordSchema }),
129
+ });
130
+ export const ProjectKnowledgeApplyRequestMessageSchema = z.object({
131
+ type: z.literal("project.knowledge.apply.request"),
132
+ requestId: z.string(),
133
+ workspaceId: z.string(),
134
+ id: z.string(),
135
+ title: z.string().max(160).optional(),
136
+ statement: z.string().optional(),
137
+ evidence: z.string().optional(),
138
+ provenanceText: z.string().optional(),
139
+ provenanceSource: z.string().max(160).optional(),
140
+ provenanceAffects: z.array(z.string()).optional(),
141
+ expectedUpdatedAt: z.string().optional(),
142
+ });
143
+ export const ProjectKnowledgeApplyResponseMessageSchema = z.object({
144
+ type: z.literal("project.knowledge.apply.response"),
145
+ payload: z.object({
146
+ requestId: z.string(),
147
+ record: ProjectKnowledgeRecordSchema.nullable(),
148
+ error: z.string().optional(),
149
+ }),
150
+ });
151
+ export const ProjectKnowledgeStatusRequestMessageSchema = z.object({
152
+ type: z.literal("project.knowledge.status.request"),
153
+ requestId: z.string(),
154
+ workspaceId: z.string(),
155
+ id: z.string(),
156
+ status: ProjectKnowledgeStatusSchema,
157
+ reason: z.string().optional(),
158
+ });
159
+ export const ProjectKnowledgeStatusResponseMessageSchema = z.object({
160
+ type: z.literal("project.knowledge.status.response"),
161
+ payload: z.object({ requestId: z.string(), record: ProjectKnowledgeRecordSchema.nullable() }),
162
+ });
163
+ export const ProjectKnowledgeProjectApplyRequestMessageSchema = z.object({
164
+ type: z.literal("project.knowledge.project.apply.request"),
165
+ requestId: z.string(),
166
+ workspaceId: z.string(),
167
+ id: z.string(),
168
+ deliveryStatus: ProjectDeliveryStatusSchema.optional(),
169
+ progress: ProjectProgressSchema.nullable().optional(),
170
+ reason: z.string(),
171
+ expectedUpdatedAt: z.string().optional(),
172
+ });
173
+ export const ProjectKnowledgeProjectApplyResponseMessageSchema = z.object({
174
+ type: z.literal("project.knowledge.project.apply.response"),
175
+ payload: z.object({
176
+ requestId: z.string(),
177
+ record: ProjectKnowledgeRecordSchema.nullable(),
178
+ error: z.string().optional(),
179
+ }),
180
+ });
181
+ export const ProjectKnowledgeReferenceApplyRequestMessageSchema = z.object({
182
+ type: z.literal("project.knowledge.reference.apply.request"),
183
+ requestId: z.string(),
184
+ workspaceId: z.string(),
185
+ id: z.string(),
186
+ disposition: ProjectReferenceDispositionSchema.optional(),
187
+ sourceUrl: z.string().nullable().optional(),
188
+ reason: z.string(),
189
+ expectedUpdatedAt: z.string().optional(),
190
+ });
191
+ export const ProjectKnowledgeReferenceApplyResponseMessageSchema = z.object({
192
+ type: z.literal("project.knowledge.reference.apply.response"),
193
+ payload: z.object({
194
+ requestId: z.string(),
195
+ record: ProjectKnowledgeRecordSchema.nullable(),
196
+ error: z.string().optional(),
197
+ }),
198
+ });
199
+ export const ProjectKnowledgeRootApplyRequestMessageSchema = z.object({
200
+ type: z.literal("project.knowledge.root.apply.request"),
201
+ requestId: z.string(),
202
+ workspaceId: z.string(),
203
+ slug: z.string(),
204
+ body: z.string(),
205
+ });
206
+ export const ProjectKnowledgeRootApplyResponseMessageSchema = z.object({
207
+ type: z.literal("project.knowledge.root.apply.response"),
208
+ payload: z.object({
209
+ requestId: z.string(),
210
+ page: ProjectKnowledgeRootPageSchema.nullable(),
211
+ }),
212
+ });
213
+ export const ProjectKnowledgeDeleteRequestMessageSchema = z.object({
214
+ type: z.literal("project.knowledge.delete.request"),
215
+ requestId: z.string(),
216
+ workspaceId: z.string(),
217
+ id: z.string(),
218
+ reason: z.string(),
219
+ expectedUpdatedAt: z.string().optional(),
220
+ });
221
+ export const ProjectKnowledgeDeleteResponseMessageSchema = z.object({
222
+ type: z.literal("project.knowledge.delete.response"),
223
+ payload: z.object({
224
+ requestId: z.string(),
225
+ deleted: z.boolean(),
226
+ error: z.string().optional(),
227
+ }),
228
+ });
229
+ //# sourceMappingURL=project-knowledge.js.map
@@ -0,0 +1,102 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Otto project-link wire schemas: the project.links.* RPCs and their payloads. Fork-only capability, so it owns its schemas; messages.ts re-exports them.
4
+ */
5
+ export declare const ProjectLinkSchema: z.ZodObject<{
6
+ projectAId: z.ZodString;
7
+ projectBId: z.ZodString;
8
+ }, z.core.$strip>;
9
+ export declare const ProjectLinksListRequestSchema: z.ZodObject<{
10
+ type: z.ZodLiteral<"project.links.list.request">;
11
+ requestId: z.ZodString;
12
+ }, z.core.$strip>;
13
+ export declare const ProjectLinksSetRequestSchema: z.ZodObject<{
14
+ type: z.ZodLiteral<"project.links.set.request">;
15
+ projectId: z.ZodString;
16
+ otherProjectId: z.ZodString;
17
+ requestId: z.ZodString;
18
+ }, z.core.$strip>;
19
+ export declare const ProjectLinksUnsetRequestSchema: z.ZodObject<{
20
+ type: z.ZodLiteral<"project.links.unset.request">;
21
+ projectId: z.ZodString;
22
+ otherProjectId: z.ZodString;
23
+ requestId: z.ZodString;
24
+ }, z.core.$strip>;
25
+ export declare const ProjectLinksListResponsePayloadSchema: z.ZodObject<{
26
+ requestId: z.ZodString;
27
+ links: z.ZodDefault<z.ZodArray<z.ZodObject<{
28
+ projectAId: z.ZodString;
29
+ projectBId: z.ZodString;
30
+ }, z.core.$strip>>>;
31
+ error: z.ZodNullable<z.ZodString>;
32
+ }, z.core.$strip>;
33
+ export declare const ProjectLinksListResponseSchema: z.ZodObject<{
34
+ type: z.ZodLiteral<"project.links.list.response">;
35
+ payload: z.ZodObject<{
36
+ requestId: z.ZodString;
37
+ links: z.ZodDefault<z.ZodArray<z.ZodObject<{
38
+ projectAId: z.ZodString;
39
+ projectBId: z.ZodString;
40
+ }, z.core.$strip>>>;
41
+ error: z.ZodNullable<z.ZodString>;
42
+ }, z.core.$strip>;
43
+ }, z.core.$strip>;
44
+ export declare const ProjectLinksMutationResponsePayloadSchema: z.ZodObject<{
45
+ requestId: z.ZodString;
46
+ accepted: z.ZodBoolean;
47
+ links: z.ZodDefault<z.ZodArray<z.ZodObject<{
48
+ projectAId: z.ZodString;
49
+ projectBId: z.ZodString;
50
+ }, z.core.$strip>>>;
51
+ error: z.ZodNullable<z.ZodString>;
52
+ }, z.core.$strip>;
53
+ export declare const ProjectLinksSetResponseSchema: z.ZodObject<{
54
+ type: z.ZodLiteral<"project.links.set.response">;
55
+ payload: z.ZodObject<{
56
+ requestId: z.ZodString;
57
+ accepted: z.ZodBoolean;
58
+ links: z.ZodDefault<z.ZodArray<z.ZodObject<{
59
+ projectAId: z.ZodString;
60
+ projectBId: z.ZodString;
61
+ }, z.core.$strip>>>;
62
+ error: z.ZodNullable<z.ZodString>;
63
+ }, z.core.$strip>;
64
+ }, z.core.$strip>;
65
+ export declare const ProjectLinksUnsetResponseSchema: z.ZodObject<{
66
+ type: z.ZodLiteral<"project.links.unset.response">;
67
+ payload: z.ZodObject<{
68
+ requestId: z.ZodString;
69
+ accepted: z.ZodBoolean;
70
+ links: z.ZodDefault<z.ZodArray<z.ZodObject<{
71
+ projectAId: z.ZodString;
72
+ projectBId: z.ZodString;
73
+ }, z.core.$strip>>>;
74
+ error: z.ZodNullable<z.ZodString>;
75
+ }, z.core.$strip>;
76
+ }, z.core.$strip>;
77
+ export declare const ProjectLinksChangedPayloadSchema: z.ZodObject<{
78
+ links: z.ZodDefault<z.ZodArray<z.ZodObject<{
79
+ projectAId: z.ZodString;
80
+ projectBId: z.ZodString;
81
+ }, z.core.$strip>>>;
82
+ }, z.core.$strip>;
83
+ export declare const ProjectLinksChangedSchema: z.ZodObject<{
84
+ type: z.ZodLiteral<"project.links.changed">;
85
+ payload: z.ZodObject<{
86
+ links: z.ZodDefault<z.ZodArray<z.ZodObject<{
87
+ projectAId: z.ZodString;
88
+ projectBId: z.ZodString;
89
+ }, z.core.$strip>>>;
90
+ }, z.core.$strip>;
91
+ }, z.core.$strip>;
92
+ export type ProjectLink = z.infer<typeof ProjectLinkSchema>;
93
+ export type ProjectLinksListResponse = z.infer<typeof ProjectLinksListResponseSchema>;
94
+ export type ProjectLinksListResponsePayload = z.infer<typeof ProjectLinksListResponsePayloadSchema>;
95
+ export type ProjectLinksSetResponse = z.infer<typeof ProjectLinksSetResponseSchema>;
96
+ export type ProjectLinksUnsetResponse = z.infer<typeof ProjectLinksUnsetResponseSchema>;
97
+ export type ProjectLinksMutationResponsePayload = z.infer<typeof ProjectLinksMutationResponsePayloadSchema>;
98
+ export type ProjectLinksChanged = z.infer<typeof ProjectLinksChangedSchema>;
99
+ export type ProjectLinksListRequest = z.infer<typeof ProjectLinksListRequestSchema>;
100
+ export type ProjectLinksSetRequest = z.infer<typeof ProjectLinksSetRequestSchema>;
101
+ export type ProjectLinksUnsetRequest = z.infer<typeof ProjectLinksUnsetRequestSchema>;
102
+ //# sourceMappingURL=project-links.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Otto project-link wire schemas: the project.links.* RPCs and their payloads. Fork-only capability, so it owns its schemas; messages.ts re-exports them.
4
+ */
5
+ // An unordered pair of linked projects. The daemon stores the pair in a
6
+ // canonical order, but clients treat it as undirected: a link between A and B
7
+ // permits opening files across both projects. See the gated-multi-root project.
8
+ export const ProjectLinkSchema = z.object({
9
+ projectAId: z.string(),
10
+ projectBId: z.string(),
11
+ });
12
+ export const ProjectLinksListRequestSchema = z.object({
13
+ type: z.literal("project.links.list.request"),
14
+ requestId: z.string(),
15
+ });
16
+ export const ProjectLinksSetRequestSchema = z.object({
17
+ type: z.literal("project.links.set.request"),
18
+ // Order is irrelevant; the daemon canonicalizes. Linking is idempotent.
19
+ projectId: z.string(),
20
+ otherProjectId: z.string(),
21
+ requestId: z.string(),
22
+ });
23
+ export const ProjectLinksUnsetRequestSchema = z.object({
24
+ type: z.literal("project.links.unset.request"),
25
+ projectId: z.string(),
26
+ otherProjectId: z.string(),
27
+ requestId: z.string(),
28
+ });
29
+ export const ProjectLinksListResponsePayloadSchema = z.object({
30
+ requestId: z.string(),
31
+ links: z.array(ProjectLinkSchema).default([]),
32
+ error: z.string().nullable(),
33
+ });
34
+ export const ProjectLinksListResponseSchema = z.object({
35
+ type: z.literal("project.links.list.response"),
36
+ payload: ProjectLinksListResponsePayloadSchema,
37
+ });
38
+ export const ProjectLinksMutationResponsePayloadSchema = z.object({
39
+ requestId: z.string(),
40
+ accepted: z.boolean(),
41
+ // The full link set after the mutation, so the client refreshes in one hop.
42
+ links: z.array(ProjectLinkSchema).default([]),
43
+ error: z.string().nullable(),
44
+ });
45
+ export const ProjectLinksSetResponseSchema = z.object({
46
+ type: z.literal("project.links.set.response"),
47
+ payload: ProjectLinksMutationResponsePayloadSchema,
48
+ });
49
+ export const ProjectLinksUnsetResponseSchema = z.object({
50
+ type: z.literal("project.links.unset.response"),
51
+ payload: ProjectLinksMutationResponsePayloadSchema,
52
+ });
53
+ // Pushed to the session whenever the link set changes (mutation or cascade on
54
+ // project removal) so open UIs re-evaluate cross-project access without polling.
55
+ export const ProjectLinksChangedPayloadSchema = z.object({
56
+ links: z.array(ProjectLinkSchema).default([]),
57
+ });
58
+ export const ProjectLinksChangedSchema = z.object({
59
+ type: z.literal("project.links.changed"),
60
+ payload: ProjectLinksChangedPayloadSchema,
61
+ });
62
+ //# sourceMappingURL=project-links.js.map
@@ -260,6 +260,66 @@ export declare const COMPACTION_THRESHOLD_PERCENTS: readonly [50, 60, 70, 80, 90
260
260
  export declare const MAX_TOOL_ROUNDS_DEFAULT = 50;
261
261
  export declare const MAX_TOOL_ROUNDS_MIN = 1;
262
262
  export declare const MAX_TOOL_ROUNDS_MAX = 1000;
263
+ /**
264
+ * Action circuit-breaker for daemon-hosted tool loops (openai-compat /
265
+ * Otto Brain). When enabled, an action (tool name + exact arguments) that
266
+ * fails this many times in a row stops being executed: the rest of the
267
+ * round's identical calls are dropped, and a repair prompt is sent to the
268
+ * model explaining what failed and how to change course. Small local models
269
+ * can degenerate into emitting the same broken action hundreds of times per
270
+ * round (the 2,912-call browser_navigate({}) incident); without the breaker
271
+ * each identical failure is appended to the conversation verbatim until the
272
+ * context window is blown and the turn dies. Defaults are safe for every
273
+ * model; bounds keep the setting a guard rail, not an off switch.
274
+ */
275
+ export declare const ACTION_BREAKER_DEFAULT_THRESHOLD = 5;
276
+ export declare const ACTION_BREAKER_MIN_THRESHOLD = 2;
277
+ export declare const ACTION_BREAKER_MAX_THRESHOLD = 100;
278
+ /**
279
+ * Tool-emission stall guard (daemon-wide, provider-agnostic). A healthy agent
280
+ * turn either acts - it emits a tool call - or it hands back to the human. An
281
+ * assistant message that does neither has no side effect, so a run that only
282
+ * produces those is not working, it is stuck. This is the count of consecutive
283
+ * such messages tolerated before the daemon interrupts the run.
284
+ *
285
+ * Purely structural: it counts stream events, never their text. A tool call
286
+ * resets it to zero, so a long working loop (a 200-turn investigation with a
287
+ * tool call every few turns) can never trip it; so does a real user prompt, so
288
+ * ordinary back-and-forth chat can never trip it either.
289
+ *
290
+ * `0` disables the guard. Bounds keep the setting a guard rail rather than a
291
+ * hair trigger. See agent-stall-guard.ts and the "unbounded tool-call
292
+ * announcement" finding: a local model emitted ~840 consecutive text-only
293
+ * messages over five minutes, re-announcing three tool calls it never sent, and
294
+ * nothing in the runtime noticed.
295
+ */
296
+ export declare const STALL_GUARD_DEFAULT_THRESHOLD = 15;
297
+ export declare const STALL_GUARD_MIN_THRESHOLD = 3;
298
+ export declare const STALL_GUARD_MAX_THRESHOLD = 500;
299
+ /**
300
+ * Per-round assistant-text budget for daemon-hosted tool loops (openai-compat /
301
+ * Otto Brain). The same invariant the stall guard enforces across messages, held
302
+ * *within* one: a model round that has streamed this much prose while emitting
303
+ * no tool call is neither acting nor finishing, so the turn is interrupted.
304
+ *
305
+ * This is the sibling STALL_GUARD_* cannot cover. That counter keys on
306
+ * `messageId` so a message streamed as a burst of deltas counts once - correct,
307
+ * and the reason it reads 1 for a *single* runaway generation. In the
308
+ * "unbounded tool-call announcement" incident the model produced 66,384
309
+ * characters inside one completion over 5m40s, never emitting a stop token and
310
+ * never emitting a tool call; the run ended only because the user typed
311
+ * /compact. Counting characters within the round is what catches that shape.
312
+ *
313
+ * Structural, not textual: it measures how much was produced, never what was
314
+ * said. Reasoning/thinking text is deliberately excluded - high-effort thinking
315
+ * is legitimately long, and the degeneration this bounds is in content.
316
+ *
317
+ * The default is far above any real answer (roughly 8K tokens of prose in one
318
+ * round), so it is a safety valve rather than a style limit. `0` disables it.
319
+ */
320
+ export declare const MAX_ROUND_TEXT_CHARS_DEFAULT = 32000;
321
+ export declare const MAX_ROUND_TEXT_CHARS_MIN = 2000;
322
+ export declare const MAX_ROUND_TEXT_CHARS_MAX = 2000000;
263
323
  /**
264
324
  * Compaction tuning for providers whose conversation the daemon owns
265
325
  * (openai-compat). These set the provider-level defaults; the per-agent
@@ -273,6 +333,19 @@ export declare const ProviderCompactionConfigSchema: z.ZodObject<{
273
333
  hideSelector: z.ZodOptional<z.ZodBoolean>;
274
334
  }, z.core.$strip>;
275
335
  export type ProviderCompactionConfig = z.infer<typeof ProviderCompactionConfigSchema>;
336
+ /**
337
+ * Action circuit-breaker tuning for daemon-hosted tool loops (openai-compat /
338
+ * Otto Brain). When enabled, an action that fails `threshold` times in a row
339
+ * (same tool name + same arguments) stops being executed for the rest of the
340
+ * round, and the model receives a repair prompt instead of a dead-end error.
341
+ * Omitted or `enabled: false` keeps the historical behavior: every tool call
342
+ * the model emits is executed, failures and all.
343
+ */
344
+ export declare const ProviderActionBreakerConfigSchema: z.ZodObject<{
345
+ enabled: z.ZodOptional<z.ZodBoolean>;
346
+ threshold: z.ZodOptional<z.ZodNumber>;
347
+ }, z.core.$strip>;
348
+ export type ProviderActionBreakerConfig = z.infer<typeof ProviderActionBreakerConfigSchema>;
276
349
  export declare const ProviderOverrideSchema: z.ZodObject<{
277
350
  extends: z.ZodOptional<z.ZodString>;
278
351
  label: z.ZodOptional<z.ZodString>;
@@ -306,13 +379,13 @@ export declare const ProviderOverrideSchema: z.ZodObject<{
306
379
  }, z.core.$strip>>>;
307
380
  disallowedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
308
381
  ottoToolGroups: z.ZodOptional<z.ZodArray<z.ZodEnum<{
309
- artifacts: "artifacts";
310
382
  preview: "preview";
311
383
  browser: "browser";
312
384
  web: "web";
313
385
  agents: "agents";
314
386
  terminals: "terminals";
315
387
  schedules: "schedules";
388
+ artifacts: "artifacts";
316
389
  widgets: "widgets";
317
390
  workspace: "workspace";
318
391
  }>>>;
@@ -345,6 +418,12 @@ export declare const ProviderOverrideSchema: z.ZodObject<{
345
418
  hideSelector: z.ZodOptional<z.ZodBoolean>;
346
419
  }, z.core.$strip>>;
347
420
  maxToolRounds: z.ZodOptional<z.ZodNumber>;
421
+ actionBreaker: z.ZodOptional<z.ZodObject<{
422
+ enabled: z.ZodOptional<z.ZodBoolean>;
423
+ threshold: z.ZodOptional<z.ZodNumber>;
424
+ }, z.core.$strip>>;
425
+ maxRoundTextChars: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodNumber]>>;
426
+ midSessionContextUpdates: z.ZodOptional<z.ZodBoolean>;
348
427
  enabled: z.ZodOptional<z.ZodBoolean>;
349
428
  order: z.ZodOptional<z.ZodNumber>;
350
429
  }, z.core.$strip>;
@@ -381,13 +460,13 @@ export declare const ProviderOverridesSchema: z.ZodRecord<z.ZodString, z.ZodObje
381
460
  }, z.core.$strip>>>;
382
461
  disallowedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
383
462
  ottoToolGroups: z.ZodOptional<z.ZodArray<z.ZodEnum<{
384
- artifacts: "artifacts";
385
463
  preview: "preview";
386
464
  browser: "browser";
387
465
  web: "web";
388
466
  agents: "agents";
389
467
  terminals: "terminals";
390
468
  schedules: "schedules";
469
+ artifacts: "artifacts";
391
470
  widgets: "widgets";
392
471
  workspace: "workspace";
393
472
  }>>>;
@@ -420,6 +499,12 @@ export declare const ProviderOverridesSchema: z.ZodRecord<z.ZodString, z.ZodObje
420
499
  hideSelector: z.ZodOptional<z.ZodBoolean>;
421
500
  }, z.core.$strip>>;
422
501
  maxToolRounds: z.ZodOptional<z.ZodNumber>;
502
+ actionBreaker: z.ZodOptional<z.ZodObject<{
503
+ enabled: z.ZodOptional<z.ZodBoolean>;
504
+ threshold: z.ZodOptional<z.ZodNumber>;
505
+ }, z.core.$strip>>;
506
+ maxRoundTextChars: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodNumber]>>;
507
+ midSessionContextUpdates: z.ZodOptional<z.ZodBoolean>;
423
508
  enabled: z.ZodOptional<z.ZodBoolean>;
424
509
  order: z.ZodOptional<z.ZodNumber>;
425
510
  }, z.core.$strip>>;
@@ -221,6 +221,66 @@ export const COMPACTION_THRESHOLD_PERCENTS = [50, 60, 70, 80, 90];
221
221
  export const MAX_TOOL_ROUNDS_DEFAULT = 50;
222
222
  export const MAX_TOOL_ROUNDS_MIN = 1;
223
223
  export const MAX_TOOL_ROUNDS_MAX = 1000;
224
+ /**
225
+ * Action circuit-breaker for daemon-hosted tool loops (openai-compat /
226
+ * Otto Brain). When enabled, an action (tool name + exact arguments) that
227
+ * fails this many times in a row stops being executed: the rest of the
228
+ * round's identical calls are dropped, and a repair prompt is sent to the
229
+ * model explaining what failed and how to change course. Small local models
230
+ * can degenerate into emitting the same broken action hundreds of times per
231
+ * round (the 2,912-call browser_navigate({}) incident); without the breaker
232
+ * each identical failure is appended to the conversation verbatim until the
233
+ * context window is blown and the turn dies. Defaults are safe for every
234
+ * model; bounds keep the setting a guard rail, not an off switch.
235
+ */
236
+ export const ACTION_BREAKER_DEFAULT_THRESHOLD = 5;
237
+ export const ACTION_BREAKER_MIN_THRESHOLD = 2;
238
+ export const ACTION_BREAKER_MAX_THRESHOLD = 100;
239
+ /**
240
+ * Tool-emission stall guard (daemon-wide, provider-agnostic). A healthy agent
241
+ * turn either acts - it emits a tool call - or it hands back to the human. An
242
+ * assistant message that does neither has no side effect, so a run that only
243
+ * produces those is not working, it is stuck. This is the count of consecutive
244
+ * such messages tolerated before the daemon interrupts the run.
245
+ *
246
+ * Purely structural: it counts stream events, never their text. A tool call
247
+ * resets it to zero, so a long working loop (a 200-turn investigation with a
248
+ * tool call every few turns) can never trip it; so does a real user prompt, so
249
+ * ordinary back-and-forth chat can never trip it either.
250
+ *
251
+ * `0` disables the guard. Bounds keep the setting a guard rail rather than a
252
+ * hair trigger. See agent-stall-guard.ts and the "unbounded tool-call
253
+ * announcement" finding: a local model emitted ~840 consecutive text-only
254
+ * messages over five minutes, re-announcing three tool calls it never sent, and
255
+ * nothing in the runtime noticed.
256
+ */
257
+ export const STALL_GUARD_DEFAULT_THRESHOLD = 15;
258
+ export const STALL_GUARD_MIN_THRESHOLD = 3;
259
+ export const STALL_GUARD_MAX_THRESHOLD = 500;
260
+ /**
261
+ * Per-round assistant-text budget for daemon-hosted tool loops (openai-compat /
262
+ * Otto Brain). The same invariant the stall guard enforces across messages, held
263
+ * *within* one: a model round that has streamed this much prose while emitting
264
+ * no tool call is neither acting nor finishing, so the turn is interrupted.
265
+ *
266
+ * This is the sibling STALL_GUARD_* cannot cover. That counter keys on
267
+ * `messageId` so a message streamed as a burst of deltas counts once - correct,
268
+ * and the reason it reads 1 for a *single* runaway generation. In the
269
+ * "unbounded tool-call announcement" incident the model produced 66,384
270
+ * characters inside one completion over 5m40s, never emitting a stop token and
271
+ * never emitting a tool call; the run ended only because the user typed
272
+ * /compact. Counting characters within the round is what catches that shape.
273
+ *
274
+ * Structural, not textual: it measures how much was produced, never what was
275
+ * said. Reasoning/thinking text is deliberately excluded - high-effort thinking
276
+ * is legitimately long, and the degeneration this bounds is in content.
277
+ *
278
+ * The default is far above any real answer (roughly 8K tokens of prose in one
279
+ * round), so it is a safety valve rather than a style limit. `0` disables it.
280
+ */
281
+ export const MAX_ROUND_TEXT_CHARS_DEFAULT = 32000;
282
+ export const MAX_ROUND_TEXT_CHARS_MIN = 2000;
283
+ export const MAX_ROUND_TEXT_CHARS_MAX = 2000000;
224
284
  /**
225
285
  * Compaction tuning for providers whose conversation the daemon owns
226
286
  * (openai-compat). These set the provider-level defaults; the per-agent
@@ -244,6 +304,28 @@ export const ProviderCompactionConfigSchema = z.object({
244
304
  */
245
305
  hideSelector: z.boolean().optional(),
246
306
  });
307
+ /**
308
+ * Action circuit-breaker tuning for daemon-hosted tool loops (openai-compat /
309
+ * Otto Brain). When enabled, an action that fails `threshold` times in a row
310
+ * (same tool name + same arguments) stops being executed for the rest of the
311
+ * round, and the model receives a repair prompt instead of a dead-end error.
312
+ * Omitted or `enabled: false` keeps the historical behavior: every tool call
313
+ * the model emits is executed, failures and all.
314
+ */
315
+ export const ProviderActionBreakerConfigSchema = z.object({
316
+ /** false (default) disables the breaker; true enables it. */
317
+ enabled: z.boolean().optional(),
318
+ /**
319
+ * Consecutive identical failures before the breaker trips.
320
+ * Default ACTION_BREAKER_DEFAULT_THRESHOLD (5), clamped to [MIN, MAX].
321
+ */
322
+ threshold: z
323
+ .number()
324
+ .int()
325
+ .min(ACTION_BREAKER_MIN_THRESHOLD)
326
+ .max(ACTION_BREAKER_MAX_THRESHOLD)
327
+ .optional(),
328
+ });
247
329
  export const ProviderOverrideSchema = z.object({
248
330
  extends: z.string().optional(),
249
331
  label: z.string().optional(),
@@ -276,6 +358,34 @@ export const ProviderOverrideSchema = z.object({
276
358
  * (openai-compat). Omitted = the built-in default (MAX_TOOL_ROUNDS_DEFAULT).
277
359
  */
278
360
  maxToolRounds: z.number().int().min(MAX_TOOL_ROUNDS_MIN).max(MAX_TOOL_ROUNDS_MAX).optional(),
361
+ /**
362
+ * Action circuit-breaker for daemon-hosted tool loops (openai-compat /
363
+ * Otto Brain). Stops a repeatedly-failing identical action and repairs
364
+ * instead of re-executing it. Omitted = disabled (historical behavior).
365
+ */
366
+ actionBreaker: ProviderActionBreakerConfigSchema.optional(),
367
+ /**
368
+ * Assistant-text budget for one model round (openai-compat / Otto Brain).
369
+ * A round that streams past this many characters without emitting a tool call
370
+ * interrupts the turn. Omitted = MAX_ROUND_TEXT_CHARS_DEFAULT; `0` disables.
371
+ */
372
+ maxRoundTextChars: z
373
+ .union([
374
+ z.literal(0),
375
+ z.number().int().min(MAX_ROUND_TEXT_CHARS_MIN).max(MAX_ROUND_TEXT_CHARS_MAX),
376
+ ])
377
+ .optional(),
378
+ /**
379
+ * Whether the daemon-hosted tool loop (openai-compat / Otto Brain) may add
380
+ * context to a conversation after it has started. Today that is the
381
+ * subdirectory instruction file loaded when the agent first touches a subtree;
382
+ * anything else the loop wants to inject mid-conversation rides under the same
383
+ * switch. Omitted = true (the shipped behavior). Turn it off for a small local
384
+ * context window, where a few thousand tokens arriving unannounced mid-task
385
+ * costs more than the rules are worth. Providers whose conversation Otto does
386
+ * not own (every ACP/CLI provider) are unaffected either way.
387
+ */
388
+ midSessionContextUpdates: z.boolean().optional(),
279
389
  enabled: z.boolean().optional(),
280
390
  order: z.number().optional(),
281
391
  });