@intentic/sandbox-contract 1.166.0 → 1.168.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-catalog.d.ts +20 -3
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +81 -6
- package/dist/agent-catalog.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +36 -8
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +1 -2
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +55 -39
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts +1 -1
- package/dist/contracts/extensions.contract.d.ts +4 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/gate.contract.d.ts +1 -1
- package/dist/contracts/kimi.contract.d.ts +0 -70
- package/dist/contracts/kimi.contract.d.ts.map +1 -1
- package/dist/contracts/kimi.contract.js +1 -5
- package/dist/contracts/kimi.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +1 -39
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +29 -5
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +78 -4
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +9 -2
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts +47 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.js.map +1 -1
- package/dist/events.d.ts +92 -159
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +37 -4
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +267 -181
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +269 -44
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +92 -25
- package/dist/schemas.js.map +1 -1
- package/dist/title.d.ts.map +1 -1
- package/dist/title.js.map +1 -1
- package/dist/workspace-state.d.ts +9 -0
- package/dist/workspace-state.d.ts.map +1 -0
- package/dist/workspace-state.js +71 -0
- package/dist/workspace-state.js.map +1 -0
- package/package.json +2 -2
- package/src/agent-catalog.test.ts +118 -0
- package/src/agent-catalog.ts +183 -24
- package/src/contracts/agent.contract.ts +1 -14
- package/src/contracts/agents.contract.ts +8 -6
- package/src/contracts/gate.contract.ts +2 -2
- package/src/contracts/kimi.contract.ts +4 -21
- package/src/contracts/system.contract.ts +24 -1
- package/src/contracts/translator.contract.ts +4 -6
- package/src/events.test.ts +32 -0
- package/src/events.ts +105 -26
- package/src/index.ts +1 -0
- package/src/model-order.ts +1 -1
- package/src/quick-model.test.ts +1 -1
- package/src/schemas.test.ts +8 -4
- package/src/schemas.ts +296 -111
- package/src/title.ts +6 -2
- package/src/workspace-state.test.ts +129 -0
- package/src/workspace-state.ts +160 -0
package/src/schemas.ts
CHANGED
|
@@ -50,8 +50,8 @@ export const EditorContextSchema = z.object({
|
|
|
50
50
|
});
|
|
51
51
|
export type EditorContext = z.infer<typeof EditorContextSchema>;
|
|
52
52
|
|
|
53
|
-
// The client-minted stable conversation identity. Constrained because
|
|
54
|
-
// and filesystem paths — the regex is the injection guard. Shared by
|
|
53
|
+
// The client-minted stable conversation identity. Constrained because isolated conversations also use it in
|
|
54
|
+
// branch names (agent/<id>) and filesystem paths — the regex is the injection guard. Shared by turn + attach.
|
|
55
55
|
const ConversationIdSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/);
|
|
56
56
|
|
|
57
57
|
// Where a conversation came from when nobody typed it into the browser: an automation wake carrying a message
|
|
@@ -102,7 +102,7 @@ export const AgentTurnSchema = z
|
|
|
102
102
|
account: z.string().optional(),
|
|
103
103
|
sessionId: z.string().optional(),
|
|
104
104
|
// The client-minted stable conversation identity (survives provider/account/harness switches, which
|
|
105
|
-
// retire sessions). Keys the fleet registry entry
|
|
105
|
+
// retire sessions). Keys the fleet registry entry and turn run, plus the worktree when isolated.
|
|
106
106
|
conversationId: ConversationIdSchema.optional(),
|
|
107
107
|
// When true, the turn runs in the conversation's isolated git worktree (created lazily on first use)
|
|
108
108
|
// instead of the shared /work tree — the parallel-agents mode. Requires conversationId.
|
|
@@ -157,10 +157,10 @@ export const AttachTurnSchema = z.object({
|
|
|
157
157
|
});
|
|
158
158
|
export type AttachTurn = z.infer<typeof AttachTurnSchema>;
|
|
159
159
|
|
|
160
|
-
// ---- agents: the
|
|
161
|
-
// A "fleet agent" is
|
|
162
|
-
//
|
|
163
|
-
// surface shows
|
|
160
|
+
// ---- agents: the conversation fleet ----
|
|
161
|
+
// A "fleet agent" is any conversation with a registry entry, keyed by its conversationId. Isolated ones own a
|
|
162
|
+
// git worktree (branch agent/<id> in every workspace repo); workspace conversations have no branch. The fleet
|
|
163
|
+
// surface shows both through the same status/activity/cost lifecycle.
|
|
164
164
|
|
|
165
165
|
// idle/running/awaiting are the turn lifecycle (awaiting = paused on a plan approval or question); ready /
|
|
166
166
|
// landed / conflict are outcomes of the land flow — `ready` is a clean completion whose delta stayed on the
|
|
@@ -173,7 +173,31 @@ export type AttachTurn = z.infer<typeof AttachTurnSchema>;
|
|
|
173
173
|
// unlabelled: without it such a turn rehydrates as `idle`, which is the resting status of a turn that finished
|
|
174
174
|
// CLEANLY, so the board files a killed agent under Finished and the question it was holding disappears with the
|
|
175
175
|
// process that asked it. See agents-store.ts — this is the status a live turn leaves on disk.
|
|
176
|
-
|
|
176
|
+
//
|
|
177
|
+
/* `stopping` and `stopped` are the two halves of a user's Stop, and they exist because a hard-cancel is NOT
|
|
178
|
+
* instant: /agent/stop aborts the provider and then waits for the turn's generator to unwind (worktree and
|
|
179
|
+
* registry cleanup), which is seconds of real time. For that whole window the runtime half still said
|
|
180
|
+
* `running`, so every surface kept its spinner turning on a turn the user had already killed — and then the
|
|
181
|
+
* card jumped to a settled state out of nowhere. `stopping` is what the daemon knows the instant the abort
|
|
182
|
+
* lands, published immediately so the press has a visible result; `stopped` is where the turn comes to rest.
|
|
183
|
+
*
|
|
184
|
+
* `stopped` is deliberately its own value rather than `interrupted` or `error`. Not `error`, which is what a
|
|
185
|
+
* stopped turn used to report (every provider adapter surfaces the abort's unwind as an error frame) — a card
|
|
186
|
+
* accusing the user's own deliberate press of being a failure. Not `interrupted` either: that one means the
|
|
187
|
+
* daemon died under the turn, and a boot pass may re-run it, which is precisely what must never happen to a
|
|
188
|
+
* turn a person chose to end. */
|
|
189
|
+
export const AgentStatusSchema = z.enum([
|
|
190
|
+
"idle",
|
|
191
|
+
"running",
|
|
192
|
+
"awaiting",
|
|
193
|
+
"stopping",
|
|
194
|
+
"stopped",
|
|
195
|
+
"ready",
|
|
196
|
+
"landed",
|
|
197
|
+
"conflict",
|
|
198
|
+
"error",
|
|
199
|
+
"interrupted",
|
|
200
|
+
]);
|
|
177
201
|
export type AgentStatus = z.infer<typeof AgentStatusSchema>;
|
|
178
202
|
// The card's live activity snippet: the last tool the agent used (with its target) and the in-progress todo.
|
|
179
203
|
export const AgentActivitySchema = z.object({
|
|
@@ -238,6 +262,14 @@ export const AgentSummarySchema = z.object({
|
|
|
238
262
|
// Completed turns and lifetime tool calls — the card's msgs/tools counters.
|
|
239
263
|
turns: z.number().optional(),
|
|
240
264
|
toolUses: z.number().optional(),
|
|
265
|
+
/* The agents THIS agent started (SubagentSessionSchema), live and lifetime. Absent ⇒ it has never delegated,
|
|
266
|
+
* which is most agents — so the card's chip appears on content rather than reading "0" down the board.
|
|
267
|
+
*
|
|
268
|
+
* It earns a place on a card because a fleet card is the answer to "what is this agent up to", and an agent
|
|
269
|
+
* running five children looked exactly like an agent running none: the work was real, the spend was real, and
|
|
270
|
+
* the board said nothing. The tokens are NOT folded into the parent's cost — a child's spend is its own, and
|
|
271
|
+
* the Subagents area is where it is attributed. */
|
|
272
|
+
subagents: z.object({ running: z.number(), total: z.number() }).optional(),
|
|
241
273
|
// The agent's cumulative output (base → branch tip across every repo), refreshed on each land —
|
|
242
274
|
// the card's "12 files · +412 −96" readout. Independent of what has landed.
|
|
243
275
|
diff: z.object({ files: z.number(), insertions: z.number(), deletions: z.number() }).optional(),
|
|
@@ -355,14 +387,56 @@ export const AgentLandSchema = z.object({ id: z.string().min(1), mode: LandModeS
|
|
|
355
387
|
// The providers whose model can run UNDER the Claude Code harness through the bundled translator (CLIProxyAPI),
|
|
356
388
|
// which holds their SUBSCRIPTION OAuth and re-serves it behind an Anthropic endpoint. The `claude` provider is
|
|
357
389
|
// absent — native Anthropic OAuth serves it directly, without the translator. Codex and Grok also have a native
|
|
358
|
-
// runtime and so carry the harness axis;
|
|
359
|
-
|
|
360
|
-
export const KeyedProviderSchema = z.enum(["codex", "grok", "gemini"]);
|
|
390
|
+
// runtime and so carry the harness axis; Kimi and Gemini are routed-only, so their turns always use Claude Code.
|
|
391
|
+
export const KeyedProviderSchema = z.enum(["codex", "grok", "kimi", "gemini"]);
|
|
361
392
|
export type KeyedProvider = z.infer<typeof KeyedProviderSchema>;
|
|
362
393
|
|
|
394
|
+
// ---- plan-limit usage ----
|
|
395
|
+
// Declared ABOVE both account shapes because both carry it: headroom is one idea in this product, not a Claude
|
|
396
|
+
// idea that other providers imitate. A native account (OauthAccount) and a routed subscription
|
|
397
|
+
// (TranslatorAccount) differ in who holds the credential and how the reading is taken — never in what a
|
|
398
|
+
// reading IS — so every surface that draws a percentage reads this one type and no other.
|
|
399
|
+
|
|
400
|
+
// One plan-limit pool. `kind` is the provider's own key ('five_hour' | 'seven_day' | 'seven_day_opus' |
|
|
401
|
+
// 'seven_day_sonnet' | 'model:Fable' | …) rather than an enum we'd have to keep in step with the provider: an
|
|
402
|
+
// unrecognised pool is shown under its raw key, which is far better than being silently folded into a
|
|
403
|
+
// neighbour. `label` is the provider's OWN display name where it supplies one (the per-model buckets do) — it
|
|
404
|
+
// wins over anything we'd infer, because the model names in a plan's limits are the provider's to rename.
|
|
405
|
+
// `resetsAt` is epoch SECONDS (matching the SDK's frame).
|
|
406
|
+
export const UsageWindowSchema = z.object({
|
|
407
|
+
kind: z.string(),
|
|
408
|
+
label: z.string().optional(),
|
|
409
|
+
utilization: z.number(), // 0-100
|
|
410
|
+
resetsAt: z.number().optional(),
|
|
411
|
+
});
|
|
412
|
+
export type UsageWindow = z.infer<typeof UsageWindowSchema>;
|
|
413
|
+
|
|
414
|
+
// An account's headroom: EVERY window the provider reports, read together, plus when the reading was taken.
|
|
415
|
+
// All of them, not the binding one, because "which pool is binding" changes between turns and a reader
|
|
416
|
+
// comparing accounts needs the same pools on every row. How the reading is TAKEN is per provider and stops at
|
|
417
|
+
// the daemon's readers: Claude's rides the turn's own stream, ChatGPT's and Google's are pulled through
|
|
418
|
+
// CLIProxyAPI's credential-scoped management call. All of them are control requests, so none costs tokens.
|
|
419
|
+
//
|
|
420
|
+
// Within one window utilization only climbs, so an un-reset window stays a valid FLOOR however old it is; past
|
|
421
|
+
// its `resetsAt` it describes a pool that no longer exists and the store drops it. `measuredAt` is epoch MS
|
|
422
|
+
// (matching connectedAt) — deliberately a different unit from the windows' seconds.
|
|
423
|
+
export const AccountUsageSchema = z.object({
|
|
424
|
+
windows: z.array(UsageWindowSchema),
|
|
425
|
+
measuredAt: z.number(),
|
|
426
|
+
});
|
|
427
|
+
export type AccountUsage = z.infer<typeof AccountUsageSchema>;
|
|
428
|
+
|
|
363
429
|
// One connected subscription in the translator. `name` is CLIProxyAPI's auth-file name — the stable store key a
|
|
364
430
|
// disconnect addresses — and `label` the sign-in identity it reported (the account email, else the file name).
|
|
365
|
-
export const TranslatorAccountSchema = z.object({
|
|
431
|
+
export const TranslatorAccountSchema = z.object({
|
|
432
|
+
name: z.string(),
|
|
433
|
+
label: z.string(),
|
|
434
|
+
// The same headroom an OauthAccount carries, on the same field, for the same reason: the account rows are
|
|
435
|
+
// one list to the reader. Optional because a provider whose quota this sandbox cannot read (Grok, Kimi) —
|
|
436
|
+
// or one that did not answer — must still render as the connected account it is, with a dot instead of a
|
|
437
|
+
// ring.
|
|
438
|
+
usage: AccountUsageSchema.optional(),
|
|
439
|
+
});
|
|
366
440
|
export type TranslatorAccount = z.infer<typeof TranslatorAccountSchema>;
|
|
367
441
|
// Which routed-provider subscriptions are connected in the translator, per provider — a LIST per provider, not
|
|
368
442
|
// a flag: CLIProxyAPI holds any number of auth files per provider side by side and balances requests across
|
|
@@ -371,6 +445,7 @@ export type TranslatorAccount = z.infer<typeof TranslatorAccountSchema>;
|
|
|
371
445
|
export const TranslatorAccountsSchema = z.object({
|
|
372
446
|
codex: z.array(TranslatorAccountSchema),
|
|
373
447
|
grok: z.array(TranslatorAccountSchema),
|
|
448
|
+
kimi: z.array(TranslatorAccountSchema),
|
|
374
449
|
gemini: z.array(TranslatorAccountSchema),
|
|
375
450
|
});
|
|
376
451
|
export type TranslatorAccounts = z.infer<typeof TranslatorAccountsSchema>;
|
|
@@ -429,14 +504,8 @@ export const SteerSchema = z
|
|
|
429
504
|
// True cancel for the conversation's in-flight turn — aborts the agent daemon-side, unlike closing the
|
|
430
505
|
// /agent fetch (which sends no cancel frame).
|
|
431
506
|
export const StopTurnSchema = z.object({ conversationId: z.string().min(1) });
|
|
432
|
-
|
|
433
|
-
//
|
|
434
|
-
// action a spent allowance offers when the sandbox holds more than one; omitted, the turn re-runs on
|
|
435
|
-
// whatever served it (a plain "try again now"). NOT_FOUND when nothing is pending (the failure was already
|
|
436
|
-
// superseded by a fresh turn, or the daemon restarted).
|
|
437
|
-
export const ResumeLimitSchema = z.object({ conversationId: z.string().min(1), account: z.string().min(1).optional() });
|
|
438
|
-
|
|
439
|
-
// ---- claude subscription usage ----
|
|
507
|
+
|
|
508
|
+
// ---- claude rate-limit gate ----
|
|
440
509
|
// The GATE signal: whether the provider is letting turns through right now, and — when it is refusing — which
|
|
441
510
|
// window is binding and when it lifts. This is the SDK's rate_limit_event, mapped one-to-one, and it is only
|
|
442
511
|
// ever about the CURRENT moment. It is deliberately NOT the thing the headroom displays read: the event names a
|
|
@@ -450,34 +519,6 @@ export const RateLimitInfoSchema = z.object({
|
|
|
450
519
|
});
|
|
451
520
|
export type RateLimitInfo = z.infer<typeof RateLimitInfoSchema>;
|
|
452
521
|
|
|
453
|
-
// One plan-limit pool. `kind` is the provider's own key ('five_hour' | 'seven_day' | 'seven_day_opus' |
|
|
454
|
-
// 'seven_day_sonnet' | 'model:Fable' | …) rather than an enum we'd have to keep in step with the provider: an
|
|
455
|
-
// unrecognised pool is shown under its raw key, which is far better than being silently folded into a
|
|
456
|
-
// neighbour. `label` is the provider's OWN display name where it supplies one (the per-model buckets do) — it
|
|
457
|
-
// wins over anything we'd infer, because the model names in a plan's limits are the provider's to rename.
|
|
458
|
-
// `resetsAt` is epoch SECONDS (matching the SDK's frame).
|
|
459
|
-
export const UsageWindowSchema = z.object({
|
|
460
|
-
kind: z.string(),
|
|
461
|
-
label: z.string().optional(),
|
|
462
|
-
utilization: z.number(), // 0-100
|
|
463
|
-
resetsAt: z.number().optional(),
|
|
464
|
-
});
|
|
465
|
-
export type UsageWindow = z.infer<typeof UsageWindowSchema>;
|
|
466
|
-
|
|
467
|
-
// An account's headroom: EVERY window the provider reports, read together, plus when the reading was taken.
|
|
468
|
-
// All of them, not the binding one, because "which pool is binding" changes between turns and a reader
|
|
469
|
-
// comparing accounts needs the same pools on every row. Sourced from the CLI's own usage endpoint at turn end
|
|
470
|
-
// (see claudeUsageWindows) — a control request, so it costs no tokens.
|
|
471
|
-
//
|
|
472
|
-
// Within one window utilization only climbs, so an un-reset window stays a valid FLOOR however old it is; past
|
|
473
|
-
// its `resetsAt` it describes a pool that no longer exists and the store drops it. `measuredAt` is epoch MS
|
|
474
|
-
// (matching connectedAt) — deliberately a different unit from the windows' seconds.
|
|
475
|
-
export const AccountUsageSchema = z.object({
|
|
476
|
-
windows: z.array(UsageWindowSchema),
|
|
477
|
-
measuredAt: z.number(),
|
|
478
|
-
});
|
|
479
|
-
export type AccountUsage = z.infer<typeof AccountUsageSchema>;
|
|
480
|
-
|
|
481
522
|
// ---- provider oauth ----
|
|
482
523
|
// Claude uses the PKCE authorize-URL + paste-back handshake (start → exchange). Codex uses OpenAI's device-code
|
|
483
524
|
// flow (start → poll): the browser signs in at verificationUri and enters userCode; the daemon polls until done.
|
|
@@ -504,8 +545,9 @@ export const OauthAccountSchema = z.object({
|
|
|
504
545
|
needsReauth: z.boolean().optional(),
|
|
505
546
|
detail: z.string().optional(),
|
|
506
547
|
// The account's last known subscription-usage snapshot, so the picker can show what's left on each account
|
|
507
|
-
// before the user commits a turn to one.
|
|
508
|
-
//
|
|
548
|
+
// before the user commits a turn to one. Absent until a reading exists for it — an unmeasured account reads
|
|
549
|
+
// as unknown, never 0%. Claude is the provider that fills it here, because its stream reports the windows;
|
|
550
|
+
// the routed subscriptions carry the identical field on TranslatorAccount, filled by a pulled reading.
|
|
509
551
|
usage: AccountUsageSchema.optional(),
|
|
510
552
|
});
|
|
511
553
|
export type OauthAccount = z.infer<typeof OauthAccountSchema>;
|
|
@@ -525,11 +567,6 @@ export const OauthExchangeSchema = z.object({
|
|
|
525
567
|
label: z.string().optional(),
|
|
526
568
|
});
|
|
527
569
|
export const AuthorizeChallengeSchema = z.object({ authorizeUrl: z.string(), verifier: z.string(), state: z.string() });
|
|
528
|
-
// Kimi (Moonshot) authenticates with an API key, not OAuth: the user pastes a key from their Moonshot account
|
|
529
|
-
// and the sandbox stores it as an account (one key per account, several accounts side by side). `label` is the
|
|
530
|
-
// user's display name (blank ⇒ the daemon derives a default). The key never rides back out — connection status
|
|
531
|
-
// is existence in `/kimi/accounts`.
|
|
532
|
-
export const KimiConnectSchema = z.object({ apiKey: z.string().min(1), label: z.string().optional() });
|
|
533
570
|
// xAI Grok (via OpenCode) uses subscription OAuth via the headless device-code method. `start` returns the
|
|
534
571
|
// `url` the user opens (xAI's verification_uri_complete, which pre-fills the code) and `code` — the same
|
|
535
572
|
// one-time code, surfaced so the card matches x.ai exactly. There is no paste-back: OpenCode polls to
|
|
@@ -539,13 +576,15 @@ export const KimiConnectSchema = z.object({ apiKey: z.string().min(1), label: z.
|
|
|
539
576
|
// A device-code login start: the verification URL + the one-time code the user enters there. The native Grok
|
|
540
577
|
// flow (via OpenCode) — see TranslatorStartSchema for the routed-provider connect, which adds `state`.
|
|
541
578
|
export const DeviceStartSchema = z.object({ url: z.string(), code: z.string() });
|
|
542
|
-
// A routed-provider subscription login start (codex/grok/gemini via CLIProxyAPI).
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
579
|
+
// A routed-provider subscription login start (codex/grok/kimi/gemini via CLIProxyAPI). Device flows poll to
|
|
580
|
+
// completion after the user approves upstream; redirect flows need the browser's landing URL pasted back. The
|
|
581
|
+
// explicit flow discriminator matters even when a provider's verification URL already embeds its optional code.
|
|
582
|
+
export const TranslatorStartSchema = z.object({
|
|
583
|
+
url: z.string(),
|
|
584
|
+
code: z.string(),
|
|
585
|
+
state: z.string(),
|
|
586
|
+
flow: z.enum(["device", "redirect"]),
|
|
587
|
+
});
|
|
549
588
|
// The paste-back half of a redirect login: the URL the provider sent the browser to, carrying the grant as
|
|
550
589
|
// ?code=&state=. `state` ties it to the handshake that issued it — the translator rejects a mismatch.
|
|
551
590
|
export const TranslatorCompleteSchema = z.object({
|
|
@@ -555,7 +594,7 @@ export const TranslatorCompleteSchema = z.object({
|
|
|
555
594
|
});
|
|
556
595
|
// A provider's model catalog, resolved daemon-side from live discovery with a persisted last-known-good list and
|
|
557
596
|
// a seed floor (Grok via opencode.ts xaiModels, Codex via codex-models.ts, Claude via the Agent SDK's
|
|
558
|
-
// supportedModels) — never empty, so the picker is never blank. `label` is the
|
|
597
|
+
// supportedModels) — never empty, so the picker is never blank. `label` is the provider's display name; `default`
|
|
559
598
|
// is the model a fresh chat on that provider seeds (always present). Shared by /grok/models, /codex/models,
|
|
560
599
|
// /claude/models. `efforts` is the reasoning-effort tiers the model accepts (Claude reports them per model);
|
|
561
600
|
// empty ⇒ the client's default tiers.
|
|
@@ -563,8 +602,8 @@ export const TranslatorCompleteSchema = z.object({
|
|
|
563
602
|
// EVERY field here is provider-reported — nothing about a model is curated in this repo, so a new release or a
|
|
564
603
|
// renamed family flows to the UI with no code change. Providers differ in how much they publish: the Claude
|
|
565
604
|
// Agent SDK reports a display name, a capability description, effort tiers, and capability flags, while the
|
|
566
|
-
// OpenAI-compatible /v1/models endpoints
|
|
567
|
-
//
|
|
605
|
+
// Some OpenAI-compatible /v1/models endpoints report ids only — those rows render label-only, and that absence
|
|
606
|
+
// is the honest answer rather than something to paper over with a hand-written table.
|
|
568
607
|
//
|
|
569
608
|
// ORDER IS MEANINGFUL: `models` arrives in the provider's own preference order, which is what the picker sorts
|
|
570
609
|
// by, and `default` is the provider's own default. Neither is re-ranked locally.
|
|
@@ -624,6 +663,11 @@ export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"
|
|
|
624
663
|
// iqSearch — loads the image-baked iq Claude Code plugin (skill + SessionStart nudge) so the agent
|
|
625
664
|
// prefers the iq CLI over grep/find/Glob; off ⇒ plugin not loaded, native search tools
|
|
626
665
|
// only. Opt-in (default off); the browser Search box uses iq regardless.
|
|
666
|
+
// iqContext — retrieves for the user's message BEFORE the turn starts and prepends the ranked answer
|
|
667
|
+
// to it, so the model opens with the anchors instead of paying a search round-trip to
|
|
668
|
+
// find them. Independent of iqSearch: that one teaches the agent to search, this one
|
|
669
|
+
// answers ahead of it.
|
|
670
|
+
// iqContextHoldout — measurement control for iqContext, same shape as terseHoldout (UsageTurn.iqContext).
|
|
627
671
|
// outputCleaners — the Bash output-cleaner spec (agent-output-filter): "off" = filter disabled (default),
|
|
628
672
|
// "" = all cleaners on, else an iq-style allow-list / default-minus
|
|
629
673
|
// spec ("git,pnpm" = only those; "-cap" = all except). Threaded to the filter via env.
|
|
@@ -643,6 +687,7 @@ export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"
|
|
|
643
687
|
// surface fail to parse the moment a toggle is added — which reaches the user as a page of switches that are
|
|
644
688
|
// silently dead, not as an error. It also means an older on-disk manifest keeps the owner's other picks rather
|
|
645
689
|
// than being discarded whole.
|
|
690
|
+
|
|
646
691
|
export const SandboxSettingsSchema = z.object({
|
|
647
692
|
stableSystemPrompt: z.boolean().default(false),
|
|
648
693
|
skills: z.array(z.string()).default([]),
|
|
@@ -678,6 +723,21 @@ export const SandboxSettingsSchema = z.object({
|
|
|
678
723
|
* Cap is roomy — the bases it stands in for are ~6.8k characters — but finite, because every turn pays it. */
|
|
679
724
|
systemPrompt: z.string().max(20000).default(""),
|
|
680
725
|
iqSearch: z.boolean().default(false),
|
|
726
|
+
/* RETRIEVE BEFORE THE TURN, don't wait to be asked. The daemon runs the user's message through the resident
|
|
727
|
+
* iq engine and prepends the ranked answer to it, so a turn that would have opened with two or three search
|
|
728
|
+
* calls opens with the anchors already in hand. Independent of `iqSearch`, which only teaches the agent to
|
|
729
|
+
* reach for the CLI once it decides to search — this one answers ahead of that decision, and the two
|
|
730
|
+
* compose: the injected capsule names the anchors, the CLI is there for what it missed.
|
|
731
|
+
*
|
|
732
|
+
* It rides the USER message (turn-context.ts), never the system prompt, for the same reason the setup
|
|
733
|
+
* notice does: it changes every turn, and the system prefix is kept byte-stable for the prompt cache.
|
|
734
|
+
* Off by default — it spends input tokens on every eligible turn, and whether that trade pays is exactly
|
|
735
|
+
* what the holdout below is for. */
|
|
736
|
+
iqContext: z.boolean().default(false),
|
|
737
|
+
// Measurement control for the pre-injection, identical in shape to `terseHoldout`: a fraction [0,1] of
|
|
738
|
+
// otherwise-eligible turns run WITHOUT the retrieved context and stamp their arm onto the ledger
|
|
739
|
+
// (UsageTurn.iqContext), so the report compares two real populations of turns instead of asserting a saving.
|
|
740
|
+
iqContextHoldout: z.number().min(0).max(1).default(0),
|
|
681
741
|
outputCleaners: z.string().default("off"),
|
|
682
742
|
outputHoldout: z.number().min(0).max(1).default(0),
|
|
683
743
|
filterBackend: z.enum(["native", "rtk"]).default("native"),
|
|
@@ -699,17 +759,12 @@ export const SandboxSettingsSchema = z.object({
|
|
|
699
759
|
* automation-opened agents (Discord, webhooks, email) finish turns with no browser in the room — a
|
|
700
760
|
* browser-held preference could not govern them. Per-agent override: AgentSummarySchema.autoLand. */
|
|
701
761
|
autoLand: z.boolean().default(true),
|
|
702
|
-
// When a turn dies on the Claude subscription's usage limit, re-run it automatically once the limit
|
|
703
|
-
// window resets (a minute after, so a skewed clock can't retry into the same closed window). Off by
|
|
704
|
-
// default: an unattended retry spends the fresh window without the user in the room, so the daemon
|
|
705
|
-
// records every limit-hit either way and the chat OFFERS the toggle at the moment it would have helped —
|
|
706
|
-
// enabling it then still resumes the turn that just bounced.
|
|
707
|
-
autoResumeOnLimit: z.boolean().default(false),
|
|
708
762
|
/* When a turn dies because the MODEL PROVIDER was failing (500/502/503, a 529 at capacity, a dropped
|
|
709
763
|
* socket), re-run it on an escalating backoff until it goes through or the attempts are spent.
|
|
710
764
|
*
|
|
711
|
-
* Defaults ON,
|
|
712
|
-
*
|
|
765
|
+
* Defaults ON, and a spent Claude allowance is the counter-example that explains why: that one is the
|
|
766
|
+
* user's own budget, and resuming into a freshly reset window spends something they may have been saving —
|
|
767
|
+
* so a usage limit stops the turn and says when it resets, and nothing re-runs it. An
|
|
713
768
|
* outage resume spends nothing the dead turn had not already committed, resolves in minutes rather than
|
|
714
769
|
* hours, and — the deciding argument — the turns hurt worst by it are the ones with nobody in the room
|
|
715
770
|
* (automation wakes, Discord, webhooks), which no browser-held preference could ever rescue. It is the same
|
|
@@ -717,8 +772,8 @@ export const SandboxSettingsSchema = z.object({
|
|
|
717
772
|
resumeAfterOutage: z.boolean().default(true),
|
|
718
773
|
/* When the daemon dies under a running turn, re-run that turn once it is back (agent/turn-journal.ts records
|
|
719
774
|
* every in-flight turn; the boot pass in agent/turn-resume.ts re-runs what survived). ON by default, where
|
|
720
|
-
*
|
|
721
|
-
* budget, while a restart is usually intentic's OWN doing — the container is recreated on every update,
|
|
775
|
+
* a spent usage limit re-runs nothing, and the difference is who broke the turn: a spent allowance is the
|
|
776
|
+
* user's own budget, while a restart is usually intentic's OWN doing — the container is recreated on every update,
|
|
722
777
|
* every environment approval and every dev-sandbox.sh swap. Approving the Dockerfile change an agent asked
|
|
723
778
|
* for must not cost the run that asked for it, and a user who just clicked Approve is in the room expecting
|
|
724
779
|
* the work to continue, not a second button.
|
|
@@ -726,7 +781,7 @@ export const SandboxSettingsSchema = z.object({
|
|
|
726
781
|
* OFF still records the interruption: the fleet card reads `interrupted` (see AgentStatusSchema) and an
|
|
727
782
|
* automation's row shows an `interrupted` run — nothing is re-run, but nothing is silently lost either. */
|
|
728
783
|
autoResumeOnRestart: z.boolean().default(true),
|
|
729
|
-
/* THE LANDING GATE — the check command run over the COMPOSITE of landed work
|
|
784
|
+
/* THE LANDING GATE — the check command run over the COMPOSITE of landed work after a landing debounce.
|
|
730
785
|
* Empty ⇒ no gate at all, which is the default: only the owner knows what verifies this workspace, and a
|
|
731
786
|
* guessed command that fails on a fresh clone would read as the gate finding a bug on its first run.
|
|
732
787
|
*
|
|
@@ -749,7 +804,7 @@ export const SandboxSettingsSchema = z.object({
|
|
|
749
804
|
// gate exists to prevent is a green light nobody earned.
|
|
750
805
|
gateTimeoutMs: z.number().min(60_000).max(3_600_000).default(900_000),
|
|
751
806
|
/* Wake a fixer automatically when the gate goes red, instead of only lighting the badge. ON with a
|
|
752
|
-
* configured command,
|
|
807
|
+
* configured command, and the difference from the unattended spend a usage limit refuses is
|
|
753
808
|
* that the spend here is the POINT: a red gate whose fix waits for the user to notice has moved the CI
|
|
754
809
|
* round-trip into the workspace without removing it from the user's day. One attempt per verdict, so a
|
|
755
810
|
* command that fails for a reason no agent can fix costs one turn, not a loop (gate/gate.ts). */
|
|
@@ -821,13 +876,21 @@ export const InputSavingsSchema = z.object({
|
|
|
821
876
|
});
|
|
822
877
|
export type InputSavings = z.infer<typeof InputSavingsSchema>;
|
|
823
878
|
|
|
824
|
-
// One arm of
|
|
825
|
-
// without it.
|
|
826
|
-
export const SavingsArmSchema = z.object({ turns: z.number(),
|
|
879
|
+
// One arm of a turn-level experiment: the turns that ran with the mechanism, and the turns the holdout ran
|
|
880
|
+
// without it. A mean PER TURN, because the arms never hold the same number of turns.
|
|
881
|
+
export const SavingsArmSchema = z.object({ turns: z.number(), mean: z.number() });
|
|
827
882
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
883
|
+
/* A turn-level A/B — the one shape both of this sandbox's turn experiments report in, because they differ in
|
|
884
|
+
* nothing but which flag flips and what the turns are judged on. Only turns the mechanism was ELIGIBLE for are
|
|
885
|
+
* counted: a turn under a custom system prompt drops the terse steer along with everything else the daemon
|
|
886
|
+
* appends, so it belongs to neither arm.
|
|
887
|
+
*
|
|
888
|
+
* `metric` says what `mean` counts and what `deltaPct` is a delta in. The terse steer is judged on the model's
|
|
889
|
+
* OWN output tokens, which is the thing it steers. Pre-injection is judged on COST, because it spends input
|
|
890
|
+
* tokens deliberately to buy back search turns — scored on output tokens it would look like a pure expense,
|
|
891
|
+
* and scored on input tokens like a pure loss; the trade only nets out in money. */
|
|
892
|
+
export const TurnExperimentSchema = z.object({
|
|
893
|
+
metric: z.enum(["outputTokens", "costUsd"]),
|
|
831
894
|
on: SavingsArmSchema,
|
|
832
895
|
off: SavingsArmSchema,
|
|
833
896
|
// Turns per arm before a delta is reported at all. Carried on the wire so the screen's "measuring…" state
|
|
@@ -835,18 +898,24 @@ export const OutputSavingsSchema = z.object({
|
|
|
835
898
|
minTurns: z.number(),
|
|
836
899
|
/* The three below are present TOGETHER, and only once both arms clear `minTurns` — a schema that can't
|
|
837
900
|
* express a half-measured experiment is how a 34%-that-becomes-8%-tomorrow never reaches the screen.
|
|
838
|
-
* deltaPct
|
|
839
|
-
* marginPct
|
|
840
|
-
*
|
|
901
|
+
* deltaPct — change in the metric's mean per turn under the mechanism; negative is a saving.
|
|
902
|
+
* marginPct — ± percentage points, 95% (Welch, unequal variances and unequal arms).
|
|
903
|
+
* saved — what the delta is worth over the turns that actually ran with it, in this window, in the
|
|
904
|
+
* metric's own unit (tokens, or dollars). */
|
|
841
905
|
deltaPct: z.number().optional(),
|
|
842
906
|
marginPct: z.number().optional(),
|
|
843
|
-
|
|
907
|
+
saved: z.number().optional(),
|
|
844
908
|
});
|
|
845
|
-
export type
|
|
909
|
+
export type TurnExperiment = z.infer<typeof TurnExperimentSchema>;
|
|
846
910
|
|
|
847
|
-
// `output`
|
|
848
|
-
// isn't there reads as "not measured", which is the truth, while zeros would read as "measured,
|
|
849
|
-
|
|
911
|
+
// `output`/`context` are absent when that experiment isn't running at all (its flag off, or no holdout set) — a
|
|
912
|
+
// section that isn't there reads as "not measured", which is the truth, while zeros would read as "measured,
|
|
913
|
+
// worth nothing".
|
|
914
|
+
export const SavingsReportSchema = z.object({
|
|
915
|
+
input: InputSavingsSchema,
|
|
916
|
+
output: TurnExperimentSchema.optional(),
|
|
917
|
+
context: TurnExperimentSchema.optional(),
|
|
918
|
+
});
|
|
850
919
|
export type SavingsReport = z.infer<typeof SavingsReportSchema>;
|
|
851
920
|
|
|
852
921
|
// ---- intentic CLI ----
|
|
@@ -999,9 +1068,9 @@ export const RepoChangesSchema = z.object({
|
|
|
999
1068
|
remote: GitRemoteStateSchema.optional(),
|
|
1000
1069
|
// WHICH AGENT PUT IT THERE: repo-relative path → the agent ids that landed it, newest land first. Keyed by
|
|
1001
1070
|
// PATH rather than carried on each GitChange because a path can be listed on two sides at once (staged and
|
|
1002
|
-
// edited again) and its origin is the same fact for both. Only agents
|
|
1003
|
-
//
|
|
1004
|
-
// agents/origins.ts),
|
|
1071
|
+
// edited again) and its origin is the same fact for both. Only branch-backed agents whose work passed
|
|
1072
|
+
// through land can appear here; workspace conversations, terminal edits and the user's typing are absent
|
|
1073
|
+
// (see agents/origins.ts), so the panel badges an attributable agent and says nothing for anyone else.
|
|
1005
1074
|
// Ids, not titles: the identity for every id named here rides the response once, in `originAgents`.
|
|
1006
1075
|
origins: z.record(z.string(), z.array(z.string())).optional(),
|
|
1007
1076
|
// Why the repo could not be scanned at all, condensed to git's own one-line reason ("fatal: bad object HEAD").
|
|
@@ -2052,9 +2121,9 @@ export const AutomationRunSchema = z.object({
|
|
|
2052
2121
|
// as "it never fired" — the one reading a 3 a.m. automation must not be given.
|
|
2053
2122
|
outcome: z.enum(["completed", "skipped", "error", "interrupted"]),
|
|
2054
2123
|
detail: z.string().optional(),
|
|
2055
|
-
// The
|
|
2056
|
-
//
|
|
2057
|
-
|
|
2124
|
+
// The stable conversation opened by the wake, so the row can open the provider-neutral agent transcript.
|
|
2125
|
+
// Absent only for a run skipped before a conversation was needed.
|
|
2126
|
+
conversationId: z.string().optional(),
|
|
2058
2127
|
});
|
|
2059
2128
|
export type AutomationRun = z.infer<typeof AutomationRunSchema>;
|
|
2060
2129
|
|
|
@@ -2227,13 +2296,12 @@ export const GateAgentSchema = z.object({
|
|
|
2227
2296
|
});
|
|
2228
2297
|
export type GateAgent = z.infer<typeof GateAgentSchema>;
|
|
2229
2298
|
|
|
2230
|
-
// The fix turn one red verdict got.
|
|
2231
|
-
//
|
|
2232
|
-
//
|
|
2233
|
-
// the same thing an automation's run record carries for the same reason.
|
|
2299
|
+
// The fix turn one red verdict got. It is a workspace conversation because the composite it must reproduce
|
|
2300
|
+
// lives as uncommitted content in the main working tree; it still has the same stable registry identity as any
|
|
2301
|
+
// other conversation.
|
|
2234
2302
|
export const GateFixSchema = z.object({
|
|
2235
2303
|
startedAt: z.number(),
|
|
2236
|
-
|
|
2304
|
+
conversationId: z.string(),
|
|
2237
2305
|
// `running` while the turn streams; `done` when it ended cleanly, whatever the re-check then said;
|
|
2238
2306
|
// `error` when the turn itself failed (a provider outage, no credential), which is worth distinguishing
|
|
2239
2307
|
// because it is the one case where re-running the fix could still help.
|
|
@@ -2406,11 +2474,9 @@ export type PortForwardResult = z.infer<typeof PortForwardResultSchema>;
|
|
|
2406
2474
|
// process (a lingering shell after a crash reads false). A process row that maps to an installed extension's
|
|
2407
2475
|
// declared process carries extensionId+processName, the address for its /extensions start/stop routes. The
|
|
2408
2476
|
// `{name}` kill-route param is a bare string validated in the handler (a bad name is a BAD_REQUEST) since the
|
|
2409
|
-
// same charset gates a `tmux kill-session -t` shell-out.
|
|
2410
|
-
//
|
|
2411
|
-
// /system/
|
|
2412
|
-
// the same rule as `agent`: it is a record of work, not a place. Its `label` is the page's own title and
|
|
2413
|
-
// `url` the page it is on, which is the one thing a browser pill has to say that a terminal pill does not.
|
|
2477
|
+
// same charset gates a `tmux kill-session -t` shell-out. The agent's BROWSER is deliberately NOT one of these
|
|
2478
|
+
// kinds: a Chromium with its own tab strip is a surface in its own right, not a pane in the terminal panel, so
|
|
2479
|
+
// it lists from /system/browsers with the pages it has open (BrowserSessionSchema below).
|
|
2414
2480
|
//
|
|
2415
2481
|
// `activityAt` (epoch ms of the session's last output) and `exitCode` (the LAST window's exit status, absent
|
|
2416
2482
|
// while that pane still lives) are what let a finished session be READ rather than merely listed: the panel's
|
|
@@ -2420,19 +2486,128 @@ export type PortForwardResult = z.infer<typeof PortForwardResultSchema>;
|
|
|
2420
2486
|
export const TerminalSessionSchema = z.object({
|
|
2421
2487
|
name: z.string(),
|
|
2422
2488
|
label: z.string().optional(),
|
|
2423
|
-
kind: z.enum(["shell", "panel", "agent", "job", "process"
|
|
2489
|
+
kind: z.enum(["shell", "panel", "agent", "job", "process"]),
|
|
2424
2490
|
running: z.boolean(),
|
|
2425
2491
|
activityAt: z.number(),
|
|
2426
2492
|
exitCode: z.number().optional(),
|
|
2427
2493
|
extensionId: z.string().optional(),
|
|
2428
2494
|
processName: z.string().optional(),
|
|
2429
|
-
// Browser sessions only: the page the agent is on right now.
|
|
2430
|
-
url: z.string().optional(),
|
|
2431
2495
|
});
|
|
2432
2496
|
export const TerminalsListSchema = z.object({ sessions: z.array(TerminalSessionSchema) });
|
|
2433
2497
|
export type TerminalsList = z.infer<typeof TerminalsListSchema>;
|
|
2434
2498
|
export const TerminalNameParamSchema = z.object({ name: z.string() });
|
|
2435
2499
|
|
|
2500
|
+
/* ---- browsers: the Chromium the agent drives through its @playwright/mcp tools ----
|
|
2501
|
+
*
|
|
2502
|
+
* A `browser-<sdk session>` Chromium (browser/browser-sessions.ts), watchable live over the
|
|
2503
|
+
* /system/browser-view WebSocket. It lists apart from the terminals because it is shaped differently in the one
|
|
2504
|
+
* way that decides a UI: a terminal is ONE stream of bytes, while a browser holds SEVERAL pages at once and the
|
|
2505
|
+
* question "what is the agent looking at?" only has an answer if the wire carries all of them. So `pages` is the
|
|
2506
|
+
* point of this schema — the view renders them as a tab strip and binds the screencast to whichever the user
|
|
2507
|
+
* picks, and `active` is the one the agent itself last touched (what the view follows until the user says
|
|
2508
|
+
* otherwise).
|
|
2509
|
+
*
|
|
2510
|
+
* `id` is opaque and minted per session, and it is what makes a tab survive a relist: it is stable for the life
|
|
2511
|
+
* of the page, unlike its url (the agent navigates away) or its position (a closed tab renumbers the rest). */
|
|
2512
|
+
export const BrowserPageSchema = z.object({
|
|
2513
|
+
id: z.string(),
|
|
2514
|
+
// The page's own title. Absent mid-navigation, which is exactly when a tab still needs to render.
|
|
2515
|
+
title: z.string().optional(),
|
|
2516
|
+
url: z.string(),
|
|
2517
|
+
// The page the agent last drove — on a finished session, the one it ended on. Exactly one page has it.
|
|
2518
|
+
active: z.boolean(),
|
|
2519
|
+
});
|
|
2520
|
+
export const BrowserSessionSchema = z.object({
|
|
2521
|
+
name: z.string(),
|
|
2522
|
+
// The pill's text: the active page's title, else its host, else which browser this is.
|
|
2523
|
+
label: z.string(),
|
|
2524
|
+
// Which MCP server drives it: `web` (the credential-free browser) or a logged-in capability's id — the
|
|
2525
|
+
// difference between a throwaway page and one signed in as the user, which is worth saying out loud.
|
|
2526
|
+
server: z.string(),
|
|
2527
|
+
// False once that Chromium is gone (the turn ended, the agent closed it, it crashed). A finished session
|
|
2528
|
+
// still lists for a while, with the pages it had — the record of where the agent went.
|
|
2529
|
+
running: z.boolean(),
|
|
2530
|
+
activityAt: z.number(),
|
|
2531
|
+
// When that Chromium went away, for the "closed 20m ago" line a finished session leads with. Absent while
|
|
2532
|
+
// running, which is the same fact as `running` — but the view needs the timestamp, not just the flag.
|
|
2533
|
+
finishedAt: z.number().optional(),
|
|
2534
|
+
pages: z.array(BrowserPageSchema),
|
|
2535
|
+
});
|
|
2536
|
+
export type BrowserPage = z.infer<typeof BrowserPageSchema>;
|
|
2537
|
+
export type BrowserSession = z.infer<typeof BrowserSessionSchema>;
|
|
2538
|
+
export const BrowsersListSchema = z.object({ sessions: z.array(BrowserSessionSchema) });
|
|
2539
|
+
export type BrowsersList = z.infer<typeof BrowsersListSchema>;
|
|
2540
|
+
export const BrowserNameParamSchema = z.object({ name: z.string() });
|
|
2541
|
+
|
|
2542
|
+
/* ---- subagents: the agents an agent starts ----
|
|
2543
|
+
*
|
|
2544
|
+
* The third thing a turn spawns that the operator can be shown, after its shell and its browser — and the only
|
|
2545
|
+
* one that is itself an agent. Two kinds land in this one list, because from outside they are the same fact
|
|
2546
|
+
* (another agent, working, that you did not start):
|
|
2547
|
+
* • `subagent` — the SDK's Agent/Task tool. The daemon learns of it from the SubagentStart/SubagentStop hooks
|
|
2548
|
+
* and the task_* stream messages, joined on `toolUseId`.
|
|
2549
|
+
* • `codex` / `grok` — a CLI the agent drove from its own Bash (agent/delegation.ts). Detected in the Bash
|
|
2550
|
+
* PreToolUse hook, bound to its thread/session id from the command's output.
|
|
2551
|
+
*
|
|
2552
|
+
* `id` IS THE SPAWNING TOOL CALL'S id — the Agent card's, or the Bash card's for a delegation. It is the one key
|
|
2553
|
+
* every source already carries (the SDK's subagent meta, its task_* messages, and the `parentToolUseId` the
|
|
2554
|
+
* client nests inner frames under), so nothing has to be correlated: a card links to its subagent with the id it
|
|
2555
|
+
* already has, and the subagent points back at the card the same way. The ids the transcripts are actually READ
|
|
2556
|
+
* with — the SDK's agent id, a Codex thread, an OpenCode session — stay daemon-side, because no surface asks a
|
|
2557
|
+
* question they answer.
|
|
2558
|
+
*
|
|
2559
|
+
* WHAT A KIND CHANGES, and it is only ever the live view: a subagent has no process of its own to look at, so
|
|
2560
|
+
* watching it means reading its transcript. A delegation runs in a tmux window, so it has both — `terminal`
|
|
2561
|
+
* names it, and the card keeps its existing "Watch in terminal" beside the transcript door. */
|
|
2562
|
+
export const SubagentKindSchema = z.enum(["subagent", "codex", "grok"]);
|
|
2563
|
+
export type SubagentKind = z.infer<typeof SubagentKindSchema>;
|
|
2564
|
+
|
|
2565
|
+
// running/pending are live; the rest are terminal. Deliberately the SDK's own task vocabulary
|
|
2566
|
+
// (SDKTaskUpdatedMessage.patch.status) rather than AgentStatus: this is not a fleet card's lifecycle (no
|
|
2567
|
+
// draft/landed/conflict), and mapping the two would invent states neither side reports.
|
|
2568
|
+
export const SubagentStatusSchema = z.enum(["pending", "running", "completed", "failed", "killed", "paused"]);
|
|
2569
|
+
export type SubagentStatus = z.infer<typeof SubagentStatusSchema>;
|
|
2570
|
+
|
|
2571
|
+
export const SubagentSessionSchema = z.object({
|
|
2572
|
+
id: z.string(),
|
|
2573
|
+
kind: SubagentKindSchema,
|
|
2574
|
+
// The conversation whose turn spawned this — what the area groups its rows by, and the way back to the chat
|
|
2575
|
+
// the card lives in.
|
|
2576
|
+
conversationId: z.string(),
|
|
2577
|
+
// What it is and what it was asked to do: the subagent type (`Explore`, `general-purpose`) or the delegated
|
|
2578
|
+
// provider's model, and the caller's one-line description. The area's row and the card's title read as
|
|
2579
|
+
// `Explore · Locate claimIndexer definition`.
|
|
2580
|
+
agentType: z.string().optional(),
|
|
2581
|
+
description: z.string().optional(),
|
|
2582
|
+
model: z.string().optional(),
|
|
2583
|
+
// How deep in the spawn tree (1 = spawned by the turn itself). From the SDK's meta.json; a subagent may
|
|
2584
|
+
// itself delegate, and a flat list that cannot say so reads as though the turn started all of them.
|
|
2585
|
+
spawnDepth: z.number().optional(),
|
|
2586
|
+
// Backgrounded: the parent went on working instead of waiting for it. This is the whole reason the list
|
|
2587
|
+
// exists — a backgrounded child used to be invisible until its result landed, sometimes minutes later.
|
|
2588
|
+
background: z.boolean().optional(),
|
|
2589
|
+
status: SubagentStatusSchema,
|
|
2590
|
+
startedAt: z.number(),
|
|
2591
|
+
endedAt: z.number().optional(),
|
|
2592
|
+
activityAt: z.number(),
|
|
2593
|
+
// What it has spent and done so far (task_progress). Tokens are the child's own, so a parent's cost line and
|
|
2594
|
+
// the sum of its children's are two different true numbers.
|
|
2595
|
+
tokens: z.number().optional(),
|
|
2596
|
+
toolUses: z.number().optional(),
|
|
2597
|
+
lastTool: z.string().optional(),
|
|
2598
|
+
// Its report — the last assistant message (SubagentStop) or the task summary. The answer to "what did it
|
|
2599
|
+
// conclude?" without opening the transcript, which is the question a finished child is read for.
|
|
2600
|
+
summary: z.string().optional(),
|
|
2601
|
+
error: z.string().optional(),
|
|
2602
|
+
// A delegation's live view: the tmux session its command runs in. Absent for an SDK subagent, which has no
|
|
2603
|
+
// process of its own to attach to.
|
|
2604
|
+
terminal: z.string().optional(),
|
|
2605
|
+
});
|
|
2606
|
+
export type SubagentSession = z.infer<typeof SubagentSessionSchema>;
|
|
2607
|
+
export const SubagentsListSchema = z.object({ sessions: z.array(SubagentSessionSchema) });
|
|
2608
|
+
export type SubagentsList = z.infer<typeof SubagentsListSchema>;
|
|
2609
|
+
export const SubagentIdParamSchema = z.object({ id: z.string() });
|
|
2610
|
+
|
|
2436
2611
|
// ---- environment: the overlay Dockerfile extending the sandbox image ----
|
|
2437
2612
|
// The approved file is DAEMON-COMPOSED: pinned FROM + capability fragments + the owner-approved custom section.
|
|
2438
2613
|
// The agent writes the proposal file (.intentic/environment.Dockerfile — custom-section content only, no FROM)
|
|
@@ -2598,7 +2773,8 @@ export const UsageTurnSchema = z.object({
|
|
|
2598
2773
|
// when the provider's own subscription default served it without the daemon naming one.
|
|
2599
2774
|
model: z.string().optional(),
|
|
2600
2775
|
harness: z.string(),
|
|
2601
|
-
// The conversation this turn belonged to, so spend can join to a fleet agent. Absent
|
|
2776
|
+
// The conversation this turn belonged to, so spend can join to a fleet agent. Absent only for an internal
|
|
2777
|
+
// one-shot turn that has no conversation identity.
|
|
2602
2778
|
conversationId: z.string().optional(),
|
|
2603
2779
|
// The provider's own turn count for the request (a Claude "turn" can be several under the hood), so turns
|
|
2604
2780
|
// and cost stay comparable across providers. 1 when the provider reported none.
|
|
@@ -2617,6 +2793,15 @@ export const UsageTurnSchema = z.object({
|
|
|
2617
2793
|
* no control to be compared against. Pooling those into the off-arm would compare steered turns against a
|
|
2618
2794
|
* population selected by something other than the coin flip, which is not a control at all. */
|
|
2619
2795
|
terse: z.boolean().optional(),
|
|
2796
|
+
/* Which arm of the pre-injection experiment this turn ran on (settings.iqContextHoldout), on the same terms
|
|
2797
|
+
* as `terse` above: absent ⇒ outside the experiment.
|
|
2798
|
+
*
|
|
2799
|
+
* TRUE means the turn was ASSIGNED the retrieved context, not that a note was necessarily prepended — a
|
|
2800
|
+
* treatment turn whose retrieval came back empty or unconfident injects nothing. That is deliberate: the
|
|
2801
|
+
* arms have to be the coin flip's populations, and re-labelling a turn by what retrieval happened to find
|
|
2802
|
+
* would sort turns by how searchable their question was, which is a property of the question. The control
|
|
2803
|
+
* arm contains the same unsearchable questions in the same proportion, so they cancel. */
|
|
2804
|
+
iqContext: z.boolean().optional(),
|
|
2620
2805
|
});
|
|
2621
2806
|
export type UsageTurn = z.infer<typeof UsageTurnSchema>;
|
|
2622
2807
|
|