@intentic/sandbox-contract 1.225.0 → 1.226.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-catalog.d.ts +2 -0
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +16 -0
- package/dist/agent-catalog.js.map +1 -1
- package/dist/contracts/activity.contract.d.ts.map +1 -1
- package/dist/contracts/activity.contract.js +17 -2
- package/dist/contracts/activity.contract.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +71 -8
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +1 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +164 -19
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.js +78 -9
- package/dist/contracts/automations.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +98 -11
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/chores.contract.d.ts.map +1 -1
- package/dist/contracts/chores.contract.js +26 -3
- package/dist/contracts/chores.contract.js.map +1 -1
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.js +52 -6
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.js +44 -5
- package/dist/contracts/claude.contract.js.map +1 -1
- package/dist/contracts/drafts.contract.d.ts.map +1 -1
- package/dist/contracts/drafts.contract.js +26 -3
- package/dist/contracts/drafts.contract.js.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.js +17 -2
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/exit.contract.d.ts.map +1 -1
- package/dist/contracts/exit.contract.js +62 -7
- package/dist/contracts/exit.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.js +124 -15
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +384 -43
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/grok.contract.js +25 -3
- package/dist/contracts/grok.contract.js.map +1 -1
- package/dist/contracts/history.contract.d.ts.map +1 -1
- package/dist/contracts/history.contract.js +35 -4
- package/dist/contracts/history.contract.js.map +1 -1
- package/dist/contracts/intentic.contract.d.ts.map +1 -1
- package/dist/contracts/intentic.contract.js +25 -3
- package/dist/contracts/intentic.contract.js.map +1 -1
- package/dist/contracts/inventory.contract.d.ts.map +1 -1
- package/dist/contracts/inventory.contract.js +26 -3
- package/dist/contracts/inventory.contract.js.map +1 -1
- package/dist/contracts/logs.contract.d.ts.map +1 -1
- package/dist/contracts/logs.contract.js +17 -2
- package/dist/contracts/logs.contract.js.map +1 -1
- package/dist/contracts/loops.contract.d.ts.map +1 -1
- package/dist/contracts/loops.contract.js +52 -6
- package/dist/contracts/loops.contract.js.map +1 -1
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/panels.contract.js +26 -3
- package/dist/contracts/panels.contract.js.map +1 -1
- package/dist/contracts/personas.contract.d.ts.map +1 -1
- package/dist/contracts/personas.contract.js +71 -8
- package/dist/contracts/personas.contract.js.map +1 -1
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/ports.contract.js +26 -3
- package/dist/contracts/ports.contract.js.map +1 -1
- package/dist/contracts/prepush.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.js +24 -3
- package/dist/contracts/prepush.contract.js.map +1 -1
- package/dist/contracts/providers.contract.d.ts.map +1 -1
- package/dist/contracts/providers.contract.js +9 -1
- package/dist/contracts/providers.contract.js.map +1 -1
- package/dist/contracts/public.contract.d.ts.map +1 -1
- package/dist/contracts/public.contract.js +26 -3
- package/dist/contracts/public.contract.js.map +1 -1
- package/dist/contracts/push.contract.d.ts.map +1 -1
- package/dist/contracts/push.contract.js +35 -4
- package/dist/contracts/push.contract.js.map +1 -1
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +43 -5
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.js +15 -2
- package/dist/contracts/sessions.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +43 -5
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/share.contract.d.ts.map +1 -1
- package/dist/contracts/share.contract.js +35 -4
- package/dist/contracts/share.contract.js.map +1 -1
- package/dist/contracts/skills.contract.d.ts.map +1 -1
- package/dist/contracts/skills.contract.js +35 -4
- package/dist/contracts/skills.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +107 -14
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.js +29 -4
- package/dist/contracts/translator.contract.js.map +1 -1
- package/dist/contracts/usage.contract.d.ts.map +1 -1
- package/dist/contracts/usage.contract.js +9 -1
- package/dist/contracts/usage.contract.js.map +1 -1
- package/dist/contracts/vpn.contract.d.ts.map +1 -1
- package/dist/contracts/vpn.contract.js +32 -4
- package/dist/contracts/vpn.contract.js.map +1 -1
- package/dist/contracts/workflows.contract.d.ts.map +1 -1
- package/dist/contracts/workflows.contract.js +70 -8
- package/dist/contracts/workflows.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +216 -25
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +179 -88
- package/dist/events.js.map +1 -1
- package/dist/history-state.d.ts.map +1 -1
- package/dist/history-state.js +1 -0
- package/dist/history-state.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/schemas.d.ts +1 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +2856 -1276
- package/dist/schemas.js.map +1 -1
- package/package.json +4 -4
- package/src/agent-catalog.test.ts +48 -3
- package/src/agent-catalog.ts +88 -0
- package/src/capability-ledger.test.ts +21 -2
- package/src/contracts/activity.contract.ts +18 -2
- package/src/contracts/agent.contract.ts +78 -8
- package/src/contracts/agents.contract.ts +180 -19
- package/src/contracts/automations.contract.ts +80 -9
- package/src/contracts/capabilities.contract.ts +106 -11
- package/src/contracts/chores.contract.ts +29 -3
- package/src/contracts/ci.contract.ts +55 -6
- package/src/contracts/claude.contract.ts +47 -5
- package/src/contracts/drafts.contract.ts +26 -3
- package/src/contracts/endpoints.contract.ts +19 -2
- package/src/contracts/exit.contract.ts +69 -7
- package/src/contracts/extensions.contract.ts +134 -15
- package/src/contracts/git.contract.ts +421 -43
- package/src/contracts/grok.contract.ts +26 -3
- package/src/contracts/history.contract.ts +37 -4
- package/src/contracts/intentic.contract.ts +28 -3
- package/src/contracts/inventory.contract.ts +27 -3
- package/src/contracts/logs.contract.ts +18 -2
- package/src/contracts/loops.contract.ts +58 -6
- package/src/contracts/panels.contract.ts +26 -3
- package/src/contracts/personas.contract.ts +77 -8
- package/src/contracts/ports.contract.ts +27 -3
- package/src/contracts/prepush.contract.ts +26 -3
- package/src/contracts/providers.contract.ts +10 -1
- package/src/contracts/public.contract.ts +29 -3
- package/src/contracts/push.contract.ts +37 -4
- package/src/contracts/secrets.contract.ts +46 -5
- package/src/contracts/sessions.contract.ts +16 -2
- package/src/contracts/settings.contract.ts +46 -5
- package/src/contracts/share.contract.ts +37 -4
- package/src/contracts/skills.contract.ts +39 -4
- package/src/contracts/system.contract.ts +119 -14
- package/src/contracts/translator.contract.ts +32 -4
- package/src/contracts/usage.contract.ts +10 -1
- package/src/contracts/vpn.contract.ts +35 -4
- package/src/contracts/workflows.contract.ts +77 -8
- package/src/contracts/workspace.contract.ts +231 -25
- package/src/events.ts +207 -88
- package/src/history-state.ts +6 -0
- package/src/schemas.ts +3506 -1260
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentic/sandbox-contract",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.226.1",
|
|
4
4
|
"description": "oRPC wire contract for the intentic sandbox daemon, shared by the daemon and its browser client",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -89,9 +89,9 @@
|
|
|
89
89
|
"@orpc/contract": "1.14.13",
|
|
90
90
|
"tslib": "2.8.1",
|
|
91
91
|
"zod": "4.4.3",
|
|
92
|
-
"@intentic/constants": "1.
|
|
93
|
-
"@intentic/
|
|
94
|
-
"@intentic/
|
|
92
|
+
"@intentic/constants": "1.226.1",
|
|
93
|
+
"@intentic/extension-manifest": "1.226.1",
|
|
94
|
+
"@intentic/registry": "1.226.1"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@types/node": "24.13.2",
|
|
@@ -49,6 +49,15 @@ describe("every provider/harness pair declares what it can do", () => {
|
|
|
49
49
|
}
|
|
50
50
|
expect(["namespace", "cwd"]).toContain(capabilities.isolation);
|
|
51
51
|
expect(["replace", "append", "none"]).toContain(capabilities.instructions);
|
|
52
|
+
expect(["hooks", "approval", "refuse-only", "none"]).toContain(capabilities.rulebook);
|
|
53
|
+
expect(["masked", "none"]).toContain(capabilities.secrets);
|
|
54
|
+
/* Masking is a PostToolUse hook and nothing else can edit what a model reads, so a record claiming
|
|
55
|
+
* masked without hooks would be claiming a seam that does not exist (see the axis doc). The one
|
|
56
|
+
* direction that IS legal is hooks without masking, which is why this is an implication and not
|
|
57
|
+
* equality. */
|
|
58
|
+
if (capabilities.secrets === "masked") {
|
|
59
|
+
expect(capabilities.rulebook).toBe("hooks");
|
|
60
|
+
}
|
|
52
61
|
// The permission modes offered must include the mode a clamp falls back to: a floor that isn't in the
|
|
53
62
|
// list would leave the composer showing a posture the runtime can't hold.
|
|
54
63
|
expect(modesFor(capabilities)).toContain(clampMode("default", capabilities));
|
|
@@ -176,16 +185,52 @@ test("every axis a record can lack has words for it", () => {
|
|
|
176
185
|
terminals: false,
|
|
177
186
|
recovery: false,
|
|
178
187
|
instructions: "none",
|
|
188
|
+
rulebook: "none",
|
|
189
|
+
secrets: "none",
|
|
179
190
|
};
|
|
180
191
|
|
|
181
|
-
//
|
|
182
|
-
// never be disclosed. fastMode is the deliberate
|
|
192
|
+
// Thirteen DISCLOSABLE axes, thirteen sentences: an axis added to the interface without one would silently
|
|
193
|
+
// never be disclosed. fastMode is the deliberate exception: a record alone can't tell the truth about it (a
|
|
183
194
|
// translator-routed turn reads true here and still can't go fast), so it is answered by fastAllowed
|
|
184
195
|
// instead. Anything else added to the interface has to move this number.
|
|
185
|
-
expect(limitationsOf(nothing)).toHaveLength(
|
|
196
|
+
expect(limitationsOf(nothing)).toHaveLength(13);
|
|
186
197
|
expect(limitationsOf(nothing).join(" ")).not.toContain("fast");
|
|
187
198
|
});
|
|
188
199
|
|
|
200
|
+
/* The two safety axes, checked the same way the instruction axis is below and for the same reason: `rulebook`
|
|
201
|
+
* has a middle value, and disclosing the floor's words for it would tell a Codex user their rules are ignored
|
|
202
|
+
* when they are in fact being applied to everything Codex asks about. */
|
|
203
|
+
test("the safety axes disclose the middle answer differently from the floor", () => {
|
|
204
|
+
const claude = limitationsOf(capabilitiesOf("claude", "native")).join(" ");
|
|
205
|
+
const codex = limitationsOf(capabilitiesOf("codex", "native")).join(" ");
|
|
206
|
+
const pi = limitationsOf(capabilitiesOf("pi", "native")).join(" ");
|
|
207
|
+
|
|
208
|
+
// The ceiling says nothing about either axis.
|
|
209
|
+
expect(claude).not.toContain("command rules");
|
|
210
|
+
expect(claude).not.toContain("stored secrets");
|
|
211
|
+
|
|
212
|
+
// The middle: rules DO apply, to what the vendor raises. Never the floor's flat "aren't applied".
|
|
213
|
+
expect(codex).toContain("only to calls this agent asks about");
|
|
214
|
+
expect(codex).not.toContain("aren't applied");
|
|
215
|
+
|
|
216
|
+
// The floor: no seam at all, said plainly.
|
|
217
|
+
expect(pi).toContain("your command rules aren't applied");
|
|
218
|
+
|
|
219
|
+
/* The third answer, which exists because OpenCode's watchdog aborts a turn that pauses. Its sentence must
|
|
220
|
+
* say the rules DO bite (unlike Pi's) and that a hold cannot ask (unlike Codex's). */
|
|
221
|
+
const grok = limitationsOf(capabilitiesOf("grok", "native")).join(" ");
|
|
222
|
+
expect(grok).toContain("a rule set to hold refuses instead");
|
|
223
|
+
expect(grok).not.toContain("aren't applied");
|
|
224
|
+
expect(grok).not.toContain("only to calls this agent asks about");
|
|
225
|
+
// Gemini rides the same loop, so it must read the same way.
|
|
226
|
+
expect(limitationsOf(capabilitiesOf("gemini", "native")).join(" ")).toBe(grok);
|
|
227
|
+
|
|
228
|
+
// Masking is binary and structural, so every non-Claude runtime says the same thing.
|
|
229
|
+
for (const provider of ["codex", "grok", "gemini", "pi", "some-installed-agent"] as const) {
|
|
230
|
+
expect(limitationsOf(capabilitiesOf(provider, "native")).join(" ")).toContain("stored secrets reach the model unmasked");
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
189
234
|
/* The instruction axis has THREE values and only two of them are worth a sentence, which is the one shape the
|
|
190
235
|
* count above cannot check: a middle value that discloses the same words as the floor would tell a Grok user
|
|
191
236
|
* their prompt is ignored when it is in fact being sent. */
|
package/src/agent-catalog.ts
CHANGED
|
@@ -286,6 +286,49 @@ export interface AgentCapabilities {
|
|
|
286
286
|
* only the Claude Code loop's: Codex's own base describes Codex's own tools, so swapping it for a prompt
|
|
287
287
|
* written about another harness is the owner's deliberate act (their custom text), never ours. */
|
|
288
288
|
readonly instructions: "replace" | "append" | "none";
|
|
289
|
+
/* WHETHER THE OWNER'S COMMAND RULEBOOK REACHES THIS RUNTIME (SandboxSettings.commandRules, decided by
|
|
290
|
+
* guard/actions.ts commandRun, delivered by guard/command-gate.ts).
|
|
291
|
+
*
|
|
292
|
+
* It exists because the rulebook was silently a Claude Code rulebook. The gate is a PreToolUse hook, which
|
|
293
|
+
* is an Agent SDK seam, so an owner who set `files.destructive: hold` was asked on a Claude turn and never
|
|
294
|
+
* on a Codex, Grok, Gemini, Pi or ACP one, with nothing on screen saying so. Same failure mode as the
|
|
295
|
+
* `instructions` axis above, and the same fix: name it once, let every surface read it.
|
|
296
|
+
*
|
|
297
|
+
* "hooks" , the runtime's own pre-execution hook carries the verdict and a HOLD can park the call.
|
|
298
|
+
* The Claude Code loop, whose PreToolUse hook fires even under bypassPermissions.
|
|
299
|
+
* "approval" , the vendor publishes a per-call approval channel the daemon answers from the same
|
|
300
|
+
* rulebook, and a hold parks on a card because the vendor is blocked on the answer
|
|
301
|
+
* (Codex's `item/commandExecution/requestApproval`, ACP's `session/request_permission`).
|
|
302
|
+
* Weaker than "hooks" in one stated way: the vendor decides WHICH calls it asks about, so a
|
|
303
|
+
* class it never raises is a class the rulebook cannot see. What it does raise is judged by
|
|
304
|
+
* the same decide fn.
|
|
305
|
+
* "refuse-only", the same channel, but the vendor puts a CLOCK on the wait, so a hold cannot park and
|
|
306
|
+
* arrives as a refusal instead. OpenCode's turn has an inactivity watchdog that reads a
|
|
307
|
+
* paused approval as a stalled turn; a card there would break the turn rather than gate it.
|
|
308
|
+
* `deny` rules work fully; `hold` rules stop the command and say they could not ask.
|
|
309
|
+
* "none" , the runtime publishes no seam before it runs a command, so no rule can apply. Pi runs its
|
|
310
|
+
* bash in-process with no approval channel at all.
|
|
311
|
+
*
|
|
312
|
+
* The taint floor rides this axis too: a runtime with no consult has no place to apply it, which is why
|
|
313
|
+
* `conversationTainted` must read a "none" runtime as tainted rather than as clean (guard/turn-taint.ts). */
|
|
314
|
+
readonly rulebook: "hooks" | "approval" | "refuse-only" | "none";
|
|
315
|
+
/* WHETHER A STORED CREDENTIAL IS MASKED IN WHAT THIS RUNTIME'S MODEL READS (secrets/secret-registry.ts and
|
|
316
|
+
* the two seams around it).
|
|
317
|
+
*
|
|
318
|
+
* "masked" is the full round trip: every stored value is replaced by its `{{secret:name}}` reference on the
|
|
319
|
+
* way into the model's context, and the same reference resolves back to the value at the two exits that
|
|
320
|
+
* spend it (a shell command, a script). The Claude Code loop, via PostToolUse and PreToolUse.
|
|
321
|
+
*
|
|
322
|
+
* "none" is a STRUCTURAL limit, not an unfinished wire, and it is the reason this axis is honest rather
|
|
323
|
+
* than aspirational. On every other runtime the tool runs inside the VENDOR'S own loop: the model has read
|
|
324
|
+
* the result before the daemon sees any frame about it, so there is no seam left to rewrite. A PostToolUse
|
|
325
|
+
* hook is the only thing that can edit what a model reads, and only the Claude Code loop has one. Nothing
|
|
326
|
+
* about wiring more transports changes that, which is why the answer here is a disclosure and the real fix
|
|
327
|
+
* is to stop putting credentials where a vendor's tool can read them at all.
|
|
328
|
+
*
|
|
329
|
+
* Read by limitationsOf, and by agent/system-prompt.ts, which must not teach the reference language to a
|
|
330
|
+
* runtime that has no exit for it. */
|
|
331
|
+
readonly secrets: "masked" | "none";
|
|
289
332
|
}
|
|
290
333
|
|
|
291
334
|
// The Claude Code Agent SDK loop, the ceiling every other runtime is measured against, and the only one that
|
|
@@ -307,6 +350,10 @@ const CLAUDE_CODE: AgentCapabilities = {
|
|
|
307
350
|
terminals: true,
|
|
308
351
|
recovery: true,
|
|
309
352
|
instructions: "replace",
|
|
353
|
+
// The only runtime with a pre-execution hook of its own, which is why it is the only one where a HOLD can
|
|
354
|
+
// park the call and wait for a card rather than having to refuse it.
|
|
355
|
+
rulebook: "hooks",
|
|
356
|
+
secrets: "masked",
|
|
310
357
|
};
|
|
311
358
|
|
|
312
359
|
/* Codex app-server: item-level events, process-backed MCP servers, and the four interactive seams its protocol
|
|
@@ -336,6 +383,13 @@ const CODEX: AgentCapabilities = {
|
|
|
336
383
|
* message ahead of its skills and team blocks. Verified against codex-cli 0.147 by reading what actually
|
|
337
384
|
* reached the wire, the keys are undocumented, and a strings dump proves only that they parse. */
|
|
338
385
|
instructions: "replace",
|
|
386
|
+
/* App-server publishes `item/commandExecution/requestApproval`, whose params carry the command text, and
|
|
387
|
+
* takes `accept`/`decline` back (codex-cli 0.147's own generated JSON Schema, read with
|
|
388
|
+
* `codex app-server generate-json-schema`). The daemon only asks Codex to raise those requests when the
|
|
389
|
+
* owner has written command rules, so an unconfigured workspace keeps `approvalPolicy: "never"` and pays
|
|
390
|
+
* nothing (codex/codex-agent.ts threadOptions). */
|
|
391
|
+
rulebook: "approval",
|
|
392
|
+
secrets: "none",
|
|
339
393
|
};
|
|
340
394
|
|
|
341
395
|
// OpenCode (the Grok runtime): its own agentic loop, its own tools, allow-all permissions. It takes a model id,
|
|
@@ -357,6 +411,16 @@ const OPENCODE: AgentCapabilities = {
|
|
|
357
411
|
// replacing that, so a custom prompt lands here as extra instructions, and the settings page says so
|
|
358
412
|
// rather than letting "replaces everything" quietly mean something else on two providers.
|
|
359
413
|
instructions: "append",
|
|
414
|
+
/* OpenCode asks over its own permission channel (`permission.updated`, replied on
|
|
415
|
+
* `/session/{id}/permissions/{permissionID}`, vocabulary once/always/reject), and the daemon judges what it
|
|
416
|
+
* raises with the same decide fn every other runtime uses.
|
|
417
|
+
*
|
|
418
|
+
* REFUSE-ONLY because of its watchdog, not because of its protocol. A Grok/Gemini turn is aborted after two
|
|
419
|
+
* minutes without an event for its session (grok/grok-agent.ts GROK_INACTIVITY_MS), and a permission paused
|
|
420
|
+
* on a person is exactly that silence, so a parked card would turn "ask me" into a broken turn. A `deny`
|
|
421
|
+
* rule is enforced in full; a `hold` stops the command and tells the agent it could not be asked about. */
|
|
422
|
+
rulebook: "refuse-only",
|
|
423
|
+
secrets: "none",
|
|
360
424
|
};
|
|
361
425
|
|
|
362
426
|
/* The same OpenCode loop, serving Gemini instead of xAI, identical abilities, which is the point of giving it
|
|
@@ -396,6 +460,12 @@ const ACP: AgentCapabilities = {
|
|
|
396
460
|
// ACP's `session/new` and `session/prompt` carry no system field: the agent owns its own instructions the
|
|
397
461
|
// same way it owns its model and its permission posture. The persona note takes the user message instead.
|
|
398
462
|
instructions: "none",
|
|
463
|
+
/* `session/request_permission` is in the protocol floor, so every conforming agent has the channel and the
|
|
464
|
+
* daemon answers it from the rulebook (acp/acp-permissions.ts). The caveat the "approval" value already
|
|
465
|
+
* carries is at its widest here: WHICH calls an agent asks about is entirely the agent's choice, and one
|
|
466
|
+
* that never asks is one no rule can reach. */
|
|
467
|
+
rulebook: "approval",
|
|
468
|
+
secrets: "none",
|
|
399
469
|
};
|
|
400
470
|
|
|
401
471
|
/* THE PI CAPABILITY ID IS RESERVED, the same way the five native ids are: an `agent`-kind capability installed
|
|
@@ -426,6 +496,12 @@ const PI: AgentCapabilities = {
|
|
|
426
496
|
// Pi's RPC opens a session with a prompt and steers it; nothing in that protocol sets standing
|
|
427
497
|
// instructions, so like ACP it hears the persona note through the user message.
|
|
428
498
|
instructions: "none",
|
|
499
|
+
/* THE ONE RUNTIME WITH NO SEAM AT ALL. Pi runs bash in-process and its RPC publishes no approval request,
|
|
500
|
+
* so there is nothing to consult before a command runs and no rule the owner writes can apply here. Said
|
|
501
|
+
* out loud rather than left to be discovered: limitationsOf renders it, and the taint floor treats a "none"
|
|
502
|
+
* runtime as permanently tainted, because a bit nobody can act on is worse than no bit. */
|
|
503
|
+
rulebook: "none",
|
|
504
|
+
secrets: "none",
|
|
429
505
|
};
|
|
430
506
|
|
|
431
507
|
// The pair → its record. An `endpoint/<id>` provider is a model API the user configured, driven BY the Claude
|
|
@@ -501,6 +577,18 @@ export const limitationsOf = (capabilities: AgentCapabilities): string[] => [
|
|
|
501
577
|
* mechanism, because that is the thing they wrote and the thing that will or will not be in force. */
|
|
502
578
|
...(capabilities.instructions === "append" ? ["your system prompt is added to theirs, not replacing it"] : []),
|
|
503
579
|
...(capabilities.instructions === "none" ? ["your system prompt isn't applied"] : []),
|
|
580
|
+
/* THE TWO SAFETY AXES, phrased as what the OWNER loses rather than as which seam is missing, because both
|
|
581
|
+
* describe something they configured on a settings page and would otherwise assume was in force everywhere.
|
|
582
|
+
*
|
|
583
|
+
* "hooks" and "masked" are the ceiling and disclose nothing. The "approval" middle answer discloses the one
|
|
584
|
+
* thing that genuinely differs from a hook: the vendor picks which calls it asks about, so a rule can only
|
|
585
|
+
* reach what it chose to raise. */
|
|
586
|
+
...(capabilities.rulebook === "approval" ? ["your command rules apply only to calls this agent asks about"] : []),
|
|
587
|
+
...(capabilities.rulebook === "refuse-only"
|
|
588
|
+
? ["your command rules can stop a command here but not pause to ask: a rule set to hold refuses instead"]
|
|
589
|
+
: []),
|
|
590
|
+
...(capabilities.rulebook === "none" ? ["your command rules aren't applied"] : []),
|
|
591
|
+
...(capabilities.secrets === "none" ? ["stored secrets reach the model unmasked, and `{{secret:name}}` isn't substituted"] : []),
|
|
504
592
|
];
|
|
505
593
|
|
|
506
594
|
// Claude's compile-time model floor, shared by the daemon's catalog (claude-models.ts, its last rung, reached
|
|
@@ -57,6 +57,11 @@ const LEDGER: Record<keyof AgentCapabilities, Backing> = {
|
|
|
57
57
|
// turn-plan.ts (honoured) plans the JS backend only where "js" is declared, so a runtime without it is
|
|
58
58
|
// handed no `jsExecution` and mounts no Code tool: the same drop-what-you-can't-honour rule as `effort`.
|
|
59
59
|
execution: "enforced",
|
|
60
|
+
/* turn-plan.ts (honoured) carries this onto every request and guard/turn-gate.ts DERIVES the gate's shape
|
|
61
|
+
* from it: "none" gets no consult and a permanently-set taint bit, "refuse-only" cannot park so a hold
|
|
62
|
+
* refuses, the other two park on a card. So a row that lies about itself changes how its turns behave.
|
|
63
|
+
* It also decides whether Codex is asked to raise approvals at all (codex-agent.ts threadOptions). */
|
|
64
|
+
rulebook: "enforced",
|
|
60
65
|
|
|
61
66
|
/* DESCRIPTIVE: true of the runtime, and nothing consults them. Each describes behaviour that is emergent
|
|
62
67
|
* rather than gated: an agent that never emits `question` frames simply never asks, one that publishes no
|
|
@@ -68,6 +73,14 @@ const LEDGER: Record<keyof AgentCapabilities, Backing> = {
|
|
|
68
73
|
commands: "descriptive",
|
|
69
74
|
terminals: "descriptive",
|
|
70
75
|
recovery: "descriptive",
|
|
76
|
+
/* DESCRIPTIVE, and the one entry here whose gap cannot be closed by wiring. Masking a stored credential in
|
|
77
|
+
* what the model READS needs a seam that edits a tool result before the model sees it, and only the Claude
|
|
78
|
+
* Code loop has one (a PostToolUse hook). Every other runtime runs its tools inside the VENDOR'S loop: the
|
|
79
|
+
* model has read the result before the daemon sees any frame about it. So nothing consults this field, and
|
|
80
|
+
* nothing can: it exists so limitationsOf can say out loud what is true, instead of letting an owner who
|
|
81
|
+
* stored a credential assume it is hidden everywhere. Moving it to ENFORCED would take a runtime that
|
|
82
|
+
* publishes a result-rewriting seam, not a change here. */
|
|
83
|
+
secrets: "descriptive",
|
|
71
84
|
};
|
|
72
85
|
|
|
73
86
|
// The full ceiling: every ability real, so `limitationsOf` has nothing to say about it. Read from the catalog
|
|
@@ -92,10 +105,16 @@ describe("a descriptive claim reaches the user", () => {
|
|
|
92
105
|
expect(limitationsOf(CEILING)).toEqual([]);
|
|
93
106
|
});
|
|
94
107
|
|
|
108
|
+
/* What "lacking this axis" MEANS for a field that is not a boolean. A boolean diminishes to false; anything
|
|
109
|
+
* else has to name its own floor here, because only the axis knows which of its values is the weak one.
|
|
110
|
+
* Adding a non-boolean descriptive field without an entry fails below rather than silently testing nothing. */
|
|
111
|
+
const DIMINISHED: Partial<Record<keyof AgentCapabilities, unknown>> = { secrets: "none" };
|
|
112
|
+
|
|
95
113
|
it.each(fieldsWhere("descriptive"))("%s puts its own sentence in the picker when the runtime lacks it", (field) => {
|
|
96
|
-
|
|
114
|
+
const floor = typeof CEILING[field] === "boolean" ? false : DIMINISHED[field];
|
|
115
|
+
expect(floor, `${field} is descriptive and not a boolean: add its weakest value to DIMINISHED above`).toBeDefined();
|
|
97
116
|
|
|
98
|
-
const lacking: AgentCapabilities = { ...CEILING, [field]:
|
|
117
|
+
const lacking: AgentCapabilities = { ...CEILING, [field]: floor };
|
|
99
118
|
|
|
100
119
|
expect(limitationsOf(lacking)).toHaveLength(1);
|
|
101
120
|
});
|
|
@@ -4,6 +4,22 @@ import { ActivityListSchema, ActivityQuerySchema, ActivityStatusSchema } from ".
|
|
|
4
4
|
// The activity audit feed (provider-agnostic; Discord is the first source). Read-only by design,
|
|
5
5
|
// events are appended daemon-side only, so the log stays a trustworthy record of what the agent did.
|
|
6
6
|
export const activityContract = {
|
|
7
|
-
list: oc
|
|
8
|
-
|
|
7
|
+
list: oc
|
|
8
|
+
.route({
|
|
9
|
+
method: "GET",
|
|
10
|
+
path: "/activity",
|
|
11
|
+
summary: "What the agent has done out in the world",
|
|
12
|
+
description:
|
|
13
|
+
"The audit trail of actions taken on outside services. Read-only on purpose: entries are written by the sandbox alone, which is what makes it a record worth trusting.",
|
|
14
|
+
})
|
|
15
|
+
.input(ActivityQuerySchema)
|
|
16
|
+
.output(ActivityListSchema),
|
|
17
|
+
status: oc
|
|
18
|
+
.route({
|
|
19
|
+
method: "GET",
|
|
20
|
+
path: "/activity/status",
|
|
21
|
+
summary: "Whether the audit trail is being kept",
|
|
22
|
+
description: "Which sources are feeding the record and whether each is working.",
|
|
23
|
+
})
|
|
24
|
+
.output(ActivityStatusSchema),
|
|
9
25
|
};
|
|
@@ -19,20 +19,90 @@ import {
|
|
|
19
19
|
// turn waiting on any interactive card (plan approval, clarifying questions, a per-tool permission prompt);
|
|
20
20
|
// steer injects a user message into the running turn; stop hard-cancels it daemon-side.
|
|
21
21
|
export const agentContract = {
|
|
22
|
-
run: oc
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
run: oc
|
|
23
|
+
.route({
|
|
24
|
+
method: "POST",
|
|
25
|
+
path: "/agent",
|
|
26
|
+
summary: "Say something to an agent",
|
|
27
|
+
description:
|
|
28
|
+
"Starts a turn and answers immediately with its id; the work runs inside the sandbox whether or not anybody stays connected. Watch it by attaching. Naming a conversation that does not exist yet opens it.",
|
|
29
|
+
})
|
|
30
|
+
.input(AgentTurnSchema)
|
|
31
|
+
.output(StartedTurnSchema),
|
|
32
|
+
attach: oc
|
|
33
|
+
.route({
|
|
34
|
+
method: "POST",
|
|
35
|
+
path: "/agent/attach",
|
|
36
|
+
summary: "Watch a turn happen",
|
|
37
|
+
description:
|
|
38
|
+
"Streams everything the agent does: its words, the tools it reaches for, and the answers it gets. Give it the point you have already seen and it replays from there before going live, so a reload loses nothing. The window that started the turn holds no special claim, and any number of watchers on any number of devices see the same thing.",
|
|
39
|
+
})
|
|
40
|
+
.input(AttachTurnSchema)
|
|
41
|
+
.output(eventIterator(AttachFrameSchema)),
|
|
42
|
+
reply: oc
|
|
43
|
+
.route({
|
|
44
|
+
method: "POST",
|
|
45
|
+
path: "/agent/reply",
|
|
46
|
+
summary: "Answer a question the agent asked",
|
|
47
|
+
description:
|
|
48
|
+
"Un-parks a turn that is waiting on you: approving a plan, choosing between options, or permitting a tool. The turn picks up where it stopped.",
|
|
49
|
+
})
|
|
50
|
+
.input(AgentReplySchema)
|
|
51
|
+
.output(OkSchema),
|
|
52
|
+
steer: oc
|
|
53
|
+
.route({
|
|
54
|
+
method: "POST",
|
|
55
|
+
path: "/agent/steer",
|
|
56
|
+
summary: "Interrupt a running turn",
|
|
57
|
+
description:
|
|
58
|
+
"Slips a message into a turn already under way, without stopping it. This is how you redirect an agent mid-thought rather than waiting for it to finish being wrong.",
|
|
59
|
+
})
|
|
60
|
+
.input(SteerSchema)
|
|
61
|
+
.output(OkSchema),
|
|
62
|
+
stop: oc
|
|
63
|
+
.route({
|
|
64
|
+
method: "POST",
|
|
65
|
+
path: "/agent/stop",
|
|
66
|
+
summary: "Stop a turn now",
|
|
67
|
+
description: "Cancels the running turn inside the sandbox. Whatever it had already written to disk stays written.",
|
|
68
|
+
})
|
|
69
|
+
.input(StopTurnSchema)
|
|
70
|
+
.output(OkSchema),
|
|
27
71
|
// Go back to a message: restore the workspace to that turn's checkpoint, drop the messages after it, and
|
|
28
72
|
// forget the provider session. CONFLICT while a turn is running, a restore cannot overwrite files an
|
|
29
73
|
// agent is editing. NOT_FOUND when that message has no checkpoint to go back to.
|
|
30
|
-
rewind: oc
|
|
74
|
+
rewind: oc
|
|
75
|
+
.route({
|
|
76
|
+
method: "POST",
|
|
77
|
+
path: "/agent/rewind",
|
|
78
|
+
summary: "Go back to an earlier message",
|
|
79
|
+
description:
|
|
80
|
+
"Puts the files back as they stood at that point, drops every message after it, and forgets what the model remembered, so the next thing you say starts from there cleanly. Refused while a turn is running, because a restore cannot overwrite files an agent is editing, and refused for a message with no saved state to return to.",
|
|
81
|
+
})
|
|
82
|
+
.input(RewindTurnSchema)
|
|
83
|
+
.output(RewindResultSchema),
|
|
31
84
|
// The provider's slash commands as last published by one of its turns, so a conversation's `/` popover is
|
|
32
85
|
// populated before it has run one. The live `commands` frame stays authoritative for a running turn.
|
|
33
|
-
commands: oc
|
|
86
|
+
commands: oc
|
|
87
|
+
.route({
|
|
88
|
+
method: "GET",
|
|
89
|
+
path: "/agent/commands",
|
|
90
|
+
summary: "Shortcut commands the agent knows",
|
|
91
|
+
description:
|
|
92
|
+
"The commands a provider published the last time one of its turns ran, so a composer can offer them before this conversation has run anything. A running turn's own list wins over this one.",
|
|
93
|
+
})
|
|
94
|
+
.input(AgentCommandsQuerySchema)
|
|
95
|
+
.output(AgentCommandsSchema),
|
|
34
96
|
// The last refusal per provider, as reported by whichever turn was refused (ProviderRefusalSchema). Read
|
|
35
97
|
// alongside the account listings by the surfaces that draw plan limits: the snapshot on an account row says
|
|
36
98
|
// how full its pools were when last polled, and this says whether one of them has since said no.
|
|
37
|
-
refusals: oc
|
|
99
|
+
refusals: oc
|
|
100
|
+
.route({
|
|
101
|
+
method: "GET",
|
|
102
|
+
path: "/agent/refusals",
|
|
103
|
+
summary: "The last time each provider said no",
|
|
104
|
+
description:
|
|
105
|
+
"What each model provider most recently refused and why. Read this alongside an account's usage: the usage says how full it was when last checked, this says whether it has since started turning work away.",
|
|
106
|
+
})
|
|
107
|
+
.output(ProviderRefusalsSchema),
|
|
38
108
|
};
|
|
@@ -47,27 +47,96 @@ import {
|
|
|
47
47
|
// own agent, so there should be none) and answers with what it actually deleted: a teardown that fails on one
|
|
48
48
|
// agent's repo leaves that one behind rather than taking the batch down with it.
|
|
49
49
|
export const agentsContract = {
|
|
50
|
-
list: oc
|
|
51
|
-
|
|
50
|
+
list: oc
|
|
51
|
+
.route({
|
|
52
|
+
method: "GET",
|
|
53
|
+
path: "/agents",
|
|
54
|
+
summary: "Every live conversation",
|
|
55
|
+
description:
|
|
56
|
+
"The fleet as the board draws it: each conversation with its title, what it is doing, when it last moved and whether anybody has read it since. Archived conversations are not in here.",
|
|
57
|
+
})
|
|
58
|
+
.output(AgentsListSchema),
|
|
59
|
+
archived: oc
|
|
60
|
+
.route({
|
|
61
|
+
method: "GET",
|
|
62
|
+
path: "/agents/archived",
|
|
63
|
+
summary: "Conversations put away",
|
|
64
|
+
description:
|
|
65
|
+
"The same shape as the live fleet, for the conversations somebody has decided are finished. Their work is kept, and any one of them can be brought back.",
|
|
66
|
+
})
|
|
67
|
+
.output(AgentsListSchema),
|
|
52
68
|
// The board's filter. Answers over BOTH halves of the fleet, the live roster and the archive, because
|
|
53
69
|
// the board hides by design (the Finished lane windows to a handful, archived agents are off the roster
|
|
54
70
|
// entirely), and a filter that reports "no matches" while the agent sits one click away is a lie. The
|
|
55
71
|
// never-carded conversations that are neither are `sessions.list`'s query, which matches by the same rule.
|
|
56
|
-
search: oc
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
search: oc
|
|
73
|
+
.route({
|
|
74
|
+
method: "GET",
|
|
75
|
+
path: "/agents/search",
|
|
76
|
+
summary: "Find a conversation",
|
|
77
|
+
description:
|
|
78
|
+
"Searches the live fleet and the archive together. Both halves on purpose: the board hides finished work by design, and a filter that says it found nothing while the answer sits one click away is simply wrong.",
|
|
79
|
+
})
|
|
80
|
+
.input(AgentSearchQuerySchema)
|
|
81
|
+
.output(AgentSearchResultSchema),
|
|
82
|
+
get: oc
|
|
83
|
+
.route({
|
|
84
|
+
method: "GET",
|
|
85
|
+
path: "/agents/{id}",
|
|
86
|
+
summary: "One conversation's card",
|
|
87
|
+
description: "Everything the board shows for a single conversation: its title, state, working branch, unread marker and timestamps.",
|
|
88
|
+
})
|
|
89
|
+
.input(AgentIdSchema)
|
|
90
|
+
.output(AgentSummarySchema),
|
|
91
|
+
transcript: oc
|
|
92
|
+
.route({
|
|
93
|
+
method: "GET",
|
|
94
|
+
path: "/agents/{id}/transcript",
|
|
95
|
+
summary: "The full conversation record",
|
|
96
|
+
description:
|
|
97
|
+
"Every message in one conversation, in order, including the tool calls and their results. This is the record the chat replays and the next turn is seeded from.",
|
|
98
|
+
})
|
|
99
|
+
.input(AgentIdSchema)
|
|
100
|
+
.output(AgentTranscriptSchema),
|
|
59
101
|
/* SPEAK AS THE AGENT, append the user's words to the conversation's record as an assistant row, with no
|
|
60
102
|
* turn behind them and no reply. The row is marked `placed` for human readers (RestoredMessageSchema); the
|
|
61
103
|
* provider session is FORGOTTEN in the same breath, rewind-style, so the next real turn opens a fresh
|
|
62
104
|
* runtime session seeded from the record, where the placed line reads as the agent's own words, because
|
|
63
105
|
* the handoff renders every assistant row identically. A running turn is CONFLICT: the illusion can only be
|
|
64
106
|
* established between turns, and a concurrent turn would resume the very session this exists to retire. */
|
|
65
|
-
place: oc
|
|
66
|
-
|
|
107
|
+
place: oc
|
|
108
|
+
.route({
|
|
109
|
+
method: "POST",
|
|
110
|
+
path: "/agents/{id}/place",
|
|
111
|
+
summary: "Put words in the agent's mouth",
|
|
112
|
+
description:
|
|
113
|
+
"Writes a line into the record as though the agent had said it, with no turn behind it and no reply. Human readers see it marked as placed. The next real turn starts fresh from the record, where the line reads as the agent's own. Refused while a turn is running.",
|
|
114
|
+
})
|
|
115
|
+
.input(AgentPlaceSchema)
|
|
116
|
+
.output(OkSchema),
|
|
117
|
+
rename: oc
|
|
118
|
+
.route({
|
|
119
|
+
method: "POST",
|
|
120
|
+
path: "/agents/{id}/rename",
|
|
121
|
+
summary: "Retitle a conversation",
|
|
122
|
+
description:
|
|
123
|
+
"Sets the title a person chose, replacing the one that was generated. Allowed while the conversation is working, and it does not count as activity.",
|
|
124
|
+
})
|
|
125
|
+
.input(AgentRenameSchema)
|
|
126
|
+
.output(AgentSummarySchema),
|
|
67
127
|
// This agent's own land-at-completion posture, an override of the sandbox-wide `autoLand` setting; null
|
|
68
128
|
// clears it back to "inherit". Legal mid-turn on purpose: the setting is read at turn COMPLETION, so
|
|
69
129
|
// flipping it while the agent works is exactly "hold THIS turn's work for review", the press that matters.
|
|
70
|
-
autoLand: oc
|
|
130
|
+
autoLand: oc
|
|
131
|
+
.route({
|
|
132
|
+
method: "POST",
|
|
133
|
+
path: "/agents/{id}/auto-land",
|
|
134
|
+
summary: "Whether this conversation merges its work automatically",
|
|
135
|
+
description:
|
|
136
|
+
"Overrides the sandbox-wide setting for one conversation; clear it to go back to following the default. Deliberately allowed mid-turn, because the setting is read when the turn finishes, so flipping it while the agent works means exactly hold this piece of work for review.",
|
|
137
|
+
})
|
|
138
|
+
.input(AgentAutoLandSchema)
|
|
139
|
+
.output(AgentSummarySchema),
|
|
71
140
|
/* THIS conversation's answer to a provider outage, an override of the sandbox-wide `resumeAfterOutage`
|
|
72
141
|
* setting; null clears it back to "inherit". The chat's offer at the moment a turn dies writes this and
|
|
73
142
|
* never the global: the press happens inside one conversation and means "finish this piece of work", so
|
|
@@ -76,20 +145,112 @@ export const agentsContract = {
|
|
|
76
145
|
* Legal mid-turn, and unlike autoLand it is legal for a WORKSPACE conversation too, an outage kills a
|
|
77
146
|
* main-tree chat exactly as readily as an isolated one, and there is no branch involved either way. */
|
|
78
147
|
resumeAfterOutage: oc
|
|
79
|
-
.route({
|
|
148
|
+
.route({
|
|
149
|
+
method: "POST",
|
|
150
|
+
path: "/agents/{id}/resume-after-outage",
|
|
151
|
+
summary: "Whether this conversation retries after a provider outage",
|
|
152
|
+
description:
|
|
153
|
+
"Overrides the sandbox-wide setting for one conversation; clear it to follow the default again. This is what the offer shown when a turn dies writes, because the press happens inside one conversation and honestly means finish this piece of work.",
|
|
154
|
+
})
|
|
80
155
|
.input(AgentResumeAfterOutageSchema)
|
|
81
156
|
.output(AgentSummarySchema),
|
|
82
|
-
seen: oc
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
157
|
+
seen: oc
|
|
158
|
+
.route({
|
|
159
|
+
method: "POST",
|
|
160
|
+
path: "/agents/{id}/seen",
|
|
161
|
+
summary: "Mark a conversation read",
|
|
162
|
+
description:
|
|
163
|
+
"Stamps the read marker behind the unread badge on one card. Allowed while the conversation is working, and reading never counts as activity.",
|
|
164
|
+
})
|
|
165
|
+
.input(AgentIdSchema)
|
|
166
|
+
.output(AgentSummarySchema),
|
|
167
|
+
seenAll: oc
|
|
168
|
+
.route({
|
|
169
|
+
method: "POST",
|
|
170
|
+
path: "/agents/seen",
|
|
171
|
+
summary: "Mark every conversation read",
|
|
172
|
+
description: "Clears the unread badge across the whole fleet at once, and hands the refreshed list back.",
|
|
173
|
+
})
|
|
174
|
+
.output(AgentsListSchema),
|
|
175
|
+
diff: oc
|
|
176
|
+
.route({
|
|
177
|
+
method: "GET",
|
|
178
|
+
path: "/agents/{id}/diff",
|
|
179
|
+
summary: "Everything a conversation has changed",
|
|
180
|
+
description:
|
|
181
|
+
"One flat set of changed files per repo, measured against where each repo stood when the conversation started, with every file flagged as already merged or not. Not the staged-and-unstaged shape a working copy has, because nobody ever checks this branch out to stage into it.",
|
|
182
|
+
})
|
|
183
|
+
.input(AgentIdSchema)
|
|
184
|
+
.output(AgentChangesSchema),
|
|
185
|
+
fileDiff: oc
|
|
186
|
+
.route({
|
|
187
|
+
method: "GET",
|
|
188
|
+
path: "/agents/{id}/{repo}/file-diff",
|
|
189
|
+
summary: "One file's before and after in a conversation's work",
|
|
190
|
+
description: "Both sides of a single file: what it held when the conversation started and what it holds on its branch now.",
|
|
191
|
+
})
|
|
192
|
+
.input(AgentFileDiffQuerySchema)
|
|
193
|
+
.output(FileDiffSchema),
|
|
194
|
+
land: oc
|
|
195
|
+
.route({
|
|
196
|
+
method: "POST",
|
|
197
|
+
path: "/agents/{id}/land",
|
|
198
|
+
summary: "Merge a conversation's work into the workspace",
|
|
199
|
+
description:
|
|
200
|
+
"Brings the conversation's branches into the main tree, one repo at a time. A conflict is reported rather than raised and nothing is lost when it fails. Refused while a turn is running, and refused for a conversation that works directly in the shared tree, which has nothing to merge.",
|
|
201
|
+
})
|
|
202
|
+
.input(AgentLandSchema)
|
|
203
|
+
.output(LandResultSchema),
|
|
87
204
|
// A collaborator's ask for the land they may not perform themselves (role floors put `land`/`discard` at
|
|
88
205
|
// maintainer). Stamps AgentSummarySchema.landRequested with the caller's identity and re-frames the fleet,
|
|
89
206
|
// so every maintainer's board carries the request; the land or discard that answers it clears the stamp.
|
|
90
|
-
requestLand: oc
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
207
|
+
requestLand: oc
|
|
208
|
+
.route({
|
|
209
|
+
method: "POST",
|
|
210
|
+
path: "/agents/{id}/request-land",
|
|
211
|
+
summary: "Ask a maintainer to merge this work",
|
|
212
|
+
description:
|
|
213
|
+
"For a collaborator who is not allowed to merge: marks the conversation as waiting for review, with who asked. The request shows on every maintainer's board and clears when somebody merges or discards it.",
|
|
214
|
+
})
|
|
215
|
+
.input(AgentIdSchema)
|
|
216
|
+
.output(AgentSummarySchema),
|
|
217
|
+
discard: oc
|
|
218
|
+
.route({
|
|
219
|
+
method: "POST",
|
|
220
|
+
path: "/agents/{id}/discard",
|
|
221
|
+
summary: "Throw a conversation's work away",
|
|
222
|
+
description:
|
|
223
|
+
"Deletes the conversation's working copies, its branches and its entry. Nothing is kept. Refused while a turn is running, and refused for a conversation working in the shared tree.",
|
|
224
|
+
})
|
|
225
|
+
.input(AgentIdSchema)
|
|
226
|
+
.output(OkSchema),
|
|
227
|
+
archive: oc
|
|
228
|
+
.route({
|
|
229
|
+
method: "POST",
|
|
230
|
+
path: "/agents/archive",
|
|
231
|
+
summary: "Put conversations away",
|
|
232
|
+
description:
|
|
233
|
+
"The gentle counterpart to discarding. Commits whatever the conversation still has in progress onto its own branch, releases its working copy, and keeps the entry and the record. It leaves the live fleet and joins the archive. Refused for a conversation that is running.",
|
|
234
|
+
})
|
|
235
|
+
.input(AgentArchiveSchema)
|
|
236
|
+
.output(AgentsMovedSchema),
|
|
237
|
+
unarchive: oc
|
|
238
|
+
.route({
|
|
239
|
+
method: "POST",
|
|
240
|
+
path: "/agents/unarchive",
|
|
241
|
+
summary: "Bring conversations back",
|
|
242
|
+
description:
|
|
243
|
+
"Returns archived conversations to the live fleet. The next turn picks up a fresh working copy from the branch that was kept.",
|
|
244
|
+
})
|
|
245
|
+
.input(AgentIdsSchema)
|
|
246
|
+
.output(AgentsMovedSchema),
|
|
247
|
+
purge: oc
|
|
248
|
+
.route({
|
|
249
|
+
method: "POST",
|
|
250
|
+
path: "/agents/purge",
|
|
251
|
+
summary: "Empty the archive for good",
|
|
252
|
+
description:
|
|
253
|
+
"Discards every conversation already in the archive: working copies, branches and entries. The whole archive rather than a chosen few, because the archive is the pile somebody has already decided is over. A teardown that fails on one conversation leaves that one behind instead of taking the rest down with it.",
|
|
254
|
+
})
|
|
255
|
+
.output(AgentsRemovedSchema),
|
|
95
256
|
};
|