@intentic/sandbox-contract 1.176.3 → 1.209.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/README.md +65 -0
- package/dist/agent-catalog.d.ts +15 -2
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +50 -1
- package/dist/agent-catalog.js.map +1 -1
- package/dist/agent-run-model.d.ts +3 -0
- package/dist/agent-run-model.d.ts.map +1 -0
- package/dist/agent-run-model.js +10 -0
- package/dist/agent-run-model.js.map +1 -0
- package/dist/capability-env.d.ts +2 -0
- package/dist/capability-env.d.ts.map +1 -0
- package/dist/capability-env.js +2 -0
- package/dist/capability-env.js.map +1 -0
- package/dist/chores/chores.d.ts +9 -0
- package/dist/chores/chores.d.ts.map +1 -1
- package/dist/chores/chores.js +257 -18
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/digest.js +0 -0
- package/dist/chores/digest.js.map +1 -1
- package/dist/chores/extension-update.d.ts +9 -0
- package/dist/chores/extension-update.d.ts.map +1 -0
- package/dist/chores/extension-update.js +15 -0
- package/dist/chores/extension-update.js.map +1 -0
- package/dist/chores/fix-deps.d.ts +10 -0
- package/dist/chores/fix-deps.d.ts.map +1 -0
- package/dist/chores/fix-deps.js +18 -0
- package/dist/chores/fix-deps.js.map +1 -0
- package/dist/chores/index.d.ts +7 -2
- package/dist/chores/index.d.ts.map +1 -1
- package/dist/chores/index.js +4 -1
- package/dist/chores/index.js.map +1 -1
- package/dist/chores/probes.d.ts.map +1 -1
- package/dist/chores/probes.js +127 -3
- package/dist/chores/probes.js.map +1 -1
- package/dist/chores/stack.d.ts +27 -0
- package/dist/chores/stack.d.ts.map +1 -0
- package/dist/chores/stack.js +121 -0
- package/dist/chores/stack.js.map +1 -0
- package/dist/chores/verdict.d.ts +2 -1
- package/dist/chores/verdict.d.ts.map +1 -1
- package/dist/chores/verdict.js +53 -6
- package/dist/chores/verdict.js.map +1 -1
- package/dist/contract-lock.d.ts +3 -0
- package/dist/contract-lock.d.ts.map +1 -0
- package/dist/contract-lock.js +35 -0
- package/dist/contract-lock.js.map +1 -0
- package/dist/contracts/activity.contract.d.ts +10 -0
- package/dist/contracts/activity.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +230 -20
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +2 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +847 -1
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +3 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts +89 -0
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.js +3 -1
- package/dist/contracts/automations.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +108 -32
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +5 -1
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/chores.contract.d.ts +43 -0
- package/dist/contracts/chores.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.d.ts +5 -0
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.js +2 -2
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/claude.contract.d.ts +3 -14
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.js +2 -3
- package/dist/contracts/claude.contract.js.map +1 -1
- package/dist/contracts/drafts.contract.d.ts +4 -0
- package/dist/contracts/drafts.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts +28 -12
- package/dist/contracts/endpoints.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.js +9 -0
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +399 -69
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.js +15 -1
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/git.contract.d.ts +209 -11
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +12 -2
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts +0 -13
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/grok.contract.js +1 -2
- package/dist/contracts/grok.contract.js.map +1 -1
- package/dist/contracts/host.contract.d.ts +40 -0
- package/dist/contracts/host.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.js +3 -2
- package/dist/contracts/host.contract.js.map +1 -1
- package/dist/contracts/loops.contract.d.ts +343 -0
- package/dist/contracts/loops.contract.d.ts.map +1 -0
- package/dist/contracts/loops.contract.js +11 -0
- package/dist/contracts/loops.contract.js.map +1 -0
- package/dist/contracts/panels.contract.d.ts +6 -0
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/personas.contract.d.ts +112 -0
- package/dist/contracts/personas.contract.d.ts.map +1 -0
- package/dist/contracts/personas.contract.js +13 -0
- package/dist/contracts/personas.contract.js.map +1 -0
- package/dist/contracts/ports.contract.d.ts +1 -0
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.d.ts +1 -0
- package/dist/contracts/prepush.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.js.map +1 -1
- package/dist/contracts/{codex.contract.d.ts → providers.contract.d.ts} +11 -3
- package/dist/contracts/providers.contract.d.ts.map +1 -0
- package/dist/contracts/providers.contract.js +6 -0
- package/dist/contracts/providers.contract.js.map +1 -0
- package/dist/contracts/public.contract.d.ts +24 -0
- package/dist/contracts/public.contract.d.ts.map +1 -0
- package/dist/contracts/public.contract.js +8 -0
- package/dist/contracts/public.contract.js.map +1 -0
- package/dist/contracts/push.contract.d.ts +3 -3
- package/dist/contracts/secrets.contract.d.ts +9 -0
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +16 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.js +1 -1
- package/dist/contracts/sessions.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts +352 -58
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +2 -1
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/share.contract.d.ts +55 -0
- package/dist/contracts/share.contract.d.ts.map +1 -0
- package/dist/contracts/share.contract.js +9 -0
- package/dist/contracts/share.contract.js.map +1 -0
- package/dist/contracts/skills.contract.d.ts +41 -0
- package/dist/contracts/skills.contract.d.ts.map +1 -0
- package/dist/contracts/skills.contract.js +9 -0
- package/dist/contracts/skills.contract.js.map +1 -0
- package/dist/contracts/system.contract.d.ts +205 -8
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +10 -2
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/workflows.contract.d.ts +575 -0
- package/dist/contracts/workflows.contract.d.ts.map +1 -0
- package/dist/contracts/workflows.contract.js +13 -0
- package/dist/contracts/workflows.contract.js.map +1 -0
- package/dist/contracts/workspace.contract.d.ts +18 -4
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +6 -3
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/conversation-ids.d.ts +2 -0
- package/dist/conversation-ids.d.ts.map +1 -0
- package/dist/conversation-ids.js +123 -0
- package/dist/conversation-ids.js.map +1 -0
- package/dist/events.d.ts +523 -17
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +106 -13
- package/dist/events.js.map +1 -1
- package/dist/history-state.d.ts.map +1 -1
- package/dist/history-state.js +10 -0
- package/dist/history-state.js.map +1 -1
- package/dist/hostnames.d.ts +4 -0
- package/dist/hostnames.d.ts.map +1 -1
- package/dist/hostnames.js +7 -2
- package/dist/hostnames.js.map +1 -1
- package/dist/index.d.ts +3792 -478
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -17
- package/dist/index.js.map +1 -1
- package/dist/listener-protocol.d.ts +77 -0
- package/dist/listener-protocol.d.ts.map +1 -0
- package/dist/listener-protocol.js +33 -0
- package/dist/listener-protocol.js.map +1 -0
- package/dist/model-order.d.ts +1 -0
- package/dist/model-order.d.ts.map +1 -1
- package/dist/model-order.js +16 -0
- package/dist/model-order.js.map +1 -1
- package/dist/output-fields.d.ts +30 -0
- package/dist/output-fields.d.ts.map +1 -0
- package/dist/output-fields.js +57 -0
- package/dist/output-fields.js.map +1 -0
- package/dist/path-refs.d.ts.map +1 -1
- package/dist/path-refs.js.map +1 -1
- package/dist/publish-drafts.d.ts +7 -0
- package/dist/publish-drafts.d.ts.map +1 -0
- package/dist/publish-drafts.js +20 -0
- package/dist/publish-drafts.js.map +1 -0
- package/dist/quick-model.d.ts +2 -1
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/quick-model.js +18 -14
- package/dist/quick-model.js.map +1 -1
- package/dist/routes.d.ts +3 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +63 -0
- package/dist/routes.js.map +1 -1
- package/dist/runtime-state.d.ts +33 -0
- package/dist/runtime-state.d.ts.map +1 -0
- package/dist/runtime-state.js +14 -0
- package/dist/runtime-state.js.map +1 -0
- package/dist/schemas.d.ts +4889 -851
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +993 -192
- package/dist/schemas.js.map +1 -1
- package/dist/search-globs.d.ts +6 -0
- package/dist/search-globs.d.ts.map +1 -0
- package/dist/search-globs.js +39 -0
- package/dist/search-globs.js.map +1 -0
- package/dist/share-paths.d.ts +8 -0
- package/dist/share-paths.d.ts.map +1 -0
- package/dist/share-paths.js +21 -0
- package/dist/share-paths.js.map +1 -0
- package/dist/title.d.ts.map +1 -1
- package/dist/title.js +1 -1
- package/dist/title.js.map +1 -1
- package/dist/tunnel-ids.d.ts +1 -0
- package/dist/tunnel-ids.d.ts.map +1 -1
- package/dist/tunnel-ids.js +1 -0
- package/dist/tunnel-ids.js.map +1 -1
- package/dist/versions.d.ts +4 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +15 -0
- package/dist/versions.js.map +1 -0
- package/dist/workflow-faults.d.ts +4 -0
- package/dist/workflow-faults.d.ts.map +1 -0
- package/dist/workflow-faults.js +115 -0
- package/dist/workflow-faults.js.map +1 -0
- package/dist/workspace-state.d.ts +280 -1
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +163 -21
- package/dist/workspace-state.js.map +1 -1
- package/package.json +30 -6
- package/src/agent-catalog.test.ts +147 -7
- package/src/agent-catalog.ts +233 -21
- package/src/agent-run-model.test.ts +60 -0
- package/src/agent-run-model.ts +51 -0
- package/src/capability-env.ts +10 -0
- package/src/capability-ledger.test.ts +110 -0
- package/src/chores/chores.test.ts +32 -0
- package/src/chores/chores.ts +456 -32
- package/src/chores/digest.ts +0 -0
- package/src/chores/extension-update.ts +37 -0
- package/src/chores/fix-deps.ts +40 -0
- package/src/chores/index.ts +7 -2
- package/src/chores/probes.test.ts +155 -3
- package/src/chores/probes.ts +200 -4
- package/src/chores/stack.test.ts +126 -0
- package/src/chores/stack.ts +244 -0
- package/src/chores/verdict.test.ts +357 -19
- package/src/chores/verdict.ts +95 -21
- package/src/contract-lock.test.ts +17 -0
- package/src/contract-lock.ts +66 -0
- package/src/contracts/agent.contract.ts +6 -0
- package/src/contracts/agents.contract.ts +12 -0
- package/src/contracts/automations.contract.ts +17 -1
- package/src/contracts/capabilities.contract.ts +22 -0
- package/src/contracts/ci.contract.ts +10 -2
- package/src/contracts/claude.contract.ts +8 -4
- package/src/contracts/endpoints.contract.ts +21 -0
- package/src/contracts/extensions.contract.ts +42 -0
- package/src/contracts/git.contract.ts +33 -6
- package/src/contracts/grok.contract.ts +4 -3
- package/src/contracts/host.contract.ts +13 -2
- package/src/contracts/loops.contract.ts +62 -0
- package/src/contracts/personas.contract.ts +56 -0
- package/src/contracts/prepush.contract.ts +4 -3
- package/src/contracts/providers.contract.ts +23 -0
- package/src/contracts/public.contract.ts +14 -0
- package/src/contracts/sessions.contract.ts +5 -1
- package/src/contracts/settings.contract.ts +5 -0
- package/src/contracts/share.contract.ts +20 -0
- package/src/contracts/skills.contract.ts +24 -0
- package/src/contracts/system.contract.ts +27 -3
- package/src/contracts/workflows.contract.ts +78 -0
- package/src/contracts/workspace.contract.ts +10 -2
- package/src/conversation-ids.test.ts +61 -0
- package/src/conversation-ids.ts +173 -0
- package/src/events.test.ts +20 -1
- package/src/events.ts +404 -43
- package/src/history-state.ts +30 -1
- package/src/hostnames.ts +20 -6
- package/src/index.ts +58 -17
- package/src/listener-protocol.ts +96 -0
- package/src/model-order.test.ts +32 -1
- package/src/model-order.ts +56 -3
- package/src/output-fields.ts +111 -0
- package/src/path-refs.test.ts +4 -4
- package/src/path-refs.ts +5 -5
- package/src/publish-drafts.ts +59 -0
- package/src/quick-model.test.ts +70 -27
- package/src/quick-model.ts +76 -40
- package/src/routes.test.ts +91 -3
- package/src/routes.ts +135 -0
- package/src/runtime-state.test.ts +45 -0
- package/src/runtime-state.ts +124 -0
- package/src/schemas.test.ts +16 -29
- package/src/schemas.ts +3480 -552
- package/src/search-globs.test.ts +46 -0
- package/src/search-globs.ts +76 -0
- package/src/share-paths.test.ts +48 -0
- package/src/share-paths.ts +68 -0
- package/src/state-portability.ts +8 -8
- package/src/title.test.ts +20 -1
- package/src/title.ts +12 -3
- package/src/tunnel-ids.ts +15 -1
- package/src/versions.test.ts +39 -0
- package/src/versions.ts +48 -0
- package/src/workflow-faults.test.ts +127 -0
- package/src/workflow-faults.ts +188 -0
- package/src/workspace-state.test.ts +169 -15
- package/src/workspace-state.ts +427 -35
- package/dist/contracts/codex.contract.d.ts.map +0 -1
- package/dist/contracts/codex.contract.js +0 -6
- package/dist/contracts/codex.contract.js.map +0 -1
- package/dist/contracts/gemini.contract.d.ts +0 -16
- package/dist/contracts/gemini.contract.d.ts.map +0 -1
- package/dist/contracts/gemini.contract.js +0 -6
- package/dist/contracts/gemini.contract.js.map +0 -1
- package/dist/contracts/kimi.contract.d.ts +0 -16
- package/dist/contracts/kimi.contract.d.ts.map +0 -1
- package/dist/contracts/kimi.contract.js +0 -6
- package/dist/contracts/kimi.contract.js.map +0 -1
- package/dist/contracts/komodo.contract.d.ts +0 -125
- package/dist/contracts/komodo.contract.d.ts.map +0 -1
- package/dist/contracts/komodo.contract.js +0 -14
- package/dist/contracts/komodo.contract.js.map +0 -1
- package/dist/contracts/memory.contract.d.ts +0 -34
- package/dist/contracts/memory.contract.d.ts.map +0 -1
- package/dist/contracts/memory.contract.js +0 -9
- package/dist/contracts/memory.contract.js.map +0 -1
- package/src/contracts/codex.contract.ts +0 -10
- package/src/contracts/gemini.contract.ts +0 -11
- package/src/contracts/kimi.contract.ts +0 -9
- package/src/contracts/komodo.contract.ts +0 -36
- package/src/contracts/memory.contract.ts +0 -13
package/src/events.ts
CHANGED
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
AgentProviderSchema,
|
|
4
4
|
AgentReplySchema,
|
|
5
5
|
AgentSummarySchema,
|
|
6
|
+
FastModeStateSchema,
|
|
6
7
|
LandConflictSchema,
|
|
8
|
+
MemberRoleSchema,
|
|
7
9
|
PermissionModeSchema,
|
|
8
10
|
RateLimitInfoSchema,
|
|
11
|
+
ShareDetailSchema,
|
|
9
12
|
SubagentKindSchema,
|
|
10
13
|
SubagentStatusSchema,
|
|
11
14
|
UsageWindowSchema,
|
|
@@ -51,6 +54,66 @@ export const PermissionAskSchema = z.object({
|
|
|
51
54
|
});
|
|
52
55
|
export type PermissionAsk = z.infer<typeof PermissionAskSchema>;
|
|
53
56
|
|
|
57
|
+
/* ONE PRICED SERVICE RUN, OFFERED — the card the daemon raises when the agent asks to run a premium service
|
|
58
|
+
* (platform/service-offer.ts). Everything with a number on it is the PLATFORM's answer, relayed verbatim from
|
|
59
|
+
* the catalog it serves the daemon: the model that asked contributes `request` (the JSON it wants sent) and
|
|
60
|
+
* `why` (its one line of rationale), and nothing else — which is what makes the price on the card impossible
|
|
61
|
+
* to misquote, and the click on it the only way the run can happen. */
|
|
62
|
+
export const ServiceOfferSchema = z.object({
|
|
63
|
+
// The service, as the platform lists it: `<slug>` is what the run names, the rest is the catalog row.
|
|
64
|
+
slug: z.string(),
|
|
65
|
+
name: z.string(),
|
|
66
|
+
publisher: z.string(),
|
|
67
|
+
description: z.string(),
|
|
68
|
+
creditsPerRun: z.number(),
|
|
69
|
+
// The owner's meter as the platform stated it with the catalog — what "N left today" renders from. Absent
|
|
70
|
+
// when the platform sent none (it answers a meter only to a member, and membership was already checked
|
|
71
|
+
// before this card went up, so in practice it is present; the field stays honest about the wire).
|
|
72
|
+
credits: z.object({ allowance: z.number(), remaining: z.number(), resetsAt: z.string() }).optional(),
|
|
73
|
+
// The request body the agent wants forwarded, verbatim — shown so the owner can see what leaves.
|
|
74
|
+
request: z.string(),
|
|
75
|
+
// The agent's one-line case for spending — the only prose on the card that is the model's.
|
|
76
|
+
why: z.string().optional(),
|
|
77
|
+
});
|
|
78
|
+
export type ServiceOffer = z.infer<typeof ServiceOfferSchema>;
|
|
79
|
+
|
|
80
|
+
/* WHAT A SERVICE STREAMS — the provider's event vocabulary, stated once here and imported by everyone who
|
|
81
|
+
* touches it: the platform validates each line of a provider's NDJSON against this before relaying it, the
|
|
82
|
+
* daemon turns `status` events into transcript frames, and the editor renders them under the offer card.
|
|
83
|
+
* A run is `status` lines (each replaces the last — a spinner label, not a log) ending in exactly one
|
|
84
|
+
* `result`, whose `data` is the answer the agent acts on. The union is where future event kinds land when
|
|
85
|
+
* services start streaming richer transcript elements; today's two are the smallest honest set. */
|
|
86
|
+
export const ServiceStreamEventSchema = z.discriminatedUnion(`event`, [
|
|
87
|
+
z.object({ event: z.literal(`status`), text: z.string() }),
|
|
88
|
+
z.object({ event: z.literal(`result`), data: z.unknown() }),
|
|
89
|
+
]);
|
|
90
|
+
export type ServiceStreamEvent = z.infer<typeof ServiceStreamEventSchema>;
|
|
91
|
+
|
|
92
|
+
/* ONE MISSING CAPABILITY, ASKED FOR — the card the daemon raises when the agent hits something this sandbox
|
|
93
|
+
* is not connected to (capabilities/capability-offer.ts). `card` names the catalog card and `name` is that
|
|
94
|
+
* card's own title, both resolved by the daemon from the catalog it validates the ask against — the model
|
|
95
|
+
* that asked contributes `why` (its one line of rationale) and nothing else, which is what makes the card
|
|
96
|
+
* impossible to misrepresent, and the click on it the only way anything gets connected. */
|
|
97
|
+
export const CapabilityOfferSchema = z.object({
|
|
98
|
+
// The catalog card being asked for, and how the catalog itself titles it ("Notion", "GitHub", "Docker").
|
|
99
|
+
card: z.string(),
|
|
100
|
+
name: z.string(),
|
|
101
|
+
// The agent's one-line case for connecting it — the only prose on the card that is the model's.
|
|
102
|
+
why: z.string().optional(),
|
|
103
|
+
});
|
|
104
|
+
export type CapabilityOffer = z.infer<typeof CapabilityOfferSchema>;
|
|
105
|
+
|
|
106
|
+
/* The trailer the PLATFORM appends to every relayed run stream — never provider-authored: it is the ledger
|
|
107
|
+
* speaking after the stream settled. `ok` means the run served and was charged (`remaining` is the meter
|
|
108
|
+
* after); `refunded` means the provider's stream died before its `result` and the charge was reversed. */
|
|
109
|
+
export const ServiceRunReceiptSchema = z.object({
|
|
110
|
+
event: z.literal(`receipt`),
|
|
111
|
+
outcome: z.enum([`ok`, `refunded`]),
|
|
112
|
+
credits: z.number(),
|
|
113
|
+
remaining: z.number().optional(),
|
|
114
|
+
});
|
|
115
|
+
export type ServiceRunReceipt = z.infer<typeof ServiceRunReceiptSchema>;
|
|
116
|
+
|
|
54
117
|
// One provider-advertised slash command — an ACP agent's available_commands entry, or a Claude Code session's
|
|
55
118
|
// supportedCommands() (its built-ins plus the workspace's own .claude/commands and any plugin/skill commands).
|
|
56
119
|
// `hint` is the argument placeholder the popover shows after the name.
|
|
@@ -101,12 +164,12 @@ export type ToolCallLocation = z.infer<typeof ToolCallLocationSchema>;
|
|
|
101
164
|
// tools (old_string/new_string) and whole-file for Write; an absent oldText means a new file / unknown
|
|
102
165
|
// previous content. Sides are capped daemon-side; `truncated` marks a clipped side.
|
|
103
166
|
//
|
|
104
|
-
// `image` is a PICTURE THE TOOL PRODUCED, carried as a workspace path rather than as bytes
|
|
105
|
-
//
|
|
106
|
-
// fetches
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
167
|
+
// `image` is a PICTURE THE TOOL PRODUCED, carried as a workspace path rather than as bytes. Browser screenshots
|
|
168
|
+
// already live under .intentic/artifacts/browser, and provider-generated images are copied into
|
|
169
|
+
// .intentic/artifacts/imagegen, so the client fetches either from /workspace/raw like any other file. Base64 on
|
|
170
|
+
// the wire would bloat the event stream and every stored transcript to show bytes the workspace already serves;
|
|
171
|
+
// the path also keeps the picture openable afterwards. Root-relative, forward-slash: the same route space as
|
|
172
|
+
// ToolCallLocation.
|
|
110
173
|
export const ToolCallContentSchema = z.discriminatedUnion("type", [
|
|
111
174
|
z.object({ type: z.literal("text"), text: z.string() }),
|
|
112
175
|
z.object({
|
|
@@ -121,9 +184,10 @@ export const ToolCallContentSchema = z.discriminatedUnion("type", [
|
|
|
121
184
|
export type ToolCallContent = z.infer<typeof ToolCallContentSchema>;
|
|
122
185
|
|
|
123
186
|
// ---- restored transcripts ----
|
|
124
|
-
// What /sessions/{id} replays into a reopened tab
|
|
125
|
-
//
|
|
126
|
-
//
|
|
187
|
+
// What /sessions/{id} replays into a reopened tab, and what the daemon's own conversation record stores. It has
|
|
188
|
+
// to REDRAW the transcript the user was looking at rather than merely paraphrase it, so it keeps the assistant's
|
|
189
|
+
// thinking and the tool cards its turn ran — which is also what lets a runtime handoff carry more than bare
|
|
190
|
+
// prose across to the replacement session (see runtime-history.ts). Reconstructed from the stored
|
|
127
191
|
// tool_use/tool_result blocks, so a restored card carries everything the live `tool_call` frame did except
|
|
128
192
|
// the streaming-only correlation fields.
|
|
129
193
|
//
|
|
@@ -158,23 +222,98 @@ export interface RestoredToolCall {
|
|
|
158
222
|
thinking?: string | undefined;
|
|
159
223
|
}
|
|
160
224
|
|
|
225
|
+
/* ONE NOTE THE DAEMON PUT IN FRONT OF A USER'S MESSAGE, as both audiences see it: the model reads `text`, and
|
|
226
|
+
* the chat draws `title` on a collapsed row that opens to that same `text`. Shared by the live frame and the
|
|
227
|
+
* restored transcript so a note reads identically whether the tab watched it arrive or reopened an hour later. */
|
|
228
|
+
export const TurnNoteSchema = z.object({ title: z.string(), text: z.string() });
|
|
229
|
+
export type TurnNote = z.infer<typeof TurnNoteSchema>;
|
|
230
|
+
|
|
161
231
|
// One restored bubble. Each stored assistant message becomes its own, which is what reproduces the live
|
|
162
232
|
// interleaving — prose, the tool cards that prose introduced, then the next block of prose — rather than
|
|
163
233
|
// collapsing a turn's whole narration into a single bubble with its tools hanging off the end.
|
|
164
234
|
export const RestoredMessageSchema = z.object({
|
|
165
|
-
|
|
235
|
+
/* `notice` is neither side of the conversation: it is something that HAPPENED to the turn, recorded so a
|
|
236
|
+
* reopened session can say it. The one that matters is a refused turn — a provider that answers "your
|
|
237
|
+
* organization has disabled Claude subscription access" produced no assistant text, so a transcript of the
|
|
238
|
+
* two speakers alone ends on the user's message and the session reads as broken. It is the same muted line
|
|
239
|
+
* the live client draws for the codes it does not turn red (ChatRole's `notice`). */
|
|
240
|
+
role: z.enum(["user", "assistant", "notice"]),
|
|
166
241
|
text: z.string(),
|
|
242
|
+
/* WHEN THIS TURN WAS SENT, in epoch milliseconds (user rows only) — what the chat shows on the bubble it
|
|
243
|
+
* belongs to. The turn's START, not the moment the record was written: a turn that ran for twenty minutes
|
|
244
|
+
* was still sent when the user pressed send, and a stamp taken at settlement would say the conversation
|
|
245
|
+
* happened at the times its answers finished.
|
|
246
|
+
*
|
|
247
|
+
* Only the user's row carries one, because it is the only row whose moment the daemon actually knows. A
|
|
248
|
+
* turn's frames arrive with no clock of their own, so an assistant bubble could only ever be stamped with
|
|
249
|
+
* the whole turn's start or end — a number that says nothing about when that particular block was written.
|
|
250
|
+
* Rows recorded before this existed simply have none, and the chat draws nothing for them. */
|
|
251
|
+
sentAt: z.number().optional(),
|
|
167
252
|
// Files the user attached to this turn (user bubbles only) as workspace-relative paths, recovered from
|
|
168
253
|
// the stored prompt's attachment note — so a reopened tab redraws chips, not the injected protocol text.
|
|
169
254
|
attachments: z.array(z.string()).optional(),
|
|
255
|
+
/* The checkpoint this message can be rewound to (user bubbles only), filled in when the transcript is read
|
|
256
|
+
* back. Not stored in the record itself — it is looked up per read from the daemon's rewind points, which
|
|
257
|
+
* a rewind rewrites — so a reopened tab offers exactly the turns that are still there to go back to. */
|
|
258
|
+
checkpointId: z.string().optional(),
|
|
170
259
|
thinking: z.string().optional(),
|
|
171
260
|
tools: z.array(RestoredToolCallSchema).optional(),
|
|
261
|
+
/* What the daemon added to this turn's message (user rows only) — the same notes the live `preamble` frame
|
|
262
|
+
* carries, recovered from the stored prompt when the transcript is read back. The reader that strips them out
|
|
263
|
+
* of the user's words is the one that hands them over here instead of dropping them on the floor.
|
|
264
|
+
*
|
|
265
|
+
* On the message rather than as a row of its own, and that is load-bearing twice: they ARE part of what was
|
|
266
|
+
* sent, and a record row per turn preamble would break the one-row-per-bubble correspondence a branch counts
|
|
267
|
+
* with (see the client's recordedRows — notices are drawn locally and never recorded). */
|
|
268
|
+
notes: z.array(TurnNoteSchema).optional(),
|
|
269
|
+
/* THE USER WROTE THIS ROW WEARING THE AGENT'S VOICE (assistant rows only) — the composer's "as agent" mode
|
|
270
|
+
* appending straight into the record, with no turn behind it (agents.place).
|
|
271
|
+
*
|
|
272
|
+
* The flag exists for exactly one audience: the HUMAN re-reading the transcript, whose bubble carries a
|
|
273
|
+
* quiet mark so that months later their own words don't pass as the agent's. The one reader that must
|
|
274
|
+
* never see it is the agent itself — a placed line reaches the model only through the handoff that seeds a
|
|
275
|
+
* fresh runtime session (agent/runtime-history.ts), which renders role and text alone, so there the line is
|
|
276
|
+
* indistinguishable from anything the agent genuinely said. Keep it that way: rendering this flag into any
|
|
277
|
+
* agent-facing text would break the feature's whole contract. */
|
|
278
|
+
placed: z.boolean().optional(),
|
|
172
279
|
});
|
|
173
280
|
export type RestoredMessage = z.infer<typeof RestoredMessageSchema>;
|
|
174
281
|
|
|
175
282
|
export const SessionTranscriptSchema = z.object({ messages: z.array(RestoredMessageSchema) });
|
|
176
283
|
export const AgentTranscriptSchema = SessionTranscriptSchema.extend({ sessionId: z.string().optional() });
|
|
177
284
|
|
|
285
|
+
/* WHAT A PUBLISHED CONVERSATION'S PAGE IS HANDED — the whole of it, baked into the page as one JSON block.
|
|
286
|
+
*
|
|
287
|
+
* A share has to keep working with nothing behind it: no daemon, no session, no sandbox that has to still be
|
|
288
|
+
* running when the recipient finally opens the link. So the page carries its conversation rather than fetching
|
|
289
|
+
* it, which also settles the security question by construction — a page with nothing to ask has no way to ask
|
|
290
|
+
* for something it was not given.
|
|
291
|
+
*
|
|
292
|
+
* The messages are the SAME RestoredMessage rows the app replays a reopened tab from, already filtered to the
|
|
293
|
+
* chosen detail level and with every picture path rewritten to the copy published beside the page. That
|
|
294
|
+
* sameness is the point: the shared page renders them with the app's own components, so what a recipient sees
|
|
295
|
+
* is what the owner saw. */
|
|
296
|
+
export const SharePayloadSchema = z.object({
|
|
297
|
+
title: z.string(),
|
|
298
|
+
// When the snapshot was taken, not when the conversation happened — see SharedConversation.sharedAt.
|
|
299
|
+
sharedAt: z.number(),
|
|
300
|
+
detail: ShareDetailSchema,
|
|
301
|
+
messages: z.array(RestoredMessageSchema),
|
|
302
|
+
});
|
|
303
|
+
export type SharePayload = z.infer<typeof SharePayloadSchema>;
|
|
304
|
+
|
|
305
|
+
/* THE THREE RESTORABLE CARDS, named so the turn journal can hold them verbatim: a parked turn's raised cards
|
|
306
|
+
* are written down beside its prompt (sandbox turn-journal.ts), and a daemon death under the park restores the
|
|
307
|
+
* very same frames instead of ending the turn `interrupted` — the card the user was about to answer survives
|
|
308
|
+
* the restart that killed the process holding it. The two handover cards are deliberately not among them:
|
|
309
|
+
* `browser_help`'s Chromium and `terminal_help`'s waiting command both die with the container, so those parks
|
|
310
|
+
* cannot be restored, only reported. */
|
|
311
|
+
const PlanCardSchema = z.object({ kind: z.literal("plan"), requestId: z.string(), text: z.string() });
|
|
312
|
+
const QuestionCardSchema = z.object({ kind: z.literal("question"), requestId: z.string(), questions: z.array(AskQuestionSchema) });
|
|
313
|
+
const PermissionCardSchema = PermissionAskSchema.extend({ kind: z.literal("permission"), requestId: z.string() });
|
|
314
|
+
export const ParkedCardSchema = z.discriminatedUnion("kind", [PlanCardSchema, QuestionCardSchema, PermissionCardSchema]);
|
|
315
|
+
export type ParkedCard = z.infer<typeof ParkedCardSchema>;
|
|
316
|
+
|
|
178
317
|
// One frame from an agent turn, relayed to the UI. `kind`-discriminated. The daemon normalizes the SDK's
|
|
179
318
|
// ~40 SDKMessage types down to this union: high-value block types get a dedicated frame
|
|
180
319
|
// (delta/thinking/tool_call/tool_call_update/todos/usage/rate_limit_info/account_usage/context_usage/init/compact); any SDK message
|
|
@@ -185,21 +324,26 @@ export const AgentTranscriptSchema = SessionTranscriptSchema.extend({ sessionId:
|
|
|
185
324
|
// subagent itself, keyed by the same tool_use id those tagged frames carry.
|
|
186
325
|
export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
187
326
|
z.object({ kind: z.literal("session"), sessionId: z.string() }),
|
|
188
|
-
/*
|
|
189
|
-
* ROOT repo's short base sha
|
|
327
|
+
/* WHERE AN ISOLATED TURN IS STANDING: the conversation's worktree identity — its branch (agent/<id>) and
|
|
328
|
+
* the ROOT repo's short base sha. First frame of the turn, before any provider frames, and again each time
|
|
329
|
+
* the branch MOVES underneath it, which is why `base` names where the branch sits now rather than the
|
|
330
|
+
* moment it was checked out.
|
|
190
331
|
*
|
|
191
332
|
* `unenforced` marks the degraded container: no CAP_SYS_ADMIN, so the turn's worktree could not be
|
|
192
333
|
* bind-mounted over the workspace root and the harness is rewriting tool paths into it instead. That
|
|
193
334
|
* fallback covers what arrives as tool input and not what a subprocess computes for itself, so the
|
|
194
335
|
* operator needs to know — this state used to be one line in the daemon log at boot, and the way it got
|
|
195
|
-
* noticed was files appearing in the main tree from agents that were supposed to be on branches.
|
|
336
|
+
* noticed was files appearing in the main tree from agents that were supposed to be on branches. Repeated
|
|
337
|
+
* on every emission, because it describes the turn and a client rebuilds its standing from the last frame.
|
|
196
338
|
*
|
|
197
|
-
* `sync` reports
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* a
|
|
202
|
-
*
|
|
339
|
+
* `sync` reports a rebase (agents/sync.ts) and rides here because this frame is already the turn's "where
|
|
340
|
+
* you are standing" announcement. Present only when the branch was BEHIND the main line — `commits` is how
|
|
341
|
+
* many main-line commits it gained, `blocked` names the repos whose rebase would not apply and was rolled
|
|
342
|
+
* back. Both can be non-empty at once in a multi-repo composition. Two moments produce it: before the turn
|
|
343
|
+
* starts, and after a card the turn parked on is answered — a question or a plan approval waits minutes
|
|
344
|
+
* for a person, and the main line does not stop moving meanwhile. It is a notice and never a question: the
|
|
345
|
+
* user is answering their agent, and the alternative to rebasing is not "stay safe" but "conflict at land
|
|
346
|
+
* time", which interrupts them harder. */
|
|
203
347
|
z.object({
|
|
204
348
|
kind: z.literal("worktree"),
|
|
205
349
|
branch: z.string(),
|
|
@@ -213,18 +357,47 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
213
357
|
// can say whether the user's own copy is at risk or the main line simply moved on underneath the agent.
|
|
214
358
|
// held ⇒ auto-land is off for this agent: nothing was applied and nothing failed — the delta is waiting
|
|
215
359
|
// on the branch for a deliberate Land (landed is false, conflicts absent).
|
|
360
|
+
// `deps` rides along when the landed delta left the main tree declaring dependencies it does not have —
|
|
361
|
+
// the residue of an agent adding one without installing it, which every LATER turn would inherit through
|
|
362
|
+
// the overlay it mounts over the main checkout. The daemon reconciles it rather than asking anyone to
|
|
363
|
+
// (workspace/reconcile-deps.ts); this is the receipt, and `deferred` is the honest answer while other turns
|
|
364
|
+
// are still running, since an install cannot touch a tree they are mounted on.
|
|
216
365
|
z.object({
|
|
217
366
|
kind: z.literal("landed"),
|
|
218
367
|
landed: z.boolean(),
|
|
219
368
|
conflicts: z.array(LandConflictSchema).optional(),
|
|
220
369
|
held: z.boolean().optional(),
|
|
370
|
+
deps: z.object({ missing: z.number(), started: z.array(z.string()), deferred: z.boolean() }).optional(),
|
|
221
371
|
}),
|
|
372
|
+
/* WHAT THE DAEMON ADDED TO THE USER'S MESSAGE before the model read it — the exact words, not a summary of
|
|
373
|
+
* them.
|
|
374
|
+
*
|
|
375
|
+
* A turn's prompt is not only what was typed: the daemon prepends notes the model needs and the user did not
|
|
376
|
+
* write (agent/turn-preamble.ts owns the list — a rebase that moved the branch, dependencies that are behind,
|
|
377
|
+
* workspace context retrieved for this very message, where an unenforced runtime's files really live). Those
|
|
378
|
+
* notes change what the agent does, and for a long time the chat's only trace of any of them was one muted
|
|
379
|
+
* line paraphrasing the rebase — so a user watching an agent act on instructions they could not see had no
|
|
380
|
+
* way to find out what those instructions said. This frame is the fix: the note text verbatim, one entry per
|
|
381
|
+
* note, rendered collapsed so it costs a click rather than a scroll.
|
|
382
|
+
*
|
|
383
|
+
* `title` is the note's own opening header, which is what the stripper already anchors on — so the two
|
|
384
|
+
* cannot drift, and a note nobody thought to title cannot reach the wire unlabelled.
|
|
385
|
+
*
|
|
386
|
+
* ONE MOMENT, always: the notes went in front of the user's own message before the turn started, so they hang
|
|
387
|
+
* off that message and are stored on it, which is how a reopened tab still has them. Nothing is injected into
|
|
388
|
+
* a RUNNING turn — the rebase taken while a card sat waiting was the only thing that ever was, and it no
|
|
389
|
+
* longer says anything to the model at all (agent/turn-preamble.ts). */
|
|
390
|
+
z.object({ kind: z.literal("preamble"), notes: z.array(TurnNoteSchema) }),
|
|
222
391
|
// The SDK's init handshake; carries the model it actually resolved for the turn.
|
|
223
392
|
z.object({ kind: z.literal("init"), model: z.string() }),
|
|
224
393
|
// The pre-turn workspace snapshot's id (the attribution-fence "user" capture), emitted once before the
|
|
225
394
|
// provider stream so the client can offer "restore to before this message" on the turn's user bubble.
|
|
226
395
|
// Absent on isolated turns (they snapshot nothing) and when the tree was already clean at turn start.
|
|
227
|
-
|
|
396
|
+
/* The workspace checkpoint capturing the state as this turn FOUND it — what "go back to before this
|
|
397
|
+
* message" restores. `index` is the message's position in the conversation's transcript, which the rewind
|
|
398
|
+
* route addresses it by; absent on a turn with no conversation behind it (the bench, a one-shot), where
|
|
399
|
+
* the id still powers a plain restore but there is no message to rewind to. */
|
|
400
|
+
z.object({ kind: z.literal("checkpoint"), id: z.string(), index: z.number().int().nonnegative().optional() }),
|
|
228
401
|
z.object({ kind: z.literal("delta"), text: z.string(), parentToolUseId: z.string().optional() }),
|
|
229
402
|
// The prose block the `delta` frames were writing is finished. A turn emits several: the model says what
|
|
230
403
|
// it is about to do, runs tools, reports what it found, runs more, then summarizes — each a separate text
|
|
@@ -318,6 +491,29 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
318
491
|
// The live gate: the provider's answer to "may this turn run", pushed mid-turn. Drives the rate-limited
|
|
319
492
|
// notice, not the headroom readouts — see RateLimitInfoSchema.
|
|
320
493
|
RateLimitInfoSchema.extend({ kind: z.literal("rate_limit_info"), account: z.string().optional() }),
|
|
494
|
+
/* WHAT SPEED THIS TURN ACTUALLY RAN AT, and when it isn't the one asked for, why. Emitted only when the
|
|
495
|
+
* answer CHANGES within a turn, so the ordinary case is one frame at init and nothing after it; a turn that
|
|
496
|
+
* enters cooldown mid-flight (fast mode has its own rate-limit pool, separate from the model's) emits a
|
|
497
|
+
* second.
|
|
498
|
+
*
|
|
499
|
+
* This frame exists because fast mode fails SILENTLY and for a lot of different reasons — the plan is free,
|
|
500
|
+
* extra usage is off, the model doesn't offer it, the turn is routed through the translator and so isn't
|
|
501
|
+
* first-party, an env var disables it, the pool is in cooldown. Asking for it and getting standard speed is
|
|
502
|
+
* indistinguishable, from the outside, from asking for it and getting it: same frames, same text, a bill
|
|
503
|
+
* that differs by 2x. A toggle whose effect can't be observed is worse than no toggle, so the daemon
|
|
504
|
+
* reports the harness's own answer rather than the client's assumption.
|
|
505
|
+
*
|
|
506
|
+
* `reason` is forwarded VERBATIM as the string the harness reported (SDK: FastModeDisabledReason) rather
|
|
507
|
+
* than re-typed as an enum here: the set is the vendor's and grows on their schedule, and a reason this
|
|
508
|
+
* build hasn't heard of should reach the user as an unfamiliar word, not fail schema validation and take
|
|
509
|
+
* the whole frame with it. The client maps the ones it knows to sentences and shows the rest as-is. */
|
|
510
|
+
z.object({
|
|
511
|
+
kind: z.literal("fast_mode"),
|
|
512
|
+
state: FastModeStateSchema,
|
|
513
|
+
// Absent when nothing is blocking fast mode — including on `state: "on"`, and on an `off` that simply
|
|
514
|
+
// wasn't asked for.
|
|
515
|
+
reason: z.string().optional(),
|
|
516
|
+
}),
|
|
321
517
|
/* The turn is alive but WAITING on the provider: a request failed transiently (5xx, 529, a dropped socket)
|
|
322
518
|
* and the harness is retrying it inside this same turn. A status, not a failure — nothing has been lost and
|
|
323
519
|
* the turn may still finish normally, so the client renders it where "thinking" goes rather than in the
|
|
@@ -329,16 +525,19 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
329
525
|
* actually loses the work — so the wait has to be visible, with its own next-attempt clock.
|
|
330
526
|
*
|
|
331
527
|
* `attempt`/`maxAttempts` are the harness's own counters; `nextAttemptAt` (epoch ms) is when it will try
|
|
332
|
-
* again, so the readout counts down instead of freezing on a number nobody can interpret.
|
|
333
|
-
*
|
|
334
|
-
*
|
|
528
|
+
* again, so the readout counts down instead of freezing on a number nobody can interpret. BOTH are optional
|
|
529
|
+
* for the same reason, which is that each runtime publishes a different half of the wait and none of them
|
|
530
|
+
* publishes all of it: Claude's harness reports the delay and the bound, Codex says which attempt it is on
|
|
531
|
+
* and nothing else (codex-agent.ts), OpenCode names the next instant but no bound (grok-agent.ts). Inventing
|
|
532
|
+
* the missing half would be a countdown, or a limit, the retry never agreed to. */
|
|
335
533
|
z.object({
|
|
336
534
|
kind: z.literal("provider_retry"),
|
|
337
535
|
attempt: z.number(),
|
|
338
|
-
maxAttempts: z.number(),
|
|
536
|
+
maxAttempts: z.number().optional(),
|
|
339
537
|
nextAttemptAt: z.number().optional(),
|
|
340
|
-
// The HTTP status behind it when there was one (529 reads as capacity,
|
|
341
|
-
// which). Absent for a transport failure that never got a response
|
|
538
|
+
// The HTTP status behind it when there was one (529 reads as capacity, 429 as a rate limit, 500 as a
|
|
539
|
+
// fault — the client says which). Absent for a transport failure that never got a response, and for a
|
|
540
|
+
// runtime that reports the refusal as prose rather than a code (grok-agent.ts reads it back off that).
|
|
342
541
|
status: z.number().optional(),
|
|
343
542
|
}),
|
|
344
543
|
// Every plan-limit pool for the account that served the turn, read from the CLI's usage endpoint once the
|
|
@@ -348,10 +547,73 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
348
547
|
z.object({ kind: z.literal("account_usage"), account: z.string().optional(), windows: z.array(UsageWindowSchema) }),
|
|
349
548
|
ContextUsageSchema.extend({ kind: z.literal("context_usage") }),
|
|
350
549
|
z.object({ kind: z.literal("compact"), trigger: z.string(), preTokens: z.number().optional(), postTokens: z.number().optional() }),
|
|
351
|
-
// The
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
550
|
+
// The four interactive cards. Each parks the turn until `POST /agent/reply` resolves its `requestId`.
|
|
551
|
+
PlanCardSchema,
|
|
552
|
+
QuestionCardSchema,
|
|
553
|
+
PermissionCardSchema,
|
|
554
|
+
// The agent's browser needs a person: it parked mid-sign-in on something it cannot clear itself (a captcha,
|
|
555
|
+
// a password it does not hold, a phone check). `session` names the browser session on /browsers — the card's
|
|
556
|
+
// one action is going THERE, where the live stage and Take control already are; the Browsers banner and this
|
|
557
|
+
// card resolve the same requestId. `account` is the capability the sign-in is for, so the card can say whose
|
|
558
|
+
// login is stuck even after the browser has navigated somewhere unrecognizable.
|
|
559
|
+
z.object({
|
|
560
|
+
kind: z.literal("browser_help"),
|
|
561
|
+
requestId: z.string(),
|
|
562
|
+
session: z.string(),
|
|
563
|
+
account: z.string(),
|
|
564
|
+
message: z.string(),
|
|
565
|
+
}),
|
|
566
|
+
// The agent's TERMINAL needs a person: a command it started is sitting at a prompt it cannot answer (a
|
|
567
|
+
// one-time password, a security-key touch, a confirm). `session` names the tmux session on the terminal
|
|
568
|
+
// panel — the card's one action is going THERE, where the live pane and its prompt already are, which is
|
|
569
|
+
// the same division of labour the browser card has with /browsers. Not journalled for restore, and for the
|
|
570
|
+
// browser card's reason one door along: the pane holding the prompt belongs to a process the restart kills.
|
|
571
|
+
z.object({
|
|
572
|
+
kind: z.literal("terminal_help"),
|
|
573
|
+
requestId: z.string(),
|
|
574
|
+
session: z.string(),
|
|
575
|
+
message: z.string(),
|
|
576
|
+
}),
|
|
577
|
+
/* A premium service run awaiting the owner's click. Raised OUTSIDE the turn generator — the daemon's
|
|
578
|
+
* services route parks the agent's own `services run` call and pushes this frame into the live run
|
|
579
|
+
* (platform/service-offer.ts) — so unlike the four cards above it is not journalled for restore: its
|
|
580
|
+
* waiter is the CLI's held connection, which dies with the daemon, and a restored card would offer
|
|
581
|
+
* buttons nothing is waiting behind. Settles through the same `POST /agent/reply` as every other card. */
|
|
582
|
+
z.object({ kind: z.literal("service_offer"), requestId: z.string(), offer: ServiceOfferSchema }),
|
|
583
|
+
/* One event off an approved run's stream, pushed as the provider emits it so the settled card shows the
|
|
584
|
+
* run living rather than a spinner of unknowable length. Today that is `status` lines; `result` stays off
|
|
585
|
+
* the transcript on purpose (it is the agent's answer to act on, not the card's to duplicate) — the frame
|
|
586
|
+
* carries the whole union so richer event kinds land here without a contract break. */
|
|
587
|
+
z.object({ kind: z.literal("service_event"), requestId: z.string(), event: ServiceStreamEventSchema }),
|
|
588
|
+
/* How an approved run ended, pushed after the platform answered so the card can settle as a receipt
|
|
589
|
+
* rather than a promise: `ok` served and charged, `refunded` failed to answer and charged nothing,
|
|
590
|
+
* `refused` the platform said no after the click (a raced-out allowance). `remaining` is the meter after,
|
|
591
|
+
* when the platform stated one. Skip needs no receipt — nothing happened, and `resolved` already says so. */
|
|
592
|
+
z.object({
|
|
593
|
+
kind: z.literal("service_receipt"),
|
|
594
|
+
requestId: z.string(),
|
|
595
|
+
outcome: z.enum(["ok", "refunded", "refused"]),
|
|
596
|
+
credits: z.number(),
|
|
597
|
+
remaining: z.number().optional(),
|
|
598
|
+
}),
|
|
599
|
+
/* A missing capability asking for the owner's setup — the agent hit something this sandbox is not
|
|
600
|
+
* connected to and raised the card instead of describing manual steps. Raised OUTSIDE the turn generator
|
|
601
|
+
* exactly like the service offer above (the daemon's ask route parks the agent's `capabilities request`
|
|
602
|
+
* call and pushes this frame into the live run; capabilities/capability-offer.ts), so it is not
|
|
603
|
+
* journalled for restore either: its waiter is the CLI's held connection, which dies with the daemon.
|
|
604
|
+
* Settles through the same `POST /agent/reply` as every other card. */
|
|
605
|
+
z.object({ kind: z.literal("capability_offer"), requestId: z.string(), offer: CapabilityOfferSchema }),
|
|
606
|
+
/* How an accepted ask ended, pushed once the daemon stops watching for the connection: `connected` — the
|
|
607
|
+
* capability came live while the agent waited (`id` is the connected instance, the agent's handle for it)
|
|
608
|
+
* — or `unfinished`, the setup did not complete while anyone was waiting (the deadline passed, or the
|
|
609
|
+
* asking command died). A skip needs no outcome frame — nothing was set up, and `resolved` already says
|
|
610
|
+
* so. It is what settles the card's "waiting for you to finish setup" state on every surface. */
|
|
611
|
+
z.object({
|
|
612
|
+
kind: z.literal("capability_outcome"),
|
|
613
|
+
requestId: z.string(),
|
|
614
|
+
outcome: z.enum(["connected", "unfinished"]),
|
|
615
|
+
id: z.string().optional(),
|
|
616
|
+
}),
|
|
355
617
|
// The card above named by `requestId` is released — the user answered (or dismissed it, or the turn was
|
|
356
618
|
// stopped out from under it), so the turn is executing again. Emitted by whoever parked, the moment its
|
|
357
619
|
// waiter settles, because the park's END is otherwise invisible on this stream: nothing else here says
|
|
@@ -395,6 +657,14 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
395
657
|
// nothing is coming (the turn was already a resume, or it ran on a credential with nothing to
|
|
396
658
|
// re-mint from) — that is the case where reconnecting really is the fix.
|
|
397
659
|
"claude-token-refused",
|
|
660
|
+
/* THE ACCOUNT IS FINE AND STILL NOT ALLOWED TO RUN — an Anthropic organization that has turned
|
|
661
|
+
* Claude Code off for this seat. The token authenticates, the plan's own usage endpoint answers
|
|
662
|
+
* with real pools, and every turn is refused anyway, which is why it is its own code rather than
|
|
663
|
+
* a member of either neighbour: a spent allowance comes back on a clock and a refused credential
|
|
664
|
+
* comes back on a re-mint, and NEITHER of those is true here. Only an admin re-enabling access
|
|
665
|
+
* is, so nothing is re-run and nothing asks the user to reconnect — the one recovery that looks
|
|
666
|
+
* plausible and is guaranteed to waste their time. */
|
|
667
|
+
"claude-not-entitled",
|
|
398
668
|
// The model provider itself failed transiently — 500/502/503, a 529 at capacity, a dropped
|
|
399
669
|
// socket — and the harness's own in-turn retries did not outlast it. Nothing about the workspace
|
|
400
670
|
// or the request is wrong, so the daemon remembers the turn and re-runs it on an escalating
|
|
@@ -427,9 +697,9 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
427
697
|
* that is down only spends tokens on refusals, so each attempt waits longer than the last
|
|
428
698
|
* (provider-health.ts owns the schedule).
|
|
429
699
|
*
|
|
430
|
-
* `attempt`/`maxAttempts` are on the wire so the notice can say the automation is BOUNDED.
|
|
431
|
-
*
|
|
432
|
-
*
|
|
700
|
+
* `attempt`/`maxAttempts` are on the wire so the notice can say the automation is BOUNDED. A retry that
|
|
701
|
+
* gives no account of how long it will keep going is the kind users switch back off the week they turn
|
|
702
|
+
* it on; one that says "attempt 2 of 6" is one they leave on. */
|
|
433
703
|
outage: z.object({ retryAt: z.number(), attempt: z.number(), maxAttempts: z.number() }).optional(),
|
|
434
704
|
}),
|
|
435
705
|
z.object({ kind: z.literal("done") }),
|
|
@@ -456,17 +726,27 @@ export type AttachFrame = z.infer<typeof AttachFrameSchema>;
|
|
|
456
726
|
* carries the run's prompt verbatim, and a window joining a resumed run would otherwise render the note as a
|
|
457
727
|
* message the USER wrote — the same words the user already said one run up, with a machine's preamble on them.
|
|
458
728
|
* Recognising the prefix is what lets that window reuse the bubble that is already there instead. */
|
|
729
|
+
// The instruction the three whole-turn re-runs share: what follows the note is the original request, repeated.
|
|
730
|
+
// `answered` deliberately does not carry it — what follows THAT note is not a repetition but the user's answer,
|
|
731
|
+
// and telling the model to "continue from that point instead of starting over" about words it has never seen
|
|
732
|
+
// is how a resume reads as the user contradicting themselves.
|
|
733
|
+
const REPEATED =
|
|
734
|
+
"The interrupted request is repeated below — where part of it was already completed in this session, continue from that point instead of starting over.";
|
|
459
735
|
export const RESUME_NOTES = {
|
|
460
|
-
auth:
|
|
461
|
-
outage:
|
|
462
|
-
restart:
|
|
736
|
+
auth: `The Claude credential that interrupted this conversation has been renewed, and this turn resumed automatically. ${REPEATED}`,
|
|
737
|
+
outage: `The model provider was briefly unavailable and interrupted this conversation; this turn resumed automatically. ${REPEATED}`,
|
|
738
|
+
restart: `The sandbox restarted while this turn was running, which stopped it, and this turn resumed automatically once it came back. ${REPEATED}`,
|
|
739
|
+
// A turn that was PARKED on the user when the daemon died: nothing re-runs at boot — the card is restored
|
|
740
|
+
// instead, and this is the turn their answer starts (turn-resume.ts). What rides below the note is the
|
|
741
|
+
// answer itself, so the model picks the session back up at exactly the decision it had handed over.
|
|
742
|
+
answered:
|
|
743
|
+
"The sandbox restarted while this conversation was waiting for the user to respond; it is back, and their response follows below — continue from where the session left off.",
|
|
463
744
|
} as const;
|
|
464
745
|
|
|
465
|
-
// The prompt a resume actually sends: the note
|
|
746
|
+
// The prompt a resume actually sends: the note (each carries its own account of what the words below are),
|
|
747
|
+
// then them.
|
|
466
748
|
export const withResumeNote = (prompt: string, note: string): string =>
|
|
467
|
-
Object.values(RESUME_NOTES).some((known) => prompt.startsWith(known))
|
|
468
|
-
? prompt
|
|
469
|
-
: `${note} The interrupted request is repeated below — where part of it was already completed in this session, continue from that point instead of starting over.\n\n${prompt}`;
|
|
749
|
+
Object.values(RESUME_NOTES).some((known) => prompt.startsWith(known)) ? prompt : `${note}\n\n${prompt}`;
|
|
470
750
|
|
|
471
751
|
// The user's own words inside a resumed prompt — the note and its explanation stripped back off. Returns the
|
|
472
752
|
// prompt unchanged when it is not a resume, so a caller can hand every attach head through it.
|
|
@@ -475,6 +755,42 @@ export const withoutResumeNote = (prompt: string): string => {
|
|
|
475
755
|
return note === undefined ? prompt : prompt.slice(prompt.indexOf("\n\n") + 2);
|
|
476
756
|
};
|
|
477
757
|
|
|
758
|
+
export type ResumeReason = keyof typeof RESUME_NOTES;
|
|
759
|
+
|
|
760
|
+
/* HOW A RESUMED TURN READS TO THE PERSON — the same interruption the note above tells the model, said in the
|
|
761
|
+
* transcript's own voice instead.
|
|
762
|
+
*
|
|
763
|
+
* Stripping the note out of the user's words is only half the job, and for years it was the only half anyone
|
|
764
|
+
* did: what a reopened conversation showed was a paragraph of machine prose stapled to the front of a message
|
|
765
|
+
* the user had already sent once, directly under their own copy of it. Both halves of that are wrong — it was
|
|
766
|
+
* never their sentence, and the words under it are a REPEAT rather than something new they said.
|
|
767
|
+
*
|
|
768
|
+
* So the two shapes below, which is the whole of what a reader has to be told:
|
|
769
|
+
*
|
|
770
|
+
* `notice` — the three whole-turn re-runs. The words under the note are already in the transcript one turn up,
|
|
771
|
+
* so the repeat is dropped entirely and the interruption takes its place as a muted line, sitting with the
|
|
772
|
+
* failure line it resolves ("Failed to authenticate…") and reading like every other thing that HAPPENED to a
|
|
773
|
+
* turn rather than like something anybody typed.
|
|
774
|
+
*
|
|
775
|
+
* `note` — the answered case, where what rides under the note is the user's actual answer to a card and belongs
|
|
776
|
+
* in the transcript as their words. Nothing is dropped; the explanation rides that message as a collapsed row,
|
|
777
|
+
* the same disclosure every other daemon-written note gets (TurnNote). */
|
|
778
|
+
export type ResumeDisclosure = { readonly kind: "notice"; readonly text: string } | { readonly kind: "note"; readonly note: TurnNote };
|
|
779
|
+
|
|
780
|
+
const RESUME_DISCLOSURES: Record<ResumeReason, ResumeDisclosure> = {
|
|
781
|
+
auth: { kind: "notice", text: "Claude sign-in renewed — this turn picked up where it left off." },
|
|
782
|
+
outage: { kind: "notice", text: "The model provider came back — this turn picked up where it left off." },
|
|
783
|
+
restart: { kind: "notice", text: "The sandbox came back — this turn picked up where it left off." },
|
|
784
|
+
answered: { kind: "note", note: { title: "Picked back up after a sandbox restart", text: RESUME_NOTES.answered } },
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
// What a stored prompt's resume note should be SHOWN as; undefined when the prompt is not a resume at all, so
|
|
788
|
+
// every reader of a stored prompt can ask without first testing whether it is one.
|
|
789
|
+
export const resumeDisclosure = (prompt: string): ResumeDisclosure | undefined => {
|
|
790
|
+
const reason = (Object.keys(RESUME_NOTES) as ResumeReason[]).find((key) => prompt.startsWith(RESUME_NOTES[key]));
|
|
791
|
+
return reason === undefined ? undefined : RESUME_DISCLOSURES[reason];
|
|
792
|
+
};
|
|
793
|
+
|
|
478
794
|
// One parsed line from `intentic … --output ndjson` (engine events, provider `log`, the terminal `result`).
|
|
479
795
|
// Open-ended by design — the sandbox consumes the wire shape, not @intentic/engine's types — so a string
|
|
480
796
|
// `kind` plus arbitrary extra fields pass through. The apply-events tail (intentic.contract `applyEvents`) rides
|
|
@@ -537,12 +853,22 @@ export type Boot = z.infer<typeof BootSchema>;
|
|
|
537
853
|
// the last `pnpm build:sandbox`), and that stays fully supported — the browser just compares the two sets so a
|
|
538
854
|
// route the daemon predates surfaces as a named, explained gap instead of a bare 404 nobody can attribute.
|
|
539
855
|
//
|
|
856
|
+
// `shapes` answers the half `routes` structurally cannot: a route BOTH builds have, whose payload changed
|
|
857
|
+
// between them. Names match, so nothing 404s — the call goes out and a field the browser expects is simply
|
|
858
|
+
// missing from the answer. It is a map of route name → a fingerprint of that route's input and output schema
|
|
859
|
+
// (see routes.ts), so a difference is a named route rather than "something, somewhere, moved". Beside `routes`
|
|
860
|
+
// rather than folded into it: existence covers every route, shape covers only the ones that can be expressed.
|
|
861
|
+
//
|
|
540
862
|
// Every added field is optional: a daemon built before one simply says nothing, and the browser's fallback is
|
|
541
|
-
// the pre-existing behaviour — routes all assumed present,
|
|
863
|
+
// the pre-existing behaviour — routes all assumed present, shapes all assumed to agree, the daemon assumed
|
|
864
|
+
// ready, the cache left alone. That is also why `routes` keeps its bare-string-array shape: an image already in
|
|
865
|
+
// the wild sends exactly that, and a breaking change here would fail the hello frame's own parse and take the
|
|
866
|
+
// whole event stream down for precisely the skew this frame exists to describe.
|
|
542
867
|
export const HelloSchema = z.object({
|
|
543
868
|
kind: z.literal("hello"),
|
|
544
869
|
workspaceId: z.string(),
|
|
545
870
|
routes: z.array(z.string()).optional(),
|
|
871
|
+
shapes: z.record(z.string(), z.string()).optional(),
|
|
546
872
|
build: z.string().optional(),
|
|
547
873
|
boot: BootProgressSchema.optional(),
|
|
548
874
|
});
|
|
@@ -562,6 +888,36 @@ export type ReposChanged = z.infer<typeof ReposChangedSchema>;
|
|
|
562
888
|
export const WorkspaceChangedSchema = z.object({ kind: z.literal("workspaceChanged"), paths: z.array(z.string()) });
|
|
563
889
|
export type WorkspaceChanged = z.infer<typeof WorkspaceChangedSchema>;
|
|
564
890
|
|
|
891
|
+
/* THE REPOS WHOSE REFS JUST MOVED — a commit, a checkout, a branch or tag, a rebase started or aborted.
|
|
892
|
+
*
|
|
893
|
+
* A third push for the same reason as the two above, and the reason is structural: a repo's git dir does not
|
|
894
|
+
* live under /work at all (it is relocated onto /history so an isolated turn's worktree can stand in for the
|
|
895
|
+
* workspace root — see git/repo-git-dirs.ts), and the file watcher descent-ignores `.git` besides. So no
|
|
896
|
+
* `workspaceChanged` path can ever say "a ref moved", and a surface built on the commit graph would otherwise
|
|
897
|
+
* be exactly as fresh as the last thing the user clicked.
|
|
898
|
+
*
|
|
899
|
+
* It matters most for the work the user did NOT do: the agent commits, rebases and lands out-of-band, with no
|
|
900
|
+
* HTTP mutation in any browser to hang an invalidation on. Ids are root-relative, "root" being the /work repo.
|
|
901
|
+
* Diff-not-snapshot, unlike reposChanged: this names what moved, and a repo absent from a frame is a repo that
|
|
902
|
+
* did not move rather than one that stopped existing. */
|
|
903
|
+
export const RefsChangedSchema = z.object({ kind: z.literal("refsChanged"), repos: z.array(z.string()) });
|
|
904
|
+
export type RefsChanged = z.infer<typeof RefsChangedSchema>;
|
|
905
|
+
|
|
906
|
+
/* WHICH RUNNING THINGS JUST MOVED — a session opened or exited, a dev server bound its port, a browser closed,
|
|
907
|
+
* a subagent reported in.
|
|
908
|
+
*
|
|
909
|
+
* The fourth push, and the one that covers what the other three structurally cannot: none of this state is on
|
|
910
|
+
* disk, so no `workspaceChanged` path can name it, and none of it is a ref or a repo. Before it, every view of a
|
|
911
|
+
* running thing polled on its own timer — which is to say each browser asked, forever, a question only the
|
|
912
|
+
* daemon could answer and almost always answered "no change".
|
|
913
|
+
*
|
|
914
|
+
* Diff-not-snapshot, and deliberately thin: the frame carries the DOMAIN that moved, never the roster itself.
|
|
915
|
+
* Invalidation only reaches a query something is observing, so a tab showing none of these pays a frame and no
|
|
916
|
+
* request — whereas a roster on the wire would bill every connected browser the full list whether or not
|
|
917
|
+
* anything on screen renders it. Which query keys a domain stands for is runtime-state.ts's table. */
|
|
918
|
+
export const RuntimeChangedSchema = z.object({ kind: z.literal("runtimeChanged"), domains: z.array(z.string()) });
|
|
919
|
+
export type RuntimeChanged = z.infer<typeof RuntimeChangedSchema>;
|
|
920
|
+
|
|
565
921
|
// One connected browser tab of a sandbox member. Identity fields come from the caller's verified Google ID
|
|
566
922
|
// token; activity fields from the tab's own /system/presence reports. No timestamps on the wire — an entry's
|
|
567
923
|
// lifetime IS its /events connection's lifetime, so there is nothing to age out or compare clocks over.
|
|
@@ -571,6 +927,9 @@ export const PresenceUserSchema = z.object({
|
|
|
571
927
|
email: z.string(),
|
|
572
928
|
name: z.string().optional(),
|
|
573
929
|
picture: z.string().optional(),
|
|
930
|
+
// The caller's trust tier, resolved by the authorizer at connection time. On the roster so every member
|
|
931
|
+
// can see who may do what — and so a tab knows its OWN role without an owner-only lookup.
|
|
932
|
+
role: MemberRoleSchema,
|
|
574
933
|
idle: z.boolean(),
|
|
575
934
|
// Route/view name the tab is on ("workspace", "automations", "ext:<id>/<key>", …).
|
|
576
935
|
view: z.string().optional(),
|
|
@@ -596,14 +955,16 @@ export const AgentsSchema = z.object({ kind: z.literal("agents"), agents: z.arra
|
|
|
596
955
|
export type Agents = z.infer<typeof AgentsSchema>;
|
|
597
956
|
|
|
598
957
|
// The /events stream union: the hello identity frame, then liveness heartbeats interleaved with boot progress,
|
|
599
|
-
// workspace-change batches, repo-set snapshots, and presence + fleet
|
|
600
|
-
// yielded frame against this, so all kinds must live here.
|
|
958
|
+
// workspace-change batches, repo-set snapshots, ref-move batches, runtime-domain nudges, and presence + fleet
|
|
959
|
+
// roster snapshots. oRPC validates every yielded frame against this, so all kinds must live here.
|
|
601
960
|
export const SystemEventSchema = z.discriminatedUnion("kind", [
|
|
602
961
|
HelloSchema,
|
|
603
962
|
HeartbeatSchema,
|
|
604
963
|
BootSchema,
|
|
605
964
|
WorkspaceChangedSchema,
|
|
606
965
|
ReposChangedSchema,
|
|
966
|
+
RefsChangedSchema,
|
|
967
|
+
RuntimeChangedSchema,
|
|
607
968
|
PresenceSchema,
|
|
608
969
|
AgentsSchema,
|
|
609
970
|
]);
|