@intentic/sandbox-contract 1.176.2 → 1.209.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 +65 -0
- package/dist/agent-catalog.d.ts +15 -2
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +50 -1
- package/dist/agent-catalog.js.map +1 -1
- package/dist/agent-run-model.d.ts +3 -0
- package/dist/agent-run-model.d.ts.map +1 -0
- package/dist/agent-run-model.js +10 -0
- package/dist/agent-run-model.js.map +1 -0
- package/dist/capability-env.d.ts +2 -0
- package/dist/capability-env.d.ts.map +1 -0
- package/dist/capability-env.js +2 -0
- package/dist/capability-env.js.map +1 -0
- package/dist/chores/chores.d.ts +9 -0
- package/dist/chores/chores.d.ts.map +1 -1
- package/dist/chores/chores.js +257 -18
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/digest.js +0 -0
- package/dist/chores/digest.js.map +1 -1
- package/dist/chores/extension-update.d.ts +9 -0
- package/dist/chores/extension-update.d.ts.map +1 -0
- package/dist/chores/extension-update.js +15 -0
- package/dist/chores/extension-update.js.map +1 -0
- package/dist/chores/fix-deps.d.ts +10 -0
- package/dist/chores/fix-deps.d.ts.map +1 -0
- package/dist/chores/fix-deps.js +18 -0
- package/dist/chores/fix-deps.js.map +1 -0
- package/dist/chores/index.d.ts +7 -2
- package/dist/chores/index.d.ts.map +1 -1
- package/dist/chores/index.js +4 -1
- package/dist/chores/index.js.map +1 -1
- package/dist/chores/probes.d.ts +1 -0
- package/dist/chores/probes.d.ts.map +1 -1
- package/dist/chores/probes.js +131 -3
- package/dist/chores/probes.js.map +1 -1
- package/dist/chores/stack.d.ts +27 -0
- package/dist/chores/stack.d.ts.map +1 -0
- package/dist/chores/stack.js +121 -0
- package/dist/chores/stack.js.map +1 -0
- package/dist/chores/verdict.d.ts +2 -1
- package/dist/chores/verdict.d.ts.map +1 -1
- package/dist/chores/verdict.js +57 -7
- package/dist/chores/verdict.js.map +1 -1
- package/dist/contract-lock.d.ts +3 -0
- package/dist/contract-lock.d.ts.map +1 -0
- package/dist/contract-lock.js +35 -0
- package/dist/contract-lock.js.map +1 -0
- package/dist/contracts/activity.contract.d.ts +10 -0
- package/dist/contracts/activity.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +230 -20
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +2 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +847 -1
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +3 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts +89 -0
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.js +3 -1
- package/dist/contracts/automations.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +108 -32
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +5 -1
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/chores.contract.d.ts +43 -0
- package/dist/contracts/chores.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.d.ts +5 -0
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.js +2 -2
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/claude.contract.d.ts +3 -14
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.js +2 -3
- package/dist/contracts/claude.contract.js.map +1 -1
- package/dist/contracts/drafts.contract.d.ts +4 -0
- package/dist/contracts/drafts.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts +28 -12
- package/dist/contracts/endpoints.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.js +9 -0
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +399 -69
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.js +15 -1
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/git.contract.d.ts +209 -11
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +12 -2
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts +0 -13
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/grok.contract.js +1 -2
- package/dist/contracts/grok.contract.js.map +1 -1
- package/dist/contracts/host.contract.d.ts +39 -0
- package/dist/contracts/host.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.js +3 -2
- package/dist/contracts/host.contract.js.map +1 -1
- package/dist/contracts/loops.contract.d.ts +343 -0
- package/dist/contracts/loops.contract.d.ts.map +1 -0
- package/dist/contracts/loops.contract.js +11 -0
- package/dist/contracts/loops.contract.js.map +1 -0
- package/dist/contracts/panels.contract.d.ts +6 -0
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/personas.contract.d.ts +112 -0
- package/dist/contracts/personas.contract.d.ts.map +1 -0
- package/dist/contracts/personas.contract.js +13 -0
- package/dist/contracts/personas.contract.js.map +1 -0
- package/dist/contracts/ports.contract.d.ts +1 -0
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.d.ts +1 -0
- package/dist/contracts/prepush.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.js.map +1 -1
- package/dist/contracts/{codex.contract.d.ts → providers.contract.d.ts} +11 -3
- package/dist/contracts/providers.contract.d.ts.map +1 -0
- package/dist/contracts/providers.contract.js +6 -0
- package/dist/contracts/providers.contract.js.map +1 -0
- package/dist/contracts/public.contract.d.ts +24 -0
- package/dist/contracts/public.contract.d.ts.map +1 -0
- package/dist/contracts/public.contract.js +8 -0
- package/dist/contracts/public.contract.js.map +1 -0
- package/dist/contracts/push.contract.d.ts +3 -3
- package/dist/contracts/secrets.contract.d.ts +9 -0
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +16 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.js +1 -1
- package/dist/contracts/sessions.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts +352 -58
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +2 -1
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/share.contract.d.ts +55 -0
- package/dist/contracts/share.contract.d.ts.map +1 -0
- package/dist/contracts/share.contract.js +9 -0
- package/dist/contracts/share.contract.js.map +1 -0
- package/dist/contracts/skills.contract.d.ts +41 -0
- package/dist/contracts/skills.contract.d.ts.map +1 -0
- package/dist/contracts/skills.contract.js +9 -0
- package/dist/contracts/skills.contract.js.map +1 -0
- package/dist/contracts/system.contract.d.ts +218 -22
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +10 -2
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/workflows.contract.d.ts +575 -0
- package/dist/contracts/workflows.contract.d.ts.map +1 -0
- package/dist/contracts/workflows.contract.js +13 -0
- package/dist/contracts/workflows.contract.js.map +1 -0
- package/dist/contracts/workspace.contract.d.ts +18 -4
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +6 -3
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/conversation-ids.d.ts +2 -0
- package/dist/conversation-ids.d.ts.map +1 -0
- package/dist/conversation-ids.js +113 -0
- package/dist/conversation-ids.js.map +1 -0
- package/dist/events.d.ts +523 -17
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +106 -13
- package/dist/events.js.map +1 -1
- package/dist/history-state.d.ts +3 -0
- package/dist/history-state.d.ts.map +1 -0
- package/dist/history-state.js +53 -0
- package/dist/history-state.js.map +1 -0
- package/dist/hostnames.d.ts +4 -0
- package/dist/hostnames.d.ts.map +1 -1
- package/dist/hostnames.js +7 -2
- package/dist/hostnames.js.map +1 -1
- package/dist/index.d.ts +3793 -478
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -17
- package/dist/index.js.map +1 -1
- package/dist/listener-protocol.d.ts +77 -0
- package/dist/listener-protocol.d.ts.map +1 -0
- package/dist/listener-protocol.js +33 -0
- package/dist/listener-protocol.js.map +1 -0
- package/dist/model-order.d.ts +1 -0
- package/dist/model-order.d.ts.map +1 -1
- package/dist/model-order.js +16 -0
- package/dist/model-order.js.map +1 -1
- package/dist/output-fields.d.ts +30 -0
- package/dist/output-fields.d.ts.map +1 -0
- package/dist/output-fields.js +57 -0
- package/dist/output-fields.js.map +1 -0
- package/dist/path-refs.d.ts.map +1 -1
- package/dist/path-refs.js.map +1 -1
- package/dist/publish-drafts.d.ts +7 -0
- package/dist/publish-drafts.d.ts.map +1 -0
- package/dist/publish-drafts.js +20 -0
- package/dist/publish-drafts.js.map +1 -0
- package/dist/quick-model.d.ts +2 -1
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/quick-model.js +18 -14
- package/dist/quick-model.js.map +1 -1
- package/dist/routes.d.ts +3 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +63 -0
- package/dist/routes.js.map +1 -1
- package/dist/runtime-state.d.ts +33 -0
- package/dist/runtime-state.d.ts.map +1 -0
- package/dist/runtime-state.js +14 -0
- package/dist/runtime-state.js.map +1 -0
- package/dist/schemas.d.ts +4938 -866
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +1011 -192
- package/dist/schemas.js.map +1 -1
- package/dist/search-globs.d.ts +6 -0
- package/dist/search-globs.d.ts.map +1 -0
- package/dist/search-globs.js +39 -0
- package/dist/search-globs.js.map +1 -0
- package/dist/share-paths.d.ts +8 -0
- package/dist/share-paths.d.ts.map +1 -0
- package/dist/share-paths.js +21 -0
- package/dist/share-paths.js.map +1 -0
- package/dist/state-portability.d.ts +8 -0
- package/dist/state-portability.d.ts.map +1 -0
- package/dist/state-portability.js +4 -0
- package/dist/state-portability.js.map +1 -0
- package/dist/title.d.ts.map +1 -1
- package/dist/title.js +1 -1
- package/dist/title.js.map +1 -1
- package/dist/tunnel-ids.d.ts +1 -0
- package/dist/tunnel-ids.d.ts.map +1 -1
- package/dist/tunnel-ids.js +1 -0
- package/dist/tunnel-ids.js.map +1 -1
- package/dist/versions.d.ts +4 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +15 -0
- package/dist/versions.js.map +1 -0
- package/dist/workflow-faults.d.ts +4 -0
- package/dist/workflow-faults.d.ts.map +1 -0
- package/dist/workflow-faults.js +115 -0
- package/dist/workflow-faults.js.map +1 -0
- package/dist/workspace-state.d.ts +282 -3
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +198 -12
- package/dist/workspace-state.js.map +1 -1
- package/package.json +30 -6
- package/src/agent-catalog.test.ts +147 -7
- package/src/agent-catalog.ts +233 -21
- package/src/agent-run-model.test.ts +60 -0
- package/src/agent-run-model.ts +51 -0
- package/src/capability-env.ts +10 -0
- package/src/capability-ledger.test.ts +110 -0
- package/src/chores/chores.test.ts +32 -0
- package/src/chores/chores.ts +456 -32
- package/src/chores/digest.ts +0 -0
- package/src/chores/extension-update.ts +37 -0
- package/src/chores/fix-deps.ts +40 -0
- package/src/chores/index.ts +7 -2
- package/src/chores/probes.test.ts +155 -3
- package/src/chores/probes.ts +209 -4
- package/src/chores/stack.test.ts +126 -0
- package/src/chores/stack.ts +244 -0
- package/src/chores/verdict.test.ts +357 -19
- package/src/chores/verdict.ts +102 -22
- package/src/contract-lock.test.ts +17 -0
- package/src/contract-lock.ts +66 -0
- package/src/contracts/agent.contract.ts +6 -0
- package/src/contracts/agents.contract.ts +12 -0
- package/src/contracts/automations.contract.ts +17 -1
- package/src/contracts/capabilities.contract.ts +22 -0
- package/src/contracts/ci.contract.ts +10 -2
- package/src/contracts/claude.contract.ts +8 -4
- package/src/contracts/endpoints.contract.ts +21 -0
- package/src/contracts/extensions.contract.ts +42 -0
- package/src/contracts/git.contract.ts +33 -6
- package/src/contracts/grok.contract.ts +4 -3
- package/src/contracts/host.contract.ts +13 -2
- package/src/contracts/loops.contract.ts +62 -0
- package/src/contracts/personas.contract.ts +56 -0
- package/src/contracts/prepush.contract.ts +4 -3
- package/src/contracts/providers.contract.ts +23 -0
- package/src/contracts/public.contract.ts +14 -0
- package/src/contracts/sessions.contract.ts +5 -1
- package/src/contracts/settings.contract.ts +5 -0
- package/src/contracts/share.contract.ts +20 -0
- package/src/contracts/skills.contract.ts +24 -0
- package/src/contracts/system.contract.ts +27 -3
- package/src/contracts/workflows.contract.ts +78 -0
- package/src/contracts/workspace.contract.ts +10 -2
- package/src/conversation-ids.test.ts +25 -0
- package/src/conversation-ids.ts +160 -0
- package/src/events.test.ts +20 -1
- package/src/events.ts +404 -43
- package/src/history-state.ts +129 -0
- package/src/hostnames.ts +20 -6
- package/src/index.ts +60 -17
- package/src/listener-protocol.ts +96 -0
- package/src/model-order.test.ts +32 -1
- package/src/model-order.ts +56 -3
- package/src/output-fields.ts +111 -0
- package/src/path-refs.test.ts +4 -4
- package/src/path-refs.ts +5 -5
- package/src/publish-drafts.ts +59 -0
- package/src/quick-model.test.ts +70 -27
- package/src/quick-model.ts +76 -40
- package/src/routes.test.ts +91 -3
- package/src/routes.ts +135 -0
- package/src/runtime-state.test.ts +45 -0
- package/src/runtime-state.ts +124 -0
- package/src/schemas.test.ts +16 -29
- package/src/schemas.ts +3526 -553
- package/src/search-globs.test.ts +46 -0
- package/src/search-globs.ts +76 -0
- package/src/share-paths.test.ts +48 -0
- package/src/share-paths.ts +68 -0
- package/src/state-portability.ts +56 -0
- package/src/title.test.ts +20 -1
- package/src/title.ts +12 -3
- package/src/tunnel-ids.ts +15 -1
- package/src/versions.test.ts +39 -0
- package/src/versions.ts +48 -0
- package/src/workflow-faults.test.ts +127 -0
- package/src/workflow-faults.ts +188 -0
- package/src/workspace-state.test.ts +189 -17
- package/src/workspace-state.ts +500 -30
- package/dist/contracts/codex.contract.d.ts.map +0 -1
- package/dist/contracts/codex.contract.js +0 -6
- package/dist/contracts/codex.contract.js.map +0 -1
- package/dist/contracts/gemini.contract.d.ts +0 -16
- package/dist/contracts/gemini.contract.d.ts.map +0 -1
- package/dist/contracts/gemini.contract.js +0 -6
- package/dist/contracts/gemini.contract.js.map +0 -1
- package/dist/contracts/kimi.contract.d.ts +0 -16
- package/dist/contracts/kimi.contract.d.ts.map +0 -1
- package/dist/contracts/kimi.contract.js +0 -6
- package/dist/contracts/kimi.contract.js.map +0 -1
- package/dist/contracts/komodo.contract.d.ts +0 -125
- package/dist/contracts/komodo.contract.d.ts.map +0 -1
- package/dist/contracts/komodo.contract.js +0 -14
- package/dist/contracts/komodo.contract.js.map +0 -1
- package/dist/contracts/memory.contract.d.ts +0 -34
- package/dist/contracts/memory.contract.d.ts.map +0 -1
- package/dist/contracts/memory.contract.js +0 -9
- package/dist/contracts/memory.contract.js.map +0 -1
- package/src/contracts/codex.contract.ts +0 -10
- package/src/contracts/gemini.contract.ts +0 -11
- package/src/contracts/kimi.contract.ts +0 -9
- package/src/contracts/komodo.contract.ts +0 -36
- package/src/contracts/memory.contract.ts +0 -13
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { StateFile } from "./state-portability.js";
|
|
2
|
+
|
|
3
|
+
/* WHAT LIVES ON /history — the second half of the daemon's state, and the half nothing declared until an
|
|
4
|
+
* export had to reason about it.
|
|
5
|
+
*
|
|
6
|
+
* `WORKSPACE_STATE_FILES` covers `<workspace>/.intentic/`, which is where the manifests live. It is not where
|
|
7
|
+
* the machinery lives. Every repo's REAL git dir is here (a repo's in-tree `.git` is a pointer file — see
|
|
8
|
+
* git/repo-git-dirs.ts for the invariant that forces it), and so are the fleet registry, the turn journal, the
|
|
9
|
+
* ledgers, the checkpoint scopes and the isolated agents' checkouts. A "workspace export" that took `/work`
|
|
10
|
+
* alone would carry a tree of repos with dangling gitdir pointers — every git command in the restored sandbox
|
|
11
|
+
* answering `fatal: not a git repository` — and an empty agent board.
|
|
12
|
+
*
|
|
13
|
+
* The two tables stay separate rather than becoming one keyed by volume, because they answer different
|
|
14
|
+
* questions. A `.intentic` entry also declares which browser QUERY it makes stale, since the file watcher
|
|
15
|
+
* reports it; nothing here is watched at all (that is the point of the volume), so an `invalidates` field on
|
|
16
|
+
* these entries would be a column of empty arrays. What they share is the portability class, and that is
|
|
17
|
+
* imported rather than duplicated.
|
|
18
|
+
*
|
|
19
|
+
* `history-state-coverage.test.ts` fails when a daemon store builds a `/history` path this list doesn't carry, in both
|
|
20
|
+
* directions — the same shape-recognizing guard that covers the workspace table.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// Paths are historyRoot-relative, forward-slash, matched by PREFIX; a directory entry keeps its trailing slash
|
|
24
|
+
// so it cannot prefix-match a sibling file. See stateFileFor for how nesting resolves.
|
|
25
|
+
export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
26
|
+
/* ---- the machinery a restored workspace is inert without ---- */
|
|
27
|
+
|
|
28
|
+
/* THE ONE THAT MAKES A BUNDLE A WORKSPACE. Every repo's real git dir, including the /work root's own
|
|
29
|
+
* ("root"), keyed by URI-encoded repo id. Carrying the working tree without this hands the target files
|
|
30
|
+
* whose `.git` points at a path that does not exist there — which is not a degraded repo but a broken one,
|
|
31
|
+
* and it takes the Changes review, the diff, land and every agent branch with it. The agent BRANCHES live
|
|
32
|
+
* in here too, which is what lets the checkouts below be left out. */
|
|
33
|
+
{ path: "gits/", portability: "carry" },
|
|
34
|
+
// The checkpoint timeline (one bare repo per scope, snapshots on refs/snapshots/head). Restoring it is what
|
|
35
|
+
// makes "restore to before that turn" still reach back past the move.
|
|
36
|
+
{ path: "scopes/", portability: "carry" },
|
|
37
|
+
// The fleet: every conversation card, its branch, its session ids, its standing.
|
|
38
|
+
{ path: "agents.json", portability: "carry" },
|
|
39
|
+
{ path: "turns/", portability: "carry" },
|
|
40
|
+
{ path: "transcripts/", portability: "carry" },
|
|
41
|
+
// What each message can be put back to — a workspace checkpoint, or an isolated conversation's own commits.
|
|
42
|
+
// Carried WITH the transcripts and the scopes above, because it is the join between them: without it a
|
|
43
|
+
// restored conversation reads back whole and offers no way back into it, even though both the messages and
|
|
44
|
+
// the states they name travelled.
|
|
45
|
+
{ path: "turn-anchors.json", portability: "carry" },
|
|
46
|
+
/* WHICH CONVERSATIONS ARE PUBLISHED AS PAGES ANYONE WITH THE LINK CAN READ.
|
|
47
|
+
*
|
|
48
|
+
* Carried, and it is the entry with the most to say for itself: the PAGES live in the workspace's outbox
|
|
49
|
+
* (`public/`), so they travel with `/work` whatever this says. Leaving the index behind would restore a
|
|
50
|
+
* sandbox that is still serving somebody's conversation on the internet with nothing in the app that knows
|
|
51
|
+
* it — no row, no link, and no way to stop sharing short of deleting files by hand. The index is what makes
|
|
52
|
+
* a published page withdrawable, so it goes wherever the pages go. */
|
|
53
|
+
{ path: "shares.json", portability: "carry" },
|
|
54
|
+
{ path: "activity.jsonl", portability: "carry" },
|
|
55
|
+
{ path: "usage.jsonl", portability: "carry" },
|
|
56
|
+
{ path: "account-usage.json", portability: "carry" },
|
|
57
|
+
{ path: "provider-refusals.json", portability: "carry" },
|
|
58
|
+
// Explicit first-time dependency setup requests. Carrying the worklist preserves the owner's decision when
|
|
59
|
+
// an export interrupts the queue before its terminal starts; fulfilled entries remove themselves.
|
|
60
|
+
{ path: "dependency-requests.json", portability: "carry" },
|
|
61
|
+
// The deploy engine's own ledgers — a run's events and the check results the Pipelines view reads back.
|
|
62
|
+
{ path: "apply-events.ndjson", portability: "carry" },
|
|
63
|
+
{ path: "check-events/", portability: "carry" },
|
|
64
|
+
|
|
65
|
+
/* ---- regenerated by the target ---- */
|
|
66
|
+
|
|
67
|
+
/* THE DELIBERATE OMISSION, and the difference between a bundle of gigabytes and one of hundreds.
|
|
68
|
+
*
|
|
69
|
+
* A conversation's worktree is a full checkout of the monorepo per agent (plus its overlay upper dir), and
|
|
70
|
+
* there can be a hundred of them. None of it is unique: the branch it holds is in `gits/` above, and the
|
|
71
|
+
* registry entry naming it travels in agents.json — so an imported conversation arrives in exactly the
|
|
72
|
+
* shape the system already has a name for. `attached()` reports its checkout as absent, the board renders
|
|
73
|
+
* it, and the next turn's `ensure()` re-creates it from the recorded composition, which is the same path an
|
|
74
|
+
* archived agent takes when it runs again. The boot sweep's `git worktree prune` clears the stale admin
|
|
75
|
+
* entries the restored git dirs still carry. */
|
|
76
|
+
{
|
|
77
|
+
path: "worktrees/",
|
|
78
|
+
portability: "derived",
|
|
79
|
+
note: "Each conversation re-attaches its checkout from its branch on its next turn.",
|
|
80
|
+
},
|
|
81
|
+
{ path: "overlays/", portability: "derived" },
|
|
82
|
+
{ path: "logs/", portability: "derived" },
|
|
83
|
+
{ path: "trash/", portability: "derived" },
|
|
84
|
+
{ path: ".isolation-probe", portability: "derived" },
|
|
85
|
+
/* The finished bundles themselves. `derived` is doing real work here rather than describing leftovers: an
|
|
86
|
+
* export that carried the export directory would pack every previous bundle into the new one, and the next
|
|
87
|
+
* export would pack THAT — each one a multiple of the last. Living on this volume is the other half of the
|
|
88
|
+
* same guard; under `/work` the file would also be watched, indexed by iq, and snapshotted into history. */
|
|
89
|
+
{ path: "exports/", portability: "derived" },
|
|
90
|
+
|
|
91
|
+
/* ---- credentials ---- */
|
|
92
|
+
|
|
93
|
+
// The ssh alias dir ~/.ssh/intentic-hosts symlinks to: per-host config, private keys and passphrases for
|
|
94
|
+
// every host capability and git remote the sandbox reaches.
|
|
95
|
+
{
|
|
96
|
+
path: "ssh-hosts/",
|
|
97
|
+
portability: "secret",
|
|
98
|
+
note: "Re-add each ssh host on the Capabilities view — its key does not travel.",
|
|
99
|
+
},
|
|
100
|
+
// The cli-proxy's config, which holds the routed subscriptions' provider tokens.
|
|
101
|
+
{
|
|
102
|
+
path: "translator/",
|
|
103
|
+
portability: "secret",
|
|
104
|
+
note: "Sign the routed AI subscriptions in again on the Agent tab.",
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/* ---- identity: what binds this sandbox to its owner, its browsers and its host ---- */
|
|
108
|
+
|
|
109
|
+
/* Signs every browser session cookie. Carrying it would let a bundle's holder mint sessions against the
|
|
110
|
+
* target — an export becomes a credential — and the target minting its own costs exactly one sign-in. */
|
|
111
|
+
{ path: "session-secret", portability: "identity", note: "Sign in again — the target signs its own sessions." },
|
|
112
|
+
{
|
|
113
|
+
path: "browser-access-disabled",
|
|
114
|
+
portability: "identity",
|
|
115
|
+
note: "Account-deletion retirement belongs to the source sandbox; the imported copy starts with fresh browser access.",
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
path: "push.json",
|
|
119
|
+
portability: "identity",
|
|
120
|
+
note: "Re-enable notifications in the browsers you use — a push subscription is bound to the sandbox that minted it.",
|
|
121
|
+
},
|
|
122
|
+
{ path: "sync-enrollments.json", portability: "identity", note: "Re-pair desktop sync from the Sync tab." },
|
|
123
|
+
{ path: "sync-pair-consumed.json", portability: "identity" },
|
|
124
|
+
{ path: "host-enrollments.json", portability: "identity" },
|
|
125
|
+
// The burn list for setup-time computer pairings. Identity, like sync's beside it, and for a sharper reason:
|
|
126
|
+
// carrying it into another sandbox would mark that sandbox's own fresh pairing as already spent.
|
|
127
|
+
{ path: "host-pair-consumed.json", portability: "identity" },
|
|
128
|
+
{ path: "local-cert/", portability: "identity" },
|
|
129
|
+
];
|
package/src/hostnames.ts
CHANGED
|
@@ -48,22 +48,34 @@ export const CATCH_ALL = { service: "http_status:404" } as const;
|
|
|
48
48
|
// ports. The slot labels are salted with the connect token rather than being the letters a…h, so a forwarded
|
|
49
49
|
// port's hostname is not derivable from the (public) sandbox id alone — see tunnel-ids for why that matters.
|
|
50
50
|
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
51
|
+
// Outbox scheme: `public-<slot>-<sandboxId>.<zone>` — the same shape again, serving the workspace's `public/`
|
|
52
|
+
// directory as static files. <slot> is publicSlotFromToken (./tunnel-ids), salted for the same reason the port
|
|
53
|
+
// slots are, and one record per sandbox rather than a pool: there is one outbox, and its link has to stay good
|
|
54
|
+
// for as long as the file does.
|
|
55
|
+
//
|
|
56
|
+
// A *label* is the first-DNS-label prefix before `-<sandboxId>` (`preview-<panel>` / `port-<slot>` /
|
|
57
|
+
// `public-<slot>`) — the unit the platform's /sandbox/preview-route mints, so one endpoint serves all three.
|
|
53
58
|
export const previewLabel = (panel: string): string => `preview-${panel}`;
|
|
54
59
|
export const portLabel = (slot: string): string => `port-${slot}`;
|
|
60
|
+
export const publicLabel = (slot: string): string => `public-${slot}`;
|
|
55
61
|
|
|
56
62
|
// The hostname a label resolves to — what the platform's /sandbox/preview-route mints from the label alone.
|
|
57
63
|
export const labelHostname = (label: string, id: string, zone: string): string => `${label}-${id}.${zone}`;
|
|
58
64
|
export const previewHostname = (panel: string, id: string, zone: string): string => labelHostname(previewLabel(panel), id, zone);
|
|
59
65
|
export const portHostname = (slot: string, id: string, zone: string): string => labelHostname(portLabel(slot), id, zone);
|
|
66
|
+
export const publicHostname = (slot: string, id: string, zone: string): string => labelHostname(publicLabel(slot), id, zone);
|
|
60
67
|
|
|
61
|
-
// A
|
|
62
|
-
//
|
|
68
|
+
// A label's public URL — undefined unless the sandbox has both a zone and an id (headless/loopback sandboxes
|
|
69
|
+
// have neither and advertise nothing). One builder, three vocabularies: a panel's preview, a forwarded port's,
|
|
70
|
+
// and the outbox's.
|
|
71
|
+
const labelUrl = (label: string, zone: string | undefined, sandboxId: string | undefined): string | undefined =>
|
|
72
|
+
zone !== undefined && zone !== "" && sandboxId !== undefined ? `https://${labelHostname(label, sandboxId, zone)}` : undefined;
|
|
63
73
|
export const previewUrl = (panel: string, zone: string | undefined, sandboxId: string | undefined): string | undefined =>
|
|
64
|
-
|
|
74
|
+
labelUrl(previewLabel(panel), zone, sandboxId);
|
|
65
75
|
export const portUrl = (slot: string, zone: string | undefined, sandboxId: string | undefined): string | undefined =>
|
|
66
|
-
|
|
76
|
+
labelUrl(portLabel(slot), zone, sandboxId);
|
|
77
|
+
export const publicUrl = (slot: string, zone: string | undefined, sandboxId: string | undefined): string | undefined =>
|
|
78
|
+
labelUrl(publicLabel(slot), zone, sandboxId);
|
|
67
79
|
|
|
68
80
|
// The key after `<prefix>` from a request's Host header. The first DNS label must carry the prefix (the
|
|
69
81
|
// own-Cloudflare wildcard also catches stray subdomains → undefined → the caller's 404) and, when the sandbox
|
|
@@ -87,6 +99,8 @@ export const panelFromHost = (hostHeader: string | undefined, sandboxId: string
|
|
|
87
99
|
keyFromHost("preview-", hostHeader, sandboxId);
|
|
88
100
|
export const portSlotFromHost = (hostHeader: string | undefined, sandboxId: string | undefined): string | undefined =>
|
|
89
101
|
keyFromHost("port-", hostHeader, sandboxId);
|
|
102
|
+
export const publicSlotFromHost = (hostHeader: string | undefined, sandboxId: string | undefined): string | undefined =>
|
|
103
|
+
keyFromHost("public-", hostHeader, sandboxId);
|
|
90
104
|
|
|
91
105
|
// The sandbox's identity AS THE USER SEES IT: the leading DNS label of its public URL, minus the `sandbox-`
|
|
92
106
|
// prefix — `https://sandbox-0f310c3c4db4.intentic.dev` → `0f310c3c4db4`, i.e. sandboxIdFromToken's digest read
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ContractRoute } from "./routes.js";
|
|
2
|
-
import { contractRoutes, routeNameForRequest } from "./routes.js";
|
|
2
|
+
import { contractRoutes, requestPathFor, routeForProcedure, routeNameForRequest, routeShapes } from "./routes.js";
|
|
3
3
|
import { activityContract } from "./contracts/activity.contract.js";
|
|
4
4
|
import { agentContract } from "./contracts/agent.contract.js";
|
|
5
5
|
import { agentsContract } from "./contracts/agents.contract.js";
|
|
@@ -8,31 +8,33 @@ import { capabilitiesContract } from "./contracts/capabilities.contract.js";
|
|
|
8
8
|
import { choresContract } from "./contracts/chores.contract.js";
|
|
9
9
|
import { ciContract } from "./contracts/ci.contract.js";
|
|
10
10
|
import { claudeContract } from "./contracts/claude.contract.js";
|
|
11
|
-
import { codexContract } from "./contracts/codex.contract.js";
|
|
12
11
|
import { draftsContract } from "./contracts/drafts.contract.js";
|
|
13
12
|
import { endpointsContract } from "./contracts/endpoints.contract.js";
|
|
14
13
|
import { extensionsContract } from "./contracts/extensions.contract.js";
|
|
15
|
-
import {
|
|
14
|
+
import { personasContract } from "./contracts/personas.contract.js";
|
|
16
15
|
import { gitContract } from "./contracts/git.contract.js";
|
|
17
16
|
import { grokContract } from "./contracts/grok.contract.js";
|
|
18
17
|
import { historyContract } from "./contracts/history.contract.js";
|
|
19
18
|
import { intenticContract } from "./contracts/intentic.contract.js";
|
|
20
19
|
import { inventoryContract } from "./contracts/inventory.contract.js";
|
|
21
|
-
import { kimiContract } from "./contracts/kimi.contract.js";
|
|
22
|
-
import { komodoContract } from "./contracts/komodo.contract.js";
|
|
23
20
|
import { logsContract } from "./contracts/logs.contract.js";
|
|
24
|
-
import {
|
|
21
|
+
import { loopsContract } from "./contracts/loops.contract.js";
|
|
25
22
|
import { panelsContract } from "./contracts/panels.contract.js";
|
|
26
23
|
import { portsContract } from "./contracts/ports.contract.js";
|
|
24
|
+
import { publicContract } from "./contracts/public.contract.js";
|
|
27
25
|
import { prepushContract } from "./contracts/prepush.contract.js";
|
|
26
|
+
import { providersContract } from "./contracts/providers.contract.js";
|
|
28
27
|
import { pushContract } from "./contracts/push.contract.js";
|
|
29
28
|
import { secretsContract } from "./contracts/secrets.contract.js";
|
|
30
29
|
import { sessionsContract } from "./contracts/sessions.contract.js";
|
|
31
30
|
import { settingsContract } from "./contracts/settings.contract.js";
|
|
31
|
+
import { shareContract } from "./contracts/share.contract.js";
|
|
32
|
+
import { skillsContract } from "./contracts/skills.contract.js";
|
|
32
33
|
import { systemContract } from "./contracts/system.contract.js";
|
|
33
34
|
import { translatorContract } from "./contracts/translator.contract.js";
|
|
34
35
|
import { usageContract } from "./contracts/usage.contract.js";
|
|
35
36
|
import { vpnContract } from "./contracts/vpn.contract.js";
|
|
37
|
+
import { workflowsContract } from "./contracts/workflows.contract.js";
|
|
36
38
|
import { workspaceContract } from "./contracts/workspace.contract.js";
|
|
37
39
|
|
|
38
40
|
export { activityContract } from "./contracts/activity.contract.js";
|
|
@@ -43,11 +45,10 @@ export { capabilitiesContract } from "./contracts/capabilities.contract.js";
|
|
|
43
45
|
export { choresContract } from "./contracts/chores.contract.js";
|
|
44
46
|
export { ciContract } from "./contracts/ci.contract.js";
|
|
45
47
|
export { claudeContract } from "./contracts/claude.contract.js";
|
|
46
|
-
export { codexContract } from "./contracts/codex.contract.js";
|
|
47
48
|
export { draftsContract } from "./contracts/drafts.contract.js";
|
|
48
|
-
export { endpointsContract } from "./contracts/endpoints.contract.js";
|
|
49
|
+
export { endpointsContract, TrialStatusSchema, type TrialStatusResponse } from "./contracts/endpoints.contract.js";
|
|
49
50
|
export { extensionsContract } from "./contracts/extensions.contract.js";
|
|
50
|
-
export {
|
|
51
|
+
export { personasContract } from "./contracts/personas.contract.js";
|
|
51
52
|
export { gitContract } from "./contracts/git.contract.js";
|
|
52
53
|
export { grokContract } from "./contracts/grok.contract.js";
|
|
53
54
|
export { historyContract } from "./contracts/history.contract.js";
|
|
@@ -56,35 +57,61 @@ export { historyContract } from "./contracts/history.contract.js";
|
|
|
56
57
|
export { hostContract } from "./contracts/host.contract.js";
|
|
57
58
|
export { intenticContract } from "./contracts/intentic.contract.js";
|
|
58
59
|
export { inventoryContract } from "./contracts/inventory.contract.js";
|
|
59
|
-
export { kimiContract } from "./contracts/kimi.contract.js";
|
|
60
|
-
export { komodoContract } from "./contracts/komodo.contract.js";
|
|
61
60
|
export { logsContract } from "./contracts/logs.contract.js";
|
|
62
|
-
export {
|
|
61
|
+
export { loopsContract } from "./contracts/loops.contract.js";
|
|
63
62
|
export { panelsContract } from "./contracts/panels.contract.js";
|
|
64
63
|
export { portsContract } from "./contracts/ports.contract.js";
|
|
64
|
+
export { publicContract } from "./contracts/public.contract.js";
|
|
65
65
|
export { prepushContract } from "./contracts/prepush.contract.js";
|
|
66
|
+
export { providersContract } from "./contracts/providers.contract.js";
|
|
66
67
|
export { pushContract } from "./contracts/push.contract.js";
|
|
67
68
|
export { secretsContract } from "./contracts/secrets.contract.js";
|
|
68
69
|
export { sessionsContract } from "./contracts/sessions.contract.js";
|
|
69
70
|
export { settingsContract } from "./contracts/settings.contract.js";
|
|
71
|
+
export { shareContract } from "./contracts/share.contract.js";
|
|
72
|
+
export { skillsContract } from "./contracts/skills.contract.js";
|
|
70
73
|
export { systemContract } from "./contracts/system.contract.js";
|
|
71
74
|
export { translatorContract } from "./contracts/translator.contract.js";
|
|
72
75
|
export { usageContract } from "./contracts/usage.contract.js";
|
|
73
76
|
export { vpnContract } from "./contracts/vpn.contract.js";
|
|
77
|
+
export { workflowsContract } from "./contracts/workflows.contract.js";
|
|
74
78
|
export { workspaceContract } from "./contracts/workspace.contract.js";
|
|
75
79
|
export * from "./events.js";
|
|
76
80
|
export * from "./sse.js";
|
|
77
81
|
export * from "./routes.js";
|
|
82
|
+
/* THE CONTAINER'S FIXED DIRECTORY LAYOUT, re-exported so extensions can reach it.
|
|
83
|
+
*
|
|
84
|
+
* The names are defined once in @intentic/constants, which sits at the bottom of the dependency graph. An
|
|
85
|
+
* EXTENSION may not import that package — the boundary rule (.oxlintrc.json, _extensions/README.md) allows
|
|
86
|
+
* only the SDK halves and this contract, so that an extension cannot couple itself to app or engine internals.
|
|
87
|
+
* That rule is right, and it left extensions with no way to name the workspace root except by spelling it.
|
|
88
|
+
*
|
|
89
|
+
* Re-exporting here is what closes that gap without widening the boundary: the layout is exactly the kind of
|
|
90
|
+
* thing this package already carries — shared vocabulary both sides of the wire must agree on, alongside the
|
|
91
|
+
* state-file table below — and there is still one definition, in one place, that everything resolves to. */
|
|
92
|
+
export { HISTORY_ROOT, HOST_STATE_ROOT, STATE_DIR, WORKSPACE_ROOT } from "@intentic/constants";
|
|
78
93
|
export * from "./workspace-state.js";
|
|
94
|
+
export * from "./runtime-state.js";
|
|
95
|
+
export * from "./state-portability.js";
|
|
96
|
+
export * from "./history-state.js";
|
|
79
97
|
export * from "./agent-catalog.js";
|
|
98
|
+
export * from "./agent-run-model.js";
|
|
99
|
+
export * from "./capability-env.js";
|
|
100
|
+
export * from "./conversation-ids.js";
|
|
80
101
|
export * from "./host-protocol.js";
|
|
102
|
+
export * from "./listener-protocol.js";
|
|
81
103
|
export * from "./hostnames.js";
|
|
82
104
|
export * from "./model-order.js";
|
|
83
105
|
export * from "./path-refs.js";
|
|
84
106
|
export * from "./quick-model.js";
|
|
107
|
+
export * from "./output-fields.js";
|
|
108
|
+
export * from "./publish-drafts.js";
|
|
85
109
|
export * from "./schemas.js";
|
|
110
|
+
export * from "./search-globs.js";
|
|
86
111
|
export * from "./terminal-protocol.js";
|
|
87
112
|
export * from "./title.js";
|
|
113
|
+
export * from "./versions.js";
|
|
114
|
+
export * from "./workflow-faults.js";
|
|
88
115
|
|
|
89
116
|
// The aggregated contract — implemented on the server by the per-domain route factories and consumed by the
|
|
90
117
|
// browser's typed oRPC client (ContractRouterClient<typeof sandboxContract>). The wire paths it declares are
|
|
@@ -98,32 +125,34 @@ export const sandboxContract = {
|
|
|
98
125
|
chores: choresContract,
|
|
99
126
|
ci: ciContract,
|
|
100
127
|
claude: claudeContract,
|
|
101
|
-
codex: codexContract,
|
|
102
128
|
drafts: draftsContract,
|
|
103
129
|
endpoints: endpointsContract,
|
|
104
130
|
extensions: extensionsContract,
|
|
131
|
+
personas: personasContract,
|
|
105
132
|
sessions: sessionsContract,
|
|
106
133
|
settings: settingsContract,
|
|
134
|
+
share: shareContract,
|
|
135
|
+
skills: skillsContract,
|
|
107
136
|
intentic: intenticContract,
|
|
108
|
-
gemini: geminiContract,
|
|
109
137
|
git: gitContract,
|
|
110
138
|
grok: grokContract,
|
|
111
|
-
kimi: kimiContract,
|
|
112
|
-
komodo: komodoContract,
|
|
113
139
|
history: historyContract,
|
|
114
140
|
workspace: workspaceContract,
|
|
115
141
|
inventory: inventoryContract,
|
|
116
142
|
logs: logsContract,
|
|
117
|
-
|
|
143
|
+
loops: loopsContract,
|
|
118
144
|
panels: panelsContract,
|
|
119
145
|
ports: portsContract,
|
|
146
|
+
public: publicContract,
|
|
120
147
|
prepush: prepushContract,
|
|
148
|
+
providers: providersContract,
|
|
121
149
|
push: pushContract,
|
|
122
150
|
secrets: secretsContract,
|
|
123
151
|
system: systemContract,
|
|
124
152
|
translator: translatorContract,
|
|
125
153
|
usage: usageContract,
|
|
126
154
|
vpn: vpnContract,
|
|
155
|
+
workflows: workflowsContract,
|
|
127
156
|
};
|
|
128
157
|
|
|
129
158
|
// Every route in THIS build of the contract, and the names the daemon advertises on its hello frame. Bound here
|
|
@@ -133,6 +162,20 @@ export const sandboxContract = {
|
|
|
133
162
|
export const SANDBOX_ROUTES: readonly ContractRoute[] = contractRoutes(sandboxContract);
|
|
134
163
|
export const SANDBOX_ROUTE_NAMES: readonly string[] = SANDBOX_ROUTES.map((route) => route.name);
|
|
135
164
|
|
|
165
|
+
/* And the SHAPE of each of those routes, advertised beside the names for the failure the names cannot describe:
|
|
166
|
+
* a route both builds have, answering a payload only one of them expects. Computed once at module load — it
|
|
167
|
+
* walks every contract schema through `z.toJSONSchema`, which is far too much work to repeat per connection and
|
|
168
|
+
* exactly the kind of thing that never changes for the life of a process. See routes.ts. */
|
|
169
|
+
export const SANDBOX_ROUTE_SHAPES: Readonly<Record<string, string>> = routeShapes(sandboxContract);
|
|
170
|
+
|
|
136
171
|
// The contract route a concrete browser request belongs to, bound to this build's route table.
|
|
137
172
|
export const sandboxRouteName = (method: string, pathWithQuery: string): string | undefined =>
|
|
138
173
|
routeNameForRequest(SANDBOX_ROUTES, method, pathWithQuery);
|
|
174
|
+
|
|
175
|
+
// The method and concrete path a TYPED call is about to put on the wire, bound to this build's route table.
|
|
176
|
+
// Undefined when the procedure is not one this contract declares, which a typed caller cannot reach — the host
|
|
177
|
+
// gate treats it as a refusal rather than assuming it is harmless.
|
|
178
|
+
export const sandboxRequestFor = (procedure: readonly string[], input: unknown): { method: string; path: string } | undefined => {
|
|
179
|
+
const route = routeForProcedure(SANDBOX_ROUTES, procedure);
|
|
180
|
+
return route === undefined ? undefined : { method: route.method, path: requestPathFor(route, input) };
|
|
181
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ActivityStatusSchema } from "./schemas.js";
|
|
3
|
+
|
|
4
|
+
/* The wire between the daemon and an extension's realtime-listener GATEWAY process (ext-discord, ext-slack,
|
|
5
|
+
* ext-telegram, ext-whatsapp, ext-imap): the four provider-scoped routes app.ts mounts under
|
|
6
|
+
* /listeners/:provider — state, dispatch, failure, status. These shapes used to live daemon-side only, with
|
|
7
|
+
* every gateway hand-writing its own copy of the payloads as untyped literals; a field rename broke five
|
|
8
|
+
* producers silently. They live in the contract now so BOTH ends compile against one declaration — the daemon
|
|
9
|
+
* parses with the schemas, the gateways (via @intentic/connector-runtime) type against the inferred types. */
|
|
10
|
+
|
|
11
|
+
// One normalized inbound event — serialized as a JSON line in the automation's payload, and the JSON body a
|
|
12
|
+
// realtime source POSTs to /listeners/<provider>/dispatch. A zod schema (not a bare interface) because it's
|
|
13
|
+
// parsed from an extension gateway's request; `provider` and `type` are open strings — the source is
|
|
14
|
+
// extension-declared (contributes.listener), not a core enum.
|
|
15
|
+
export const ListenerMessageSchema = z.object({
|
|
16
|
+
provider: z.string().min(1),
|
|
17
|
+
type: z.string().min(1),
|
|
18
|
+
id: z.string(),
|
|
19
|
+
channelId: z.string(),
|
|
20
|
+
author: z.object({ id: z.string(), name: z.string() }),
|
|
21
|
+
content: z.string(),
|
|
22
|
+
// Discord message: it @mentions one of our bots or replies to a bot's message. Voice events never set it.
|
|
23
|
+
mentioned: z.boolean().optional(),
|
|
24
|
+
// CI pipeline event: the ref it ran on. Top-level rather than inside `extra` for the same reason
|
|
25
|
+
// `mentioned` is — the dispatcher MATCHES on it, and a narrowing axis the trigger can name has to be
|
|
26
|
+
// a field of the message rather than a key in a provider's opaque bag.
|
|
27
|
+
branch: z.string().optional(),
|
|
28
|
+
// Prior channel messages (chronological) fetched when a bot is tagged, so the agent can reason about why.
|
|
29
|
+
// Kept a top-level field (not in `extra`) so it reaches the model's payload but stays out of the activity
|
|
30
|
+
// feed, which logs only content/extra.
|
|
31
|
+
history: z
|
|
32
|
+
.array(
|
|
33
|
+
z.object({
|
|
34
|
+
author: z.object({ id: z.string(), name: z.string() }),
|
|
35
|
+
content: z.string(),
|
|
36
|
+
timestamp: z.string(),
|
|
37
|
+
self: z.boolean().optional(),
|
|
38
|
+
}),
|
|
39
|
+
)
|
|
40
|
+
.optional(),
|
|
41
|
+
timestamp: z.string(),
|
|
42
|
+
// Provider-specific fields (discord message: guildId, attachments; voice_utterance: path;
|
|
43
|
+
// voice_transcript: path, participants, durationSeconds).
|
|
44
|
+
extra: z.record(z.string(), z.unknown()).optional(),
|
|
45
|
+
});
|
|
46
|
+
export type ListenerMessage = z.infer<typeof ListenerMessageSchema>;
|
|
47
|
+
|
|
48
|
+
// One ndjson frame of a /listeners/<provider>/dispatch?stream=1 response — a text delta for one automation's
|
|
49
|
+
// reply, the provider's own failure sentence, or that automation's terminal marker. A type, not a schema: the
|
|
50
|
+
// DAEMON produces these (listener.routes.ts), so nothing parses them from untrusted input.
|
|
51
|
+
export interface ListenerDispatchFrame {
|
|
52
|
+
readonly automationId: string;
|
|
53
|
+
readonly delta?: string;
|
|
54
|
+
// The turn refused or broke, in the provider's own words — forwarded verbatim because a gateway delivers
|
|
55
|
+
// into the owner's own channel, where the actual sentence is the useful thing.
|
|
56
|
+
readonly failed?: string;
|
|
57
|
+
readonly end?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* WHERE ONE CAPABILITY'S LINK-A-DEVICE CEREMONY STANDS (whatsapp), reported every status tick.
|
|
61
|
+
*
|
|
62
|
+
* A CODE IS NOT THE ONLY THING WORTH SAYING, and publishing only codes is what made a phone that had never
|
|
63
|
+
* linked read as connected: the seconds before the first code, a gateway that just restarted, and a number
|
|
64
|
+
* WhatsApp refused all looked identical from the daemon's side — an absent code — so the card fell through to
|
|
65
|
+
* "ready" and the owner was sent away from the one screen that could have shown them the step. Each of those is
|
|
66
|
+
* its own state here, and every one of them means NOT PAIRED YET.
|
|
67
|
+
*
|
|
68
|
+
* `since` stamps the CURRENT code. WhatsApp closes an unpaired socket after a minute or so and each reopen mints
|
|
69
|
+
* a fresh code, so a code is a thing with an age — the card says how old the one on screen is rather than
|
|
70
|
+
* leaving the owner to type a dead one twice. */
|
|
71
|
+
export const ListenerPairingSchema = z.object({
|
|
72
|
+
// waiting: a socket is up and the code hasn't arrived (or the last one died with its socket).
|
|
73
|
+
// code: `code` is live — type it on the phone. failed: `detail` says what WhatsApp refused.
|
|
74
|
+
state: z.enum(["waiting", "code", "failed"]),
|
|
75
|
+
code: z.string().optional(),
|
|
76
|
+
detail: z.string().optional(),
|
|
77
|
+
since: z.number().optional(),
|
|
78
|
+
});
|
|
79
|
+
export type ListenerPairing = z.infer<typeof ListenerPairingSchema>;
|
|
80
|
+
|
|
81
|
+
// Push-based listener status: a gateway process POSTs its live connection/voice snapshot to
|
|
82
|
+
// /listeners/<provider>/status, and the activity route reads it from there — the daemon holds no provider
|
|
83
|
+
// connection of its own to probe. The body IS the ActivityStatus the /activity/status probe used to build from
|
|
84
|
+
// in-process discord singletons, plus the per-gateway extras that ride the same channel: whether whisper is
|
|
85
|
+
// present (discord's voice-pending signal) and each unpaired capability's ceremony by id (whatsapp's
|
|
86
|
+
// link-a-device flow — the capability card renders it as the step the owner is standing in front of).
|
|
87
|
+
export const ListenerStatusSchema = ActivityStatusSchema.extend({
|
|
88
|
+
whisperReady: z.boolean().optional(),
|
|
89
|
+
pairing: z.record(z.string(), ListenerPairingSchema).optional(),
|
|
90
|
+
});
|
|
91
|
+
export type ListenerStatus = z.infer<typeof ListenerStatusSchema>;
|
|
92
|
+
|
|
93
|
+
// A connection's place in the reconcile lifecycle, as the status snapshot reports it: `idle` = up but holding
|
|
94
|
+
// nothing on purpose (no enabled listener automation to connect for), `pairing` = the socket is up but the
|
|
95
|
+
// credential is a ceremony nobody has completed yet (whatsapp), the rest are the connect loop.
|
|
96
|
+
export type ListenerGatewayPhase = "idle" | "ready" | "pairing" | "connecting" | "disconnected";
|
package/src/model-order.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import { compareCheapestFirst, compareModelIds, compareUnrankedModelIds, familyOf, releaseOf, tierRankOf } from "./model-order.js";
|
|
2
|
+
import { compareCheapestFirst, compareModelIds, compareUnrankedModelIds, familyOf, namesThinking, releaseOf, tierRankOf } from "./model-order.js";
|
|
3
3
|
|
|
4
4
|
/* The order every provider's catalog is served and browsed in. The rule exists because only Anthropic publishes
|
|
5
5
|
* a ranking: the OpenAI-compatible endpoints behind Codex, Gemini, Kimi and Grok hand back a SET, and taking
|
|
@@ -186,6 +186,37 @@ test("reads a release-local tier ladder from the cheap end too", () => {
|
|
|
186
186
|
expect(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"].toSorted(compareCheapestFirst)[0]).toBe("gpt-5.6-luna");
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
+
test("refuses the thinking variant of a model, however new it is", () => {
|
|
190
|
+
/* The bug this rule exists for, in the shape the live catalog actually publishes it: a routed channel vends
|
|
191
|
+
* one row per thinking LEVEL, and the newest row of the cheapest model was the high one — so the ladder
|
|
192
|
+
* whose whole job is to be the cheap rung reached for the most expensive reading of it, and a commit
|
|
193
|
+
* message that takes 2s took closer to 30. */
|
|
194
|
+
expect(["gemini-3.6-flash-high", "gemini-3.5-flash-extra-low"].toSorted(compareCheapestFirst)[0]).toBe("gemini-3.5-flash-extra-low");
|
|
195
|
+
// …and it is the LEVEL that decides, not the release: same model, quieter row wins.
|
|
196
|
+
expect(["gemini-3.5-flash-high", "gemini-3.5-flash-minimal"].toSorted(compareCheapestFirst)[0]).toBe("gemini-3.5-flash-minimal");
|
|
197
|
+
// Tier still outranks it: a thinking cheap model beats a silent expensive one, which is the order that
|
|
198
|
+
// keeps this from quietly promoting a frontier row for being unannotated.
|
|
199
|
+
expect(["gemini-3-pro", "gemini-3.6-flash-high"].toSorted(compareCheapestFirst)[0]).toBe("gemini-3.6-flash-high");
|
|
200
|
+
// An id nobody annotated is not accused of thinking, and is not credited with silence either: it sits
|
|
201
|
+
// between the stated ends.
|
|
202
|
+
expect(["gemini-3-flash", "gemini-3.5-flash-low"].toSorted(compareCheapestFirst)[0]).toBe("gemini-3.5-flash-low");
|
|
203
|
+
expect(["gemini-3-flash", "gemini-3.6-flash-high"].toSorted(compareCheapestFirst)[0]).toBe("gemini-3-flash");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("names the thinking rows, and only those", () => {
|
|
207
|
+
// What a settings row shows beside a pin, so that choosing one is a choice rather than an accident.
|
|
208
|
+
expect(namesThinking("gemini-3.6-flash-high")).toBe(true);
|
|
209
|
+
expect(namesThinking("gemini-3.1-pro-low")).toBe(false);
|
|
210
|
+
expect(namesThinking("gemini-3.5-flash-extra-low")).toBe(false);
|
|
211
|
+
expect(namesThinking("claude-haiku-4-5-20251001")).toBe(false);
|
|
212
|
+
expect(namesThinking("gpt-5.6-luna")).toBe(false);
|
|
213
|
+
// An effort word that is not `high` still names one; so does the on/off form a channel vends beside its
|
|
214
|
+
// quiet row.
|
|
215
|
+
expect(namesThinking("gpt-oss-120b-medium")).toBe(true);
|
|
216
|
+
expect(namesThinking("kimi-k2-thinking")).toBe(true);
|
|
217
|
+
expect(namesThinking("kimi-k2")).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
189
220
|
test("falls back on the newest release for a catalog that publishes no cheap tier at all", () => {
|
|
190
221
|
// Kimi names no tier word anywhere, so every row is UNRANKED and the tier term cancels. Serving the newest
|
|
191
222
|
// of what it does publish is the honest answer — there is no cheaper rung to find.
|
package/src/model-order.ts
CHANGED
|
@@ -135,6 +135,46 @@ const lastRankOf = (family: string, ranks: Readonly<Record<string, number>>): nu
|
|
|
135
135
|
|
|
136
136
|
const releaseTierRankOf = (family: string): number => lastRankOf(family, RELEASE_TIER_RANK);
|
|
137
137
|
|
|
138
|
+
/* HOW HARD AN ID SAYS IT WILL THINK. A routed catalog does not publish one row per model — it publishes one row
|
|
139
|
+
* per model PER THINKING LEVEL, spelling the level into the id: `gemini-3.6-flash-high` and
|
|
140
|
+
* `gemini-3.5-flash-extra-low` are the same Flash at opposite ends of its reasoning budget.
|
|
141
|
+
*
|
|
142
|
+
* Which the ranking above cannot see at all, and that blind spot has a direction: `high` and `low` are both
|
|
143
|
+
* unrecognized words, so two variants of one model tie on tier and the RELEASE tiebreak settles them — and the
|
|
144
|
+
* newest variant a channel publishes is routinely the high one. The quick model, whose entire job is to be the
|
|
145
|
+
* cheap rung, would therefore reach for the most expensive reading of the cheapest model it can find.
|
|
146
|
+
*
|
|
147
|
+
* That is not a small mis-sort. Thinking is the difference between a commit message that is in the box before
|
|
148
|
+
* the user has finished reading the file list and one that arrives half a minute later — measured at ~2s versus
|
|
149
|
+
* ~27s on the same model and the same diff (agent/one-shot.ts, which disables thinking for the rungs where a
|
|
150
|
+
* request parameter can). For a routed rung there is no such parameter: the id IS the setting, so this ranking
|
|
151
|
+
* is the only place the choice can be made.
|
|
152
|
+
*
|
|
153
|
+
* Read ONLY by the cheap-end order. A picker orders a catalog by what a person reaches for, and a person
|
|
154
|
+
* pinning `-high` on purpose means it — see compareCheapestFirst for the seam this belongs to. */
|
|
155
|
+
const THINKING_RANK: Readonly<Record<string, number>> = {
|
|
156
|
+
minimal: 0,
|
|
157
|
+
none: 0,
|
|
158
|
+
low: 1,
|
|
159
|
+
medium: 3,
|
|
160
|
+
high: 4,
|
|
161
|
+
max: 4,
|
|
162
|
+
// Not a level but a switch, and the loudest statement an id can make about this: a channel that vends
|
|
163
|
+
// `kimi-k2` beside `kimi-k2-thinking` is naming the same model with its reasoning turned on.
|
|
164
|
+
thinking: 4,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/* An id naming NO level sits between the two ends rather than at either — the provider's own default, which for
|
|
168
|
+
* a model that can think is usually some thinking. Ranking it cheapest would seat a silent default ahead of an
|
|
169
|
+
* id that explicitly says `minimal`, and ranking it dearest would bury every model whose channel publishes no
|
|
170
|
+
* variants at all (Anthropic's, Kimi's) beneath one that does. Neither is what the id claims. */
|
|
171
|
+
const UNSTATED_THINKING = 2;
|
|
172
|
+
|
|
173
|
+
const thinkingRankOf = (family: string): number => {
|
|
174
|
+
const rank = lastRankOf(family, THINKING_RANK);
|
|
175
|
+
return rank === UNRANKED ? UNSTATED_THINKING : rank;
|
|
176
|
+
};
|
|
177
|
+
|
|
138
178
|
// The LAST recognized word wins, because tier words compose and the rightmost is the most specific one:
|
|
139
179
|
// gemini-flash-lite is the cheap end of Flash, gpt-codex-max the frontier end of Codex.
|
|
140
180
|
export const tierRankOf = (family: string): number => lastRankOf(family, TIER_RANK);
|
|
@@ -165,9 +205,9 @@ export const compareModelIds = (left: string, right: string): number => {
|
|
|
165
205
|
export const compareUnrankedModelIds = (left: string, right: string): number => compareModelIds(left, right) || left.localeCompare(right);
|
|
166
206
|
|
|
167
207
|
/* THE SAME TIER SCALE READ FROM THE OTHER END, for the one caller that wants the WEAKEST model rather than the
|
|
168
|
-
* strongest: the quick model behind
|
|
169
|
-
* what a user reaches for; this orders it by what a helper should spend, and the two are
|
|
170
|
-
* they share TIER_RANK rather than each naming its own list of cheap ids.
|
|
208
|
+
* strongest: the quick model behind an automatic helper (the commit message written at land time). A picker
|
|
209
|
+
* orders a catalog by what a user reaches for; this orders it by what a helper should spend, and the two are
|
|
210
|
+
* exact opposites — so they share TIER_RANK rather than each naming its own list of cheap ids.
|
|
171
211
|
*
|
|
172
212
|
* The direction of UNRANKED is the reason this can't just be compareModelIds reversed. There, an unrecognized
|
|
173
213
|
* family LEADS, because an id carrying no tier word is the provider's base line and a family nobody here has
|
|
@@ -181,7 +221,20 @@ export const compareCheapestFirst = (left: string, right: string): number => {
|
|
|
181
221
|
const rightFamily = familyOf(right);
|
|
182
222
|
return (
|
|
183
223
|
tierRankOf(rightFamily) - tierRankOf(leftFamily) ||
|
|
224
|
+
// BEFORE release, and that placement is the point: two rows of one model differing only in thinking
|
|
225
|
+
// level are the same model, so recency has nothing to say between them, and letting it speak is what
|
|
226
|
+
// seated the high variant. Tier still wins over both — a thinking Haiku is cheaper than a silent Opus.
|
|
227
|
+
thinkingRankOf(leftFamily) - thinkingRankOf(rightFamily) ||
|
|
184
228
|
compareRelease(releaseOf(left), releaseOf(right)) ||
|
|
185
229
|
releaseTierRankOf(rightFamily) - releaseTierRankOf(leftFamily)
|
|
186
230
|
);
|
|
187
231
|
};
|
|
232
|
+
|
|
233
|
+
/* WOULD RUNNING THIS ID MAKE THE MODEL THINK — as far as its name admits, which for a routed catalog is as far
|
|
234
|
+
* as anyone can tell without running it. True only for an id that spells out a level ABOVE the quiet end, so an
|
|
235
|
+
* ordinary id nobody has annotated (claude-haiku-4-5, kimi-k2) is never accused of it.
|
|
236
|
+
*
|
|
237
|
+
* Exported for one job: a settings row that lets someone pin the quick model has to be able to say that the row
|
|
238
|
+
* they picked is the thinking one. The ordering above keeps Auto off these by construction; a PIN is a
|
|
239
|
+
* deliberate choice and is honoured as written, which only works if the choice is legible when it is made. */
|
|
240
|
+
export const namesThinking = (id: string): boolean => thinkingRankOf(familyOf(id)) > UNSTATED_THINKING;
|