@camox/api-contract 0.65.1 → 0.66.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/router.d.ts CHANGED
@@ -2298,6 +2298,25 @@ declare const schema: {
2298
2298
  }, {}, {
2299
2299
  length: number | undefined;
2300
2300
  }>;
2301
+ overviewName: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2302
+ name: "overview_name";
2303
+ tableName: "drafts";
2304
+ dataType: "string";
2305
+ columnType: "SQLiteText";
2306
+ data: string;
2307
+ driverParam: string;
2308
+ notNull: false;
2309
+ hasDefault: false;
2310
+ isPrimaryKey: false;
2311
+ isAutoincrement: false;
2312
+ hasRuntimeDefault: false;
2313
+ enumValues: [string, ...string[]];
2314
+ baseColumn: never;
2315
+ identity: undefined;
2316
+ generated: undefined;
2317
+ }, {}, {
2318
+ length: number | undefined;
2319
+ }>;
2301
2320
  overviewBullets: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2302
2321
  name: "overview_bullets";
2303
2322
  tableName: "drafts";
@@ -10357,10 +10376,12 @@ type Overview =
10357
10376
  } |
10358
10377
  /**
10359
10378
  * Written. A `stale` one describes an older version of the draft's changes: it's still shown
10360
- * while a new one is written.
10379
+ * while a new one is written. `name` is the short name AI suggests keeping the draft under,
10380
+ * null for an overview written before overviews had one.
10361
10381
  */
10362
10382
  {
10363
10383
  status: "ready";
10384
+ name: string | null;
10364
10385
  headline: string;
10365
10386
  bullets: string[];
10366
10387
  stale: boolean;
@@ -18121,6 +18142,7 @@ declare const router: {
18121
18142
  status: "pending";
18122
18143
  } | {
18123
18144
  status: "ready";
18145
+ name: string | null;
18124
18146
  headline: string;
18125
18147
  bullets: string[];
18126
18148
  stale: boolean;
@@ -18162,6 +18184,7 @@ declare const router: {
18162
18184
  status: "pending";
18163
18185
  } | {
18164
18186
  status: "ready";
18187
+ name: string | null;
18165
18188
  headline: string;
18166
18189
  bullets: string[];
18167
18190
  stale: boolean;
package/dist/threads.d.ts CHANGED
@@ -45,6 +45,13 @@ export declare const BRIEF_ENTRY = "camox.brief";
45
45
  * it at the end of the conversation; editors never see it, since the draft's card says so.
46
46
  */
47
47
  export declare const DRAFT_ENDED_ENTRY = "camox.draft-ended";
48
+ /**
49
+ * The kind of the transcript entry a Live thread starts afresh from once its agent handed a
50
+ * request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
51
+ * data. Editors see it as the draft's card, alone, and the model reads a note naming the draft.
52
+ * Everything before it is left out of both.
53
+ */
54
+ export declare const DRAFT_CARD_ENTRY = "camox.draft-card";
48
55
  /**
49
56
  * The kind of the transcript entry recording a member's push to the draft's code from their own
50
57
  * checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
@@ -67,6 +74,23 @@ export declare const START_DRAFT_TOOL = "startDraft";
67
74
  export declare const BUILD_TOOL = "build";
68
75
  /** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
69
76
  export declare const LOOK_TOOL = "look";
77
+ /**
78
+ * The agent's tool handing an artifact's source to its Artifact Agent, which writes and changes
79
+ * it in a conversation of its own and answers the agent (ADR 0035).
80
+ */
81
+ export declare const ARTIFACT_TOOL = "artifact";
82
+ /**
83
+ * The kind of the entry recording a request the agent sent an Artifact Agent, in the Artifact
84
+ * Agent's own conversation, which editors never see: who the run acts for and the operation ids
85
+ * of its messages, as data (`artifactRequestDataSchema`).
86
+ */
87
+ export declare const ARTIFACT_REQUEST_ENTRY = "camox.artifact-request";
88
+ /** What an Artifact Agent's request records: the member the run acts for, and its messages. */
89
+ export declare const artifactRequestDataSchema: z.ZodObject<{
90
+ member: z.ZodString;
91
+ operationIds: z.ZodArray<z.ZodString>;
92
+ }, z.core.$strip>;
93
+ export type ArtifactRequestData = z.infer<typeof artifactRequestDataSchema>;
70
94
  /**
71
95
  * The tools whose calls change a draft: the content tools that write, and the code tools that
72
96
  * write files. The model calls the code tools, and code mode scripts call both. A test in the API
@@ -155,16 +179,12 @@ export declare const pushDataSchema: z.ZodObject<{
155
179
  }>;
156
180
  }, z.core.$strip>;
157
181
  export type PushData = z.infer<typeof pushDataSchema>;
158
- /**
159
- * What an editor's message entry records: who sent it, the operation id it was sent with, and for
160
- * a message sent on Live as the agent handed the request off, the draft it went to with it.
161
- */
182
+ /** What an editor's message entry records: who sent it, and the operation id it was sent with. */
162
183
  export declare const editorMessageDataSchema: z.ZodObject<{
163
184
  id: z.ZodString;
164
185
  name: z.ZodString;
165
186
  image: z.ZodNullable<z.ZodString>;
166
187
  operationId: z.ZodOptional<z.ZodString>;
167
- handedOffTo: z.ZodOptional<z.ZodString>;
168
188
  }, z.core.$strip>;
169
189
  /**
170
190
  * A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
@@ -344,7 +364,35 @@ export type ThreadMessage = {
344
364
  * - `fixing`: the end of the turn's build failed, and the agent fixes it.
345
365
  * - `startingDraft`: on Live, its `startDraft` call runs.
346
366
  */
347
- export type AgentActivity = "thinking" | "exploring" | "changing" | "building" | "checking" | "fixing" | "startingDraft";
367
+ export declare const agentActivitySchema: z.ZodEnum<{
368
+ building: "building";
369
+ changing: "changing";
370
+ checking: "checking";
371
+ exploring: "exploring";
372
+ fixing: "fixing";
373
+ startingDraft: "startingDraft";
374
+ thinking: "thinking";
375
+ }>;
376
+ export type AgentActivity = z.infer<typeof agentActivitySchema>;
377
+ /**
378
+ * What an `artifact` call's result carries for the studio: the Artifact Agent's answer, whether
379
+ * it's the agent's answer to the turn too, which then ends on it, and while it works, what the
380
+ * Artifact Agent is doing.
381
+ */
382
+ export declare const artifactDetailsSchema: z.ZodObject<{
383
+ answer: z.ZodOptional<z.ZodString>;
384
+ final: z.ZodOptional<z.ZodBoolean>;
385
+ activity: z.ZodOptional<z.ZodEnum<{
386
+ building: "building";
387
+ changing: "changing";
388
+ checking: "checking";
389
+ exploring: "exploring";
390
+ fixing: "fixing";
391
+ startingDraft: "startingDraft";
392
+ thinking: "thinking";
393
+ }>>;
394
+ }, z.core.$strip>;
395
+ export type ArtifactDetails = z.infer<typeof artifactDetailsSchema>;
348
396
  /** Everything the studio shows for a thread. */
349
397
  export type ThreadView = {
350
398
  messages: readonly ThreadMessage[];
@@ -376,6 +424,8 @@ export type ThreadState = {
376
424
  changing?: boolean;
377
425
  /** Whether the model's latest round calls `look`: from writing the call until it's called again. */
378
426
  checking?: boolean;
427
+ /** What the Artifact Agent the running `artifact` call waits for is doing, as it last said. */
428
+ artifactActivity?: AgentActivity;
379
429
  /** The drafts the thread's cards name, as the thread last looked them up. */
380
430
  drafts?: DraftCardStates;
381
431
  };
@@ -387,8 +437,6 @@ type ThreadStep = {
387
437
  type: "editor";
388
438
  sender: ThreadSender;
389
439
  text: string;
390
- /** The draft the message went to, as the agent on Live handed the request off. */
391
- handedOffTo?: string;
392
440
  } | {
393
441
  type: "connectedAgent";
394
442
  member: ThreadSender;
@@ -566,7 +614,7 @@ export declare const editorTextOf: (content: Message["content"]) => string;
566
614
  * run is over, or a later message started another run after its last model call came back
567
615
  * unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
568
616
  */
569
- export declare function threadViewOf({ steps, running, toolCalls, callingTools, changing, checking, drafts }: ThreadState): ThreadView;
617
+ export declare function threadViewOf({ steps, running, toolCalls, callingTools, changing, checking, artifactActivity, drafts }: ThreadState): ThreadView;
570
618
  /** Folds one of Pi's agent events into the thread's state. */
571
619
  export declare function reduceThreadState(state: ThreadState, event: AgentEvent): ThreadState;
572
620
  /** Folds a batch of Pi's agent events into the thread's state. */
package/dist/threads.js CHANGED
@@ -44,6 +44,13 @@ const BRIEF_ENTRY = "camox.brief";
44
44
  */
45
45
  const DRAFT_ENDED_ENTRY = "camox.draft-ended";
46
46
  /**
47
+ * The kind of the transcript entry a Live thread starts afresh from once its agent handed a
48
+ * request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
49
+ * data. Editors see it as the draft's card, alone, and the model reads a note naming the draft.
50
+ * Everything before it is left out of both.
51
+ */
52
+ const DRAFT_CARD_ENTRY = "camox.draft-card";
53
+ /**
47
54
  * The kind of the transcript entry recording a member's push to the draft's code from their own
48
55
  * checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
49
56
  * answering it, and editors see who pushed, their commits, and whether they built.
@@ -65,6 +72,22 @@ const BUILD_TOOL = "build";
65
72
  /** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
66
73
  const LOOK_TOOL = "look";
67
74
  /**
75
+ * The agent's tool handing an artifact's source to its Artifact Agent, which writes and changes
76
+ * it in a conversation of its own and answers the agent (ADR 0035).
77
+ */
78
+ const ARTIFACT_TOOL = "artifact";
79
+ /**
80
+ * The kind of the entry recording a request the agent sent an Artifact Agent, in the Artifact
81
+ * Agent's own conversation, which editors never see: who the run acts for and the operation ids
82
+ * of its messages, as data (`artifactRequestDataSchema`).
83
+ */
84
+ const ARTIFACT_REQUEST_ENTRY = "camox.artifact-request";
85
+ /** What an Artifact Agent's request records: the member the run acts for, and its messages. */
86
+ const artifactRequestDataSchema = z.object({
87
+ member: z.string(),
88
+ operationIds: z.array(z.string())
89
+ });
90
+ /**
68
91
  * The tools whose calls change a draft: the content tools that write, and the code tools that
69
92
  * write files. The model calls the code tools, and code mode scripts call both. A test in the API
70
93
  * keeps this in step with the tools themselves.
@@ -132,14 +155,8 @@ const pushDataSchema = z.object({
132
155
  subjects: z.array(z.string()),
133
156
  outcome: pushOutcomeSchema
134
157
  });
135
- /**
136
- * What an editor's message entry records: who sent it, the operation id it was sent with, and for
137
- * a message sent on Live as the agent handed the request off, the draft it went to with it.
138
- */
139
- const editorMessageDataSchema = threadSenderSchema.extend({
140
- operationId: z.string().optional(),
141
- handedOffTo: z.string().optional()
142
- });
158
+ /** What an editor's message entry records: who sent it, and the operation id it was sent with. */
159
+ const editorMessageDataSchema = threadSenderSchema.extend({ operationId: z.string().optional() });
143
160
  /**
144
161
  * A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
145
162
  * the run that started it: the studio that sent one of them enters the draft, if it still shows
@@ -193,6 +210,37 @@ const touchedBatchSchema = z.object({
193
210
  operationIds: z.array(z.string()),
194
211
  touches: z.array(touchSchema)
195
212
  });
213
+ /**
214
+ * What the agent is doing during a run, as editors see it, without the details of how it works:
215
+ * - `thinking`: the model is called, and hasn't started writing a tool call.
216
+ * - `exploring`: it uses tools that only read, from writing a call until the model is called again.
217
+ * - `changing`: a call of that round changes the draft (`CHANGING_TOOLS`), as soon as it's written
218
+ * or, in a code mode script, as soon as the script calls one.
219
+ * - `building`: its `build` call runs, or the end of the turn builds its code changes.
220
+ * - `checking`: it looks at what it designed, from writing a `look` call until the model is
221
+ * called again.
222
+ * - `fixing`: the end of the turn's build failed, and the agent fixes it.
223
+ * - `startingDraft`: on Live, its `startDraft` call runs.
224
+ */
225
+ const agentActivitySchema = z.enum([
226
+ "thinking",
227
+ "exploring",
228
+ "changing",
229
+ "building",
230
+ "checking",
231
+ "fixing",
232
+ "startingDraft"
233
+ ]);
234
+ /**
235
+ * What an `artifact` call's result carries for the studio: the Artifact Agent's answer, whether
236
+ * it's the agent's answer to the turn too, which then ends on it, and while it works, what the
237
+ * Artifact Agent is doing.
238
+ */
239
+ const artifactDetailsSchema = z.object({
240
+ answer: z.string().optional(),
241
+ final: z.boolean().optional(),
242
+ activity: agentActivitySchema.optional()
243
+ });
196
244
  const EMPTY_THREAD_VIEW = {
197
245
  messages: [],
198
246
  running: false
@@ -307,19 +355,23 @@ function threadStepOf(entry) {
307
355
  ...push
308
356
  };
309
357
  }
358
+ if (entry.kind === "camox.draft-card") {
359
+ const card = startedDraftCardSchema.safeParse(entry.data).data;
360
+ return card && {
361
+ id,
362
+ type: "draft",
363
+ ...card
364
+ };
365
+ }
310
366
  const message = entry.model?.find((candidate) => candidate.role !== "system");
311
367
  if (!message) return void 0;
312
368
  if (message.role === "user") {
313
- if (entry.kind === "camox.editor-message") {
314
- const handedOffTo = editorMessageDataSchema.safeParse(entry.data).data?.handedOffTo;
315
- return sender && {
316
- id,
317
- type: "editor",
318
- sender,
319
- text: editorTextOf(message.content),
320
- ...handedOffTo && { handedOffTo }
321
- };
322
- }
369
+ if (entry.kind === "camox.editor-message") return sender && {
370
+ id,
371
+ type: "editor",
372
+ sender,
373
+ text: editorTextOf(message.content)
374
+ };
323
375
  if (entry.kind === "camox.connected-agent-message") {
324
376
  const data = connectedAgentMessageDataSchema.safeParse(entry.data).data;
325
377
  return data && {
@@ -353,6 +405,14 @@ function threadStepOf(entry) {
353
405
  }
354
406
  if (message.role === "assistant") return assistantStep(id, message);
355
407
  if (message.role === "toolResult") {
408
+ if (message.toolName === "artifact" && !message.isError) {
409
+ const details = artifactDetailsSchema.safeParse(message.details).data;
410
+ if (details?.final && details.answer) return {
411
+ id,
412
+ type: "answer",
413
+ text: details.answer
414
+ };
415
+ }
356
416
  const card = message.toolName === "startDraft" && !message.isError ? startedDraftCardSchema.safeParse(message.details).data : void 0;
357
417
  return card ? {
358
418
  id,
@@ -373,7 +433,7 @@ function threadStepOf(entry) {
373
433
  * run is over, or a later message started another run after its last model call came back
374
434
  * unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
375
435
  */
376
- function threadViewOf({ steps, running, toolCalls = [], callingTools = false, changing = false, checking = false, drafts = {} }) {
436
+ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, changing = false, checking = false, artifactActivity, drafts = {} }) {
377
437
  const messages = [];
378
438
  let waiting;
379
439
  let unfinished = false;
@@ -395,17 +455,7 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
395
455
  endTurn();
396
456
  };
397
457
  for (const step of steps) switch (step.type) {
398
- case "editor": {
399
- const card = step.handedOffTo ? messages.findLastIndex((message) => message.from === "draft" && message.draftId === step.handedOffTo) : -1;
400
- if (card !== -1) {
401
- messages.splice(card, 0, {
402
- id: step.id,
403
- from: "editor",
404
- sender: step.sender,
405
- text: step.text
406
- });
407
- break;
408
- }
458
+ case "editor":
409
459
  if (unfinished) fail();
410
460
  messages.push({
411
461
  id: step.id,
@@ -415,7 +465,6 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
415
465
  });
416
466
  waiting = step.id;
417
467
  break;
418
- }
419
468
  case "connectedAgent":
420
469
  if (unfinished) fail();
421
470
  messages.push({
@@ -512,14 +561,16 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
512
561
  toolCalls,
513
562
  callingTools,
514
563
  changing,
515
- checking
564
+ checking,
565
+ artifactActivity
516
566
  })
517
567
  };
518
568
  }
519
569
  /** What the agent is doing during a run, from where the turn's build stands and its tool calls. */
520
- function activityOf({ status, toolCalls, callingTools, changing, checking }) {
570
+ function activityOf({ status, toolCalls, callingTools, changing, checking, artifactActivity }) {
521
571
  const calling = (name) => toolCalls.some((call) => call.name === name);
522
572
  if (status === "building" || calling("build")) return "building";
573
+ if (calling("artifact")) return artifactActivity ?? "changing";
523
574
  if (checking) return "checking";
524
575
  if (status === "fixing") return "fixing";
525
576
  if (calling("startDraft")) return "startingDraft";
@@ -533,8 +584,22 @@ const changes = (name) => CHANGING_TOOLS.has(name);
533
584
  const callsChangingTool = (block) => block.type === "toolCall" && changes(block.name);
534
585
  /** Whether a block of the model's message is a call of `look`. */
535
586
  const callsLook = (block) => block.type === "toolCall" && block.name === "look";
587
+ /**
588
+ * Adds an entry to the thread's state as Pi places it. An entry heading a new context, such as
589
+ * the card a Live thread starts afresh from, leaves out every step before its head, as Pi's
590
+ * snapshots do.
591
+ */
536
592
  const append = (state, entry) => {
537
- const step = entry && threadStepOf(entry);
593
+ if (!entry) return state;
594
+ const step = threadStepOf(entry);
595
+ if (entry.head !== void 0) {
596
+ const head = Number(entry.head);
597
+ const kept = state.steps.filter((known) => Number(known.id) >= head && known.id !== String(entry.id));
598
+ return {
599
+ ...state,
600
+ steps: step ? [step, ...kept] : kept
601
+ };
602
+ }
538
603
  if (!step || state.steps.some((known) => known.id === step.id)) return state;
539
604
  return {
540
605
  ...state,
@@ -550,9 +615,11 @@ function reduceThreadState(state, event) {
550
615
  name: tool.name
551
616
  }]);
552
617
  const generated = event.generation?.message?.content ?? [];
618
+ const artifact = event.tools.find((tool) => tool.name === "artifact" && tool.status !== "done");
553
619
  return {
554
620
  ...state,
555
621
  steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
622
+ artifactActivity: artifactDetailsSchema.safeParse(artifact?.details).data?.activity,
556
623
  running: event.run !== void 0,
557
624
  toolCalls,
558
625
  callingTools: generated.some((part) => part.type === "toolCall"),
@@ -570,7 +637,8 @@ function reduceThreadState(state, event) {
570
637
  toolCalls: [],
571
638
  callingTools: false,
572
639
  changing: false,
573
- checking: false
640
+ checking: false,
641
+ artifactActivity: void 0
574
642
  };
575
643
  case "message_start": return event.message.role === "assistant" ? {
576
644
  ...state,
@@ -604,9 +672,18 @@ function reduceThreadState(state, event) {
604
672
  }]
605
673
  };
606
674
  }
675
+ case "tool_execution_update": {
676
+ if (event.toolName !== "artifact" || event.parentToolCallId !== void 0) return state;
677
+ const activity = artifactDetailsSchema.safeParse(event.details).data?.activity;
678
+ return activity ? {
679
+ ...state,
680
+ artifactActivity: activity
681
+ } : state;
682
+ }
607
683
  case "tool_execution_end": return append({
608
684
  ...state,
609
- toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId)
685
+ toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId),
686
+ ...event.toolName === "artifact" && event.parentToolCallId === void 0 && { artifactActivity: void 0 }
610
687
  }, event.entry);
611
688
  case "message_end":
612
689
  case "entry_appended": return append(state, event.entry);
@@ -628,4 +705,4 @@ function reduceThreadFrame(state, frame) {
628
705
  }
629
706
 
630
707
  //#endregion
631
- export { BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, CONNECTED_AGENT_MESSAGE_ENTRY, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, LOOK_TOOL, PUSH_ENTRY, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, buildStatusDataSchema, buildStatusSchema, connectedAgentClientSchema, connectedAgentMessageDataSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, isSender, pushDataSchema, pushOutcomeSchema, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftCardSchema, startedDraftSchema, textOf, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
708
+ export { ARTIFACT_REQUEST_ENTRY, ARTIFACT_TOOL, BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, CONNECTED_AGENT_MESSAGE_ENTRY, DRAFT_CARD_ENTRY, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, LOOK_TOOL, PUSH_ENTRY, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, agentActivitySchema, artifactDetailsSchema, artifactRequestDataSchema, buildStatusDataSchema, buildStatusSchema, connectedAgentClientSchema, connectedAgentMessageDataSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, isSender, pushDataSchema, pushOutcomeSchema, 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.65.1",
3
+ "version": "0.66.0",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "src",
package/src/threads.ts CHANGED
@@ -57,6 +57,14 @@ export const BRIEF_ENTRY = "camox.brief";
57
57
  */
58
58
  export const DRAFT_ENDED_ENTRY = "camox.draft-ended";
59
59
 
60
+ /**
61
+ * The kind of the transcript entry a Live thread starts afresh from once its agent handed a
62
+ * request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
63
+ * data. Editors see it as the draft's card, alone, and the model reads a note naming the draft.
64
+ * Everything before it is left out of both.
65
+ */
66
+ export const DRAFT_CARD_ENTRY = "camox.draft-card";
67
+
60
68
  /**
61
69
  * The kind of the transcript entry recording a member's push to the draft's code from their own
62
70
  * checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
@@ -80,6 +88,26 @@ export const BUILD_TOOL = "build";
80
88
  /** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
81
89
  export const LOOK_TOOL = "look";
82
90
 
91
+ /**
92
+ * The agent's tool handing an artifact's source to its Artifact Agent, which writes and changes
93
+ * it in a conversation of its own and answers the agent (ADR 0035).
94
+ */
95
+ export const ARTIFACT_TOOL = "artifact";
96
+
97
+ /**
98
+ * The kind of the entry recording a request the agent sent an Artifact Agent, in the Artifact
99
+ * Agent's own conversation, which editors never see: who the run acts for and the operation ids
100
+ * of its messages, as data (`artifactRequestDataSchema`).
101
+ */
102
+ export const ARTIFACT_REQUEST_ENTRY = "camox.artifact-request";
103
+
104
+ /** What an Artifact Agent's request records: the member the run acts for, and its messages. */
105
+ export const artifactRequestDataSchema = z.object({
106
+ member: z.string(),
107
+ operationIds: z.array(z.string()),
108
+ });
109
+ export type ArtifactRequestData = z.infer<typeof artifactRequestDataSchema>;
110
+
83
111
  /**
84
112
  * The tools whose calls change a draft: the content tools that write, and the code tools that
85
113
  * write files. The model calls the code tools, and code mode scripts call both. A test in the API
@@ -172,13 +200,9 @@ export const pushDataSchema = z.object({
172
200
  });
173
201
  export type PushData = z.infer<typeof pushDataSchema>;
174
202
 
175
- /**
176
- * What an editor's message entry records: who sent it, the operation id it was sent with, and for
177
- * a message sent on Live as the agent handed the request off, the draft it went to with it.
178
- */
203
+ /** What an editor's message entry records: who sent it, and the operation id it was sent with. */
179
204
  export const editorMessageDataSchema = threadSenderSchema.extend({
180
205
  operationId: z.string().optional(),
181
- handedOffTo: z.string().optional(),
182
206
  });
183
207
 
184
208
  /**
@@ -284,14 +308,28 @@ export type ThreadMessage =
284
308
  * - `fixing`: the end of the turn's build failed, and the agent fixes it.
285
309
  * - `startingDraft`: on Live, its `startDraft` call runs.
286
310
  */
287
- export type AgentActivity =
288
- | "thinking"
289
- | "exploring"
290
- | "changing"
291
- | "building"
292
- | "checking"
293
- | "fixing"
294
- | "startingDraft";
311
+ export const agentActivitySchema = z.enum([
312
+ "thinking",
313
+ "exploring",
314
+ "changing",
315
+ "building",
316
+ "checking",
317
+ "fixing",
318
+ "startingDraft",
319
+ ]);
320
+ export type AgentActivity = z.infer<typeof agentActivitySchema>;
321
+
322
+ /**
323
+ * What an `artifact` call's result carries for the studio: the Artifact Agent's answer, whether
324
+ * it's the agent's answer to the turn too, which then ends on it, and while it works, what the
325
+ * Artifact Agent is doing.
326
+ */
327
+ export const artifactDetailsSchema = z.object({
328
+ answer: z.string().optional(),
329
+ final: z.boolean().optional(),
330
+ activity: agentActivitySchema.optional(),
331
+ });
332
+ export type ArtifactDetails = z.infer<typeof artifactDetailsSchema>;
295
333
 
296
334
  /** Everything the studio shows for a thread. */
297
335
  export type ThreadView = {
@@ -323,6 +361,8 @@ export type ThreadState = {
323
361
  changing?: boolean;
324
362
  /** Whether the model's latest round calls `look`: from writing the call until it's called again. */
325
363
  checking?: boolean;
364
+ /** What the Artifact Agent the running `artifact` call waits for is doing, as it last said. */
365
+ artifactActivity?: AgentActivity;
326
366
  /** The drafts the thread's cards name, as the thread last looked them up. */
327
367
  drafts?: DraftCardStates;
328
368
  };
@@ -331,13 +371,7 @@ export const EMPTY_THREAD_STATE: ThreadState = { steps: [], running: false };
331
371
 
332
372
  /** One transcript entry, as far as the studio's view is concerned. */
333
373
  type ThreadStep = { id: string } & (
334
- | {
335
- type: "editor";
336
- sender: ThreadSender;
337
- text: string;
338
- /** The draft the message went to, as the agent on Live handed the request off. */
339
- handedOffTo?: string;
340
- }
374
+ | { type: "editor"; sender: ThreadSender; text: string }
341
375
  | { type: "connectedAgent"; member: ThreadSender; client: ConnectedAgentClient; text: string }
342
376
  /** Camox starting a turn of its own, saying why. */
343
377
  | { type: "camox"; text: string }
@@ -495,20 +529,16 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
495
529
  const push = pushDataSchema.safeParse(entry.data).data;
496
530
  return push && { id, type: "push", ...push };
497
531
  }
532
+ // The model reads a note: editors see the card it starts the Live thread afresh with.
533
+ if (entry.kind === DRAFT_CARD_ENTRY) {
534
+ const card = startedDraftCardSchema.safeParse(entry.data).data;
535
+ return card && { id, type: "draft", ...card };
536
+ }
498
537
  const message = entry.model?.find((candidate) => candidate.role !== "system");
499
538
  if (!message) return undefined;
500
539
  if (message.role === "user") {
501
540
  if (entry.kind === EDITOR_MESSAGE_ENTRY) {
502
- const handedOffTo = editorMessageDataSchema.safeParse(entry.data).data?.handedOffTo;
503
- return (
504
- sender && {
505
- id,
506
- type: "editor",
507
- sender,
508
- text: editorTextOf(message.content),
509
- ...(handedOffTo && { handedOffTo }),
510
- }
511
- );
541
+ return sender && { id, type: "editor", sender, text: editorTextOf(message.content) };
512
542
  }
513
543
  if (entry.kind === CONNECTED_AGENT_MESSAGE_ENTRY) {
514
544
  const data = connectedAgentMessageDataSchema.safeParse(entry.data).data;
@@ -533,6 +563,11 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
533
563
  }
534
564
  if (message.role === "assistant") return assistantStep(id, message);
535
565
  if (message.role === "toolResult") {
566
+ // An Artifact Agent's answer that ends the turn is the agent's: editors never see it apart.
567
+ if (message.toolName === ARTIFACT_TOOL && !message.isError) {
568
+ const details = artifactDetailsSchema.safeParse(message.details).data;
569
+ if (details?.final && details.answer) return { id, type: "answer", text: details.answer };
570
+ }
536
571
  const card =
537
572
  message.toolName === START_DRAFT_TOOL && !message.isError
538
573
  ? startedDraftCardSchema.safeParse(message.details).data
@@ -558,6 +593,7 @@ export function threadViewOf({
558
593
  callingTools = false,
559
594
  changing = false,
560
595
  checking = false,
596
+ artifactActivity,
561
597
  drafts = {},
562
598
  }: ThreadState): ThreadView {
563
599
  const messages: ThreadMessage[] = [];
@@ -583,24 +619,7 @@ export function threadViewOf({
583
619
 
584
620
  for (const step of steps) {
585
621
  switch (step.type) {
586
- case "editor": {
587
- // A message sent as the agent on Live handed the request off went to the draft with it:
588
- // it belongs to the turn ending on that draft's card, which it shows just before. Pi
589
- // placed it after the card, which leaves the transcript the model read as it was.
590
- const card = step.handedOffTo
591
- ? messages.findLastIndex(
592
- (message) => message.from === "draft" && message.draftId === step.handedOffTo,
593
- )
594
- : -1;
595
- if (card !== -1) {
596
- messages.splice(card, 0, {
597
- id: step.id,
598
- from: "editor",
599
- sender: step.sender,
600
- text: step.text,
601
- });
602
- break;
603
- }
622
+ case "editor":
604
623
  // Pi places a steering message only once a model call is settled: after a round of tool
605
624
  // calls, or with the answer ending the run. Never between a failed call and its retry, so
606
625
  // an editor's message after an unfinished call means that call's turn is over.
@@ -608,7 +627,6 @@ export function threadViewOf({
608
627
  messages.push({ id: step.id, from: "editor", sender: step.sender, text: step.text });
609
628
  waiting = step.id;
610
629
  break;
611
- }
612
630
  case "connectedAgent":
613
631
  // Like an editor's message: it starts a turn, or steers the one going.
614
632
  if (unfinished) fail();
@@ -691,7 +709,7 @@ export function threadViewOf({
691
709
  return {
692
710
  messages,
693
711
  running,
694
- activity: activityOf({ status, toolCalls, callingTools, changing, checking }),
712
+ activity: activityOf({ status, toolCalls, callingTools, changing, checking, artifactActivity }),
695
713
  };
696
714
  }
697
715
 
@@ -702,17 +720,21 @@ function activityOf({
702
720
  callingTools,
703
721
  changing,
704
722
  checking,
723
+ artifactActivity,
705
724
  }: {
706
725
  status: BuildStatus | undefined;
707
726
  toolCalls: NonNullable<ThreadState["toolCalls"]>;
708
727
  callingTools: boolean;
709
728
  changing: boolean;
710
729
  checking: boolean;
730
+ artifactActivity: AgentActivity | undefined;
711
731
  }): AgentActivity {
712
732
  const calling = (name: string) => toolCalls.some((call) => call.name === name);
713
733
  // Fixing a build error takes thinking, reading and writing, which all read as fixing, but its
714
734
  // builds show as building.
715
735
  if (status === "building" || calling(BUILD_TOOL)) return "building";
736
+ // An Artifact Agent works on the agent's behalf: editors see what it does as the agent's.
737
+ if (calling(ARTIFACT_TOOL)) return artifactActivity ?? "changing";
716
738
  if (checking) return "checking";
717
739
  if (status === "fixing") return "fixing";
718
740
  if (calling(START_DRAFT_TOOL)) return "startingDraft";
@@ -732,8 +754,21 @@ const callsChangingTool = (block: AssistantMessage["content"][number]) =>
732
754
  const callsLook = (block: AssistantMessage["content"][number]) =>
733
755
  block.type === "toolCall" && block.name === LOOK_TOOL;
734
756
 
757
+ /**
758
+ * Adds an entry to the thread's state as Pi places it. An entry heading a new context, such as
759
+ * the card a Live thread starts afresh from, leaves out every step before its head, as Pi's
760
+ * snapshots do.
761
+ */
735
762
  const append = (state: ThreadState, entry: EntryRecord | undefined): ThreadState => {
736
- const step = entry && threadStepOf(entry);
763
+ if (!entry) return state;
764
+ const step = threadStepOf(entry);
765
+ if (entry.head !== undefined) {
766
+ const head = Number(entry.head);
767
+ const kept = state.steps.filter(
768
+ (known) => Number(known.id) >= head && known.id !== String(entry.id),
769
+ );
770
+ return { ...state, steps: step ? [step, ...kept] : kept };
771
+ }
737
772
  if (!step || state.steps.some((known) => known.id === step.id)) return state;
738
773
  return { ...state, steps: [...state.steps, step] };
739
774
  };
@@ -746,9 +781,13 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
746
781
  tool.status === "done" ? [] : [{ callId: tool.callId, name: tool.name }],
747
782
  );
748
783
  const generated = event.generation?.message?.content ?? [];
784
+ const artifact = event.tools.find(
785
+ (tool) => tool.name === ARTIFACT_TOOL && tool.status !== "done",
786
+ );
749
787
  return {
750
788
  ...state,
751
789
  steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
790
+ artifactActivity: artifactDetailsSchema.safeParse(artifact?.details).data?.activity,
752
791
  running: event.run !== undefined,
753
792
  toolCalls,
754
793
  callingTools: generated.some((part) => part.type === "toolCall"),
@@ -778,6 +817,7 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
778
817
  callingTools: false,
779
818
  changing: false,
780
819
  checking: false,
820
+ artifactActivity: undefined,
781
821
  };
782
822
  case "message_start":
783
823
  // The model is called again: it thinks until it writes a tool call.
@@ -811,11 +851,18 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
811
851
  toolCalls: [...(state.toolCalls ?? []), { callId: event.toolCallId, name: event.toolName }],
812
852
  };
813
853
  }
854
+ case "tool_execution_update": {
855
+ if (event.toolName !== ARTIFACT_TOOL || event.parentToolCallId !== undefined) return state;
856
+ const activity = artifactDetailsSchema.safeParse(event.details).data?.activity;
857
+ return activity ? { ...state, artifactActivity: activity } : state;
858
+ }
814
859
  case "tool_execution_end":
815
860
  return append(
816
861
  {
817
862
  ...state,
818
863
  toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId),
864
+ ...(event.toolName === ARTIFACT_TOOL &&
865
+ event.parentToolCallId === undefined && { artifactActivity: undefined }),
819
866
  },
820
867
  event.entry,
821
868
  );