@intentic/sandbox-contract 1.159.0 → 1.161.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 +1 -0
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +1 -0
- package/dist/agent-catalog.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +50 -4
- 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 +195 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +7 -2
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/claude.contract.d.ts +26 -0
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.js +2 -1
- package/dist/contracts/claude.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +1 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.d.ts +13 -0
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +2 -1
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts +2 -0
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/kimi.contract.d.ts +26 -0
- package/dist/contracts/kimi.contract.d.ts.map +1 -1
- package/dist/contracts/kimi.contract.js +2 -1
- package/dist/contracts/kimi.contract.js.map +1 -1
- package/dist/contracts/memory.contract.d.ts +34 -0
- package/dist/contracts/memory.contract.d.ts.map +1 -0
- package/dist/contracts/memory.contract.js +9 -0
- package/dist/contracts/memory.contract.js.map +1 -0
- package/dist/contracts/panels.contract.d.ts +1 -0
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/push.contract.d.ts +1 -1
- package/dist/contracts/push.contract.js +2 -2
- package/dist/contracts/push.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +2 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +72 -15
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +3 -2
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +15 -2
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +2 -1
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts +13 -3
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.js +1 -1
- package/dist/contracts/translator.contract.js.map +1 -1
- package/dist/contracts/usage.contract.js +2 -2
- package/dist/contracts/usage.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +38 -5
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +2 -1
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events.d.ts +124 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +21 -3
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +491 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/model-order.d.ts +1 -0
- package/dist/model-order.d.ts.map +1 -1
- package/dist/model-order.js +1 -0
- package/dist/model-order.js.map +1 -1
- package/dist/path-refs.d.ts +1 -0
- package/dist/path-refs.d.ts.map +1 -1
- package/dist/path-refs.js +3 -0
- package/dist/path-refs.js.map +1 -1
- package/dist/quick-model.d.ts +13 -0
- package/dist/quick-model.d.ts.map +1 -0
- package/dist/quick-model.js +33 -0
- package/dist/quick-model.js.map +1 -0
- package/dist/schemas.d.ts +374 -14
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +145 -15
- package/dist/schemas.js.map +1 -1
- package/dist/title.d.ts +6 -0
- package/dist/title.d.ts.map +1 -0
- package/dist/title.js +149 -0
- package/dist/title.js.map +1 -0
- package/package.json +2 -2
- package/src/agent-catalog.ts +6 -0
- package/src/contracts/agent.contract.ts +14 -1
- package/src/contracts/agents.contract.ts +25 -1
- package/src/contracts/claude.contract.ts +4 -1
- package/src/contracts/git.contract.ts +6 -0
- package/src/contracts/kimi.contract.ts +13 -2
- package/src/contracts/memory.contract.ts +13 -0
- package/src/contracts/push.contract.ts +2 -2
- package/src/contracts/settings.contract.ts +16 -4
- package/src/contracts/system.contract.ts +4 -0
- package/src/contracts/translator.contract.ts +3 -2
- package/src/contracts/usage.contract.ts +2 -2
- package/src/contracts/workspace.contract.ts +6 -0
- package/src/events.ts +85 -6
- package/src/index.ts +7 -1
- package/src/model-order.test.ts +40 -1
- package/src/model-order.ts +15 -0
- package/src/path-refs.test.ts +27 -1
- package/src/path-refs.ts +16 -0
- package/src/quick-model.test.ts +88 -0
- package/src/quick-model.ts +103 -0
- package/src/schemas.test.ts +30 -1
- package/src/schemas.ts +500 -42
- package/src/title.test.ts +130 -0
- package/src/title.ts +254 -0
package/src/schemas.ts
CHANGED
|
@@ -162,9 +162,18 @@ export type AttachTurn = z.infer<typeof AttachTurnSchema>;
|
|
|
162
162
|
// conversationId. Isolated ones own a git worktree (branch agent/<id> in every workspace repo); the fleet
|
|
163
163
|
// surface shows all of them with live status/activity/cost so the user can drive N agents in parallel.
|
|
164
164
|
|
|
165
|
-
// idle/running/awaiting are the turn lifecycle (awaiting = paused on a plan approval or question);
|
|
166
|
-
// conflict are outcomes of the land flow
|
|
167
|
-
|
|
165
|
+
// idle/running/awaiting are the turn lifecycle (awaiting = paused on a plan approval or question); ready /
|
|
166
|
+
// landed / conflict are outcomes of the land flow — `ready` is a clean completion whose delta stayed on the
|
|
167
|
+
// agent's branch because auto-land is off (the user lands it deliberately, from the review panel or the card);
|
|
168
|
+
// error is a terminal turn failure surfaced on the card.
|
|
169
|
+
//
|
|
170
|
+
// `interrupted` is the turn that never got to report ANY of those: the daemon died under it (a container
|
|
171
|
+
// rebuild, a crash, an OOM kill), taking the provider process and the whole runtime half of the fleet — status,
|
|
172
|
+
// attention flags, the park a question raised — with it. It exists because the alternative is worse than
|
|
173
|
+
// unlabelled: without it such a turn rehydrates as `idle`, which is the resting status of a turn that finished
|
|
174
|
+
// CLEANLY, so the board files a killed agent under Finished and the question it was holding disappears with the
|
|
175
|
+
// process that asked it. See agents-store.ts — this is the status a live turn leaves on disk.
|
|
176
|
+
export const AgentStatusSchema = z.enum(["idle", "running", "awaiting", "ready", "landed", "conflict", "error", "interrupted"]);
|
|
168
177
|
export type AgentStatus = z.infer<typeof AgentStatusSchema>;
|
|
169
178
|
// The card's live activity snippet: the last tool the agent used (with its target) and the in-progress todo.
|
|
170
179
|
export const AgentActivitySchema = z.object({
|
|
@@ -190,10 +199,21 @@ export const AgentSummarySchema = z.object({
|
|
|
190
199
|
status: AgentStatusSchema,
|
|
191
200
|
provider: AgentProviderSchema,
|
|
192
201
|
harness: AgentHarnessSchema,
|
|
202
|
+
// What the agent's last turn ran with — the model, its reasoning effort, and whether extended thinking was
|
|
203
|
+
// on. Recorded per agent because they are facts about THIS conversation: a client opening it seeds its
|
|
204
|
+
// composer from them, rather than from whatever that browser last picked in some other tab. Absent for an
|
|
205
|
+
// agent whose turns predate the record (model has always been kept; the other two are newer).
|
|
193
206
|
model: z.string().optional(),
|
|
207
|
+
effort: z.string().optional(),
|
|
208
|
+
thinking: z.boolean().optional(),
|
|
194
209
|
account: z.string().optional(),
|
|
195
210
|
// The worktree branch (agent/<id>); absent for a non-isolated (main-tree) conversation.
|
|
196
211
|
branch: z.string().optional(),
|
|
212
|
+
// This agent's own answer to "land automatically at turn completion?" — an explicit per-agent override of
|
|
213
|
+
// the sandbox-wide `autoLand` setting. ABSENT ⇒ inherit, which is the common case and the one that keeps
|
|
214
|
+
// the global toggle meaningful: an agent that never expressed an opinion follows the sandbox wherever it
|
|
215
|
+
// is pointed next. Written by `agents.autoLand`; the UI shows the EFFECTIVE value (this ?? the setting).
|
|
216
|
+
autoLand: z.boolean().optional(),
|
|
197
217
|
// Present when the conversation was opened by an outside message rather than by the user (see
|
|
198
218
|
// AgentOriginSchema) — the card's provenance line. Absent ⇒ the user started it.
|
|
199
219
|
origin: AgentOriginSchema.optional(),
|
|
@@ -248,17 +268,88 @@ export const AgentIdsSchema = z.object({ ids: z.array(z.string().min(1)).min(1).
|
|
|
248
268
|
// browser holds its optimistic add/remove of exactly these ids until it sees a roster at or past `rev`.
|
|
249
269
|
export const AgentsMovedSchema = z.object({ moved: z.array(AgentSummarySchema), rev: z.number() });
|
|
250
270
|
export type AgentsMoved = z.infer<typeof AgentsMovedSchema>;
|
|
271
|
+
// What a purge actually deleted. Ids, not summaries: these agents no longer exist anywhere — there is nothing
|
|
272
|
+
// left to show and nothing to put back, so the only thing the caller can do with the answer is drop those rows
|
|
273
|
+
// and count them. No revision either: archived agents are already off the broadcast roster (see `list`), so a
|
|
274
|
+
// purge changes nothing the board's pending-move machinery has to hold a card against.
|
|
275
|
+
export const AgentsRemovedSchema = z.object({ removed: z.array(z.string()) });
|
|
276
|
+
export type AgentsRemoved = z.infer<typeof AgentsRemovedSchema>;
|
|
277
|
+
/* Search the fleet by what the USER wrote — the board's filter (and the popped-out rail's).
|
|
278
|
+
*
|
|
279
|
+
* Deliberately the user's own prompts and nothing else. An agent's replies and its tool output mention
|
|
280
|
+
* nearly every identifier in the workspace, so a transcript-wide match on a fleet this size returns most of
|
|
281
|
+
* the board and the filter stops filtering; the words the user typed are both what they remember and what
|
|
282
|
+
* tells two agents apart. The card TITLE is the first of those prompts (sanitized), so a title match and a
|
|
283
|
+
* prompt match are one rule, not two.
|
|
284
|
+
*
|
|
285
|
+
* Two chars minimum: below that every agent matches and the scan is pure cost.
|
|
286
|
+
*/
|
|
287
|
+
export const AgentSearchQuerySchema = z.object({ query: z.string().trim().min(2) });
|
|
288
|
+
// One matching agent, and the evidence for it. `snippet` is the matched prompt windowed around the hit —
|
|
289
|
+
// absent when the match is the title, which the card already shows. A card that matches for a reason the user
|
|
290
|
+
// cannot see is worse than no filter at all.
|
|
291
|
+
export const AgentMatchSchema = z.object({ id: z.string(), snippet: z.string().optional() });
|
|
292
|
+
export type AgentMatch = z.infer<typeof AgentMatchSchema>;
|
|
293
|
+
// `scanned` is how many agents the daemon actually read prompts for, so the board can say when a query saw
|
|
294
|
+
// less than the whole fleet rather than implying it saw all of it.
|
|
295
|
+
export const AgentSearchResultSchema = z.object({ matches: z.array(AgentMatchSchema), scanned: z.number() });
|
|
296
|
+
export type AgentSearchResult = z.infer<typeof AgentSearchResultSchema>;
|
|
251
297
|
// rename's input: the user-chosen display title (bounded like sanitizeTitle's cap).
|
|
252
298
|
export const AgentRenameSchema = z.object({ id: z.string().min(1), title: z.string().trim().min(1).max(80) });
|
|
299
|
+
// autoLand's input: this agent's own land-at-completion posture. `null` CLEARS the override back to "inherit
|
|
300
|
+
// the sandbox setting" — the browser sends it whenever the user toggles back to what the global already says,
|
|
301
|
+
// so agents don't accumulate frozen overrides that quietly stop following the global toggle.
|
|
302
|
+
export const AgentAutoLandSchema = z.object({ id: z.string().min(1), autoLand: z.boolean().nullable() });
|
|
253
303
|
export const AgentFileDiffQuerySchema = z.object({ id: z.string().min(1), repo: z.string().min(1), path: z.string().min(1) });
|
|
254
|
-
|
|
255
|
-
|
|
304
|
+
/* WHY a path would not land. The distinction is the whole difference between an actionable report and a dead
|
|
305
|
+
* end, because the three have nothing in common but their symptom:
|
|
306
|
+
* `workspace` — you have uncommitted edits on that path. Yours is the copy at risk; commit or stash it.
|
|
307
|
+
* `diverged` — the main tree's COMMITTED content moved under the agent since it branched. Nothing of
|
|
308
|
+
* yours is at risk; the agent's delta is simply written against an older file.
|
|
309
|
+
* `binary` — git cannot three-way merge the file at all, so no automatic resolution exists.
|
|
310
|
+
* The old report named only the first, which is the rarest of the three. */
|
|
311
|
+
export const LandConflictReasonSchema = z.enum(["workspace", "diverged", "binary"]);
|
|
312
|
+
export type LandConflictReason = z.infer<typeof LandConflictReasonSchema>;
|
|
313
|
+
export const LandConflictPathSchema = z.object({ path: z.string(), reason: LandConflictReasonSchema });
|
|
314
|
+
|
|
315
|
+
/* land's outcome, per repo of the composition. `paths` is the set that genuinely failed to apply — NOT the
|
|
316
|
+
* whole delta, which is what the first version reported whenever it could not pin the cause down, turning
|
|
317
|
+
* four real conflicts into a wall of fourteen. `clean` counts what would land regardless, so the UI can say
|
|
318
|
+
* how much is being held back by how little, and offer to take it. An empty `paths` with `clean: 0` is the
|
|
319
|
+
* repo-unavailable case: the main checkout is gone, and no path-level account exists. */
|
|
320
|
+
export const LandConflictSchema = z.object({
|
|
321
|
+
repo: z.string(),
|
|
322
|
+
paths: z.array(LandConflictPathSchema),
|
|
323
|
+
clean: z.number(),
|
|
324
|
+
});
|
|
325
|
+
export type LandConflict = z.infer<typeof LandConflictSchema>;
|
|
326
|
+
|
|
327
|
+
// land's outcome; landed only when every repo with changes applied cleanly. Conflicted repos keep their
|
|
328
|
+
// worktree state — nothing is lost, and "Land now" stays available. `resolving` is populated only by a
|
|
329
|
+
// `merge` land: the paths written into the workspace carrying conflict markers, which the user finishes by
|
|
330
|
+
// hand in their own editor exactly as they would any merge.
|
|
256
331
|
export const LandResultSchema = z.object({
|
|
257
332
|
landed: z.boolean(),
|
|
258
|
-
conflicts: z.array(
|
|
333
|
+
conflicts: z.array(LandConflictSchema).optional(),
|
|
334
|
+
resolving: z.array(z.object({ repo: z.string(), paths: z.array(z.string()) })).optional(),
|
|
335
|
+
// A `measure` outcome with an outstanding delta: nothing was applied and nothing failed — the work is
|
|
336
|
+
// waiting on the branch for a deliberate Land. `landed: false` alone can't say that (it means refusal).
|
|
337
|
+
held: z.boolean().optional(),
|
|
259
338
|
});
|
|
260
339
|
export type LandResult = z.infer<typeof LandResultSchema>;
|
|
261
340
|
|
|
341
|
+
/* land's input. `check` is the safe default and the historical behaviour: the delta is applied only if ALL of
|
|
342
|
+
* it applies, so a refusal leaves the workspace byte-identical. `merge` is the escape hatch the conflict
|
|
343
|
+
* report offers — a three-way apply that lands every clean path and leaves the rest with conflict markers to
|
|
344
|
+
* resolve in place. It is opt-in because it WRITES on failure, which is the one thing `check` promises not
|
|
345
|
+
* to do. `measure` is the auto-land-off mode: everything a land does EXCEPT touching the main tree — the
|
|
346
|
+
* provenance commit onto agent/<id>, the cumulative diffstat, and the bookkeeping for work that reached the
|
|
347
|
+
* main line by another road — so a held agent's card stays as current as a landed one's while its delta waits
|
|
348
|
+
* on the branch for a deliberate Land. */
|
|
349
|
+
export const LandModeSchema = z.enum(["check", "merge", "measure"]);
|
|
350
|
+
export type LandMode = z.infer<typeof LandModeSchema>;
|
|
351
|
+
export const AgentLandSchema = z.object({ id: z.string().min(1), mode: LandModeSchema.optional() });
|
|
352
|
+
|
|
262
353
|
// ---- routed-provider subscriptions ----
|
|
263
354
|
|
|
264
355
|
// The providers whose model can run UNDER the Claude Code harness through the bundled translator (CLIProxyAPI),
|
|
@@ -269,9 +360,19 @@ export type LandResult = z.infer<typeof LandResultSchema>;
|
|
|
269
360
|
export const KeyedProviderSchema = z.enum(["codex", "grok", "gemini"]);
|
|
270
361
|
export type KeyedProvider = z.infer<typeof KeyedProviderSchema>;
|
|
271
362
|
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
export const
|
|
363
|
+
// One connected subscription in the translator. `name` is CLIProxyAPI's auth-file name — the stable store key a
|
|
364
|
+
// disconnect addresses — and `label` the sign-in identity it reported (the account email, else the file name).
|
|
365
|
+
export const TranslatorAccountSchema = z.object({ name: z.string(), label: z.string() });
|
|
366
|
+
export type TranslatorAccount = z.infer<typeof TranslatorAccountSchema>;
|
|
367
|
+
// Which routed-provider subscriptions are connected in the translator, per provider — a LIST per provider, not
|
|
368
|
+
// a flag: CLIProxyAPI holds any number of auth files per provider side by side and balances requests across
|
|
369
|
+
// them, so connecting a second ChatGPT or Google account is more headroom, and each is disconnectable on its
|
|
370
|
+
// own. Drives the account rows in Sandbox ▸ Agent.
|
|
371
|
+
export const TranslatorAccountsSchema = z.object({
|
|
372
|
+
codex: z.array(TranslatorAccountSchema),
|
|
373
|
+
grok: z.array(TranslatorAccountSchema),
|
|
374
|
+
gemini: z.array(TranslatorAccountSchema),
|
|
375
|
+
});
|
|
275
376
|
export type TranslatorAccounts = z.infer<typeof TranslatorAccountsSchema>;
|
|
276
377
|
|
|
277
378
|
// The side-channel body that un-parks a turn waiting on the user. Every interactive card — plan approval,
|
|
@@ -328,6 +429,12 @@ export const SteerSchema = z
|
|
|
328
429
|
// True cancel for the conversation's in-flight turn — aborts the agent daemon-side, unlike closing the
|
|
329
430
|
// /agent fetch (which sends no cancel frame).
|
|
330
431
|
export const StopTurnSchema = z.object({ conversationId: z.string().min(1) });
|
|
432
|
+
// Fire the conversation's remembered usage-limit resume NOW instead of waiting out the reset. `account`
|
|
433
|
+
// points the re-run at a different connected account of the same provider — the "resume on another account"
|
|
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() });
|
|
331
438
|
|
|
332
439
|
// ---- claude subscription usage ----
|
|
333
440
|
// The GATE signal: whether the provider is letting turns through right now, and — when it is refusing — which
|
|
@@ -381,6 +488,14 @@ export type AccountUsage = z.infer<typeof AccountUsageSchema>;
|
|
|
381
488
|
export const OauthAccountSchema = z.object({
|
|
382
489
|
id: z.string(),
|
|
383
490
|
label: z.string(),
|
|
491
|
+
// WHO this account signs in as, in the provider's own words — Anthropic returns the email and the
|
|
492
|
+
// organization alongside the tokens, so a connection can name itself instead of arriving as a second row
|
|
493
|
+
// called "Claude". Kept BESIDE `label` rather than folded into it: the label is the user's to rename, and a
|
|
494
|
+
// renamed account still has to be able to say whose it is. Absent when the provider tells us nothing (a
|
|
495
|
+
// pasted API key carries no identity) — which is exactly when renaming is the only answer, so every
|
|
496
|
+
// sandbox-owned account can be renamed.
|
|
497
|
+
email: z.string().optional(),
|
|
498
|
+
organization: z.string().optional(),
|
|
384
499
|
scope: z.string().optional(),
|
|
385
500
|
connectedAt: z.number(), // epoch ms
|
|
386
501
|
// Set only when the account's stored credential can no longer be refreshed (revoked/expired refresh token)
|
|
@@ -397,6 +512,10 @@ export type OauthAccount = z.infer<typeof OauthAccountSchema>;
|
|
|
397
512
|
export const OauthAccountListSchema = z.object({ accounts: z.array(OauthAccountSchema) });
|
|
398
513
|
// Address one account of a provider (disconnect, and the turn's `account`).
|
|
399
514
|
export const AccountIdSchema = z.object({ id: z.string().min(1) });
|
|
515
|
+
// Rename one account of a provider whose credential the sandbox owns (Claude, Kimi). Blank ⇒ the daemon falls
|
|
516
|
+
// back to the derived name, so clearing a label restores the sign-in identity rather than leaving a nameless
|
|
517
|
+
// row. Grok is absent for the same reason it holds one account: OpenCode owns that credential, not this store.
|
|
518
|
+
export const AccountRenameSchema = z.object({ id: z.string().min(1), label: z.string().max(80) });
|
|
400
519
|
// The completing calls carry the user-chosen label (blank ⇒ the daemon derives one from the sign-in identity
|
|
401
520
|
// or a provider default).
|
|
402
521
|
export const OauthExchangeSchema = z.object({
|
|
@@ -464,10 +583,29 @@ export const ModelsSchema = z.object({ models: z.array(ModelSchema), default: z.
|
|
|
464
583
|
// ---- sessions ----
|
|
465
584
|
|
|
466
585
|
export const SessionIdParamSchema = z.object({ id: z.string() });
|
|
467
|
-
export const SessionSummarySchema = z.object({
|
|
586
|
+
export const SessionSummarySchema = z.object({
|
|
587
|
+
id: z.string(),
|
|
588
|
+
title: z.string(),
|
|
589
|
+
updatedAt: z.number(),
|
|
590
|
+
// Why a searched session matched: the line of the USER's own prompt the query hit, windowed around it.
|
|
591
|
+
// Absent on an unfiltered list, and on a match the title already shows — a snippet repeating the row's
|
|
592
|
+
// own heading is noise, not evidence. See AgentMatchSchema for the same field on the fleet's side.
|
|
593
|
+
snippet: z.string().optional(),
|
|
594
|
+
});
|
|
468
595
|
export const SessionsListSchema = z.object({ sessions: z.array(SessionSummarySchema) });
|
|
469
596
|
|
|
470
597
|
// ---- settings: per-sandbox agent settings (.intentic/settings.json) ----
|
|
598
|
+
|
|
599
|
+
// Which prompt the agent is, before this turn composes anything on top. Two built-in bases and an escape
|
|
600
|
+
// hatch: Intentic's own (the default), Claude Code's preset, or the owner's text. Declared out here rather
|
|
601
|
+
// than inline in the settings object because both sides of the wire branch on it — the daemon to build the
|
|
602
|
+
// turn, the browser to decide which base it can show you.
|
|
603
|
+
export const SystemPromptModeSchema = z.enum(["intentic", "claude", "custom"]);
|
|
604
|
+
export type SystemPromptMode = z.infer<typeof SystemPromptModeSchema>;
|
|
605
|
+
// The two bases a user can READ and fork — "custom" is excluded because there is nothing to fetch: it is
|
|
606
|
+
// whatever they have already typed into the settings field.
|
|
607
|
+
export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"]) });
|
|
608
|
+
|
|
471
609
|
// Small user-owned config the /settings routes edit and streamAgent reads — all opt-in booleans the owner
|
|
472
610
|
// toggles in the UI (so each can be A/B benchmarked):
|
|
473
611
|
// stableSystemPrompt — keeps the system prompt byte-stable across turns (the delegation note rides the user
|
|
@@ -480,6 +618,9 @@ export const SessionsListSchema = z.object({ sessions: z.array(SessionSummarySch
|
|
|
480
618
|
// guard + fewer output tokens); off ⇒ the native file tools.
|
|
481
619
|
// terseOutput — appends a concise-response steer to the end of the system prompt (a stable suffix, so it
|
|
482
620
|
// composes with stableSystemPrompt) to cut the model's OWN output tokens.
|
|
621
|
+
// systemPromptMode — which base the agent's prompt is: "intentic" (default), "claude", or "custom".
|
|
622
|
+
// systemPrompt — the owner's own prompt text, used only by "custom" mode, where it is the ENTIRE system
|
|
623
|
+
// prompt and nothing the daemon would otherwise append rides with it — see its own note.
|
|
483
624
|
// iqSearch — loads the image-baked iq Claude Code plugin (skill + SessionStart nudge) so the agent
|
|
484
625
|
// prefers the iq CLI over grep/find/Glob; off ⇒ plugin not loaded, native search tools
|
|
485
626
|
// only. Opt-in (default off); the browser Search box uses iq regardless.
|
|
@@ -490,7 +631,7 @@ export const SessionsListSchema = z.object({ sessions: z.array(SessionSummarySch
|
|
|
490
631
|
// (recorded raw as `heldOut`), so the savings report compares a real cleaned-vs-raw
|
|
491
632
|
// population instead of an estimate. 0 = no holdout (default).
|
|
492
633
|
// filterBackend — which cleaner runs the compression: "native" (agent-output-filter, default) or "rtk"
|
|
493
|
-
// (the rtk binary
|
|
634
|
+
// (the image-baked rtk binary, rewritten at the PreToolUse hook) — an
|
|
494
635
|
// A/B backend switch, so native and rtk can be benchmarked head-to-head.
|
|
495
636
|
// The booleans default off, skills defaults [] (no skill loaded), outputCleaners defaults "off" (cleaning off),
|
|
496
637
|
// outputHoldout 0, filterBackend "native" — a fresh sandbox starts with cleaning and iq off until the owner enables them.
|
|
@@ -507,33 +648,167 @@ export const SandboxSettingsSchema = z.object({
|
|
|
507
648
|
skills: z.array(z.string()).default([]),
|
|
508
649
|
hashlineEdits: z.boolean().default(false),
|
|
509
650
|
terseOutput: z.boolean().default(false),
|
|
651
|
+
/* Measurement control for the terse steer, at TURN level — the same trick `outputHoldout` plays over
|
|
652
|
+
* commands, one layer up. A fraction [0,1] of otherwise-eligible turns run WITHOUT the steer and record
|
|
653
|
+
* which arm they ran on (UsageTurn.terse), so the savings report can compare two real populations.
|
|
654
|
+
*
|
|
655
|
+
* It has to be an experiment: unlike a cleaned command, which yields its own raw baseline in the same
|
|
656
|
+
* event, a turn cannot be re-run to see what it would have said unsteered. 0 ⇒ no measurement (every
|
|
657
|
+
* eligible turn is steered), which is the default because the control costs the very tokens it measures. */
|
|
658
|
+
terseHoldout: z.number().min(0).max(1).default(0),
|
|
659
|
+
/* WHICH SYSTEM PROMPT THE AGENT RUNS ON — the base, before anything this turn composes.
|
|
660
|
+
*
|
|
661
|
+
* intentic — Intentic's own prompt, tuned for this harness (intentic-prompt.ts). The default.
|
|
662
|
+
* claude — Claude Code's preset, as shipped in the CLI this sandbox runs. Not a copy stored here, so
|
|
663
|
+
* picking it tracks whatever the installed CLI's prompt is rather than freezing at a snapshot.
|
|
664
|
+
* custom — `systemPrompt` below, and nothing else at all.
|
|
665
|
+
*
|
|
666
|
+
* The first two are peers: both get the harness's own guidance appended (the AskUserQuestion/plan blocks
|
|
667
|
+
* the chat's cards need, the checklist guidance behind the todo panel, the browser-tool guidance), plus the
|
|
668
|
+
* delegation note and the terse steer. `custom` is the one that does not, by the owner's explicit choice —
|
|
669
|
+
* see the field below. */
|
|
670
|
+
systemPromptMode: SystemPromptModeSchema.default("intentic"),
|
|
671
|
+
/* The owner's own prompt, used only when `systemPromptMode` is "custom". Then it is the ENTIRE system
|
|
672
|
+
* prompt: both built-in bases are gone and so is everything the daemon would otherwise append — the widget
|
|
673
|
+
* guidance the chat's cards are driven by, and the terse-output steer (whose toggle goes inert). That is
|
|
674
|
+
* the price of total control, and the UI states it at the moment of the edit rather than letting the
|
|
675
|
+
* widgets go quietly dark. Only the cross-provider delegation note survives, because it has a home outside
|
|
676
|
+
* the system prompt already (the user-message preamble stableSystemPrompt puts it in).
|
|
677
|
+
*
|
|
678
|
+
* Cap is roomy — the bases it stands in for are ~6.8k characters — but finite, because every turn pays it. */
|
|
679
|
+
systemPrompt: z.string().max(20000).default(""),
|
|
510
680
|
iqSearch: z.boolean().default(false),
|
|
511
681
|
outputCleaners: z.string().default("off"),
|
|
512
682
|
outputHoldout: z.number().min(0).max(1).default(0),
|
|
513
683
|
filterBackend: z.enum(["native", "rtk"]).default("native"),
|
|
684
|
+
/* The model behind the one-click helpers that are not a conversation — today the commit box's autofill.
|
|
685
|
+
* `${provider}:${modelId}`, or EMPTY for Auto, which is the default and the interesting case: Auto is
|
|
686
|
+
* resolved from whatever accounts are connected at the moment it is read (resolveQuickModel), so it can
|
|
687
|
+
* never name a provider this sandbox has no credential for and it improves by itself when one is added.
|
|
688
|
+
* Storing a resolved id here instead would go stale exactly like a pinned model does. */
|
|
689
|
+
quickModel: z.string().default(""),
|
|
514
690
|
// How long a finished agent stays on the board before it is archived automatically (days; 0 ⇒ never).
|
|
515
691
|
// Unlike every other flag here this one defaults ON, because the lane it governs is the board's only
|
|
516
692
|
// terminal state: without a sweep the Finished lane grows for the life of the sandbox, and each card it
|
|
517
693
|
// holds is a live worktree checkout, not just a row.
|
|
518
694
|
agentRetentionDays: z.number().min(0).max(365).default(3),
|
|
695
|
+
/* Land a clean turn's delta into the main tree automatically at completion — the Claude Code review model,
|
|
696
|
+
* and the historical behaviour, so it defaults ON (flipping the default would silently change every
|
|
697
|
+
* existing sandbox). OFF holds finished work on the agent's branch instead: the card reads "Ready to
|
|
698
|
+
* land" and the user lands it deliberately, from the review panel or the card. Sandbox-wide because
|
|
699
|
+
* automation-opened agents (Discord, webhooks, email) finish turns with no browser in the room — a
|
|
700
|
+
* browser-held preference could not govern them. Per-agent override: AgentSummarySchema.autoLand. */
|
|
701
|
+
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
|
+
/* When a turn dies because the MODEL PROVIDER was failing (500/502/503, a 529 at capacity, a dropped
|
|
709
|
+
* socket), re-run it on an escalating backoff until it goes through or the attempts are spent.
|
|
710
|
+
*
|
|
711
|
+
* Defaults ON, unlike autoResumeOnLimit, and the difference is not an inconsistency: a spent allowance is
|
|
712
|
+
* the user's own budget, and resuming into a fresh window spends something they may have been saving. An
|
|
713
|
+
* outage resume spends nothing the dead turn had not already committed, resolves in minutes rather than
|
|
714
|
+
* hours, and — the deciding argument — the turns hurt worst by it are the ones with nobody in the room
|
|
715
|
+
* (automation wakes, Discord, webhooks), which no browser-held preference could ever rescue. It is the same
|
|
716
|
+
* reasoning that leaves the auth resume ungated: this is the provider's failure, not the user's decision. */
|
|
717
|
+
resumeAfterOutage: z.boolean().default(true),
|
|
519
718
|
});
|
|
520
719
|
export type SandboxSettings = z.infer<typeof SandboxSettingsSchema>;
|
|
521
720
|
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
|
|
721
|
+
// One of the two built-in bases, as text: Intentic's own prompt, or Claude Code's preset read out of the CLI
|
|
722
|
+
// this sandbox runs (preset-prompt.ts captures it rather than storing a transcription). What the settings page
|
|
723
|
+
// shows behind "View" and drops into the editor behind "Edit a copy".
|
|
724
|
+
//
|
|
725
|
+
// `version` is the CLI build a captured preset came from, so the UI can say WHICH text the user is looking at:
|
|
726
|
+
// a custom prompt forked from an older build is a snapshot, and the version is the only honest way to tell.
|
|
727
|
+
// Empty for Intentic's prompt, which ships with the app and has no version of its own to report.
|
|
728
|
+
export const BuiltinPromptTextSchema = z.object({ text: z.string(), version: z.string() });
|
|
729
|
+
export type BuiltinPromptText = z.infer<typeof BuiltinPromptTextSchema>;
|
|
730
|
+
|
|
731
|
+
/* ---- savings report: what each token-reduction mechanism actually saved ----
|
|
732
|
+
*
|
|
733
|
+
* TWO FAMILIES, deliberately never one list of bars. They are measured differently, and a chart that ranks
|
|
734
|
+
* them side by side claims a confidence and a denominator that only one of them has:
|
|
735
|
+
*
|
|
736
|
+
* input — shell output the cleaners trimmed before the model ever saw it. Both sides of the comparison come
|
|
737
|
+
* off the SAME command (raw in, emitted out), so the counterfactual is observed rather than
|
|
738
|
+
* estimated: exact, per command, no sample size to argue about.
|
|
739
|
+
* output — the model's own tokens under the terse steer. There is no second run of the same turn to compare
|
|
740
|
+
* against, so the only honest number is an experiment: a turn-level holdout, an n per arm, and a
|
|
741
|
+
* margin. It is absent entirely until both arms are large enough for the delta to mean anything.
|
|
742
|
+
*
|
|
743
|
+
* The two are also in different units of value — a saved tool-output token is saved again on every later
|
|
744
|
+
* request of that conversation, an output token is saved once but costs several times as much — which is the
|
|
745
|
+
* other reason they are separate sections with separate totals rather than one number.
|
|
746
|
+
*/
|
|
747
|
+
|
|
748
|
+
// One mechanism's realized saving, biggest first. `savedTokens` is what THIS stage removed from what reached
|
|
749
|
+
// it in pipeline order — sequential attribution, which is why the stages sum exactly to raw − emitted and can
|
|
750
|
+
// be drawn as one stacked bar. It is NOT "what turning this cleaner off would cost you": the cap downstream
|
|
751
|
+
// would have eaten some of the same lines. `commands` is how many commands the stage ran on. Negative for the
|
|
752
|
+
// `footer` stage, which adds the retrieval pointer back — a cost on the same ledger as what it bought.
|
|
753
|
+
export const SavingsStageSchema = z.object({ id: z.string(), commands: z.number(), savedTokens: z.number() });
|
|
754
|
+
|
|
755
|
+
// Whichever cleaner is ACTUALLY compressing output owns these numbers, so they are read from that backend's
|
|
756
|
+
// own ledger: "native" aggregates historyRoot/logs/filter-stats.jsonl (one row per agent Bash command, written
|
|
757
|
+
// by agent-output-filter), "rtk" reads rtk's own gain ledger. Reading one ledger regardless of backend is how
|
|
758
|
+
// this card went stale: under rtk the native filter is switched off, nothing appends, and the last numbers
|
|
759
|
+
// written — a test run's, as it happened — sat on the card looking live.
|
|
760
|
+
export const InputSavingsSchema = z.object({
|
|
761
|
+
// Which backend's ledger these numbers came from — shown on the card, because a number without its source
|
|
762
|
+
// cannot be told apart from a stale one. Defaulted for the same daemon-older-than-browser seam as settings.
|
|
763
|
+
source: z.enum(["native", "rtk"]).default("native"),
|
|
764
|
+
// False ⇒ these totals cover the ledger's whole life, not the range the reader selected: `rtk gain` reports
|
|
765
|
+
// no timestamps, so its numbers cannot be windowed and the screen has to say so rather than let a 7-day
|
|
766
|
+
// filter sit above an all-time figure.
|
|
767
|
+
windowed: z.boolean(),
|
|
768
|
+
// When that ledger last recorded a command (epoch ms), so the card can show its age instead of implying
|
|
769
|
+
// freshness it doesn't have. Absent when the ledger has never been written or its age can't be read.
|
|
770
|
+
updatedAt: z.number().optional(),
|
|
528
771
|
commands: z.number(),
|
|
529
772
|
rawTokens: z.number(),
|
|
530
773
|
emittedTokens: z.number(),
|
|
531
774
|
savedPct: z.number(),
|
|
532
|
-
|
|
775
|
+
// Per-stage attribution, biggest first. Native-only: rtk reports totals, not which of its handlers fired.
|
|
776
|
+
perCleaner: z.array(SavingsStageSchema),
|
|
777
|
+
// The measured control — commands the holdout left raw — against the cleaned population. A real saved-%
|
|
778
|
+
// for the pipeline as a whole rather than an estimate, and the only whole-pipeline counterfactual there is.
|
|
533
779
|
holdout: z.object({ cleaned: z.number(), heldOut: z.number(), measuredSavedPct: z.number().optional() }),
|
|
780
|
+
// High-volume commands that matched no cleaner: where the next handler is worth writing. Native-only.
|
|
534
781
|
gaps: z.array(z.object({ command: z.string(), tokens: z.number() })),
|
|
535
782
|
});
|
|
536
|
-
export type
|
|
783
|
+
export type InputSavings = z.infer<typeof InputSavingsSchema>;
|
|
784
|
+
|
|
785
|
+
// One arm of the turn-level experiment: the turns that ran with the steer, and the turns the holdout ran
|
|
786
|
+
// without it. Mean output tokens PER TURN, because the arms never hold the same number of turns.
|
|
787
|
+
export const SavingsArmSchema = z.object({ turns: z.number(), meanOutputTokens: z.number() });
|
|
788
|
+
|
|
789
|
+
// The terse steer, as measured. Only turns where the steer was ELIGIBLE are counted — a turn under a custom
|
|
790
|
+
// system prompt drops the steer along with everything else the daemon appends, so it belongs to neither arm.
|
|
791
|
+
export const OutputSavingsSchema = z.object({
|
|
792
|
+
on: SavingsArmSchema,
|
|
793
|
+
off: SavingsArmSchema,
|
|
794
|
+
// Turns per arm before a delta is reported at all. Carried on the wire so the screen's "measuring…" state
|
|
795
|
+
// counts toward the daemon's real threshold instead of a number the browser guessed.
|
|
796
|
+
minTurns: z.number(),
|
|
797
|
+
/* The three below are present TOGETHER, and only once both arms clear `minTurns` — a schema that can't
|
|
798
|
+
* express a half-measured experiment is how a 34%-that-becomes-8%-tomorrow never reaches the screen.
|
|
799
|
+
* deltaPct — change in mean output tokens per turn under the steer; negative is a saving.
|
|
800
|
+
* marginPct — ± percentage points, 95% (Welch, unequal variances and unequal arms).
|
|
801
|
+
* savedTokens — what the delta is worth over the turns that actually ran with the steer, in this window. */
|
|
802
|
+
deltaPct: z.number().optional(),
|
|
803
|
+
marginPct: z.number().optional(),
|
|
804
|
+
savedTokens: z.number().optional(),
|
|
805
|
+
});
|
|
806
|
+
export type OutputSavings = z.infer<typeof OutputSavingsSchema>;
|
|
807
|
+
|
|
808
|
+
// `output` is absent when the experiment isn't running at all (terse off, or no holdout set) — a section that
|
|
809
|
+
// isn't there reads as "not measured", which is the truth, while zeros would read as "measured, worth nothing".
|
|
810
|
+
export const SavingsReportSchema = z.object({ input: InputSavingsSchema, output: OutputSavingsSchema.optional() });
|
|
811
|
+
export type SavingsReport = z.infer<typeof SavingsReportSchema>;
|
|
537
812
|
|
|
538
813
|
// ---- intentic CLI ----
|
|
539
814
|
|
|
@@ -579,6 +854,24 @@ export const GitStatusSchema = z.object({ branch: z.string(), dirty: z.boolean()
|
|
|
579
854
|
export const GitFilesSchema = z.object({ files: z.array(z.string()) });
|
|
580
855
|
export const GitFileSchema = z.object({ path: z.string(), content: z.string() });
|
|
581
856
|
export const CommitResultSchema = z.object({ committed: z.boolean() });
|
|
857
|
+
|
|
858
|
+
/* AI-drafted commit message. Workspace-wide, not per repo, because the commit box's target IS a set of repos
|
|
859
|
+
* sharing one message — so the draft has to see every one of their diffs to describe what the commit actually
|
|
860
|
+
* records. `repos` and `all` mirror the panel's own commit target exactly: `all` reads the WORKTREE (what
|
|
861
|
+
* "Commit all" would sweep), absent reads the INDEX (what a bare commit records). Getting that wrong would
|
|
862
|
+
* describe changes the commit isn't going to contain. */
|
|
863
|
+
export const CommitMessageDraftSchema = z.object({
|
|
864
|
+
repos: z.array(z.string().min(1)).min(1).max(50),
|
|
865
|
+
all: z.boolean().optional(),
|
|
866
|
+
});
|
|
867
|
+
// The draft plus WHICH model wrote it, so the surface can name it rather than claiming an anonymous "AI" —
|
|
868
|
+
// that name is also the only place the resolved quick model is visible before anyone opens settings.
|
|
869
|
+
export const CommitMessageSchema = z.object({
|
|
870
|
+
message: z.string(),
|
|
871
|
+
provider: z.string(),
|
|
872
|
+
model: z.string(),
|
|
873
|
+
});
|
|
874
|
+
export type CommitMessageDraft = z.infer<typeof CommitMessageSchema>;
|
|
582
875
|
// One change to a file — an uncommitted working-tree change (status vs HEAD, untracked included), an agent
|
|
583
876
|
// worktree's delta vs its base, or a file in a commit. `additions`/`deletions` are the numstat line counts,
|
|
584
877
|
// undefined for a binary file (git reports "-"/"-") or an untracked file (no HEAD blob to diff against).
|
|
@@ -658,6 +951,11 @@ export const RepoChangesSchema = z.object({
|
|
|
658
951
|
// additions/deletions describe the diff it is listed under, never a conflation of the two.
|
|
659
952
|
staged: z.array(GitChangeSchema),
|
|
660
953
|
unstaged: z.array(GitChangeSchema),
|
|
954
|
+
// How many changes were CUT from the two sides above (conflicts are never cut). A cloned monorepo or a
|
|
955
|
+
// mass delete carries six-figure change lists — a payload no panel can render and no browser should hold —
|
|
956
|
+
// so past the daemon's per-repo budget the lists arrive truncated and this carries the dropped count, which
|
|
957
|
+
// the panel adds to its badges and states under the group. Absent ⇒ the lists are complete.
|
|
958
|
+
truncated: z.number().optional(),
|
|
661
959
|
// Where this repo stands against its remote; `ahead`/`behind` are 0 with no remote or no upstream.
|
|
662
960
|
remote: GitRemoteStateSchema.optional(),
|
|
663
961
|
// WHICH AGENT PUT IT THERE: repo-relative path → the agent ids that landed it, newest land first. Keyed by
|
|
@@ -665,7 +963,7 @@ export const RepoChangesSchema = z.object({
|
|
|
665
963
|
// edited again) and its origin is the same fact for both. Only agents can appear here — a main-tree turn,
|
|
666
964
|
// a terminal edit and your own typing never pass through land, so they are simply absent (see
|
|
667
965
|
// agents/origins.ts), which is why the panel badges an agent and says nothing at all for anyone else.
|
|
668
|
-
// Ids, not titles:
|
|
966
|
+
// Ids, not titles: the identity for every id named here rides the response once, in `originAgents`.
|
|
669
967
|
origins: z.record(z.string(), z.array(z.string())).optional(),
|
|
670
968
|
// Why the repo could not be scanned at all, condensed to git's own one-line reason ("fatal: bad object HEAD").
|
|
671
969
|
// A repo left torn by a canceled or failed upload used to be dropped from the response entirely, so it just
|
|
@@ -673,9 +971,31 @@ export const RepoChangesSchema = z.object({
|
|
|
673
971
|
error: z.string().optional(),
|
|
674
972
|
});
|
|
675
973
|
export type RepoChanges = z.infer<typeof RepoChangesSchema>;
|
|
974
|
+
|
|
975
|
+
// WHO AN ORIGIN ID IS — the display identity of one agent named in `origins`, carried BY THE RESPONSE rather
|
|
976
|
+
// than looked up in the client's fleet roster. The roster is the LIVE board and deliberately drops archived
|
|
977
|
+
// agents (AgentsRegistry.list), while a landing outlives the agent that made it: archiving a finished agent
|
|
978
|
+
// does not commit its lines, so the very common case — land, archive the card, review at leisure — is exactly
|
|
979
|
+
// the one a roster lookup cannot answer, and the panel fell back to "Agent 1a2b3c" with a generic icon for it.
|
|
980
|
+
// The daemon reads attribution and identity from the same registry in the same pass, so it is the one place
|
|
981
|
+
// they cannot disagree. Per response, not per repo: one agent commonly lands into several.
|
|
982
|
+
export const OriginAgentSchema = z.object({
|
|
983
|
+
// Absent for an entry that never got a title (a turn that failed before one was derived).
|
|
984
|
+
title: z.string().optional(),
|
|
985
|
+
provider: AgentProviderSchema,
|
|
986
|
+
});
|
|
987
|
+
export type OriginAgent = z.infer<typeof OriginAgentSchema>;
|
|
988
|
+
|
|
676
989
|
// The aggregated review set across every repo (root + every discovered repo); a repo appears when it has changes,
|
|
677
990
|
// when it is out of sync with its remote, or when it failed to scan.
|
|
678
|
-
export const GitChangesSchema = z.object({
|
|
991
|
+
export const GitChangesSchema = z.object({
|
|
992
|
+
repos: z.array(RepoChangesSchema),
|
|
993
|
+
// Keyed by agent id; covers every id any repo's `origins` names, and only those. Absent when nothing in
|
|
994
|
+
// the review is attributable. An id can still be missing from it — the retention sweep can retire an
|
|
995
|
+
// entry whose landed lines are somehow still uncommitted — and the panel keeps its id-shaped fallback for
|
|
996
|
+
// exactly that, rather than dropping the chip and re-attributing the file to the user.
|
|
997
|
+
originAgents: z.record(z.string(), OriginAgentSchema).optional(),
|
|
998
|
+
});
|
|
679
999
|
export type GitChanges = z.infer<typeof GitChangesSchema>;
|
|
680
1000
|
|
|
681
1001
|
// One file an agent touched, plus whether that change is ALREADY in the main tree. The review lists the
|
|
@@ -696,7 +1016,15 @@ export const AgentRepoChangesSchema = z.object({
|
|
|
696
1016
|
changes: z.array(AgentChangeSchema),
|
|
697
1017
|
});
|
|
698
1018
|
export type AgentRepoChanges = z.infer<typeof AgentRepoChangesSchema>;
|
|
699
|
-
|
|
1019
|
+
/* The review, plus WHY the last land refused — because a conflict is discovered by the daemon (a clean turn
|
|
1020
|
+
* auto-lands the moment it finishes) and acted on in the browser, possibly hours later, on a surface the user
|
|
1021
|
+
* reaches by clicking the card's "Resolve conflict". Carrying the report only in the land RESPONSE meant the
|
|
1022
|
+
* one path that opens the review already knowing there is a conflict was the one path that could not show it:
|
|
1023
|
+
* the panel opened with an empty report, no explanation, and no merge affordance — a dead end at the exact
|
|
1024
|
+
* moment the UI had promised something to resolve. It rides the review because that is the surface that
|
|
1025
|
+
* resolves it, and it refreshes with it: every land invalidates this query, so the report is never staler
|
|
1026
|
+
* than the last attempt. */
|
|
1027
|
+
export const AgentChangesSchema = z.object({ repos: z.array(AgentRepoChangesSchema), conflicts: z.array(LandConflictSchema).optional() });
|
|
700
1028
|
export type AgentChanges = z.infer<typeof AgentChangesSchema>;
|
|
701
1029
|
|
|
702
1030
|
// ---- git history graph (the "Git Graph" view over a repo's real commits) ----
|
|
@@ -802,11 +1130,11 @@ export const WorkspaceTreeEntrySchema = z.object({
|
|
|
802
1130
|
path: z.string(),
|
|
803
1131
|
type: z.enum(["file", "dir"]),
|
|
804
1132
|
size: z.number().optional(),
|
|
805
|
-
// Set on a dir whose child list was cut short by the entry cap — some of its items aren't in `children`.
|
|
806
|
-
truncated: z.boolean().optional(),
|
|
807
1133
|
// Ignored-by-tooling (node_modules, .git, .gitignore'd paths, browser profiles): the client grays the row.
|
|
808
|
-
// An ignored DIR is listed without `children` — the client lazy-loads it via /workspace/children on expand.
|
|
809
1134
|
ignored: z.boolean().optional(),
|
|
1135
|
+
// A DIR without `children` was listed but not descended into — because it's ignored, or because the walk's
|
|
1136
|
+
// breadth-first budget stopped above it. Either way the client lazy-loads it via /workspace/children on
|
|
1137
|
+
// expand, so "not loaded yet" and "empty directory" (`children: []`) stay distinguishable.
|
|
810
1138
|
get children() {
|
|
811
1139
|
return z.array(WorkspaceTreeEntrySchema).optional();
|
|
812
1140
|
},
|
|
@@ -815,17 +1143,17 @@ export type WorkspaceTreeEntry = z.infer<typeof WorkspaceTreeEntrySchema>;
|
|
|
815
1143
|
export const WorkspaceTreeSchema = z.object({
|
|
816
1144
|
root: z.string(),
|
|
817
1145
|
tree: z.array(WorkspaceTreeEntrySchema),
|
|
818
|
-
//
|
|
819
|
-
|
|
1146
|
+
// How many of the ROOT's own entries the budget cut (0 = complete); per-dir cuts are counted on each dir entry.
|
|
1147
|
+
hidden: z.number(),
|
|
820
1148
|
});
|
|
821
1149
|
export type WorkspaceTree = z.infer<typeof WorkspaceTreeSchema>;
|
|
822
|
-
// Lazy-load one directory's children — for
|
|
823
|
-
//
|
|
824
|
-
//
|
|
1150
|
+
// Lazy-load one directory's children — for a dir the tree walk listed but didn't descend into. Child dirs again
|
|
1151
|
+
// carry no `children`, so they lazy-load on their own expand. `hidden` = how many entries the cap cut (0 = all
|
|
1152
|
+
// listed).
|
|
825
1153
|
export const WorkspaceChildrenQuerySchema = z.object({ path: z.string().min(1) });
|
|
826
1154
|
export const WorkspaceChildrenSchema = z.object({
|
|
827
1155
|
entries: z.array(WorkspaceTreeEntrySchema),
|
|
828
|
-
|
|
1156
|
+
hidden: z.number(),
|
|
829
1157
|
});
|
|
830
1158
|
export type WorkspaceChildren = z.infer<typeof WorkspaceChildrenSchema>;
|
|
831
1159
|
export const WorkspaceFileQuerySchema = z.object({ path: z.string().min(1) });
|
|
@@ -906,6 +1234,58 @@ export const WorkspaceSearchResultSchema = z.object({
|
|
|
906
1234
|
});
|
|
907
1235
|
export type WorkspaceSearchResult = z.infer<typeof WorkspaceSearchResultSchema>;
|
|
908
1236
|
|
|
1237
|
+
// ---- codebase health: one repository's structure and risk, in numbers ----
|
|
1238
|
+
|
|
1239
|
+
// The repo-level companion to the management panel and the git-history graph: what the same resident engine's
|
|
1240
|
+
// `hotspots` (churn × complexity) and `map` (PageRank over the import graph) verbs rank, as figures a panel can
|
|
1241
|
+
// plot instead of lines a terminal prints.
|
|
1242
|
+
//
|
|
1243
|
+
// Every field is a COUNT that can be recounted in the files themselves — commits, branch points, exported
|
|
1244
|
+
// symbols. Deliberately no composite "maintainability grade": those aren't comparable across projects and can't
|
|
1245
|
+
// be checked, and a repo-health surface that launders counts into a letter is worse than none.
|
|
1246
|
+
// How many hotspot files and key modules a report carries when the caller names no limit. A leaderboard, not an
|
|
1247
|
+
// inventory: past a screenful the ranking stops being the point, and the reader should be reading the files.
|
|
1248
|
+
export const HEALTH_LIMIT = 20;
|
|
1249
|
+
export const WorkspaceHealthQuerySchema = z.object({
|
|
1250
|
+
// "root" (the /work repo) or a nested repo's root-relative dir — the same {repo} ids the git routes take.
|
|
1251
|
+
repo: z.string().min(1),
|
|
1252
|
+
// Churn window (2d, 12h, 1w, 3m). Absent = all of history, which is what a hotspot ranking wants by default.
|
|
1253
|
+
since: z.string().max(16).optional(),
|
|
1254
|
+
limit: z.coerce.number().int().positive().max(200).optional(),
|
|
1255
|
+
});
|
|
1256
|
+
// One file that is BOTH churning and tangled. `score` is the product the ranking sorts by — carried explicitly
|
|
1257
|
+
// so the panel plots the number it ranks by rather than recomputing it.
|
|
1258
|
+
export const WorkspaceHotspotSchema = z.object({
|
|
1259
|
+
path: z.string(),
|
|
1260
|
+
commits: z.number(),
|
|
1261
|
+
adds: z.number(),
|
|
1262
|
+
dels: z.number(),
|
|
1263
|
+
complexity: z.number(),
|
|
1264
|
+
score: z.number(),
|
|
1265
|
+
// Epoch ms of the latest commit touching the file, within the window.
|
|
1266
|
+
latestMs: z.number(),
|
|
1267
|
+
});
|
|
1268
|
+
export type WorkspaceHotspot = z.infer<typeof WorkspaceHotspotSchema>;
|
|
1269
|
+
// A file of the import graph's ranked skeleton — order IS the rank, so no rank number rides along.
|
|
1270
|
+
export const WorkspaceKeyModuleSchema = z.object({ path: z.string(), exports: z.number() });
|
|
1271
|
+
export type WorkspaceKeyModule = z.infer<typeof WorkspaceKeyModuleSchema>;
|
|
1272
|
+
export const WorkspaceHealthSchema = z.object({
|
|
1273
|
+
repo: z.string(),
|
|
1274
|
+
totals: z.object({
|
|
1275
|
+
files: z.number(),
|
|
1276
|
+
symbols: z.number(),
|
|
1277
|
+
// Summed branch points across the scoped files.
|
|
1278
|
+
complexity: z.number(),
|
|
1279
|
+
// How many files qualify as hotspots at all — the lists below are capped, this is not.
|
|
1280
|
+
hotspots: z.number(),
|
|
1281
|
+
}),
|
|
1282
|
+
hotspots: z.array(WorkspaceHotspotSchema),
|
|
1283
|
+
modules: z.array(WorkspaceKeyModuleSchema),
|
|
1284
|
+
// Same index-freshness signal the search route reports: a panel drawn off a half-built index says so.
|
|
1285
|
+
freshness: WorkspaceSearchFreshnessSchema,
|
|
1286
|
+
});
|
|
1287
|
+
export type WorkspaceHealth = z.infer<typeof WorkspaceHealthSchema>;
|
|
1288
|
+
|
|
909
1289
|
// ---- workspace setup (dependency readiness) ----
|
|
910
1290
|
|
|
911
1291
|
// One project under /work and whether its dependencies are actually installed. A drop omits node_modules/.venv
|
|
@@ -981,12 +1361,13 @@ export const TemplatesListSchema = z.object({ templates: z.array(TemplateSummary
|
|
|
981
1361
|
export type TemplatesList = z.infer<typeof TemplatesListSchema>;
|
|
982
1362
|
|
|
983
1363
|
// One app instance currently in a monorepo, with its own preview dev server + live status (started/stopped
|
|
984
|
-
// from the apps extension). `app` is the user-chosen instance name (the _apps/ dir); `
|
|
985
|
-
// manifest key it was
|
|
1364
|
+
// from the apps extension). `app` is the user-chosen instance name (the _apps/ dir); `kind` is what sort of
|
|
1365
|
+
// app it is — the manifest key it was scaffolded from (api/web/landing), else the framework detected from its
|
|
1366
|
+
// dependencies (astro/next/…), and absent when it was discovered purely by its `dev` script. previewUrl is
|
|
986
1367
|
// https://preview-<repo>--<app>-<sandboxId>.<zone> (absent on loopback — no zone or no connect token).
|
|
987
1368
|
export const RepoAppSchema = z.object({
|
|
988
1369
|
app: z.string(),
|
|
989
|
-
|
|
1370
|
+
kind: z.string().optional(),
|
|
990
1371
|
previewUrl: z.string().optional(),
|
|
991
1372
|
running: z.boolean(),
|
|
992
1373
|
healthy: z.boolean(),
|
|
@@ -1534,7 +1915,9 @@ export const WorkspaceEventSchema = z.object({
|
|
|
1534
1915
|
agentId: z.string(),
|
|
1535
1916
|
title: z.string().optional(),
|
|
1536
1917
|
branch: z.string(),
|
|
1537
|
-
|
|
1918
|
+
// `ready` is a clean turn whose delta was HELD on the branch (auto-land off) — for a chore, the moment
|
|
1919
|
+
// before the user's deliberate Land, which is exactly when a pre-land review wants to run.
|
|
1920
|
+
outcome: z.enum(["landed", "conflict", "ready", "idle", "error"]),
|
|
1538
1921
|
repos: z.array(z.object({ repo: z.string(), from: z.string(), dir: z.string() })),
|
|
1539
1922
|
});
|
|
1540
1923
|
export type WorkspaceEvent = z.infer<typeof WorkspaceEventSchema>;
|
|
@@ -1681,13 +2064,15 @@ export const PanelSummarySchema = z.object({
|
|
|
1681
2064
|
role: z.enum(["intent", "desired-state", "app"]).optional(),
|
|
1682
2065
|
// Content facts: deploy.config.ts (the intent ledger's day-one marker), desired-state.json (present after
|
|
1683
2066
|
// the first resolve), .intentic/ui/index.html (a sandboxed directory UI), pnpm-workspace.yaml +
|
|
1684
|
-
// turbo.json (a pnpm+turbo monorepo),
|
|
1685
|
-
// root manifest / workspace catalog)
|
|
2067
|
+
// turbo.json (a pnpm+turbo monorepo), vitest evidence (a root vitest.config.ts, or "vitest" in the
|
|
2068
|
+
// root manifest / workspace catalog), and docs/user-stories (a directory of stories an agent can test
|
|
2069
|
+
// against the running app — the one fact here that says nothing about the repo's language).
|
|
1686
2070
|
deployConfig: z.boolean(),
|
|
1687
2071
|
desiredState: z.boolean(),
|
|
1688
2072
|
directoryUi: z.boolean(),
|
|
1689
2073
|
monorepo: z.boolean(),
|
|
1690
2074
|
vitest: z.boolean(),
|
|
2075
|
+
userStories: z.boolean(),
|
|
1691
2076
|
});
|
|
1692
2077
|
export type PanelSummary = z.infer<typeof PanelSummarySchema>;
|
|
1693
2078
|
export const PanelsListSchema = z.object({ panels: z.array(PanelSummarySchema) });
|
|
@@ -1750,11 +2135,19 @@ export type PortForwardResult = z.infer<typeof PortForwardResultSchema>;
|
|
|
1750
2135
|
// declared process carries extensionId+processName, the address for its /extensions start/stop routes. The
|
|
1751
2136
|
// `{name}` kill-route param is a bare string validated in the handler (a bad name is a BAD_REQUEST) since the
|
|
1752
2137
|
// same charset gates a `tmux kill-session -t` shell-out.
|
|
2138
|
+
//
|
|
2139
|
+
// `activityAt` (epoch ms of the session's last output) and `exitCode` (the LAST window's exit status, absent
|
|
2140
|
+
// while that pane still lives) are what let a finished session be READ rather than merely listed: the panel's
|
|
2141
|
+
// Recent-terminals popover orders by the one and reports the other ("exit 1 · 1h ago"), and the daemon's
|
|
2142
|
+
// retention sweep ages sessions out by the same clock. 0 is "tmux didn't say" — treated as unknown by both,
|
|
2143
|
+
// never as 1970.
|
|
1753
2144
|
export const TerminalSessionSchema = z.object({
|
|
1754
2145
|
name: z.string(),
|
|
1755
2146
|
label: z.string().optional(),
|
|
1756
2147
|
kind: z.enum(["shell", "panel", "agent", "job", "process"]),
|
|
1757
2148
|
running: z.boolean(),
|
|
2149
|
+
activityAt: z.number(),
|
|
2150
|
+
exitCode: z.number().optional(),
|
|
1758
2151
|
extensionId: z.string().optional(),
|
|
1759
2152
|
processName: z.string().optional(),
|
|
1760
2153
|
});
|
|
@@ -1833,6 +2226,12 @@ export const InfoSchema = z.object({
|
|
|
1833
2226
|
updateAvailable: z.boolean().optional(),
|
|
1834
2227
|
});
|
|
1835
2228
|
|
|
2229
|
+
// A daemon-minted session (system.session): the steady-state browser credential, exchanged for a verified
|
|
2230
|
+
// Google ID token so Google UI is a sign-in moment instead of an hourly renewal. `expiresAt` is epoch ms —
|
|
2231
|
+
// the browser renews ahead of it without parsing the token; `email` is who the daemon verified.
|
|
2232
|
+
export const DaemonSessionSchema = z.object({ token: z.string(), expiresAt: z.number(), email: z.string() });
|
|
2233
|
+
export type DaemonSession = z.infer<typeof DaemonSessionSchema>;
|
|
2234
|
+
|
|
1836
2235
|
// Intentic-provided host SSH tunnel: minting it needs intentic's PLATFORM Cloudflare account, so the daemon
|
|
1837
2236
|
// can't do it directly — it relays to the platform authenticated by the connect token (the announce pattern).
|
|
1838
2237
|
// The panel embeds the returned connector token + hostname in its connect-host one-liner.
|
|
@@ -1932,6 +2331,14 @@ export const UsageTurnSchema = z.object({
|
|
|
1932
2331
|
cacheCreationTokens: z.number(),
|
|
1933
2332
|
costUsd: z.number(),
|
|
1934
2333
|
durationMs: z.number(),
|
|
2334
|
+
/* Which arm of the terse experiment this turn ran on (settings.terseHoldout) — the only record of it, and
|
|
2335
|
+
* the reason the savings report can say what the steer is worth instead of guessing.
|
|
2336
|
+
*
|
|
2337
|
+
* ABSENT means "not part of the experiment", not "off": a turn under a custom system prompt drops the
|
|
2338
|
+
* steer along with everything else the daemon appends, and a turn run with the experiment switched off has
|
|
2339
|
+
* no control to be compared against. Pooling those into the off-arm would compare steered turns against a
|
|
2340
|
+
* population selected by something other than the coin flip, which is not a control at all. */
|
|
2341
|
+
terse: z.boolean().optional(),
|
|
1935
2342
|
});
|
|
1936
2343
|
export type UsageTurn = z.infer<typeof UsageTurnSchema>;
|
|
1937
2344
|
|
|
@@ -1959,12 +2366,14 @@ export const UsageRollupRowSchema = z.object({
|
|
|
1959
2366
|
durationMs: z.number(),
|
|
1960
2367
|
});
|
|
1961
2368
|
export type UsageRollupRow = z.infer<typeof UsageRollupRowSchema>;
|
|
1962
|
-
// Inclusive UTC day bounds (YYYY-MM-DD). Both absent ⇒ the whole ledger.
|
|
1963
|
-
|
|
2369
|
+
// Inclusive UTC day bounds (YYYY-MM-DD). Both absent ⇒ the whole ledger. Shared by every windowed read of a
|
|
2370
|
+
// daemon ledger (spend, savings): one window shape, so a screen that filters two ledgers at once filters them
|
|
2371
|
+
// with the same calendar.
|
|
2372
|
+
export const DayWindowQuerySchema = z.object({
|
|
1964
2373
|
from: z.string().optional(),
|
|
1965
2374
|
to: z.string().optional(),
|
|
1966
2375
|
});
|
|
1967
|
-
export type
|
|
2376
|
+
export type DayWindowQuery = z.infer<typeof DayWindowQuerySchema>;
|
|
1968
2377
|
export const UsageRollupSchema = z.object({ rows: z.array(UsageRollupRowSchema) });
|
|
1969
2378
|
|
|
1970
2379
|
// ---- usage: per-account token/cost totals ----
|
|
@@ -2013,6 +2422,45 @@ export const LogReadSchema = z.object({
|
|
|
2013
2422
|
});
|
|
2014
2423
|
export type LogRead = z.infer<typeof LogReadSchema>;
|
|
2015
2424
|
|
|
2425
|
+
// ---- memory: the agent's persistent memory notes (.intentic/claude/projects/<project>/memory) ----
|
|
2426
|
+
// The markdown files the agent curates across sessions — MEMORY.md (the index) plus one file per fact. They
|
|
2427
|
+
// live under the workspace's .intentic/claude control plane (symlinked from ~/.claude/projects), which the
|
|
2428
|
+
// generic /workspace file API deliberately refuses (session transcripts and provider state share that tree),
|
|
2429
|
+
// so these purpose-built routes are the only browser surface — scoped to the memory dirs and nothing else.
|
|
2430
|
+
|
|
2431
|
+
export const MemoryFileEntrySchema = z.object({
|
|
2432
|
+
// The project slug the memory belongs to (one dir per agent cwd, e.g. "-history-gits-root").
|
|
2433
|
+
project: z.string(),
|
|
2434
|
+
// Path relative to that project's memory dir, e.g. "MEMORY.md" or "team-conventions.md".
|
|
2435
|
+
name: z.string(),
|
|
2436
|
+
sizeBytes: z.number(),
|
|
2437
|
+
// Epoch ms mtime.
|
|
2438
|
+
modifiedAt: z.number(),
|
|
2439
|
+
});
|
|
2440
|
+
export type MemoryFileEntry = z.infer<typeof MemoryFileEntrySchema>;
|
|
2441
|
+
export const MemoryListSchema = z.object({ files: z.array(MemoryFileEntrySchema) });
|
|
2442
|
+
|
|
2443
|
+
// `project` + `name` ride the query (names may contain slashes, which don't fit a path segment).
|
|
2444
|
+
export const MemoryFileQuerySchema = z.object({
|
|
2445
|
+
project: z.string().min(1),
|
|
2446
|
+
name: z.string().min(1),
|
|
2447
|
+
});
|
|
2448
|
+
export const MemoryFileSchema = z.object({
|
|
2449
|
+
project: z.string(),
|
|
2450
|
+
name: z.string(),
|
|
2451
|
+
content: z.string(),
|
|
2452
|
+
sizeBytes: z.number(),
|
|
2453
|
+
modifiedAt: z.number(),
|
|
2454
|
+
});
|
|
2455
|
+
export type MemoryFile = z.infer<typeof MemoryFileSchema>;
|
|
2456
|
+
|
|
2457
|
+
// Memory notes are small by construction (one fact per file); the cap guards the route, not real usage.
|
|
2458
|
+
export const MemoryWriteSchema = z.object({
|
|
2459
|
+
project: z.string().min(1),
|
|
2460
|
+
name: z.string().min(1),
|
|
2461
|
+
content: z.string().max(1_048_576),
|
|
2462
|
+
});
|
|
2463
|
+
|
|
2016
2464
|
// A tab's self-report of what it is looking at, keyed by its /events connection's clientId. Full replace,
|
|
2017
2465
|
// not a merge — an absent field means "cleared", so a tab leaving a file drops the path with the same report.
|
|
2018
2466
|
export const PresenceReportSchema = z.object({
|
|
@@ -2044,7 +2492,10 @@ export type PushSubscription = z.infer<typeof PushSubscriptionSchema>;
|
|
|
2044
2492
|
|
|
2045
2493
|
// What the service worker renders. `url` is the in-app route the notification opens (the click handler
|
|
2046
2494
|
// focuses an existing tab there rather than spawning a new one); `tag` collapses repeats — a second
|
|
2047
|
-
// "waiting on you" for the same conversation REPLACES the first instead of stacking.
|
|
2495
|
+
// "waiting on you" for the same conversation REPLACES the first instead of stacking. Push payloads are
|
|
2496
|
+
// capped by the push services themselves (~4KB after encryption), which is why nothing here carries a
|
|
2497
|
+
// transcript or a diff — the notification is a pointer back into the workspace, not a delivery mechanism
|
|
2498
|
+
// for content.
|
|
2048
2499
|
export const PushNotificationSchema = z.object({
|
|
2049
2500
|
title: z.string().min(1),
|
|
2050
2501
|
body: z.string(),
|
|
@@ -2061,5 +2512,12 @@ export type PushNotification = z.infer<typeof PushNotificationSchema>;
|
|
|
2061
2512
|
// granted permission with no server-side row would notify nothing).
|
|
2062
2513
|
export const PushConfigSchema = z.object({ publicKey: z.string(), subscribed: z.boolean() });
|
|
2063
2514
|
export const PushEndpointSchema = z.object({ endpoint: z.string().url() });
|
|
2064
|
-
// `endpoint`
|
|
2515
|
+
// The optional `endpoint` says WHICH browser is asking; without it `subscribed` could only speak for the
|
|
2516
|
+
// sandbox as a whole, which is never the question the settings toggle needs answered.
|
|
2065
2517
|
export const PushConfigQuerySchema = z.object({ endpoint: z.string().url().optional() });
|
|
2518
|
+
|
|
2519
|
+
// What a test send actually achieved. `{ ok: true }` would be a lie the one place it matters most: the button
|
|
2520
|
+
// exists to prove a chain the user cannot inspect, so "the daemon accepted the request" is not the answer to
|
|
2521
|
+
// the question being asked. A count separates "your OS swallowed it" from "nothing was sent at all".
|
|
2522
|
+
export const PushTestSchema = z.object({ delivered: z.number().int().nonnegative() });
|
|
2523
|
+
export type PushTest = z.infer<typeof PushTestSchema>;
|