@gtc6244/immediacy-daemon 0.1.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 (133) hide show
  1. package/dist/src/actions.d.ts +22 -0
  2. package/dist/src/actions.js +44 -0
  3. package/dist/src/actions.js.map +1 -0
  4. package/dist/src/agent/agent.d.ts +89 -0
  5. package/dist/src/agent/agent.js +22 -0
  6. package/dist/src/agent/agent.js.map +1 -0
  7. package/dist/src/agent/claude.d.ts +70 -0
  8. package/dist/src/agent/claude.js +404 -0
  9. package/dist/src/agent/claude.js.map +1 -0
  10. package/dist/src/agent/codex.d.ts +44 -0
  11. package/dist/src/agent/codex.js +303 -0
  12. package/dist/src/agent/codex.js.map +1 -0
  13. package/dist/src/agent/mock.d.ts +31 -0
  14. package/dist/src/agent/mock.js +155 -0
  15. package/dist/src/agent/mock.js.map +1 -0
  16. package/dist/src/agent/model-error.d.ts +8 -0
  17. package/dist/src/agent/model-error.js +25 -0
  18. package/dist/src/agent/model-error.js.map +1 -0
  19. package/dist/src/agent/openrouter.d.ts +38 -0
  20. package/dist/src/agent/openrouter.js +60 -0
  21. package/dist/src/agent/openrouter.js.map +1 -0
  22. package/dist/src/agent/workspace-scope.d.ts +25 -0
  23. package/dist/src/agent/workspace-scope.js +71 -0
  24. package/dist/src/agent/workspace-scope.js.map +1 -0
  25. package/dist/src/allowlist.d.ts +56 -0
  26. package/dist/src/allowlist.js +141 -0
  27. package/dist/src/allowlist.js.map +1 -0
  28. package/dist/src/chatlog.d.ts +50 -0
  29. package/dist/src/chatlog.js +179 -0
  30. package/dist/src/chatlog.js.map +1 -0
  31. package/dist/src/cli/paths.d.ts +40 -0
  32. package/dist/src/cli/paths.js +76 -0
  33. package/dist/src/cli/paths.js.map +1 -0
  34. package/dist/src/cli/service.d.ts +20 -0
  35. package/dist/src/cli/service.js +245 -0
  36. package/dist/src/cli/service.js.map +1 -0
  37. package/dist/src/cli.d.ts +2 -0
  38. package/dist/src/cli.js +227 -0
  39. package/dist/src/cli.js.map +1 -0
  40. package/dist/src/config.d.ts +33 -0
  41. package/dist/src/config.js +122 -0
  42. package/dist/src/config.js.map +1 -0
  43. package/dist/src/credentials.d.ts +38 -0
  44. package/dist/src/credentials.js +120 -0
  45. package/dist/src/credentials.js.map +1 -0
  46. package/dist/src/crypto.d.ts +49 -0
  47. package/dist/src/crypto.js +202 -0
  48. package/dist/src/crypto.js.map +1 -0
  49. package/dist/src/daemon-identity.d.ts +31 -0
  50. package/dist/src/daemon-identity.js +97 -0
  51. package/dist/src/daemon-identity.js.map +1 -0
  52. package/dist/src/envelope.d.ts +14 -0
  53. package/dist/src/envelope.js +28 -0
  54. package/dist/src/envelope.js.map +1 -0
  55. package/dist/src/events.d.ts +40 -0
  56. package/dist/src/events.js +24 -0
  57. package/dist/src/events.js.map +1 -0
  58. package/dist/src/forward.d.ts +51 -0
  59. package/dist/src/forward.js +170 -0
  60. package/dist/src/forward.js.map +1 -0
  61. package/dist/src/fs-scope.d.ts +17 -0
  62. package/dist/src/fs-scope.js +47 -0
  63. package/dist/src/fs-scope.js.map +1 -0
  64. package/dist/src/github.d.ts +70 -0
  65. package/dist/src/github.js +178 -0
  66. package/dist/src/github.js.map +1 -0
  67. package/dist/src/gitops.d.ts +113 -0
  68. package/dist/src/gitops.js +557 -0
  69. package/dist/src/gitops.js.map +1 -0
  70. package/dist/src/hub.d.ts +50 -0
  71. package/dist/src/hub.js +92 -0
  72. package/dist/src/hub.js.map +1 -0
  73. package/dist/src/index.d.ts +22 -0
  74. package/dist/src/index.js +56 -0
  75. package/dist/src/index.js.map +1 -0
  76. package/dist/src/jobs/scheduler.d.ts +94 -0
  77. package/dist/src/jobs/scheduler.js +373 -0
  78. package/dist/src/jobs/scheduler.js.map +1 -0
  79. package/dist/src/jobs/store.d.ts +33 -0
  80. package/dist/src/jobs/store.js +111 -0
  81. package/dist/src/jobs/store.js.map +1 -0
  82. package/dist/src/log.d.ts +61 -0
  83. package/dist/src/log.js +166 -0
  84. package/dist/src/log.js.map +1 -0
  85. package/dist/src/migrate-state-dir.d.ts +1 -0
  86. package/dist/src/migrate-state-dir.js +47 -0
  87. package/dist/src/migrate-state-dir.js.map +1 -0
  88. package/dist/src/models.d.ts +36 -0
  89. package/dist/src/models.js +71 -0
  90. package/dist/src/models.js.map +1 -0
  91. package/dist/src/permission-gate.d.ts +48 -0
  92. package/dist/src/permission-gate.js +93 -0
  93. package/dist/src/permission-gate.js.map +1 -0
  94. package/dist/src/permission-scope.d.ts +77 -0
  95. package/dist/src/permission-scope.js +244 -0
  96. package/dist/src/permission-scope.js.map +1 -0
  97. package/dist/src/ports.d.ts +71 -0
  98. package/dist/src/ports.js +216 -0
  99. package/dist/src/ports.js.map +1 -0
  100. package/dist/src/projects.d.ts +83 -0
  101. package/dist/src/projects.js +205 -0
  102. package/dist/src/projects.js.map +1 -0
  103. package/dist/src/protocol.d.ts +1226 -0
  104. package/dist/src/protocol.js +16 -0
  105. package/dist/src/protocol.js.map +1 -0
  106. package/dist/src/repo-sync.d.ts +49 -0
  107. package/dist/src/repo-sync.js +83 -0
  108. package/dist/src/repo-sync.js.map +1 -0
  109. package/dist/src/server.d.ts +33 -0
  110. package/dist/src/server.js +973 -0
  111. package/dist/src/server.js.map +1 -0
  112. package/dist/src/terminal.d.ts +32 -0
  113. package/dist/src/terminal.js +92 -0
  114. package/dist/src/terminal.js.map +1 -0
  115. package/dist/src/titler.d.ts +15 -0
  116. package/dist/src/titler.js +72 -0
  117. package/dist/src/titler.js.map +1 -0
  118. package/dist/src/types.d.ts +99 -0
  119. package/dist/src/types.js +3 -0
  120. package/dist/src/types.js.map +1 -0
  121. package/dist/src/usage-limit.d.ts +49 -0
  122. package/dist/src/usage-limit.js +83 -0
  123. package/dist/src/usage-limit.js.map +1 -0
  124. package/dist/src/usage.d.ts +23 -0
  125. package/dist/src/usage.js +63 -0
  126. package/dist/src/usage.js.map +1 -0
  127. package/dist/src/workspace.d.ts +303 -0
  128. package/dist/src/workspace.js +1086 -0
  129. package/dist/src/workspace.js.map +1 -0
  130. package/dist/src/worktree.d.ts +133 -0
  131. package/dist/src/worktree.js +561 -0
  132. package/dist/src/worktree.js.map +1 -0
  133. package/package.json +67 -0
@@ -0,0 +1,1226 @@
1
+ /**
2
+ * Wire protocol — the contract between the Mac daemon and the Flutter client.
3
+ *
4
+ * This file is the SOURCE OF TRUTH. The Dart mirror lives at
5
+ * app/lib/protocol.dart and must be kept in sync by hand (the shapes are small
6
+ * and stable enough that codegen is not worth the toolchain weight yet).
7
+ *
8
+ * Transport-agnostic by design: nothing here may assume Cloudflare, WSS, or any
9
+ * particular framing. A single multiplexed connection carries every message.
10
+ *
11
+ * The envelope fields (v, id, ts, ws, type) stay in clear for routing. Only
12
+ * `payload` is encrypted in the E2E layer (slice 5); until then it travels as
13
+ * plain JSON.
14
+ */
15
+ export declare const PROTOCOL_VERSION: 1;
16
+ /** Workspace lifecycle status, surfaced as a badge in the client. */
17
+ export type WorkspaceStatus = "idle" | "running" | "awaiting_permission" | "done" | "error";
18
+ /** Generic envelope. `T` is the message `type`; `P` its payload shape. */
19
+ export interface Envelope<T extends MessageType = MessageType> {
20
+ v: typeof PROTOCOL_VERSION;
21
+ id: string;
22
+ ts: number;
23
+ ws: string | null;
24
+ type: T;
25
+ payload: PayloadFor<T>;
26
+ }
27
+ /** Client → daemon. */
28
+ export type ClientMessageType = "project.list" | "project.create" | "project.delete" | "project.reorder" | "project.branches" | "project.pulls" | "project.issues" | "issue.create" | "workspace.list" | "workspace.create" | "workspace.delete" | "workspace.archive" | "workspace.action" | "delete.batch" | "prompt.send" | "agent.stop" | "permission.respond" | "question.respond" | "allowlist.list" | "allowlist.revoke" | "allowlist.clear" | "terminal.input" | "terminal.resize" | "terminal.snapshot" | "forward.list" | "forward.open" | "forward.data" | "forward.close" | "git.status" | "git.commit" | "git.push" | "git.resolve" | "git.merge" | "pr.create" | "pr.merge" | "workspace.open" | "diff.get" | "tree.get" | "file.get" | "file.set" | "chat.history" | "github.status" | "github.repos" | "models.list" | "job.list" | "job.create" | "job.update" | "job.delete" | "job.run" | "job.runs" | "settings.get" | "settings.set" | "config.set" | "log.tail" | "hello" | "pair.info" | "daemon.get" | "daemon.rename" | "ping" | "echo";
29
+ /** Daemon → client. */
30
+ export type DaemonMessageType = "project.list.result" | "project.branches.result" | "project.pulls.result" | "project.issues.result" | "workspace.list.result" | "workspace.created" | "workspace.renamed" | "delete.batch.result" | "event.assistant_message" | "event.tool_use" | "event.permission_request" | "event.permission_resolved" | "event.ask_question" | "event.session" | "event.agent_done" | "event.session_limit" | "terminal.data" | "terminal.snapshot.result" | "forward.available" | "git.status.result" | "git.result" | "diff.result" | "tree.result" | "file.result" | "file.saved" | "chat.history.result" | "github.status.result" | "github.repos.result" | "models.list.result" | "job.list.result" | "job.runs.result" | "event.job_run" | "settings.result" | "allowlist.result" | "log.tail.result" | "log.event" | "hello.ok" | "pair.info.result" | "daemon.info" | "pong" | "error" | "echo.result";
31
+ export type MessageType = ClientMessageType | DaemonMessageType;
32
+ export interface ProjectListPayload {
33
+ }
34
+ /**
35
+ * Register a project (one repo the user can spin worktrees off). Source is one
36
+ * of: `blank` — a brand-new empty repo the daemon `git init`s (no existing repo
37
+ * needed, local or remote); a GitHub repo (cloned into the daemon's managed
38
+ * root); or an existing local repo path. Where blank/GitHub projects live on
39
+ * disk is inherent — the client never chooses. `name` defaults to the repo
40
+ * name. GitHub/local are idempotent (by full name / resolved path); blank
41
+ * always creates.
42
+ */
43
+ export interface ProjectCreatePayload {
44
+ /** Create a brand-new empty repo via `git init` — no existing repo required. */
45
+ blank?: boolean;
46
+ repo?: string;
47
+ github?: {
48
+ fullName: string;
49
+ defaultBranch?: string;
50
+ };
51
+ name?: string;
52
+ }
53
+ /** Remove a project registration. Rejected while it still has worktrees. */
54
+ export interface ProjectDeletePayload {
55
+ id: string;
56
+ }
57
+ /**
58
+ * Reorder the project list. `ids` is the full set of project ids in the desired
59
+ * display order. Ids the daemon doesn't recognise are ignored; any known project
60
+ * missing from `ids` (e.g. a concurrent create the client hadn't seen yet) keeps
61
+ * its prior relative position at the end.
62
+ */
63
+ export interface ProjectReorderPayload {
64
+ ids: string[];
65
+ }
66
+ export interface WorkspaceListPayload {
67
+ }
68
+ /**
69
+ * Create a workspace (git worktree) under a project. Preferred form passes
70
+ * `projectId` (from project.list); the repo/branch are taken from the project.
71
+ *
72
+ * The legacy form — a bare `repo` path or `github` source with no `projectId` —
73
+ * still works: the daemon find-or-creates the matching project first, then
74
+ * forks the worktree off it.
75
+ */
76
+ export interface WorkspaceCreatePayload {
77
+ /**
78
+ * Create a repo-less free-form chat instead of a git worktree. When `"chat"`,
79
+ * every git/project field below is ignored and none is required.
80
+ */
81
+ kind?: "worktree" | "chat";
82
+ /** Project to fork the worktree from (from project.list.result). */
83
+ projectId?: string;
84
+ repo?: string;
85
+ github?: {
86
+ fullName: string;
87
+ defaultBranch?: string;
88
+ };
89
+ name?: string;
90
+ branchBase?: string;
91
+ /**
92
+ * Check out this *existing* branch instead of forking a fresh one — the
93
+ * "create worktree from an existing branch" flow. A local branch is checked
94
+ * out directly; otherwise a local tracking branch is created off
95
+ * `origin/<sourceBranch>`. The diff/PR base stays the project's default
96
+ * branch, so an existing feature branch still diffs against main.
97
+ */
98
+ sourceBranch?: string;
99
+ /**
100
+ * Check out an existing open PR's head branch instead of forking a fresh one —
101
+ * the "create worktree from an existing open PR" flow (the successor to
102
+ * `sourceBranch`). The daemon resolves the PR's head ref and fetches
103
+ * `refs/pull/<n>/head` so fork PRs work too. Mutually exclusive with
104
+ * `sourceBranch`; the diff/PR base stays the project's default branch.
105
+ */
106
+ sourcePr?: number;
107
+ /**
108
+ * Seed the new workspace's agent with this first prompt right after the
109
+ * worktree is created — powers "create worktree from an issue", where the
110
+ * (user-confirmed) issue becomes the task. Omitted → the workspace starts idle.
111
+ */
112
+ initialPrompt?: string;
113
+ /** Model id from the catalog (see models.list); omitted → daemon default. */
114
+ model?: string;
115
+ }
116
+ /** Request the branches a new worktree can be based on, for one project. */
117
+ export interface ProjectBranchesPayload {
118
+ projectId: string;
119
+ }
120
+ /** Request the open pull requests a new worktree can be based on. */
121
+ export interface ProjectPullsPayload {
122
+ projectId: string;
123
+ }
124
+ /** Request the open issues a new worktree's task can be seeded from. */
125
+ export interface ProjectIssuesPayload {
126
+ projectId: string;
127
+ }
128
+ /**
129
+ * Open a GitHub issue from a worktree's output — the mirror of `pr.create`. The
130
+ * agent's produced plan/document is the issue `body`. Outcome rides the shared
131
+ * `git.result` channel with `op: "issue"`.
132
+ */
133
+ export interface IssueCreatePayload {
134
+ ws: string;
135
+ title: string;
136
+ body?: string;
137
+ }
138
+ export interface ModelsListPayload {
139
+ }
140
+ export interface GithubStatusPayload {
141
+ }
142
+ export interface GithubReposPayload {
143
+ /** Optional cap on how many repos to return (most-recently-pushed first). */
144
+ limit?: number;
145
+ }
146
+ export interface WorkspaceDeletePayload {
147
+ ws: string;
148
+ }
149
+ export interface WorkspaceArchivePayload {
150
+ ws: string;
151
+ }
152
+ /**
153
+ * A prompt-backed agent action. Each kind maps to a built-in base instruction
154
+ * plus the matching `prompts.*` custom text (see AppConfig): `code_review` →
155
+ * `codeReview`, `create_pr` → `createPr`, `fix_errors` → `fixErrors`,
156
+ * `resolve_merge_conflicts` → `resolveMergeConflicts`, `rename_branch` →
157
+ * `renameBranch`.
158
+ */
159
+ export type WorkspaceActionKind = "code_review" | "create_pr" | "fix_errors" | "resolve_merge_conflicts" | "rename_branch";
160
+ export interface WorkspaceActionPayload {
161
+ ws: string;
162
+ kind: WorkspaceActionKind;
163
+ }
164
+ /**
165
+ * Mass-delete request. `workspaces` are worktree ids to remove; `projects` are
166
+ * project ids to remove *including* their remaining worktrees. The daemon
167
+ * removes worktrees first (a project can't be deleted while it still has any),
168
+ * skips ids it doesn't know, and continues past per-item failures — a dirty
169
+ * worktree that blocks removal is reported in the result, not fatal. Both
170
+ * arrays are optional; at least one should be non-empty.
171
+ */
172
+ export interface DeleteBatchPayload {
173
+ workspaces?: string[];
174
+ projects?: string[];
175
+ }
176
+ /**
177
+ * A file attached to a prompt — an image or a document the user picked in the
178
+ * composer. Carried inline (base64) in the same `prompt.send` frame as the text,
179
+ * over the one multiplexed connection; the daemon turns each into a Claude
180
+ * content block (image / PDF document / inlined text) for the model to read.
181
+ *
182
+ * Keep these modest: the daemon caps per-file and per-message size (see
183
+ * server.ts) so a stray large file can't stall the encrypted channel.
184
+ */
185
+ export interface PromptAttachment {
186
+ /** Original filename, e.g. "screenshot.png" — shown in the transcript. */
187
+ name: string;
188
+ /** MIME type, e.g. "image/png", "application/pdf", "text/plain". */
189
+ mediaType: string;
190
+ /** The file's bytes, base64-encoded (no data: URI prefix). */
191
+ data: string;
192
+ }
193
+ export interface PromptSendPayload {
194
+ ws: string;
195
+ text: string;
196
+ /** Files attached to this prompt (images / documents). Optional. */
197
+ attachments?: PromptAttachment[];
198
+ }
199
+ export interface PermissionRespondPayload {
200
+ ws: string;
201
+ requestId: string;
202
+ /**
203
+ * `allow`/`deny` answer this one call. `allow_all` also persists a category
204
+ * rule on the daemon (the host, folder, or tool this call belongs to) so every
205
+ * later matching call auto-approves without another prompt — see
206
+ * {@link AllowRulePayload} and the `allowlist.*` messages.
207
+ */
208
+ decision: "allow" | "deny" | "allow_all";
209
+ }
210
+ /**
211
+ * One clarifying question the model asked via the `AskUserQuestion` tool. Mirrors
212
+ * the Agent SDK's input schema: each question is multiple-choice with 2–4
213
+ * options, single- or multi-select. `header` is a ≤12-char chip label.
214
+ */
215
+ export interface AgentQuestionOption {
216
+ label: string;
217
+ description?: string;
218
+ /** Optional rich preview (markdown/HTML) when the session enables it. */
219
+ preview?: string;
220
+ }
221
+ export interface AgentQuestion {
222
+ question: string;
223
+ header: string;
224
+ options: AgentQuestionOption[];
225
+ multiSelect: boolean;
226
+ }
227
+ /**
228
+ * The user's answers to an AskUserQuestion, keyed by each question's `question`
229
+ * text. A single-select answer is the chosen option's `label`; a multi-select
230
+ * answer is an array of labels (or free text the user typed for an "Other").
231
+ */
232
+ export type QuestionAnswers = Record<string, string | string[]>;
233
+ /**
234
+ * A client's reply to an `event.ask_question`. Either structured `answers` (the
235
+ * common case) or a freeform `response` the user typed instead of picking any
236
+ * option — when `response` is set the model is told "The user responded: …".
237
+ */
238
+ export interface QuestionRespondPayload {
239
+ ws: string;
240
+ requestId: string;
241
+ answers: QuestionAnswers;
242
+ response?: string;
243
+ }
244
+ /**
245
+ * A persisted "Allow all" rule — a *category*, not a single call:
246
+ * - host: any web request to `host` (any page on that site);
247
+ * - dir: any file/shell call whose target resolves inside `path`;
248
+ * - tool: any call of `tool` (for calls we can't bound to a host or folder).
249
+ */
250
+ export type AllowRulePayload = {
251
+ kind: "host";
252
+ host: string;
253
+ } | {
254
+ kind: "dir";
255
+ path: string;
256
+ } | {
257
+ kind: "tool";
258
+ tool: string;
259
+ };
260
+ /** One grant in the allowlist, with provenance for the admin list. */
261
+ export interface AllowEntryPayload {
262
+ /** Stable identity; also the handle passed to `allowlist.revoke`. */
263
+ key: string;
264
+ rule: AllowRulePayload;
265
+ /** One-line human description, e.g. "all requests to example.com". */
266
+ description: string;
267
+ /** Epoch ms the grant was made. */
268
+ createdMs: number;
269
+ /** Workspace + tool the grant was created from (context, not scope). */
270
+ fromWorkspace?: string;
271
+ fromTool?: string;
272
+ }
273
+ export interface AllowlistListPayload {
274
+ }
275
+ export interface AllowlistRevokePayload {
276
+ /** The `key` of the entry to drop (from an AllowEntryPayload). */
277
+ key: string;
278
+ }
279
+ export interface AllowlistClearPayload {
280
+ }
281
+ /** The full current allowlist (reply to `allowlist.list`; also broadcast). */
282
+ export interface AllowlistResultPayload {
283
+ entries: AllowEntryPayload[];
284
+ }
285
+ export interface TerminalInputPayload {
286
+ ws: string;
287
+ data: string;
288
+ /**
289
+ * Stable per-device id. Each device gets its OWN pty (its own size + its own
290
+ * scrollback) so a phone and a tablet with different screens don't clobber
291
+ * each other's geometry. Omitted by older clients → the daemon falls back to
292
+ * a per-connection id (isolated, but not restored across a reconnect).
293
+ */
294
+ deviceId?: string;
295
+ }
296
+ /** Stop the current agent turn (the session can be resumed by a new prompt). */
297
+ export interface AgentStopPayload {
298
+ ws: string;
299
+ }
300
+ export interface TerminalResizePayload {
301
+ ws: string;
302
+ cols: number;
303
+ rows: number;
304
+ /** See TerminalInputPayload.deviceId — resizes only this device's pty. */
305
+ deviceId?: string;
306
+ }
307
+ /**
308
+ * Ask for the terminal's retained scrollback so a client that just connected
309
+ * (or just opened the pane) can render history it never streamed.
310
+ */
311
+ export interface TerminalSnapshotPayload {
312
+ ws: string;
313
+ /** See TerminalInputPayload.deviceId — snapshots this device's own pty. */
314
+ deviceId?: string;
315
+ }
316
+ /** Branch/ahead-behind/dirty summary for the review-and-merge path. */
317
+ export interface GitStatusPayload {
318
+ ws: string;
319
+ /**
320
+ * Set by a deliberate open/refresh of the worktree. Forces the daemon to
321
+ * fetch origin past its per-repo throttle before computing the numbers, so a
322
+ * just-pushed base is reflected on the click. Omitted/false on background
323
+ * polls, which stay throttled. Mirrored in protocol.dart / Protocol.swift.
324
+ */
325
+ sync?: boolean;
326
+ }
327
+ /** Stage everything in the worktree and commit. */
328
+ export interface GitCommitPayload {
329
+ ws: string;
330
+ message: string;
331
+ }
332
+ /** Push the workspace branch to origin (sets upstream on first push). */
333
+ export interface GitPushPayload {
334
+ ws: string;
335
+ }
336
+ /** Merge the base branch into the workspace branch to clear a divergence. */
337
+ export interface GitResolvePayload {
338
+ ws: string;
339
+ }
340
+ /**
341
+ * Drive an in-progress merge's conflict resolution from the client:
342
+ * - `ours`/`theirs` keep the workspace-branch / merged-in-base version of the
343
+ * conflicting files (all of them, or just `paths` when given);
344
+ * - `commit` finalizes the merge (refused while conflicts remain);
345
+ * - `abort` rolls the merge back.
346
+ */
347
+ export interface GitMergePayload {
348
+ ws: string;
349
+ action: "ours" | "theirs" | "commit" | "abort";
350
+ /** Subset of conflicting files for `ours`/`theirs`; omit to apply to all. */
351
+ paths?: string[];
352
+ }
353
+ /** Open a pull request for the workspace branch via the gh CLI. */
354
+ export interface PrCreatePayload {
355
+ ws: string;
356
+ title: string;
357
+ body?: string;
358
+ }
359
+ /** Merge the workspace branch's open pull request via `gh pr merge`. */
360
+ export interface PrMergePayload {
361
+ ws: string;
362
+ }
363
+ /**
364
+ * Reveal the worktree on the daemon's Mac. Local-only convenience: the client
365
+ * stays a pure front end and the daemon shells out to `open`.
366
+ */
367
+ export interface WorkspaceOpenPayload {
368
+ ws: string;
369
+ target: "finder" | "terminal" | "editor";
370
+ }
371
+ export interface DiffGetPayload {
372
+ ws: string;
373
+ }
374
+ /** List every tracked + untracked (non-ignored) file in the worktree. */
375
+ export interface TreeGetPayload {
376
+ ws: string;
377
+ }
378
+ /** Read one file's contents from the worktree (path is worktree-relative). */
379
+ export interface FileGetPayload {
380
+ ws: string;
381
+ path: string;
382
+ }
383
+ /** Write new UTF-8 contents to one worktree-relative file (in-app editor save). */
384
+ export interface FileSetPayload {
385
+ ws: string;
386
+ path: string;
387
+ content: string;
388
+ }
389
+ /**
390
+ * Fetch a workspace's persisted chat transcript ("transactions"). The daemon
391
+ * keeps a running log of every prompt/response so a client can rebuild the
392
+ * conversation after a reboot or a fresh connect. See ChatHistoryResultPayload.
393
+ */
394
+ export interface ChatHistoryPayload {
395
+ ws: string;
396
+ }
397
+ export interface PingPayload {
398
+ }
399
+ export interface EchoPayload {
400
+ text: string;
401
+ }
402
+ /**
403
+ * A registered project: one repo, the group worktrees hang under in the
404
+ * sidebar. Persists even with zero worktrees so it stays a stable "+ new
405
+ * worktree here" target.
406
+ */
407
+ export interface ProjectSummary {
408
+ id: string;
409
+ name: string;
410
+ /** Local repo path the worktrees fork from (a clone path for GitHub sources). */
411
+ repo: string;
412
+ /** Present when the project was added from GitHub (drives clone/fetch). */
413
+ github?: {
414
+ fullName: string;
415
+ defaultBranch?: string;
416
+ };
417
+ /**
418
+ * `git remote get-url origin` for this project's repo, when it has one. Lets a
419
+ * client that is connected to several daemons match a project on daemon A to
420
+ * the "same" project on daemon B (same origin) — the basis for moving a
421
+ * workspace between daemons. Omitted for a repo with no origin remote.
422
+ */
423
+ originUrl?: string;
424
+ }
425
+ export interface ProjectListResultPayload {
426
+ projects: ProjectSummary[];
427
+ }
428
+ /** Branches a new worktree can be based on for a project (local + remote). */
429
+ export interface ProjectBranchesResultPayload {
430
+ /** Echoed back so clients can key the result to the project they asked about. */
431
+ projectId: string;
432
+ branches: string[];
433
+ /** The repo's default branch, when known (pickers surface it first). */
434
+ defaultBranch?: string;
435
+ }
436
+ /** One open pull request, as surfaced in the "create worktree from PR" picker. */
437
+ export interface PullRequestSummary {
438
+ number: number;
439
+ title: string;
440
+ /** The PR's head branch — what gets checked out into the new worktree. */
441
+ headRefName: string;
442
+ /** Login of the PR author (for a subtitle in the picker). */
443
+ author: string;
444
+ url: string;
445
+ isDraft: boolean;
446
+ }
447
+ /** Open pull requests a new worktree can be based on for a project. */
448
+ export interface ProjectPullsResultPayload {
449
+ /** Echoed back so clients can key the result to the project they asked about. */
450
+ projectId: string;
451
+ pulls: PullRequestSummary[];
452
+ }
453
+ /**
454
+ * One open issue. `body` is carried so the "from issue" flow can seed (and let
455
+ * the user confirm) the new worktree's task without a second round-trip.
456
+ */
457
+ export interface IssueSummary {
458
+ number: number;
459
+ title: string;
460
+ body: string;
461
+ author: string;
462
+ url: string;
463
+ }
464
+ /** Open issues a new worktree's task can be seeded from, for a project. */
465
+ export interface ProjectIssuesResultPayload {
466
+ /** Echoed back so clients can key the result to the project they asked about. */
467
+ projectId: string;
468
+ issues: IssueSummary[];
469
+ }
470
+ /** One item that could not be deleted in a `delete.batch`, with the reason. */
471
+ export interface DeleteFailure {
472
+ id: string;
473
+ kind: "workspace" | "project";
474
+ message: string;
475
+ }
476
+ /**
477
+ * Outcome of a `delete.batch`: what was removed and what failed. Unlike the
478
+ * single-item `workspace.delete` — which throws and surfaces as an `error`
479
+ * frame — a batch never aborts on one bad item; it always resolves and reports
480
+ * per-item failures here instead.
481
+ */
482
+ export interface DeleteBatchResultPayload {
483
+ deletedWorkspaces: string[];
484
+ deletedProjects: string[];
485
+ failures: DeleteFailure[];
486
+ }
487
+ export interface WorkspaceSummary {
488
+ ws: string;
489
+ name: string;
490
+ branch: string;
491
+ status: WorkspaceStatus;
492
+ /** Project this worktree belongs to (groups the sidebar). */
493
+ projectId: string;
494
+ /**
495
+ * Workspace flavor. Absent/`"worktree"` is the normal git-backed workspace;
496
+ * `"chat"` is a repo-less free-form chat (no branch, no diff) surfaced under
497
+ * the sidebar's Chat button rather than a project group.
498
+ */
499
+ kind?: "worktree" | "chat";
500
+ /** Model id backing this workspace's agent (see models.list). */
501
+ model?: string;
502
+ /** Lines added vs the base branch (uncommitted + committed); omitted if 0. */
503
+ added?: number;
504
+ /** Lines removed vs the base branch; omitted if 0. */
505
+ removed?: number;
506
+ /** Files differing from the base branch (count, not lines); omitted if 0. */
507
+ filesChanged?: number;
508
+ /**
509
+ * `git remote get-url origin` of the workspace's repo, when it has one. A
510
+ * client moving this workspace to another daemon uses it to find (or create) a
511
+ * matching project there before pulling the branch. Omitted if no origin.
512
+ */
513
+ repoUrl?: string;
514
+ }
515
+ /** Which agent backend runs a model. */
516
+ export type ModelProvider = "claude" | "codex" | "openrouter";
517
+ /** One selectable model, surfaced to the client via models.list.result. */
518
+ export interface ModelInfo {
519
+ id: string;
520
+ label: string;
521
+ provider: ModelProvider;
522
+ default?: boolean;
523
+ }
524
+ export interface ModelsListResultPayload {
525
+ models: ModelInfo[];
526
+ /** The id used when a workspace is created without an explicit model. */
527
+ defaultModel: string;
528
+ }
529
+ /**
530
+ * A billing/auth provider the daemon can hold an API key for. Distinct from
531
+ * `ModelProvider` (which names the agent *backend*): `anthropic` keys back the
532
+ * Claude backend, `openai` keys back the Codex backend, `openrouter` keys back
533
+ * the OpenRouter backend.
534
+ */
535
+ export type CredentialProvider = "anthropic" | "openai" | "openrouter";
536
+ /**
537
+ * Which credential a provider's sessions will use:
538
+ * - "apikey" — an API key is stored on (or seeded into) the daemon.
539
+ * - "sso" — no key, so sessions fall back to the CLI/SDK's own cached
540
+ * subscription login (Claude Code login / Codex ChatGPT login).
541
+ * - "none" — no key and no login fallback (OpenRouter is API-key only).
542
+ */
543
+ export type CredentialSource = "apikey" | "sso" | "none";
544
+ /** Per-provider credential status — safe to broadcast (no raw key material). */
545
+ export interface CredentialInfo {
546
+ provider: CredentialProvider;
547
+ source: CredentialSource;
548
+ /** An effective API key is present (stored on the daemon or seeded from env). */
549
+ hasKey: boolean;
550
+ /** Non-reversible hint (last 4 chars, e.g. "…a1b2"); null when no key. */
551
+ maskedKey: string | null;
552
+ }
553
+ /** Read the credential status for every provider (plus the app config). */
554
+ export interface SettingsGetPayload {
555
+ }
556
+ /** Set (non-empty) or clear (empty/omitted) a provider's stored API key. */
557
+ export interface SettingsSetPayload {
558
+ provider: CredentialProvider;
559
+ apiKey: string;
560
+ }
561
+ /**
562
+ * Broadcast whenever credentials or the app config change. Carries the full,
563
+ * current state of both so every client converges: credential status (no raw
564
+ * key material) and the daemon-owned {@link AppConfig}.
565
+ */
566
+ export interface SettingsResultPayload {
567
+ credentials: CredentialInfo[];
568
+ config: AppConfig;
569
+ }
570
+ /**
571
+ * How a workspace's branch name is prefixed:
572
+ * - "none" — no prefix; the slug is the branch name.
573
+ * - "username" — prefix with the local git user (e.g. `alice/`).
574
+ * - "custom" — prefix with the literal {@link GitConfig.branchPrefix}.
575
+ */
576
+ export type BranchPrefixType = "none" | "username" | "custom";
577
+ /**
578
+ * Custom prompt text the daemon injects into agent sessions / actions.
579
+ *
580
+ * `general` is appended to the system prompt of every agent session. The rest
581
+ * are per-action prompts; an action's prompt is used only once that action
582
+ * exists in the daemon (several actions are not built yet — the field is stored
583
+ * and editable so the wiring is ready when the action lands).
584
+ */
585
+ export interface PromptsConfig {
586
+ /** Appended to the system prompt of every agent session in this daemon. */
587
+ general: string;
588
+ /** Custom prompt for the code-review action. */
589
+ codeReview: string;
590
+ /** Custom prompt for the create-PR action. */
591
+ createPr: string;
592
+ /** Custom prompt for the fix-errors action. */
593
+ fixErrors: string;
594
+ /** Custom prompt for the resolve-merge-conflicts action. */
595
+ resolveMergeConflicts: string;
596
+ /** Custom prompt for the branch-naming / rename-branch action. */
597
+ renameBranch: string;
598
+ }
599
+ /**
600
+ * Git lifecycle behavior for workspaces.
601
+ *
602
+ * NOTE: `deleteBranchOnArchive` and `archiveOnMerge` presuppose an archive/merge
603
+ * lifecycle the daemon does not have yet, and deleting a branch conflicts with
604
+ * the standing invariant that `workspace.delete` never touches the branch. They
605
+ * are persisted and editable, but inert until that lifecycle is designed.
606
+ */
607
+ export interface GitConfig {
608
+ /** (Reserved) Delete the workspace branch when a workspace is archived. */
609
+ deleteBranchOnArchive: boolean;
610
+ /** (Reserved) Archive a workspace automatically when its PR merges. */
611
+ archiveOnMerge: boolean;
612
+ /** Configure the upstream remote on first push (`git push -u`). */
613
+ worktreePushAutoSetupRemote: boolean;
614
+ /** How branch-name prefixes are generated. */
615
+ branchPrefixType: BranchPrefixType;
616
+ /** Literal prefix used when `branchPrefixType` is "custom" (e.g. `feature/`). */
617
+ branchPrefix: string;
618
+ }
619
+ /** The full daemon-owned app configuration. */
620
+ export interface AppConfig {
621
+ prompts: PromptsConfig;
622
+ git: GitConfig;
623
+ }
624
+ /**
625
+ * Replace the daemon's app config wholesale (last-write-wins). The daemon
626
+ * validates/clamps every field, persists, then broadcasts a fresh
627
+ * `settings.result` so all clients converge.
628
+ */
629
+ export interface ConfigSetPayload {
630
+ config: AppConfig;
631
+ }
632
+ export interface WorkspaceListResultPayload {
633
+ workspaces: WorkspaceSummary[];
634
+ }
635
+ export interface WorkspaceCreatedPayload {
636
+ ws: string;
637
+ name: string;
638
+ branch: string;
639
+ /** Present as `"chat"` for a repo-less chat workspace; else worktree. */
640
+ kind?: "worktree" | "chat";
641
+ }
642
+ /** A workspace's sidebar label changed (e.g. chat auto-title). */
643
+ export interface WorkspaceRenamedPayload {
644
+ ws: string;
645
+ name: string;
646
+ }
647
+ export interface EventAssistantMessagePayload {
648
+ ws: string;
649
+ text: string;
650
+ }
651
+ export interface EventToolUsePayload {
652
+ ws: string;
653
+ tool: string;
654
+ inputSummary: string;
655
+ }
656
+ export interface EventPermissionRequestPayload {
657
+ ws: string;
658
+ requestId: string;
659
+ tool: string;
660
+ inputSummary: string;
661
+ }
662
+ /**
663
+ * A previously-broadcast permission request was resolved without the client
664
+ * answering it — a new "allow all" rule retroactively covered it — so clients
665
+ * collapse its card. Emitted by the retroactive-release sweep (see
666
+ * ClaudeAgent.releaseCoveredPending).
667
+ */
668
+ export interface EventPermissionResolvedPayload {
669
+ ws: string;
670
+ requestId: string;
671
+ decision: "allow" | "deny";
672
+ }
673
+ /**
674
+ * The model asked one or more clarifying questions via `AskUserQuestion`. The
675
+ * client renders an interactive card and replies with `question.respond`
676
+ * carrying the picked option label(s). Unlike a permission (allow/deny), the
677
+ * chosen answers are fed back to the model as the tool's result.
678
+ */
679
+ export interface EventAskQuestionPayload {
680
+ ws: string;
681
+ requestId: string;
682
+ questions: AgentQuestion[];
683
+ }
684
+ export interface EventSessionPayload {
685
+ ws: string;
686
+ sessionId: string;
687
+ status: WorkspaceStatus;
688
+ }
689
+ export interface EventAgentDonePayload {
690
+ ws: string;
691
+ summary?: string;
692
+ /**
693
+ * Pre-formatted token/cost readout for the turn, e.g. "12.3k in · 1.1k out ·
694
+ * $0.04". The daemon formats it once (see usage.ts) so the live "✓ done" line
695
+ * and the replayed transcript entry stay identical across clients. Absent when
696
+ * the backend reported no usage.
697
+ */
698
+ usageText?: string;
699
+ }
700
+ /**
701
+ * A plan/session usage limit was hit in a workspace. Clients surface this as a
702
+ * card offering to schedule a one-shot restart job at `resetAt` (epoch ms, when
703
+ * the notice carried a reset time). Distinct from a transient 429 (which stays a
704
+ * plain `error`).
705
+ */
706
+ export interface EventSessionLimitPayload {
707
+ ws: string;
708
+ message: string;
709
+ resetAt?: number;
710
+ }
711
+ export interface TerminalDataPayload {
712
+ ws: string;
713
+ data: string;
714
+ }
715
+ /** Retained scrollback (may be empty if no terminal has been started). */
716
+ export interface TerminalSnapshotResultPayload {
717
+ ws: string;
718
+ data: string;
719
+ }
720
+ /**
721
+ * A TCP port a worktree's process tree is listening on — discovered by the
722
+ * daemon (via `lsof`, correlated to a worktree by the owning process's `cwd`).
723
+ * `addr` is the raw bind address lsof reported (`127.0.0.1`, `*`, `[::1]`, …);
724
+ * the daemon always dials `127.0.0.1:port` regardless, so an all-interfaces bind
725
+ * is still reachable.
726
+ */
727
+ export interface ForwardPort {
728
+ ws: string;
729
+ port: number;
730
+ addr: string;
731
+ }
732
+ /** Ask for the current worktree-owned listening ports (reply: forward.available). */
733
+ export interface ForwardListPayload {
734
+ }
735
+ /** The current set of worktree-owned listening ports. Broadcast on every change. */
736
+ export interface ForwardAvailablePayload {
737
+ ports: ForwardPort[];
738
+ }
739
+ /**
740
+ * Open a forwarded TCP stream: the client just accepted a local connection on
741
+ * its loopback listener and wants it bridged to `127.0.0.1:port` on the daemon.
742
+ * `streamId` is a fresh client-minted id that keys every later frame for this
743
+ * connection. The daemon refuses (immediate `forward.close` with `error`) unless
744
+ * `port` is currently in the worktree-owned set — you cannot reach arbitrary
745
+ * loopback ports on the daemon box.
746
+ */
747
+ export interface ForwardOpenPayload {
748
+ ws: string;
749
+ streamId: string;
750
+ port: number;
751
+ }
752
+ /**
753
+ * One chunk of forwarded bytes, base64-encoded. BIDIRECTIONAL: client→daemon
754
+ * carries browser→server bytes; daemon→client carries the server's reply. Keyed
755
+ * by `streamId`.
756
+ */
757
+ export interface ForwardDataPayload {
758
+ streamId: string;
759
+ data: string;
760
+ }
761
+ /**
762
+ * Tear down a forwarded stream. BIDIRECTIONAL: either side closes its end.
763
+ * `error` is set when the daemon refuses or aborts the stream (an unowned port,
764
+ * a dial failure, or the target hung up unexpectedly).
765
+ */
766
+ export interface ForwardClosePayload {
767
+ streamId: string;
768
+ error?: string;
769
+ }
770
+ export interface GitStatusResultPayload {
771
+ ws: string;
772
+ branch: string;
773
+ baseBranch: string;
774
+ /** Commits on the workspace branch that the base doesn't have. */
775
+ ahead: number;
776
+ /** Commits on the base that the workspace branch doesn't have. */
777
+ behind: number;
778
+ /** Whether the branch has an `origin/<branch>` remote-tracking ref yet. */
779
+ hasRemote: boolean;
780
+ /** Commits on HEAD not yet on the remote branch (all of them before first push). */
781
+ unpushed: number;
782
+ /** A merge is in progress (MERGE_HEAD present) — the conflict flow is active. */
783
+ merging: boolean;
784
+ /**
785
+ * An open pull request already exists for this branch (via `gh`). Clients hide
786
+ * "Create PR" when true and gate "Merge"/"Resolve" on it. Always false for a
787
+ * branch with no remote — a PR can't exist before the first push.
788
+ */
789
+ hasPr: boolean;
790
+ /**
791
+ * This branch's pull request has been merged on the remote (via `gh`). Backs
792
+ * the "Archive" affordance once the PR lands. Mutually exclusive with `hasPr`.
793
+ */
794
+ prMerged: boolean;
795
+ /**
796
+ * The branch's PR number, when one exists (open, merged, or closed). Clients
797
+ * render it as a `#<n>` link to `prUrl`. Absent for a branch with no PR.
798
+ */
799
+ prNumber?: number;
800
+ /** The branch's PR web URL, paired with `prNumber`. Absent when there's no PR. */
801
+ prUrl?: string;
802
+ /**
803
+ * Reconciling the base into this branch would conflict — a `git merge-tree`
804
+ * prediction that never touches the worktree. Backs the "Resolve" button;
805
+ * false whenever the branch is not behind the base.
806
+ */
807
+ conflictsWithBase: boolean;
808
+ /** Worktree-relative paths with merge conflicts (unmerged), sorted. */
809
+ conflicted: string[];
810
+ /** Worktree-relative paths with uncommitted changes (porcelain). */
811
+ changed: string[];
812
+ /** Total lines added vs the base branch (committed + uncommitted). */
813
+ added: number;
814
+ /** Total lines removed vs the base branch. */
815
+ removed: number;
816
+ }
817
+ /** Outcome of a git.commit / git.push / git.resolve / git.merge / pr.create / pr.merge / issue.create request. */
818
+ export interface GitResultPayload {
819
+ ws: string;
820
+ op: "commit" | "push" | "pr" | "pr_merge" | "resolve" | "merge" | "issue";
821
+ ok: boolean;
822
+ /** Human-readable success detail or failure reason. */
823
+ message: string;
824
+ /** PR URL when op === "pr", or issue URL when op === "issue", and ok. */
825
+ url?: string;
826
+ }
827
+ /** Role of a persisted transcript entry — mirrors the client's ChatRole. */
828
+ export type ChatEntryRole = "user" | "assistant" | "tool" | "permission" | "question" | "limit" | "system";
829
+ /**
830
+ * One durable line in a workspace's transcript. The daemon appends these to a
831
+ * per-workspace running log as prompts and events flow, so the whole
832
+ * conversation can be replayed on demand (chat.history).
833
+ */
834
+ export interface ChatEntry {
835
+ ts: number;
836
+ role: ChatEntryRole;
837
+ text: string;
838
+ requestId?: string;
839
+ tool?: string;
840
+ resolved?: "allow" | "deny";
841
+ questions?: AgentQuestion[];
842
+ answers?: QuestionAnswers;
843
+ response?: string;
844
+ resetAt?: number;
845
+ /**
846
+ * user-only: metadata for files attached to this prompt. Only `name` and
847
+ * `mediaType` are persisted — never the bytes — so the transcript can render
848
+ * an "📎 name" chip on replay without carrying the payload in the log.
849
+ */
850
+ attachments?: ChatAttachmentMeta[];
851
+ }
852
+ /** Lightweight attachment descriptor persisted in the transcript (no bytes). */
853
+ export interface ChatAttachmentMeta {
854
+ name: string;
855
+ mediaType: string;
856
+ }
857
+ export interface ChatHistoryResultPayload {
858
+ ws: string;
859
+ entries: ChatEntry[];
860
+ }
861
+ export interface DiffHunk {
862
+ header: string;
863
+ lines: string[];
864
+ }
865
+ export interface DiffFile {
866
+ path: string;
867
+ hunks: DiffHunk[];
868
+ }
869
+ export interface DiffResultPayload {
870
+ ws: string;
871
+ files: DiffFile[];
872
+ }
873
+ /** Flat, sorted list of worktree-relative file paths (the full codebase). */
874
+ export interface TreeResultPayload {
875
+ ws: string;
876
+ files: string[];
877
+ }
878
+ /** One file's contents for the in-app viewer. */
879
+ export interface FileResultPayload {
880
+ ws: string;
881
+ path: string;
882
+ content: string;
883
+ truncated: boolean;
884
+ binary: boolean;
885
+ }
886
+ /** Acknowledges a successful in-app editor save (file.set). */
887
+ export interface FileSavedPayload {
888
+ ws: string;
889
+ path: string;
890
+ bytes: number;
891
+ }
892
+ export interface GithubStatusResultPayload {
893
+ available: boolean;
894
+ authenticated: boolean;
895
+ user?: string;
896
+ }
897
+ export interface GithubRepo {
898
+ fullName: string;
899
+ name: string;
900
+ owner: string;
901
+ description: string;
902
+ isPrivate: boolean;
903
+ defaultBranch: string;
904
+ url: string;
905
+ }
906
+ export interface GithubReposResultPayload {
907
+ repos: GithubRepo[];
908
+ }
909
+ export interface PongPayload {
910
+ }
911
+ /** Client → daemon: announce this device's public key to establish E2E. */
912
+ export interface HelloPayload {
913
+ /** base64 of the device's X25519 public key. */
914
+ pub: string;
915
+ /** Optional human label for the paired-devices record. */
916
+ label?: string;
917
+ }
918
+ /** Daemon → client: handshake ack carrying the daemon's public key. */
919
+ export interface HelloOkPayload {
920
+ /** base64 of the daemon's public key (empty when E2E is off). */
921
+ pub: string;
922
+ /** Whether payloads on this socket are now sealed. */
923
+ sealed: boolean;
924
+ }
925
+ /** Client → daemon: ask for the daemon's public key (to build a pairing QR). */
926
+ export interface PairInfoPayload {
927
+ }
928
+ /** Daemon → client: the daemon's public key, or empty when E2E is off. */
929
+ export interface PairInfoResultPayload {
930
+ pub: string;
931
+ }
932
+ /** Client → daemon: ask for this daemon's identity (name, id, host, version). */
933
+ export interface DaemonGetPayload {
934
+ }
935
+ /** Client → daemon: set this daemon's human name. Empty/blank is rejected. */
936
+ export interface DaemonRenamePayload {
937
+ name: string;
938
+ }
939
+ /**
940
+ * Daemon → client: this daemon's identity. Pushed unsolicited right after a
941
+ * socket connects, returned for `daemon.get`, and broadcast after
942
+ * `daemon.rename`. A client connected to multiple daemons uses `daemonId` as the
943
+ * stable key and `name` as the label; older daemons never send this, so a client
944
+ * falls back to the connection's URL host as the name.
945
+ */
946
+ export interface DaemonInfoPayload {
947
+ /** Stable per-daemon uuid, persisted in ~/.immediacy/daemon.json. */
948
+ daemonId: string;
949
+ /** Human name; defaults to the machine hostname on first run. */
950
+ name: string;
951
+ /** os.hostname() of the daemon's machine (shown as a subtitle / default name). */
952
+ hostname: string;
953
+ /** Daemon package version (VERSION file), for display / diagnostics. */
954
+ version: string;
955
+ /** The PROTOCOL_VERSION this daemon speaks, for future skew detection. */
956
+ protocolVersion: number;
957
+ }
958
+ export interface ErrorPayload {
959
+ code: string;
960
+ message: string;
961
+ ws?: string;
962
+ }
963
+ export interface EchoResultPayload {
964
+ text: string;
965
+ }
966
+ /**
967
+ * A time-based trigger. `cron` is a croner pattern (standard 5-field cron, plus
968
+ * seconds if 6 fields); e.g. "0 9 * * *" = 09:00 daily. If the daemon was down
969
+ * at fire time the run is simply missed (no catch-up) — next occurrence fires
970
+ * normally.
971
+ */
972
+ export interface ScheduleTrigger {
973
+ kind: "schedule";
974
+ cron: string;
975
+ /** IANA timezone (e.g. "America/New_York"); daemon-local zone if omitted. */
976
+ timezone?: string;
977
+ }
978
+ /** Identifiable prior actions a reactive trigger can wait behind. */
979
+ export type JobSystemEvent = "agent.done" | "pr.created" | "commit" | "push" | "workspace.created";
980
+ /**
981
+ * A reactive trigger: run `delayMinutes` after `event` fires. Optionally scoped
982
+ * to a single workspace (`ws`); otherwise any workspace's event arms it.
983
+ */
984
+ export interface AfterTrigger {
985
+ kind: "after";
986
+ event: JobSystemEvent;
987
+ delayMinutes: number;
988
+ ws?: string;
989
+ }
990
+ export type JobTrigger = ScheduleTrigger | AfterTrigger;
991
+ /** Run the job's prompt inside an existing workspace. */
992
+ export interface WorkspaceTarget {
993
+ kind: "workspace";
994
+ /** For an `after` trigger, omit to run in the workspace the event fired in. */
995
+ ws?: string;
996
+ }
997
+ /**
998
+ * Spin up a throwaway workspace from a repo source, run the prompt, then tear it
999
+ * down — kept for review if the worktree ends dirty. The natural fit for a pure
1000
+ * time-based job with no standing workspace to run in.
1001
+ */
1002
+ export interface EphemeralTarget {
1003
+ kind: "ephemeral";
1004
+ repo?: string;
1005
+ github?: {
1006
+ fullName: string;
1007
+ defaultBranch?: string;
1008
+ };
1009
+ branchBase?: string;
1010
+ model?: string;
1011
+ /** Base name for the throwaway workspace (defaults to the job name). */
1012
+ name?: string;
1013
+ }
1014
+ export type JobTarget = WorkspaceTarget | EphemeralTarget;
1015
+ export type JobRunStatus = "started" | "ok" | "error" | "skipped";
1016
+ /** A persisted job definition (the daemon owns id/timestamps/bookkeeping). */
1017
+ export interface JobRecord {
1018
+ id: string;
1019
+ name: string;
1020
+ /** The prompt handed to the agent when the job fires (supports `{{ctx}}`). */
1021
+ prompt: string;
1022
+ trigger: JobTrigger;
1023
+ target: JobTarget;
1024
+ enabled: boolean;
1025
+ createdAt: number;
1026
+ updatedAt: number;
1027
+ lastRunAt?: number;
1028
+ lastStatus?: JobRunStatus;
1029
+ /** Next fire time for a schedule trigger (informational; undefined for after). */
1030
+ nextRunAt?: number;
1031
+ }
1032
+ /** Client-supplied fields for job.create / job.update (server fills the rest). */
1033
+ export interface JobInput {
1034
+ name: string;
1035
+ prompt: string;
1036
+ trigger: JobTrigger;
1037
+ target: JobTarget;
1038
+ enabled?: boolean;
1039
+ }
1040
+ /** One entry in a job's run history (append-only, like ChatEntry). */
1041
+ export interface JobRun {
1042
+ id: string;
1043
+ jobId: string;
1044
+ jobName: string;
1045
+ ts: number;
1046
+ status: JobRunStatus;
1047
+ /** Workspace the run executed in (resolved, incl. an ephemeral one). */
1048
+ ws?: string;
1049
+ trigger: "schedule" | "after" | "manual";
1050
+ /** Human-readable detail / error / skip reason. */
1051
+ message: string;
1052
+ }
1053
+ export interface JobListPayload {
1054
+ }
1055
+ export interface JobCreatePayload {
1056
+ job: JobInput;
1057
+ }
1058
+ export interface JobUpdatePayload {
1059
+ id: string;
1060
+ patch: Partial<JobInput>;
1061
+ }
1062
+ export interface JobDeletePayload {
1063
+ id: string;
1064
+ }
1065
+ /** Manually fire a job now, ignoring its trigger. */
1066
+ export interface JobRunPayload {
1067
+ id: string;
1068
+ }
1069
+ export interface JobRunsPayload {
1070
+ /** Restrict to one job's runs; all jobs if omitted. */
1071
+ jobId?: string;
1072
+ /** Cap on how many (most-recent-first); daemon default if omitted. */
1073
+ limit?: number;
1074
+ }
1075
+ export interface JobListResultPayload {
1076
+ jobs: JobRecord[];
1077
+ }
1078
+ export interface JobRunsResultPayload {
1079
+ runs: JobRun[];
1080
+ }
1081
+ /** Broadcast when a job starts, finishes, errors, or is skipped. */
1082
+ export interface EventJobRunPayload {
1083
+ run: JobRun;
1084
+ }
1085
+ /** Severity. `debug` is dropped unless LOG_DEBUG=1; warn/error always kept. */
1086
+ export type LogLevel = "debug" | "info" | "warn" | "error";
1087
+ /** Which subsystem an entry came from — drives the client-side filter. */
1088
+ export type LogCategory = "conn" | "model" | "agent" | "forward" | "error" | "system";
1089
+ /**
1090
+ * One structured log line. `seq` is a daemon-monotonic counter (resets to 0 on
1091
+ * restart) and is the ordering + dedup key: clients render ascending `seq` and
1092
+ * drop any live entry with `seq <=` the highest already shown. Never carries a
1093
+ * secret (NaCl key, Access token, device pub) — enforced at the call site and
1094
+ * by a unit test.
1095
+ */
1096
+ export interface LogEntry {
1097
+ seq: number;
1098
+ ts: number;
1099
+ level: LogLevel;
1100
+ cat: LogCategory;
1101
+ msg: string;
1102
+ ws?: string;
1103
+ data?: Record<string, unknown>;
1104
+ }
1105
+ /**
1106
+ * Client → daemon: fetch a tail of the ring buffer and subscribe this socket to
1107
+ * live `log.event` pushes. `limit` defaults to 200 (clamped to the 500 ring).
1108
+ * With `sinceSeq`, return only entries with `seq > sinceSeq` (used to backfill a
1109
+ * gap after a reconnect). Re-sending re-subscribes idempotently.
1110
+ */
1111
+ export interface LogTailPayload {
1112
+ limit?: number;
1113
+ sinceSeq?: number;
1114
+ }
1115
+ /** Daemon → client: the snapshot, ascending by `seq`. */
1116
+ export interface LogTailResultPayload {
1117
+ entries: LogEntry[];
1118
+ }
1119
+ /** Daemon → client: one live entry, pushed only to subscribed sockets. */
1120
+ export interface LogEventPayload {
1121
+ entry: LogEntry;
1122
+ }
1123
+ export interface PayloadMap {
1124
+ "project.list": ProjectListPayload;
1125
+ "project.create": ProjectCreatePayload;
1126
+ "project.delete": ProjectDeletePayload;
1127
+ "project.branches": ProjectBranchesPayload;
1128
+ "project.pulls": ProjectPullsPayload;
1129
+ "project.issues": ProjectIssuesPayload;
1130
+ "issue.create": IssueCreatePayload;
1131
+ "workspace.list": WorkspaceListPayload;
1132
+ "workspace.create": WorkspaceCreatePayload;
1133
+ "workspace.delete": WorkspaceDeletePayload;
1134
+ "workspace.archive": WorkspaceArchivePayload;
1135
+ "workspace.action": WorkspaceActionPayload;
1136
+ "delete.batch": DeleteBatchPayload;
1137
+ "prompt.send": PromptSendPayload;
1138
+ "agent.stop": AgentStopPayload;
1139
+ "permission.respond": PermissionRespondPayload;
1140
+ "question.respond": QuestionRespondPayload;
1141
+ "allowlist.list": AllowlistListPayload;
1142
+ "allowlist.revoke": AllowlistRevokePayload;
1143
+ "allowlist.clear": AllowlistClearPayload;
1144
+ "terminal.input": TerminalInputPayload;
1145
+ "terminal.resize": TerminalResizePayload;
1146
+ "terminal.snapshot": TerminalSnapshotPayload;
1147
+ "forward.list": ForwardListPayload;
1148
+ "forward.open": ForwardOpenPayload;
1149
+ "forward.data": ForwardDataPayload;
1150
+ "forward.close": ForwardClosePayload;
1151
+ "git.status": GitStatusPayload;
1152
+ "git.commit": GitCommitPayload;
1153
+ "git.push": GitPushPayload;
1154
+ "git.resolve": GitResolvePayload;
1155
+ "git.merge": GitMergePayload;
1156
+ "pr.create": PrCreatePayload;
1157
+ "pr.merge": PrMergePayload;
1158
+ "workspace.open": WorkspaceOpenPayload;
1159
+ "diff.get": DiffGetPayload;
1160
+ "tree.get": TreeGetPayload;
1161
+ "file.get": FileGetPayload;
1162
+ "file.set": FileSetPayload;
1163
+ "chat.history": ChatHistoryPayload;
1164
+ "github.status": GithubStatusPayload;
1165
+ "github.repos": GithubReposPayload;
1166
+ "models.list": ModelsListPayload;
1167
+ "job.list": JobListPayload;
1168
+ "job.create": JobCreatePayload;
1169
+ "job.update": JobUpdatePayload;
1170
+ "job.delete": JobDeletePayload;
1171
+ "job.run": JobRunPayload;
1172
+ "job.runs": JobRunsPayload;
1173
+ "settings.get": SettingsGetPayload;
1174
+ "settings.set": SettingsSetPayload;
1175
+ "config.set": ConfigSetPayload;
1176
+ "log.tail": LogTailPayload;
1177
+ hello: HelloPayload;
1178
+ "pair.info": PairInfoPayload;
1179
+ "daemon.get": DaemonGetPayload;
1180
+ "daemon.rename": DaemonRenamePayload;
1181
+ ping: PingPayload;
1182
+ echo: EchoPayload;
1183
+ "project.list.result": ProjectListResultPayload;
1184
+ "project.branches.result": ProjectBranchesResultPayload;
1185
+ "project.pulls.result": ProjectPullsResultPayload;
1186
+ "project.issues.result": ProjectIssuesResultPayload;
1187
+ "workspace.list.result": WorkspaceListResultPayload;
1188
+ "workspace.created": WorkspaceCreatedPayload;
1189
+ "workspace.renamed": WorkspaceRenamedPayload;
1190
+ "delete.batch.result": DeleteBatchResultPayload;
1191
+ "event.assistant_message": EventAssistantMessagePayload;
1192
+ "event.tool_use": EventToolUsePayload;
1193
+ "event.permission_request": EventPermissionRequestPayload;
1194
+ "event.permission_resolved": EventPermissionResolvedPayload;
1195
+ "event.ask_question": EventAskQuestionPayload;
1196
+ "event.session": EventSessionPayload;
1197
+ "event.agent_done": EventAgentDonePayload;
1198
+ "event.session_limit": EventSessionLimitPayload;
1199
+ "terminal.data": TerminalDataPayload;
1200
+ "terminal.snapshot.result": TerminalSnapshotResultPayload;
1201
+ "forward.available": ForwardAvailablePayload;
1202
+ "git.status.result": GitStatusResultPayload;
1203
+ "git.result": GitResultPayload;
1204
+ "diff.result": DiffResultPayload;
1205
+ "tree.result": TreeResultPayload;
1206
+ "file.result": FileResultPayload;
1207
+ "file.saved": FileSavedPayload;
1208
+ "chat.history.result": ChatHistoryResultPayload;
1209
+ "github.status.result": GithubStatusResultPayload;
1210
+ "github.repos.result": GithubReposResultPayload;
1211
+ "models.list.result": ModelsListResultPayload;
1212
+ "job.list.result": JobListResultPayload;
1213
+ "job.runs.result": JobRunsResultPayload;
1214
+ "event.job_run": EventJobRunPayload;
1215
+ "settings.result": SettingsResultPayload;
1216
+ "allowlist.result": AllowlistResultPayload;
1217
+ "log.tail.result": LogTailResultPayload;
1218
+ "log.event": LogEventPayload;
1219
+ "hello.ok": HelloOkPayload;
1220
+ "pair.info.result": PairInfoResultPayload;
1221
+ "daemon.info": DaemonInfoPayload;
1222
+ pong: PongPayload;
1223
+ error: ErrorPayload;
1224
+ "echo.result": EchoResultPayload;
1225
+ }
1226
+ export type PayloadFor<T extends MessageType> = T extends keyof PayloadMap ? PayloadMap[T] : never;