@intentic/sandbox-contract 1.158.0 → 1.160.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 +10 -0
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +14 -1
- package/dist/agent-catalog.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +74 -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 +248 -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/automations.contract.d.ts +7 -0
- package/dist/contracts/automations.contract.d.ts.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 +32 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +2 -1
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +35 -6
- 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/workspace.contract.d.ts +43 -5
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +3 -1
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events.d.ts +176 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +16 -5
- package/dist/events.js.map +1 -1
- package/dist/hostnames.d.ts +2 -0
- package/dist/hostnames.d.ts.map +1 -1
- package/dist/hostnames.js +27 -1
- package/dist/hostnames.js.map +1 -1
- package/dist/index.d.ts +566 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/model-order.d.ts +2 -0
- package/dist/model-order.d.ts.map +1 -1
- package/dist/model-order.js +3 -0
- package/dist/model-order.js.map +1 -1
- package/dist/path-refs.d.ts +5 -0
- package/dist/path-refs.d.ts.map +1 -0
- package/dist/path-refs.js +21 -0
- package/dist/path-refs.js.map +1 -0
- 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/routes.d.ts +8 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +39 -0
- package/dist/routes.js.map +1 -0
- package/dist/schemas.d.ts +351 -10
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +143 -13
- 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 +51 -1
- 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 +5 -1
- package/src/contracts/system.contract.ts +4 -0
- package/src/contracts/translator.contract.ts +3 -2
- package/src/contracts/workspace.contract.ts +12 -0
- package/src/events.ts +89 -11
- package/src/hostnames.test.ts +22 -0
- package/src/hostnames.ts +40 -1
- package/src/index.ts +20 -0
- package/src/model-order.test.ts +73 -1
- package/src/model-order.ts +31 -0
- package/src/path-refs.test.ts +68 -0
- package/src/path-refs.ts +59 -0
- package/src/quick-model.test.ts +88 -0
- package/src/quick-model.ts +103 -0
- package/src/routes.test.ts +83 -0
- package/src/routes.ts +84 -0
- package/src/schemas.test.ts +23 -1
- package/src/schemas.ts +461 -38
- package/src/title.test.ts +130 -0
- package/src/title.ts +254 -0
package/src/schemas.ts
CHANGED
|
@@ -54,6 +54,24 @@ export type EditorContext = z.infer<typeof EditorContextSchema>;
|
|
|
54
54
|
// and filesystem paths — the regex is the injection guard. Shared by the turn input and the attach input.
|
|
55
55
|
const ConversationIdSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/);
|
|
56
56
|
|
|
57
|
+
// Where a conversation came from when nobody typed it into the browser: an automation wake carrying a message
|
|
58
|
+
// from OUTSIDE the sandbox (a Discord mention, a web-chat visitor, a webhook). Such a wake runs as an ordinary
|
|
59
|
+
// isolated conversation — registry entry, worktree, chat tab, land flow — and this is the only thing that
|
|
60
|
+
// distinguishes it on the surface: the card's provenance line and the reason its first prompt is not the
|
|
61
|
+
// user's. Set daemon-side by the dispatcher that received the message; the browser never sends one.
|
|
62
|
+
export const AgentOriginSchema = z.object({
|
|
63
|
+
// The automation whose configured prompt opened the conversation.
|
|
64
|
+
automationId: z.string(),
|
|
65
|
+
// The listener provider that received the message ("discord", "webchat", …) or "webhook" for an event
|
|
66
|
+
// trigger. An open string for the same reason Trigger.provider is: sources are extension-declared.
|
|
67
|
+
provider: z.string(),
|
|
68
|
+
// The external thread it arrived on — a Discord channel id, a widget conversation id. Absent for webhooks.
|
|
69
|
+
channelId: z.string().optional(),
|
|
70
|
+
// Who sent it, as the source names them.
|
|
71
|
+
author: z.string().optional(),
|
|
72
|
+
});
|
|
73
|
+
export type AgentOrigin = z.infer<typeof AgentOriginSchema>;
|
|
74
|
+
|
|
57
75
|
// How tool calls are gated — the Claude Agent SDK's PermissionMode, narrowed to the four the composer offers
|
|
58
76
|
// (the SDK also has 'dontAsk'/'auto', which have no UI here). The user picks one per turn AND the agent can
|
|
59
77
|
// move itself between them mid-turn, so this is both a turn input and the payload of the `mode` frame.
|
|
@@ -89,6 +107,10 @@ export const AgentTurnSchema = z
|
|
|
89
107
|
// When true, the turn runs in the conversation's isolated git worktree (created lazily on first use)
|
|
90
108
|
// instead of the shared /work tree — the parallel-agents mode. Requires conversationId.
|
|
91
109
|
isolated: z.boolean().optional(),
|
|
110
|
+
// Set ONLY by the daemon's own automation dispatchers: this turn opens a conversation on behalf of an
|
|
111
|
+
// outside message rather than a user. Recorded on the registry entry so the fleet can say where the
|
|
112
|
+
// agent came from. Requires conversationId — there is nothing to record it on otherwise.
|
|
113
|
+
origin: AgentOriginSchema.optional(),
|
|
92
114
|
// The client-held transcript of a conversation that just switched provider/account: seeds the FIRST
|
|
93
115
|
// turn of the replacement session. The daemon folds it into the prompt as one role-attributed context
|
|
94
116
|
// preamble for every runtime. Mutually exclusive with sessionId — a resumed session has its context.
|
|
@@ -114,6 +136,9 @@ export const AgentTurnSchema = z
|
|
|
114
136
|
})
|
|
115
137
|
.refine((turn) => turn.isolated !== true || turn.conversationId !== undefined, {
|
|
116
138
|
message: "isolated requires conversationId",
|
|
139
|
+
})
|
|
140
|
+
.refine((turn) => turn.origin === undefined || turn.conversationId !== undefined, {
|
|
141
|
+
message: "origin requires conversationId",
|
|
117
142
|
});
|
|
118
143
|
export type AgentTurn = z.infer<typeof AgentTurnSchema>;
|
|
119
144
|
|
|
@@ -137,9 +162,18 @@ export type AttachTurn = z.infer<typeof AttachTurnSchema>;
|
|
|
137
162
|
// conversationId. Isolated ones own a git worktree (branch agent/<id> in every workspace repo); the fleet
|
|
138
163
|
// surface shows all of them with live status/activity/cost so the user can drive N agents in parallel.
|
|
139
164
|
|
|
140
|
-
// idle/running/awaiting are the turn lifecycle (awaiting = paused on a plan approval or question);
|
|
141
|
-
// conflict are outcomes of the land flow
|
|
142
|
-
|
|
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"]);
|
|
143
177
|
export type AgentStatus = z.infer<typeof AgentStatusSchema>;
|
|
144
178
|
// The card's live activity snippet: the last tool the agent used (with its target) and the in-progress todo.
|
|
145
179
|
export const AgentActivitySchema = z.object({
|
|
@@ -165,10 +199,24 @@ export const AgentSummarySchema = z.object({
|
|
|
165
199
|
status: AgentStatusSchema,
|
|
166
200
|
provider: AgentProviderSchema,
|
|
167
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).
|
|
168
206
|
model: z.string().optional(),
|
|
207
|
+
effort: z.string().optional(),
|
|
208
|
+
thinking: z.boolean().optional(),
|
|
169
209
|
account: z.string().optional(),
|
|
170
210
|
// The worktree branch (agent/<id>); absent for a non-isolated (main-tree) conversation.
|
|
171
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(),
|
|
217
|
+
// Present when the conversation was opened by an outside message rather than by the user (see
|
|
218
|
+
// AgentOriginSchema) — the card's provenance line. Absent ⇒ the user started it.
|
|
219
|
+
origin: AgentOriginSchema.optional(),
|
|
172
220
|
// The ROOT repo's short base sha — the checkout moment's display identity. Per-repo bases stay
|
|
173
221
|
// daemon-internal (agents.diff already reports against them).
|
|
174
222
|
base: z.string().optional(),
|
|
@@ -200,7 +248,12 @@ export const AgentSummarySchema = z.object({
|
|
|
200
248
|
archivedAt: z.number().optional(),
|
|
201
249
|
});
|
|
202
250
|
export type AgentSummary = z.infer<typeof AgentSummarySchema>;
|
|
203
|
-
|
|
251
|
+
// `rev` is the registry revision this roster was read at — a counter the daemon bumps on every registry change.
|
|
252
|
+
// It is what makes the browser's optimistic writes safe: the fleet is published as full snapshots (last frame
|
|
253
|
+
// wins), so without an ordering stamp a roster READ before a mutation but delivered after it silently puts the
|
|
254
|
+
// mutated agents back. The browser drops any roster older than the newest it has applied, and holds its own
|
|
255
|
+
// pending change until a roster at or past the revision that applied it arrives. See useAgents.ts.
|
|
256
|
+
export const AgentsListSchema = z.object({ agents: z.array(AgentSummarySchema), rev: z.number() });
|
|
204
257
|
export const AgentIdSchema = z.object({ id: z.string().min(1) });
|
|
205
258
|
// archive's input: the agents to take off the board. Absent `ids` ⇒ every finished agent that is archivable
|
|
206
259
|
// right now (the lane header's "Clear"); unarchive always names its ids (a restore, or a bulk archive's undo).
|
|
@@ -211,19 +264,92 @@ export const AgentIdsSchema = z.object({ ids: z.array(z.string().min(1)).min(1).
|
|
|
211
264
|
// the slower response resurrect what the faster one just filed away — a delta composes where a snapshot races.
|
|
212
265
|
// Whole summaries rather than ids because the receiving side has to SHOW them (the archive list, and the agent
|
|
213
266
|
// detail page addressed by id); the ids "Undo" needs come off them for free.
|
|
214
|
-
|
|
267
|
+
// The agents an archive/unarchive actually moved, plus the registry revision that applied the move — the
|
|
268
|
+
// browser holds its optimistic add/remove of exactly these ids until it sees a roster at or past `rev`.
|
|
269
|
+
export const AgentsMovedSchema = z.object({ moved: z.array(AgentSummarySchema), rev: z.number() });
|
|
215
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>;
|
|
216
297
|
// rename's input: the user-chosen display title (bounded like sanitizeTitle's cap).
|
|
217
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() });
|
|
218
303
|
export const AgentFileDiffQuerySchema = z.object({ id: z.string().min(1), repo: z.string().min(1), path: z.string().min(1) });
|
|
219
|
-
|
|
220
|
-
|
|
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.
|
|
221
331
|
export const LandResultSchema = z.object({
|
|
222
332
|
landed: z.boolean(),
|
|
223
|
-
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(),
|
|
224
338
|
});
|
|
225
339
|
export type LandResult = z.infer<typeof LandResultSchema>;
|
|
226
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
|
+
|
|
227
353
|
// ---- routed-provider subscriptions ----
|
|
228
354
|
|
|
229
355
|
// The providers whose model can run UNDER the Claude Code harness through the bundled translator (CLIProxyAPI),
|
|
@@ -234,9 +360,19 @@ export type LandResult = z.infer<typeof LandResultSchema>;
|
|
|
234
360
|
export const KeyedProviderSchema = z.enum(["codex", "grok", "gemini"]);
|
|
235
361
|
export type KeyedProvider = z.infer<typeof KeyedProviderSchema>;
|
|
236
362
|
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
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
|
+
});
|
|
240
376
|
export type TranslatorAccounts = z.infer<typeof TranslatorAccountsSchema>;
|
|
241
377
|
|
|
242
378
|
// The side-channel body that un-parks a turn waiting on the user. Every interactive card — plan approval,
|
|
@@ -293,6 +429,12 @@ export const SteerSchema = z
|
|
|
293
429
|
// True cancel for the conversation's in-flight turn — aborts the agent daemon-side, unlike closing the
|
|
294
430
|
// /agent fetch (which sends no cancel frame).
|
|
295
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() });
|
|
296
438
|
|
|
297
439
|
// ---- claude subscription usage ----
|
|
298
440
|
// The GATE signal: whether the provider is letting turns through right now, and — when it is refusing — which
|
|
@@ -346,6 +488,14 @@ export type AccountUsage = z.infer<typeof AccountUsageSchema>;
|
|
|
346
488
|
export const OauthAccountSchema = z.object({
|
|
347
489
|
id: z.string(),
|
|
348
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(),
|
|
349
499
|
scope: z.string().optional(),
|
|
350
500
|
connectedAt: z.number(), // epoch ms
|
|
351
501
|
// Set only when the account's stored credential can no longer be refreshed (revoked/expired refresh token)
|
|
@@ -362,6 +512,10 @@ export type OauthAccount = z.infer<typeof OauthAccountSchema>;
|
|
|
362
512
|
export const OauthAccountListSchema = z.object({ accounts: z.array(OauthAccountSchema) });
|
|
363
513
|
// Address one account of a provider (disconnect, and the turn's `account`).
|
|
364
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) });
|
|
365
519
|
// The completing calls carry the user-chosen label (blank ⇒ the daemon derives one from the sign-in identity
|
|
366
520
|
// or a provider default).
|
|
367
521
|
export const OauthExchangeSchema = z.object({
|
|
@@ -429,10 +583,29 @@ export const ModelsSchema = z.object({ models: z.array(ModelSchema), default: z.
|
|
|
429
583
|
// ---- sessions ----
|
|
430
584
|
|
|
431
585
|
export const SessionIdParamSchema = z.object({ id: z.string() });
|
|
432
|
-
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
|
+
});
|
|
433
595
|
export const SessionsListSchema = z.object({ sessions: z.array(SessionSummarySchema) });
|
|
434
596
|
|
|
435
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
|
+
|
|
436
609
|
// Small user-owned config the /settings routes edit and streamAgent reads — all opt-in booleans the owner
|
|
437
610
|
// toggles in the UI (so each can be A/B benchmarked):
|
|
438
611
|
// stableSystemPrompt — keeps the system prompt byte-stable across turns (the delegation note rides the user
|
|
@@ -445,6 +618,9 @@ export const SessionsListSchema = z.object({ sessions: z.array(SessionSummarySch
|
|
|
445
618
|
// guard + fewer output tokens); off ⇒ the native file tools.
|
|
446
619
|
// terseOutput — appends a concise-response steer to the end of the system prompt (a stable suffix, so it
|
|
447
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.
|
|
448
624
|
// iqSearch — loads the image-baked iq Claude Code plugin (skill + SessionStart nudge) so the agent
|
|
449
625
|
// prefers the iq CLI over grep/find/Glob; off ⇒ plugin not loaded, native search tools
|
|
450
626
|
// only. Opt-in (default off); the browser Search box uses iq regardless.
|
|
@@ -455,7 +631,7 @@ export const SessionsListSchema = z.object({ sessions: z.array(SessionSummarySch
|
|
|
455
631
|
// (recorded raw as `heldOut`), so the savings report compares a real cleaned-vs-raw
|
|
456
632
|
// population instead of an estimate. 0 = no holdout (default).
|
|
457
633
|
// filterBackend — which cleaner runs the compression: "native" (agent-output-filter, default) or "rtk"
|
|
458
|
-
// (the rtk binary
|
|
634
|
+
// (the image-baked rtk binary, rewritten at the PreToolUse hook) — an
|
|
459
635
|
// A/B backend switch, so native and rtk can be benchmarked head-to-head.
|
|
460
636
|
// The booleans default off, skills defaults [] (no skill loaded), outputCleaners defaults "off" (cleaning off),
|
|
461
637
|
// outputHoldout 0, filterBackend "native" — a fresh sandbox starts with cleaning and iq off until the owner enables them.
|
|
@@ -472,24 +648,87 @@ export const SandboxSettingsSchema = z.object({
|
|
|
472
648
|
skills: z.array(z.string()).default([]),
|
|
473
649
|
hashlineEdits: z.boolean().default(false),
|
|
474
650
|
terseOutput: z.boolean().default(false),
|
|
651
|
+
/* WHICH SYSTEM PROMPT THE AGENT RUNS ON — the base, before anything this turn composes.
|
|
652
|
+
*
|
|
653
|
+
* intentic — Intentic's own prompt, tuned for this harness (intentic-prompt.ts). The default.
|
|
654
|
+
* claude — Claude Code's preset, as shipped in the CLI this sandbox runs. Not a copy stored here, so
|
|
655
|
+
* picking it tracks whatever the installed CLI's prompt is rather than freezing at a snapshot.
|
|
656
|
+
* custom — `systemPrompt` below, and nothing else at all.
|
|
657
|
+
*
|
|
658
|
+
* The first two are peers: both get the harness's own guidance appended (the AskUserQuestion/plan blocks
|
|
659
|
+
* the chat's cards need, the checklist guidance behind the todo panel, the browser-tool guidance), plus the
|
|
660
|
+
* delegation note and the terse steer. `custom` is the one that does not, by the owner's explicit choice —
|
|
661
|
+
* see the field below. */
|
|
662
|
+
systemPromptMode: SystemPromptModeSchema.default("intentic"),
|
|
663
|
+
/* The owner's own prompt, used only when `systemPromptMode` is "custom". Then it is the ENTIRE system
|
|
664
|
+
* prompt: both built-in bases are gone and so is everything the daemon would otherwise append — the widget
|
|
665
|
+
* guidance the chat's cards are driven by, and the terse-output steer (whose toggle goes inert). That is
|
|
666
|
+
* the price of total control, and the UI states it at the moment of the edit rather than letting the
|
|
667
|
+
* widgets go quietly dark. Only the cross-provider delegation note survives, because it has a home outside
|
|
668
|
+
* the system prompt already (the user-message preamble stableSystemPrompt puts it in).
|
|
669
|
+
*
|
|
670
|
+
* Cap is roomy — the bases it stands in for are ~6.8k characters — but finite, because every turn pays it. */
|
|
671
|
+
systemPrompt: z.string().max(20000).default(""),
|
|
475
672
|
iqSearch: z.boolean().default(false),
|
|
476
673
|
outputCleaners: z.string().default("off"),
|
|
477
674
|
outputHoldout: z.number().min(0).max(1).default(0),
|
|
478
675
|
filterBackend: z.enum(["native", "rtk"]).default("native"),
|
|
676
|
+
/* The model behind the one-click helpers that are not a conversation — today the commit box's autofill.
|
|
677
|
+
* `${provider}:${modelId}`, or EMPTY for Auto, which is the default and the interesting case: Auto is
|
|
678
|
+
* resolved from whatever accounts are connected at the moment it is read (resolveQuickModel), so it can
|
|
679
|
+
* never name a provider this sandbox has no credential for and it improves by itself when one is added.
|
|
680
|
+
* Storing a resolved id here instead would go stale exactly like a pinned model does. */
|
|
681
|
+
quickModel: z.string().default(""),
|
|
479
682
|
// How long a finished agent stays on the board before it is archived automatically (days; 0 ⇒ never).
|
|
480
683
|
// Unlike every other flag here this one defaults ON, because the lane it governs is the board's only
|
|
481
684
|
// terminal state: without a sweep the Finished lane grows for the life of the sandbox, and each card it
|
|
482
685
|
// holds is a live worktree checkout, not just a row.
|
|
483
686
|
agentRetentionDays: z.number().min(0).max(365).default(3),
|
|
687
|
+
/* Land a clean turn's delta into the main tree automatically at completion — the Claude Code review model,
|
|
688
|
+
* and the historical behaviour, so it defaults ON (flipping the default would silently change every
|
|
689
|
+
* existing sandbox). OFF holds finished work on the agent's branch instead: the card reads "Ready to
|
|
690
|
+
* land" and the user lands it deliberately, from the review panel or the card. Sandbox-wide because
|
|
691
|
+
* automation-opened agents (Discord, webhooks, email) finish turns with no browser in the room — a
|
|
692
|
+
* browser-held preference could not govern them. Per-agent override: AgentSummarySchema.autoLand. */
|
|
693
|
+
autoLand: z.boolean().default(true),
|
|
694
|
+
// When a turn dies on the Claude subscription's usage limit, re-run it automatically once the limit
|
|
695
|
+
// window resets (a minute after, so a skewed clock can't retry into the same closed window). Off by
|
|
696
|
+
// default: an unattended retry spends the fresh window without the user in the room, so the daemon
|
|
697
|
+
// records every limit-hit either way and the chat OFFERS the toggle at the moment it would have helped —
|
|
698
|
+
// enabling it then still resumes the turn that just bounced.
|
|
699
|
+
autoResumeOnLimit: z.boolean().default(false),
|
|
484
700
|
});
|
|
485
701
|
export type SandboxSettings = z.infer<typeof SandboxSettingsSchema>;
|
|
486
702
|
|
|
703
|
+
// One of the two built-in bases, as text: Intentic's own prompt, or Claude Code's preset read out of the CLI
|
|
704
|
+
// this sandbox runs (preset-prompt.ts captures it rather than storing a transcription). What the settings page
|
|
705
|
+
// shows behind "View" and drops into the editor behind "Edit a copy".
|
|
706
|
+
//
|
|
707
|
+
// `version` is the CLI build a captured preset came from, so the UI can say WHICH text the user is looking at:
|
|
708
|
+
// a custom prompt forked from an older build is a snapshot, and the version is the only honest way to tell.
|
|
709
|
+
// Empty for Intentic's prompt, which ships with the app and has no version of its own to report.
|
|
710
|
+
export const BuiltinPromptTextSchema = z.object({ text: z.string(), version: z.string() });
|
|
711
|
+
export type BuiltinPromptText = z.infer<typeof BuiltinPromptTextSchema>;
|
|
712
|
+
|
|
487
713
|
// ---- output-cleaner savings report (rtk-`gain`-style) ----
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
714
|
+
// Whichever cleaner is ACTUALLY compressing output owns the numbers, so the report is read from that backend's
|
|
715
|
+
// own ledger: "native" aggregates historyRoot/logs/filter-stats.jsonl (one row per agent Bash command, written
|
|
716
|
+
// by agent-output-filter), "rtk" reads rtk's own gain ledger. Reading one ledger regardless of backend is how
|
|
717
|
+
// this card went stale: under rtk the native filter is switched off, nothing appends, and the last numbers
|
|
718
|
+
// written — a test run's, as it happened — sat on the card looking live.
|
|
719
|
+
//
|
|
720
|
+
// `perCleaner` attributes which cleaner ids fired across commands; `holdout` is the measured control (commands
|
|
721
|
+
// the holdout bypassed) vs the cleaned population — a real saved-% rather than an estimate; `gaps` are
|
|
722
|
+
// high-volume commands that matched no cleaner (the next handler to write). All three are native-only: rtk
|
|
723
|
+
// reports totals, not per-command attribution, so they arrive empty under that backend. Empty/zeroed when no
|
|
724
|
+
// commands have run yet.
|
|
492
725
|
export const CleanerSavingsSchema = z.object({
|
|
726
|
+
// Which backend's ledger these numbers came from — shown on the card, because a number without its source
|
|
727
|
+
// cannot be told apart from a stale one. Defaulted for the same daemon-older-than-browser seam as settings.
|
|
728
|
+
source: z.enum(["native", "rtk"]).default("native"),
|
|
729
|
+
// When that ledger last recorded a command (epoch ms), so the card can show its age instead of implying
|
|
730
|
+
// freshness it doesn't have. Absent when the ledger has never been written or its age can't be read.
|
|
731
|
+
updatedAt: z.number().optional(),
|
|
493
732
|
commands: z.number(),
|
|
494
733
|
rawTokens: z.number(),
|
|
495
734
|
emittedTokens: z.number(),
|
|
@@ -544,6 +783,24 @@ export const GitStatusSchema = z.object({ branch: z.string(), dirty: z.boolean()
|
|
|
544
783
|
export const GitFilesSchema = z.object({ files: z.array(z.string()) });
|
|
545
784
|
export const GitFileSchema = z.object({ path: z.string(), content: z.string() });
|
|
546
785
|
export const CommitResultSchema = z.object({ committed: z.boolean() });
|
|
786
|
+
|
|
787
|
+
/* AI-drafted commit message. Workspace-wide, not per repo, because the commit box's target IS a set of repos
|
|
788
|
+
* sharing one message — so the draft has to see every one of their diffs to describe what the commit actually
|
|
789
|
+
* records. `repos` and `all` mirror the panel's own commit target exactly: `all` reads the WORKTREE (what
|
|
790
|
+
* "Commit all" would sweep), absent reads the INDEX (what a bare commit records). Getting that wrong would
|
|
791
|
+
* describe changes the commit isn't going to contain. */
|
|
792
|
+
export const CommitMessageDraftSchema = z.object({
|
|
793
|
+
repos: z.array(z.string().min(1)).min(1).max(50),
|
|
794
|
+
all: z.boolean().optional(),
|
|
795
|
+
});
|
|
796
|
+
// The draft plus WHICH model wrote it, so the surface can name it rather than claiming an anonymous "AI" —
|
|
797
|
+
// that name is also the only place the resolved quick model is visible before anyone opens settings.
|
|
798
|
+
export const CommitMessageSchema = z.object({
|
|
799
|
+
message: z.string(),
|
|
800
|
+
provider: z.string(),
|
|
801
|
+
model: z.string(),
|
|
802
|
+
});
|
|
803
|
+
export type CommitMessageDraft = z.infer<typeof CommitMessageSchema>;
|
|
547
804
|
// One change to a file — an uncommitted working-tree change (status vs HEAD, untracked included), an agent
|
|
548
805
|
// worktree's delta vs its base, or a file in a commit. `additions`/`deletions` are the numstat line counts,
|
|
549
806
|
// undefined for a binary file (git reports "-"/"-") or an untracked file (no HEAD blob to diff against).
|
|
@@ -623,6 +880,11 @@ export const RepoChangesSchema = z.object({
|
|
|
623
880
|
// additions/deletions describe the diff it is listed under, never a conflation of the two.
|
|
624
881
|
staged: z.array(GitChangeSchema),
|
|
625
882
|
unstaged: z.array(GitChangeSchema),
|
|
883
|
+
// How many changes were CUT from the two sides above (conflicts are never cut). A cloned monorepo or a
|
|
884
|
+
// mass delete carries six-figure change lists — a payload no panel can render and no browser should hold —
|
|
885
|
+
// so past the daemon's per-repo budget the lists arrive truncated and this carries the dropped count, which
|
|
886
|
+
// the panel adds to its badges and states under the group. Absent ⇒ the lists are complete.
|
|
887
|
+
truncated: z.number().optional(),
|
|
626
888
|
// Where this repo stands against its remote; `ahead`/`behind` are 0 with no remote or no upstream.
|
|
627
889
|
remote: GitRemoteStateSchema.optional(),
|
|
628
890
|
// WHICH AGENT PUT IT THERE: repo-relative path → the agent ids that landed it, newest land first. Keyed by
|
|
@@ -630,7 +892,7 @@ export const RepoChangesSchema = z.object({
|
|
|
630
892
|
// edited again) and its origin is the same fact for both. Only agents can appear here — a main-tree turn,
|
|
631
893
|
// a terminal edit and your own typing never pass through land, so they are simply absent (see
|
|
632
894
|
// agents/origins.ts), which is why the panel badges an agent and says nothing at all for anyone else.
|
|
633
|
-
// Ids, not titles:
|
|
895
|
+
// Ids, not titles: the identity for every id named here rides the response once, in `originAgents`.
|
|
634
896
|
origins: z.record(z.string(), z.array(z.string())).optional(),
|
|
635
897
|
// Why the repo could not be scanned at all, condensed to git's own one-line reason ("fatal: bad object HEAD").
|
|
636
898
|
// A repo left torn by a canceled or failed upload used to be dropped from the response entirely, so it just
|
|
@@ -638,9 +900,31 @@ export const RepoChangesSchema = z.object({
|
|
|
638
900
|
error: z.string().optional(),
|
|
639
901
|
});
|
|
640
902
|
export type RepoChanges = z.infer<typeof RepoChangesSchema>;
|
|
903
|
+
|
|
904
|
+
// WHO AN ORIGIN ID IS — the display identity of one agent named in `origins`, carried BY THE RESPONSE rather
|
|
905
|
+
// than looked up in the client's fleet roster. The roster is the LIVE board and deliberately drops archived
|
|
906
|
+
// agents (AgentsRegistry.list), while a landing outlives the agent that made it: archiving a finished agent
|
|
907
|
+
// does not commit its lines, so the very common case — land, archive the card, review at leisure — is exactly
|
|
908
|
+
// the one a roster lookup cannot answer, and the panel fell back to "Agent 1a2b3c" with a generic icon for it.
|
|
909
|
+
// The daemon reads attribution and identity from the same registry in the same pass, so it is the one place
|
|
910
|
+
// they cannot disagree. Per response, not per repo: one agent commonly lands into several.
|
|
911
|
+
export const OriginAgentSchema = z.object({
|
|
912
|
+
// Absent for an entry that never got a title (a turn that failed before one was derived).
|
|
913
|
+
title: z.string().optional(),
|
|
914
|
+
provider: AgentProviderSchema,
|
|
915
|
+
});
|
|
916
|
+
export type OriginAgent = z.infer<typeof OriginAgentSchema>;
|
|
917
|
+
|
|
641
918
|
// The aggregated review set across every repo (root + every discovered repo); a repo appears when it has changes,
|
|
642
919
|
// when it is out of sync with its remote, or when it failed to scan.
|
|
643
|
-
export const GitChangesSchema = z.object({
|
|
920
|
+
export const GitChangesSchema = z.object({
|
|
921
|
+
repos: z.array(RepoChangesSchema),
|
|
922
|
+
// Keyed by agent id; covers every id any repo's `origins` names, and only those. Absent when nothing in
|
|
923
|
+
// the review is attributable. An id can still be missing from it — the retention sweep can retire an
|
|
924
|
+
// entry whose landed lines are somehow still uncommitted — and the panel keeps its id-shaped fallback for
|
|
925
|
+
// exactly that, rather than dropping the chip and re-attributing the file to the user.
|
|
926
|
+
originAgents: z.record(z.string(), OriginAgentSchema).optional(),
|
|
927
|
+
});
|
|
644
928
|
export type GitChanges = z.infer<typeof GitChangesSchema>;
|
|
645
929
|
|
|
646
930
|
// One file an agent touched, plus whether that change is ALREADY in the main tree. The review lists the
|
|
@@ -661,7 +945,15 @@ export const AgentRepoChangesSchema = z.object({
|
|
|
661
945
|
changes: z.array(AgentChangeSchema),
|
|
662
946
|
});
|
|
663
947
|
export type AgentRepoChanges = z.infer<typeof AgentRepoChangesSchema>;
|
|
664
|
-
|
|
948
|
+
/* The review, plus WHY the last land refused — because a conflict is discovered by the daemon (a clean turn
|
|
949
|
+
* auto-lands the moment it finishes) and acted on in the browser, possibly hours later, on a surface the user
|
|
950
|
+
* reaches by clicking the card's "Resolve conflict". Carrying the report only in the land RESPONSE meant the
|
|
951
|
+
* one path that opens the review already knowing there is a conflict was the one path that could not show it:
|
|
952
|
+
* the panel opened with an empty report, no explanation, and no merge affordance — a dead end at the exact
|
|
953
|
+
* moment the UI had promised something to resolve. It rides the review because that is the surface that
|
|
954
|
+
* resolves it, and it refreshes with it: every land invalidates this query, so the report is never staler
|
|
955
|
+
* than the last attempt. */
|
|
956
|
+
export const AgentChangesSchema = z.object({ repos: z.array(AgentRepoChangesSchema), conflicts: z.array(LandConflictSchema).optional() });
|
|
665
957
|
export type AgentChanges = z.infer<typeof AgentChangesSchema>;
|
|
666
958
|
|
|
667
959
|
// ---- git history graph (the "Git Graph" view over a repo's real commits) ----
|
|
@@ -767,11 +1059,11 @@ export const WorkspaceTreeEntrySchema = z.object({
|
|
|
767
1059
|
path: z.string(),
|
|
768
1060
|
type: z.enum(["file", "dir"]),
|
|
769
1061
|
size: z.number().optional(),
|
|
770
|
-
// Set on a dir whose child list was cut short by the entry cap — some of its items aren't in `children`.
|
|
771
|
-
truncated: z.boolean().optional(),
|
|
772
1062
|
// Ignored-by-tooling (node_modules, .git, .gitignore'd paths, browser profiles): the client grays the row.
|
|
773
|
-
// An ignored DIR is listed without `children` — the client lazy-loads it via /workspace/children on expand.
|
|
774
1063
|
ignored: z.boolean().optional(),
|
|
1064
|
+
// A DIR without `children` was listed but not descended into — because it's ignored, or because the walk's
|
|
1065
|
+
// breadth-first budget stopped above it. Either way the client lazy-loads it via /workspace/children on
|
|
1066
|
+
// expand, so "not loaded yet" and "empty directory" (`children: []`) stay distinguishable.
|
|
775
1067
|
get children() {
|
|
776
1068
|
return z.array(WorkspaceTreeEntrySchema).optional();
|
|
777
1069
|
},
|
|
@@ -780,21 +1072,27 @@ export type WorkspaceTreeEntry = z.infer<typeof WorkspaceTreeEntrySchema>;
|
|
|
780
1072
|
export const WorkspaceTreeSchema = z.object({
|
|
781
1073
|
root: z.string(),
|
|
782
1074
|
tree: z.array(WorkspaceTreeEntrySchema),
|
|
783
|
-
//
|
|
784
|
-
|
|
1075
|
+
// How many of the ROOT's own entries the budget cut (0 = complete); per-dir cuts are counted on each dir entry.
|
|
1076
|
+
hidden: z.number(),
|
|
785
1077
|
});
|
|
786
1078
|
export type WorkspaceTree = z.infer<typeof WorkspaceTreeSchema>;
|
|
787
|
-
// Lazy-load one directory's children — for
|
|
788
|
-
//
|
|
789
|
-
//
|
|
1079
|
+
// Lazy-load one directory's children — for a dir the tree walk listed but didn't descend into. Child dirs again
|
|
1080
|
+
// carry no `children`, so they lazy-load on their own expand. `hidden` = how many entries the cap cut (0 = all
|
|
1081
|
+
// listed).
|
|
790
1082
|
export const WorkspaceChildrenQuerySchema = z.object({ path: z.string().min(1) });
|
|
791
1083
|
export const WorkspaceChildrenSchema = z.object({
|
|
792
1084
|
entries: z.array(WorkspaceTreeEntrySchema),
|
|
793
|
-
|
|
1085
|
+
hidden: z.number(),
|
|
794
1086
|
});
|
|
795
1087
|
export type WorkspaceChildren = z.infer<typeof WorkspaceChildrenSchema>;
|
|
796
1088
|
export const WorkspaceFileQuerySchema = z.object({ path: z.string().min(1) });
|
|
797
1089
|
export const WorkspaceFileSchema = z.object({ path: z.string(), content: z.string() });
|
|
1090
|
+
// Resolve a file reference an agent (or a compiler, or a terminal) NAMED to the workspace path it means. Prose
|
|
1091
|
+
// paths are routinely partial — a model that has been discussing `_apps/web/src` writes
|
|
1092
|
+
// `pages/workspace/Foo.vue` — so a clickable mention has to be matched as a path SUFFIX against the real tree,
|
|
1093
|
+
// not read as root-relative. `path` is absent when nothing in the workspace ends in that reference.
|
|
1094
|
+
export const WorkspaceResolveQuerySchema = z.object({ path: z.string().min(1).max(512) });
|
|
1095
|
+
export const WorkspaceResolveSchema = z.object({ path: z.string().optional() });
|
|
798
1096
|
// Direct file management over the /work tree (delete / new folder / rename+move / copy). Byte writes + the
|
|
799
1097
|
// editor's text save go through the plain POST /workspace/upload route (a body doesn't fit oRPC), not here.
|
|
800
1098
|
export const WorkspaceDirSchema = z.object({ path: z.string().min(1) });
|
|
@@ -865,6 +1163,58 @@ export const WorkspaceSearchResultSchema = z.object({
|
|
|
865
1163
|
});
|
|
866
1164
|
export type WorkspaceSearchResult = z.infer<typeof WorkspaceSearchResultSchema>;
|
|
867
1165
|
|
|
1166
|
+
// ---- codebase health: one repository's structure and risk, in numbers ----
|
|
1167
|
+
|
|
1168
|
+
// The repo-level companion to the management panel and the git-history graph: what the same resident engine's
|
|
1169
|
+
// `hotspots` (churn × complexity) and `map` (PageRank over the import graph) verbs rank, as figures a panel can
|
|
1170
|
+
// plot instead of lines a terminal prints.
|
|
1171
|
+
//
|
|
1172
|
+
// Every field is a COUNT that can be recounted in the files themselves — commits, branch points, exported
|
|
1173
|
+
// symbols. Deliberately no composite "maintainability grade": those aren't comparable across projects and can't
|
|
1174
|
+
// be checked, and a repo-health surface that launders counts into a letter is worse than none.
|
|
1175
|
+
// How many hotspot files and key modules a report carries when the caller names no limit. A leaderboard, not an
|
|
1176
|
+
// inventory: past a screenful the ranking stops being the point, and the reader should be reading the files.
|
|
1177
|
+
export const HEALTH_LIMIT = 20;
|
|
1178
|
+
export const WorkspaceHealthQuerySchema = z.object({
|
|
1179
|
+
// "root" (the /work repo) or a nested repo's root-relative dir — the same {repo} ids the git routes take.
|
|
1180
|
+
repo: z.string().min(1),
|
|
1181
|
+
// Churn window (2d, 12h, 1w, 3m). Absent = all of history, which is what a hotspot ranking wants by default.
|
|
1182
|
+
since: z.string().max(16).optional(),
|
|
1183
|
+
limit: z.coerce.number().int().positive().max(200).optional(),
|
|
1184
|
+
});
|
|
1185
|
+
// One file that is BOTH churning and tangled. `score` is the product the ranking sorts by — carried explicitly
|
|
1186
|
+
// so the panel plots the number it ranks by rather than recomputing it.
|
|
1187
|
+
export const WorkspaceHotspotSchema = z.object({
|
|
1188
|
+
path: z.string(),
|
|
1189
|
+
commits: z.number(),
|
|
1190
|
+
adds: z.number(),
|
|
1191
|
+
dels: z.number(),
|
|
1192
|
+
complexity: z.number(),
|
|
1193
|
+
score: z.number(),
|
|
1194
|
+
// Epoch ms of the latest commit touching the file, within the window.
|
|
1195
|
+
latestMs: z.number(),
|
|
1196
|
+
});
|
|
1197
|
+
export type WorkspaceHotspot = z.infer<typeof WorkspaceHotspotSchema>;
|
|
1198
|
+
// A file of the import graph's ranked skeleton — order IS the rank, so no rank number rides along.
|
|
1199
|
+
export const WorkspaceKeyModuleSchema = z.object({ path: z.string(), exports: z.number() });
|
|
1200
|
+
export type WorkspaceKeyModule = z.infer<typeof WorkspaceKeyModuleSchema>;
|
|
1201
|
+
export const WorkspaceHealthSchema = z.object({
|
|
1202
|
+
repo: z.string(),
|
|
1203
|
+
totals: z.object({
|
|
1204
|
+
files: z.number(),
|
|
1205
|
+
symbols: z.number(),
|
|
1206
|
+
// Summed branch points across the scoped files.
|
|
1207
|
+
complexity: z.number(),
|
|
1208
|
+
// How many files qualify as hotspots at all — the lists below are capped, this is not.
|
|
1209
|
+
hotspots: z.number(),
|
|
1210
|
+
}),
|
|
1211
|
+
hotspots: z.array(WorkspaceHotspotSchema),
|
|
1212
|
+
modules: z.array(WorkspaceKeyModuleSchema),
|
|
1213
|
+
// Same index-freshness signal the search route reports: a panel drawn off a half-built index says so.
|
|
1214
|
+
freshness: WorkspaceSearchFreshnessSchema,
|
|
1215
|
+
});
|
|
1216
|
+
export type WorkspaceHealth = z.infer<typeof WorkspaceHealthSchema>;
|
|
1217
|
+
|
|
868
1218
|
// ---- workspace setup (dependency readiness) ----
|
|
869
1219
|
|
|
870
1220
|
// One project under /work and whether its dependencies are actually installed. A drop omits node_modules/.venv
|
|
@@ -940,12 +1290,13 @@ export const TemplatesListSchema = z.object({ templates: z.array(TemplateSummary
|
|
|
940
1290
|
export type TemplatesList = z.infer<typeof TemplatesListSchema>;
|
|
941
1291
|
|
|
942
1292
|
// One app instance currently in a monorepo, with its own preview dev server + live status (started/stopped
|
|
943
|
-
// from the apps extension). `app` is the user-chosen instance name (the _apps/ dir); `
|
|
944
|
-
// manifest key it was
|
|
1293
|
+
// from the apps extension). `app` is the user-chosen instance name (the _apps/ dir); `kind` is what sort of
|
|
1294
|
+
// app it is — the manifest key it was scaffolded from (api/web/landing), else the framework detected from its
|
|
1295
|
+
// dependencies (astro/next/…), and absent when it was discovered purely by its `dev` script. previewUrl is
|
|
945
1296
|
// https://preview-<repo>--<app>-<sandboxId>.<zone> (absent on loopback — no zone or no connect token).
|
|
946
1297
|
export const RepoAppSchema = z.object({
|
|
947
1298
|
app: z.string(),
|
|
948
|
-
|
|
1299
|
+
kind: z.string().optional(),
|
|
949
1300
|
previewUrl: z.string().optional(),
|
|
950
1301
|
running: z.boolean(),
|
|
951
1302
|
healthy: z.boolean(),
|
|
@@ -1493,7 +1844,9 @@ export const WorkspaceEventSchema = z.object({
|
|
|
1493
1844
|
agentId: z.string(),
|
|
1494
1845
|
title: z.string().optional(),
|
|
1495
1846
|
branch: z.string(),
|
|
1496
|
-
|
|
1847
|
+
// `ready` is a clean turn whose delta was HELD on the branch (auto-land off) — for a chore, the moment
|
|
1848
|
+
// before the user's deliberate Land, which is exactly when a pre-land review wants to run.
|
|
1849
|
+
outcome: z.enum(["landed", "conflict", "ready", "idle", "error"]),
|
|
1497
1850
|
repos: z.array(z.object({ repo: z.string(), from: z.string(), dir: z.string() })),
|
|
1498
1851
|
});
|
|
1499
1852
|
export type WorkspaceEvent = z.infer<typeof WorkspaceEventSchema>;
|
|
@@ -1546,6 +1899,10 @@ export const AutomationApprovalSchema = z.object({
|
|
|
1546
1899
|
automationId: z.string(),
|
|
1547
1900
|
// The event/listener payload the wake would have carried; absent for schedule triggers.
|
|
1548
1901
|
payload: z.string().optional(),
|
|
1902
|
+
// The provenance + title the held wake would have opened its conversation with — snapshotted alongside the
|
|
1903
|
+
// payload so an approved external wake surfaces on the fleet exactly as an auto one would have.
|
|
1904
|
+
origin: AgentOriginSchema.optional(),
|
|
1905
|
+
title: z.string().optional(),
|
|
1549
1906
|
createdAt: z.number(),
|
|
1550
1907
|
});
|
|
1551
1908
|
export type AutomationApproval = z.infer<typeof AutomationApprovalSchema>;
|
|
@@ -1636,13 +1993,15 @@ export const PanelSummarySchema = z.object({
|
|
|
1636
1993
|
role: z.enum(["intent", "desired-state", "app"]).optional(),
|
|
1637
1994
|
// Content facts: deploy.config.ts (the intent ledger's day-one marker), desired-state.json (present after
|
|
1638
1995
|
// the first resolve), .intentic/ui/index.html (a sandboxed directory UI), pnpm-workspace.yaml +
|
|
1639
|
-
// turbo.json (a pnpm+turbo monorepo),
|
|
1640
|
-
// root manifest / workspace catalog)
|
|
1996
|
+
// turbo.json (a pnpm+turbo monorepo), vitest evidence (a root vitest.config.ts, or "vitest" in the
|
|
1997
|
+
// root manifest / workspace catalog), and docs/user-stories (a directory of stories an agent can test
|
|
1998
|
+
// against the running app — the one fact here that says nothing about the repo's language).
|
|
1641
1999
|
deployConfig: z.boolean(),
|
|
1642
2000
|
desiredState: z.boolean(),
|
|
1643
2001
|
directoryUi: z.boolean(),
|
|
1644
2002
|
monorepo: z.boolean(),
|
|
1645
2003
|
vitest: z.boolean(),
|
|
2004
|
+
userStories: z.boolean(),
|
|
1646
2005
|
});
|
|
1647
2006
|
export type PanelSummary = z.infer<typeof PanelSummarySchema>;
|
|
1648
2007
|
export const PanelsListSchema = z.object({ panels: z.array(PanelSummarySchema) });
|
|
@@ -1696,7 +2055,8 @@ export type PortForwardResult = z.infer<typeof PortForwardResultSchema>;
|
|
|
1696
2055
|
// is the /system/terminal WebSocket, not oRPC): `shell` = a web-* session the user opened (numbered pill),
|
|
1697
2056
|
// `panel` = a panel-* dev-server session (labeled by its panel key, started via Start; running:false =
|
|
1698
2057
|
// untracked, e.g. a finished one-shot job's lingering shell), `agent` = an agent-* session the Claude agent's
|
|
1699
|
-
// Bash commands run in (live-watchable, AI-marked in the UI
|
|
2058
|
+
// Bash commands run in (live-watchable, AI-marked in the UI; running:false once every window is a finished
|
|
2059
|
+
// command's dead pane, which is what lets the panel sweep it), `job` = a job-* session the daemon's terminal
|
|
1700
2060
|
// runner executes user-triggered flows in (capability adds, infra check), `process` = a managed background
|
|
1701
2061
|
// process riding a panel session (an extension's declared processes, dockerd) — surfaced in the panel's
|
|
1702
2062
|
// background-processes popover with read-only log views, never as a killable tab; running is the actual
|
|
@@ -1704,11 +2064,19 @@ export type PortForwardResult = z.infer<typeof PortForwardResultSchema>;
|
|
|
1704
2064
|
// declared process carries extensionId+processName, the address for its /extensions start/stop routes. The
|
|
1705
2065
|
// `{name}` kill-route param is a bare string validated in the handler (a bad name is a BAD_REQUEST) since the
|
|
1706
2066
|
// same charset gates a `tmux kill-session -t` shell-out.
|
|
2067
|
+
//
|
|
2068
|
+
// `activityAt` (epoch ms of the session's last output) and `exitCode` (the LAST window's exit status, absent
|
|
2069
|
+
// while that pane still lives) are what let a finished session be READ rather than merely listed: the panel's
|
|
2070
|
+
// Recent-terminals popover orders by the one and reports the other ("exit 1 · 1h ago"), and the daemon's
|
|
2071
|
+
// retention sweep ages sessions out by the same clock. 0 is "tmux didn't say" — treated as unknown by both,
|
|
2072
|
+
// never as 1970.
|
|
1707
2073
|
export const TerminalSessionSchema = z.object({
|
|
1708
2074
|
name: z.string(),
|
|
1709
2075
|
label: z.string().optional(),
|
|
1710
2076
|
kind: z.enum(["shell", "panel", "agent", "job", "process"]),
|
|
1711
2077
|
running: z.boolean(),
|
|
2078
|
+
activityAt: z.number(),
|
|
2079
|
+
exitCode: z.number().optional(),
|
|
1712
2080
|
extensionId: z.string().optional(),
|
|
1713
2081
|
processName: z.string().optional(),
|
|
1714
2082
|
});
|
|
@@ -1787,6 +2155,12 @@ export const InfoSchema = z.object({
|
|
|
1787
2155
|
updateAvailable: z.boolean().optional(),
|
|
1788
2156
|
});
|
|
1789
2157
|
|
|
2158
|
+
// A daemon-minted session (system.session): the steady-state browser credential, exchanged for a verified
|
|
2159
|
+
// Google ID token so Google UI is a sign-in moment instead of an hourly renewal. `expiresAt` is epoch ms —
|
|
2160
|
+
// the browser renews ahead of it without parsing the token; `email` is who the daemon verified.
|
|
2161
|
+
export const DaemonSessionSchema = z.object({ token: z.string(), expiresAt: z.number(), email: z.string() });
|
|
2162
|
+
export type DaemonSession = z.infer<typeof DaemonSessionSchema>;
|
|
2163
|
+
|
|
1790
2164
|
// Intentic-provided host SSH tunnel: minting it needs intentic's PLATFORM Cloudflare account, so the daemon
|
|
1791
2165
|
// can't do it directly — it relays to the platform authenticated by the connect token (the announce pattern).
|
|
1792
2166
|
// The panel embeds the returned connector token + hostname in its connect-host one-liner.
|
|
@@ -1967,6 +2341,45 @@ export const LogReadSchema = z.object({
|
|
|
1967
2341
|
});
|
|
1968
2342
|
export type LogRead = z.infer<typeof LogReadSchema>;
|
|
1969
2343
|
|
|
2344
|
+
// ---- memory: the agent's persistent memory notes (.intentic/claude/projects/<project>/memory) ----
|
|
2345
|
+
// The markdown files the agent curates across sessions — MEMORY.md (the index) plus one file per fact. They
|
|
2346
|
+
// live under the workspace's .intentic/claude control plane (symlinked from ~/.claude/projects), which the
|
|
2347
|
+
// generic /workspace file API deliberately refuses (session transcripts and provider state share that tree),
|
|
2348
|
+
// so these purpose-built routes are the only browser surface — scoped to the memory dirs and nothing else.
|
|
2349
|
+
|
|
2350
|
+
export const MemoryFileEntrySchema = z.object({
|
|
2351
|
+
// The project slug the memory belongs to (one dir per agent cwd, e.g. "-history-gits-root").
|
|
2352
|
+
project: z.string(),
|
|
2353
|
+
// Path relative to that project's memory dir, e.g. "MEMORY.md" or "team-conventions.md".
|
|
2354
|
+
name: z.string(),
|
|
2355
|
+
sizeBytes: z.number(),
|
|
2356
|
+
// Epoch ms mtime.
|
|
2357
|
+
modifiedAt: z.number(),
|
|
2358
|
+
});
|
|
2359
|
+
export type MemoryFileEntry = z.infer<typeof MemoryFileEntrySchema>;
|
|
2360
|
+
export const MemoryListSchema = z.object({ files: z.array(MemoryFileEntrySchema) });
|
|
2361
|
+
|
|
2362
|
+
// `project` + `name` ride the query (names may contain slashes, which don't fit a path segment).
|
|
2363
|
+
export const MemoryFileQuerySchema = z.object({
|
|
2364
|
+
project: z.string().min(1),
|
|
2365
|
+
name: z.string().min(1),
|
|
2366
|
+
});
|
|
2367
|
+
export const MemoryFileSchema = z.object({
|
|
2368
|
+
project: z.string(),
|
|
2369
|
+
name: z.string(),
|
|
2370
|
+
content: z.string(),
|
|
2371
|
+
sizeBytes: z.number(),
|
|
2372
|
+
modifiedAt: z.number(),
|
|
2373
|
+
});
|
|
2374
|
+
export type MemoryFile = z.infer<typeof MemoryFileSchema>;
|
|
2375
|
+
|
|
2376
|
+
// Memory notes are small by construction (one fact per file); the cap guards the route, not real usage.
|
|
2377
|
+
export const MemoryWriteSchema = z.object({
|
|
2378
|
+
project: z.string().min(1),
|
|
2379
|
+
name: z.string().min(1),
|
|
2380
|
+
content: z.string().max(1_048_576),
|
|
2381
|
+
});
|
|
2382
|
+
|
|
1970
2383
|
// A tab's self-report of what it is looking at, keyed by its /events connection's clientId. Full replace,
|
|
1971
2384
|
// not a merge — an absent field means "cleared", so a tab leaving a file drops the path with the same report.
|
|
1972
2385
|
export const PresenceReportSchema = z.object({
|
|
@@ -1998,7 +2411,10 @@ export type PushSubscription = z.infer<typeof PushSubscriptionSchema>;
|
|
|
1998
2411
|
|
|
1999
2412
|
// What the service worker renders. `url` is the in-app route the notification opens (the click handler
|
|
2000
2413
|
// focuses an existing tab there rather than spawning a new one); `tag` collapses repeats — a second
|
|
2001
|
-
// "waiting on you" for the same conversation REPLACES the first instead of stacking.
|
|
2414
|
+
// "waiting on you" for the same conversation REPLACES the first instead of stacking. Push payloads are
|
|
2415
|
+
// capped by the push services themselves (~4KB after encryption), which is why nothing here carries a
|
|
2416
|
+
// transcript or a diff — the notification is a pointer back into the workspace, not a delivery mechanism
|
|
2417
|
+
// for content.
|
|
2002
2418
|
export const PushNotificationSchema = z.object({
|
|
2003
2419
|
title: z.string().min(1),
|
|
2004
2420
|
body: z.string(),
|
|
@@ -2015,5 +2431,12 @@ export type PushNotification = z.infer<typeof PushNotificationSchema>;
|
|
|
2015
2431
|
// granted permission with no server-side row would notify nothing).
|
|
2016
2432
|
export const PushConfigSchema = z.object({ publicKey: z.string(), subscribed: z.boolean() });
|
|
2017
2433
|
export const PushEndpointSchema = z.object({ endpoint: z.string().url() });
|
|
2018
|
-
// `endpoint`
|
|
2434
|
+
// The optional `endpoint` says WHICH browser is asking; without it `subscribed` could only speak for the
|
|
2435
|
+
// sandbox as a whole, which is never the question the settings toggle needs answered.
|
|
2019
2436
|
export const PushConfigQuerySchema = z.object({ endpoint: z.string().url().optional() });
|
|
2437
|
+
|
|
2438
|
+
// What a test send actually achieved. `{ ok: true }` would be a lie the one place it matters most: the button
|
|
2439
|
+
// exists to prove a chain the user cannot inspect, so "the daemon accepted the request" is not the answer to
|
|
2440
|
+
// the question being asked. A count separates "your OS swallowed it" from "nothing was sent at all".
|
|
2441
|
+
export const PushTestSchema = z.object({ delivered: z.number().int().nonnegative() });
|
|
2442
|
+
export type PushTest = z.infer<typeof PushTestSchema>;
|