@intentic/sandbox-contract 1.167.0 → 1.169.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-catalog.d.ts +20 -2
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +80 -4
- package/dist/agent-catalog.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +36 -8
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +1 -2
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +37 -39
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +4 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.d.ts +25 -0
- package/dist/contracts/prepush.contract.d.ts.map +1 -0
- package/dist/contracts/prepush.contract.js +8 -0
- package/dist/contracts/prepush.contract.js.map +1 -0
- package/dist/contracts/sessions.contract.d.ts +1 -39
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +8 -10
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +56 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +7 -2
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts +36 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/events.d.ts +88 -159
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +37 -4
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +218 -157
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/quick-model.d.ts +1 -0
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/quick-model.js +1 -1
- package/dist/quick-model.js.map +1 -1
- package/dist/schemas.d.ts +176 -69
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +48 -40
- package/dist/schemas.js.map +1 -1
- package/dist/title.d.ts.map +1 -1
- package/dist/title.js.map +1 -1
- package/dist/workspace-state.d.ts +9 -0
- package/dist/workspace-state.d.ts.map +1 -0
- package/dist/workspace-state.js +61 -0
- package/dist/workspace-state.js.map +1 -0
- package/package.json +2 -2
- package/src/agent-catalog.test.ts +118 -0
- package/src/agent-catalog.ts +179 -15
- package/src/contracts/agent.contract.ts +1 -14
- package/src/contracts/prepush.contract.ts +16 -0
- package/src/contracts/system.contract.ts +15 -1
- package/src/events.test.ts +32 -0
- package/src/events.ts +105 -27
- package/src/index.ts +4 -3
- package/src/quick-model.ts +4 -2
- package/src/schemas.test.ts +13 -18
- package/src/schemas.ts +179 -152
- package/src/title.ts +6 -2
- package/src/workspace-state.test.ts +129 -0
- package/src/workspace-state.ts +150 -0
- package/dist/contracts/gate.contract.d.ts +0 -47
- package/dist/contracts/gate.contract.d.ts.map +0 -1
- package/dist/contracts/gate.contract.js +0 -9
- package/dist/contracts/gate.contract.js.map +0 -1
- package/src/contracts/gate.contract.ts +0 -19
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { FileContribution } from "@intentic/extension-api";
|
|
2
|
+
|
|
3
|
+
/* WHICH WORKSPACE FILE BACKS WHICH CORE VIEW — one declaration, read by both sides of the wire.
|
|
4
|
+
*
|
|
5
|
+
* The daemon's own state lives under `<workspace>/.intentic/`, the agent edits it out-of-band with its file
|
|
6
|
+
* tools, and the file watcher pushes every change as a `workspaceChanged` batch. Turning those paths back into
|
|
7
|
+
* "and therefore this view is stale" used to be a hand-written table in the BROWSER (web's systemEventRouting),
|
|
8
|
+
* maintained separately from the paths the daemon actually writes (composition.ts) — two lists of the same
|
|
9
|
+
* fact, in two packages, with nothing tying them together.
|
|
10
|
+
*
|
|
11
|
+
* They drifted, exactly as that shape always does. `.intentic/drafts/` is written by the AGENT (the drafts
|
|
12
|
+
* skill puts a file there) and rendered by the Drafts view, but it was never added to the browser's table — so
|
|
13
|
+
* a draft appearing on disk while the owner watched the page changed nothing until they refocused the tab.
|
|
14
|
+
* Extension settings and the members list were missing for the same reason; writing them out is what showed
|
|
15
|
+
* that neither is a drafts-shaped hole — see their entries.
|
|
16
|
+
*
|
|
17
|
+
* So the binding is declared HERE, once, in the package both the daemon and the browser already import, and
|
|
18
|
+
* each side derives what it needs: the daemon builds its store paths from `path`, the browser builds its
|
|
19
|
+
* invalidation table from `invalidates`. Adding a manifest without saying what it makes stale is now a change
|
|
20
|
+
* to one visible list rather than an omission in a file nobody edits — and `workspace-state.test.ts` fails when
|
|
21
|
+
* a daemon store names a `.intentic` path this list doesn't carry.
|
|
22
|
+
*
|
|
23
|
+
* This mirrors what routes.ts does for the route surface ("nothing is generated and nothing is hand-maintained")
|
|
24
|
+
* one layer over: the same refusal to keep the same knowledge in two places.
|
|
25
|
+
*
|
|
26
|
+
* EXTENSIONS declare their own half in their manifest (`contributes.files`, @intentic/extension-api), in the same
|
|
27
|
+
* two fields, and the browser unions the two lists — see staleQueryKeys. That split is what this table is FOR:
|
|
28
|
+
* before it existed the core enumeration had to carry `automations` and `automation-approvals`, query keys owned
|
|
29
|
+
* by the automations extension, because the extension had no way to say so itself. A key belongs to whoever
|
|
30
|
+
* queries it. */
|
|
31
|
+
|
|
32
|
+
// A core entry is an extension's `contributes.files` entry plus the one thing only the core list needs: the
|
|
33
|
+
// right to declare NO invalidations, which for a daemon-owned file is the answer more often than not.
|
|
34
|
+
export interface WorkspaceStateFile {
|
|
35
|
+
/* Workspace-root-relative, forward-slash — the space `workspaceChanged` paths arrive in. Matching is by
|
|
36
|
+
* PREFIX, which lets one entry cover three shapes without a second matching rule:
|
|
37
|
+
* - an exact file `.intentic/settings.json`
|
|
38
|
+
* - a directory `.intentic/drafts/` (one file per draft)
|
|
39
|
+
* - a name family `.intentic/environment.` (…Dockerfile, .custom.Dockerfile, .approved.Dockerfile)
|
|
40
|
+
* A directory entry keeps its trailing slash so it can never prefix-match a sibling file. */
|
|
41
|
+
readonly path: string;
|
|
42
|
+
/* The browser query keys this file's contents feed. EMPTY is a real answer, not a gap — a file the browser
|
|
43
|
+
* renders nothing from, or one deliberately kept off the push path — and `why` says which. Never a prefix
|
|
44
|
+
* test over `.intentic/` as a whole: one stray write must not cost every view a refetch, which is the
|
|
45
|
+
* amplification that once turned an iq index rebuild into an endless request storm. */
|
|
46
|
+
readonly invalidates: readonly string[];
|
|
47
|
+
// Why this file has no invalidations, for the entries that declare none. Absent when it has some.
|
|
48
|
+
readonly why?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const WORKSPACE_STATE_FILES: readonly WorkspaceStateFile[] = [
|
|
52
|
+
// A capability add/remove recomposes the environment overlay and can add or drop a repo's panel.
|
|
53
|
+
{ path: ".intentic/capabilities.json", invalidates: ["capabilities", "environment", "panels"] },
|
|
54
|
+
{ path: ".intentic/environment.", invalidates: ["environment"] },
|
|
55
|
+
{ path: ".intentic/settings.json", invalidates: ["settings"] },
|
|
56
|
+
// Written by the AGENT's file tools (the drafts skill), read by the owner's approval inbox — the one entry
|
|
57
|
+
// here whose whole point is that a change arrives from outside the browser that renders it.
|
|
58
|
+
{ path: ".intentic/drafts/", invalidates: ["drafts"] },
|
|
59
|
+
// ---- declared by the extension that renders them (contributes.files), not here ----
|
|
60
|
+
// The path is the DAEMON's (automations-store writes both), the query keys are the intentic.automations
|
|
61
|
+
// extension's. It declares them in its own manifest and the browser unions the two lists, so uninstalling
|
|
62
|
+
// the extension takes its invalidations with it instead of leaving a rule for a view that no longer exists.
|
|
63
|
+
{
|
|
64
|
+
path: ".intentic/automations.json",
|
|
65
|
+
invalidates: [],
|
|
66
|
+
why: "Declared by the intentic.automations extension's contributes.files — `automations` is its query key, not core's.",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
path: ".intentic/approvals/",
|
|
70
|
+
invalidates: [],
|
|
71
|
+
why: "Declared by the intentic.automations extension's contributes.files — `automation-approvals` is its query key, not core's.",
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/* ---- reached by no query, for reasons that are not oversights ----
|
|
75
|
+
*
|
|
76
|
+
* This channel's currency is a QUERY KEY, and invalidation only reaches a query something is observing.
|
|
77
|
+
* Both entries below are outside that by design, so an empty set is the honest record — naming a key no
|
|
78
|
+
* query uses would put the drift this table exists to remove straight back into it. Each says which
|
|
79
|
+
* constraint would have to move first, so the next reader doesn't re-derive it. */
|
|
80
|
+
{
|
|
81
|
+
path: ".intentic/extension-settings.json",
|
|
82
|
+
invalidates: [],
|
|
83
|
+
why: "Held in a module-level shallowRef store per extension (web's extensionSettingsStore) with no query observer, and deliberately so: api.settings.get must answer SYNCHRONOUSLY from an extension's first activate() line, and the store outlives every component scope. A module-level QueryObserver is the one shape that would make invalidation refetch, and this app already ruled it out — it detaches on the queryClient.clear() at logout (see useSandbox's sandbox-list mirror). So a remote member's setting edit reaches this browser on its next load, not live.",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
path: ".intentic/members.json",
|
|
87
|
+
invalidates: [],
|
|
88
|
+
why: "Not this view's source at all: SandboxAccess renders the PLATFORM's invite records (apiClient.invite.list), and this file is the daemon's ENFORCED copy — written first so a grant the enforcer never got is never recorded, then never read back. A change here means the two disagreed, which the write order makes fail-closed rather than stale.",
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// ---- daemon-owned, nothing derives from watching them ----
|
|
92
|
+
/* Agent session transcripts, rewritten on every streamed token.
|
|
93
|
+
*
|
|
94
|
+
* The memory notes under it (`projects/<slug>/memory/**`) ARE user-facing and the /memory view polls them
|
|
95
|
+
* every 30s, which is the one place in this table where a poll survives a real change feed being available.
|
|
96
|
+
* It stays a poll deliberately: the watcher's exclusion is a DESCENT filter, so reaching those notes means
|
|
97
|
+
* letting it walk `.intentic/claude` → `projects` → every project slug. Measured on the live workspace that
|
|
98
|
+
* is +119 watched directories against ~593 today (a fifth more), with 314 continuously-rewritten transcripts
|
|
99
|
+
* inside the newly-watched set, to make ONE memory directory live. Notes change at agent-turn cadence, so the
|
|
100
|
+
* poll costs a request a minute and the alternative costs a permanent 20% on the watcher. */
|
|
101
|
+
{
|
|
102
|
+
path: ".intentic/claude/",
|
|
103
|
+
invalidates: [],
|
|
104
|
+
why: "Agent session transcripts — see the note above on why the memory notes under it stay polled.",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
path: ".intentic/ci.json",
|
|
108
|
+
invalidates: [],
|
|
109
|
+
why: "Webhook secret + conclusion memory; the Pipelines view reads it through /ci/runs, not off disk.",
|
|
110
|
+
},
|
|
111
|
+
{ path: ".intentic/bridge-tokens.json", invalidates: [], why: "Hashed ACP bridge tokens, listed on demand by the owner." },
|
|
112
|
+
{
|
|
113
|
+
path: ".intentic/owner.json",
|
|
114
|
+
invalidates: [],
|
|
115
|
+
why: "Bound once on first use; a change here means the sandbox was re-owned, which re-authenticates anyway.",
|
|
116
|
+
},
|
|
117
|
+
{ path: ".intentic/workspace.json", invalidates: [], why: "The workspace identity, read from the /events hello frame rather than as a file." },
|
|
118
|
+
{ path: ".intentic/templates.json", invalidates: [], why: "Scaffold templates, read when the scaffold dialog opens." },
|
|
119
|
+
{
|
|
120
|
+
path: ".intentic/browser/",
|
|
121
|
+
invalidates: [],
|
|
122
|
+
why: "Browser-login profiles: Chromium rewrites these constantly. Descent-ignored by the watcher outright.",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
path: ".intentic/extensions/",
|
|
126
|
+
invalidates: [],
|
|
127
|
+
why: "Extension checkouts — whole git clones. The `extensions` query is driven by the capability manifest above, not by their contents.",
|
|
128
|
+
},
|
|
129
|
+
{ path: ".intentic/plugins/", invalidates: [], why: "Agent plugin dirs, read by the SDK's loader each turn." },
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
/* The query keys a batch of changed paths makes stale, deduped and stable. The browser's `/events` handler calls
|
|
133
|
+
* this; keeping it here rather than in the web means the rule is unit-testable without a query client, and the
|
|
134
|
+
* daemon can assert against the same table.
|
|
135
|
+
*
|
|
136
|
+
* `contributed` is what the ACTIVATED extensions declared in `contributes.files` — passed in rather than
|
|
137
|
+
* imported, because which extensions are live is a browser fact this package has no way to know. It is a
|
|
138
|
+
* required argument for the same reason: an added second source that callers may forget is a source that
|
|
139
|
+
* silently does nothing, which is the failure this whole file exists to remove. Extension entries are unioned
|
|
140
|
+
* flat with the core ones, not layered over them: both lists describe the same fact about the same file, and a
|
|
141
|
+
* path can legitimately match one entry in each — a core prefix that invalidates nothing must not veto a
|
|
142
|
+
* narrower extension entry beneath it, or everything under one of the daemon's machine-state prefixes would be
|
|
143
|
+
* unreachable to extensions by construction. */
|
|
144
|
+
export const staleQueryKeys = (paths: readonly string[], contributed: readonly FileContribution[]): readonly string[] => [
|
|
145
|
+
...new Set(
|
|
146
|
+
[...WORKSPACE_STATE_FILES, ...contributed]
|
|
147
|
+
.filter((file) => file.invalidates.length > 0 && paths.some((path) => path.startsWith(file.path)))
|
|
148
|
+
.flatMap((file) => file.invalidates),
|
|
149
|
+
),
|
|
150
|
+
];
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
export declare const gateContract: {
|
|
2
|
-
verdict: import("@orpc/contract").ContractProcedureBuilderWithOutput<import("@orpc/contract").Schema<unknown, unknown>, import("zod").ZodObject<{
|
|
3
|
-
status: import("zod").ZodEnum<{
|
|
4
|
-
armed: "armed";
|
|
5
|
-
cancelled: "cancelled";
|
|
6
|
-
error: "error";
|
|
7
|
-
failed: "failed";
|
|
8
|
-
idle: "idle";
|
|
9
|
-
passed: "passed";
|
|
10
|
-
running: "running";
|
|
11
|
-
}>;
|
|
12
|
-
command: import("zod").ZodString;
|
|
13
|
-
startedAt: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
14
|
-
finishedAt: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
15
|
-
exitCode: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
16
|
-
timedOut: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
17
|
-
output: import("zod").ZodString;
|
|
18
|
-
fingerprint: import("zod").ZodString;
|
|
19
|
-
stale: import("zod").ZodBoolean;
|
|
20
|
-
implicated: import("zod").ZodArray<import("zod").ZodObject<{
|
|
21
|
-
agentId: import("zod").ZodString;
|
|
22
|
-
title: import("zod").ZodOptional<import("zod").ZodString>;
|
|
23
|
-
provider: import("zod").ZodOptional<import("zod").ZodString>;
|
|
24
|
-
paths: import("zod").ZodArray<import("zod").ZodString>;
|
|
25
|
-
}, import("zod/v4/core").$strip>>;
|
|
26
|
-
fix: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
27
|
-
startedAt: import("zod").ZodNumber;
|
|
28
|
-
conversationId: import("zod").ZodString;
|
|
29
|
-
outcome: import("zod").ZodEnum<{
|
|
30
|
-
done: "done";
|
|
31
|
-
error: "error";
|
|
32
|
-
running: "running";
|
|
33
|
-
}>;
|
|
34
|
-
detail: import("zod").ZodOptional<import("zod").ZodString>;
|
|
35
|
-
}, import("zod/v4/core").$strip>>;
|
|
36
|
-
}, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
|
|
37
|
-
run: import("@orpc/contract").ContractProcedureBuilderWithOutput<import("@orpc/contract").Schema<unknown, unknown>, import("zod").ZodObject<{
|
|
38
|
-
ok: import("zod").ZodLiteral<true>;
|
|
39
|
-
}, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
|
|
40
|
-
cancel: import("@orpc/contract").ContractProcedureBuilderWithOutput<import("@orpc/contract").Schema<unknown, unknown>, import("zod").ZodObject<{
|
|
41
|
-
ok: import("zod").ZodLiteral<true>;
|
|
42
|
-
}, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
|
|
43
|
-
fix: import("@orpc/contract").ContractProcedureBuilderWithOutput<import("@orpc/contract").Schema<unknown, unknown>, import("zod").ZodObject<{
|
|
44
|
-
ok: import("zod").ZodLiteral<true>;
|
|
45
|
-
}, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
|
|
46
|
-
};
|
|
47
|
-
//# sourceMappingURL=gate.contract.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gate.contract.d.ts","sourceRoot":"","sources":["../../src/contracts/gate.contract.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,YAAY;IACrB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IACP,GAAG;;;IACH,MAAM;;;IACN,GAAG;;;CACN,CAAC"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { oc } from "@orpc/contract";
|
|
2
|
-
import { GateVerdictSchema, OkSchema } from "../schemas.js";
|
|
3
|
-
export const gateContract = {
|
|
4
|
-
verdict: oc.route({ method: "GET", path: "/gate/verdict" }).output(GateVerdictSchema),
|
|
5
|
-
run: oc.route({ method: "POST", path: "/gate/run" }).output(OkSchema),
|
|
6
|
-
cancel: oc.route({ method: "POST", path: "/gate/cancel" }).output(OkSchema),
|
|
7
|
-
fix: oc.route({ method: "POST", path: "/gate/fix" }).output(OkSchema),
|
|
8
|
-
};
|
|
9
|
-
//# sourceMappingURL=gate.contract.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gate.contract.js","sourceRoot":"","sources":["../../src/contracts/gate.contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAY5D,MAAM,CAAC,MAAM,YAAY,GAAG;IACxB,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC;IACrF,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IACrE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC3E,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;CACxE,CAAC"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { oc } from "@orpc/contract";
|
|
2
|
-
import { GateVerdictSchema, OkSchema } from "../schemas.js";
|
|
3
|
-
|
|
4
|
-
// The landing gate — the check command run over the composite of landed work once the fleet goes quiet (see
|
|
5
|
-
// GateVerdictSchema for where this sits and why). Four verbs, all about ONE verdict: the gate answers about the
|
|
6
|
-
// main working tree, of which there is exactly one, so nothing here is addressed by id.
|
|
7
|
-
//
|
|
8
|
-
// `verdict` is the one read, and the panel polls it; it recomputes staleness per call, so a passed verdict stops
|
|
9
|
-
// claiming a green light the moment the tree moves under it. The three writes answer `ok` and nothing else —
|
|
10
|
-
// each starts work that outlives the request, so there is no result to return and the poll is what reports.
|
|
11
|
-
// `run` arms nothing and waits for nothing: it starts the check now, the user's own "I'm about to commit, check
|
|
12
|
-
// this". `fix` opens a seeded workspace conversation for a red verdict; it has the same registry lifecycle as
|
|
13
|
-
// /ci/fix, minus the isolated worktree it must not have.
|
|
14
|
-
export const gateContract = {
|
|
15
|
-
verdict: oc.route({ method: "GET", path: "/gate/verdict" }).output(GateVerdictSchema),
|
|
16
|
-
run: oc.route({ method: "POST", path: "/gate/run" }).output(OkSchema),
|
|
17
|
-
cancel: oc.route({ method: "POST", path: "/gate/cancel" }).output(OkSchema),
|
|
18
|
-
fix: oc.route({ method: "POST", path: "/gate/fix" }).output(OkSchema),
|
|
19
|
-
};
|