@camox/api-contract 0.74.1 → 0.76.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.
package/dist/threads.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DraftOutcome } from "./drafts.js";
2
2
  import { z } from "zod";
3
- import { AgentEvent } from "@earendil-works/pi-durable";
4
3
  import { Message } from "@earendil-works/pi-ai";
4
+ import { AgentEvent } from "@earendil-works/pi-durable";
5
5
  //#region src/threads.d.ts
6
6
  /**
7
7
  * The kind of the transcript entry recording an editor's message, with its sender and the
@@ -69,6 +69,14 @@ export type PushOutcome = z.infer<typeof pushOutcomeSchema>;
69
69
  * call of it went through ends on the draft's card.
70
70
  */
71
71
  export declare const START_DRAFT_TOOL = "startDraft";
72
+ /**
73
+ * The agent on Live's tool restoring an archived draft and handing the editor to it, as
74
+ * `startDraft` does (ADR 0025, ADR 0046). A run whose call of it went through ends on the
75
+ * draft's card.
76
+ */
77
+ export declare const RESTORE_DRAFT_TOOL = "restoreDraft";
78
+ /** Whether a call of the tool `name` hands the editor off to a draft, which ends the run on its card. */
79
+ export declare const handsOffToDraft: (name: string) => name is "restoreDraft" | "startDraft";
72
80
  /** The agent's tool building its code changes mid-turn, which the end of a turn also does. */
73
81
  export declare const BUILD_TOOL = "build";
74
82
  /** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
@@ -140,21 +148,27 @@ export declare const handsOff: (name: string) => boolean;
140
148
  * keeps this in step with the tools themselves.
141
149
  */
142
150
  export declare const CHANGING_TOOLS: ReadonlySet<string>;
143
- /** The draft a `startDraft` call started, as its result's details name it for the card. */
151
+ /**
152
+ * The draft a `startDraft` call started, or a `restoreDraft` call restored, as its result's
153
+ * details name it for the card.
154
+ */
144
155
  export declare const startedDraftCardSchema: z.ZodObject<{
145
156
  draftId: z.ZodString;
146
157
  name: z.ZodString;
158
+ restored: z.ZodOptional<z.ZodLiteral<true>>;
147
159
  }, z.core.$strip>;
148
160
  export type StartedDraftCard = z.infer<typeof startedDraftCardSchema>;
149
161
  /**
150
162
  * Where a draft a Live thread's card names stands, as the thread looks it up whenever it's shown
151
- * (ADR 0025): its current name, or its last once it ended, and how it ended. Never written into
152
- * the transcript, so the model's cached prompt keeps up. A name cleared by a rename is null: the
153
- * card keeps the name the agent gave it.
163
+ * (ADR 0025): its current name, or its last once it ended, and how it ended. An archived draft
164
+ * hasn't ended: `archived` says it's set aside until someone restores it (ADR 0046). Never written
165
+ * into the transcript, so the model's cached prompt keeps up. A name cleared by a rename is null:
166
+ * the card keeps the name the agent gave it.
154
167
  */
155
168
  export type DraftCardState = {
156
169
  name: string | null;
157
170
  ended?: DraftOutcome;
171
+ archived?: true;
158
172
  };
159
173
  /** The drafts a Live thread's cards name, by id, as the thread last looked them up. */
160
174
  export type DraftCardStates = Readonly<Record<string, DraftCardState>>;
@@ -190,6 +204,14 @@ export declare const connectedAgentClientSchema: z.ZodObject<{
190
204
  verified: z.ZodBoolean;
191
205
  }, z.core.$strip>;
192
206
  export type ConnectedAgentClient = z.infer<typeof connectedAgentClientSchema>;
207
+ /** An Attachment a Connected Agent's message carries: an image the agent sees with it. */
208
+ export declare const messageAttachmentSchema: z.ZodObject<{
209
+ id: z.ZodString;
210
+ filename: z.ZodString;
211
+ mimeType: z.ZodString;
212
+ url: z.ZodString;
213
+ }, z.core.$strip>;
214
+ export type MessageAttachmentData = z.infer<typeof messageAttachmentSchema>;
193
215
  /**
194
216
  * What a Connected Agent's message entry records: the member it acts as, its client, the
195
217
  * operation id it was sent with, and whether it answers the agent's questions (ADR 0042).
@@ -206,6 +228,12 @@ export declare const connectedAgentMessageDataSchema: z.ZodObject<{
206
228
  }, z.core.$strip>;
207
229
  operationId: z.ZodOptional<z.ZodString>;
208
230
  answer: z.ZodOptional<z.ZodBoolean>;
231
+ attachments: z.ZodOptional<z.ZodArray<z.ZodObject<{
232
+ id: z.ZodString;
233
+ filename: z.ZodString;
234
+ mimeType: z.ZodString;
235
+ url: z.ZodString;
236
+ }, z.core.$strip>>>;
209
237
  }, z.core.$strip>;
210
238
  export type ConnectedAgentMessageData = z.infer<typeof connectedAgentMessageDataSchema>;
211
239
  /** What a push's entry records: who pushed, their commits' subjects, oldest first, and the build. */
@@ -231,13 +259,14 @@ export declare const editorMessageDataSchema: z.ZodObject<{
231
259
  operationId: z.ZodOptional<z.ZodString>;
232
260
  }, z.core.$strip>;
233
261
  /**
234
- * A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
235
- * the run that started it: the studio that sent one of them enters the draft, if it still shows
262
+ * A draft the agent on Live started with `startDraft`, or restored with `restoreDraft`, and the
263
+ * operation ids of the messages of the run that did: the studio that sent one of them enters the draft, if it still shows
236
264
  * Live.
237
265
  */
238
266
  export declare const startedDraftSchema: z.ZodObject<{
239
267
  draftId: z.ZodString;
240
268
  operationIds: z.ZodArray<z.ZodString>;
269
+ restored: z.ZodOptional<z.ZodLiteral<true>>;
241
270
  }, z.core.$strip>;
242
271
  export type StartedDraft = z.infer<typeof startedDraftSchema>;
243
272
  /** How a write touched what it names. */
@@ -350,6 +379,8 @@ export type ThreadMessage = {
350
379
  client: ConnectedAgentClient;
351
380
  text: string;
352
381
  answer: boolean;
382
+ /** The images it carries for the agent to see, if any (ADR 0044). */
383
+ attachments?: MessageAttachmentData[];
353
384
  } |
354
385
  /** Camox's own message, starting a turn it began itself: why it did. */
355
386
  {
@@ -389,12 +420,14 @@ export type ThreadMessage = {
389
420
  /**
390
421
  * In place of the agent's message, for a Live turn handed off to a draft the agent started:
391
422
  * the draft's card, with its current name, which opens it. Once the draft ended, it has its
392
- * last name and how it ended, and opens nothing. The turn is finished.
423
+ * last name and how it ended, and opens nothing. While the draft is archived, `archived` is set,
424
+ * and the card offers to restore it. The turn is finished.
393
425
  */
394
426
  ({
395
427
  id: string;
396
428
  from: "draft";
397
429
  ended?: DraftOutcome;
430
+ archived?: true;
398
431
  } & StartedDraftCard) |
399
432
  /**
400
433
  * A member's push to the draft's code from their own checkout: who pushed, their commits'
@@ -502,6 +535,7 @@ type ThreadStep = {
502
535
  client: ConnectedAgentClient;
503
536
  text: string;
504
537
  answer: boolean;
538
+ attachments?: MessageAttachmentData[];
505
539
  } |
506
540
  /** Camox starting a turn of its own, saying why. */
507
541
  {
package/dist/threads.js CHANGED
@@ -66,6 +66,14 @@ const pushOutcomeSchema = z.enum([
66
66
  * call of it went through ends on the draft's card.
67
67
  */
68
68
  const START_DRAFT_TOOL = "startDraft";
69
+ /**
70
+ * The agent on Live's tool restoring an archived draft and handing the editor to it, as
71
+ * `startDraft` does (ADR 0025, ADR 0046). A run whose call of it went through ends on the
72
+ * draft's card.
73
+ */
74
+ const RESTORE_DRAFT_TOOL = "restoreDraft";
75
+ /** Whether a call of the tool `name` hands the editor off to a draft, which ends the run on its card. */
76
+ const handsOffToDraft = (name) => name === "startDraft" || name === "restoreDraft";
69
77
  /** The agent's tool building its code changes mid-turn, which the end of a turn also does. */
70
78
  const BUILD_TOOL = "build";
71
79
  /** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
@@ -138,6 +146,7 @@ const CHANGING_TOOLS = /* @__PURE__ */ new Set([
138
146
  "addBackToLocale",
139
147
  "addLocale",
140
148
  "applyFindingFix",
149
+ "copyFromPastWork",
141
150
  "createBlock",
142
151
  "createCollectionRecord",
143
152
  "createPage",
@@ -158,10 +167,15 @@ const CHANGING_TOOLS = /* @__PURE__ */ new Set([
158
167
  "rm",
159
168
  "write"
160
169
  ]);
161
- /** The draft a `startDraft` call started, as its result's details name it for the card. */
170
+ /**
171
+ * The draft a `startDraft` call started, or a `restoreDraft` call restored, as its result's
172
+ * details name it for the card.
173
+ */
162
174
  const startedDraftCardSchema = z.object({
163
175
  draftId: z.string(),
164
- name: z.string()
176
+ name: z.string(),
177
+ /** The agent restored the archived draft rather than starting it (ADR 0046). */
178
+ restored: z.literal(true).optional()
165
179
  });
166
180
  const reconcileDataSchema = z.object({ message: z.string() });
167
181
  const buildStatusSchema = z.enum(["building", "fixing"]);
@@ -182,6 +196,13 @@ const connectedAgentClientSchema = z.object({
182
196
  name: z.string(),
183
197
  verified: z.boolean()
184
198
  });
199
+ /** An Attachment a Connected Agent's message carries: an image the agent sees with it. */
200
+ const messageAttachmentSchema = z.object({
201
+ id: z.string(),
202
+ filename: z.string(),
203
+ mimeType: z.string(),
204
+ url: z.string()
205
+ });
185
206
  /**
186
207
  * What a Connected Agent's message entry records: the member it acts as, its client, the
187
208
  * operation id it was sent with, and whether it answers the agent's questions (ADR 0042).
@@ -190,7 +211,9 @@ const connectedAgentMessageDataSchema = z.object({
190
211
  member: threadSenderSchema,
191
212
  client: connectedAgentClientSchema,
192
213
  operationId: z.string().optional(),
193
- answer: z.boolean().optional()
214
+ answer: z.boolean().optional(),
215
+ /** The images the message carries for the agent to see, as studios list them (ADR 0044). */
216
+ attachments: z.array(messageAttachmentSchema).optional()
194
217
  });
195
218
  /** What a push's entry records: who pushed, their commits' subjects, oldest first, and the build. */
196
219
  const pushDataSchema = z.object({
@@ -201,13 +224,18 @@ const pushDataSchema = z.object({
201
224
  /** What an editor's message entry records: who sent it, and the operation id it was sent with. */
202
225
  const editorMessageDataSchema = threadSenderSchema.extend({ operationId: z.string().optional() });
203
226
  /**
204
- * A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
205
- * the run that started it: the studio that sent one of them enters the draft, if it still shows
227
+ * A draft the agent on Live started with `startDraft`, or restored with `restoreDraft`, and the
228
+ * operation ids of the messages of the run that did: the studio that sent one of them enters the draft, if it still shows
206
229
  * Live.
207
230
  */
208
231
  const startedDraftSchema = z.object({
209
232
  draftId: z.string(),
210
- operationIds: z.array(z.string())
233
+ operationIds: z.array(z.string()),
234
+ /**
235
+ * The agent restored the archived draft rather than starting it: its thread goes on from its
236
+ * own messages, without the run's.
237
+ */
238
+ restored: z.literal(true).optional()
211
239
  });
212
240
  /** How a write touched what it names. */
213
241
  const touchKindSchema = z.enum([
@@ -419,7 +447,8 @@ function threadStepOf(entry) {
419
447
  verified: data.client.verified
420
448
  },
421
449
  text: editorTextOf(message.content),
422
- answer: data.answer ?? false
450
+ answer: data.answer ?? false,
451
+ ...data.attachments?.length && { attachments: data.attachments }
423
452
  };
424
453
  }
425
454
  if (entry.kind === "camox.brief") return {
@@ -450,7 +479,7 @@ function threadStepOf(entry) {
450
479
  text: details.answer
451
480
  };
452
481
  }
453
- const card = message.toolName === "startDraft" && !message.isError ? startedDraftCardSchema.safeParse(message.details).data : void 0;
482
+ const card = handsOffToDraft(message.toolName) && !message.isError ? startedDraftCardSchema.safeParse(message.details).data : void 0;
454
483
  return card ? {
455
484
  id,
456
485
  type: "draft",
@@ -525,7 +554,8 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
525
554
  member: step.member,
526
555
  client: step.client,
527
556
  text: step.text,
528
- answer: step.answer
557
+ answer: step.answer,
558
+ ...step.attachments && { attachments: step.attachments }
529
559
  });
530
560
  waiting = step.id;
531
561
  break;
@@ -572,7 +602,9 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
572
602
  from: "draft",
573
603
  draftId: step.draftId,
574
604
  name: draft?.name ?? step.name,
575
- ...draft?.ended && { ended: draft.ended }
605
+ ...step.restored && { restored: true },
606
+ ...draft?.ended && { ended: draft.ended },
607
+ ...draft?.archived && { archived: true }
576
608
  });
577
609
  }
578
610
  endTurn();
@@ -629,7 +661,7 @@ function activityOf({ status, toolCalls, callingTools, changing, checking, hande
629
661
  if (status === "fixing") return "fixing";
630
662
  if (toolCalls.some((call) => handsOff(call.name))) return handedOffActivity ?? "changing";
631
663
  if (checking) return "checking";
632
- if (calling("startDraft")) return "startingDraft";
664
+ if (toolCalls.some((call) => handsOffToDraft(call.name))) return "startingDraft";
633
665
  if (changing) return "changing";
634
666
  if (callingTools || toolCalls.length > 0) return "exploring";
635
667
  return "thinking";
@@ -761,4 +793,4 @@ function reduceThreadFrame(state, frame) {
761
793
  }
762
794
 
763
795
  //#endregion
764
- export { ARTIFACT_REQUEST_ENTRY, ARTIFACT_TOOL, ASK_TOOL, BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, CODE_REQUEST_ENTRY, CODE_TOOL, CONNECTED_AGENT_MESSAGE_ENTRY, DRAFT_CARD_ENTRY, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, LOOK_TOOL, PUSH_ENTRY, QUESTIONS_ENTRY, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, agentActivitySchema, artifactRequestDataSchema, buildStatusDataSchema, buildStatusSchema, codeRequestDataSchema, connectedAgentClientSchema, connectedAgentMessageDataSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, handOffDetailsSchema, handsOff, isSender, pushDataSchema, pushOutcomeSchema, questionsDataSchema, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftCardSchema, startedDraftSchema, textOf, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
796
+ export { ARTIFACT_REQUEST_ENTRY, ARTIFACT_TOOL, ASK_TOOL, BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, CODE_REQUEST_ENTRY, CODE_TOOL, CONNECTED_AGENT_MESSAGE_ENTRY, DRAFT_CARD_ENTRY, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, LOOK_TOOL, PUSH_ENTRY, QUESTIONS_ENTRY, RECONCILE_ENTRY, RESTORE_DRAFT_TOOL, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, agentActivitySchema, artifactRequestDataSchema, buildStatusDataSchema, buildStatusSchema, codeRequestDataSchema, connectedAgentClientSchema, connectedAgentMessageDataSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, handOffDetailsSchema, handsOff, handsOffToDraft, isSender, messageAttachmentSchema, pushDataSchema, pushOutcomeSchema, questionsDataSchema, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftCardSchema, startedDraftSchema, textOf, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camox/api-contract",
3
- "version": "0.74.1",
3
+ "version": "0.76.0",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "src",
package/src/drafts.ts CHANGED
@@ -158,6 +158,22 @@ export type EndedDraft = {
158
158
  endedAt: number;
159
159
  };
160
160
 
161
+ /**
162
+ * A draft set aside without ending it (ADR 0046): who archived it, a member or Camox, and when.
163
+ * It keeps its id, changes, thread and code until it's restored or deleted.
164
+ */
165
+ export type ArchivedDraft = {
166
+ draftId: string;
167
+ archivedBy: { id: string; name: string };
168
+ archivedAt: number;
169
+ };
170
+
171
+ /**
172
+ * The oRPC error code a request naming an archived draft fails with: its `data` is an
173
+ * `ArchivedDraft`. Nothing reads or writes the draft until it's restored.
174
+ */
175
+ export const DRAFT_ARCHIVED_ERROR_CODE = "DRAFT_ARCHIVED";
176
+
161
177
  /**
162
178
  * The oRPC error code a request naming an ended draft fails with. Its `data` is an `EndedDraft`,
163
179
  * so a page can render Live and say who published or deleted the draft.
package/src/query-keys.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { EndedDraft } from "./drafts";
1
+ import type { ArchivedDraft, EndedDraft } from "./drafts";
2
2
 
3
3
  /**
4
4
  * Use TypeScript to enforce 'camox' as the first query key to ensure they're all namespaced.
@@ -58,6 +58,14 @@ export const queryKeys = {
58
58
  list: ["camox", "drafts", "list"],
59
59
  // The draft a viewer is in, scoped to it like content.
60
60
  get: ["camox", "drafts", "get"],
61
+ // Archived drafts (ADR 0046): under `all`, so whatever changes the drafts list refreshes it.
62
+ archived: ["camox", "drafts", "archived"],
63
+ },
64
+ // Publications (ADR 0045): the same for every viewer, whatever they're in.
65
+ publications: {
66
+ all: ["camox", "publications"],
67
+ list: ["camox", "publications", "list"],
68
+ get: ["camox", "publications", "get"],
61
69
  },
62
70
  // The Specialists of a scope: their reports and pending work.
63
71
  specialists: {
@@ -100,6 +108,9 @@ export type InvalidationMessage = {
100
108
  /** A draft was published or deleted: its viewers go back to Live. */
101
109
  export type DraftEndedMessage = { type: "draftEnded" } & EndedDraft;
102
110
 
111
+ /** A draft was archived: its viewers go back to Live (ADR 0046). */
112
+ export type DraftArchivedMessage = { type: "draftArchived" } & ArchivedDraft;
113
+
103
114
  /** A draft with code has a new revision, built with Camox's `sdkVersion`. */
104
115
  export type RevisionAnnouncement = { draftId: string; revisionId: string; sdkVersion: string };
105
116
 
@@ -107,4 +118,8 @@ export type RevisionAnnouncement = { draftId: string; revisionId: string; sdkVer
107
118
  export type RevisionMessage = { type: "revision" } & RevisionAnnouncement;
108
119
 
109
120
  /** Everything the project room broadcasts. */
110
- export type ProjectRoomMessage = InvalidationMessage | DraftEndedMessage | RevisionMessage;
121
+ export type ProjectRoomMessage =
122
+ | InvalidationMessage
123
+ | DraftEndedMessage
124
+ | DraftArchivedMessage
125
+ | RevisionMessage;
package/src/threads.ts CHANGED
@@ -81,6 +81,17 @@ export type PushOutcome = z.infer<typeof pushOutcomeSchema>;
81
81
  */
82
82
  export const START_DRAFT_TOOL = "startDraft";
83
83
 
84
+ /**
85
+ * The agent on Live's tool restoring an archived draft and handing the editor to it, as
86
+ * `startDraft` does (ADR 0025, ADR 0046). A run whose call of it went through ends on the
87
+ * draft's card.
88
+ */
89
+ export const RESTORE_DRAFT_TOOL = "restoreDraft";
90
+
91
+ /** Whether a call of the tool `name` hands the editor off to a draft, which ends the run on its card. */
92
+ export const handsOffToDraft = (name: string) =>
93
+ name === START_DRAFT_TOOL || name === RESTORE_DRAFT_TOOL;
94
+
84
95
  /** The agent's tool building its code changes mid-turn, which the end of a turn also does. */
85
96
  export const BUILD_TOOL = "build";
86
97
 
@@ -169,6 +180,7 @@ export const CHANGING_TOOLS: ReadonlySet<string> = new Set([
169
180
  "addBackToLocale",
170
181
  "addLocale",
171
182
  "applyFindingFix",
183
+ "copyFromPastWork",
172
184
  "createBlock",
173
185
  "createCollectionRecord",
174
186
  "createPage",
@@ -191,17 +203,26 @@ export const CHANGING_TOOLS: ReadonlySet<string> = new Set([
191
203
  "write",
192
204
  ]);
193
205
 
194
- /** The draft a `startDraft` call started, as its result's details name it for the card. */
195
- export const startedDraftCardSchema = z.object({ draftId: z.string(), name: z.string() });
206
+ /**
207
+ * The draft a `startDraft` call started, or a `restoreDraft` call restored, as its result's
208
+ * details name it for the card.
209
+ */
210
+ export const startedDraftCardSchema = z.object({
211
+ draftId: z.string(),
212
+ name: z.string(),
213
+ /** The agent restored the archived draft rather than starting it (ADR 0046). */
214
+ restored: z.literal(true).optional(),
215
+ });
196
216
  export type StartedDraftCard = z.infer<typeof startedDraftCardSchema>;
197
217
 
198
218
  /**
199
219
  * Where a draft a Live thread's card names stands, as the thread looks it up whenever it's shown
200
- * (ADR 0025): its current name, or its last once it ended, and how it ended. Never written into
201
- * the transcript, so the model's cached prompt keeps up. A name cleared by a rename is null: the
202
- * card keeps the name the agent gave it.
220
+ * (ADR 0025): its current name, or its last once it ended, and how it ended. An archived draft
221
+ * hasn't ended: `archived` says it's set aside until someone restores it (ADR 0046). Never written
222
+ * into the transcript, so the model's cached prompt keeps up. A name cleared by a rename is null:
223
+ * the card keeps the name the agent gave it.
203
224
  */
204
- export type DraftCardState = { name: string | null; ended?: DraftOutcome };
225
+ export type DraftCardState = { name: string | null; ended?: DraftOutcome; archived?: true };
205
226
 
206
227
  /** The drafts a Live thread's cards name, by id, as the thread last looked them up. */
207
228
  export type DraftCardStates = Readonly<Record<string, DraftCardState>>;
@@ -232,6 +253,15 @@ export const isSender = (data: unknown): data is ThreadSender =>
232
253
  export const connectedAgentClientSchema = z.object({ name: z.string(), verified: z.boolean() });
233
254
  export type ConnectedAgentClient = z.infer<typeof connectedAgentClientSchema>;
234
255
 
256
+ /** An Attachment a Connected Agent's message carries: an image the agent sees with it. */
257
+ export const messageAttachmentSchema = z.object({
258
+ id: z.string(),
259
+ filename: z.string(),
260
+ mimeType: z.string(),
261
+ url: z.string(),
262
+ });
263
+ export type MessageAttachmentData = z.infer<typeof messageAttachmentSchema>;
264
+
235
265
  /**
236
266
  * What a Connected Agent's message entry records: the member it acts as, its client, the
237
267
  * operation id it was sent with, and whether it answers the agent's questions (ADR 0042).
@@ -241,6 +271,8 @@ export const connectedAgentMessageDataSchema = z.object({
241
271
  client: connectedAgentClientSchema,
242
272
  operationId: z.string().optional(),
243
273
  answer: z.boolean().optional(),
274
+ /** The images the message carries for the agent to see, as studios list them (ADR 0044). */
275
+ attachments: z.array(messageAttachmentSchema).optional(),
244
276
  });
245
277
  export type ConnectedAgentMessageData = z.infer<typeof connectedAgentMessageDataSchema>;
246
278
 
@@ -258,13 +290,18 @@ export const editorMessageDataSchema = threadSenderSchema.extend({
258
290
  });
259
291
 
260
292
  /**
261
- * A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
262
- * the run that started it: the studio that sent one of them enters the draft, if it still shows
293
+ * A draft the agent on Live started with `startDraft`, or restored with `restoreDraft`, and the
294
+ * operation ids of the messages of the run that did: the studio that sent one of them enters the draft, if it still shows
263
295
  * Live.
264
296
  */
265
297
  export const startedDraftSchema = z.object({
266
298
  draftId: z.string(),
267
299
  operationIds: z.array(z.string()),
300
+ /**
301
+ * The agent restored the archived draft rather than starting it: its thread goes on from its
302
+ * own messages, without the run's.
303
+ */
304
+ restored: z.literal(true).optional(),
268
305
  });
269
306
  export type StartedDraft = z.infer<typeof startedDraftSchema>;
270
307
 
@@ -326,6 +363,8 @@ export type ThreadMessage =
326
363
  client: ConnectedAgentClient;
327
364
  text: string;
328
365
  answer: boolean;
366
+ /** The images it carries for the agent to see, if any (ADR 0044). */
367
+ attachments?: MessageAttachmentData[];
329
368
  }
330
369
  /** Camox's own message, starting a turn it began itself: why it did. */
331
370
  | { id: string; from: "camox"; text: string }
@@ -346,9 +385,10 @@ export type ThreadMessage =
346
385
  /**
347
386
  * In place of the agent's message, for a Live turn handed off to a draft the agent started:
348
387
  * the draft's card, with its current name, which opens it. Once the draft ended, it has its
349
- * last name and how it ended, and opens nothing. The turn is finished.
388
+ * last name and how it ended, and opens nothing. While the draft is archived, `archived` is set,
389
+ * and the card offers to restore it. The turn is finished.
350
390
  */
351
- | ({ id: string; from: "draft"; ended?: DraftOutcome } & StartedDraftCard)
391
+ | ({ id: string; from: "draft"; ended?: DraftOutcome; archived?: true } & StartedDraftCard)
352
392
  /**
353
393
  * A member's push to the draft's code from their own checkout: who pushed, their commits'
354
394
  * subjects, oldest first, and how the pushed head built. It's no turn: the agent doesn't
@@ -441,6 +481,7 @@ type ThreadStep = { id: string } & (
441
481
  client: ConnectedAgentClient;
442
482
  text: string;
443
483
  answer: boolean;
484
+ attachments?: MessageAttachmentData[];
444
485
  }
445
486
  /** Camox starting a turn of its own, saying why. */
446
487
  | { type: "camox"; text: string }
@@ -618,6 +659,7 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
618
659
  client: { name: data.client.name, verified: data.client.verified },
619
660
  text: editorTextOf(message.content),
620
661
  answer: data.answer ?? false,
662
+ ...(data.attachments?.length && { attachments: data.attachments }),
621
663
  }
622
664
  );
623
665
  }
@@ -644,7 +686,7 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
644
686
  if (details?.final && details.answer) return { id, type: "answer", text: details.answer };
645
687
  }
646
688
  const card =
647
- message.toolName === START_DRAFT_TOOL && !message.isError
689
+ handsOffToDraft(message.toolName) && !message.isError
648
690
  ? startedDraftCardSchema.safeParse(message.details).data
649
691
  : undefined;
650
692
  return card ? { id, type: "draft", ...card } : { id, type: "tools" };
@@ -722,6 +764,7 @@ export function threadViewOf({
722
764
  client: step.client,
723
765
  text: step.text,
724
766
  answer: step.answer,
767
+ ...(step.attachments && { attachments: step.attachments }),
725
768
  });
726
769
  waiting = step.id;
727
770
  break;
@@ -755,7 +798,9 @@ export function threadViewOf({
755
798
  from: "draft",
756
799
  draftId: step.draftId,
757
800
  name: draft?.name ?? step.name,
801
+ ...(step.restored && { restored: true as const }),
758
802
  ...(draft?.ended && { ended: draft.ended }),
803
+ ...(draft?.archived && { archived: true as const }),
759
804
  });
760
805
  }
761
806
  endTurn();
@@ -834,7 +879,7 @@ function activityOf({
834
879
  // the agent's.
835
880
  if (toolCalls.some((call) => handsOff(call.name))) return handedOffActivity ?? "changing";
836
881
  if (checking) return "checking";
837
- if (calling(START_DRAFT_TOOL)) return "startingDraft";
882
+ if (toolCalls.some((call) => handsOffToDraft(call.name))) return "startingDraft";
838
883
  if (changing) return "changing";
839
884
  if (callingTools || toolCalls.length > 0) return "exploring";
840
885
  return "thinking";