@intentic/extension-api 1.310.0 → 1.312.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 CHANGED
@@ -1,196 +1,40 @@
1
- # @intentic/extension-api
1
+ # extension-api
2
2
 
3
- The one SDK an extension programs against: the extension-author contract for the intentic app.
3
+ The versioned public API an intentic extension compiles against: `IntenticApi` for its browser half, `ExtensionServerApi` for its backend half, and the helpers both halves share.
4
4
 
5
- One of the packages an extension may depend on, with `@intentic/extension-manifest`,
6
- `@intentic/extension-ui` and `@intentic/sandbox-contract`. Published to npm; it must stay free of app
7
- internals. See the extension system in [ARCHITECTURE.md](../../ARCHITECTURE.md) for how the host loads and
8
- gates extensions.
9
-
10
- It **does** name `@intentic/sandbox-contract` types, and that is deliberate: `api.sandbox.rpc` (and the backend's
11
- `api.daemon.rpc`) is the daemon's own contract as a typed client, which is the whole reason an extension no longer has to build a URL to reach
12
- it. The dependency is type-only, so nothing of the contract lands in an extension's runtime. This used to be
13
- forbidden: the contract imported the manifest schema from here, so depending back would have closed a cycle.
14
- `@intentic/extension-manifest` exists to break exactly that, and its README has the reasoning.
15
-
16
- ## What's here
17
-
18
- - **[api.ts](src/api.ts)**: `IntenticApi`, the host surface delivered to `activate(api, context)`. There is
19
- no ambient global; everything an extension registers is a `Disposable` pushed onto
20
- `context.subscriptions`, so deactivation unwinds it. `api.sandbox.rpc` is the typed daemon client, gated by
21
- the manifest's `permissions.sandbox` allowlist exactly as the older `request`/`json` doors are.
22
- - **The manifest schema lives in [@intentic/extension-manifest](../extension-manifest)**, not here: it is what
23
- an extension *declares*, and the daemon needs it without needing any of this package. The manifest is the
24
- **approval + gating surface**: the install dialog shows exactly the declared contribution points, and the host
25
- refuses any runtime registration (view, command, viewer, setting, process…) the approved manifest never
26
- declared. Contribution points: `views`, `files`, `viewers`, `documents`, `commands`, `settings`,
27
- `processes`, `agent`, `environment`, `capabilities`, `listener`, `automationTemplates`, `bin`, plus the
28
- `permissions.sandbox` route allowlist. That list is not prose to be kept in sync by hand:
29
- `surface-guard.test.ts` reads it back out of this file and fails when it stops matching the schema.
30
- Every one of those points carries its own description, generated out to
31
- [an authoring schema](https://intentic.dev/intentic-extension.schema.json); point a manifest's `$schema` at
32
- it and an editor completes the fields, explains what each does, and marks a key nothing declares.
33
- A `listener` owns both halves of its public vocabulary: labelled event types for daemon validation and the
34
- source/filter/starter wording a generic automation editor renders. Installing a listener therefore adds a
35
- configurable automation source without an app release or a second provider table.
36
- `automationTemplates` is the other half of that bargain: the starting points for a pack's own service:
37
- trigger, prompt, guard, setup instructions: declared by whoever knows the service rather than written into
38
- the automations surface. Both fold into one catalogue the daemon serves (`GET /automations/catalog`), which
39
- is also what `POST /automations` validates against, so the editor cannot offer a trigger the daemon refuses.
40
- Identity is derived, never declared: `extensionIdOf(manifest) = ${publisher}.${name}`.
41
- - **[facts.ts](src/facts.ts)**: the stable **detection** vocabulary (`RepoFacts`, `CapabilityFacts`) a
42
- view's `detect()` reads to decide when to activate. This is *not* the data plane.
43
- - **[server.ts](src/server.ts)**: `ExtensionServerApi`, the BACKEND half's surface. A manifest `server`
44
- bundle exports `activateServer(api, context)` and runs in the daemon's backend host (one separate
45
- supervised process shared by every enabled backend); `api.routes.mount` serves the extension's own
46
- `/x/<id>/…` namespace, `api.daemon.rpc` reaches the daemon's routes under the manifest's
47
- `permissions.daemon` allowlist, and workspace files are plain `node:fs` under `api.workspaceRoot`: full
48
- trust, so paths rather than a file service. The extension's own namespace needs no `permissions.sandbox`
49
- entry on the UI side: its backend is its own.
50
-
51
- Three surfaces, at three different grains, and the grain is what picks one. A **view** activates per *repo*
52
- off the facts (`rail`, `directory`, `sandbox`). A **viewer** takes over a *file extension*. A **document**
53
- answers per *directory*: `detect(path)` marks the rows it can explain in the Workspace tree, and the host
54
- opens the provider's component as a tab beside the code. A monorepo is one repo with fifty-five documented
55
- packages, which is exactly the case a per-repo `detect()` cannot express. An offer that is EVIDENCE about the
56
- directory rather than an affordance every directory of its kind has says so (`evidence: true`), and the tree
57
- keeps its icon on the row instead of revealing it on hover: the difference between a reader seeing which
58
- packages have a page and a reader having to go looking for one.
59
- - **[scope.ts](src/scope.ts)**, `sandboxRef` (with `sandboxShallowRef` and `sandboxValue`) and
60
- `sandboxScopeGuard`: how an extension keeps state that belongs to ONE sandbox. See "Where state lives" below;
61
- this is the rule most easily got wrong, because getting it wrong looks fine until somebody switches sandbox.
62
- - **[background.ts](src/background.ts)**, `sandboxPoll` and `sandboxLedger`: the work an extension does while
63
- none of it is on screen. A tile that badges has to be filled by something, and what has already been seen has
64
- to be written down somewhere; both were hand-written in six extensions before they were here.
65
- - **[stream.ts](src/stream.ts)**, **[version.ts](src/version.ts)**: SSE/ndjson helpers and the host API
66
- version (`engines.intentic` is checked against it before activation).
67
-
68
- Version 2 makes listener contributions self-describing (`events` + `automation`); version 1 listeners only
69
- declared bare event ids and cannot describe a generic editor. Version 2.1 adds the backend half: the
70
- manifest `server` bundle and `permissions.daemon`, additively: a 2.0 manifest is a 2.1 manifest that ships
71
- no backend.
72
-
73
- ## The data plane
74
-
75
- An extension talks to the daemon through `api.sandbox.rpc`, the daemon's own contract as a typed client: a call
76
- names a procedure (`api.sandbox.rpc.ci.runs()`), its input is checked at build time, and its answer arrives parsed
77
- by the procedure's output schema, so there is no path to spell and no parse to write. Auth is injected host-side;
78
- the bundle never sees a token. **Its reach is not unrestricted:** the host resolves each call to the method and
79
- path it will send and matches that against the manifest's `permissions.sandbox` allowlist, the same gate
80
- `request`/`json` pass through, and an undeclared route throws before anything is sent.
81
-
82
- `request(path)` stays for what the contract does not carry: bytes (`/workspace/raw`), uploads, an extension's own
83
- `/x/<id>/` backend. `json(path)` stays in the published surface for extensions built against it; every extension
84
- in this repository calls a contract route through `rpc` (the `contract-paths` check holds them to it).
85
-
86
- A backend reaches the daemon the same way, through `api.daemon.rpc`: the same typed client, built by the daemon's
87
- backend host and handed to `activateServer`, so a server bundle carries no client of its own. It presents the
88
- extension's minted grant, and the host refuses a call before sending it unless the manifest's `permissions.daemon`
89
- covers the method and path it resolves to: the verdict the daemon's grant reaches for the same call through
90
- `api.daemon.request`, which stays for bytes (`/workspace/raw`), as `json` does for backends built against it.
91
-
92
- The in-repo, compiled-together design means a wire change is a compiler error fixed atomically, so there is no
93
- separate "stable data API" to promote. `facts.ts` stays the stable surface only for *detection*.
94
-
95
- ## Where state lives
96
-
97
- Three tiers, and the tier decides what happens when the user points the browser at a **different sandbox**.
98
- Everything an extension holds is about one workspace, so a switch has to leave nothing of the last one behind.
99
-
100
- - **Cached reads**: `useQuery` in a view, or `api.sandbox.fetch(query)` from outside one. Key them with
101
- `api.sandbox.key(...)` and the switch is handled by construction: the key carries the active sandbox id, so
102
- the next box is a different cache entry. Use the *same* key for a view's query and for the badge poll that
103
- warms it, and the poll's answer becomes the view's first paint.
104
- - **State inside a mounted component**: an ordinary `ref` in a `.vue` file. Nothing to do; it dies with the
105
- component.
106
- - **Module state owned by `activate()`**: the badge counts, presence maps and poll results that must survive
107
- the view being unmounted, because a badge you only see after opening the view is pointless. Declare it with
108
- `sandboxRef(() => initial)` and the host empties it on every switch. There is no subscription to remember
109
- and no teardown to write; `dispose` is there for state that owns an object URL or anything else the garbage
110
- collector will not take back. A value replaced whole rather than edited in place (a map, an object with its
111
- own methods) goes in `sandboxShallowRef` instead, the same lifetime without the deep proxy.
112
-
113
- For anything asynchronous in that third tier, take a `sandboxScopeGuard()` **before** the await and ask it
114
- **after**: a poll issued against the last sandbox otherwise resolves a moment later and writes its answer
115
- into the new one, which is the same wrong badge with a harder repro. It matters twice over for a call that
116
- WRITES: acknowledging what a badge has shown, in the wrong workspace's tree, is bookkeeping no later poll
117
- corrects.
118
-
119
- ## Keeping a tile current while nothing is mounted
120
-
121
- Most of that third tier exists to feed a rail badge, so `sandboxPoll` covers the whole shape and you should not
122
- need `sandboxRef` directly for one:
123
-
124
- ```ts
125
- const { state: unseen, start } = sandboxPoll<readonly Finding[]>({
126
- host, // your hostSlot's accessor — nothing is bound until activate()
127
- everyMs: 10 * 60_000, // a backstop here: the file binding below is what actually refreshes this
128
- initial: () => [],
129
- read: async (api) => findings(await api.sandbox.fetch(query())),
130
- });
5
+ ```mermaid
6
+ flowchart LR
7
+ web["Editor<br/>extension host"] -- "activate(api, context)" --> view["Extension bundle<br/>views · commands"]
8
+ backend["Daemon<br/>backend host"] -- "activateServer(api)" --> server["Server bundle<br/>routes under /x/id"]
9
+ sdk(["extension-api<br/>types · helpers"]) -.-> view
10
+ sdk -.-> server
11
+ loader["Daemon loader"] -- "engines.intentic<br/>vs extensionApiVersion" --> sdk
131
12
  ```
132
13
 
133
- `start()` returns the `Disposable` to push onto `context.subscriptions`; `refresh()` reads off-cycle for the
134
- moments that should not wait out the interval. The five rules a hand-written version has to remember: never
135
- reject, skip an unreachable daemon, discard an answer that outlived its sandbox, keep the last good value on
136
- failure, stop the clock on disposal, are the poll's, not yours. Pass `immediate: false` if there is nothing
137
- worth asking until something else tells you what to ask about, and read `previous` in `read` if a round
138
- accumulates onto what you already hold rather than replacing it.
139
-
140
- **If the answer lives in a file, the write is the feed and the interval is only a backstop.** `start()`
141
- subscribes to your own `contributes.files` paths being written (`api.workspace.onDidChangeFiles`), so a
142
- declaration you already made is what moves the tile, within a frame of the file changing rather than at the next
143
- tick. Nothing to opt into: declare the paths your views derive from and the badge over them reacts; declare none
144
- and the poll is the timer it always was. Bursts coalesce, so a run writing a file per story costs one read, and a
145
- reconnect wakes it too, because a frame pushed while the stream was down is a frame nobody resends. Pick
146
- `everyMs` accordingly: minutes for a file-backed badge (it is covering a dropped event, not carrying the news),
147
- and the honest cadence of the source for anything behind somebody else's API, where nothing can push at all.
148
- All polls in a window share one background-read lane, so aligned extension clocks and reconnect wakes cannot
149
- turn into parallel request bursts. Repeated triggers coalesce while waiting; one that lands during the read gets
150
- one trailing pass, so serialization does not trade load for a missed update.
151
-
152
- What the tile SAYS stays yours: `badge()` is the judgement each surface exists to make, and no two of them
153
- agree about tone or wording.
154
-
155
- It says it on two channels, and which one you reach for is the only rule the host enforces. `count`/`mark`
156
- is what the reader is OWED, and it wears the chip: a number, a tone, something to go and do. `running` is
157
- what is HAPPENING, a sentence like `2 running`, and it draws as a turning mark in the tile's own corner with
158
- no plate and no tone. Fill both when both are true — a red branch with its fix already re-running is the
159
- ordinary case, not the exotic one — and the tile says so without either claim evicting the other. Either
160
- channel seats the tile: the rail has always shown live work, and a run in flight that had to fail before
161
- earning a tile would be invisible for exactly as long as watching it was worth anything. A running mark needs
162
- no read marker and no ledger, which is the other half of why it is a separate channel: it goes out by itself
163
- when the work ends.
164
-
165
- `sandboxLedger(host, path)` is the other half: the JSON file recording what the owner has already seen, as
166
- `key → mark`, where the mark is what makes an entry stale. Compare marks (a chore's evidence digest, a story's
167
- verdict) and the same key with new evidence is news again; ignore them and it is a plain presence ledger. It
168
- reads a missing or mangled file as "nothing acknowledged", writes nothing when nothing moved, and holds the
169
- scope guard across its own read-then-write so an acknowledgement cannot land in the wrong workspace's tree.
170
-
171
- This is not advice. `sandboxScope.guard.test.ts` in the app walks each extension's UI entry through its own
172
- imports and refuses module-level `ref`/`shallowRef`/`reactive`, any reassignable module binding, and any
173
- repeating clock in what it reaches, because the failure it prevents was found in six extensions at once: a
174
- rail tile reading `21` under a workspace that had two.
175
-
176
- ## Authoring an extension
177
-
178
- `activate(api, context)` registers contributions and returns; `deactivate` is optional. A UI extension also
179
- ships a prebuilt single-file ESM `entry` bundle (built with `vue` and `@intentic/extension-api` external).
180
- The five UI extensions under [`_extensions/`](../../_extensions) are the working templates; start from one.
14
+ - There is no ambient global. The host hands `IntenticApi` to `activate(api, context)`, and everything registered
15
+ returns a `Disposable`. A manifest's `server` bundle gets `ExtensionServerApi` in a Node process shared by every
16
+ enabled extension, and reaches daemon routes only as far as `permissions.daemon` allows.
17
+ - This package is published to npm, and its API only grows within a major. `extensionApiVersion` moves
18
+ with every surface change (additive is a minor), and a manifest's `engines.intentic` range is matched against it at
19
+ load, failing closed. The editor's `surface-guard.test.ts` fails when the surface moves without a new entry in
20
+ `src/surface.json`.
21
+ - Helpers every extension needs live here too: sandbox-scoped module state (`sandboxRef`, cleared on every sandbox
22
+ switch), background polling for rail badges, `sandboxLedger` (whose writes reject rather than overwrite a file they
23
+ could not read), `sandboxDocument` (a file of the extension's own read through the `conversions` its shape has had,
24
+ and written with what a newer version of the extension put in it kept), SSE and ndjson stream reading, and the
25
+ payload for `api.workspace.openDiff`.
26
+ - Contribution points: `agent`, `automationTemplates`, `bin`, `capabilities`, `commands`, `documents`,
27
+ `environment`, `files`, `listener`, `processes`, `settings`, `viewers` and `views`, plus the manifest's top-level
28
+ fields, all read off `CONTRIBUTION_POINTS` in
29
+ [extension-manifest](../extension-manifest/src/points/index.ts).
30
+ - `./protocol` exports only the version and the engines matcher, for the daemon, which cannot load the Vue-dependent
31
+ barrel.
181
32
 
182
33
  ## Key files
183
34
 
184
- - [src/api.ts](src/api.ts): the handle an extension is given; the centre of this package.
185
- - [src/facts.ts](src/facts.ts): the public facts a view's `detect()` answers from.
186
- - [src/engines.ts](src/engines.ts): how `engines.intentic` is matched against the version below, for the host
187
- and the daemon alike.
188
- - [src/route.ts](src/route.ts): the query rules a view with internal navigation uses.
189
- - [src/scope.ts](src/scope.ts): module state that belongs to one sandbox, and the guard for work in flight
190
- across a switch.
191
- - [src/version.ts](src/version.ts) and [src/surface.json](src/surface.json): the protocol version, and what
192
- each version of it promised.
193
-
194
- The manifest schema and the `permissions.sandbox` matcher are **not here**: they moved to
195
- [@intentic/extension-manifest](../extension-manifest), which exists so the daemon can read a manifest without
196
- depending on the browser-facing API.
35
+ - [src/api.ts](src/api.ts) — `IntenticApi`, `ExtensionContext` and the module shape a bundle exports.
36
+ - [src/server.ts](src/server.ts) — `ExtensionServerApi`, the backend half.
37
+ - [src/version.ts](src/version.ts) — `extensionApiVersion` and what each version added.
38
+ - [src/surface.json](src/surface.json) — the recorded public surface, per version.
39
+ - [src/scope.ts](src/scope.ts) — module state that belongs to one sandbox.
40
+ - [src/background.ts](src/background.ts) — `sandboxPoll` and `sandboxLedger` for work done off screen.
@@ -1,3 +1,4 @@
1
+ import { type Conversion, type Granularity } from "@intentic/sandbox-contract/documents";
1
2
  import type { Ref } from "vue";
2
3
  import type { Disposable, IntenticApi } from "./api.js";
3
4
  export interface SandboxPoll<T> {
@@ -20,4 +21,33 @@ export interface SandboxLedger {
20
21
  replace(entries: Readonly<Record<string, string>>): Promise<boolean>;
21
22
  }
22
23
  export declare const sandboxLedger: (host: () => IntenticApi, path: string) => SandboxLedger;
24
+ export declare const conversions: {
25
+ readonly at: <const Path extends string, const Inner extends Conversion>(path: Path, inner: Inner) => import("@intentic/sandbox-contract/documents").AtConversion<Path, Inner>;
26
+ readonly drop: <const Key extends string>(key: Key) => import("@intentic/sandbox-contract/documents").DropConversion<Key>;
27
+ readonly dropAll: <const Keys extends readonly string[]>(keys: Keys) => { readonly [I in keyof Keys]: import("@intentic/sandbox-contract/documents").DropConversion<Keys[I] & string>; };
28
+ readonly fold: <const From extends string, const Into extends string, Out>(describe: string, shape: {
29
+ readonly from: readonly From[];
30
+ readonly into: Into;
31
+ readonly applies: (value: import("@intentic/sandbox-contract/documents").JsonObject) => boolean;
32
+ readonly convert: (fields: Readonly<Partial<Record<From, unknown>>>, whole: import("@intentic/sandbox-contract/documents").JsonObject) => Out;
33
+ }) => import("@intentic/sandbox-contract/documents").FoldConversion<From, Into, Out>;
34
+ readonly mapValue: <const Key extends string, const Mapping extends Readonly<Record<string, unknown>>>(key: Key, mapping: Mapping) => import("@intentic/sandbox-contract/documents").MapValueConversion<Key, Mapping>;
35
+ readonly nested: <const Path extends string, const History extends readonly Conversion[], const Entries extends boolean = false>(path: Path, history: History, entries?: Entries) => { readonly [I in keyof History]: History[I] extends import("@intentic/sandbox-contract/documents").RetireEntriesConversion<unknown> ? import("@intentic/sandbox-contract/documents").AtConversion<Path, History[I]> : import("@intentic/sandbox-contract/documents").AtConversion<Entries extends true ? `${Path}.*` : Path, History[I]>; };
36
+ readonly pinDefault: <const Key extends string, const Value>(key: Key, value: Value) => import("@intentic/sandbox-contract/documents").PinDefaultConversion<Key, Value>;
37
+ readonly rename: <const From extends string, const To extends string>(from: From, to: To) => import("@intentic/sandbox-contract/documents").RenameConversion<From, To>;
38
+ readonly retireEntries: <Retired>(describe: string, isRetired: (entry: unknown) => entry is Retired) => import("@intentic/sandbox-contract/documents").RetireEntriesConversion<Retired>;
39
+ readonly retype: <const Key extends string, In, Out>(key: Key, guard: (value: unknown) => value is In, convert: (value: In) => Out, describe?: string) => import("@intentic/sandbox-contract/documents").RetypeConversion<Key, In, Out>;
40
+ readonly transform: <In extends import("@intentic/sandbox-contract/documents").JsonObject, Out extends import("@intentic/sandbox-contract/documents").JsonObject>(describe: string, applies: (value: import("@intentic/sandbox-contract/documents").JsonObject) => value is In, convert: (value: In) => Out) => import("@intentic/sandbox-contract/documents").TransformConversion<In, Out>;
41
+ };
42
+ export interface SandboxDocument<T> {
43
+ read(): Promise<T>;
44
+ update(change: (current: T) => T): Promise<boolean>;
45
+ }
46
+ export interface SandboxDocumentOptions<T> {
47
+ readonly parse: (raw: unknown) => T | undefined;
48
+ readonly fallback: () => T;
49
+ readonly history?: readonly Conversion[];
50
+ readonly granularity?: Granularity;
51
+ }
52
+ export declare const sandboxDocument: <T>(host: () => IntenticApi, path: string, options: SandboxDocumentOptions<T>) => SandboxDocument<T>;
23
53
  //# sourceMappingURL=background.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../src/background.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAOxD,MAAM,WAAW,WAAW,CAAC,CAAC;IAE1B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAEvB,KAAK,IAAI,UAAU,CAAC;IAEpB,OAAO,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC;IAEjC,QAAQ,CAAC,IAAI,EAAE,MAAM,WAAW,CAAC;IAEjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAE1B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAE7D,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAE7B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;CAC5C;AAqFD,eAAO,MAAM,WAAW,GAAI,CAAC,WAAW,kBAAkB,CAAC,CAAC,CAAC,KAAG,WAAW,CAAC,CAAC,CAsC5E,CAAC;AAIF,MAAM,WAAW,aAAa;IAE1B,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAElD,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElE,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE;AAKD,eAAO,MAAM,aAAa,SAAU,MAAM,WAAW,QAAQ,MAAM,KAAG,aA6BrE,CAAC"}
1
+ {"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../src/background.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,UAAU,EAKf,KAAK,WAAW,EAQnB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAOxD,MAAM,WAAW,WAAW,CAAC,CAAC;IAE1B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAEvB,KAAK,IAAI,UAAU,CAAC;IAEpB,OAAO,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC;IAEjC,QAAQ,CAAC,IAAI,EAAE,MAAM,WAAW,CAAC;IAEjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAE1B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAE7D,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAE7B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;CAC5C;AAqFD,eAAO,MAAM,WAAW,GAAI,CAAC,WAAW,kBAAkB,CAAC,CAAC,CAAC,KAAG,WAAW,CAAC,CAAC,CAsC5E,CAAC;AAKF,MAAM,WAAW,aAAa;IAE1B,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAElD,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElE,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE;AAKD,eAAO,MAAM,aAAa,SAAU,MAAM,WAAW,QAAQ,MAAM,KAAG,aA8CrE,CAAC;AAKF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;CAA+G,CAAC;AAMxI,MAAM,WAAW,eAAe,CAAC,CAAC;IAE9B,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAGnB,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC;IAErC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAE3B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAEzC,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;CACtC;AAED,eAAO,MAAM,eAAe,GAAI,CAAC,QAAQ,MAAM,WAAW,QAAQ,MAAM,WAAW,sBAAsB,CAAC,CAAC,CAAC,KAAG,eAAe,CAAC,CAAC,CA+C/H,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { at, carryUnknown, convertDocument, drop, dropAll, fold, mapValue, nested, pinDefault, rename, retireEntries, retype, transform, } from "@intentic/sandbox-contract/documents";
1
2
  import { sandboxRef, sandboxScopeGuard } from "./scope.js";
2
3
  const WAKE_MS = 400;
3
4
  const scheduledReads = [];
@@ -107,13 +108,25 @@ export const sandboxPoll = (options) => {
107
108
  };
108
109
  const sameEntries = (left, right) => Object.keys(left).length === Object.keys(right).length && Object.entries(left).every(([key, mark]) => right[key] === mark);
109
110
  export const sandboxLedger = (host, path) => {
110
- const read = async () => {
111
- const parsed = await host().workspace.readJson(path);
112
- return Object.fromEntries(Object.entries(parsed ?? {}).filter((entry) => typeof entry[1] === `string`));
111
+ const marksOf = (parsed) => typeof parsed === `object` && parsed !== null && !Array.isArray(parsed)
112
+ ? Object.fromEntries(Object.entries(parsed).filter((entry) => typeof entry[1] === `string`))
113
+ : {};
114
+ const read = async () => marksOf(await host().workspace.readJson(path));
115
+ const readToWrite = async () => {
116
+ const answer = await host().sandbox.rpc.workspace.file({ path });
117
+ if (!answer.present) {
118
+ return {};
119
+ }
120
+ try {
121
+ return marksOf(JSON.parse(answer.content));
122
+ }
123
+ catch {
124
+ return {};
125
+ }
113
126
  };
114
127
  const settle = async (next) => {
115
128
  const current = sandboxScopeGuard();
116
- const seen = await read();
129
+ const seen = await readToWrite();
117
130
  const wanted = next(seen);
118
131
  if (sameEntries(seen, wanted)) {
119
132
  return true;
@@ -130,4 +143,50 @@ export const sandboxLedger = (host, path) => {
130
143
  replace: async (entries) => settle(() => entries),
131
144
  };
132
145
  };
146
+ export const conversions = { at, drop, dropAll, fold, mapValue, nested, pinDefault, rename, retireEntries, retype, transform };
147
+ export const sandboxDocument = (host, path, options) => {
148
+ const convert = (raw) => convertDocument(options.history ?? [], options.granularity ?? `object`, raw).value;
149
+ const readRaw = async () => {
150
+ const answer = await host().sandbox.rpc.workspace.file({ path });
151
+ if (!answer.present) {
152
+ return undefined;
153
+ }
154
+ return convert(JSON.parse(answer.content));
155
+ };
156
+ const read = async () => {
157
+ try {
158
+ const raw = await readRaw();
159
+ return (raw === undefined ? undefined : options.parse(raw)) ?? options.fallback();
160
+ }
161
+ catch {
162
+ return options.fallback();
163
+ }
164
+ };
165
+ let queue = Promise.resolve();
166
+ const update = (change) => {
167
+ const run = async () => {
168
+ const current = sandboxScopeGuard();
169
+ const raw = await readRaw();
170
+ const parsed = raw === undefined ? undefined : options.parse(raw);
171
+ if (raw !== undefined && parsed === undefined) {
172
+ throw new Error(`${path} holds what this version of the extension cannot read; it is left as it is`);
173
+ }
174
+ const before = parsed ?? options.fallback();
175
+ const after = change(before);
176
+ if (after === before) {
177
+ return true;
178
+ }
179
+ if (!current()) {
180
+ return false;
181
+ }
182
+ const written = raw === undefined ? after : carryUnknown(raw, parsed, after);
183
+ await host().workspace.write(path, `${JSON.stringify(written, undefined, 2)}\n`);
184
+ return true;
185
+ };
186
+ const next = queue.then(run, run);
187
+ queue = next.catch(() => undefined);
188
+ return next;
189
+ };
190
+ return { read, update };
191
+ };
133
192
  //# sourceMappingURL=background.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"background.js","sourceRoot":"","sources":["../src/background.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"background.js","sourceRoot":"","sources":["../src/background.ts"],"names":[],"mappings":"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"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const extensionApiVersion = "2.18.0";
1
+ export declare const extensionApiVersion = "2.19.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AA+GA,eAAO,MAAM,mBAAmB,WAAW,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAmHA,eAAO,MAAM,mBAAmB,WAAW,CAAC"}
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const extensionApiVersion = "2.18.0";
1
+ export const extensionApiVersion = "2.19.0";
2
2
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AA+GA,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAmHA,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentic/extension-api",
3
- "version": "1.310.0",
3
+ "version": "1.312.0",
4
4
  "description": "The versioned public API intentic extensions compile against, manifest schema, detection facts and the host API",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -42,8 +42,8 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@intentic/sandbox-contract": "1.310.0",
46
- "@orpc/contract": "1.14.13",
45
+ "@intentic/sandbox-contract": "1.312.0",
46
+ "@orpc/contract": "1.15.4",
47
47
  "tslib": "2.8.1"
48
48
  },
49
49
  "peerDependencies": {
@@ -51,9 +51,9 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@intentic/tsconfig": "0.0.0",
54
- "@types/node": "24.13.2",
54
+ "@types/node": "24.13.6",
55
55
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
56
- "vue": "3.5.40"
56
+ "vue": "3.5.43"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsgo",
package/src/background.ts CHANGED
@@ -1,3 +1,20 @@
1
+ import {
2
+ at,
3
+ carryUnknown,
4
+ type Conversion,
5
+ convertDocument,
6
+ drop,
7
+ dropAll,
8
+ fold,
9
+ type Granularity,
10
+ mapValue,
11
+ nested,
12
+ pinDefault,
13
+ rename,
14
+ retireEntries,
15
+ retype,
16
+ transform,
17
+ } from "@intentic/sandbox-contract/documents";
1
18
  import type { Ref } from "vue";
2
19
  import type { Disposable, IntenticApi } from "./api.js";
3
20
  import { sandboxRef, sandboxScopeGuard } from "./scope.js";
@@ -154,7 +171,8 @@ export const sandboxPoll = <T>(options: SandboxPollOptions<T>): SandboxPoll<T> =
154
171
  };
155
172
 
156
173
  // Key → mark map of what the owner has already seen, stored as JSON under `.intentic`. Missing or unparseable reads as
157
- // nothing acknowledged; a write's `false` return means only that the sandbox scope moved mid-write.
174
+ // nothing acknowledged; a write's `false` return means only that the sandbox scope moved mid-write, and a write over a
175
+ // ledger that could not be read at all (refused, unreachable) rejects rather than dropping every acknowledgement in it.
158
176
  export interface SandboxLedger {
159
177
  // Everything acknowledged so far. Absent, unparseable or not-an-object all read as nothing.
160
178
  read(): Promise<Readonly<Record<string, string>>>;
@@ -168,17 +186,34 @@ const sameEntries = (left: Readonly<Record<string, string>>, right: Readonly<Rec
168
186
  Object.keys(left).length === Object.keys(right).length && Object.entries(left).every(([key, mark]) => right[key] === mark);
169
187
 
170
188
  export const sandboxLedger = (host: () => IntenticApi, path: string): SandboxLedger => {
171
- const read = async (): Promise<Readonly<Record<string, string>>> => {
172
- const parsed = await host().workspace.readJson<Record<string, unknown>>(path);
173
- // Non-string values are dropped, not coerced: a mark is always a string.
174
- return Object.fromEntries(Object.entries(parsed ?? {}).filter((entry): entry is [string, string] => typeof entry[1] === `string`));
189
+ // Non-string values are dropped, not coerced: a mark is always a string.
190
+ const marksOf = (parsed: unknown): Readonly<Record<string, string>> =>
191
+ typeof parsed === `object` && parsed !== null && !Array.isArray(parsed)
192
+ ? Object.fromEntries(Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === `string`))
193
+ : {};
194
+
195
+ const read = async (): Promise<Readonly<Record<string, string>>> => marksOf(await host().workspace.readJson<Record<string, unknown>>(path));
196
+
197
+ // Through the contract's own read, which throws for a refused or unreachable read where `readJson` answers absent:
198
+ // a write computed from that absence would drop every acknowledgement the file really holds.
199
+ const readToWrite = async (): Promise<Readonly<Record<string, string>>> => {
200
+ const answer = await host().sandbox.rpc.workspace.file({ path });
201
+ if (!answer.present) {
202
+ return {};
203
+ }
204
+ try {
205
+ return marksOf(JSON.parse(answer.content));
206
+ } catch {
207
+ // silent-catch: an unparseable ledger holds nothing acknowledged, the contract `read` states.
208
+ return {};
209
+ }
175
210
  };
176
211
 
177
212
  // One writer for both mark and replace; the scope guard lives here, not at the call site, since a sandbox switch
178
213
  // mid-write would file the acknowledgement into the workspace the owner just left.
179
214
  const settle = async (next: (seen: Readonly<Record<string, string>>) => Readonly<Record<string, string>>): Promise<boolean> => {
180
215
  const current = sandboxScopeGuard();
181
- const seen = await read();
216
+ const seen = await readToWrite();
182
217
  const wanted = next(seen);
183
218
  // Already saying it: nothing to write, and the caller's fold stays correct.
184
219
  if (sameEntries(seen, wanted)) {
@@ -197,3 +232,79 @@ export const sandboxLedger = (host: () => IntenticApi, path: string): SandboxLed
197
232
  replace: async (entries) => settle(() => entries),
198
233
  };
199
234
  };
235
+
236
+ // The vocabulary an extension's stored file evolves by, the same the daemon's own stores use: each a guarded, pure
237
+ // rewrite of the raw JSON, a no-op on a file already past it, so a file from any earlier version of the extension reads
238
+ // as today's shape. One namespace rather than ten loose names, since `rename` and `drop` are an author's words too.
239
+ export const conversions = { at, drop, dropAll, fold, mapValue, nested, pinDefault, rename, retireEntries, retype, transform } as const;
240
+
241
+ // A file an extension keeps under the workspace (its own record, cache or settings), read through the conversions its
242
+ // shape has had, and written with everything a NEWER version of the extension put in it kept in place, so switching an
243
+ // extension back never deletes what the later version recorded. A file that exists but cannot be read is left alone:
244
+ // `update` rejects rather than writing its fallback over it.
245
+ export interface SandboxDocument<T> {
246
+ // Today's value: the file converted and parsed; the fallback when it is absent or this version cannot read it.
247
+ read(): Promise<T>;
248
+ // Read-change-write, serialized through this handle. Returning `current` unchanged writes nothing; `false` means only
249
+ // that the sandbox changed mid-write, and nothing was written.
250
+ update(change: (current: T) => T): Promise<boolean>;
251
+ }
252
+
253
+ export interface SandboxDocumentOptions<T> {
254
+ // Today's shape, or undefined for a file this version cannot read.
255
+ readonly parse: (raw: unknown) => T | undefined;
256
+ readonly fallback: () => T;
257
+ // Append-only: a conversion is never edited or removed once shipped, only followed by another.
258
+ readonly history?: readonly Conversion[];
259
+ // Where the conversions apply: the document, each entry of a top-level array, or each value of an object keyed by id.
260
+ readonly granularity?: Granularity;
261
+ }
262
+
263
+ export const sandboxDocument = <T>(host: () => IntenticApi, path: string, options: SandboxDocumentOptions<T>): SandboxDocument<T> => {
264
+ const convert = (raw: unknown): unknown => convertDocument(options.history ?? [], options.granularity ?? `object`, raw).value;
265
+ // Absent is undefined; present but unreadable throws, so no caller mistakes it for absent.
266
+ const readRaw = async (): Promise<unknown> => {
267
+ // Through the contract's own read, which throws for a refused or unreachable read where `readJson` answers absent.
268
+ const answer = await host().sandbox.rpc.workspace.file({ path });
269
+ if (!answer.present) {
270
+ return undefined;
271
+ }
272
+ return convert(JSON.parse(answer.content));
273
+ };
274
+ const read = async (): Promise<T> => {
275
+ try {
276
+ const raw = await readRaw();
277
+ return (raw === undefined ? undefined : options.parse(raw)) ?? options.fallback();
278
+ } catch {
279
+ // silent-catch: a file this version cannot read reads as the fallback, the contract `read` states.
280
+ return options.fallback();
281
+ }
282
+ };
283
+ let queue: Promise<unknown> = Promise.resolve();
284
+ const update = (change: (current: T) => T): Promise<boolean> => {
285
+ const run = async (): Promise<boolean> => {
286
+ const current = sandboxScopeGuard();
287
+ const raw = await readRaw();
288
+ const parsed = raw === undefined ? undefined : options.parse(raw);
289
+ if (raw !== undefined && parsed === undefined) {
290
+ throw new Error(`${path} holds what this version of the extension cannot read; it is left as it is`);
291
+ }
292
+ const before = parsed ?? options.fallback();
293
+ const after = change(before);
294
+ if (after === before) {
295
+ return true;
296
+ }
297
+ if (!current()) {
298
+ return false;
299
+ }
300
+ const written = raw === undefined ? after : carryUnknown(raw, parsed, after);
301
+ await host().workspace.write(path, `${JSON.stringify(written, undefined, 2)}\n`);
302
+ return true;
303
+ };
304
+ const next = queue.then(run, run);
305
+ // silent-catch: the queue only orders the next update behind this one; this one's caller still gets its rejection
306
+ queue = next.catch(() => undefined);
307
+ return next;
308
+ };
309
+ return { read, update };
310
+ };
package/src/surface.json CHANGED
@@ -1609,5 +1609,117 @@
1609
1609
  "request",
1610
1610
  "rpc"
1611
1611
  ]
1612
+ },
1613
+ "2.19.0": {
1614
+ "manifest": [
1615
+ "$schema",
1616
+ "art",
1617
+ "category",
1618
+ "contributes",
1619
+ "engines",
1620
+ "entry",
1621
+ "icon",
1622
+ "logo",
1623
+ "name",
1624
+ "permissions",
1625
+ "publisher",
1626
+ "server",
1627
+ "version"
1628
+ ],
1629
+ "contributes": [
1630
+ "agent",
1631
+ "automationTemplates",
1632
+ "bin",
1633
+ "capabilities",
1634
+ "commands",
1635
+ "documents",
1636
+ "environment",
1637
+ "files",
1638
+ "listener",
1639
+ "processes",
1640
+ "settings",
1641
+ "viewers",
1642
+ "views"
1643
+ ],
1644
+ "api": [
1645
+ "apiVersion",
1646
+ "audience",
1647
+ "chat",
1648
+ "commands",
1649
+ "documents",
1650
+ "href",
1651
+ "models",
1652
+ "navigate",
1653
+ "processes",
1654
+ "route",
1655
+ "sandbox",
1656
+ "settings",
1657
+ "terminal",
1658
+ "theme",
1659
+ "viewers",
1660
+ "views",
1661
+ "workspace"
1662
+ ],
1663
+ "listener": [
1664
+ "automation",
1665
+ "events",
1666
+ "provider"
1667
+ ],
1668
+ "sandboxApi": [
1669
+ "fetch",
1670
+ "json",
1671
+ "key",
1672
+ "origin",
1673
+ "previewAddress",
1674
+ "reachable",
1675
+ "request",
1676
+ "role",
1677
+ "rpc"
1678
+ ],
1679
+ "moduleExports": [
1680
+ "conversions",
1681
+ "extensionApiVersion",
1682
+ "flattenQuery",
1683
+ "hostSlot",
1684
+ "mergeQuery",
1685
+ "readDaemonStream",
1686
+ "resetSandboxScope",
1687
+ "sandboxDocument",
1688
+ "sandboxLedger",
1689
+ "sandboxPoll",
1690
+ "sandboxRef",
1691
+ "sandboxScopeGuard",
1692
+ "sandboxShallowRef",
1693
+ "sandboxValue",
1694
+ "satisfiesEngines"
1695
+ ],
1696
+ "workspaceApi": [
1697
+ "capabilities",
1698
+ "file",
1699
+ "fillDiff",
1700
+ "inProject",
1701
+ "onDidChange",
1702
+ "onDidChangeFiles",
1703
+ "onDidChangeProject",
1704
+ "onDidChangeRefs",
1705
+ "onDidChangeRepos",
1706
+ "openDiff",
1707
+ "project",
1708
+ "readJson",
1709
+ "repos",
1710
+ "setProject",
1711
+ "write"
1712
+ ],
1713
+ "chatApi": [
1714
+ "composeLoop",
1715
+ "composeWorkflow",
1716
+ "openAgent",
1717
+ "openSession"
1718
+ ],
1719
+ "daemonApi": [
1720
+ "json",
1721
+ "request",
1722
+ "rpc"
1723
+ ]
1612
1724
  }
1613
1725
  }
package/src/version.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // The extension API's protocol version, the value `engines.intentic` ranges are matched against at load, and
2
2
  // the value the host reports as IntenticApi.apiVersion. Bumped ONLY with the published package: additive
3
- // surface = minor, breaking = major. This package is the one deliberate exception to the repo's no-legacy rule.
3
+ // surface = minor, breaking = major; published, so its surface only grows within a major.
4
4
  //
5
5
  // 1.0.0 rather than 0.5.0, and the reason is the drift that forced that bump. While the major was 0 the caret
6
6
  // matcher treats the MINOR as breaking (engines.ts), so every addition invalidated every declared range, which
@@ -109,4 +109,8 @@
109
109
  // server.ts): the backend half was the one surface no recorded grain could see.
110
110
  // 2.18.0 adds `api.workspace.onDidChangeRepos`: the repository set moving (a clone, a scaffold, a delete). `repos()` is
111
111
  // narrowed to the open project, so a view listing every repository (the Projects dashboard) could only poll. Additive.
112
- export const extensionApiVersion = "2.18.0";
112
+ // 2.19.0 adds `sandboxDocument` and the `conversions` vocabulary: a file an extension keeps, read through the
113
+ // conversions its shape has had and written with what a newer version of the extension put in it kept in place, the
114
+ // evolution the daemon's own stores got (the daemon's store/evolution/conversions.ts). Until now an extension that changed the
115
+ // shape of its own file had two choices, a hand-written tolerant reader or a reset for everyone who updated. Additive.
116
+ export const extensionApiVersion = "2.19.0";