@intentic/sandbox-contract 1.239.0 → 1.242.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/README.md +52 -16
- package/dist/agent-catalog.d.ts +5 -28
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +21 -157
- package/dist/agent-catalog.js.map +1 -1
- package/dist/agent-run-model.d.ts +3 -2
- package/dist/agent-run-model.d.ts.map +1 -1
- package/dist/agent-run-model.js +9 -6
- package/dist/agent-run-model.js.map +1 -1
- package/dist/agent-runtimes.d.ts +27 -0
- package/dist/agent-runtimes.d.ts.map +1 -0
- package/dist/agent-runtimes.js +113 -0
- package/dist/agent-runtimes.js.map +1 -0
- package/dist/approvals-execution.d.ts +11 -0
- package/dist/approvals-execution.d.ts.map +1 -0
- package/dist/approvals-execution.js +44 -0
- package/dist/approvals-execution.js.map +1 -0
- package/dist/arrival.d.ts +151 -0
- package/dist/arrival.d.ts.map +1 -0
- package/dist/arrival.js +56 -0
- package/dist/arrival.js.map +1 -0
- package/dist/card-status.d.ts +5 -0
- package/dist/card-status.d.ts.map +1 -0
- package/dist/card-status.js +74 -0
- package/dist/card-status.js.map +1 -0
- package/dist/chores/chores.d.ts.map +1 -1
- package/dist/chores/chores.js +1 -1
- package/dist/chores/chores.js.map +1 -1
- package/dist/command-classes.d.ts +0 -1
- package/dist/command-classes.d.ts.map +1 -1
- package/dist/command-classes.js +9 -2
- package/dist/command-classes.js.map +1 -1
- package/dist/command-run.d.ts +10 -0
- package/dist/command-run.d.ts.map +1 -0
- package/dist/command-run.js +46 -0
- package/dist/command-run.js.map +1 -0
- package/dist/contracts/agent.contract.d.ts +991 -771
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +483 -1
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +20 -2
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/approvals.contract.d.ts +99 -0
- package/dist/contracts/approvals.contract.d.ts.map +1 -0
- package/dist/contracts/approvals.contract.js +32 -0
- package/dist/contracts/approvals.contract.js.map +1 -0
- package/dist/contracts/ci.contract.d.ts +1 -4
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.js +1 -9
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/claude.contract.d.ts +9 -0
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/cursor.contract.d.ts +6 -0
- package/dist/contracts/cursor.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.d.ts +63 -1
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +22 -4
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts +3 -0
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/keys.contract.d.ts +81 -0
- package/dist/contracts/keys.contract.d.ts.map +1 -0
- package/dist/contracts/keys.contract.js +51 -0
- package/dist/contracts/keys.contract.js.map +1 -0
- package/dist/contracts/panels.contract.d.ts +7 -0
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.js +2 -2
- package/dist/contracts/providers.contract.d.ts +2 -0
- package/dist/contracts/providers.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +99 -101
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/safety.contract.d.ts +35 -0
- package/dist/contracts/safety.contract.d.ts.map +1 -0
- package/dist/contracts/safety.contract.js +32 -0
- package/dist/contracts/safety.contract.js.map +1 -0
- package/dist/contracts/sessions.contract.d.ts +211 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +30 -32
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +277 -1
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.d.ts +31 -3
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/usage.contract.d.ts +29 -20
- package/dist/contracts/usage.contract.d.ts.map +1 -1
- package/dist/contracts/usage.contract.js +13 -0
- package/dist/contracts/usage.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +8 -0
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/conversation-ids.d.ts +2 -0
- package/dist/conversation-ids.d.ts.map +1 -1
- package/dist/conversation-ids.js +8 -0
- package/dist/conversation-ids.js.map +1 -1
- package/dist/credential-material.d.ts +1 -0
- package/dist/credential-material.d.ts.map +1 -1
- package/dist/credential-material.js +24 -2
- package/dist/credential-material.js.map +1 -1
- package/dist/definition.d.ts +51 -93
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js +6 -28
- package/dist/definition.js.map +1 -1
- package/dist/events.d.ts +2947 -991
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +233 -82
- package/dist/events.js.map +1 -1
- package/dist/history-state.d.ts.map +1 -1
- package/dist/history-state.js +1 -0
- package/dist/history-state.js.map +1 -1
- package/dist/index.d.ts +2532 -1054
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/dist/ingress-protocol.d.ts +13 -1
- package/dist/ingress-protocol.d.ts.map +1 -1
- package/dist/ingress-protocol.js +19 -0
- package/dist/ingress-protocol.js.map +1 -1
- package/dist/mentions.d.ts +3 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +13 -0
- package/dist/mentions.js.map +1 -0
- package/dist/needs-action.d.ts +7 -0
- package/dist/needs-action.d.ts.map +1 -0
- package/dist/needs-action.js +3 -0
- package/dist/needs-action.js.map +1 -0
- package/dist/overlay-lint.d.ts +7 -0
- package/dist/overlay-lint.d.ts.map +1 -0
- package/dist/overlay-lint.js +68 -0
- package/dist/overlay-lint.js.map +1 -0
- package/dist/owner-ticket.d.ts +18 -0
- package/dist/owner-ticket.d.ts.map +1 -0
- package/dist/owner-ticket.js +51 -0
- package/dist/owner-ticket.js.map +1 -0
- package/dist/plan-pools.d.ts +12 -0
- package/dist/plan-pools.d.ts.map +1 -0
- package/dist/plan-pools.js +30 -0
- package/dist/plan-pools.js.map +1 -0
- package/dist/provider-specs.d.ts +228 -0
- package/dist/provider-specs.d.ts.map +1 -0
- package/dist/provider-specs.js +124 -0
- package/dist/provider-specs.js.map +1 -0
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/quick-model.js +2 -1
- package/dist/quick-model.js.map +1 -1
- package/dist/runner-protocol.js +2 -2
- package/dist/runner-protocol.js.map +1 -1
- package/dist/runtime-state.d.ts +2 -2
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +1 -1
- package/dist/runtime-state.js.map +1 -1
- package/dist/safety-policy.d.ts +49 -0
- package/dist/safety-policy.d.ts.map +1 -0
- package/dist/safety-policy.js +52 -0
- package/dist/safety-policy.js.map +1 -0
- package/dist/schemas/agent.d.ts +21 -3
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +18 -8
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +19 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +15 -0
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/approvals.d.ts +241 -0
- package/dist/schemas/approvals.d.ts.map +1 -0
- package/dist/schemas/approvals.js +63 -0
- package/dist/schemas/approvals.js.map +1 -0
- package/dist/schemas/automations.d.ts +5 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/ci.d.ts +5 -9
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js +3 -10
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/computers.d.ts +31 -5
- package/dist/schemas/computers.d.ts.map +1 -1
- package/dist/schemas/computers.js +7 -5
- package/dist/schemas/computers.js.map +1 -1
- package/dist/schemas/environment.d.ts +11 -131
- package/dist/schemas/environment.d.ts.map +1 -1
- package/dist/schemas/environment.js +5 -42
- package/dist/schemas/environment.js.map +1 -1
- package/dist/schemas/git-history.d.ts +4 -0
- package/dist/schemas/git-history.d.ts.map +1 -1
- package/dist/schemas/git.d.ts +179 -0
- package/dist/schemas/git.d.ts.map +1 -1
- package/dist/schemas/git.js +39 -1
- package/dist/schemas/git.js.map +1 -1
- package/dist/schemas/panels.d.ts +21 -0
- package/dist/schemas/panels.d.ts.map +1 -1
- package/dist/schemas/panels.js +5 -0
- package/dist/schemas/panels.js.map +1 -1
- package/dist/schemas/plan-limits.d.ts +50 -3
- package/dist/schemas/plan-limits.d.ts.map +1 -1
- package/dist/schemas/plan-limits.js +11 -6
- package/dist/schemas/plan-limits.js.map +1 -1
- package/dist/schemas/provider-oauth.d.ts +6 -0
- package/dist/schemas/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/provider-subscriptions.d.ts +2 -1
- package/dist/schemas/provider-subscriptions.d.ts.map +1 -1
- package/dist/schemas/provider-subscriptions.js +2 -1
- package/dist/schemas/provider-subscriptions.js.map +1 -1
- package/dist/schemas/settings.d.ts +29 -16
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +13 -20
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/system.d.ts +3 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +5 -1
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/workspace-repos.d.ts +14 -0
- package/dist/schemas/workspace-repos.d.ts.map +1 -1
- package/dist/schemas/workspace-repos.js +5 -0
- package/dist/schemas/workspace-repos.js.map +1 -1
- package/dist/schemas/workspace-tree.d.ts +1 -0
- package/dist/schemas/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace-tree.js +3 -0
- package/dist/schemas/workspace-tree.js.map +1 -1
- package/dist/transcript-fold.d.ts +31 -0
- package/dist/transcript-fold.d.ts.map +1 -0
- package/dist/transcript-fold.js +457 -0
- package/dist/transcript-fold.js.map +1 -0
- package/dist/workspace-state.d.ts +20 -3
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +22 -2
- package/dist/workspace-state.js.map +1 -1
- package/package.json +27 -5
- package/src/agent-catalog.test.ts +1 -1
- package/src/agent-catalog.ts +76 -555
- package/src/agent-run-model.test.ts +31 -15
- package/src/agent-run-model.ts +26 -12
- package/src/agent-runtimes.ts +419 -0
- package/src/approvals-execution.ts +96 -0
- package/src/arrival.ts +160 -0
- package/src/capability-ledger.test.ts +6 -4
- package/src/card-status.ts +93 -0
- package/src/chores/chores.ts +6 -3
- package/src/command-classes.test.ts +25 -0
- package/src/command-classes.ts +47 -5
- package/src/command-run.ts +78 -0
- package/src/contract-lock.test.ts +1 -1
- package/src/contract-lock.ts +2 -2
- package/src/contracts/agents.contract.ts +35 -2
- package/src/contracts/approvals.contract.ts +37 -0
- package/src/contracts/ci.contract.ts +0 -13
- package/src/contracts/git.contract.ts +28 -3
- package/src/contracts/keys.contract.ts +79 -0
- package/src/contracts/prepush.contract.ts +3 -3
- package/src/contracts/safety.contract.ts +48 -0
- package/src/contracts/usage.contract.ts +22 -0
- package/src/conversation-ids.test.ts +31 -1
- package/src/conversation-ids.ts +37 -3
- package/src/credential-material.test.ts +69 -1
- package/src/credential-material.ts +86 -5
- package/src/definition.ts +25 -53
- package/src/events.ts +508 -198
- package/src/history-state.ts +6 -0
- package/src/index.ts +24 -5
- package/src/ingress-protocol.ts +52 -1
- package/src/mentions.test.ts +22 -0
- package/src/mentions.ts +25 -0
- package/src/needs-action.ts +14 -0
- package/src/overlay-lint.test.ts +89 -0
- package/src/overlay-lint.ts +116 -0
- package/src/owner-ticket.test.ts +54 -0
- package/src/owner-ticket.ts +102 -0
- package/src/plan-pools.test.ts +71 -0
- package/src/plan-pools.ts +82 -0
- package/src/provider-specs.test.ts +130 -0
- package/src/provider-specs.ts +352 -0
- package/src/quick-model.ts +9 -5
- package/src/runner-protocol.ts +2 -2
- package/src/runtime-state.ts +6 -6
- package/src/safety-policy.ts +165 -0
- package/src/schemas/agent.ts +62 -17
- package/src/schemas/agents.ts +71 -0
- package/src/schemas/approvals.ts +174 -0
- package/src/schemas/ci.ts +19 -26
- package/src/schemas/computers.ts +52 -11
- package/src/schemas/environment.ts +21 -100
- package/src/schemas/git.ts +121 -6
- package/src/schemas/issues.ts +1 -1
- package/src/schemas/panels.ts +20 -0
- package/src/schemas/plan-limits.ts +58 -11
- package/src/schemas/provider-subscriptions.ts +7 -2
- package/src/schemas/settings.ts +67 -93
- package/src/schemas/system.ts +12 -1
- package/src/schemas/workspace-repos.ts +9 -0
- package/src/schemas/workspace-tree.ts +11 -0
- package/src/transcript-fold.test.ts +493 -0
- package/src/transcript-fold.ts +655 -0
- package/src/workspace-state.test.ts +77 -29
- package/src/workspace-state.ts +99 -31
- package/dist/contracts/drafts.contract.d.ts +0 -57
- package/dist/contracts/drafts.contract.d.ts.map +0 -1
- package/dist/contracts/drafts.contract.js +0 -32
- package/dist/contracts/drafts.contract.js.map +0 -1
- package/dist/definition-action.d.ts +0 -7
- package/dist/definition-action.d.ts.map +0 -1
- package/dist/definition-action.js +0 -3
- package/dist/definition-action.js.map +0 -1
- package/dist/publish-drafts.d.ts +0 -7
- package/dist/publish-drafts.d.ts.map +0 -1
- package/dist/publish-drafts.js +0 -20
- package/dist/publish-drafts.js.map +0 -1
- package/dist/schemas/drafts.d.ts +0 -84
- package/dist/schemas/drafts.d.ts.map +0 -1
- package/dist/schemas/drafts.js +0 -44
- package/dist/schemas/drafts.js.map +0 -1
- package/src/contracts/drafts.contract.ts +0 -35
- package/src/definition-action.ts +0 -8
- package/src/publish-drafts.ts +0 -59
- package/src/schemas/drafts.ts +0 -108
package/src/events.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { AgentHarnessSchema, AgentProviderSchema, PermissionModeSchema } from ".
|
|
|
3
3
|
import { AgentSummarySchema, LandConflictSchema } from "./schemas/agents.js";
|
|
4
4
|
import { RateLimitInfoSchema } from "./schemas/claude-gate.js";
|
|
5
5
|
import { FastModeStateSchema } from "./schemas/fast-mode.js";
|
|
6
|
-
import { AgentReplySchema, UsageWindowSchema } from "./schemas/plan-limits.js";
|
|
6
|
+
import { AccountUsageSchema, AgentReplySchema, ProviderRefusalSchema, UsageWindowSchema } from "./schemas/plan-limits.js";
|
|
7
7
|
import { ShareDetailSchema } from "./schemas/share.js";
|
|
8
8
|
import { MemberRoleSchema } from "./schemas/shared.js";
|
|
9
9
|
import { SubagentKindSchema, SubagentStatusSchema, SubagentVerificationSchema } from "./schemas/terminal.js";
|
|
@@ -45,7 +45,11 @@ export type AskQuestion = z.infer<typeof AskQuestionSchema>;
|
|
|
45
45
|
export const ProgramAskSchema = z.object({
|
|
46
46
|
text: z.string().describe("What would run."),
|
|
47
47
|
language: z.enum(["bash", "javascript"]).describe("Which of the two backends it is written for, named as the grammar that colours it."),
|
|
48
|
-
truncated: z
|
|
48
|
+
truncated: z
|
|
49
|
+
.boolean()
|
|
50
|
+
.describe(
|
|
51
|
+
"Whether this is an excerpt of a longer program, so the card can say so instead of ending mid-word. An excerpt always carries the flagged fragment: the beginning, then a window around the fragment, with any skipped middle written into the text as a bracketed count.",
|
|
52
|
+
),
|
|
49
53
|
spans: z
|
|
50
54
|
.array(z.object({ start: z.number().int().nonnegative(), end: z.number().int().nonnegative() }))
|
|
51
55
|
.describe("Which fragments of the text put it in the class that held it. Offsets into text, in order, never overlapping."),
|
|
@@ -76,11 +80,16 @@ export const PermissionAskSchema = z.object({
|
|
|
76
80
|
program: ProgramAskSchema.optional().describe(
|
|
77
81
|
"The program this card is holding, when the card is about one. Present on a command gate's card and absent on every other permission ask.",
|
|
78
82
|
),
|
|
83
|
+
/* THE JUDGE'S OWN SENTENCE, and on a command card it is the reason the card exists rather than a note added
|
|
84
|
+
* to it: the judge read the owner's policy and the program and decided this needed asking, and this is what
|
|
85
|
+
* it decided. Written by the quick model from the program text and the policy, never by the agent being
|
|
86
|
+
* gated — a card whose persuasive half was authored by the thing it is stopping argues for its own approval,
|
|
87
|
+
* and the turns that raise cards are exactly the ones whose account of themselves may be a stranger's. */
|
|
79
88
|
explain: z
|
|
80
89
|
.string()
|
|
81
90
|
.optional()
|
|
82
91
|
.describe(
|
|
83
|
-
"One plain sentence saying what the program does and why
|
|
92
|
+
"One plain sentence saying what the program does and why it is being asked about. Written by the judge that read your safety policy, never by the agent being gated.",
|
|
84
93
|
),
|
|
85
94
|
});
|
|
86
95
|
export type PermissionAsk = z.infer<typeof PermissionAskSchema>;
|
|
@@ -286,19 +295,192 @@ export const ToolCallContentSchema = z.discriminatedUnion("type", [
|
|
|
286
295
|
]);
|
|
287
296
|
export type ToolCallContent = z.infer<typeof ToolCallContentSchema>;
|
|
288
297
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
298
|
+
/* WHAT A PARKED CARD IS ABOUT: the document the turn wrote and is now asking a question against.
|
|
299
|
+
*
|
|
300
|
+
* A card asks for a decision; until this it carried no SUBJECT. The commonest shape of a real decision is "I
|
|
301
|
+
* analysed this and wrote it up, now choose", and the write-up went into a file whose card had already folded
|
|
302
|
+
* itself into `Write · +135 −0` twenty tool calls back. So the reader was asked to choose between options
|
|
303
|
+
* describing a document the chat had never shown them.
|
|
304
|
+
*
|
|
305
|
+
* Carried BY VALUE rather than as a path, for the same reason the diff on a tool call is: the bytes are already
|
|
306
|
+
* in hand when the card is raised, a path would make the card's meaning depend on a file that keeps changing
|
|
307
|
+
* under it, and a restored or published transcript has no workspace to go read. The path rides along anyway, so
|
|
308
|
+
* a document past the wire cap still has somewhere to send the reader.
|
|
309
|
+
*
|
|
310
|
+
* Nothing is asked of the MODEL for this. It calls `ask` exactly as before; the daemon knows what the turn
|
|
311
|
+
* wrote, because every write came past it as a frame (documents.ts decides which of them is a document). A
|
|
312
|
+
* harness that can see the answer must not spend prompt on asking the model to repeat it. */
|
|
313
|
+
export const CardDocumentSchema = z.object({
|
|
314
|
+
path: z.string().describe("Where it lives, as a workspace path."),
|
|
315
|
+
title: z.string().describe("What it is called: its opening heading, or its file name."),
|
|
316
|
+
markdown: z.string().describe("The document itself."),
|
|
317
|
+
truncated: z.boolean().optional().describe("It was clipped at the wire cap; the file on disk has more."),
|
|
318
|
+
plan: z.boolean().optional().describe("It is one of the CLI's plan files, written to be approved rather than merely read."),
|
|
319
|
+
});
|
|
320
|
+
export type CardDocument = z.infer<typeof CardDocumentSchema>;
|
|
321
|
+
|
|
322
|
+
/* ONE CARD'S OWN FIELDS, spelled once. Three readers carry the same card and must agree on what it is: the
|
|
323
|
+
* frame that raises it (AgentEventSchema below), the journal entry that keeps a parked one across a restart
|
|
324
|
+
* (ParkedCardSchema), and the record row that keeps it for good (TranscriptRowSchema's card fields). A shape
|
|
325
|
+
* declared inline in each was three shapes with one name. */
|
|
326
|
+
const REQUEST_ID = z.string().describe("What to send back when you answer.");
|
|
327
|
+
const planCard = {
|
|
328
|
+
requestId: REQUEST_ID,
|
|
329
|
+
text: z.string().describe("The plan itself."),
|
|
330
|
+
// Present when the adjacent plan prose POINTS at a document instead of being one: the model wrote the real
|
|
331
|
+
// plan to a file and summarised it there. Absent when the text already is the whole plan.
|
|
332
|
+
document: CardDocumentSchema.optional().describe("The write-up this plan refers to, when the plan itself is a pointer to one."),
|
|
333
|
+
};
|
|
334
|
+
const questionCard = {
|
|
335
|
+
requestId: REQUEST_ID,
|
|
336
|
+
questions: z.array(AskQuestionSchema).describe("What it wants to know."),
|
|
337
|
+
document: CardDocumentSchema.optional().describe("The document this turn wrote and is asking about, so the choice can be read beside it."),
|
|
338
|
+
};
|
|
339
|
+
const permissionCard = { requestId: REQUEST_ID };
|
|
340
|
+
// The agent's browser needs a person: it parked mid-sign-in on something it cannot clear itself (a captcha,
|
|
341
|
+
// a password it does not hold, a phone check). `session` names the browser session on /browsers, the card's
|
|
342
|
+
// one action is going THERE, where the live stage and Take control already are; the Browsers banner and this
|
|
343
|
+
// card resolve the same requestId. `account` is the capability the sign-in is for, so the card can say whose
|
|
344
|
+
// login is stuck even after the browser has navigated somewhere unrecognizable.
|
|
345
|
+
const browserHelpCard = {
|
|
346
|
+
requestId: z.string(),
|
|
347
|
+
session: z.string(),
|
|
348
|
+
account: z.string(),
|
|
349
|
+
message: z.string(),
|
|
350
|
+
};
|
|
351
|
+
// The agent's TERMINAL needs a person: a command it started is sitting at a prompt it cannot answer (a
|
|
352
|
+
// one-time password, a security-key touch, a confirm). `session` names the tmux session on the terminal
|
|
353
|
+
// panel, the card's one action is going THERE, where the live pane and its prompt already are, which is
|
|
354
|
+
// the same division of labour the browser card has with /browsers.
|
|
355
|
+
const terminalHelpCard = {
|
|
356
|
+
requestId: z.string(),
|
|
357
|
+
session: z.string(),
|
|
358
|
+
message: z.string(),
|
|
359
|
+
};
|
|
360
|
+
const serviceOfferCard = { requestId: z.string(), offer: ServiceOfferSchema };
|
|
361
|
+
const capabilityOfferCard = { requestId: z.string(), offer: CapabilityOfferSchema };
|
|
362
|
+
const paymentOfferCard = { requestId: z.string(), offer: PaymentOfferSchema };
|
|
363
|
+
|
|
364
|
+
/* HOW AN OFFER'S ACCEPTED HALF ENDED, the follow-up that lands on the card after the click. Each is the body of
|
|
365
|
+
* the frame that reports it (`service_receipt`, `capability_outcome`, `payment_receipt`) and the field the
|
|
366
|
+
* record keeps it in, one shape for both, so a receipt reopened tomorrow says exactly what the live card said. */
|
|
367
|
+
export const ServiceReceiptSchema = z.object({
|
|
368
|
+
outcome: z.enum(["ok", "refunded", "refused"]),
|
|
369
|
+
credits: z.number(),
|
|
370
|
+
remaining: z.number().optional(),
|
|
371
|
+
});
|
|
372
|
+
export type ServiceReceipt = z.infer<typeof ServiceReceiptSchema>;
|
|
373
|
+
export const CapabilityOutcomeSchema = z.object({
|
|
374
|
+
outcome: z.enum(["connected", "unfinished"]),
|
|
375
|
+
id: z.string().optional(),
|
|
376
|
+
});
|
|
377
|
+
export type CapabilityOutcome = z.infer<typeof CapabilityOutcomeSchema>;
|
|
378
|
+
export const PaymentReceiptSchema = z.object({
|
|
379
|
+
outcome: z.enum(["paid", "failed"]),
|
|
380
|
+
amountUsd: z.string(),
|
|
381
|
+
transaction: z.string().optional(),
|
|
382
|
+
network: z.string().optional(),
|
|
383
|
+
});
|
|
384
|
+
export type PaymentReceipt = z.infer<typeof PaymentReceiptSchema>;
|
|
385
|
+
|
|
386
|
+
/* THE THREE RESTORABLE CARDS, named so the turn journal can hold them verbatim: a parked turn's raised cards
|
|
387
|
+
* are written down beside its prompt (sandbox turn-journal.ts), and a daemon death under the park restores the
|
|
388
|
+
* very same frames instead of ending the turn `interrupted`, the card the user was about to answer survives
|
|
389
|
+
* the restart that killed the process holding it. The two handover cards are deliberately not among them:
|
|
390
|
+
* `browser_help`'s Chromium and `terminal_help`'s waiting command both die with the container, so those parks
|
|
391
|
+
* cannot be restored, only reported. */
|
|
392
|
+
const PlanCardSchema = z.object({
|
|
393
|
+
kind: z.literal("plan").describe("The agent has written a plan and is waiting for a yes."),
|
|
394
|
+
...planCard,
|
|
395
|
+
});
|
|
396
|
+
const QuestionCardSchema = z.object({
|
|
397
|
+
kind: z.literal("question").describe("The agent has asked you something and is waiting."),
|
|
398
|
+
...questionCard,
|
|
399
|
+
});
|
|
400
|
+
const PermissionCardSchema = PermissionAskSchema.extend({
|
|
401
|
+
kind: z.literal("permission").describe("The agent wants to use a tool it needs permission for."),
|
|
402
|
+
...permissionCard,
|
|
403
|
+
});
|
|
404
|
+
export const ParkedCardSchema = z.discriminatedUnion("kind", [PlanCardSchema, QuestionCardSchema, PermissionCardSchema]);
|
|
405
|
+
export type ParkedCard = z.infer<typeof ParkedCardSchema>;
|
|
406
|
+
|
|
407
|
+
// ---- transcript cards ----
|
|
408
|
+
/* THE CARDS A TURN PARKED ON, as a transcript row carries them: the card exactly as it was raised, how it was
|
|
409
|
+
* settled, and whatever landed on it afterwards (a permission's late explanation, an offer's stream and
|
|
410
|
+
* receipt). One shape for the live row and the recorded one, because they are the same row: the daemon folds
|
|
411
|
+
* the turn's frames into these rows as they stream (transcript-fold.ts) and writes the same rows down when the
|
|
412
|
+
* turn settles, so a chat reopened tomorrow is the chat that was on screen.
|
|
413
|
+
*
|
|
414
|
+
* The STATUS is settled by the fold, from the reply that released the card (card-status.ts), and rides the
|
|
415
|
+
* row rather than the reply it came from: every reader wants the verdict, and the one derivation lives beside
|
|
416
|
+
* the fold that applies it. `pending` is a card the turn is still parked on; `cancelled` is nobody answering,
|
|
417
|
+
* the turn stopped or died under the card, which is not a decision and does not read back as one. */
|
|
418
|
+
export const PlanStatusSchema = z.enum(["pending", "approved", "rejected", "cancelled"]);
|
|
419
|
+
export type PlanStatus = z.infer<typeof PlanStatusSchema>;
|
|
420
|
+
export const QuestionStatusSchema = z.enum(["pending", "answered", "cancelled"]);
|
|
421
|
+
export type QuestionStatus = z.infer<typeof QuestionStatusSchema>;
|
|
422
|
+
export const PermissionStatusSchema = z.enum(["pending", "allowed", "always", "denied", "cancelled"]);
|
|
423
|
+
export type PermissionStatus = z.infer<typeof PermissionStatusSchema>;
|
|
424
|
+
export const HelpStatusSchema = z.enum(["pending", "helped", "declined", "cancelled"]);
|
|
425
|
+
export type HelpStatus = z.infer<typeof HelpStatusSchema>;
|
|
426
|
+
export const OfferStatusSchema = z.enum(["pending", "approved", "skipped", "cancelled"]);
|
|
427
|
+
export type OfferStatus = z.infer<typeof OfferStatusSchema>;
|
|
428
|
+
// A yes settles the DECISION, not the ask: the owner is now setting the capability up, so the card moves to
|
|
429
|
+
// `connecting` and stays there until the capability_outcome frame says how the setup ended.
|
|
430
|
+
export const CapabilityOfferStatusSchema = z.enum(["pending", "connecting", "skipped", "cancelled"]);
|
|
431
|
+
export type CapabilityOfferStatus = z.infer<typeof CapabilityOfferStatusSchema>;
|
|
432
|
+
|
|
433
|
+
export const TranscriptPlanSchema = z.object({ ...planCard, status: PlanStatusSchema.describe("Where the decision stands.") });
|
|
434
|
+
export type TranscriptPlan = z.infer<typeof TranscriptPlanSchema>;
|
|
435
|
+
export const TranscriptQuestionSchema = z.object({
|
|
436
|
+
...questionCard,
|
|
437
|
+
status: QuestionStatusSchema.describe("Where the answer stands."),
|
|
438
|
+
answers: z
|
|
439
|
+
.record(z.string(), z.array(z.string()))
|
|
440
|
+
.optional()
|
|
441
|
+
.describe("What was chosen, keyed by the question, with the chosen labels or the user's own words."),
|
|
442
|
+
});
|
|
443
|
+
export type TranscriptQuestion = z.infer<typeof TranscriptQuestionSchema>;
|
|
444
|
+
// `explain`, the judge's sentence, lands here through PermissionAskSchema; it is on the card from the moment
|
|
445
|
+
// it is raised, so nothing patches it in afterwards.
|
|
446
|
+
export const TranscriptPermissionSchema = PermissionAskSchema.extend({ ...permissionCard, status: PermissionStatusSchema.describe("Where the decision stands.") });
|
|
447
|
+
export type TranscriptPermission = z.infer<typeof TranscriptPermissionSchema>;
|
|
448
|
+
export const TranscriptBrowserHelpSchema = z.object({ ...browserHelpCard, status: HelpStatusSchema.describe("How the hand-over ended.") });
|
|
449
|
+
export type TranscriptBrowserHelp = z.infer<typeof TranscriptBrowserHelpSchema>;
|
|
450
|
+
export const TranscriptTerminalHelpSchema = z.object({ ...terminalHelpCard, status: HelpStatusSchema.describe("How the hand-over ended.") });
|
|
451
|
+
export type TranscriptTerminalHelp = z.infer<typeof TranscriptTerminalHelpSchema>;
|
|
452
|
+
export const TranscriptServiceOfferSchema = z.object({
|
|
453
|
+
...serviceOfferCard,
|
|
454
|
+
status: OfferStatusSchema.describe("Where the decision stands."),
|
|
455
|
+
events: z.array(ServiceStreamEventSchema).optional().describe("The approved run's stream, in order (the service_event frames)."),
|
|
456
|
+
receipt: ServiceReceiptSchema.optional().describe("How the approved run ended (the service_receipt frame)."),
|
|
457
|
+
});
|
|
458
|
+
export type TranscriptServiceOffer = z.infer<typeof TranscriptServiceOfferSchema>;
|
|
459
|
+
export const TranscriptCapabilityOfferSchema = z.object({
|
|
460
|
+
...capabilityOfferCard,
|
|
461
|
+
status: CapabilityOfferStatusSchema.describe("Where the decision stands."),
|
|
462
|
+
outcome: CapabilityOutcomeSchema.optional().describe("How an accepted ask's setup ended (the capability_outcome frame)."),
|
|
463
|
+
});
|
|
464
|
+
export type TranscriptCapabilityOffer = z.infer<typeof TranscriptCapabilityOfferSchema>;
|
|
465
|
+
export const TranscriptPaymentOfferSchema = z.object({
|
|
466
|
+
...paymentOfferCard,
|
|
467
|
+
status: OfferStatusSchema.describe("Where the decision stands."),
|
|
468
|
+
receipt: PaymentReceiptSchema.optional().describe("How the approved payment ended (the payment_receipt frame)."),
|
|
469
|
+
});
|
|
470
|
+
export type TranscriptPaymentOffer = z.infer<typeof TranscriptPaymentOfferSchema>;
|
|
471
|
+
|
|
472
|
+
// ---- transcript rows ----
|
|
473
|
+
// What a conversation is made of, on every surface: the rows the daemon folds a turn's frames into as they
|
|
474
|
+
// stream (the live chat renders these, patched as they grow), the rows the record keeps once the turn settles,
|
|
475
|
+
// and the rows /agents/{id}/transcript replays into a reopened tab. One shape because it is one thing: a
|
|
476
|
+
// reopened chat REDRAWS the transcript the user was looking at rather than paraphrasing it, so a row keeps the
|
|
477
|
+
// assistant's thinking and the tool cards its block ran, which is also what lets a runtime handoff carry more
|
|
478
|
+
// than bare prose across to a replacement session (see runtime-history.ts).
|
|
296
479
|
//
|
|
297
|
-
// One
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
export const RestoredToolCallSchema: z.ZodType<RestoredToolCall> = z.lazy(() =>
|
|
480
|
+
// One tool card. A subagent's own calls and its thinking nest under the Agent card that spawned them, so a
|
|
481
|
+
// delegation reads as one unit instead of a flat run of siblings. z.lazy because the shape refers to itself: a
|
|
482
|
+
// subagent that delegates nests one level deeper.
|
|
483
|
+
export const TranscriptToolSchema: z.ZodType<TranscriptTool> = z.lazy(() =>
|
|
302
484
|
z.object({
|
|
303
485
|
id: z.string().describe("The call's id."),
|
|
304
486
|
name: z.string().describe("Which tool."),
|
|
@@ -310,17 +492,40 @@ export const RestoredToolCallSchema: z.ZodType<RestoredToolCall> = z.lazy(() =>
|
|
|
310
492
|
locations: z.array(ToolCallLocationSchema).optional().describe("The files it touched."),
|
|
311
493
|
content: z.array(ToolCallContentSchema).optional().describe("What it produced: text, a change to a file, or a picture."),
|
|
312
494
|
children: z
|
|
313
|
-
.array(
|
|
495
|
+
.array(TranscriptToolSchema)
|
|
314
496
|
.optional()
|
|
315
497
|
.describe(
|
|
316
498
|
"Calls a delegated helper made, nested under the call that started it, so a reopened conversation redraws the delegation rather than collapsing it into one result.",
|
|
317
499
|
),
|
|
318
500
|
thinking: z.string().optional().describe("What the agent was reasoning about around this call."),
|
|
501
|
+
subagent: TranscriptSubagentSchema.optional().describe(
|
|
502
|
+
"The helper this call started, as the daemon's registry sees it: what it is, how it is going, what it has spent. What a card can say about a backgrounded child whose result is minutes away.",
|
|
503
|
+
),
|
|
319
504
|
}),
|
|
320
505
|
);
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
506
|
+
/* THE CHILD A CALL STARTED, on the card whose id the `subagent`/`subagent_update` frames name (that call's own),
|
|
507
|
+
* so no correlation is needed: an Agent card wears its subagent's live state, and a Bash card that turned out
|
|
508
|
+
* to be a `codex exec` wears its delegate's. The identifying fields arrive once (the `subagent` frame), the
|
|
509
|
+
* moving ones (status, spend, what it is doing) replace as each update lands. */
|
|
510
|
+
export const TranscriptSubagentSchema = z.object({
|
|
511
|
+
kind: SubagentKindSchema,
|
|
512
|
+
agentType: z.string().optional(),
|
|
513
|
+
description: z.string().optional(),
|
|
514
|
+
model: z.string().optional(),
|
|
515
|
+
provider: z.string().optional(),
|
|
516
|
+
background: z.boolean().optional(),
|
|
517
|
+
status: SubagentStatusSchema,
|
|
518
|
+
tokens: z.number().optional(),
|
|
519
|
+
toolUses: z.number().optional(),
|
|
520
|
+
lastTool: z.string().optional(),
|
|
521
|
+
summary: z.string().optional(),
|
|
522
|
+
error: z.string().optional(),
|
|
523
|
+
verification: SubagentVerificationSchema.optional(),
|
|
524
|
+
});
|
|
525
|
+
export type TranscriptSubagent = z.infer<typeof TranscriptSubagentSchema>;
|
|
526
|
+
// Mutable, unlike most of this file: the fold settles a card IN PLACE when its result arrives turns later
|
|
527
|
+
// (transcript-fold.ts's `cards` map, readWorkspaceSession's `awaiting`), which is what saves it a second pass.
|
|
528
|
+
export interface TranscriptTool {
|
|
324
529
|
id: string;
|
|
325
530
|
name: string;
|
|
326
531
|
category: ToolKind;
|
|
@@ -328,8 +533,9 @@ export interface RestoredToolCall {
|
|
|
328
533
|
target?: string | undefined;
|
|
329
534
|
locations?: ToolCallLocation[] | undefined;
|
|
330
535
|
content?: ToolCallContent[] | undefined;
|
|
331
|
-
children?:
|
|
536
|
+
children?: TranscriptTool[] | undefined;
|
|
332
537
|
thinking?: string | undefined;
|
|
538
|
+
subagent?: TranscriptSubagent | undefined;
|
|
333
539
|
}
|
|
334
540
|
|
|
335
541
|
/* ONE NOTE THE DAEMON PUT IN FRONT OF A USER'S MESSAGE, as both audiences see it: the model reads `text`, and
|
|
@@ -341,15 +547,26 @@ export const TurnNoteSchema = z.object({
|
|
|
341
547
|
});
|
|
342
548
|
export type TurnNote = z.infer<typeof TurnNoteSchema>;
|
|
343
549
|
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
550
|
+
// End-of-turn accounting (assistant rows only, the last bubble of a turn): what the turn cost, attached where
|
|
551
|
+
// the answer ended so a reader can see what each exchange spent.
|
|
552
|
+
export const TranscriptUsageSchema = z.object({
|
|
553
|
+
costUsd: z.number().optional(),
|
|
554
|
+
inputTokens: z.number().optional(),
|
|
555
|
+
outputTokens: z.number().optional(),
|
|
556
|
+
durationMs: z.number().optional(),
|
|
557
|
+
numTurns: z.number().optional(),
|
|
558
|
+
});
|
|
559
|
+
export type TranscriptUsage = z.infer<typeof TranscriptUsageSchema>;
|
|
560
|
+
|
|
561
|
+
// One row. Each block of the agent's prose is its own, with the tool cards that block introduced, which is what
|
|
562
|
+
// reproduces the way a turn actually unfolded rather than collapsing its whole narration into one bubble with
|
|
563
|
+
// every tool hanging off the end.
|
|
564
|
+
export const TranscriptRowSchema = z.object({
|
|
565
|
+
/* `notice` is neither side of the conversation: it is something that HAPPENED to the turn, a refusal, a
|
|
566
|
+
* landed delta, a compaction, a stop, written down so a reopened conversation says it too. The one that
|
|
567
|
+
* matters most is a refused turn: a provider that answers "your organization has disabled Claude
|
|
568
|
+
* subscription access" produced no assistant text, so a transcript of the two speakers alone ends on the
|
|
569
|
+
* user's message and reads as broken. */
|
|
353
570
|
role: z
|
|
354
571
|
.enum(["user", "assistant", "notice"])
|
|
355
572
|
.describe(
|
|
@@ -371,28 +588,36 @@ export const RestoredMessageSchema = z.object({
|
|
|
371
588
|
.describe(
|
|
372
589
|
"When it was sent, in milliseconds. On the user's rows only, because that is the only moment actually known: a turn's own frames arrive with no clock, so stamping the agent's rows could only ever mean the whole turn's start or end.",
|
|
373
590
|
),
|
|
374
|
-
// Files the user attached to this turn (user
|
|
375
|
-
//
|
|
591
|
+
// Files the user attached to this turn (user rows only) as workspace-relative paths, the uploads alone:
|
|
592
|
+
// a path @-mentioned inline in the text is already visible there and is not drawn as a chip.
|
|
376
593
|
attachments: z.array(z.string()).optional().describe("Files attached to this message, as workspace paths."),
|
|
377
|
-
/* The checkpoint this message can be rewound to (user
|
|
378
|
-
*
|
|
379
|
-
*
|
|
594
|
+
/* The checkpoint this message can be rewound to (user rows only), and where this message sits in the
|
|
595
|
+
* conversation's record, which is what the rewind route addresses it by. Never stored: both are stamped
|
|
596
|
+
* onto the live row by the turn's own `checkpoint` frame and onto a replayed row by the read that serves
|
|
597
|
+
* it, looked up from the daemon's rewind points, which a rewind rewrites, so a reopened tab offers exactly
|
|
598
|
+
* the turns that are still there to go back to. */
|
|
380
599
|
checkpointId: z
|
|
381
600
|
.string()
|
|
382
601
|
.optional()
|
|
383
602
|
.describe(
|
|
384
603
|
"The saved point this message can be rewound to. Looked up on each read rather than stored, so what is offered is exactly what is still there to go back to.",
|
|
385
604
|
),
|
|
605
|
+
rewindIndex: z
|
|
606
|
+
.number()
|
|
607
|
+
.int()
|
|
608
|
+
.nonnegative()
|
|
609
|
+
.optional()
|
|
610
|
+
.describe("This message's position in the conversation's record, which is how a rewind names it. Present only beside a checkpoint."),
|
|
386
611
|
thinking: z.string().optional().describe("What the agent was reasoning about."),
|
|
387
|
-
tools: z.array(
|
|
612
|
+
tools: z.array(TranscriptToolSchema).optional().describe("The tool calls this part of the turn made."),
|
|
613
|
+
todos: z.array(TodoItemSchema).optional().describe("The agent's task checklist, as of this bubble."),
|
|
614
|
+
usage: TranscriptUsageSchema.optional().describe("What the turn cost, on the bubble its answer ended in."),
|
|
388
615
|
/* What the daemon added to this turn's message (user rows only), the same notes the live `preamble` frame
|
|
389
|
-
* carries
|
|
390
|
-
* (sessions/turn-transcript.ts); only a conversation adopted from a provider's session store recovers them
|
|
391
|
-
* by parsing the composed prompt kept there, the one store the daemon never wrote typed.
|
|
616
|
+
* carries, read off that frame by the fold.
|
|
392
617
|
*
|
|
393
618
|
* On the message rather than as a row of its own, and that matters twice: they ARE part of what was
|
|
394
619
|
* sent, and a record row per turn preamble would break the one-row-per-bubble correspondence a branch counts
|
|
395
|
-
* with
|
|
620
|
+
* with. */
|
|
396
621
|
notes: z
|
|
397
622
|
.array(TurnNoteSchema)
|
|
398
623
|
.optional()
|
|
@@ -414,29 +639,118 @@ export const RestoredMessageSchema = z.object({
|
|
|
414
639
|
.describe(
|
|
415
640
|
"A person wrote this in the agent's voice, with no turn behind it. Marked for the human re-reading the conversation months later, so their own words do not pass as the agent's. The agent itself never sees the mark.",
|
|
416
641
|
),
|
|
417
|
-
/* THE ONE-PRESS OFFER A
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
* chat's other one-press notices are drawn live and never recorded, so they never needed this; a routed turn
|
|
423
|
-
* is different precisely because the whole point of recording it is that somebody reads it LATER. A KIND, not
|
|
424
|
-
* a callback, exactly as the live ones are: the reader decides what the press does and whether the offer
|
|
425
|
-
* still stands (a chat already holding its pick shows a settled sentence, not a stale button). */
|
|
642
|
+
/* THE ONE-PRESS OFFER A NOTICE CARRIES (notice rows only), named rather than inferred from its words: the
|
|
643
|
+
* landed notice's "keep future work on the branch", the outage notice's "stop resuming these by itself",
|
|
644
|
+
* the terminal a dependency install the daemon just started is running in, and the routed turn's "keep
|
|
645
|
+
* this chat on my pick". A KIND, not a callback: the chat decides what the press does and whether the
|
|
646
|
+
* offer still stands (a chat already holding its pick shows a settled sentence, not a stale button). */
|
|
426
647
|
noticeAction: z
|
|
427
|
-
.enum(["tierHold"])
|
|
648
|
+
.enum(["landHold", "outageOptOut", "depsInstall", "tierHold"])
|
|
428
649
|
.optional()
|
|
429
|
-
.describe("A one-press follow-up this
|
|
650
|
+
.describe("A one-press follow-up this notice offers, by name. The chat decides what it does and whether it still applies."),
|
|
651
|
+
/* A WAIT THIS NOTICE DESCRIBES that had not finished when it was written (notice rows only): the chat draws
|
|
652
|
+
* a spinner over it while the wait is on, and the plain line once it is over. A KIND rather than a boolean
|
|
653
|
+
* because whether the wait is STILL running is a fact about the conversation now, not about a row in a
|
|
654
|
+
* record: the reader pairs the kind with the live state that answers it. */
|
|
655
|
+
noticeWait: z.enum(["credentialRenewal"]).optional().describe("The wait this notice describes, by name, so a reader can say whether it is still on."),
|
|
656
|
+
/* THE CARD THIS BUBBLE PARKED ON (assistant rows only), at most one: a card closes the bubble it lands in,
|
|
657
|
+
* so the next thing the agent says opens a fresh row beneath it. One field per kind rather than one union
|
|
658
|
+
* field, so a reader reaches the card it draws by name. */
|
|
659
|
+
plan: TranscriptPlanSchema.optional().describe("The plan this row asked approval for, and the answer."),
|
|
660
|
+
question: TranscriptQuestionSchema.optional().describe("The questions this row asked, and the picks that answered them."),
|
|
661
|
+
permission: TranscriptPermissionSchema.optional().describe("The tool this row asked permission for, and the decision."),
|
|
662
|
+
browserHelp: TranscriptBrowserHelpSchema.optional().describe("The browser hand-over this row asked for, and how it ended."),
|
|
663
|
+
terminalHelp: TranscriptTerminalHelpSchema.optional().describe("The terminal hand-over this row asked for, and how it ended."),
|
|
664
|
+
serviceOffer: TranscriptServiceOfferSchema.optional().describe("The priced service run this row offered, the decision, and the receipt."),
|
|
665
|
+
capabilityOffer: TranscriptCapabilityOfferSchema.optional().describe("The capability setup this row asked for, the decision, and the outcome."),
|
|
666
|
+
paymentOffer: TranscriptPaymentOfferSchema.optional().describe("The payment this row asked for, the decision, and the receipt."),
|
|
430
667
|
});
|
|
431
|
-
export type
|
|
668
|
+
export type TranscriptRow = z.infer<typeof TranscriptRowSchema>;
|
|
669
|
+
|
|
670
|
+
/* THE CARD FIELDS A ROW CAN CARRY, as one list, for every reader that has to ask "does this row hold a card":
|
|
671
|
+
* the fold that counts a card-only bubble as a row, the chat's row count (a branch is cut by it), and the
|
|
672
|
+
* surfaces that draw whichever card a bubble is waiting on. */
|
|
673
|
+
export const CARD_FIELDS = ["plan", "question", "permission", "browserHelp", "terminalHelp", "serviceOffer", "capabilityOffer", "paymentOffer"] as const;
|
|
674
|
+
export type CardField = (typeof CARD_FIELDS)[number];
|
|
675
|
+
export type TranscriptCards = Pick<TranscriptRow, CardField>;
|
|
676
|
+
// Whether a row holds a card at all, answered or not.
|
|
677
|
+
export const holdsCard = (row: TranscriptCards): boolean => CARD_FIELDS.some((field) => row[field] !== undefined);
|
|
678
|
+
// Whether a row is holding the turn open on a card nobody has answered.
|
|
679
|
+
export const isAwaitingDecision = (row: TranscriptCards): boolean => CARD_FIELDS.some((field) => row[field]?.status === "pending");
|
|
680
|
+
|
|
681
|
+
/* ONE CHANGE TO A RUN'S ROWS, what the attach stream carries while a turn runs. The daemon folds each frame
|
|
682
|
+
* into its rows (transcript-fold.ts) and says what moved, so a client keeps rows, never frames: it applies
|
|
683
|
+
* these to the list it holds and draws it. `index` counts from the run's first row, which the attach head
|
|
684
|
+
* places in the conversation.
|
|
685
|
+
*
|
|
686
|
+
* Prose and thinking arrive as APPENDS to a row rather than as the row again, so the chat can type them out at
|
|
687
|
+
* the pace they are written; a tool card arrives whole (`tool`, by id, replacing an earlier copy of the same
|
|
688
|
+
* id wherever it nests), because its updates are snapshots already; everything else replaces its row. `drop`
|
|
689
|
+
* is the one removal: an assistant row opened for a block that then wrote nothing. */
|
|
690
|
+
export const TranscriptPatchSchema = z.discriminatedUnion("op", [
|
|
691
|
+
z.object({ op: z.literal("append").describe("A new row at the end."), row: TranscriptRowSchema }),
|
|
692
|
+
z.object({ op: z.literal("replace").describe("This row, whole, in place of the one at that index."), index: z.number().int().nonnegative(), row: TranscriptRowSchema }),
|
|
693
|
+
z.object({ op: z.literal("drop").describe("The row at that index is gone: it was opened and never written into."), index: z.number().int().nonnegative() }),
|
|
694
|
+
z.object({ op: z.literal("text").describe("More of the agent's prose, onto that row's text."), index: z.number().int().nonnegative(), text: z.string() }),
|
|
695
|
+
z.object({ op: z.literal("thinking").describe("More of the agent's reasoning, onto that row's thinking."), index: z.number().int().nonnegative(), text: z.string() }),
|
|
696
|
+
z.object({
|
|
697
|
+
op: z.literal("tool").describe("A tool card, whole: new, or the latest state of one already there, matched by id wherever it nests."),
|
|
698
|
+
index: z.number().int().nonnegative(),
|
|
699
|
+
tool: TranscriptToolSchema,
|
|
700
|
+
parent: z.string().optional().describe("The card this one nests under, when it is a delegated helper's own call."),
|
|
701
|
+
}),
|
|
702
|
+
]);
|
|
703
|
+
export type TranscriptPatch = z.infer<typeof TranscriptPatchSchema>;
|
|
432
704
|
|
|
433
705
|
export const SessionTranscriptSchema = z.object({
|
|
434
706
|
messages: z
|
|
435
|
-
.array(
|
|
707
|
+
.array(TranscriptRowSchema)
|
|
436
708
|
.describe(
|
|
437
709
|
"The conversation, in order. Each block of the agent's prose is its own message with the tools that block introduced, which is what reproduces the way it actually unfolded.",
|
|
438
710
|
),
|
|
439
711
|
});
|
|
712
|
+
/* HOW A TURN THAT LEFT WORK BEHIND ENDED, as the daemon has it, for whoever asks and however long after.
|
|
713
|
+
*
|
|
714
|
+
* One shape for every ending that leaves finished work behind a live session, because they are one situation
|
|
715
|
+
* with one answer (a press) and they differ only in what can honestly be said about WHEN and what the press
|
|
716
|
+
* DOES. The client folds this straight into its pick-up state (chat/pickUp.ts), which is why the field names
|
|
717
|
+
* line up: a second vocabulary between the two halves is how they came to disagree in the first place.
|
|
718
|
+
*
|
|
719
|
+
* IT USED TO BE A BOOLEAN, and the boolean is what left the longest wait uncovered. One flag could only say
|
|
720
|
+
* "a Stop, or a daemon killed under it", so a spent allowance, the one ending that reliably OUTLIVES the
|
|
721
|
+
* window that hit it, reached a reopened tab as nothing at all: no strip, no countdown, no press, and the user
|
|
722
|
+
* typing the word by hand hours later. It could not say more without these three facts, and each of them
|
|
723
|
+
* changes what the surface may promise:
|
|
724
|
+
*
|
|
725
|
+
* · `resetsAt` is the only honest "not before this" any ending knows, and it is the whole of what a chat
|
|
726
|
+
* reopened the next morning wants to be told;
|
|
727
|
+
* · `held` is what makes the press cheap. The daemon keeps the refused turn whole, so continuing RE-RUNS it
|
|
728
|
+
* and adds nothing to the conversation; without this the same press appends a message reading "Continue",
|
|
729
|
+
* which is exactly the transcript pollution the press exists to prevent, and `ran` separates a turn that
|
|
730
|
+
* got somewhere from one the allowance refused at the door (two different sentences);
|
|
731
|
+
* · `scheduled` says somebody else is already bringing this turn back, so the surface REPORTS a wait instead
|
|
732
|
+
* of offering one, and no local automation races the daemon's own pass for it. */
|
|
733
|
+
export const TurnEndingSchema = z.object({
|
|
734
|
+
reason: z
|
|
735
|
+
.enum(["stopped", "limit", "outage"])
|
|
736
|
+
.describe(
|
|
737
|
+
"Which ending left the work here: a Stop or a daemon killed under the turn, a spent usage allowance, or a provider that refused it.",
|
|
738
|
+
),
|
|
739
|
+
resetsAt: z
|
|
740
|
+
.number()
|
|
741
|
+
.optional()
|
|
742
|
+
.describe("When the spent allowance reopens, in epoch seconds. Absent for every ending that names no instant, and for a provider that publishes none."),
|
|
743
|
+
held: z
|
|
744
|
+
.object({ ran: z.boolean().describe("Whether the held turn got anywhere before it was refused, which is a different sentence from one refused at the door.") })
|
|
745
|
+
.optional()
|
|
746
|
+
.describe("Present when the daemon still holds the refused turn whole, so a press re-runs it rather than appending a message after it."),
|
|
747
|
+
scheduled: z
|
|
748
|
+
.boolean()
|
|
749
|
+
.optional()
|
|
750
|
+
.describe("Whether something other than the user is already booked to send this turn again, so the surface reports the wait instead of offering a press."),
|
|
751
|
+
});
|
|
752
|
+
export type TurnEnding = z.infer<typeof TurnEndingSchema>;
|
|
753
|
+
|
|
440
754
|
/* THE RECORD A REOPENED TAB IS REBUILT FROM: the messages, plus what the session behind them is BOUND to.
|
|
441
755
|
*
|
|
442
756
|
* A provider session is minted on one runtime under one credential, and it resumes only there, so a client
|
|
@@ -446,7 +760,7 @@ export const SessionTranscriptSchema = z.object({
|
|
|
446
760
|
* is what made switching BACK to the account that minted it announce a fresh session and then retire a
|
|
447
761
|
* perfectly resumable one, spending the whole transcript again on a cold prompt cache.
|
|
448
762
|
*
|
|
449
|
-
*
|
|
763
|
+
* The session fields are all optional, and absent together on a conversation that has no session to resume. */
|
|
450
764
|
export const AgentTranscriptSchema = SessionTranscriptSchema.extend({
|
|
451
765
|
sessionId: z.string().optional().describe("The provider session behind the last turn, when there is one."),
|
|
452
766
|
provider: AgentProviderSchema.optional().describe("Which provider minted that session."),
|
|
@@ -455,6 +769,19 @@ export const AgentTranscriptSchema = SessionTranscriptSchema.extend({
|
|
|
455
769
|
.string()
|
|
456
770
|
.optional()
|
|
457
771
|
.describe("Which stored account it belongs to, as the daemon resolved it. Absent when no stored account paid for the turn."),
|
|
772
|
+
/* AND HOW THE LAST TURN ENDED, for the endings that leave the client something to OFFER rather than
|
|
773
|
+
* something to draw: work half done behind a session that is perfectly alive, where the only thing missing
|
|
774
|
+
* is somebody saying carry on.
|
|
775
|
+
*
|
|
776
|
+
* It rides the transcript because the offer used to ride the WINDOW. A chat armed the continue press from
|
|
777
|
+
* the stream it was watching when the turn stopped, so the press existed only where somebody had been
|
|
778
|
+
* looking: stop an agent from the board with its chat closed, or reopen the tab on another device, or after
|
|
779
|
+
* a reload that dropped the tab, and the same stopped session came back with no way on but typing the word
|
|
780
|
+
* by hand, which is precisely what the press exists to spare. The daemon is the one party that knows this
|
|
781
|
+
* about a conversation whoever asks and however long after, so it is the one that says it. */
|
|
782
|
+
ending: TurnEndingSchema.optional().describe(
|
|
783
|
+
"How the last turn ended, when it left work behind that one press finishes. Absent for a conversation whose last turn ended on its own, and for the failures that name something to repair first.",
|
|
784
|
+
),
|
|
458
785
|
});
|
|
459
786
|
|
|
460
787
|
/* WHAT A PUBLISHED CONVERSATION'S PAGE IS HANDED, the whole of it, baked into the page as one JSON block.
|
|
@@ -464,7 +791,7 @@ export const AgentTranscriptSchema = SessionTranscriptSchema.extend({
|
|
|
464
791
|
* it, which also settles the security question by construction, a page with nothing to ask has no way to ask
|
|
465
792
|
* for something it was not given.
|
|
466
793
|
*
|
|
467
|
-
* The messages are the SAME
|
|
794
|
+
* The messages are the SAME TranscriptRow rows the app replays a reopened tab from, already filtered to the
|
|
468
795
|
* chosen detail level and with every picture path rewritten to the copy published beside the page. That
|
|
469
796
|
* sameness is the point: the shared page renders them with the app's own components, so what a recipient sees
|
|
470
797
|
* is what the owner saw. */
|
|
@@ -473,68 +800,16 @@ export const SharePayloadSchema = z.object({
|
|
|
473
800
|
// When the snapshot was taken, not when the conversation happened, see SharedConversation.sharedAt.
|
|
474
801
|
sharedAt: z.number(),
|
|
475
802
|
detail: ShareDetailSchema,
|
|
476
|
-
messages: z.array(
|
|
803
|
+
messages: z.array(TranscriptRowSchema),
|
|
477
804
|
});
|
|
478
805
|
export type SharePayload = z.infer<typeof SharePayloadSchema>;
|
|
479
806
|
|
|
480
|
-
/* WHAT A PARKED CARD IS ABOUT: the document the turn wrote and is now asking a question against.
|
|
481
|
-
*
|
|
482
|
-
* A card asks for a decision; until this it carried no SUBJECT. The commonest shape of a real decision is "I
|
|
483
|
-
* analysed this and wrote it up, now choose", and the write-up went into a file whose card had already folded
|
|
484
|
-
* itself into `Write · +135 −0` twenty tool calls back. So the reader was asked to choose between options
|
|
485
|
-
* describing a document the chat had never shown them.
|
|
486
|
-
*
|
|
487
|
-
* Carried BY VALUE rather than as a path, for the same reason the diff on a tool call is: the bytes are already
|
|
488
|
-
* in hand when the card is raised, a path would make the card's meaning depend on a file that keeps changing
|
|
489
|
-
* under it, and a restored or published transcript has no workspace to go read. The path rides along anyway, so
|
|
490
|
-
* a document past the wire cap still has somewhere to send the reader.
|
|
491
|
-
*
|
|
492
|
-
* Nothing is asked of the MODEL for this. It calls `ask` exactly as before; the daemon knows what the turn
|
|
493
|
-
* wrote, because every write came past it as a frame (documents.ts decides which of them is a document). A
|
|
494
|
-
* harness that can see the answer must not spend prompt on asking the model to repeat it. */
|
|
495
|
-
export const CardDocumentSchema = z.object({
|
|
496
|
-
path: z.string().describe("Where it lives, as a workspace path."),
|
|
497
|
-
title: z.string().describe("What it is called: its opening heading, or its file name."),
|
|
498
|
-
markdown: z.string().describe("The document itself."),
|
|
499
|
-
truncated: z.boolean().optional().describe("It was clipped at the wire cap; the file on disk has more."),
|
|
500
|
-
plan: z.boolean().optional().describe("It is one of the CLI's plan files, written to be approved rather than merely read."),
|
|
501
|
-
});
|
|
502
|
-
export type CardDocument = z.infer<typeof CardDocumentSchema>;
|
|
503
|
-
|
|
504
|
-
/* THE THREE RESTORABLE CARDS, named so the turn journal can hold them verbatim: a parked turn's raised cards
|
|
505
|
-
* are written down beside its prompt (sandbox turn-journal.ts), and a daemon death under the park restores the
|
|
506
|
-
* very same frames instead of ending the turn `interrupted`, the card the user was about to answer survives
|
|
507
|
-
* the restart that killed the process holding it. The two handover cards are deliberately not among them:
|
|
508
|
-
* `browser_help`'s Chromium and `terminal_help`'s waiting command both die with the container, so those parks
|
|
509
|
-
* cannot be restored, only reported. */
|
|
510
|
-
const PlanCardSchema = z.object({
|
|
511
|
-
kind: z.literal("plan").describe("The agent has written a plan and is waiting for a yes."),
|
|
512
|
-
requestId: z.string().describe("What to send back when you answer."),
|
|
513
|
-
text: z.string().describe("The plan itself."),
|
|
514
|
-
// Present when the adjacent plan prose POINTS at a document instead of being one: the model wrote the real
|
|
515
|
-
// plan to a file and summarised it there. Absent when the text already is the whole plan.
|
|
516
|
-
document: CardDocumentSchema.optional().describe("The write-up this plan refers to, when the plan itself is a pointer to one."),
|
|
517
|
-
});
|
|
518
|
-
const QuestionCardSchema = z.object({
|
|
519
|
-
kind: z.literal("question").describe("The agent has asked you something and is waiting."),
|
|
520
|
-
requestId: z.string().describe("What to send back when you answer."),
|
|
521
|
-
questions: z.array(AskQuestionSchema).describe("What it wants to know."),
|
|
522
|
-
document: CardDocumentSchema.optional().describe("The document this turn wrote and is asking about, so the choice can be read beside it."),
|
|
523
|
-
});
|
|
524
|
-
const PermissionCardSchema = PermissionAskSchema.extend({
|
|
525
|
-
kind: z.literal("permission").describe("The agent wants to use a tool it needs permission for."),
|
|
526
|
-
requestId: z.string().describe("What to send back when you answer."),
|
|
527
|
-
});
|
|
528
|
-
export const ParkedCardSchema = z.discriminatedUnion("kind", [PlanCardSchema, QuestionCardSchema, PermissionCardSchema]);
|
|
529
|
-
export type ParkedCard = z.infer<typeof ParkedCardSchema>;
|
|
530
|
-
|
|
531
807
|
// One frame from an agent turn, relayed to the UI. `kind`-discriminated. The daemon normalizes the SDK's
|
|
532
808
|
// ~40 SDKMessage types down to this union: high-value block types get a dedicated frame
|
|
533
809
|
// (delta/thinking/tool_call/tool_call_update/todos/usage/rate_limit_info/account_usage/context_usage/init/compact); any SDK message
|
|
534
810
|
// without a UI mapping is dropped. `plan`/`question`/`permission` pause the turn until the user answers on the
|
|
535
|
-
// `POST /agent/reply` side channel, and `resolved` releases the one it names; `
|
|
536
|
-
//
|
|
537
|
-
// agent changes it.
|
|
811
|
+
// `POST /agent/reply` side channel, and `resolved` releases the one it names; `mode` reports the live
|
|
812
|
+
// permission posture as the agent changes it.
|
|
538
813
|
// `parentToolUseId` tags frames produced inside a subagent (Task tool); `subagent`/`subagent_update` report the
|
|
539
814
|
// subagent itself, keyed by the same tool_use id those tagged frames carry.
|
|
540
815
|
export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
@@ -843,35 +1118,18 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
843
1118
|
PlanCardSchema,
|
|
844
1119
|
QuestionCardSchema,
|
|
845
1120
|
PermissionCardSchema,
|
|
846
|
-
// The agent's browser needs a person
|
|
847
|
-
//
|
|
848
|
-
|
|
849
|
-
//
|
|
850
|
-
// login is stuck even after the browser has navigated somewhere unrecognizable.
|
|
851
|
-
z.object({
|
|
852
|
-
kind: z.literal("browser_help"),
|
|
853
|
-
requestId: z.string(),
|
|
854
|
-
session: z.string(),
|
|
855
|
-
account: z.string(),
|
|
856
|
-
message: z.string(),
|
|
857
|
-
}),
|
|
858
|
-
// The agent's TERMINAL needs a person: a command it started is sitting at a prompt it cannot answer (a
|
|
859
|
-
// one-time password, a security-key touch, a confirm). `session` names the tmux session on the terminal
|
|
860
|
-
// panel, the card's one action is going THERE, where the live pane and its prompt already are, which is
|
|
861
|
-
// the same division of labour the browser card has with /browsers. Not journalled for restore, and for the
|
|
1121
|
+
// The agent's browser needs a person (see browserHelpCard for what the card carries). Not journalled for
|
|
1122
|
+
// restore: the Chromium holding the page dies with the container.
|
|
1123
|
+
z.object({ kind: z.literal("browser_help"), ...browserHelpCard }),
|
|
1124
|
+
// The agent's TERMINAL needs a person (see terminalHelpCard). Not journalled for restore, and for the
|
|
862
1125
|
// browser card's reason one door along: the pane holding the prompt belongs to a process the restart kills.
|
|
863
|
-
z.object({
|
|
864
|
-
kind: z.literal("terminal_help"),
|
|
865
|
-
requestId: z.string(),
|
|
866
|
-
session: z.string(),
|
|
867
|
-
message: z.string(),
|
|
868
|
-
}),
|
|
1126
|
+
z.object({ kind: z.literal("terminal_help"), ...terminalHelpCard }),
|
|
869
1127
|
/* A premium service run awaiting the owner's click. Raised OUTSIDE the turn generator, the daemon's
|
|
870
1128
|
* services route parks the agent's own `services run` call and pushes this frame into the live run
|
|
871
1129
|
* (platform/service-offer.ts), so unlike the four cards above it is not journalled for restore: its
|
|
872
1130
|
* waiter is the CLI's held connection, which dies with the daemon, and a restored card would offer
|
|
873
1131
|
* buttons nothing is waiting behind. Settles through the same `POST /agent/reply` as every other card. */
|
|
874
|
-
z.object({ kind: z.literal("service_offer"),
|
|
1132
|
+
z.object({ kind: z.literal("service_offer"), ...serviceOfferCard }),
|
|
875
1133
|
/* One event off an approved run's stream, pushed as the provider emits it so the settled card shows the
|
|
876
1134
|
* run living rather than a spinner of unknowable length. Today that is `status` lines; `result` stays off
|
|
877
1135
|
* the transcript on purpose (it is the agent's answer to act on, not the card's to duplicate), the frame
|
|
@@ -881,49 +1139,31 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
881
1139
|
* rather than a promise: `ok` served and charged, `refunded` failed to answer and charged nothing,
|
|
882
1140
|
* `refused` the platform said no after the click (a raced-out allowance). `remaining` is the meter after,
|
|
883
1141
|
* when the platform stated one. Skip needs no receipt, nothing happened, and `resolved` already says so. */
|
|
884
|
-
z.
|
|
885
|
-
kind: z.literal("service_receipt"),
|
|
886
|
-
requestId: z.string(),
|
|
887
|
-
outcome: z.enum(["ok", "refunded", "refused"]),
|
|
888
|
-
credits: z.number(),
|
|
889
|
-
remaining: z.number().optional(),
|
|
890
|
-
}),
|
|
1142
|
+
ServiceReceiptSchema.extend({ kind: z.literal("service_receipt"), requestId: z.string() }),
|
|
891
1143
|
/* A missing capability asking for the owner's setup, the agent hit something this sandbox is not
|
|
892
1144
|
* connected to and raised the card instead of describing manual steps. Raised OUTSIDE the turn generator
|
|
893
1145
|
* exactly like the service offer above (the daemon's ask route parks the agent's `capabilities request`
|
|
894
1146
|
* call and pushes this frame into the live run; capabilities/capability-offer.ts), so it is not
|
|
895
1147
|
* journalled for restore either: its waiter is the CLI's held connection, which dies with the daemon.
|
|
896
1148
|
* Settles through the same `POST /agent/reply` as every other card. */
|
|
897
|
-
z.object({ kind: z.literal("capability_offer"),
|
|
1149
|
+
z.object({ kind: z.literal("capability_offer"), ...capabilityOfferCard }),
|
|
898
1150
|
/* How an accepted ask ended, pushed once the daemon stops watching for the connection: `connected`, the
|
|
899
1151
|
* capability came live while the agent waited (`id` is the connected instance, the agent's handle for it)
|
|
900
1152
|
*, or `unfinished`, the setup did not complete while anyone was waiting (the deadline passed, or the
|
|
901
1153
|
* asking command died). A skip needs no outcome frame, nothing was set up, and `resolved` already says
|
|
902
1154
|
* so. It is what settles the card's "waiting for you to finish setup" state on every surface. */
|
|
903
|
-
z.
|
|
904
|
-
kind: z.literal("capability_outcome"),
|
|
905
|
-
requestId: z.string(),
|
|
906
|
-
outcome: z.enum(["connected", "unfinished"]),
|
|
907
|
-
id: z.string().optional(),
|
|
908
|
-
}),
|
|
1155
|
+
CapabilityOutcomeSchema.extend({ kind: z.literal("capability_outcome"), requestId: z.string() }),
|
|
909
1156
|
/* A USDC payment awaiting the owner's click. Raised OUTSIDE the turn generator exactly like the service
|
|
910
1157
|
* offer above (the daemon's wallet route parks the agent's `wallet fetch` call and pushes this frame into
|
|
911
1158
|
* the live run; wallet/payment-offer.ts), so it is not journalled for restore either: its waiter is the
|
|
912
1159
|
* CLI's held connection, which dies with the daemon. Settles through the same `POST /agent/reply`. */
|
|
913
|
-
z.object({ kind: z.literal("payment_offer"),
|
|
1160
|
+
z.object({ kind: z.literal("payment_offer"), ...paymentOfferCard }),
|
|
914
1161
|
/* How an approved (or auto-approved) payment ended, pushed after the endpoint answered so the card can
|
|
915
1162
|
* settle as a receipt rather than a promise: `paid`, the endpoint confirmed settlement (`transaction` is
|
|
916
1163
|
* the onchain hash when it stated one); `failed`, the payment was refused or settlement failed, in which
|
|
917
1164
|
* case the signed authorization expires unused and NOTHING left the wallet. A skip needs no receipt,
|
|
918
1165
|
* nothing moved, and `resolved` already says so. */
|
|
919
|
-
z.
|
|
920
|
-
kind: z.literal("payment_receipt"),
|
|
921
|
-
requestId: z.string(),
|
|
922
|
-
outcome: z.enum(["paid", "failed"]),
|
|
923
|
-
amountUsd: z.string(),
|
|
924
|
-
transaction: z.string().optional(),
|
|
925
|
-
network: z.string().optional(),
|
|
926
|
-
}),
|
|
1166
|
+
PaymentReceiptSchema.extend({ kind: z.literal("payment_receipt"), requestId: z.string() }),
|
|
927
1167
|
// The card above named by `requestId` is released, the user answered (or dismissed it, or the turn was
|
|
928
1168
|
// stopped out from under it), so the turn is executing again. Emitted by whoever parked, the moment its
|
|
929
1169
|
// waiter settles, because the park's END is otherwise invisible on this stream: nothing else here says
|
|
@@ -939,19 +1179,12 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
939
1179
|
// already moved on. It rides verbatim, exactly as the client POSTed it; absent, nobody answered (the turn
|
|
940
1180
|
// was stopped, or died under the card), which is not a decision and must not replay as one.
|
|
941
1181
|
z.object({ kind: z.literal("resolved"), requestId: z.string(), reply: AgentReplySchema.optional() }),
|
|
942
|
-
/*
|
|
943
|
-
*
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
*
|
|
947
|
-
*
|
|
948
|
-
* like the agent freezing, which is the failure the explanation was meant to prevent rather than cause. So
|
|
949
|
-
* the card goes out complete and unexplained, and this arrives if and when it does. Same shape and the same
|
|
950
|
-
* reason as `service_event`, which streams a running service's status onto an offer already rendered.
|
|
951
|
-
*
|
|
952
|
-
* Never arrives at all when the setting is off, when nothing is connected to answer, or when the user
|
|
953
|
-
* settles the card first, and none of those change what the card says. */
|
|
954
|
-
z.object({ kind: z.literal("permission_note"), requestId: z.string(), explain: z.string() }),
|
|
1182
|
+
/* There was a `permission_note` frame here: a late sentence raced onto a command card that had already gone
|
|
1183
|
+
* out, because the explanation was optional and the card must not wait for a quick-model rung that might
|
|
1184
|
+
* take tens of seconds. It is gone with the setting that made it optional. The judge now decides the
|
|
1185
|
+
* verdict, so the sentence is not a decoration arriving afterwards — it is the REASON THE CARD EXISTS, and
|
|
1186
|
+
* a card cannot be raised before it is known. Nothing races, and `PermissionAsk.explain` is populated at
|
|
1187
|
+
* raise time (guard/command-gate.ts). */
|
|
955
1188
|
// The turn's permission mode, whenever it changes, the user's pick at turn start, then every move the
|
|
956
1189
|
// AGENT makes on its own (EnterPlanMode on a request that needs thinking through, ExitPlanMode once the
|
|
957
1190
|
// user approves). The composer's mode selector follows this, so the UI never lies about the live posture.
|
|
@@ -1077,23 +1310,34 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
1077
1310
|
// rate_limit_event or the account's persisted usage windows). Absent when the reset instant is unknown
|
|
1078
1311
|
// (nothing to schedule against).
|
|
1079
1312
|
resetsAt: z.number().optional(),
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1313
|
+
/* Where the daemon's resume of THIS turn stands, for the three codes that have one (provider-outage,
|
|
1314
|
+
* claude-token-refused, rate_limit). "scheduled" = the resume is armed and this turn comes back by
|
|
1315
|
+
* itself; "available" = the daemon remembered the failed turn and arming THIS conversation
|
|
1316
|
+
* (AgentSummarySchema's resumeAfterOutage / resumeAfterLimit) picks up that same resume, which is what
|
|
1317
|
+
* the offer banner hangs off, gated codes only, since a credential renewal is never gated on a posture
|
|
1318
|
+
* at all. The two words are read against the effective posture (the conversation's override, else the
|
|
1319
|
+
* sandbox default), so a chat armed on its own says "scheduled" while the unarmed board around it says
|
|
1320
|
+
* "available". Absent means there is nothing automatic to resume: a limit whose reset instant nobody
|
|
1321
|
+
* published has nothing to schedule against, and a refused credential has none once re-minting it has
|
|
1322
|
+
* already been tried and failed.
|
|
1323
|
+
*
|
|
1324
|
+
* A SPENT ALLOWANCE USED TO BE ABSENT HERE BY RULE, and the rule was right about the default and wrong
|
|
1325
|
+
* about the ceiling. The budget is the user's, so nothing fires unless they said so, which is what the
|
|
1326
|
+
* posture is; what the old absence also cost was the case a press cannot reach, a 2am wall on a board
|
|
1327
|
+
* nobody is watching. Both words are now honest for it: unarmed says "available", which is an offer,
|
|
1328
|
+
* and armed says "scheduled", which the card counts down to. */
|
|
1089
1329
|
autoResume: z.enum(["scheduled", "available"]).optional(),
|
|
1090
1330
|
/* THE DAEMON IS STILL HOLDING THIS EXACT TURN, so the way on is to RE-RUN it rather than to send
|
|
1091
1331
|
* something after it. rate_limit only, and the counterpart to `autoResume` rather than a member of it:
|
|
1092
|
-
* that field answers "is a machine bringing this back",
|
|
1093
|
-
*
|
|
1332
|
+
* that field answers "is a machine bringing this back", which for a spent allowance is a posture the
|
|
1333
|
+
* user sets and defaults to no (the allowance is their own budget to spend, turn-resume.ts). This
|
|
1094
1334
|
* answers the question that was never asked, "and if the user says go, what happens", which had exactly
|
|
1095
1335
|
* one possible answer for as long as it went unasked: a new user message reading "Continue".
|
|
1096
1336
|
*
|
|
1337
|
+
* BOTH ANSWERS RUN THROUGH THIS FIELD, which is why it is not folded into the one above: an armed
|
|
1338
|
+
* conversation's scheduled fire and an unarmed one's press are the same held turn re-run the same way,
|
|
1339
|
+
* and the only difference is who says go.
|
|
1340
|
+
*
|
|
1097
1341
|
* That answer was wrong in a way the chat could not show. The press is not a new instruction, it is the
|
|
1098
1342
|
* same one again, and appending it said otherwise to the only reader that matters: the provider session
|
|
1099
1343
|
* grew one "Continue" per press, each with a synthetic "No response requested." above it, so a chat that
|
|
@@ -1120,34 +1364,73 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
1120
1364
|
]);
|
|
1121
1365
|
export type AgentEvent = z.infer<typeof AgentEventSchema>;
|
|
1122
1366
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1367
|
+
/* THE FRAMES THAT ARE FACTS ABOUT THE TURN rather than words in it: which session it runs, where it stands,
|
|
1368
|
+
* what it costs, how it failed. Everything else an AgentEvent can say is transcript, and reaches a client as
|
|
1369
|
+
* rows and patches (TranscriptPatchSchema) after the daemon has folded it; these reach it as themselves,
|
|
1370
|
+
* because there is nothing to fold, a client keeps them as state beside the transcript. A frame can be both,
|
|
1371
|
+
* a `worktree` that rebased writes a notice AND says where the branch is, so the two lists overlap, and the
|
|
1372
|
+
* fold and this list each take the half that is theirs. */
|
|
1373
|
+
export const TURN_FACT_KINDS = [
|
|
1374
|
+
"session",
|
|
1375
|
+
"worktree",
|
|
1376
|
+
"init",
|
|
1377
|
+
"terminal",
|
|
1378
|
+
"browser",
|
|
1379
|
+
"commands",
|
|
1380
|
+
"usage",
|
|
1381
|
+
"rate_limit_info",
|
|
1382
|
+
"fast_mode",
|
|
1383
|
+
"tier",
|
|
1384
|
+
"provider_retry",
|
|
1385
|
+
"account_usage",
|
|
1386
|
+
"context_usage",
|
|
1387
|
+
"mode",
|
|
1388
|
+
"error",
|
|
1389
|
+
] as const;
|
|
1390
|
+
export type TurnFact = Extract<AgentEvent, { kind: (typeof TURN_FACT_KINDS)[number] }>;
|
|
1391
|
+
export const isTurnFact = (event: AgentEvent): event is TurnFact => (TURN_FACT_KINDS as readonly string[]).includes(event.kind);
|
|
1392
|
+
// The same members AgentEventSchema declares, picked out rather than declared twice: a fact's shape is the
|
|
1393
|
+
// frame's shape, and a second spelling of it would be the drift the list above exists to prevent.
|
|
1394
|
+
type AgentEventMember = (typeof AgentEventSchema.options)[number];
|
|
1395
|
+
const factMembers = AgentEventSchema.options.filter((member) => (TURN_FACT_KINDS as readonly string[]).includes(member.shape.kind.value)) as unknown as [
|
|
1396
|
+
AgentEventMember,
|
|
1397
|
+
...AgentEventMember[],
|
|
1398
|
+
];
|
|
1399
|
+
export const TurnFactSchema = z.discriminatedUnion("kind", factMembers) as unknown as z.ZodType<TurnFact>;
|
|
1400
|
+
|
|
1401
|
+
/* The /agent/attach stream: a head carrying the run's rows so far, then every change to them and every fact
|
|
1402
|
+
* about the turn as each lands, then `end` when the run is over, nothing more coming. A stream that closes
|
|
1403
|
+
* WITHOUT `end` was dropped mid-run; the client re-attaches and takes the head's rows again, whole, which is
|
|
1404
|
+
* what makes attaching idempotent: a window never re-folds what it has already drawn, it replaces it.
|
|
1405
|
+
*
|
|
1406
|
+
* Facts REPLAY on every attach (their seq is at or below the head's), because a window joining late still has
|
|
1407
|
+
* to learn which session the turn runs and where its branch stands; patches are only ever live (their seq is
|
|
1408
|
+
* above the head's), because the head already holds their result. */
|
|
1129
1409
|
export const AttachFrameSchema = z.discriminatedUnion("kind", [
|
|
1130
1410
|
z.object({
|
|
1131
|
-
kind: z.literal("attached").describe("The first frame, identifying the run you have joined."),
|
|
1411
|
+
kind: z.literal("attached").describe("The first frame, identifying the run you have joined and handing you its transcript so far."),
|
|
1132
1412
|
run: z.string().describe("The run's id."),
|
|
1133
|
-
prompt: z.string().describe("What was said to start it, so a window that did not start the turn can still draw the message."),
|
|
1134
1413
|
startedAt: z.number().describe("When it started, in milliseconds, so a window joining late can show how long it has been going."),
|
|
1135
|
-
seq: z.number().describe("How many frames
|
|
1414
|
+
seq: z.number().describe("How many frames the run has produced so far. A fact at or below this number is being replayed; a patch is never."),
|
|
1415
|
+
rows: z
|
|
1416
|
+
.array(TranscriptRowSchema)
|
|
1417
|
+
.describe("The turn's rows as they stand: what was asked, and everything the agent has said and done since. Draw these, then apply the patches that follow."),
|
|
1136
1418
|
}),
|
|
1137
1419
|
z.object({
|
|
1138
|
-
kind: z.literal("
|
|
1139
|
-
seq: z
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
),
|
|
1420
|
+
kind: z.literal("patch").describe("One change to the run's rows."),
|
|
1421
|
+
seq: z.number().describe("Its position in the run, counting from one."),
|
|
1422
|
+
patch: TranscriptPatchSchema,
|
|
1423
|
+
}),
|
|
1424
|
+
z.object({
|
|
1425
|
+
kind: z.literal("fact").describe("One thing about the turn that is not a row: its session, its branch, its cost, a failure."),
|
|
1426
|
+
seq: z.number().describe("Its position in the run, counting from one. At or below the head's number, it is being replayed."),
|
|
1427
|
+
fact: TurnFactSchema,
|
|
1145
1428
|
}),
|
|
1146
1429
|
z.object({
|
|
1147
1430
|
kind: z
|
|
1148
1431
|
.literal("end")
|
|
1149
1432
|
.describe(
|
|
1150
|
-
"The run is over and every frame has been delivered. A stream that closes without this was dropped mid-run, so re-attach
|
|
1433
|
+
"The run is over and every frame has been delivered. A stream that closes without this was dropped mid-run, so re-attach rather than assuming the turn finished.",
|
|
1151
1434
|
),
|
|
1152
1435
|
}),
|
|
1153
1436
|
]);
|
|
@@ -1427,9 +1710,34 @@ export type Presence = z.infer<typeof PresenceSchema>;
|
|
|
1427
1710
|
export const AgentsSchema = z.object({ kind: z.literal("agents"), agents: z.array(AgentSummarySchema), rev: z.number() });
|
|
1428
1711
|
export type Agents = z.infer<typeof AgentsSchema>;
|
|
1429
1712
|
|
|
1713
|
+
/* AN ACCOUNT'S HEADROOM JUST MOVED, the reading itself, keyed the way the daemon's store keys it (a Claude
|
|
1714
|
+
* account id, or `${provider}:${authFile}` for a routed subscription).
|
|
1715
|
+
*
|
|
1716
|
+
* The fifth push, and the one that lets every ring, rail and picker row stop refetching on mount. A reading
|
|
1717
|
+
* lands on the daemon for one of four reasons, a turn settled, a plan refused, a screen asked, a provider
|
|
1718
|
+
* pushed, and until this frame existed only the window that caused it ever heard: every other window drew the
|
|
1719
|
+
* number it had loaded that morning until something in it happened to remount. Snapshot-not-diff per account,
|
|
1720
|
+
* last frame wins, and a browser that missed one simply holds the older reading, which is what `measuredAt`
|
|
1721
|
+
* is for. `usage` absent ⇒ the account's snapshot was cleared (it was disconnected). */
|
|
1722
|
+
export const AccountUsageChangedSchema = z.object({
|
|
1723
|
+
kind: z.literal("accountUsage"),
|
|
1724
|
+
// The provider whose row this account is, because the key alone does not say (a native id is bare).
|
|
1725
|
+
provider: z.string(),
|
|
1726
|
+
account: z.string(),
|
|
1727
|
+
usage: AccountUsageSchema.optional(),
|
|
1728
|
+
});
|
|
1729
|
+
export type AccountUsageChanged = z.infer<typeof AccountUsageChangedSchema>;
|
|
1730
|
+
|
|
1731
|
+
// A provider's last refusal was recorded or settled. The observed half of "can I run on this" (see
|
|
1732
|
+
// ProviderRefusalSchema), pushed for the same reason the reading above is: a refusal at 4am used to reach a
|
|
1733
|
+
// window only when it next reloaded its account rows. `refusal` absent ⇒ settled, nothing stands.
|
|
1734
|
+
export const ProviderRefusalChangedSchema = z.object({ kind: z.literal("providerRefusal"), provider: z.string(), refusal: ProviderRefusalSchema.optional() });
|
|
1735
|
+
export type ProviderRefusalChanged = z.infer<typeof ProviderRefusalChangedSchema>;
|
|
1736
|
+
|
|
1430
1737
|
// The /events stream union: the hello identity frame, then liveness heartbeats interleaved with boot progress,
|
|
1431
|
-
// workspace-change batches, repo-set snapshots, ref-move batches, runtime-domain nudges,
|
|
1432
|
-
// roster snapshots. oRPC validates every yielded frame against this,
|
|
1738
|
+
// workspace-change batches, repo-set snapshots, ref-move batches, runtime-domain nudges, presence + fleet
|
|
1739
|
+
// roster snapshots, and account headroom / refusal changes. oRPC validates every yielded frame against this,
|
|
1740
|
+
// so all kinds must live here.
|
|
1433
1741
|
export const SystemEventSchema = z.discriminatedUnion("kind", [
|
|
1434
1742
|
HelloSchema,
|
|
1435
1743
|
HeartbeatSchema,
|
|
@@ -1440,5 +1748,7 @@ export const SystemEventSchema = z.discriminatedUnion("kind", [
|
|
|
1440
1748
|
RuntimeChangedSchema,
|
|
1441
1749
|
PresenceSchema,
|
|
1442
1750
|
AgentsSchema,
|
|
1751
|
+
AccountUsageChangedSchema,
|
|
1752
|
+
ProviderRefusalChangedSchema,
|
|
1443
1753
|
]);
|
|
1444
1754
|
export type SystemEvent = z.infer<typeof SystemEventSchema>;
|