@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,395 @@
1
+ import { z } from "zod";
2
+ import { AgentPersonalitySchema, AgentTeamSchema } from "./personality-schemas.js";
3
+ import { STALL_GUARD_DEFAULT_THRESHOLD, STALL_GUARD_MAX_THRESHOLD } from "./provider-config.js";
4
+ /**
5
+ * Otto's mutable daemon-config fragments: the per-feature config sections Paseo's MutableDaemonConfigSchema composes. Each Otto feature registers one field there; the section schema lives here.
6
+ */
7
+ // Daemon-wide agent behavior toggles. Each maps to a Claude-tier capability;
8
+ // providers that can't honor a setting silently ignore it (WP-E wires the
9
+ // reads). All default true so a fresh host behaves exactly like today.
10
+ export const MutableAgentBehaviorsConfigSchema = z
11
+ .object({
12
+ // Native next-prompt predictions (Claude prompt_suggestion stream events).
13
+ promptSuggestions: z.boolean().default(true),
14
+ // Agent-authored progress summaries emitted during a turn.
15
+ agentProgressSummaries: z.boolean().default(true),
16
+ // Default value of an agent's notifyOnFinish when the spawn path leaves it
17
+ // unspecified (the current implicit default).
18
+ notifyOnFinishDefault: z.boolean().default(true),
19
+ // Provider-agnostic task-list reminders. Otto renders every provider's
20
+ // native todo list into one timeline UI; when an agent leaves that list with
21
+ // unfinished items, these keep it from going stale (the user shouldn't have
22
+ // to dismiss a half-checked list themselves).
23
+ // Passive: while a stale list is open, attach a reminder to the agent's next
24
+ // turn (mirrors the harness's own "your todo list looks stale" nudge).
25
+ todoNudge: z.boolean().default(true),
26
+ // Active: when the agent goes idle with a stale list, inject a one-shot
27
+ // reconcile pass so it marks done what's done (or states what's genuinely
28
+ // left) before the turn truly ends.
29
+ todoReconcileOnIdle: z.boolean().default(true),
30
+ // Provider-agnostic tool-emission stall guard: consecutive assistant
31
+ // messages that neither call a tool nor hand back to the user before the
32
+ // daemon interrupts the run. A tool call or a real user prompt resets the
33
+ // count, so working loops and ordinary chat never trip it. 0 disables.
34
+ // See STALL_GUARD_* in provider-config.ts and agent-stall-guard.ts.
35
+ stallGuardThreshold: z
36
+ .number()
37
+ .int()
38
+ .min(0)
39
+ .max(STALL_GUARD_MAX_THRESHOLD)
40
+ .default(STALL_GUARD_DEFAULT_THRESHOLD),
41
+ })
42
+ .passthrough();
43
+ /**
44
+ * Language-server code intelligence, host-scoped because the servers are processes
45
+ * on the daemon's machine - they follow the host, not the client.
46
+ *
47
+ * `enabled` defaults **on** and that is safe: nothing spawns until a
48
+ * code-intelligence action needs a language in a workspace, so an unused language
49
+ * costs nothing. What the switch guarantees is that off means off - no server
50
+ * spawns for any workspace, and the ctags index still serves the outline and the
51
+ * fuzzy finder.
52
+ *
53
+ * `languages` keys are registry row ids (`typescript`, `python`, `csharp`, …). An
54
+ * absent key means "use the row's own default", so a new row ships with its
55
+ * intended default rather than reading as disabled.
56
+ */
57
+ export const MutableLspConfigSchema = z
58
+ .object({
59
+ enabled: z.boolean().default(true),
60
+ languages: z.record(z.string(), z.boolean()).default({}),
61
+ /**
62
+ * How much of a .NET workspace the C# server loads.
63
+ *
64
+ * `"solution"` names the workspace root's single solution with `csharp-ls -s`. `"allProjects"`
65
+ * passes nothing, leaving csharp-ls to glob every `.csproj` under the root - complete coverage,
66
+ * but it loads them one at a time (measured at ~4s each, so a 200-project repo is minutes, not
67
+ * seconds). Absent means `"solution"`.
68
+ *
69
+ * Deliberately carries NO `.default()`. The patch schema is `MutableLspConfigSchema.partial()`,
70
+ * and Zod keeps defaults through `.partial()`, so a default here would be injected into every
71
+ * unrelated `lsp` patch and deep-merge would silently reset the user's choice.
72
+ */
73
+ csharpProjectScope: z.enum(["solution", "allProjects"]).optional(),
74
+ /** Hard LRU cap on simultaneously running servers, across all workspaces. */
75
+ maxRunningServers: z.number().int().positive().default(6),
76
+ idleMinutes: z.number().int().positive().default(10),
77
+ /** Shorter allowance for workspaces the user is not currently looking at. */
78
+ backgroundIdleMinutes: z.number().int().positive().default(2),
79
+ })
80
+ .passthrough();
81
+ /**
82
+ * "Microsoft .NET Solution Management" - the Solution view's own switch.
83
+ *
84
+ * **A sibling of `lsp`, not a member of it.** Turning C# code intelligence off does not turn
85
+ * this off and vice versa: they are independent capabilities that happen to share a language,
86
+ * and nesting this inside the LSP settings object would imply exactly the coupling that
87
+ * decision rejects. (It would also be wrong on the facts - LSP has no project-structure
88
+ * request, so nothing here rides on a language server.)
89
+ *
90
+ * Defaults **off**: the feature spawns a process and evaluates MSBuild. Disabled is genuinely
91
+ * off, not merely hidden - no discovery walk, no `.sln` read, no `.csproj` parse, no sidecar,
92
+ * no cache, no watcher, and no view switcher. The daemon reads this before scheduling any work,
93
+ * so a disabled feature costs exactly one boolean check.
94
+ */
95
+ export const MutableDotnetSolutionConfigSchema = z
96
+ .object({
97
+ enabled: z.boolean().default(false),
98
+ /** Hard cap on simultaneously running sidecars, across all workspaces. */
99
+ maxRunningProbes: z.number().int().positive().default(2),
100
+ idleMinutes: z.number().int().positive().default(10),
101
+ })
102
+ .passthrough();
103
+ // Host-level git hosting credentials, one set per provider. A workspace's
104
+ // provider is derived from its git remote (bitbucket.org → Bitbucket,
105
+ // github.com → GitHub), so credentials are configured once per host, not per
106
+ // project. Keys persist to $OTTO_HOME/config.json and are echoed in
107
+ // get_daemon_config_response the same way provider connection keys are.
108
+ export const MutableGitHostingBitbucketCloudConfigSchema = z
109
+ .object({
110
+ // Atlassian account email + API token, sent as HTTP Basic auth.
111
+ email: z.string().optional(),
112
+ apiToken: z.string().optional(),
113
+ })
114
+ .passthrough();
115
+ // The one Atlassian account credential, shared by every Atlassian surface:
116
+ // Bitbucket Cloud for git hosting and Jira for the Kanban board. Both are HTTP
117
+ // Basic (account email + API token), so there is one credential to author and
118
+ // one place it can go stale. `atlassian` supersedes `bitbucketCloud`; the
119
+ // daemon reads this first and falls back to the older key.
120
+ // COMPAT(atlassianCredential): added in v0.8.11, drop the bitbucketCloud
121
+ // fallback after 2027-02-28.
122
+ export const MutableGitHostingAtlassianConfigSchema = z
123
+ .object({
124
+ email: z.string().optional(),
125
+ apiToken: z.string().optional(),
126
+ // Jira Cloud site base URL, e.g. https://acme.atlassian.net. Not a secret.
127
+ // Required for Jira: Basic-auth Jira Cloud calls are site-addressed, unlike
128
+ // the OAuth-only api.atlassian.com/ex/jira gateway.
129
+ jiraSiteUrl: z.string().optional(),
130
+ })
131
+ .passthrough();
132
+ export const MutableGitHostingProvidersConfigSchema = z
133
+ .object({
134
+ bitbucketCloud: MutableGitHostingBitbucketCloudConfigSchema.optional(),
135
+ atlassian: MutableGitHostingAtlassianConfigSchema.optional(),
136
+ })
137
+ .passthrough();
138
+ export const MutableGitHostingConfigSchema = z
139
+ .object({
140
+ providers: MutableGitHostingProvidersConfigSchema.optional(),
141
+ })
142
+ .passthrough();
143
+ // RETIRED: the Kanban board surface no longer has credentials of its own. It
144
+ // reuses the host's existing authentication - GitHub through the `gh` CLI, Jira
145
+ // through the shared Atlassian credential above. Nothing reads these fields any
146
+ // more and the settings UI never wrote them, but the schema stays so existing
147
+ // $OTTO_HOME/config.json files and older clients keep parsing (removed fields
148
+ // stay accepted; we only stop sending them). They remain masked via
149
+ // SECRET_WIRE_PATHS so a hand-edited token is never echoed back in the clear.
150
+ // COMPAT(kanbanProviderTokens): retired in v0.8.11, delete after 2027-02-28.
151
+ export const MutableKanbanConfigSchema = z
152
+ .object({
153
+ providers: z
154
+ .object({
155
+ github: z
156
+ .object({
157
+ // Fine-grained or classic PAT with `projects: read`. Empty string
158
+ // = not configured.
159
+ token: z.string().optional(),
160
+ })
161
+ .passthrough(),
162
+ jira: z
163
+ .object({
164
+ // Jira Cloud API token (site-wide token or PAT). Empty string =
165
+ // not configured.
166
+ token: z.string().optional(),
167
+ })
168
+ .passthrough(),
169
+ })
170
+ .passthrough()
171
+ .optional(),
172
+ })
173
+ .passthrough();
174
+ export const MutableAgentPersonalitiesConfigSchema = z
175
+ .object({
176
+ personalities: z.array(AgentPersonalitySchema).default([]),
177
+ })
178
+ .passthrough();
179
+ // Patch shape declared explicitly rather than via .partial(): partial() keeps
180
+ // the personalities .default([]), so a patch touching the section without an
181
+ // explicit personalities array would have an empty array injected and
182
+ // deep-merge would wipe the stored roster.
183
+ export const MutableAgentPersonalitiesConfigPatchSchema = z
184
+ .object({
185
+ personalities: z.array(AgentPersonalitySchema).optional(),
186
+ })
187
+ .passthrough();
188
+ export const MutableAgentTeamsConfigSchema = z
189
+ .object({
190
+ teams: z.array(AgentTeamSchema).default([]),
191
+ // The host's active team id; null/absent = no team active (exactly legacy
192
+ // behavior). Host-scoped daemon config rather than device-local: the team
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
195
+ // hot-reloads the switch to every connected client.
196
+ activeTeamId: z.string().nullable().optional(),
197
+ })
198
+ .passthrough();
199
+ // Patch shape declared explicitly rather than via .partial(): partial() keeps
200
+ // the teams .default([]), so a patch that only touches activeTeamId would have
201
+ // an empty array injected and deep-merge would wipe the stored teams.
202
+ export const MutableAgentTeamsConfigPatchSchema = z
203
+ .object({
204
+ teams: z.array(AgentTeamSchema).optional(),
205
+ activeTeamId: z.string().nullable().optional(),
206
+ })
207
+ .passthrough();
208
+ // The editable projection of @otto-code/brain's own config (the brain's
209
+ // config.json stays the source of truth on disk; the daemon writes changes
210
+ // through). Every field is defaulted so a new client parsing an old daemon's
211
+ // config sees a well-formed, OFF section.
212
+ export const MutableBrainTlsConfigSchema = z
213
+ .object({
214
+ mode: z.enum(["off", "files", "self-signed", "tailscale"]).default("off"),
215
+ certFile: z.string().nullable().default(null),
216
+ keyFile: z.string().nullable().default(null),
217
+ hostname: z.string().nullable().default(null),
218
+ certDir: z.string().nullable().default(null),
219
+ renewBeforeDays: z.number().int().min(1).default(21),
220
+ })
221
+ .passthrough();
222
+ // Where a remote brain lives, when brain.mode is "remote". Every field is
223
+ // defaulted so an old daemon's config parses as a well-formed, empty target.
224
+ export const MutableBrainRemoteConfigSchema = z
225
+ .object({
226
+ host: z.string().default(""),
227
+ port: z.number().int().default(1234),
228
+ secure: z.boolean().default(false),
229
+ // Secret: masked with DAEMON_CONFIG_SECRET_SENTINEL on the way out.
230
+ authToken: z.string().nullable().default(null),
231
+ // SHA-256 fingerprint of the remote brain's TLS certificate (openssl's
232
+ // "AB:CD:..." form; colons optional). When set, the daemon pins HTTPS
233
+ // connections to exactly this certificate instead of the system trust
234
+ // store - required for a brain serving tls.mode=self-signed. When null,
235
+ // the certificate must validate against the system trust store.
236
+ certFingerprint: z.string().nullable().default(null),
237
+ })
238
+ .passthrough();
239
+ export const MutableBrainConfigSchema = z
240
+ .object({
241
+ enabled: z.boolean().default(false),
242
+ autoStart: z.boolean().default(false),
243
+ // "local": the daemon spawns and supervises the brain on this host.
244
+ // "remote": the daemon connects to a brain running on another Otto host
245
+ // (read-only: status/evals/config, no lifecycle). Gated by features.brainRemote.
246
+ mode: z.enum(["local", "remote"]).default("local"),
247
+ remote: MutableBrainRemoteConfigSchema.default({
248
+ host: "",
249
+ port: 1234,
250
+ secure: false,
251
+ authToken: null,
252
+ certFingerprint: null,
253
+ }),
254
+ listen: z
255
+ .object({
256
+ host: z.string().default("127.0.0.1"),
257
+ port: z.number().int().default(1234),
258
+ })
259
+ .passthrough()
260
+ .default({ host: "127.0.0.1", port: 1234 }),
261
+ defaultModel: z.string().nullable().default(null),
262
+ runtime: z
263
+ .object({
264
+ source: z.enum(["auto", "managed", "lmstudio"]).default("auto"),
265
+ path: z.string().nullable().default(null),
266
+ logVerbosity: z.number().int().min(0).max(5).default(3),
267
+ })
268
+ .default({ source: "auto", path: null, logVerbosity: 3 }),
269
+ // Pin the host to one model: serve only the default/resident model and
270
+ // refuse completion requests that ask for a different one.
271
+ lockModel: z.boolean().default(false),
272
+ // Sharing gates (off by default). allowRemoteConfig: key holders may CHANGE
273
+ // config over the network (POST /__host/config), not just use it.
274
+ // allowInsecureBind: permit a non-loopback bind with no token (open share).
275
+ allowRemoteConfig: z.boolean().default(false),
276
+ allowInsecureBind: z.boolean().default(false),
277
+ authMode: z.enum(["none", "token"]).default("none"),
278
+ // Secret: masked with DAEMON_CONFIG_SECRET_SENTINEL on the way out; an
279
+ // unchanged sentinel is stripped from inbound patches.
280
+ authToken: z.string().nullable().default(null),
281
+ tls: MutableBrainTlsConfigSchema.default({
282
+ mode: "off",
283
+ certFile: null,
284
+ keyFile: null,
285
+ hostname: null,
286
+ certDir: null,
287
+ renewBeforeDays: 21,
288
+ }),
289
+ })
290
+ .passthrough();
291
+ // The brain PATCH schema - deliberately NOT `MutableBrainConfigSchema.partial()`.
292
+ // Every field of the full schema carries a `.default()` (so an old daemon's
293
+ // half-written config still parses as a well-formed OFF section), and Zod keeps
294
+ // those defaults through `.partial()`: `MutableBrainConfigSchema.partial().parse(
295
+ // { allowRemoteConfig: true })` expands to the FULL object with every other field
296
+ // defaulted. The daemon deep-merges the parsed patch over the stored config, so a
297
+ // single-field patch would silently reset the entire brain block to defaults -
298
+ // turning sharing off (host back to loopback), wiping the auth token, and
299
+ // disabling the server. Mirroring the shape WITHOUT defaults keeps an omitted
300
+ // field omitted, so the deep-merge preserves it. Every level is deep-partial so a
301
+ // nested patch (e.g. just `listen.host`) preserves its siblings too. Keep the
302
+ // field set in sync with MutableBrainConfigSchema; `.passthrough()` carries any
303
+ // field a newer daemon adds through untouched in the meantime.
304
+ export const MutableBrainTlsPatchSchema = z
305
+ .object({
306
+ mode: z.enum(["off", "files", "self-signed", "tailscale"]),
307
+ certFile: z.string().nullable(),
308
+ keyFile: z.string().nullable(),
309
+ hostname: z.string().nullable(),
310
+ certDir: z.string().nullable(),
311
+ renewBeforeDays: z.number().int().min(1),
312
+ })
313
+ .partial()
314
+ .passthrough();
315
+ export const MutableBrainRemotePatchSchema = z
316
+ .object({
317
+ host: z.string(),
318
+ port: z.number().int(),
319
+ secure: z.boolean(),
320
+ authToken: z.string().nullable(),
321
+ certFingerprint: z.string().nullable(),
322
+ })
323
+ .partial()
324
+ .passthrough();
325
+ export const MutableBrainListenPatchSchema = z
326
+ .object({
327
+ host: z.string(),
328
+ port: z.number().int(),
329
+ })
330
+ .partial()
331
+ .passthrough();
332
+ export const MutableBrainConfigPatchSchema = z
333
+ .object({
334
+ enabled: z.boolean(),
335
+ autoStart: z.boolean(),
336
+ mode: z.enum(["local", "remote"]),
337
+ remote: MutableBrainRemotePatchSchema,
338
+ listen: MutableBrainListenPatchSchema,
339
+ defaultModel: z.string().nullable(),
340
+ runtime: z
341
+ .object({
342
+ source: z.enum(["auto", "managed", "lmstudio"]),
343
+ path: z.string().nullable(),
344
+ logVerbosity: z.number().int().min(0).max(5),
345
+ })
346
+ .partial(),
347
+ lockModel: z.boolean(),
348
+ allowRemoteConfig: z.boolean(),
349
+ allowInsecureBind: z.boolean(),
350
+ authMode: z.enum(["none", "token"]),
351
+ authToken: z.string().nullable(),
352
+ tls: MutableBrainTlsPatchSchema,
353
+ })
354
+ .partial()
355
+ .passthrough();
356
+ export const DEFAULT_MUTABLE_BRAIN_CONFIG = {
357
+ enabled: false,
358
+ autoStart: false,
359
+ mode: "local",
360
+ remote: { host: "", port: 1234, secure: false, authToken: null, certFingerprint: null },
361
+ listen: { host: "127.0.0.1", port: 1234 },
362
+ defaultModel: null,
363
+ runtime: { source: "auto", path: null, logVerbosity: 3 },
364
+ lockModel: false,
365
+ allowRemoteConfig: false,
366
+ allowInsecureBind: false,
367
+ authMode: "none",
368
+ authToken: null,
369
+ tls: {
370
+ mode: "off",
371
+ certFile: null,
372
+ keyFile: null,
373
+ hostname: null,
374
+ certDir: null,
375
+ renewBeforeDays: 21,
376
+ },
377
+ };
378
+ export const GIT_FETCH_INTERVAL_SECONDS = [60, 180, 300, 600, 900, 1800, 3600];
379
+ export const MutableGitFetchConfigSchema = z.object({
380
+ enabled: z.boolean(),
381
+ intervalSeconds: z.union([
382
+ z.literal(60),
383
+ z.literal(180),
384
+ z.literal(300),
385
+ z.literal(600),
386
+ z.literal(900),
387
+ z.literal(1800),
388
+ z.literal(3600),
389
+ ]),
390
+ });
391
+ export const DEFAULT_MUTABLE_GIT_FETCH_CONFIG = {
392
+ enabled: true,
393
+ intervalSeconds: 180,
394
+ };
395
+ //# sourceMappingURL=daemon-config.js.map