@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 +33 -189
- package/dist/background.d.ts +30 -0
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +63 -4
- package/dist/background.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
- package/src/background.ts +117 -6
- package/src/surface.json +112 -0
- package/src/version.ts +6 -2
package/README.md
CHANGED
|
@@ -1,196 +1,40 @@
|
|
|
1
|
-
#
|
|
1
|
+
# extension-api
|
|
2
2
|
|
|
3
|
-
The
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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)
|
|
185
|
-
- [src/
|
|
186
|
-
- [src/
|
|
187
|
-
|
|
188
|
-
- [src/
|
|
189
|
-
- [src/
|
|
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.
|
package/dist/background.d.ts
CHANGED
|
@@ -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
|
package/dist/background.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/background.js
CHANGED
|
@@ -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
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
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
|
package/dist/background.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"background.js","sourceRoot":"","sources":["../src/background.ts"],"names":[],"mappings":"
|
|
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.
|
|
1
|
+
export declare const extensionApiVersion = "2.19.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"
|
|
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.
|
|
1
|
+
export const extensionApiVersion = "2.19.0";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"
|
|
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.
|
|
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.
|
|
46
|
-
"@orpc/contract": "1.
|
|
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.
|
|
54
|
+
"@types/node": "24.13.6",
|
|
55
55
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
56
|
-
"vue": "3.5.
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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";
|