@agent-compose/sdk 0.8.5 → 0.8.7

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 (100) hide show
  1. package/README.md +213 -189
  2. package/dist/agent/agent-context.d.ts +3 -3
  3. package/dist/agent/agent-loop.d.ts +6 -5
  4. package/dist/agent/perf-sampler.d.ts +27 -2
  5. package/dist/agent/run-agent.d.ts +1 -1
  6. package/dist/client.d.ts +119 -54
  7. package/dist/directives.d.ts +3 -3
  8. package/dist/display.d.ts +7 -0
  9. package/dist/errors.d.ts +1 -1
  10. package/dist/generated/agentc-commands.d.ts +34 -0
  11. package/dist/index.d.ts +12 -12
  12. package/dist/index.js +771 -204
  13. package/dist/request-context/request-context.d.ts +1 -1
  14. package/dist/runtimes/_cli-agent.d.ts +185 -68
  15. package/dist/runtimes/_reported-model.d.ts +16 -0
  16. package/dist/runtimes/claude-code.d.ts +60 -1
  17. package/dist/runtimes/claude.d.ts +1 -1
  18. package/dist/runtimes/codex.d.ts +94 -6
  19. package/dist/runtimes/codex.mid-turn-hook.test.d.ts +10 -0
  20. package/dist/runtimes/model-report.test.d.ts +14 -0
  21. package/dist/runtimes/openai-desktop.js +741 -200
  22. package/dist/runtimes/opencode.d.ts +48 -11
  23. package/dist/runtimes/opencode.test.d.ts +14 -0
  24. package/dist/sandbox/baked-clis.d.ts +75 -0
  25. package/dist/sandbox/exec-stream.d.ts +1 -2
  26. package/dist/sandbox/network-policy.d.ts +23 -5
  27. package/dist/sandbox.d.ts +4 -2
  28. package/dist/step-invocation/protocol.d.ts +3 -4
  29. package/dist/step-invocation/server.d.ts +2 -2
  30. package/dist/step-invocation/types.d.ts +1 -1
  31. package/dist/types/api-conversations.d.ts +442 -29
  32. package/dist/types/api-factory.d.ts +99 -10
  33. package/dist/types/api-projects.d.ts +521 -0
  34. package/dist/types/api-runs.d.ts +83 -0
  35. package/dist/types/api-scopes.d.ts +32 -3
  36. package/dist/types/conversation-stream.d.ts +5 -0
  37. package/dist/types/execution-context.d.ts +1 -1
  38. package/dist/types/protocol.d.ts +86 -2
  39. package/dist/types/runtime.d.ts +9 -2
  40. package/dist/types/workflow-metadata.d.ts +2 -4
  41. package/dist/types/workflow-plan.d.ts +1 -3
  42. package/dist/utils/bundler.d.ts +23 -0
  43. package/dist/workflow-steps/observability.d.ts +2 -3
  44. package/dist/workflow-steps/runner.d.ts +5 -8
  45. package/dist/workflow-steps/types.d.ts +8 -10
  46. package/dist/workflow-steps/workflow.d.ts +2 -1
  47. package/dist/workflows/engine.d.ts +3 -5
  48. package/dist/workflows/invoke-child.d.ts +2 -2
  49. package/package.json +2 -2
  50. package/src/agent/agent-context.ts +168 -125
  51. package/src/agent/agent-loop.ts +7 -6
  52. package/src/agent/perf-sampler.ts +54 -3
  53. package/src/agent/run-agent.ts +1 -1
  54. package/src/client.ts +226 -71
  55. package/src/directives.ts +3 -3
  56. package/src/display.ts +12 -0
  57. package/src/errors.ts +1 -0
  58. package/src/generated/agentc-commands.ts +571 -0
  59. package/src/index.ts +57 -21
  60. package/src/pause/pause-core.ts +2 -1
  61. package/src/request-context/request-context.ts +1 -1
  62. package/src/runtimes/_cli-agent.ts +318 -122
  63. package/src/runtimes/_reported-model.ts +24 -0
  64. package/src/runtimes/claude-code.ts +195 -12
  65. package/src/runtimes/claude.ts +9 -2
  66. package/src/runtimes/codex.ts +188 -19
  67. package/src/runtimes/opencode.ts +195 -26
  68. package/src/sandbox/baked-clis.ts +86 -0
  69. package/src/sandbox/exec-stream.ts +1 -2
  70. package/src/sandbox/network-policy.ts +51 -7
  71. package/src/sandbox/providers/e2b.ts +3 -3
  72. package/src/sandbox/providers/vercel.ts +6 -6
  73. package/src/sandbox.ts +8 -2
  74. package/src/step-invocation/invoker.ts +2 -6
  75. package/src/step-invocation/protocol.ts +3 -4
  76. package/src/step-invocation/server.ts +2 -2
  77. package/src/types/api-conversations.ts +366 -23
  78. package/src/types/api-factory.ts +95 -10
  79. package/src/types/api-projects.ts +477 -0
  80. package/src/types/api-runs.ts +73 -0
  81. package/src/types/api-scopes.ts +32 -3
  82. package/src/types/conversation-stream.ts +5 -0
  83. package/src/types/execution-context.ts +1 -1
  84. package/src/types/protocol.ts +91 -2
  85. package/src/types/runtime.ts +8 -2
  86. package/src/types/sandbox-environment.ts +1 -2
  87. package/src/types/workflow-metadata.ts +2 -4
  88. package/src/types/workflow-plan.ts +1 -3
  89. package/src/utils/bundler.ts +88 -19
  90. package/src/workflow-steps/observability.ts +2 -3
  91. package/src/workflow-steps/runner.ts +5 -8
  92. package/src/workflow-steps/types.ts +8 -10
  93. package/src/workflow-steps/workflow.ts +2 -1
  94. package/src/workflows/engine.ts +3 -5
  95. package/src/workflows/invoke-child.ts +2 -2
  96. package/dist/generated/verb-synopsis.d.ts +0 -34
  97. package/dist/pause/__tests__/errors.test.d.ts +0 -1
  98. package/dist/pause/__tests__/wrappers.test.d.ts +0 -1
  99. package/dist/step-invocation/__tests__/protocol.test.d.ts +0 -1
  100. package/src/generated/verb-synopsis.ts +0 -544
@@ -102,6 +102,18 @@ export interface RegisterWorkflowInput {
102
102
  factoryDrive?: "required" | "none";
103
103
  /** Factory slug. Defaults to `"default"`. */
104
104
  factorySlug?: string;
105
+ /** Register INTO a project (`agentc register --project`, ADR-0087): the
106
+ * workflow, its schedules and its runs are visible to the project's
107
+ * members alone, read live, so leaving the project loses them — the
108
+ * registrant included (the workflow belongs to the project, with no
109
+ * personal owner). Any member with a write or owner seat in the project
110
+ * may register into it on a key that holds `invoke`; the `manage` scope
111
+ * is not needed. A read seat is refused (403 `role_read_only`); a
112
+ * project the registrant cannot see answers 404 exactly as one that does
113
+ * not exist. Omitted, a registration from a terminal or an API key is
114
+ * the whole space's and takes `manage` (403 `manage_required`
115
+ * otherwise). */
116
+ projectId?: string;
105
117
  }
106
118
 
107
119
  export interface TemplateRow {
@@ -138,9 +150,10 @@ export interface ListTemplatesOptions {
138
150
  factorySlug?: string;
139
151
  }
140
152
 
141
- /** One team connector grant, as `GET /api/v1/connectors` returns it (wire
142
- * shape verbatim — snake_case). The typed subset the SDK pins; additional
143
- * fields flow through untyped. */
153
+ /** One connector grant, as `GET /api/v1/connectors` returns it (wire shape
154
+ * verbatim — snake_case): the caller's own grants and the workspace's
155
+ * GitHub installations, never a teammate's other accounts. The typed
156
+ * subset the SDK pins; additional fields flow through untyped. */
144
157
  export interface ConnectorGrantSummary {
145
158
  id: string;
146
159
  provider: string;
@@ -331,6 +344,8 @@ export interface UpdateFactoryInput {
331
344
 
332
345
  export interface ScheduleRow {
333
346
  id: string;
347
+ /** The factory the schedule (and the workflow it fires) lives in. */
348
+ factoryId: string;
334
349
  name: string;
335
350
  workflowName: string;
336
351
  cron: string;
@@ -396,11 +411,34 @@ export type SessionSecretInput =
396
411
  * single-use page a session writer opens to provide the named values. */
397
412
  export interface SessionSecretRequestCreated {
398
413
  requestId: string;
399
- url: string;
414
+ /** The single-use link, the owner's to tap. Null on a DEDUPED answer: the
415
+ * open ask's link already went out and nothing new exists to send. */
416
+ url: string | null;
400
417
  expiresAt: string;
401
- /** True = a standing auto-approve set already filled the request at the
402
- * mint — nothing is pending; source the session env now. */
418
+ /** True = a standing auto-approve set or this work's grant already filled
419
+ * the request at the mint — nothing is pending. */
403
420
  autoApproved?: boolean;
421
+ /** With autoApproved: true = the values are on the session's RUNNING
422
+ * machine now (its env file, and its egress ruleset for a brokered key),
423
+ * so re-sourcing the session env loads them; false = no machine is
424
+ * running or the push failed, and they land when the next turn starts. */
425
+ loaded?: boolean;
426
+ /** Set with autoApproved when the grant came from this work's earlier
427
+ * worker (the same person's grant, carried with no card): who gave it. */
428
+ carried?: { grantedByName: string | null };
429
+ /** True = an OPEN request of the same kind (or the same key set) already
430
+ * stood on this session, and this answer IS that request: no new row, no
431
+ * new card, nobody asked again. `keys` are the open request's key names,
432
+ * the names the values land under; `note` says what to do. */
433
+ deduped?: boolean;
434
+ keys?: string[];
435
+ kind?: VaultRequestKind | null;
436
+ note?: string;
437
+ /** Who holds the ask: the thread's agent (it raises the card once), the
438
+ * owner's assistant, or nobody (no owner, no agent). */
439
+ routedTo?: "agent" | "owner" | null;
440
+ /** A chat's worker: its card is in the chat, where the work was asked for. */
441
+ card?: "in the chat";
404
442
  }
405
443
 
406
444
  /** What KIND of credential a vault request asks for (advisory): the vault
@@ -408,9 +446,32 @@ export interface SessionSecretRequestCreated {
408
446
  export type VaultRequestKind =
409
447
  | "login" | "password" | "api_key" | "payment_card" | "env_file" | "note" | "other";
410
448
 
449
+ /** How one requested key is delivered to the session: BROKERED — the egress
450
+ * edge adds `headerName: [scheme ]<value>` on requests to `host`, and the
451
+ * value never enters the machine; INJECTED — the value lands in the session
452
+ * env file. An `api_key` request declares one per key. */
453
+ export type VaultKeyShape =
454
+ | { mode: "brokered"; host: string; headerName: string; scheme?: string }
455
+ | { mode: "injected" };
456
+
457
+ /** The STANDING entry a request is for, named by its label and/or (a card)
458
+ * its last four digits — at least one. The card and the vault page lead
459
+ * with that entry, so the person sees which saved credential is meant. */
460
+ export interface RequestedVaultEntry {
461
+ label?: string;
462
+ last4?: string;
463
+ }
464
+
465
+ /** A saved payment card's receipts: its network (from the number's issuer
466
+ * prefix; null when unknown) and its last four. Never more. */
467
+ export interface VaultCardHint {
468
+ brand: string | null;
469
+ last4: string;
470
+ }
471
+
411
472
  /** One STANDING vault entry usable for a session (the catalog read,
412
- * 2026-08-31) — labels, kinds, and field NAMES only; values are write-only
413
- * and never on this wire. */
473
+ * 2026-08-31) — labels, kinds, identifiers and field NAMES only; values
474
+ * are write-only and never on this wire. */
414
475
  export interface VaultCatalogEntry {
415
476
  label: string;
416
477
  kind: string;
@@ -419,6 +480,12 @@ export interface VaultCatalogEntry {
419
480
  ownerName: string | null;
420
481
  projectName: string | null;
421
482
  lastUsedAt: string | null;
483
+ /** The account the entry belongs to, or a card's network and last four
484
+ * ("Amex •••• 1002") — how look-alike labels are told apart. Absent on
485
+ * an older server. */
486
+ identifier?: string | null;
487
+ /** The card's receipts when the entry is a card; null otherwise. */
488
+ card?: VaultCardHint | null;
422
489
  }
423
490
 
424
491
  /** A vault link's polled status. */
@@ -429,7 +496,7 @@ export interface SessionSecretRequestStatus {
429
496
  expiresAt: string;
430
497
  /** Cancellation attribution (task #111) — present on cancelled rows:
431
498
  * who ended the ask ('human' = denied; 'agent' = withdrawn) and why. */
432
- cancelledVia?: "human" | "agent";
499
+ cancelledVia?: "human" | "agent" | "thread_agent";
433
500
  cancelReason?: string | null;
434
501
  /** The denying human's display name (best-effort; 'human' via only). */
435
502
  deniedByName?: string | null;
@@ -444,6 +511,8 @@ export interface SessionSecretRequestStatus {
444
511
  export interface SessionSecretRequestSummary {
445
512
  requestId: string;
446
513
  keys: string[];
514
+ /** What kind of credential the ask is for; null when the requester did not say. */
515
+ kind: VaultRequestKind | null;
447
516
  reason: string | null;
448
517
  expiresAt: string;
449
518
  createdAt: string;
@@ -451,6 +520,10 @@ export interface SessionSecretRequestSummary {
451
520
 
452
521
  export interface CreateApiKeyInput {
453
522
  name?: string;
523
+ /** Omitted → `read` alone (the read-only default). Anything more —
524
+ * `invoke`, `manage`, `admin` — is chosen explicitly, and the server
525
+ * refuses any scope the minter does not hold (403 `scope_not_held`).
526
+ * `events:read` / `events:write` are deprecated but still accepted. */
454
527
  scopes?: string[];
455
528
  expiresAt?: string;
456
529
  factorySlug?: string;
@@ -523,6 +596,9 @@ export interface UsageResponse {
523
596
  * app-level (the platform GitHub App's own webhook) or polling — there
524
597
  * is no per-repo webhook setup (retired 2026-08-18). */
525
598
  export interface DriveRepoLink {
599
+ /** Last successful API head read; a deferred attempt does not advance it. */
600
+ lastHeadObservedAt: string | null;
601
+ headCheckDeferredUntil: string | null;
526
602
  id: string;
527
603
  factoryId: string;
528
604
  /** Linked directory prefix under the drive root (relative, no slashes
@@ -556,7 +632,16 @@ export interface DriveRepoLink {
556
632
  autoLinkSkips: Array<{ branch: string; reason: string; at: string }>;
557
633
  lastSyncedGitSha: string | null;
558
634
  lastSyncedAcgCommit: string | null;
559
- syncState: "idle" | "syncing" | "pushing" | "error" | "conflict" | "diverged" | "reauth_required";
635
+ /** 'queued': the branch's first sync (or a full re-ingest) is waiting in
636
+ * the ingest queue; `ingestPriority` / `ingestQueueAhead` say where. */
637
+ syncState: "idle" | "queued" | "syncing" | "pushing" | "error" | "conflict" | "diverged" | "reauth_required";
638
+ /** Drain tier while queued (0 foreground, 1 active, 2 background). */
639
+ ingestPriority: number | null;
640
+ ingestQueuedAt: string | null;
641
+ /** When a runner started this branch's ingest; null when none runs. */
642
+ ingestClaimedAt: string | null;
643
+ /** Queued branches of the same repo ahead of this one (list only). */
644
+ ingestQueueAhead?: number | null;
560
645
  /** Human-readable detail when syncState is 'error' or 'conflict'. */
561
646
  syncError: string | null;
562
647
  createdBy: string | null;
@@ -32,6 +32,23 @@ export interface Project {
32
32
  role: ProjectRole;
33
33
  memberCount: number;
34
34
  objectCount: number;
35
+ /** The overview's words and dates (owner 2026-10-05): a short
36
+ * description, and the start and target dates as calendar days
37
+ * (`YYYY-MM-DD`); null until someone sets them. */
38
+ description: string | null;
39
+ startDate: string | null;
40
+ targetDate: string | null;
41
+ }
42
+
43
+ /** What `updateProject` may set: each field absent = untouched, null =
44
+ * cleared. `name` and the details need `write`; `visibility` needs a
45
+ * project owner or a team admin. */
46
+ export interface ProjectUpdate {
47
+ name?: string;
48
+ visibility?: "public" | "private";
49
+ description?: string | null;
50
+ startDate?: string | null;
51
+ targetDate?: string | null;
35
52
  }
36
53
 
37
54
  /** A page of projects, newest-activity first. Cursor-paginated: pass the
@@ -93,6 +110,16 @@ export interface ProjectObjectsPage {
93
110
  next_cursor: string | null;
94
111
  }
95
112
 
113
+ /** How many objects of each kind a project holds for the caller — the
114
+ * whole project's counts under the list's own visibility, never a
115
+ * page's. `GET /projects/:id/objects/counts`. */
116
+ export interface ProjectObjectCounts {
117
+ file: number;
118
+ conversation: number;
119
+ session: number;
120
+ folder: number;
121
+ }
122
+
96
123
  /** One file the caller could NOT contribute to a project when adding a
97
124
  * session — only files the caller OWNS are materialized. `path` is present
98
125
  * only when visible to the caller; paths concealed from the caller are
@@ -138,3 +165,453 @@ export interface RefreshProjectObjectResult {
138
165
  objects: ProjectObject[];
139
166
  skipped: ProjectSkippedFile[];
140
167
  }
168
+
169
+ // ── The project status view (owner 2026-09-27) ─────────────────────────────
170
+
171
+ /** A person on a project status row, identity merged server-side. `email`
172
+ * rides only while they share the workspace with the viewer: the
173
+ * dashboard's label rule (name, else email, else "Teammate") picks. */
174
+ export interface ProjectStatusPerson {
175
+ id: string;
176
+ name: string | null;
177
+ email: string | null;
178
+ image: string | null;
179
+ avatarUrl: string | null;
180
+ }
181
+
182
+ /** Whom a thread waits on (owner 2026-10-01: status "is multiplayer, could
183
+ * be needs Chris or Westan"; it names people). A plan task that waits on a
184
+ * person, as the thread agent wrote it (a teammate, or someone outside by
185
+ * name), or a worker's question owed an answer, which waits on the person
186
+ * who asked for the work (the thread's opening message), else the last
187
+ * person to speak on the thread. */
188
+ export interface ProjectStatusWaitingOn {
189
+ /** The teammate, when known. */
190
+ person: ProjectStatusPerson | null;
191
+ /** How the thread names them: the teammate's name, "Dana's IT team", or
192
+ * "an answer" when a worker's question has nobody to wait on yet. */
193
+ who: string;
194
+ /** What they will do or answer, in the people's terms. */
195
+ what: string | null;
196
+ /** `task`: a plan task waiting on a person; `question`: a worker's
197
+ * question owed an answer; `answer`: the thread's own question to a
198
+ * person (its raise declared whom it waits on), open until one of them
199
+ * answers where Ivy spoke. */
200
+ kind: "task" | "question" | "answer";
201
+ /** The chat message where the person said it, when the thread agent
202
+ * recorded one, or Ivy's message carrying the thread's question: the way
203
+ * to the ask. Null otherwise. */
204
+ messageId: string | null;
205
+ /** An answer wait's place: the chat thread Ivy spoke in (null for the
206
+ * room's top level). Absent on the other kinds. */
207
+ threadRootId?: string | null;
208
+ /** When the thread asked (ISO). Absent on the other kinds. */
209
+ at?: string | null;
210
+ }
211
+
212
+ /** Whom a TASK waits on (owner 2026-10-05: a todo may wait on more than one
213
+ * person's answer or sign-off): every teammate it names, as people, and
214
+ * the words the thread agent (or the person who set it) wrote for whoever
215
+ * they are. */
216
+ export interface ThreadStatusWaitingOn {
217
+ /** The teammates, the one whose act comes first first; empty for a wait
218
+ * on someone the workspace cannot name ("Dana's IT team"). */
219
+ people: ProjectStatusPerson[];
220
+ /** How the thread names them. */
221
+ who: string;
222
+ /** What they will do or answer, in the people's terms; null when a person
223
+ * set the wait without saying for what. */
224
+ what: string | null;
225
+ /** The chat message where the person said it, when one was recorded. */
226
+ messageId: string | null;
227
+ }
228
+
229
+ /** Who a todo is assigned to (owner 2026-10-05, like an issue's assignee):
230
+ * Ivy, or one person. Null on the todo = nobody, after a person took the
231
+ * assignee off. */
232
+ export type ThreadStatusAssignee = { kind: "ivy" } | ({ kind: "person" } & ProjectStatusPerson);
233
+
234
+ /** One plan task of a thread, as the thread agent's plan view reads it. */
235
+ export interface ProjectStatusTask {
236
+ id: string;
237
+ objective: string;
238
+ status: "open" | "done";
239
+ dueAt: string | null;
240
+ overdue: boolean;
241
+ /** Open and waiting on a task that is not done. */
242
+ blocked: boolean;
243
+ criteria: { met: number; total: number };
244
+ /** Open and waiting on a person. */
245
+ waitingOn: ThreadStatusWaitingOn | null;
246
+ assignee: ThreadStatusAssignee | null;
247
+ }
248
+
249
+ /** What a thread's workers are doing, from platform rows: `waiting` a
250
+ * worker's question is owed an answer; `working` a worker has a live turn;
251
+ * `idle` workers exist and none is running; `done` the thread is closed;
252
+ * `none` no worker was started. */
253
+ export type ProjectStatusWorker = "working" | "waiting" | "idle" | "done" | "none";
254
+
255
+ /** One outcome thread born in one of the project's chats. */
256
+ export interface ProjectStatusThread {
257
+ id: string;
258
+ title: string;
259
+ state: "open" | "waiting" | "snoozed" | "closed";
260
+ note: string | null;
261
+ /** The chat the thread was born in, and whether it is public or private. */
262
+ conversation: { id: string; title: string | null; access: "public" | "private" };
263
+ /** The chat message the thread opened on, for a deep link; null if none. */
264
+ openingMessageId: string | null;
265
+ /** Who holds the next move: a person while the thread waits on people,
266
+ * else Ivy. */
267
+ owner: { kind: "ivy" } | ({ kind: "person" } & ProjectStatusPerson);
268
+ /** The people who spoke on the thread, most recent first. */
269
+ people: ProjectStatusPerson[];
270
+ /** The now line: what its running worker is doing (`worker`), else the
271
+ * thread agent's note, its own reading of where things stand (`note`);
272
+ * null when there is neither. `at` is when the text was set (ISO). */
273
+ now: { kind: "worker" | "note"; text: string; at: string | null } | null;
274
+ /** `running`: how many of its workers have a live turn. `line`: while
275
+ * `status` is working, the one-line status its running worker set most
276
+ * recently ("Reading the Fly logs") and when (ISO); null otherwise. */
277
+ worker: { status: ProjectStatusWorker; sessions: number; running: number; line: { text: string; at: string } | null };
278
+ /** Whom the thread waits on, named; empty when it waits on nobody. */
279
+ waitingOn: ProjectStatusWaitingOn[];
280
+ /** The next open task deadline, else the thread agent's check-in. */
281
+ next: { at: string; what: string; kind: "deadline" | "check_in" } | null;
282
+ progress: { done: number; total: number };
283
+ tasks: ProjectStatusTask[];
284
+ overdue: number;
285
+ blocked: number;
286
+ unmetCriteria: number;
287
+ lastMovementAt: string;
288
+ closedAt: string | null;
289
+ }
290
+
291
+ /** What moved: a task done, a thread opened or closed, a result or decision
292
+ * the thread agent raised to the people, a worker's declared result, or a
293
+ * person's answer (`by` names them). */
294
+ export type ProjectStatusActivityKind = "task_done" | "thread_closed" | "thread_opened" | "result" | "decision" | "answer";
295
+
296
+ export interface ProjectStatusActivity {
297
+ at: string;
298
+ kind: ProjectStatusActivityKind;
299
+ threadId: string;
300
+ threadTitle: string;
301
+ text: string;
302
+ by: ProjectStatusPerson | null;
303
+ }
304
+
305
+ /** How many live threads wait on one person (or one named outsider). */
306
+ export interface ProjectStatusWaitingCount {
307
+ person: ProjectStatusPerson | null;
308
+ who: string;
309
+ threads: number;
310
+ }
311
+
312
+ /** The header's counts: "3 in progress, waiting on Chris (2) and Westan (1),
313
+ * 4 done this week". */
314
+ export interface ProjectStatusCounts {
315
+ /** Live threads (open, waiting or snoozed). */
316
+ inProgress: number;
317
+ /** Threads closed in the last seven days. */
318
+ doneThisWeek: number;
319
+ waitingOn: ProjectStatusWaitingCount[];
320
+ }
321
+
322
+ /** GET /projects/:id/status. `threads` (the live ones) is cursor-paginated
323
+ * by last movement; `recentlyClosed` rides the first page only (empty on
324
+ * later pages). The project facts (`sources`, `progress`, `dueAt`,
325
+ * `nextCheckIn`, `activity`) span every thread the viewer may read, on
326
+ * every page. */
327
+ export interface ProjectStatus {
328
+ project: { id: string; name: string; visibility: "public" | "private"; role: ProjectRole };
329
+ /** The chats in the project, where its threads are born. */
330
+ sources: number;
331
+ progress: { done: number; total: number };
332
+ /** The latest deadline of an open task: the project's finish line. */
333
+ dueAt: string | null;
334
+ nextCheckIn: { at: string; threadId: string; threadTitle: string; conversationId: string; with: ProjectStatusPerson | null } | null;
335
+ counts: ProjectStatusCounts;
336
+ threads: ProjectStatusThread[];
337
+ /** Closed in the last seven days. */
338
+ recentlyClosed: ProjectStatusThread[];
339
+ activity: ProjectStatusActivity[];
340
+ generatedAt: string;
341
+ next_cursor: string | null;
342
+ }
343
+
344
+ // ── One thread's status (GET /threads/:id/status) and one chat's work
345
+ // (GET /conversations/:id/work) ───────────────────────────────────────────
346
+
347
+ /** A worker's state as the chat's chips say it: working (a live turn, or
348
+ * parked on a question), starting (nothing produced yet), done, failed,
349
+ * stopped. */
350
+ export type ThreadStatusWorkerState = "starting" | "working" | "done" | "failed" | "stopped";
351
+
352
+ /** The effort a worker was started with; null = the harness's own default. */
353
+ export type ThreadStatusWorkerEffort = "low" | "medium" | "high" | "xhigh" | "max";
354
+
355
+ /** What pays for a worker's inference, as its hover card says it (owner
356
+ * 2026-10-05: a findable detail, never a mark on rows): a person's connected
357
+ * plan (the person by id and name only, when the turn named them), platform
358
+ * credits, or a connected provider key by its provider family. */
359
+ export type WorkerFunding =
360
+ | { lane: "subscription"; owner: { id: string; name: string | null } | null }
361
+ | { lane: "platform" }
362
+ | { lane: "byok"; provider: string };
363
+
364
+ export interface ThreadStatusWorker {
365
+ /** The worker's session (its page is /conversations/:id). */
366
+ conversationId: string;
367
+ title: string | null;
368
+ state: ThreadStatusWorkerState;
369
+ /** Its one-line status while working; null otherwise. */
370
+ line: { text: string; at: string } | null;
371
+ startedAt: string;
372
+ /** When a settled worker last spoke, or its session ended. */
373
+ finishedAt: string | null;
374
+ /** The result it declared, when it did. */
375
+ result: string | null;
376
+ /** The question it is parked on, while one is owed. */
377
+ asking: string | null;
378
+ /** The model stamped at birth (what it was CONFIGURED to run); null =
379
+ * the runtime's own default. */
380
+ model: string | null;
381
+ /** The models its turns ACTUALLY ran (owner 2026-10-06: "what model
382
+ * actually did the work"), each once in first-seen order — a mid-turn
383
+ * switch lists both: the harness's own report (its stream; codex's
384
+ * rollout file read at the turn's end), then the metering gateway's
385
+ * attested record of the model each platform-funded call went to. Never
386
+ * derived from `model`: empty when nothing is on record, and the
387
+ * surfaces then say the model isn't known. Absent on older servers. */
388
+ ranModels?: string[];
389
+ effort: ThreadStatusWorkerEffort | null;
390
+ /** The harness it runs ("claude-code", "codex", ...); null when unknown. */
391
+ runtime: string | null;
392
+ /** Where it runs: a cloud sandbox or the person's machine; null when
393
+ * unknown. */
394
+ executor: "local" | "cloud" | null;
395
+ /** When its running turn started (ISO); null while no turn runs. */
396
+ workingSince: string | null;
397
+ /** When its newest turn ended (ISO): the moment it stopped, while no turn
398
+ * runs. Null while a turn runs, and for a worker that has not run one. */
399
+ lastTurnEndedAt: string | null;
400
+ /** Its machine has a live desktop to show (the Desktop pill's own
401
+ * verdict, for the session's current machine): the chat draws a door to
402
+ * it on the step the worker is on. False for a local session, a machine
403
+ * with no GUI, or a session no longer on a machine. */
404
+ desktopCapable: boolean;
405
+ /** What pays for it, when the record says (see WorkerFunding); null when
406
+ * nothing is on record. Absent on older servers. */
407
+ funding?: WorkerFunding | null;
408
+ }
409
+
410
+ /** A message that directed a step: who wrote it and the opening of what
411
+ * they said, where it sits (its chat and, for a reply, the root of its
412
+ * chat thread; null for the chat's top level) and when. */
413
+ export interface ThreadStatusDirector {
414
+ messageId: string;
415
+ conversationId: string;
416
+ threadRootId: string | null;
417
+ /** The person who wrote it, as the status wire names people; null for a
418
+ * message Ivy wrote. */
419
+ author: ProjectStatusPerson | null;
420
+ /** The opening of what they said, in plain words (links read as their
421
+ * labels), capped; null when the message shows no words. */
422
+ excerpt: string | null;
423
+ /** When it was said (ISO). */
424
+ at: string;
425
+ }
426
+
427
+ /** Something a worker produced, filed under the step it was on: a desktop
428
+ * screenshot or a capture pushed with a need (`path`, a drive file), or
429
+ * its declared result (`text`, its words). */
430
+ export interface ThreadStatusOutput {
431
+ id: string;
432
+ kind: "screenshot" | "capture" | "result";
433
+ /** When it landed (ISO). */
434
+ at: string;
435
+ /** The drive path of a picture; null for words. */
436
+ path: string | null;
437
+ /** The words: a result's own, a picture's summary. */
438
+ text: string;
439
+ }
440
+
441
+ /** What a person did on a todo in the chat's todo view (owner 2026-10-05):
442
+ * a comment, or an edit of its assignee, details, status or whom it waits
443
+ * on. The todo's Activity, with the platform's own history. */
444
+ export interface ThreadStatusTodoActivity {
445
+ id: string;
446
+ /** When (ISO). */
447
+ at: string;
448
+ kind: "comment" | "assignee" | "description" | "status" | "waiting_on";
449
+ /** Who did it; null for a person the workspace cannot name. */
450
+ by: ProjectStatusPerson | null;
451
+ /** A comment's words, or the details written; null otherwise. */
452
+ text: string | null;
453
+ /** The assignee set (`kind` assignee); null for nobody. */
454
+ assignee: ThreadStatusAssignee | null;
455
+ /** The status set (`kind` status). */
456
+ status: "open" | "in_progress" | "waiting" | "done" | null;
457
+ /** The people set (`kind` waiting_on). */
458
+ waitingOn: ProjectStatusPerson[];
459
+ }
460
+
461
+ /** One step of the thread's plan. */
462
+ export interface ThreadStatusTodo {
463
+ id: string;
464
+ objective: string;
465
+ status: "open" | "done";
466
+ dueAt: string | null;
467
+ overdue: boolean;
468
+ /** Open and waiting on a step that is not done. */
469
+ blocked: boolean;
470
+ /** The steps it waits on, by their objective. */
471
+ blockedOn: string[];
472
+ /** Open and waiting on people. */
473
+ waitingOn: ThreadStatusWaitingOn | null;
474
+ /** Who it is assigned to: Ivy, one person, or nobody (owner 2026-10-05).
475
+ * Stored on the server; the plan's default is the first person it waits
476
+ * on, else Ivy, and a person's choice stands over the plan's. */
477
+ assignee: ThreadStatusAssignee | null;
478
+ /** The details a person wrote on it, plain text with line breaks; null
479
+ * when none. */
480
+ description: string | null;
481
+ /** What people did on it (edits and comments), oldest first, bounded. */
482
+ activity: ThreadStatusTodoActivity[];
483
+ /** Its done criteria, as counts. */
484
+ criteria: { met: number; total: number };
485
+ /** The thread's worker on this step (the thread agent's stamp), or null. */
486
+ workerConversationId: string | null;
487
+ /** Done, and completed with this note: what it produced, in one line. */
488
+ result: string | null;
489
+ /** When a worker was first put on the step (ISO): its own clock in the
490
+ * chat, never a worker's turn. Null until then. */
491
+ startedAt: string | null;
492
+ /** Done at this moment (ISO): when its check-off lands in the chat. Null
493
+ * while open. */
494
+ completedAt: string | null;
495
+ /** The messages that directed the step (the request or follow-up that
496
+ * added it, then every steer, from another chat thread too), oldest
497
+ * first. Empty = the job's request alone. The step's status shows in the
498
+ * chat thread of each. */
499
+ directedBy: ThreadStatusDirector[];
500
+ /** What the worker produced for the step, newest first (bounded): its
501
+ * screenshots and captures, and its declared result. */
502
+ outputs: ThreadStatusOutput[];
503
+ }
504
+
505
+ /** One of the last things that happened on the thread: a worker's stream
506
+ * event (its type), or the thread's record ("said" for a person's words,
507
+ * "raised" for what the agent put to the people, "worker_result", ...). */
508
+ export interface ThreadStatusEvent {
509
+ at: string;
510
+ kind: string;
511
+ text: string;
512
+ by: ProjectStatusPerson | null;
513
+ }
514
+
515
+ /** One thread's work, as the card under the message that started it reads
516
+ * it: the thread, the root it opened on, the note, its workers and its
517
+ * steps. */
518
+ export interface ChatWorkThread {
519
+ thread: ProjectStatusThread;
520
+ /** The message the thread opened on, in this chat: the job's request —
521
+ * who asked for it, their words, where it sits (the job's started mark
522
+ * shows there) and when. Null when the thread opened on no message here
523
+ * (the chat shows no card for it, but its workers still count in the
524
+ * chat's working status), or the viewer cannot see it. */
525
+ request: ThreadStatusDirector | null;
526
+ /** The job's latest place: the newest agent-authored message after the
527
+ * request in the request's own chat thread (the thread under a room
528
+ * request, or the reply thread the request sits in), else the room's
529
+ * newest after it for a room request answered inline. "Jump to latest"
530
+ * lands here. Null while nothing has answered; the request is then its
531
+ * place. */
532
+ latestMessageId: string | null;
533
+ /** The chat thread that latest message sits in (null for the room's top
534
+ * level); null with no latest message. */
535
+ latestThreadRootId: string | null;
536
+ /** The thread agent's rolling note: where things stand, in its words. */
537
+ summary: string | null;
538
+ /** When the thread was opened (ISO): the job's start, the beginning of
539
+ * its total time. */
540
+ openedAt: string;
541
+ /** Working first, then starting, then what settled (newest first). */
542
+ workers: ThreadStatusWorker[];
543
+ /** The plan's steps, in plan order (oldest first). */
544
+ todos: ThreadStatusTodo[];
545
+ }
546
+
547
+ export interface ThreadStatus extends ChatWorkThread {
548
+ events: ThreadStatusEvent[];
549
+ generatedAt: string;
550
+ }
551
+
552
+ /** GET /conversations/:id/work: the chat's threads, most recently moved
553
+ * first and bounded (the server's CHAT_WORK_THREADS), each with its work.
554
+ * 404 for a chat the viewer cannot read. */
555
+ export interface ChatWork {
556
+ threads: ChatWorkThread[];
557
+ /** The project the chat is filed in, as the viewer may see it; null for
558
+ * a chat in none. A todo's Properties name it. */
559
+ project: { id: string; name: string } | null;
560
+ generatedAt: string;
561
+ }
562
+
563
+ /** PATCH /conversations/:id/todos/:todoId (owner 2026-10-05: "change the
564
+ * assignee", "write details in here myself"): what a person who can read
565
+ * the chat may change on a todo. Each field is optional; one call may
566
+ * carry several. Answers the chat's whole work read (ChatWork), so the
567
+ * surfaces redraw from one truth. */
568
+ export interface ChatTodoPatch {
569
+ /** The one assignee: Ivy, a person who can read the chat, or null for
570
+ * nobody. */
571
+ assignee?: { kind: "ivy" } | { kind: "person"; userId: string } | null;
572
+ /** The details, plain text with line breaks; null or "" clears. */
573
+ description?: string | null;
574
+ /** Where it stands, in the plan's own states: open (not started), in
575
+ * progress, waiting (on the people given, or the ones it already waits
576
+ * on), done. A done todo stays done. */
577
+ status?: "open" | "in_progress" | "waiting" | "done";
578
+ /** Whom it waits on: people who can read the chat (several allowed), and
579
+ * for what; [] or null clears the wait. */
580
+ waitingOn?: { userIds: string[]; what?: string | null } | null;
581
+ }
582
+
583
+ /** POST /conversations/:id/todos/:todoId/comments: a comment on the todo,
584
+ * which lands in its Activity and reaches the thread's agent. Answers the
585
+ * chat's whole work read. */
586
+ export interface ChatTodoComment {
587
+ text: string;
588
+ }
589
+
590
+ // ── The personal status (GET /me/status) ───────────────────────────────────
591
+
592
+ export interface MeStatusCounts {
593
+ waitingOnYou: number;
594
+ inProgress: number;
595
+ /** The viewer's projects with a relevant live thread. */
596
+ projects: number;
597
+ doneThisWeek: number;
598
+ }
599
+
600
+ /** GET /me/status, for the current workspace: the threads relevant to the
601
+ * viewer (they posted, were mentioned, asked, have a to-do, or are in the
602
+ * chat), never the whole workspace. `threads` is cursor-paginated by last
603
+ * movement; `waitingOnYou`, `recentlyClosed` and `activity` ride the first
604
+ * page. The client groups by `chatProjects` (the viewer's project for each
605
+ * chat; a chat absent there is in none of theirs). */
606
+ export interface MeStatus {
607
+ counts: MeStatusCounts;
608
+ /** Live threads a task of which waits on the viewer, or whose worker's
609
+ * question is owed and the viewer asked for the work (else spoke last). */
610
+ waitingOnYou: ProjectStatusThread[];
611
+ threads: ProjectStatusThread[];
612
+ recentlyClosed: ProjectStatusThread[];
613
+ chatProjects: Array<{ conversationId: string; project: { id: string; name: string } }>;
614
+ activity: Array<ProjectStatusActivity & { conversationId: string | null }>;
615
+ generatedAt: string;
616
+ next_cursor: string | null;
617
+ }