@intentic/sandbox-contract 1.240.0 → 1.242.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/README.md +52 -16
- package/dist/agent-catalog.d.ts +5 -28
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +21 -157
- package/dist/agent-catalog.js.map +1 -1
- package/dist/agent-run-model.d.ts +3 -2
- package/dist/agent-run-model.d.ts.map +1 -1
- package/dist/agent-run-model.js +9 -6
- package/dist/agent-run-model.js.map +1 -1
- package/dist/agent-runtimes.d.ts +27 -0
- package/dist/agent-runtimes.d.ts.map +1 -0
- package/dist/agent-runtimes.js +113 -0
- package/dist/agent-runtimes.js.map +1 -0
- package/dist/approvals-execution.d.ts +11 -0
- package/dist/approvals-execution.d.ts.map +1 -0
- package/dist/approvals-execution.js +44 -0
- package/dist/approvals-execution.js.map +1 -0
- package/dist/arrival.d.ts +151 -0
- package/dist/arrival.d.ts.map +1 -0
- package/dist/arrival.js +56 -0
- package/dist/arrival.js.map +1 -0
- package/dist/card-status.d.ts +5 -0
- package/dist/card-status.d.ts.map +1 -0
- package/dist/card-status.js +74 -0
- package/dist/card-status.js.map +1 -0
- package/dist/chores/chores.d.ts.map +1 -1
- package/dist/chores/chores.js +1 -1
- package/dist/chores/chores.js.map +1 -1
- package/dist/command-classes.d.ts +0 -1
- package/dist/command-classes.d.ts.map +1 -1
- package/dist/command-classes.js +9 -2
- package/dist/command-classes.js.map +1 -1
- package/dist/command-run.d.ts +10 -0
- package/dist/command-run.d.ts.map +1 -0
- package/dist/command-run.js +46 -0
- package/dist/command-run.js.map +1 -0
- package/dist/contracts/agent.contract.d.ts +991 -771
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +345 -337
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +20 -2
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/approvals.contract.d.ts +99 -0
- package/dist/contracts/approvals.contract.d.ts.map +1 -0
- package/dist/contracts/approvals.contract.js +32 -0
- package/dist/contracts/approvals.contract.js.map +1 -0
- package/dist/contracts/ci.contract.d.ts +1 -4
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.js +1 -9
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/claude.contract.d.ts +9 -0
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/cursor.contract.d.ts +6 -0
- package/dist/contracts/cursor.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.d.ts +63 -1
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +22 -4
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts +3 -0
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/keys.contract.d.ts +81 -0
- package/dist/contracts/keys.contract.d.ts.map +1 -0
- package/dist/contracts/keys.contract.js +51 -0
- package/dist/contracts/keys.contract.js.map +1 -0
- package/dist/contracts/panels.contract.d.ts +7 -0
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.js +2 -2
- package/dist/contracts/providers.contract.d.ts +2 -0
- package/dist/contracts/providers.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +84 -86
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/safety.contract.d.ts +35 -0
- package/dist/contracts/safety.contract.d.ts.map +1 -0
- package/dist/contracts/safety.contract.js +32 -0
- package/dist/contracts/safety.contract.js.map +1 -0
- package/dist/contracts/sessions.contract.d.ts +73 -337
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +30 -32
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +139 -337
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.d.ts +31 -3
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/usage.contract.d.ts +29 -20
- package/dist/contracts/usage.contract.d.ts.map +1 -1
- package/dist/contracts/usage.contract.js +13 -0
- package/dist/contracts/usage.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +8 -0
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/conversation-ids.d.ts +2 -0
- package/dist/conversation-ids.d.ts.map +1 -1
- package/dist/conversation-ids.js +8 -0
- package/dist/conversation-ids.js.map +1 -1
- package/dist/credential-material.d.ts +1 -0
- package/dist/credential-material.d.ts.map +1 -1
- package/dist/credential-material.js +24 -2
- package/dist/credential-material.js.map +1 -1
- package/dist/definition.d.ts +51 -93
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js +6 -28
- package/dist/definition.js.map +1 -1
- package/dist/events.d.ts +1831 -2285
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +155 -55
- 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 +1959 -1903
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/dist/ingress-protocol.d.ts +13 -1
- package/dist/ingress-protocol.d.ts.map +1 -1
- package/dist/ingress-protocol.js +19 -0
- package/dist/ingress-protocol.js.map +1 -1
- package/dist/mentions.d.ts +3 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +13 -0
- package/dist/mentions.js.map +1 -0
- package/dist/needs-action.d.ts +7 -0
- package/dist/needs-action.d.ts.map +1 -0
- package/dist/needs-action.js +3 -0
- package/dist/needs-action.js.map +1 -0
- package/dist/overlay-lint.d.ts +7 -0
- package/dist/overlay-lint.d.ts.map +1 -0
- package/dist/overlay-lint.js +68 -0
- package/dist/overlay-lint.js.map +1 -0
- package/dist/owner-ticket.d.ts +18 -0
- package/dist/owner-ticket.d.ts.map +1 -0
- package/dist/owner-ticket.js +51 -0
- package/dist/owner-ticket.js.map +1 -0
- package/dist/plan-pools.d.ts +12 -0
- package/dist/plan-pools.d.ts.map +1 -0
- package/dist/plan-pools.js +30 -0
- package/dist/plan-pools.js.map +1 -0
- package/dist/provider-specs.d.ts +228 -0
- package/dist/provider-specs.d.ts.map +1 -0
- package/dist/provider-specs.js +124 -0
- package/dist/provider-specs.js.map +1 -0
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/quick-model.js +2 -1
- package/dist/quick-model.js.map +1 -1
- package/dist/runner-protocol.js +2 -2
- package/dist/runner-protocol.js.map +1 -1
- package/dist/runtime-state.d.ts +2 -2
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +1 -1
- package/dist/runtime-state.js.map +1 -1
- package/dist/safety-policy.d.ts +49 -0
- package/dist/safety-policy.d.ts.map +1 -0
- package/dist/safety-policy.js +52 -0
- package/dist/safety-policy.js.map +1 -0
- package/dist/schemas/agent.d.ts +21 -3
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +18 -8
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +19 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +15 -0
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/approvals.d.ts +241 -0
- package/dist/schemas/approvals.d.ts.map +1 -0
- package/dist/schemas/approvals.js +63 -0
- package/dist/schemas/approvals.js.map +1 -0
- package/dist/schemas/automations.d.ts +5 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/ci.d.ts +5 -9
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js +3 -10
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/computers.d.ts +31 -5
- package/dist/schemas/computers.d.ts.map +1 -1
- package/dist/schemas/computers.js +7 -5
- package/dist/schemas/computers.js.map +1 -1
- package/dist/schemas/environment.d.ts +11 -131
- package/dist/schemas/environment.d.ts.map +1 -1
- package/dist/schemas/environment.js +5 -42
- package/dist/schemas/environment.js.map +1 -1
- package/dist/schemas/git-history.d.ts +4 -0
- package/dist/schemas/git-history.d.ts.map +1 -1
- package/dist/schemas/git.d.ts +179 -0
- package/dist/schemas/git.d.ts.map +1 -1
- package/dist/schemas/git.js +39 -1
- package/dist/schemas/git.js.map +1 -1
- package/dist/schemas/panels.d.ts +21 -0
- package/dist/schemas/panels.d.ts.map +1 -1
- package/dist/schemas/panels.js +5 -0
- package/dist/schemas/panels.js.map +1 -1
- package/dist/schemas/plan-limits.d.ts +50 -3
- package/dist/schemas/plan-limits.d.ts.map +1 -1
- package/dist/schemas/plan-limits.js +11 -6
- package/dist/schemas/plan-limits.js.map +1 -1
- package/dist/schemas/provider-oauth.d.ts +6 -0
- package/dist/schemas/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/provider-subscriptions.d.ts +2 -1
- package/dist/schemas/provider-subscriptions.d.ts.map +1 -1
- package/dist/schemas/provider-subscriptions.js +2 -1
- package/dist/schemas/provider-subscriptions.js.map +1 -1
- package/dist/schemas/settings.d.ts +29 -16
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +13 -20
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/system.d.ts +3 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +5 -1
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/workspace-repos.d.ts +14 -0
- package/dist/schemas/workspace-repos.d.ts.map +1 -1
- package/dist/schemas/workspace-repos.js +5 -0
- package/dist/schemas/workspace-repos.js.map +1 -1
- package/dist/schemas/workspace-tree.d.ts +1 -0
- package/dist/schemas/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace-tree.js +3 -0
- package/dist/schemas/workspace-tree.js.map +1 -1
- package/dist/transcript-fold.d.ts +31 -0
- package/dist/transcript-fold.d.ts.map +1 -0
- package/dist/transcript-fold.js +457 -0
- package/dist/transcript-fold.js.map +1 -0
- package/dist/workspace-state.d.ts +20 -3
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +22 -2
- package/dist/workspace-state.js.map +1 -1
- package/package.json +27 -5
- package/src/agent-catalog.test.ts +1 -1
- package/src/agent-catalog.ts +76 -555
- package/src/agent-run-model.test.ts +31 -15
- package/src/agent-run-model.ts +26 -12
- package/src/agent-runtimes.ts +419 -0
- package/src/approvals-execution.ts +96 -0
- package/src/arrival.ts +160 -0
- package/src/capability-ledger.test.ts +6 -4
- package/src/card-status.ts +93 -0
- package/src/chores/chores.ts +6 -3
- package/src/command-classes.test.ts +25 -0
- package/src/command-classes.ts +47 -5
- package/src/command-run.ts +78 -0
- package/src/contract-lock.test.ts +1 -1
- package/src/contract-lock.ts +2 -2
- package/src/contracts/agents.contract.ts +35 -2
- package/src/contracts/approvals.contract.ts +37 -0
- package/src/contracts/ci.contract.ts +0 -13
- package/src/contracts/git.contract.ts +28 -3
- package/src/contracts/keys.contract.ts +79 -0
- package/src/contracts/prepush.contract.ts +3 -3
- package/src/contracts/safety.contract.ts +48 -0
- package/src/contracts/usage.contract.ts +22 -0
- package/src/conversation-ids.test.ts +31 -1
- package/src/conversation-ids.ts +37 -3
- package/src/credential-material.test.ts +69 -1
- package/src/credential-material.ts +86 -5
- package/src/definition.ts +25 -53
- package/src/events.ts +374 -173
- package/src/history-state.ts +6 -0
- package/src/index.ts +24 -5
- package/src/ingress-protocol.ts +52 -1
- package/src/mentions.test.ts +22 -0
- package/src/mentions.ts +25 -0
- package/src/needs-action.ts +14 -0
- package/src/overlay-lint.test.ts +89 -0
- package/src/overlay-lint.ts +116 -0
- package/src/owner-ticket.test.ts +54 -0
- package/src/owner-ticket.ts +102 -0
- package/src/plan-pools.test.ts +71 -0
- package/src/plan-pools.ts +82 -0
- package/src/provider-specs.test.ts +130 -0
- package/src/provider-specs.ts +352 -0
- package/src/quick-model.ts +9 -5
- package/src/runner-protocol.ts +2 -2
- package/src/runtime-state.ts +6 -6
- package/src/safety-policy.ts +165 -0
- package/src/schemas/agent.ts +62 -17
- package/src/schemas/agents.ts +71 -0
- package/src/schemas/approvals.ts +174 -0
- package/src/schemas/ci.ts +19 -26
- package/src/schemas/computers.ts +52 -11
- package/src/schemas/environment.ts +21 -100
- package/src/schemas/git.ts +121 -6
- package/src/schemas/issues.ts +1 -1
- package/src/schemas/panels.ts +20 -0
- package/src/schemas/plan-limits.ts +58 -11
- package/src/schemas/provider-subscriptions.ts +7 -2
- package/src/schemas/settings.ts +67 -93
- package/src/schemas/system.ts +12 -1
- package/src/schemas/workspace-repos.ts +9 -0
- package/src/schemas/workspace-tree.ts +11 -0
- package/src/transcript-fold.test.ts +493 -0
- package/src/transcript-fold.ts +655 -0
- package/src/workspace-state.test.ts +77 -29
- package/src/workspace-state.ts +99 -31
- package/dist/contracts/drafts.contract.d.ts +0 -57
- package/dist/contracts/drafts.contract.d.ts.map +0 -1
- package/dist/contracts/drafts.contract.js +0 -32
- package/dist/contracts/drafts.contract.js.map +0 -1
- package/dist/definition-action.d.ts +0 -7
- package/dist/definition-action.d.ts.map +0 -1
- package/dist/definition-action.js +0 -3
- package/dist/definition-action.js.map +0 -1
- package/dist/publish-drafts.d.ts +0 -7
- package/dist/publish-drafts.d.ts.map +0 -1
- package/dist/publish-drafts.js +0 -20
- package/dist/publish-drafts.js.map +0 -1
- package/dist/schemas/drafts.d.ts +0 -84
- package/dist/schemas/drafts.d.ts.map +0 -1
- package/dist/schemas/drafts.js +0 -44
- package/dist/schemas/drafts.js.map +0 -1
- package/src/contracts/drafts.contract.ts +0 -35
- package/src/definition-action.ts +0 -8
- package/src/publish-drafts.ts +0 -59
- package/src/schemas/drafts.ts +0 -108
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
2
|
import { resolveAgentRunModels } from "./agent-run-model.js";
|
|
3
3
|
import type { QuickModelSource } from "./quick-model.js";
|
|
4
|
+
import type { AgentRunPin } from "./schemas/agent.js";
|
|
4
5
|
|
|
5
6
|
/* Which model a run somebody's BUTTON started opens on. The rule answers the same two surfaces its quick-model
|
|
6
7
|
* sibling does: the daemon walks it, the settings row names it, so what these pin is the pair of properties
|
|
7
8
|
* that separate the two: an account this sandbox cannot reach never sits at the head of the chain, and an empty
|
|
8
|
-
* answer stays empty rather than being filled in with a tier nobody chose.
|
|
9
|
+
* answer stays empty rather than being filled in with a tier nobody chose.
|
|
10
|
+
*
|
|
11
|
+
* And one property neither of those covers, new with the pins being objects: an entry's own knobs are the
|
|
12
|
+
* entry's, so what survives the walk is the WHOLE pin. A resolver that handed back the pair inside it would run
|
|
13
|
+
* the fallback at the head's effort, which is a tier that appears nowhere on the user's screen. */
|
|
9
14
|
|
|
10
15
|
const CLAUDE: QuickModelSource = { provider: `claude`, ready: true, models: [`claude-opus-5`, `claude-sonnet-5`] };
|
|
11
16
|
const CODEX: QuickModelSource = { provider: `codex`, ready: true, models: [`gpt-5.6`] };
|
|
@@ -13,10 +18,12 @@ const GOOGLE: QuickModelSource = { provider: `gemini`, ready: true, models: [`ge
|
|
|
13
18
|
|
|
14
19
|
const offline = (source: QuickModelSource): QuickModelSource => ({ ...source, ready: false });
|
|
15
20
|
|
|
21
|
+
const pin = (provider: string, model: string, knobs: Partial<AgentRunPin> = {}): AgentRunPin => ({ provider, model, ...knobs });
|
|
22
|
+
|
|
16
23
|
test("keeps the user's own order, this list is read, never ranked", () => {
|
|
17
24
|
// The opposite of the quick chain, which sorts by tier and cost. Here the order IS the setting: someone who
|
|
18
25
|
// put Opus above GPT wants Opus first, and a resolver that knew better would spend the wrong account.
|
|
19
|
-
expect(resolveAgentRunModels([CLAUDE, CODEX], [`codex
|
|
26
|
+
expect(resolveAgentRunModels([CLAUDE, CODEX], [pin(`codex`, `gpt-5.6`), pin(`claude`, `claude-opus-5`)])).toEqual([
|
|
20
27
|
{ provider: `codex`, model: `gpt-5.6` },
|
|
21
28
|
{ provider: `claude`, model: `claude-opus-5` },
|
|
22
29
|
]);
|
|
@@ -25,36 +32,45 @@ test("keeps the user's own order, this list is read, never ranked", () => {
|
|
|
25
32
|
test("steps over a provider this sandbox has no credential for", () => {
|
|
26
33
|
// The whole reason the setting is a list. With Claude disconnected the head would otherwise be an account
|
|
27
34
|
// that fails every Fix with agent, while a perfectly good Codex sits underneath it.
|
|
28
|
-
expect(resolveAgentRunModels([offline(CLAUDE), CODEX], [`claude
|
|
35
|
+
expect(resolveAgentRunModels([offline(CLAUDE), CODEX], [pin(`claude`, `claude-opus-5`), pin(`codex`, `gpt-5.6`)])).toEqual([
|
|
29
36
|
{ provider: `codex`, model: `gpt-5.6` },
|
|
30
37
|
]);
|
|
31
38
|
});
|
|
32
39
|
|
|
40
|
+
test("each surviving entry keeps its own knobs, not the head's", () => {
|
|
41
|
+
// The effort used to be one setting beside the list, so a fallback ran at whatever the head was set to. It
|
|
42
|
+
// is now a property of the entry that actually answers, which is the only place it was ever true.
|
|
43
|
+
expect(
|
|
44
|
+
resolveAgentRunModels(
|
|
45
|
+
[offline(CODEX), CLAUDE],
|
|
46
|
+
[pin(`codex`, `gpt-5.6`, { effort: `low` }), pin(`claude`, `claude-opus-5`, { effort: `max`, thinking: true })],
|
|
47
|
+
),
|
|
48
|
+
).toEqual([{ provider: `claude`, model: `claude-opus-5`, effort: `max`, thinking: true }]);
|
|
49
|
+
});
|
|
50
|
+
|
|
33
51
|
test("resolves to nothing when no pin is reachable: it does NOT fall back to whatever is connected", () => {
|
|
34
52
|
// The deliberate difference from resolveQuickModels, which lands on its Auto ladder here. An agent run is
|
|
35
53
|
// billed in whole sessions, so an unreachable list hands the choice back to the caller's floor (the user's
|
|
36
54
|
// own composer pick) rather than spending an account they never pointed at.
|
|
37
|
-
expect(resolveAgentRunModels([offline(CLAUDE), GOOGLE], [`claude
|
|
55
|
+
expect(resolveAgentRunModels([offline(CLAUDE), GOOGLE], [pin(`claude`, `claude-opus-5`)])).toEqual([]);
|
|
38
56
|
});
|
|
39
57
|
|
|
40
58
|
test("an empty list resolves to nothing even with accounts connected", () => {
|
|
41
59
|
expect(resolveAgentRunModels([CLAUDE, CODEX, GOOGLE], [])).toEqual([]);
|
|
42
60
|
});
|
|
43
61
|
|
|
44
|
-
test("drops a duplicate rather than spending two attempts proving one account is out", () => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test("drops a malformed key instead of sending it to a provider", () => {
|
|
51
|
-
expect(resolveAgentRunModels([CLAUDE], [`claude-opus-5`, `claude:`, `:claude-opus-5`, `claude:claude-sonnet-5`])).toEqual([
|
|
52
|
-
{ provider: `claude`, model: `claude-sonnet-5` },
|
|
53
|
-
]);
|
|
62
|
+
test("drops a duplicate rather than spending two attempts proving one account is out, and the first one's knobs are the ones kept", () => {
|
|
63
|
+
// Two entries can now name one model and disagree about how hard it thinks, which is what reordering a list
|
|
64
|
+
// by hand produces. The one the user reads first is the one they meant.
|
|
65
|
+
expect(
|
|
66
|
+
resolveAgentRunModels([CLAUDE], [pin(`claude`, `claude-opus-5`, { effort: `max` }), pin(`claude`, `claude-opus-5`, { effort: `low` })]),
|
|
67
|
+
).toEqual([{ provider: `claude`, model: `claude-opus-5`, effort: `max` }]);
|
|
54
68
|
});
|
|
55
69
|
|
|
56
70
|
test("carries a model id the static catalog has never heard of", () => {
|
|
57
71
|
// The picker offers a custom-id escape hatch, so a pin can name a model released after this build. Second-
|
|
58
72
|
// guessing it here would quietly run something other than what the settings row says.
|
|
59
|
-
expect(resolveAgentRunModels([CLAUDE], [`claude
|
|
73
|
+
expect(resolveAgentRunModels([CLAUDE], [pin(`claude`, `claude-opus-9-preview`)])).toEqual([
|
|
74
|
+
{ provider: `claude`, model: `claude-opus-9-preview` },
|
|
75
|
+
]);
|
|
60
76
|
});
|
package/src/agent-run-model.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { quickModelKey, type QuickModelSource } from "./quick-model.js";
|
|
2
|
+
import type { AgentRunPin } from "./schemas/agent.js";
|
|
2
3
|
|
|
3
4
|
/* WHAT A SURFACE-STARTED AGENT RUN OPENS ON, the resolver for `agentRunModels`, sibling to resolveQuickModels
|
|
4
5
|
* and deliberately not the same function.
|
|
@@ -35,17 +36,30 @@ import { parsePinned, type QuickModelChoice, quickModelKey, type QuickModelSourc
|
|
|
35
36
|
* row renders the stored list, not this one, because a setting that vanished from view would look like the app
|
|
36
37
|
* had eaten it.
|
|
37
38
|
*
|
|
39
|
+
* THE WHOLE PIN SURVIVES, not the pair inside it: the entry's own effort, harness and cost knobs are what the
|
|
40
|
+
* turn is composed from (turn-resume.ts), so a resolver that handed back a bare (provider, model) would silently
|
|
41
|
+
* run the head of the list at the provider's defaults. Nothing here reads or judges those fields, which is the
|
|
42
|
+
* point of carrying them whole.
|
|
43
|
+
*
|
|
38
44
|
* Empty out means nobody has pinned anything this sandbox can reach, and the caller's floor takes over. */
|
|
39
|
-
export const resolveAgentRunModels = (sources: readonly QuickModelSource[], pinned: readonly
|
|
45
|
+
export const resolveAgentRunModels = (sources: readonly QuickModelSource[], pinned: readonly AgentRunPin[]): readonly AgentRunPin[] => {
|
|
40
46
|
const ready = new Set(sources.filter((source) => source.ready).map((source) => source.provider));
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
// Taken verbatim, unvalidated against the catalog, the same reading resolveQuickModels gives its keys and
|
|
48
|
+
// for the same reason: the picker offers a custom-id escape hatch for a model the static catalog has not
|
|
49
|
+
// caught up with, and second-guessing the id here would run a different model than the settings row names.
|
|
50
|
+
const requested = pinned.filter((pin) => ready.has(pin.provider));
|
|
51
|
+
/* The same model twice would spend two attempts proving the same account is out. Hand-edited list, so this
|
|
52
|
+
* is a real state rather than a defensive branch.
|
|
53
|
+
*
|
|
54
|
+
* THE FIRST OF A PAIR WINS, WHOLE. Two entries can now name one model and differ in their knobs (the same
|
|
55
|
+
* Sonnet at Max and again at Low, written while reordering the list), and the one the user reads first is
|
|
56
|
+
* the one they meant; keeping the earlier position with the later entry's effort would run a tier that
|
|
57
|
+
* appears nowhere the pin does. */
|
|
58
|
+
const chain: AgentRunPin[] = [];
|
|
59
|
+
for (const pin of requested) {
|
|
60
|
+
if (!chain.some((held) => quickModelKey(held) === quickModelKey(pin))) {
|
|
61
|
+
chain.push(pin);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return chain;
|
|
51
65
|
};
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/* WHAT EACH AGENTIC LOOP CAN DO, one record per runtime, and nothing about WHICH PROVIDER runs on it.
|
|
2
|
+
*
|
|
3
|
+
* Seven runtimes serve turns behind one seam (AgentRequest in, AgentEvent frames out): the Claude Code Agent SDK
|
|
4
|
+
* loop, Codex app-server, OpenCode (twice, once per channel), Cursor's own loop run in-process, any ACP agent,
|
|
5
|
+
* and Pi's RPC surface. They do NOT do the same things, and for a long time the only thing that said so was a
|
|
6
|
+
* comment inside each adapter, "Ignores the Claude-only request fields", which no surface above it could read.
|
|
7
|
+
* So the composer offered "Ask before each file edit" on a runtime whose every tool call is pre-approved, and
|
|
8
|
+
* offered a reasoning-effort scale to a runtime that drops the field.
|
|
9
|
+
*
|
|
10
|
+
* A capability is listed here only if something READS it: the daemon gates a seam on it, the composer hides or
|
|
11
|
+
* clamps a control by it, or `limitationsOf` tells the user about it. That is the whole point, an ability the
|
|
12
|
+
* matrix claims and nothing consults is how the drift started.
|
|
13
|
+
*
|
|
14
|
+
* THIS FILE IS THE BOTTOM OF THE PROVIDER GRAPH and imports nothing from the rest of the contract, which is
|
|
15
|
+
* what lets provider-specs.ts name these records while schemas/agent.ts reads its provider vocabulary back out
|
|
16
|
+
* of that table. A runtime knows nothing about providers; a provider names two runtimes. Keeping the arrow
|
|
17
|
+
* pointing one way is the whole reason the records live apart from the catalog that reads them.
|
|
18
|
+
*
|
|
19
|
+
* Adding a provider is a row in provider-specs.ts pointing at two of these, not a hunt for literals;
|
|
20
|
+
* agent-catalog.test.ts walks PROVIDERS × HARNESSES and demands one, so a pair can never be silently absent. */
|
|
21
|
+
|
|
22
|
+
// An execution backend: one way a turn runs work of its own, named for the AgentCapabilities.execution axis
|
|
23
|
+
// and for the persona switch that grants it. Adding a language is a member here and a backend in the daemon's
|
|
24
|
+
// execution/ module, never a new one-off tool wired where nothing else can see it.
|
|
25
|
+
export type ExecutionBackend = "shell" | "js";
|
|
26
|
+
|
|
27
|
+
export interface AgentCapabilities {
|
|
28
|
+
// Which agentic loop actually serves the turn, the question "is the harness `claude-code`" only looks like.
|
|
29
|
+
// Claude is always its own Claude Code loop and Kimi has no native runtime, so both run it whatever harness
|
|
30
|
+
// the client sent; codex/grok/gemini each have a native runtime to switch away from. Names the session store
|
|
31
|
+
// a finished conversation's transcript is backfilled from, too.
|
|
32
|
+
//
|
|
33
|
+
// `opencode-gemini` is the OpenCode loop pointed at Gemini rather than at xAI, and it is a SEPARATE runtime
|
|
34
|
+
// id from `opencode` on purpose: adapter health is keyed by this field (adapter-health.ts), so sharing one
|
|
35
|
+
// would make Grok's xAI credential decide whether the picker greys out Gemini, and the reverse.
|
|
36
|
+
readonly runtime: "claude-code" | "codex" | "opencode" | "opencode-gemini" | "acp" | "pi" | "cursor";
|
|
37
|
+
// Mid-turn injection (the SteeringQueue behind /agent/steer). Needs the SDK's streaming-input mode.
|
|
38
|
+
readonly steering: boolean;
|
|
39
|
+
// How much of the permission-mode axis the runtime honours. "modes" = every PermissionMode, with per-tool
|
|
40
|
+
// permission cards and `mode` frames when the agent moves itself; "plan" = propose-then-approve or run, and
|
|
41
|
+
// nothing in between, the container is the isolation boundary and every tool call is pre-approved.
|
|
42
|
+
readonly permissions: "modes" | "plan";
|
|
43
|
+
// Can stop mid-turn and ask the user a multiple-choice question (`question` frames).
|
|
44
|
+
readonly questions: boolean;
|
|
45
|
+
/* Which of the turn's tools reach the agent. "full" = http MCP tools + in-process SDK servers + plugin
|
|
46
|
+
* checkouts + the browser servers; "tools" = all of that EXCEPT plugin checkouts; "browser" = the
|
|
47
|
+
* process-backed browser servers alone; "http" = the http MCP tools alone, and only if the agent advertises
|
|
48
|
+
* http MCP support; "none" = the runtime has no seam for them at all. Keeping the partial answers distinct
|
|
49
|
+
* matters: a runtime that can drive a connected account must not be described as tool-less, and one that
|
|
50
|
+
* cannot host daemon-side SDK servers must not claim full.
|
|
51
|
+
*
|
|
52
|
+
* "tools" exists for the Cursor runtime and would have been a lie either way without it. Cursor's SDK takes
|
|
53
|
+
* stdio AND http/sse MCP servers, and its `customTools` run host callbacks in this process, which is the
|
|
54
|
+
* seam an in-process SDK server needs, so calling it "browser" would understate it by three whole
|
|
55
|
+
* categories. What it genuinely cannot host is a Claude Code PLUGIN checkout: that is a directory layout the
|
|
56
|
+
* Agent SDK loads, not a protocol, and no other runtime will ever read one. So the gap is real, permanent
|
|
57
|
+
* and worth its own word rather than being rounded to "full". */
|
|
58
|
+
readonly mcp: "full" | "tools" | "browser" | "http" | "none";
|
|
59
|
+
/* WHICH EXECUTION BACKENDS THE RUNTIME HOSTS, the ways a turn RUNS things, as opposed to the tools it is
|
|
60
|
+
* handed. "shell" is the runtime's own command tool (Bash on the Claude Code loop, each foreign loop's
|
|
61
|
+
* equivalent); "js" is the sandbox's JavaScript backend (execution/ in the daemon): the model writes a
|
|
62
|
+
* script instead of a command line, and the daemon runs it in a permission-fenced Node subprocess.
|
|
63
|
+
*
|
|
64
|
+
* A first-class axis rather than a corollary of `mcp`, because the two answer different questions: `mcp`
|
|
65
|
+
* says which TOOLS reach the model's context, this says which ways of EXECUTING the daemon can stand
|
|
66
|
+
* behind for this runtime, with the same guard, secret and persona seams the shell gets. A runtime that
|
|
67
|
+
* cannot host a backend simply never shows it, and the persona switch for it (PersonaPowersSchema.code)
|
|
68
|
+
* then has nothing to grant there. */
|
|
69
|
+
readonly execution: readonly ExecutionBackend[];
|
|
70
|
+
// Reasoning-effort selection is forwarded to the model.
|
|
71
|
+
readonly effort: boolean;
|
|
72
|
+
/* The runtime can serve a turn at fast speed when asked (AgentTurn.fast). A statement about the LOOP, not
|
|
73
|
+
* about the route: the Claude Code loop knows how to ask for it, which is why every provider this record
|
|
74
|
+
* hands the loop to reads true here, including the ones served through the translator, whose turns the
|
|
75
|
+
* harness will then refuse fast mode for because a translator endpoint is not first-party. That second
|
|
76
|
+
* question is answered where the endpoint is decided (planHarnessTurn), because it is a fact about the
|
|
77
|
+
* CREDENTIAL rather than about the runtime, and this record is a pure function of (provider, harness). */
|
|
78
|
+
readonly fastMode: boolean;
|
|
79
|
+
// How an isolated conversation's worktree is enforced. "namespace" = the worktree IS /work inside the turn's
|
|
80
|
+
// mount namespace (with the tool-input rewrite as the fallback when the container can't build one); "cwd" =
|
|
81
|
+
// the turn is merely cwd'd into the worktree, so an absolute /work path still reaches the shared checkout,
|
|
82
|
+
// which is why those turns are told where their tree is (turn-preamble.ts).
|
|
83
|
+
readonly isolation: "namespace" | "cwd";
|
|
84
|
+
// Publishes its slash commands (`commands` frames) for the composer's `/` popover.
|
|
85
|
+
readonly commands: boolean;
|
|
86
|
+
// Runs its shell in a tmux session the terminal panel can attach to (`terminal` frames).
|
|
87
|
+
readonly terminals: boolean;
|
|
88
|
+
// Fails with the coded frames the daemon's auto-resume keys off (rate_limit, provider-outage), so a turn the
|
|
89
|
+
// provider killed is re-run once the breaker says the provider is back (turn-resume.ts).
|
|
90
|
+
readonly recovery: boolean;
|
|
91
|
+
/* HOW MUCH OF ITS STANDING INSTRUCTIONS THIS RUNTIME WILL TAKE FROM US, the axis behind the sandbox's
|
|
92
|
+
* system-prompt setting (SandboxSettings.systemPromptMode) and the persona's own override.
|
|
93
|
+
*
|
|
94
|
+
* It exists because that setting was silently a Claude Code setting. The composer offers Codex, Grok and
|
|
95
|
+
* Gemini on their own runtimes, and a turn on any of them ignored the prompt the owner had written without
|
|
96
|
+
* saying so anywhere, the one failure mode a settings page cannot recover from, because nothing on screen
|
|
97
|
+
* is wrong. Naming it here means every surface reads the same answer and the daemon composes to it
|
|
98
|
+
* (agent/system-prompt.ts), rather than each learning the exception separately.
|
|
99
|
+
*
|
|
100
|
+
* "replace", the whole base prompt can be swapped for the owner's text, and extra guidance appended on
|
|
101
|
+
* top of whichever base is in force. The Claude Code loop (SDK `systemPrompt`) and native
|
|
102
|
+
* Codex (`model_instructions_file` replaces its base; `developer_instructions` adds a
|
|
103
|
+
* developer message, both verified on the wire against codex-cli 0.147).
|
|
104
|
+
* "append" , extra system text only; the runtime's own base prompt stands. OpenCode takes one per
|
|
105
|
+
* message (`system` on the prompt body), and there is no seam for replacing its base.
|
|
106
|
+
* "none" , no system seam at all. What must still reach the model (the persona note) rides the user
|
|
107
|
+
* message instead, which is the door the delegation note already uses.
|
|
108
|
+
*
|
|
109
|
+
* The BASE CHOICE. Intentic's prompt or Claude Code's, is a "replace" runtime's question and, of those,
|
|
110
|
+
* only the Claude Code loop's: Codex's own base describes Codex's own tools, so swapping it for a prompt
|
|
111
|
+
* written about another harness is the owner's deliberate act (their custom text), never ours. */
|
|
112
|
+
readonly instructions: "replace" | "append" | "none";
|
|
113
|
+
/* HOW THIS RUNTIME DISCOVERS THE WORKSPACE'S LOADED SKILLS.
|
|
114
|
+
*
|
|
115
|
+
* "native", the runtime scans one of the filesystem projections itself: `.agents/skills/` for Codex,
|
|
116
|
+
* `.claude/skills/` for the Claude Code loop. Its own loader injects the catalogue and reads
|
|
117
|
+
* the matching SKILL.md on demand, so adding our own note would duplicate it.
|
|
118
|
+
* "prompt", the runtime has no loader the daemon can rely on. turn-plan.ts puts the same name,
|
|
119
|
+
* description and absolute SKILL.md path into the opening user-message preamble. This is a
|
|
120
|
+
* separate axis from `instructions`: Pi and ACP take no system prompt at all, while OpenCode
|
|
121
|
+
* and Cursor take an append, but all four still need skill discovery. */
|
|
122
|
+
readonly skillDiscovery: "native" | "prompt";
|
|
123
|
+
/* WHETHER THE OWNER'S SAFETY POLICY REACHES THIS RUNTIME (the document at .intentic/config/safety.md, read
|
|
124
|
+
* by the judge in agent/command-judge.ts, delivered by guard/command-gate.ts).
|
|
125
|
+
*
|
|
126
|
+
* It exists because the policy is silently a Claude Code policy without it. The gate is a PreToolUse hook,
|
|
127
|
+
* which is an Agent SDK seam, so an owner whose policy says "ask before force-pushing" was asked on a Claude
|
|
128
|
+
* turn and never on a Codex, Grok, Gemini, Pi or ACP one, with nothing on screen saying so. Same failure
|
|
129
|
+
* mode as the `instructions` axis above, and the same fix: name it once, let every surface read it.
|
|
130
|
+
*
|
|
131
|
+
* "hooks" , the runtime's own pre-execution hook carries the verdict and an ASK can park the call.
|
|
132
|
+
* The Claude Code loop, whose PreToolUse hook fires even under bypassPermissions.
|
|
133
|
+
* "approval" , the vendor publishes a per-call approval channel the daemon answers from the same
|
|
134
|
+
* policy, and an ask parks on a card because the vendor is blocked on the answer
|
|
135
|
+
* (Codex's `item/commandExecution/requestApproval`, ACP's `session/request_permission`).
|
|
136
|
+
* Weaker than "hooks" in one stated way: the vendor decides WHICH calls it asks about, so a
|
|
137
|
+
* command it never raises is one the policy cannot see. What it does raise is judged by
|
|
138
|
+
* the same judge.
|
|
139
|
+
* "refuse-only", the same channel, but the vendor puts a CLOCK on the wait, so a hold cannot park and
|
|
140
|
+
* arrives as a refusal instead. OpenCode's turn has an inactivity watchdog that reads a
|
|
141
|
+
* paused approval as a stalled turn; a card there would break the turn rather than gate it.
|
|
142
|
+
* `refuse` verdicts work fully; an `ask` stops the command and says it could not ask.
|
|
143
|
+
* "none" , the runtime publishes no seam before it runs a command, so no policy can apply. Pi runs
|
|
144
|
+
* its bash in-process with no approval channel at all.
|
|
145
|
+
*
|
|
146
|
+
* The taint bit rides this axis too: a runtime with no consult has no place to hand the judge its facts,
|
|
147
|
+
* which is why `conversationTainted` must read a "none" runtime as tainted rather than as clean
|
|
148
|
+
* (guard/turn-taint.ts). */
|
|
149
|
+
readonly rulebook: "hooks" | "approval" | "refuse-only" | "none";
|
|
150
|
+
/* WHETHER A STORED CREDENTIAL IS MASKED IN WHAT THIS RUNTIME'S MODEL READS (secrets/secret-registry.ts and
|
|
151
|
+
* the two seams around it).
|
|
152
|
+
*
|
|
153
|
+
* "masked" is the full round trip: every stored value is replaced by its `{{secret:name}}` reference on the
|
|
154
|
+
* way into the model's context, and the same reference resolves back to the value at the two exits that
|
|
155
|
+
* spend it (a shell command, a script). The Claude Code loop, via PostToolUse and PreToolUse.
|
|
156
|
+
*
|
|
157
|
+
* "none" is a STRUCTURAL limit, not an unfinished wire, and it is the reason this axis is honest rather
|
|
158
|
+
* than aspirational. On every other runtime the tool runs inside the VENDOR'S own loop: the model has read
|
|
159
|
+
* the result before the daemon sees any frame about it, so there is no seam left to rewrite. A PostToolUse
|
|
160
|
+
* hook is the only thing that can edit what a model reads, and only the Claude Code loop has one. Nothing
|
|
161
|
+
* about wiring more transports changes that, which is why the answer here is a disclosure and the real fix
|
|
162
|
+
* is to stop putting credentials where a vendor's tool can read them at all.
|
|
163
|
+
*
|
|
164
|
+
* Read by limitationsOf, and by agent/system-prompt.ts, which must not teach the reference language to a
|
|
165
|
+
* runtime that has no exit for it. */
|
|
166
|
+
readonly secrets: "masked" | "none";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// The Claude Code Agent SDK loop, the ceiling every other runtime is measured against, and the only one that
|
|
170
|
+
// owns the whole request: permission callbacks, the ask tool, plugins, hooks, and the spawn seam a mount
|
|
171
|
+
// namespace needs.
|
|
172
|
+
export const CLAUDE_CODE: AgentCapabilities = {
|
|
173
|
+
runtime: "claude-code",
|
|
174
|
+
steering: true,
|
|
175
|
+
permissions: "modes",
|
|
176
|
+
questions: true,
|
|
177
|
+
mcp: "full",
|
|
178
|
+
// The one loop with a seam the daemon can put its own backend through, so it hosts the JS backend beside
|
|
179
|
+
// its Bash. Every other runtime below hosts only its own shell.
|
|
180
|
+
execution: ["shell", "js"],
|
|
181
|
+
effort: true,
|
|
182
|
+
fastMode: true,
|
|
183
|
+
isolation: "namespace",
|
|
184
|
+
commands: true,
|
|
185
|
+
terminals: true,
|
|
186
|
+
recovery: true,
|
|
187
|
+
instructions: "replace",
|
|
188
|
+
skillDiscovery: "native",
|
|
189
|
+
// The only runtime with a pre-execution hook of its own, which is why it is the only one where a HOLD can
|
|
190
|
+
// park the call and wait for a card rather than having to refuse it.
|
|
191
|
+
rulebook: "hooks",
|
|
192
|
+
secrets: "masked",
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/* Codex app-server: item-level events, process-backed MCP servers, and the four interactive seams its protocol
|
|
196
|
+
* actually publishes, `turn/steer` for mid-turn injection, the experimental `item/tool/requestUserInput` server
|
|
197
|
+
* request behind a question card, `skills/list` for the `/` popover (a picked command rides back as a structured
|
|
198
|
+
* skill input), and the same mount namespace the Claude Code loop gets, because app-server is a child process
|
|
199
|
+
* the adapter spawns and nsenter can put it in the turn's namespace like any other.
|
|
200
|
+
*
|
|
201
|
+
* Browser servers ride the per-thread config; daemon-side SDK servers, plugins and server-initiated APPROVALS
|
|
202
|
+
* stay unwired, the container is the isolation boundary, so approvals are declined by design rather than
|
|
203
|
+
* missing (codex-app-server.ts refuses every server request but the question one). */
|
|
204
|
+
export const CODEX: AgentCapabilities = {
|
|
205
|
+
runtime: "codex",
|
|
206
|
+
steering: true,
|
|
207
|
+
permissions: "plan",
|
|
208
|
+
questions: true,
|
|
209
|
+
mcp: "browser",
|
|
210
|
+
execution: ["shell"],
|
|
211
|
+
effort: true,
|
|
212
|
+
fastMode: false,
|
|
213
|
+
isolation: "namespace",
|
|
214
|
+
commands: true,
|
|
215
|
+
terminals: false,
|
|
216
|
+
recovery: false,
|
|
217
|
+
/* Both halves, through the per-thread `config` block the adapter already sends: `model_instructions_file`
|
|
218
|
+
* takes the place of Codex's own base prompt, `developer_instructions` arrives as an extra developer
|
|
219
|
+
* message ahead of its skills and team blocks. Verified against codex-cli 0.147 by reading what actually
|
|
220
|
+
* reached the wire, the keys are undocumented, and a strings dump proves only that they parse. */
|
|
221
|
+
instructions: "replace",
|
|
222
|
+
skillDiscovery: "native",
|
|
223
|
+
/* App-server publishes `item/commandExecution/requestApproval`, whose params carry the command text, and
|
|
224
|
+
* takes `accept`/`decline` back (codex-cli 0.147's own generated JSON Schema, read with
|
|
225
|
+
* `codex app-server generate-json-schema`). The daemon only asks Codex to raise those requests when the
|
|
226
|
+
* owner has written command rules, so an unconfigured workspace keeps `approvalPolicy: "never"` and pays
|
|
227
|
+
* nothing (codex/codex-agent.ts threadOptions). */
|
|
228
|
+
rulebook: "approval",
|
|
229
|
+
secrets: "none",
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// OpenCode (the Grok runtime): its own agentic loop, its own tools, allow-all permissions. It takes a model id,
|
|
233
|
+
// a prompt and one system message of ours, no effort scale, no tools of ours, no command list.
|
|
234
|
+
export const OPENCODE: AgentCapabilities = {
|
|
235
|
+
runtime: "opencode",
|
|
236
|
+
steering: false,
|
|
237
|
+
permissions: "plan",
|
|
238
|
+
questions: false,
|
|
239
|
+
mcp: "none",
|
|
240
|
+
execution: ["shell"],
|
|
241
|
+
effort: false,
|
|
242
|
+
fastMode: false,
|
|
243
|
+
isolation: "cwd",
|
|
244
|
+
commands: false,
|
|
245
|
+
terminals: false,
|
|
246
|
+
recovery: false,
|
|
247
|
+
// `system` on the prompt body, per message. It ADDS to OpenCode's own prompt, there is no seam for
|
|
248
|
+
// replacing that, so a custom prompt lands here as extra instructions, and the settings page says so
|
|
249
|
+
// rather than letting "replaces everything" quietly mean something else on two providers.
|
|
250
|
+
instructions: "append",
|
|
251
|
+
skillDiscovery: "prompt",
|
|
252
|
+
/* OpenCode asks over its own permission channel (`permission.updated`, replied on
|
|
253
|
+
* `/session/{id}/permissions/{permissionID}`, vocabulary once/always/reject), and the daemon judges what it
|
|
254
|
+
* raises with the same decide fn every other runtime uses.
|
|
255
|
+
*
|
|
256
|
+
* REFUSE-ONLY because of its watchdog, not because of its protocol. A Grok/Gemini turn is aborted after two
|
|
257
|
+
* minutes without an event for its session (grok/grok-agent.ts GROK_INACTIVITY_MS), and a permission paused
|
|
258
|
+
* on a person is exactly that silence, so a parked card would turn "ask me" into a broken turn. A `deny`
|
|
259
|
+
* rule is enforced in full; a `hold` stops the command and tells the agent it could not be asked about. */
|
|
260
|
+
rulebook: "refuse-only",
|
|
261
|
+
secrets: "none",
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/* The same OpenCode loop, serving Gemini instead of xAI, identical abilities, which is the point of giving it
|
|
265
|
+
* its own row rather than its own record shape.
|
|
266
|
+
*
|
|
267
|
+
* It exists because the alternative was Gemini's ONLY route being the Claude Code loop, and that loop announces
|
|
268
|
+
* itself to whatever it is pointed at: the CLI prepends its own "You are a Claude agent, built on Anthropic's
|
|
269
|
+
* Claude Agent SDK." to every request, baked into the binary with no option to suppress it. Google's Antigravity
|
|
270
|
+
* channel matches that exact sentence and refuses the request, reported as a quota error, which sent the
|
|
271
|
+
* translator walking all 31 connected accounts looking for one with room, ~60s per attempt, none of which could
|
|
272
|
+
* ever have answered. Under this runtime the request carries OpenCode's own prompt, so the turn is simply not
|
|
273
|
+
* Claude Code traffic and the block has nothing to match.
|
|
274
|
+
*
|
|
275
|
+
* The credential is unchanged: both harnesses reach Google through the translator and the same auth files. Only
|
|
276
|
+
* the loop around the model differs. */
|
|
277
|
+
export const OPENCODE_GEMINI: AgentCapabilities = {
|
|
278
|
+
...OPENCODE,
|
|
279
|
+
runtime: "opencode-gemini",
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Any agent speaking the Agent Client Protocol: a documented floor rather than the native ceiling. It publishes
|
|
283
|
+
// commands, runs its terminals in the conversation's tmux session, and takes our http MCP tools when it says it
|
|
284
|
+
// can, but it owns its own model, effort and permission posture.
|
|
285
|
+
export const ACP: AgentCapabilities = {
|
|
286
|
+
runtime: "acp",
|
|
287
|
+
steering: false,
|
|
288
|
+
permissions: "plan",
|
|
289
|
+
questions: false,
|
|
290
|
+
mcp: "http",
|
|
291
|
+
execution: ["shell"],
|
|
292
|
+
effort: false,
|
|
293
|
+
fastMode: false,
|
|
294
|
+
isolation: "cwd",
|
|
295
|
+
commands: true,
|
|
296
|
+
terminals: true,
|
|
297
|
+
recovery: false,
|
|
298
|
+
// ACP's `session/new` and `session/prompt` carry no system field: the agent owns its own instructions the
|
|
299
|
+
// same way it owns its model and its permission posture. The persona note takes the user message instead.
|
|
300
|
+
instructions: "none",
|
|
301
|
+
skillDiscovery: "prompt",
|
|
302
|
+
/* `session/request_permission` is in the protocol floor, so every conforming agent has the channel and the
|
|
303
|
+
* daemon answers it from the rulebook (acp/acp-permissions.ts). The caveat the "approval" value already
|
|
304
|
+
* carries is at its widest here: WHICH calls an agent asks about is entirely the agent's choice, and one
|
|
305
|
+
* that never asks is one no rule can reach. */
|
|
306
|
+
rulebook: "approval",
|
|
307
|
+
secrets: "none",
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// Pi driven over its RPC mode (`pi --mode rpc`, strict-LF JSONL over stdio): above the ACP floor and below the
|
|
311
|
+
// Claude Code ceiling. Its `steer` command is real mid-turn injection; `set_thinking_level` takes the effort
|
|
312
|
+
// tiers; `get_commands` publishes its extension/skill commands. It has no MCP seam (Pi's own extensions are its
|
|
313
|
+
// tool surface), no approval channel (plan is the shared two-phase emulation), and runs bash in-process, no
|
|
314
|
+
// tmux session for the terminal panel to attach to.
|
|
315
|
+
export const PI: AgentCapabilities = {
|
|
316
|
+
runtime: "pi",
|
|
317
|
+
steering: true,
|
|
318
|
+
permissions: "plan",
|
|
319
|
+
questions: false,
|
|
320
|
+
mcp: "none",
|
|
321
|
+
execution: ["shell"],
|
|
322
|
+
effort: true,
|
|
323
|
+
fastMode: false,
|
|
324
|
+
isolation: "cwd",
|
|
325
|
+
commands: true,
|
|
326
|
+
terminals: false,
|
|
327
|
+
recovery: false,
|
|
328
|
+
// Pi's RPC opens a session with a prompt and steers it; nothing in that protocol sets standing
|
|
329
|
+
// instructions, so like ACP it hears the persona note through the user message.
|
|
330
|
+
instructions: "none",
|
|
331
|
+
skillDiscovery: "prompt",
|
|
332
|
+
/* THE ONE RUNTIME WITH NO SEAM AT ALL. Pi runs bash in-process and its RPC publishes no approval request,
|
|
333
|
+
* so there is nothing to consult before a command runs and no rule the owner writes can apply here. Said
|
|
334
|
+
* out loud rather than left to be discovered: limitationsOf renders it, and the taint floor treats a "none"
|
|
335
|
+
* runtime as permanently tainted, because a bit nobody can act on is worse than no bit. */
|
|
336
|
+
rulebook: "none",
|
|
337
|
+
secrets: "none",
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/* CURSOR'S OWN AGENT RUNTIME, driven through `@cursor/sdk`, the SDK Anysphere publishes, in this daemon's own
|
|
341
|
+
* process. The second-richest row in this file after the Claude Code loop, and the reason is the SDK rather
|
|
342
|
+
* than the vendor: it is an EMBEDDING surface, not a CLI wrapped in a pipe, so most of the seams the other
|
|
343
|
+
* foreign runtimes lack are simply function arguments here.
|
|
344
|
+
*
|
|
345
|
+
* WHY NOT THROUGH OPENCODE, which is already in this image and already serves two providers. Every Cursor
|
|
346
|
+
* bridge for OpenCode is a community reverse-engineering of Cursor's private agent RPC or a localhost shim
|
|
347
|
+
* around its CLI, and the OPENCODE record above is the weakest in this file. Routing Cursor through it would
|
|
348
|
+
* have capped a first-party SDK at Grok's ceiling and made the row depend on a third party's spare time.
|
|
349
|
+
*
|
|
350
|
+
* WHY THE HARNESS AXIS DOESN'T APPLY, the same way it doesn't for Gemini, and for the mirror-image reason.
|
|
351
|
+
* Gemini has no Claude Code route because Google refuses that traffic; Cursor has none because there is no
|
|
352
|
+
* translator route at all, CLIProxyAPI does not serve Cursor as a provider (asked for repeatedly upstream and
|
|
353
|
+
* closed as not planned), and Cursor publishes no OpenAI-compatible endpoint on a subscription. The SDK IS the
|
|
354
|
+
* only door, so `capabilitiesOf` answers this record whatever harness the client sent.
|
|
355
|
+
*
|
|
356
|
+
* The three axes below that read weaker than they could are deliberate, not unfinished: see the notes on each. */
|
|
357
|
+
export const CURSOR: AgentCapabilities = {
|
|
358
|
+
runtime: "cursor",
|
|
359
|
+
// The SDK's Run can be cancelled but not written to mid-flight: a second `send` on a busy agent is an
|
|
360
|
+
// AgentBusyError, not an injection. So the steering queue has nowhere to go and the composer hides it.
|
|
361
|
+
steering: false,
|
|
362
|
+
/* Cursor's OWN plan mode (`mode: "agent" | "plan"`), not this repo's two-phase emulation, which is the
|
|
363
|
+
* better version of the same bargain: the model is put in a read-only posture by the vendor rather than
|
|
364
|
+
* being asked to behave.
|
|
365
|
+
*
|
|
366
|
+
* Not "modes", and that is the honest half. The hook seam below can gate shell, MCP, file reads and file
|
|
367
|
+
* edits, which is most of the tool surface but not all of it, and a per-tool posture with a silent gap in
|
|
368
|
+
* it is worse than one that says where it stops. */
|
|
369
|
+
permissions: "plan",
|
|
370
|
+
/* TRUE BECAUSE WE SUPPLY THE TOOL, not because Cursor's own askQuestion is wired. That one is put in
|
|
371
|
+
* `disallowedTools`: in a headless run it has been reported to answer itself with a fabricated "Questions
|
|
372
|
+
* skipped by the user", which is the single worst failure shape available here, an agent acting on consent
|
|
373
|
+
* nobody gave. The ask tool the daemon registers through `customTools` runs in this process, parks on a
|
|
374
|
+
* real card, and cannot invent an answer because it is the thing that receives one. */
|
|
375
|
+
questions: true,
|
|
376
|
+
// stdio + http/sse MCP servers, plus host callbacks through `customTools` (which is where the browser stack
|
|
377
|
+
// and the in-process SDK servers land). Everything but a Claude Code plugin checkout, see the axis note.
|
|
378
|
+
mcp: "tools",
|
|
379
|
+
execution: ["shell"],
|
|
380
|
+
/* Cursor publishes effort as MODEL PARAMETERS rather than as one scale (`ModelListItem.parameters` /
|
|
381
|
+
* `variants` → `ModelSelection.params`), so the shared tiers are mapped onto whatever the selected model
|
|
382
|
+
* declares, and a model that declares none simply offers no control. True here because the axis is
|
|
383
|
+
* forwardable at all; which tiers exist is the live catalog's answer, not this record's. */
|
|
384
|
+
effort: true,
|
|
385
|
+
fastMode: false,
|
|
386
|
+
/* "cwd", and this is the one place the SDK's in-process design costs something. A namespace is built around
|
|
387
|
+
* a CHILD the daemon spawns (that is how the Claude Code loop and Codex app-server get theirs); Cursor's
|
|
388
|
+
* loop runs inside the daemon, whose own /work must stay the shared checkout, so an isolated conversation
|
|
389
|
+
* gets its worktree by working directory and the turn is told where its tree is (turn-preamble.ts). */
|
|
390
|
+
isolation: "cwd",
|
|
391
|
+
// Cursor's commands are files on disk (`.cursor/commands`), which the SDK loads but does not publish back,
|
|
392
|
+
// so there is no list to hand the `/` popover.
|
|
393
|
+
commands: false,
|
|
394
|
+
// The SDK runs its shell in-process; there is no tmux session for the terminal panel to attach to.
|
|
395
|
+
terminals: false,
|
|
396
|
+
// The SDK throws typed errors (RateLimitError and friends) rather than dissolving a refusal into prose, so
|
|
397
|
+
// the adapter can file the coded frames auto-resume keys off.
|
|
398
|
+
recovery: true,
|
|
399
|
+
/* "append", the OpenCode answer, reached by a completely different road. There is no system-prompt argument
|
|
400
|
+
* on `Agent.create`; what there is, is the `beforeSubmitPrompt` hook, whose reply carries
|
|
401
|
+
* `additional_context` that is folded into the request. So the owner's prompt and the persona note DO reach
|
|
402
|
+
* the model, on top of Cursor's own base prompt, and nothing can replace that base. */
|
|
403
|
+
instructions: "append",
|
|
404
|
+
skillDiscovery: "prompt",
|
|
405
|
+
/* THE FULL HOOK TIER, the only foreign runtime that reaches it. Cursor reads `.cursor/hooks.json` in its
|
|
406
|
+
* local runtime, and `beforeShellExecution` answers with `allow` / `deny` / `ask` plus the messages that
|
|
407
|
+
* explain it, with `failClosed` available so a crashed gate blocks instead of waving the command through.
|
|
408
|
+
*
|
|
409
|
+
* What earns "hooks" rather than "approval" is that a HOLD can genuinely park: the hook is a process the
|
|
410
|
+
* daemon wrote, so it blocks on the card and the vendor is simply waiting on a script, exactly the shape
|
|
411
|
+
* that makes the Claude Code loop's PreToolUse hook able to stop and ask. The vendor never decides which
|
|
412
|
+
* calls to raise, either, which is the caveat the "approval" tier carries and this one does not. */
|
|
413
|
+
rulebook: "hooks",
|
|
414
|
+
/* "none", and structurally so, like every other foreign runtime. Masking needs a seam that rewrites what
|
|
415
|
+
* the model READS after a tool ran; Cursor's `afterShellExecution` fires with the output but its reply is
|
|
416
|
+
* discarded upstream, and `beforeReadFile` sees the content only to allow or deny it. Both are gates, not
|
|
417
|
+
* filters, so there is nothing here to substitute a reference back into. */
|
|
418
|
+
secrets: "none",
|
|
419
|
+
};
|