@camox/api-contract 0.51.0 → 0.52.1
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/drafts.d.ts +31 -1
- package/dist/drafts.js +39 -3
- package/dist/query-keys.d.ts +11 -1
- package/dist/router.d.ts +1063 -16
- package/dist/threads.d.ts +56 -1
- package/dist/threads.js +79 -10
- package/package.json +1 -1
- package/src/drafts.ts +43 -1
- package/src/query-keys.ts +7 -1
- package/src/threads.ts +83 -5
package/dist/threads.d.ts
CHANGED
|
@@ -8,6 +8,32 @@ import { AgentEvent } from "@earendil-works/pi-durable";
|
|
|
8
8
|
export declare const EDITOR_MESSAGE_ENTRY = "camox.editor-message";
|
|
9
9
|
/** The kind of the transcript entry recording that an editor stopped a run, with them as data. */
|
|
10
10
|
export declare const RUN_STOPPED_ENTRY = "camox.run-stopped";
|
|
11
|
+
/**
|
|
12
|
+
* The kind of the transcript entry recording where the build of the code a turn changed stands,
|
|
13
|
+
* as the turn ends, with a `buildStatusDataSchema` as data: it started, or it failed and the
|
|
14
|
+
* agent is fixing it. The model never sees it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const BUILD_STATUS_ENTRY = "camox.build-status";
|
|
17
|
+
/**
|
|
18
|
+
* The kind of the transcript entry starting Camox's own turn, which reconciles the draft's code
|
|
19
|
+
* with Live's when merging it clashed, with a `reconcileDataSchema` as data: the short message
|
|
20
|
+
* editors see. The model gets Camox's instructions instead.
|
|
21
|
+
*/
|
|
22
|
+
export declare const RECONCILE_ENTRY = "camox.reconcile";
|
|
23
|
+
export declare const reconcileDataSchema: z.ZodObject<{
|
|
24
|
+
message: z.ZodString;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export declare const buildStatusSchema: z.ZodEnum<{
|
|
27
|
+
building: "building";
|
|
28
|
+
fixing: "fixing";
|
|
29
|
+
}>;
|
|
30
|
+
export type BuildStatus = z.infer<typeof buildStatusSchema>;
|
|
31
|
+
export declare const buildStatusDataSchema: z.ZodObject<{
|
|
32
|
+
status: z.ZodEnum<{
|
|
33
|
+
building: "building";
|
|
34
|
+
fixing: "fixing";
|
|
35
|
+
}>;
|
|
36
|
+
}, z.core.$strip>;
|
|
11
37
|
/** Who sent a message in a thread. */
|
|
12
38
|
export declare const threadSenderSchema: z.ZodObject<{
|
|
13
39
|
id: z.ZodString;
|
|
@@ -131,6 +157,12 @@ export type ThreadMessage = {
|
|
|
131
157
|
sender: ThreadSender;
|
|
132
158
|
text: string;
|
|
133
159
|
} |
|
|
160
|
+
/** Camox's own message, starting a turn it began itself: why it did. */
|
|
161
|
+
{
|
|
162
|
+
id: string;
|
|
163
|
+
from: "camox";
|
|
164
|
+
text: string;
|
|
165
|
+
} |
|
|
134
166
|
/** The agent's final message of a turn, in Markdown. */
|
|
135
167
|
{
|
|
136
168
|
id: string;
|
|
@@ -167,6 +199,11 @@ export type ThreadView = LiveThreadHandoff & {
|
|
|
167
199
|
messages: readonly ThreadMessage[];
|
|
168
200
|
/** Whether a run is going: the agent is working on a turn, and stopping would end it. */
|
|
169
201
|
running: boolean;
|
|
202
|
+
/**
|
|
203
|
+
* While a run is going, where the build of the code its turn changed stands: the end of the
|
|
204
|
+
* turn is building it, or it failed and the agent is fixing it.
|
|
205
|
+
*/
|
|
206
|
+
status?: BuildStatus;
|
|
170
207
|
};
|
|
171
208
|
export declare const EMPTY_THREAD_VIEW: ThreadView;
|
|
172
209
|
/**
|
|
@@ -186,6 +223,11 @@ type ThreadStep = {
|
|
|
186
223
|
type: "editor";
|
|
187
224
|
sender: ThreadSender;
|
|
188
225
|
text: string;
|
|
226
|
+
} |
|
|
227
|
+
/** Camox starting a turn of its own, saying why. */
|
|
228
|
+
{
|
|
229
|
+
type: "camox";
|
|
230
|
+
text: string;
|
|
189
231
|
} | {
|
|
190
232
|
type: "answer";
|
|
191
233
|
text: string;
|
|
@@ -200,6 +242,18 @@ type ThreadStep = {
|
|
|
200
242
|
} | {
|
|
201
243
|
type: "stopped";
|
|
202
244
|
sender: ThreadSender;
|
|
245
|
+
} |
|
|
246
|
+
/** Where the build of the turn's code stands, as the turn ends. */
|
|
247
|
+
{
|
|
248
|
+
type: "build";
|
|
249
|
+
status: BuildStatus;
|
|
250
|
+
} |
|
|
251
|
+
/**
|
|
252
|
+
* The end of the turn sent the agent back to work, after its answer: the answer wasn't the
|
|
253
|
+
* turn's last, and what the agent was told is never shown.
|
|
254
|
+
*/
|
|
255
|
+
{
|
|
256
|
+
type: "continued";
|
|
203
257
|
});
|
|
204
258
|
/** Studio → thread. */
|
|
205
259
|
export declare const threadClientMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -259,7 +313,8 @@ export type ThreadServerMessage =
|
|
|
259
313
|
*/
|
|
260
314
|
export declare const THREAD_ENDED_CLOSE_CODE = 4000;
|
|
261
315
|
/**
|
|
262
|
-
* What the studio shows for a thread. A turn starts with an editor's message,
|
|
316
|
+
* What the studio shows for a thread. A turn starts with an editor's message, or Camox's own
|
|
317
|
+
* when it began the turn itself, and messages sent
|
|
263
318
|
* while it runs join it. It ends with the agent's final message; text written beside tool calls
|
|
264
319
|
* is never shown. A stopped turn shows who stopped it instead, and whatever its run wrote after
|
|
265
320
|
* the stop is left out. A turn that ended without either shows a failed line in its place: its
|
package/dist/threads.js
CHANGED
|
@@ -8,6 +8,21 @@ import { z } from "zod";
|
|
|
8
8
|
const EDITOR_MESSAGE_ENTRY = "camox.editor-message";
|
|
9
9
|
/** The kind of the transcript entry recording that an editor stopped a run, with them as data. */
|
|
10
10
|
const RUN_STOPPED_ENTRY = "camox.run-stopped";
|
|
11
|
+
/**
|
|
12
|
+
* The kind of the transcript entry recording where the build of the code a turn changed stands,
|
|
13
|
+
* as the turn ends, with a `buildStatusDataSchema` as data: it started, or it failed and the
|
|
14
|
+
* agent is fixing it. The model never sees it.
|
|
15
|
+
*/
|
|
16
|
+
const BUILD_STATUS_ENTRY = "camox.build-status";
|
|
17
|
+
/**
|
|
18
|
+
* The kind of the transcript entry starting Camox's own turn, which reconciles the draft's code
|
|
19
|
+
* with Live's when merging it clashed, with a `reconcileDataSchema` as data: the short message
|
|
20
|
+
* editors see. The model gets Camox's instructions instead.
|
|
21
|
+
*/
|
|
22
|
+
const RECONCILE_ENTRY = "camox.reconcile";
|
|
23
|
+
const reconcileDataSchema = z.object({ message: z.string() });
|
|
24
|
+
const buildStatusSchema = z.enum(["building", "fixing"]);
|
|
25
|
+
const buildStatusDataSchema = z.object({ status: buildStatusSchema });
|
|
11
26
|
/** Who sent a message in a thread. */
|
|
12
27
|
const threadSenderSchema = z.object({
|
|
13
28
|
id: z.string(),
|
|
@@ -131,14 +146,36 @@ function threadStepOf(entry) {
|
|
|
131
146
|
type: "stopped",
|
|
132
147
|
sender
|
|
133
148
|
};
|
|
149
|
+
if (entry.kind === "camox.build-status") {
|
|
150
|
+
const build = buildStatusDataSchema.safeParse(entry.data).data;
|
|
151
|
+
return build && {
|
|
152
|
+
id,
|
|
153
|
+
type: "build",
|
|
154
|
+
status: build.status
|
|
155
|
+
};
|
|
156
|
+
}
|
|
134
157
|
const message = entry.model?.find((candidate) => candidate.role !== "system");
|
|
135
158
|
if (!message) return void 0;
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
159
|
+
if (message.role === "user") {
|
|
160
|
+
if (entry.kind === "camox.editor-message") return sender && {
|
|
161
|
+
id,
|
|
162
|
+
type: "editor",
|
|
163
|
+
sender,
|
|
164
|
+
text: textOf(message.content)
|
|
165
|
+
};
|
|
166
|
+
if (entry.kind === "camox.reconcile") {
|
|
167
|
+
const reconcile = reconcileDataSchema.safeParse(entry.data).data;
|
|
168
|
+
return reconcile && {
|
|
169
|
+
id,
|
|
170
|
+
type: "camox",
|
|
171
|
+
text: reconcile.message
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
id,
|
|
176
|
+
type: "continued"
|
|
177
|
+
};
|
|
178
|
+
}
|
|
142
179
|
if (message.role === "assistant") return assistantStep(id, message);
|
|
143
180
|
if (message.role === "toolResult") return {
|
|
144
181
|
id,
|
|
@@ -146,7 +183,8 @@ function threadStepOf(entry) {
|
|
|
146
183
|
};
|
|
147
184
|
}
|
|
148
185
|
/**
|
|
149
|
-
* What the studio shows for a thread. A turn starts with an editor's message,
|
|
186
|
+
* What the studio shows for a thread. A turn starts with an editor's message, or Camox's own
|
|
187
|
+
* when it began the turn itself, and messages sent
|
|
150
188
|
* while it runs join it. It ends with the agent's final message; text written beside tool calls
|
|
151
189
|
* is never shown. A stopped turn shows who stopped it instead, and whatever its run wrote after
|
|
152
190
|
* the stop is left out. A turn that ended without either shows a failed line in its place: its
|
|
@@ -157,10 +195,14 @@ function threadViewOf({ steps, running, ...drafts }) {
|
|
|
157
195
|
const messages = [];
|
|
158
196
|
let waiting;
|
|
159
197
|
let unfinished = false;
|
|
198
|
+
let status;
|
|
199
|
+
let answered;
|
|
160
200
|
/** Ends the turn waiting for the agent's message, if any. */
|
|
161
201
|
const endTurn = () => {
|
|
162
202
|
waiting = void 0;
|
|
163
203
|
unfinished = false;
|
|
204
|
+
status = void 0;
|
|
205
|
+
answered = void 0;
|
|
164
206
|
};
|
|
165
207
|
/** Ends the turn waiting for the agent's message with a failed line, if any. */
|
|
166
208
|
const fail = () => {
|
|
@@ -181,6 +223,15 @@ function threadViewOf({ steps, running, ...drafts }) {
|
|
|
181
223
|
});
|
|
182
224
|
waiting = step.id;
|
|
183
225
|
break;
|
|
226
|
+
case "camox":
|
|
227
|
+
if (unfinished) fail();
|
|
228
|
+
messages.push({
|
|
229
|
+
id: step.id,
|
|
230
|
+
from: "camox",
|
|
231
|
+
text: step.text
|
|
232
|
+
});
|
|
233
|
+
waiting = step.id;
|
|
234
|
+
break;
|
|
184
235
|
case "stopped":
|
|
185
236
|
if (waiting === void 0) break;
|
|
186
237
|
messages.push({
|
|
@@ -197,7 +248,24 @@ function threadViewOf({ steps, running, ...drafts }) {
|
|
|
197
248
|
from: "agent",
|
|
198
249
|
text: step.text
|
|
199
250
|
});
|
|
200
|
-
|
|
251
|
+
{
|
|
252
|
+
const before = {
|
|
253
|
+
waiting,
|
|
254
|
+
status
|
|
255
|
+
};
|
|
256
|
+
endTurn();
|
|
257
|
+
answered = before;
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
case "continued":
|
|
261
|
+
if (!answered) break;
|
|
262
|
+
if (messages.at(-1)?.from === "agent") messages.pop();
|
|
263
|
+
waiting = answered.waiting;
|
|
264
|
+
status = answered.status === "fixing" ? "fixing" : void 0;
|
|
265
|
+
answered = void 0;
|
|
266
|
+
break;
|
|
267
|
+
case "build":
|
|
268
|
+
status = step.status;
|
|
201
269
|
break;
|
|
202
270
|
case "tools":
|
|
203
271
|
unfinished = false;
|
|
@@ -208,7 +276,8 @@ function threadViewOf({ steps, running, ...drafts }) {
|
|
|
208
276
|
return {
|
|
209
277
|
...drafts,
|
|
210
278
|
messages,
|
|
211
|
-
running
|
|
279
|
+
running,
|
|
280
|
+
...running && status && { status }
|
|
212
281
|
};
|
|
213
282
|
}
|
|
214
283
|
const append = (state, entry) => {
|
|
@@ -260,4 +329,4 @@ function reduceThreadFrame(state, frame) {
|
|
|
260
329
|
}
|
|
261
330
|
|
|
262
331
|
//#endregion
|
|
263
|
-
export { EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, RUN_STOPPED_ENTRY, THREAD_ENDED_CLOSE_CODE, editorMessageDataSchema, isSender, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftSchema, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
|
|
332
|
+
export { BUILD_STATUS_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, THREAD_ENDED_CLOSE_CODE, buildStatusDataSchema, buildStatusSchema, editorMessageDataSchema, isSender, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftSchema, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
|
package/package.json
CHANGED
package/src/drafts.ts
CHANGED
|
@@ -9,6 +9,22 @@ export const DRAFT_HEADER = "x-camox-draft";
|
|
|
9
9
|
*/
|
|
10
10
|
export const LOCALE_HEADER = "x-camox-locale";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Request header the Worker a publish with code uploaded sends on its public reads, naming
|
|
14
|
+
* the draft it publishes (spec #560, section 13). While that draft is in its publishing state, a
|
|
15
|
+
* read naming it sees Live overlaid with the draft, with no session, so the new code serves the
|
|
16
|
+
* content it was previewed with from its first request. Once the draft has ended, or if it isn't
|
|
17
|
+
* publishing, the read is Live's. A request naming a draft with `DRAFT_HEADER` ignores it.
|
|
18
|
+
*/
|
|
19
|
+
export const PUBLISHING_DRAFT_HEADER = "x-camox-publishing-draft";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The Worker variable carrying the publishing draft's ID, which the publish sets on the Worker it
|
|
23
|
+
* uploads (apps/api/src/domains/publishes/scripts.ts). The website's Worker sends it as
|
|
24
|
+
* `PUBLISHING_DRAFT_HEADER`.
|
|
25
|
+
*/
|
|
26
|
+
export const PUBLISHING_DRAFT_VAR = "CAMOX_PUBLISHING_DRAFT";
|
|
27
|
+
|
|
12
28
|
/**
|
|
13
29
|
* Request header asking the API to create the draft named by `DRAFT_HEADER` if it doesn't
|
|
14
30
|
* exist yet. Its value is `"1"`.
|
|
@@ -56,6 +72,31 @@ export const DRAFT_ENDED_ERROR_CODE = "DRAFT_ENDED";
|
|
|
56
72
|
*/
|
|
57
73
|
export const CONFLICTS_CHANGED_ERROR = "CONFLICTS_CHANGED";
|
|
58
74
|
|
|
75
|
+
/**
|
|
76
|
+
* oRPC error code for a publish of a draft with code whose revision isn't the one the review
|
|
77
|
+
* showed: the agent left a newer one, or the draft is already publishing. The caller reviews
|
|
78
|
+
* the new preview.
|
|
79
|
+
*/
|
|
80
|
+
export const REVISION_CHANGED_ERROR = "REVISION_CHANGED";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Why a publish with code went back to the review instead of landing: the agent left a revision
|
|
84
|
+
* after the one the review showed, Live's conflicts changed, the draft got a blocker, Live's code
|
|
85
|
+
* moved and the draft's merge of it clashed (the "doesn't merge with Live's" blocker, which
|
|
86
|
+
* Camox's turn fixes), Live's code moved and the draft took too long to merge it, or the publish
|
|
87
|
+
* failed on Camox's side.
|
|
88
|
+
*/
|
|
89
|
+
export const PUBLISH_ABORT_REASONS = [
|
|
90
|
+
"revisionChanged",
|
|
91
|
+
"conflictsChanged",
|
|
92
|
+
"blocked",
|
|
93
|
+
"codeDoesNotMerge",
|
|
94
|
+
"liveCodeMoved",
|
|
95
|
+
"failed",
|
|
96
|
+
] as const;
|
|
97
|
+
|
|
98
|
+
export type PublishAbortReason = (typeof PUBLISH_ABORT_REASONS)[number];
|
|
99
|
+
|
|
59
100
|
/** oRPC error code for a publish refused because the draft still has blockers. */
|
|
60
101
|
export const PUBLISH_BLOCKED_ERROR = "PUBLISH_BLOCKED";
|
|
61
102
|
|
|
@@ -68,12 +109,13 @@ export const TRANSLATIONS_PENDING_ERROR = "TRANSLATIONS_PENDING";
|
|
|
68
109
|
/**
|
|
69
110
|
* Why the publish tool published nothing of a draft with changes, and returned a fresh preview to
|
|
70
111
|
* review again, by the error code of each: Live changed since the preview, the draft has blockers,
|
|
71
|
-
*
|
|
112
|
+
* translations are still running, or the draft's code changed since the preview.
|
|
72
113
|
*/
|
|
73
114
|
export const NOT_PUBLISHED_CODES = {
|
|
74
115
|
conflictsChanged: CONFLICTS_CHANGED_ERROR,
|
|
75
116
|
blocked: PUBLISH_BLOCKED_ERROR,
|
|
76
117
|
translating: TRANSLATIONS_PENDING_ERROR,
|
|
118
|
+
revisionChanged: REVISION_CHANGED_ERROR,
|
|
77
119
|
} as const;
|
|
78
120
|
|
|
79
121
|
export type NotPublishedReason = keyof typeof NOT_PUBLISHED_CODES;
|
package/src/query-keys.ts
CHANGED
|
@@ -92,5 +92,11 @@ export type InvalidationMessage = {
|
|
|
92
92
|
/** A draft was published or discarded: its viewers go back to Live. */
|
|
93
93
|
export type DraftEndedMessage = { type: "draftEnded" } & EndedDraft;
|
|
94
94
|
|
|
95
|
+
/** A draft with code has a new revision, built with Camox's `sdkVersion`. */
|
|
96
|
+
export type RevisionAnnouncement = { draftId: string; revisionId: string; sdkVersion: string };
|
|
97
|
+
|
|
98
|
+
/** A draft's new revision: its viewers' frames load it. */
|
|
99
|
+
export type RevisionMessage = { type: "revision" } & RevisionAnnouncement;
|
|
100
|
+
|
|
95
101
|
/** Everything the project room broadcasts. */
|
|
96
|
-
export type ProjectRoomMessage = InvalidationMessage | DraftEndedMessage;
|
|
102
|
+
export type ProjectRoomMessage = InvalidationMessage | DraftEndedMessage | RevisionMessage;
|
package/src/threads.ts
CHANGED
|
@@ -15,6 +15,27 @@ export const EDITOR_MESSAGE_ENTRY = "camox.editor-message";
|
|
|
15
15
|
/** The kind of the transcript entry recording that an editor stopped a run, with them as data. */
|
|
16
16
|
export const RUN_STOPPED_ENTRY = "camox.run-stopped";
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* The kind of the transcript entry recording where the build of the code a turn changed stands,
|
|
20
|
+
* as the turn ends, with a `buildStatusDataSchema` as data: it started, or it failed and the
|
|
21
|
+
* agent is fixing it. The model never sees it.
|
|
22
|
+
*/
|
|
23
|
+
export const BUILD_STATUS_ENTRY = "camox.build-status";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The kind of the transcript entry starting Camox's own turn, which reconciles the draft's code
|
|
27
|
+
* with Live's when merging it clashed, with a `reconcileDataSchema` as data: the short message
|
|
28
|
+
* editors see. The model gets Camox's instructions instead.
|
|
29
|
+
*/
|
|
30
|
+
export const RECONCILE_ENTRY = "camox.reconcile";
|
|
31
|
+
|
|
32
|
+
export const reconcileDataSchema = z.object({ message: z.string() });
|
|
33
|
+
|
|
34
|
+
export const buildStatusSchema = z.enum(["building", "fixing"]);
|
|
35
|
+
export type BuildStatus = z.infer<typeof buildStatusSchema>;
|
|
36
|
+
|
|
37
|
+
export const buildStatusDataSchema = z.object({ status: buildStatusSchema });
|
|
38
|
+
|
|
18
39
|
/** Who sent a message in a thread. */
|
|
19
40
|
export const threadSenderSchema = z.object({
|
|
20
41
|
id: z.string(),
|
|
@@ -88,6 +109,8 @@ export type TouchedBatch = z.infer<typeof touchedBatchSchema>;
|
|
|
88
109
|
/** A message the studio shows in a thread. */
|
|
89
110
|
export type ThreadMessage =
|
|
90
111
|
| { id: string; from: "editor"; sender: ThreadSender; text: string }
|
|
112
|
+
/** Camox's own message, starting a turn it began itself: why it did. */
|
|
113
|
+
| { id: string; from: "camox"; text: string }
|
|
91
114
|
/** The agent's final message of a turn, in Markdown. */
|
|
92
115
|
| { id: string; from: "agent"; text: string }
|
|
93
116
|
/**
|
|
@@ -115,6 +138,11 @@ export type ThreadView = LiveThreadHandoff & {
|
|
|
115
138
|
messages: readonly ThreadMessage[];
|
|
116
139
|
/** Whether a run is going: the agent is working on a turn, and stopping would end it. */
|
|
117
140
|
running: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* While a run is going, where the build of the code its turn changed stands: the end of the
|
|
143
|
+
* turn is building it, or it failed and the agent is fixing it.
|
|
144
|
+
*/
|
|
145
|
+
status?: BuildStatus;
|
|
118
146
|
};
|
|
119
147
|
|
|
120
148
|
export const EMPTY_THREAD_VIEW: ThreadView = { messages: [], running: false };
|
|
@@ -131,12 +159,21 @@ export const EMPTY_THREAD_STATE: ThreadState = { steps: [], running: false };
|
|
|
131
159
|
/** One transcript entry, as far as the studio's view is concerned. */
|
|
132
160
|
type ThreadStep = { id: string } & (
|
|
133
161
|
| { type: "editor"; sender: ThreadSender; text: string }
|
|
162
|
+
/** Camox starting a turn of its own, saying why. */
|
|
163
|
+
| { type: "camox"; text: string }
|
|
134
164
|
| { type: "answer"; text: string }
|
|
135
165
|
/** A round of tool calls, or their results: the run goes on after it. */
|
|
136
166
|
| { type: "tools" }
|
|
137
167
|
/** A model call that ended without an answer: failed, aborted, or cut short. */
|
|
138
168
|
| { type: "unfinished" }
|
|
139
169
|
| { type: "stopped"; sender: ThreadSender }
|
|
170
|
+
/** Where the build of the turn's code stands, as the turn ends. */
|
|
171
|
+
| { type: "build"; status: BuildStatus }
|
|
172
|
+
/**
|
|
173
|
+
* The end of the turn sent the agent back to work, after its answer: the answer wasn't the
|
|
174
|
+
* turn's last, and what the agent was told is never shown.
|
|
175
|
+
*/
|
|
176
|
+
| { type: "continued" }
|
|
140
177
|
);
|
|
141
178
|
|
|
142
179
|
/** Studio → thread. */
|
|
@@ -211,10 +248,23 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
211
248
|
const id = String(entry.id);
|
|
212
249
|
const sender = senderOf(entry.data);
|
|
213
250
|
if (entry.kind === RUN_STOPPED_ENTRY && sender) return { id, type: "stopped", sender };
|
|
251
|
+
if (entry.kind === BUILD_STATUS_ENTRY) {
|
|
252
|
+
const build = buildStatusDataSchema.safeParse(entry.data).data;
|
|
253
|
+
return build && { id, type: "build", status: build.status };
|
|
254
|
+
}
|
|
214
255
|
const message = entry.model?.find((candidate) => candidate.role !== "system");
|
|
215
256
|
if (!message) return undefined;
|
|
216
|
-
if (
|
|
217
|
-
|
|
257
|
+
if (message.role === "user") {
|
|
258
|
+
if (entry.kind === EDITOR_MESSAGE_ENTRY) {
|
|
259
|
+
return sender && { id, type: "editor", sender, text: textOf(message.content) };
|
|
260
|
+
}
|
|
261
|
+
if (entry.kind === RECONCILE_ENTRY) {
|
|
262
|
+
const reconcile = reconcileDataSchema.safeParse(entry.data).data;
|
|
263
|
+
return reconcile && { id, type: "camox", text: reconcile.message };
|
|
264
|
+
}
|
|
265
|
+
// Editors' messages have a kind of their own: a user message without it is Pi continuing a
|
|
266
|
+
// run as the end of a turn asked.
|
|
267
|
+
return { id, type: "continued" };
|
|
218
268
|
}
|
|
219
269
|
if (message.role === "assistant") return assistantStep(id, message);
|
|
220
270
|
if (message.role === "toolResult") return { id, type: "tools" };
|
|
@@ -222,7 +272,8 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
222
272
|
}
|
|
223
273
|
|
|
224
274
|
/**
|
|
225
|
-
* What the studio shows for a thread. A turn starts with an editor's message,
|
|
275
|
+
* What the studio shows for a thread. A turn starts with an editor's message, or Camox's own
|
|
276
|
+
* when it began the turn itself, and messages sent
|
|
226
277
|
* while it runs join it. It ends with the agent's final message; text written beside tool calls
|
|
227
278
|
* is never shown. A stopped turn shows who stopped it instead, and whatever its run wrote after
|
|
228
279
|
* the stop is left out. A turn that ended without either shows a failed line in its place: its
|
|
@@ -234,10 +285,16 @@ export function threadViewOf({ steps, running, ...drafts }: ThreadState): Thread
|
|
|
234
285
|
// The latest editor's message of a turn still waiting for the agent's message.
|
|
235
286
|
let waiting: string | undefined;
|
|
236
287
|
let unfinished = false;
|
|
288
|
+
let status: BuildStatus | undefined;
|
|
289
|
+
// The turn the agent's last answer ended, and the status before it: the end of the turn may
|
|
290
|
+
// send the agent back to work after it.
|
|
291
|
+
let answered: { waiting: string | undefined; status: BuildStatus | undefined } | undefined;
|
|
237
292
|
/** Ends the turn waiting for the agent's message, if any. */
|
|
238
293
|
const endTurn = () => {
|
|
239
294
|
waiting = undefined;
|
|
240
295
|
unfinished = false;
|
|
296
|
+
status = undefined;
|
|
297
|
+
answered = undefined;
|
|
241
298
|
};
|
|
242
299
|
/** Ends the turn waiting for the agent's message with a failed line, if any. */
|
|
243
300
|
const fail = () => {
|
|
@@ -255,6 +312,11 @@ export function threadViewOf({ steps, running, ...drafts }: ThreadState): Thread
|
|
|
255
312
|
messages.push({ id: step.id, from: "editor", sender: step.sender, text: step.text });
|
|
256
313
|
waiting = step.id;
|
|
257
314
|
break;
|
|
315
|
+
case "camox":
|
|
316
|
+
if (unfinished) fail();
|
|
317
|
+
messages.push({ id: step.id, from: "camox", text: step.text });
|
|
318
|
+
waiting = step.id;
|
|
319
|
+
break;
|
|
258
320
|
case "stopped":
|
|
259
321
|
if (waiting === undefined) break;
|
|
260
322
|
messages.push({ id: step.id, from: "stopped", sender: step.sender });
|
|
@@ -264,7 +326,23 @@ export function threadViewOf({ steps, running, ...drafts }: ThreadState): Thread
|
|
|
264
326
|
// A stopped turn's run may still answer before it ends.
|
|
265
327
|
if (waiting === undefined && messages.at(-1)?.from === "stopped") break;
|
|
266
328
|
messages.push({ id: step.id, from: "agent", text: step.text });
|
|
267
|
-
|
|
329
|
+
{
|
|
330
|
+
const before = { waiting, status };
|
|
331
|
+
endTurn();
|
|
332
|
+
answered = before;
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
335
|
+
case "continued":
|
|
336
|
+
// The answer before wasn't the turn's last. Once the end of the turn gives up fixing,
|
|
337
|
+
// the agent explains without a status.
|
|
338
|
+
if (!answered) break;
|
|
339
|
+
if (messages.at(-1)?.from === "agent") messages.pop();
|
|
340
|
+
waiting = answered.waiting;
|
|
341
|
+
status = answered.status === "fixing" ? "fixing" : undefined;
|
|
342
|
+
answered = undefined;
|
|
343
|
+
break;
|
|
344
|
+
case "build":
|
|
345
|
+
status = step.status;
|
|
268
346
|
break;
|
|
269
347
|
case "tools":
|
|
270
348
|
unfinished = false;
|
|
@@ -275,7 +353,7 @@ export function threadViewOf({ steps, running, ...drafts }: ThreadState): Thread
|
|
|
275
353
|
}
|
|
276
354
|
}
|
|
277
355
|
if (!running) fail();
|
|
278
|
-
return { ...drafts, messages, running };
|
|
356
|
+
return { ...drafts, messages, running, ...(running && status && { status }) };
|
|
279
357
|
}
|
|
280
358
|
|
|
281
359
|
const append = (state: ThreadState, entry: EntryRecord | undefined): ThreadState => {
|