@intentic/extension-api 1.214.0 → 1.215.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
@@ -56,6 +56,12 @@ packages, which is exactly the case a per-repo `detect()` cannot express. An off
56
56
  directory rather than an affordance every directory of its kind has says so (`evidence: true`), and the tree
57
57
  keeps its icon on the row instead of revealing it on hover — the difference between a reader seeing which
58
58
  packages have a page and a reader having to go looking for one.
59
+ - **[scope.ts](src/scope.ts)** — `sandboxRef` and `sandboxScopeGuard`: how an extension keeps state that
60
+ belongs to ONE sandbox. See "Where state lives" below; this is the rule most easily got wrong, because
61
+ 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.
59
65
  - **[stream.ts](src/stream.ts)**, **[version.ts](src/version.ts)** — SSE/ndjson helpers and the host API
60
66
  version (`engines.intentic` is checked against it before activation).
61
67
 
@@ -73,6 +79,64 @@ are `sandbox-contract` schemas, parsed at the call site (`Schema.parse(await api
73
79
  in-repo, compiled-together design means a wire change is a compiler error fixed atomically, so there is no
74
80
  separate "stable data API" to promote. `facts.ts` stays the stable surface only for *detection*.
75
81
 
82
+ ## Where state lives
83
+
84
+ Three tiers, and the tier decides what happens when the user points the browser at a **different sandbox**.
85
+ Everything an extension holds is about one workspace, so a switch has to leave nothing of the last one behind.
86
+
87
+ - **Cached reads** — `useQuery` in a view, or `api.sandbox.fetch(query)` from outside one. Key them with
88
+ `api.sandbox.key(...)` and the switch is handled by construction: the key carries the active sandbox id, so
89
+ the next box is a different cache entry. Use the *same* key for a view's query and for the badge poll that
90
+ warms it, and the poll's answer becomes the view's first paint.
91
+ - **State inside a mounted component** — an ordinary `ref` in a `.vue` file. Nothing to do; it dies with the
92
+ component.
93
+ - **Module state owned by `activate()`** — the badge counts, presence maps and poll results that must survive
94
+ the view being unmounted, because a badge you only see after opening the view is pointless. Declare it with
95
+ `sandboxRef(() => initial)` and the host empties it on every switch. There is no subscription to remember
96
+ and no teardown to write; `dispose` is there for state that owns an object URL or anything else the garbage
97
+ collector will not take back.
98
+
99
+ For anything asynchronous in that third tier, take a `sandboxScopeGuard()` **before** the await and ask it
100
+ **after** — a poll issued against the last sandbox otherwise resolves a moment later and writes its answer
101
+ into the new one, which is the same wrong badge with a harder repro. It matters twice over for a call that
102
+ WRITES: acknowledging what a badge has shown, in the wrong workspace's tree, is bookkeeping no later poll
103
+ corrects.
104
+
105
+ ## Keeping a tile current while nothing is mounted
106
+
107
+ Most of that third tier exists to feed a rail badge, so `sandboxPoll` covers the whole shape and you should not
108
+ need `sandboxRef` directly for one:
109
+
110
+ ```ts
111
+ const { state: unseen, start } = sandboxPoll<readonly Finding[]>({
112
+ host, // your hostSlot's accessor — nothing is bound until activate()
113
+ everyMs: 60_000, // no default: the right interval is a claim about how fast the answer moves
114
+ initial: () => [],
115
+ read: async (api) => findings(await api.sandbox.fetch(query())),
116
+ });
117
+ ```
118
+
119
+ `start()` returns the `Disposable` to push onto `context.subscriptions`; `refresh()` reads off-cycle for the
120
+ moments that should not wait out the interval. The five rules a hand-written version has to remember — never
121
+ reject, skip an unreachable daemon, discard an answer that outlived its sandbox, keep the last good value on
122
+ failure, stop the clock on disposal — are the poll's, not yours. Pass `immediate: false` if there is nothing
123
+ worth asking until something else tells you what to ask about, and read `previous` in `read` if a round
124
+ accumulates onto what you already hold rather than replacing it.
125
+
126
+ What the tile SAYS stays yours: `badge()` is the judgement each surface exists to make, and no two of them
127
+ agree about tone or wording.
128
+
129
+ `sandboxLedger(host, path)` is the other half — the JSON file recording what the owner has already seen, as
130
+ `key → mark`, where the mark is what makes an entry stale. Compare marks (a chore's evidence digest, a story's
131
+ verdict) and the same key with new evidence is news again; ignore them and it is a plain presence ledger. It
132
+ reads a missing or mangled file as "nothing acknowledged", writes nothing when nothing moved, and holds the
133
+ scope guard across its own read-then-write so an acknowledgement cannot land in the wrong workspace's tree.
134
+
135
+ This is not advice. `sandboxScope.guard.test.ts` in the app walks each extension's UI entry through its own
136
+ imports and refuses module-level `ref`/`shallowRef`/`reactive`, any reassignable module binding, and any
137
+ repeating clock in what it reaches — because the failure it prevents was found in six extensions at once: a
138
+ rail tile reading `21` under a workspace that had two.
139
+
76
140
  ## Authoring an extension
77
141
 
78
142
  `activate(api, context)` registers contributions and returns; `deactivate` is optional. A UI extension also
@@ -86,6 +150,8 @@ The five UI extensions under [`_extensions/`](../../_extensions) are the working
86
150
  - [src/engines.ts](src/engines.ts) — how `engines.intentic` is matched against the version below, for the host
87
151
  and the daemon alike.
88
152
  - [src/route.ts](src/route.ts) — the query rules a view with internal navigation uses.
153
+ - [src/scope.ts](src/scope.ts) — module state that belongs to one sandbox, and the guard for work in flight
154
+ across a switch.
89
155
  - [src/version.ts](src/version.ts) and [src/surface.json](src/surface.json) — the protocol version, and what
90
156
  each version of it promised.
91
157
 
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Ref } from "vue";
2
+ import type { Disposable, IntenticApi } from "./api.js";
3
+ export interface SandboxPoll<T> {
4
+ readonly state: Ref<T>;
5
+ start(): Disposable;
6
+ refresh(): void;
7
+ }
8
+ export interface SandboxPollOptions<T> {
9
+ readonly host: () => IntenticApi;
10
+ readonly everyMs: number;
11
+ readonly initial: () => T;
12
+ readonly read: (api: IntenticApi, previous: T) => Promise<T>;
13
+ readonly immediate?: boolean;
14
+ readonly dispose?: (previous: T) => void;
15
+ }
16
+ export declare const sandboxPoll: <T>(options: SandboxPollOptions<T>) => SandboxPoll<T>;
17
+ export interface SandboxLedger {
18
+ read(): Promise<Readonly<Record<string, string>>>;
19
+ mark(entries: Readonly<Record<string, string>>): Promise<boolean>;
20
+ replace(entries: Readonly<Record<string, string>>): Promise<boolean>;
21
+ }
22
+ export declare const sandboxLedger: (host: () => IntenticApi, path: string) => SandboxLedger;
23
+ //# sourceMappingURL=background.d.ts.map
@@ -0,0 +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;AAyBxD,MAAM,WAAW,WAAW,CAAC,CAAC;IAI1B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAEvB,KAAK,IAAI,UAAU,CAAC;IAGpB,OAAO,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC;IAGjC,QAAQ,CAAC,IAAI,EAAE,MAAM,WAAW,CAAC;IAIjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAK1B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAI7D,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAE7B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;CAC5C;AAED,eAAO,MAAM,WAAW,GAAI,CAAC,WAAW,kBAAkB,CAAC,CAAC,CAAC,KAAG,WAAW,CAAC,CAAC,CAgC5E,CAAC;AA0BF,MAAM,WAAW,aAAa;IAE1B,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAIlD,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAKlE,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,aAgCrE,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { sandboxRef, sandboxScopeGuard } from "./scope.js";
2
+ export const sandboxPoll = (options) => {
3
+ const state = sandboxRef(options.initial, options.dispose);
4
+ const once = async () => {
5
+ try {
6
+ const api = options.host();
7
+ if (!api.sandbox.reachable()) {
8
+ return;
9
+ }
10
+ const current = sandboxScopeGuard();
11
+ const next = await options.read(api, state.value);
12
+ if (!current()) {
13
+ return;
14
+ }
15
+ state.value = next;
16
+ }
17
+ catch {
18
+ }
19
+ };
20
+ return {
21
+ state,
22
+ refresh: () => void once(),
23
+ start: () => {
24
+ if (options.immediate !== false) {
25
+ void once();
26
+ }
27
+ const timer = setInterval(() => void once(), options.everyMs);
28
+ return { dispose: () => clearInterval(timer) };
29
+ },
30
+ };
31
+ };
32
+ const sameEntries = (left, right) => Object.keys(left).length === Object.keys(right).length && Object.entries(left).every(([key, mark]) => right[key] === mark);
33
+ export const sandboxLedger = (host, path) => {
34
+ const read = async () => {
35
+ const parsed = await host().workspace.readJson(path);
36
+ return Object.fromEntries(Object.entries(parsed ?? {}).filter((entry) => typeof entry[1] === `string`));
37
+ };
38
+ const settle = async (next) => {
39
+ const current = sandboxScopeGuard();
40
+ const seen = await read();
41
+ const wanted = next(seen);
42
+ if (sameEntries(seen, wanted)) {
43
+ return true;
44
+ }
45
+ if (!current()) {
46
+ return false;
47
+ }
48
+ await host().workspace.write(path, `${JSON.stringify(wanted, undefined, 2)}\n`);
49
+ return true;
50
+ };
51
+ return {
52
+ read,
53
+ mark: async (entries) => settle((seen) => ({ ...seen, ...entries })),
54
+ replace: async (entries) => settle(() => entries),
55
+ };
56
+ };
57
+ //# sourceMappingURL=background.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.js","sourceRoot":"","sources":["../src/background.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export * from "./api.js";
2
+ export * from "./background.js";
2
3
  export * from "./diff.js";
3
4
  export * from "./engines.js";
4
5
  export * from "./facts.js";
5
6
  export * from "./host.js";
6
7
  export * from "./route.js";
8
+ export * from "./scope.js";
7
9
  export * from "./server.js";
8
10
  export * from "./stream.js";
9
11
  export * from "./version.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  export * from "./api.js";
2
+ export * from "./background.js";
2
3
  export * from "./diff.js";
3
4
  export * from "./engines.js";
4
5
  export * from "./facts.js";
5
6
  export * from "./host.js";
6
7
  export * from "./route.js";
8
+ export * from "./scope.js";
7
9
  export * from "./server.js";
8
10
  export * from "./stream.js";
9
11
  export * from "./version.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { satisfiesEngines } from "./engines.js";
2
+ export { extensionApiVersion } from "./version.js";
3
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { satisfiesEngines } from "./engines.js";
2
+ export { extensionApiVersion } from "./version.js";
3
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { type Ref } from "vue";
2
+ export declare const sandboxRef: <T>(initial: () => T, dispose?: (previous: T) => void) => Ref<T>;
3
+ export interface SandboxValue<T> {
4
+ value: T;
5
+ }
6
+ export declare const sandboxValue: <T>(initial: () => T, dispose?: (previous: T) => void) => SandboxValue<T>;
7
+ export declare const sandboxScopeGuard: () => (() => boolean);
8
+ export declare const resetSandboxScope: () => void;
9
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AA+CpC,eAAO,MAAM,UAAU,GAAI,CAAC,WAAW,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,KAAG,GAAG,CAAC,CAAC,CAStF,CAAC;AAIF,MAAM,WAAW,YAAY,CAAC,CAAC;IAC3B,KAAK,EAAE,CAAC,CAAC;CACZ;AAoBD,eAAO,MAAM,YAAY,GAAI,CAAC,WAAW,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,KAAG,YAAY,CAAC,CAAC,CASjG,CAAC;AAiBF,eAAO,MAAM,iBAAiB,QAAO,CAAC,MAAM,OAAO,CAGlD,CAAC;AAIF,eAAO,MAAM,iBAAiB,QAAO,IAKpC,CAAC"}
package/dist/scope.js ADDED
@@ -0,0 +1,34 @@
1
+ import { ref } from "vue";
2
+ const registered = [];
3
+ let generation = 0;
4
+ export const sandboxRef = (initial, dispose) => {
5
+ const state = ref(initial());
6
+ registered.push({
7
+ clear: () => {
8
+ dispose?.(state.value);
9
+ state.value = initial();
10
+ },
11
+ });
12
+ return state;
13
+ };
14
+ export const sandboxValue = (initial, dispose) => {
15
+ const box = { value: initial() };
16
+ registered.push({
17
+ clear: () => {
18
+ dispose?.(box.value);
19
+ box.value = initial();
20
+ },
21
+ });
22
+ return box;
23
+ };
24
+ export const sandboxScopeGuard = () => {
25
+ const taken = generation;
26
+ return () => taken === generation;
27
+ };
28
+ export const resetSandboxScope = () => {
29
+ generation += 1;
30
+ for (const entry of registered) {
31
+ entry.clear();
32
+ }
33
+ };
34
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAY,MAAM,KAAK,CAAC;AA6BpC,MAAM,UAAU,GAAiB,EAAE,CAAC;AAKpC,IAAI,UAAU,GAAG,CAAC,CAAC;AAanB,MAAM,CAAC,MAAM,UAAU,GAAG,CAAI,OAAgB,EAAE,OAA+B,EAAU,EAAE;IACvF,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,CAAW,CAAC;IACvC,UAAU,CAAC,IAAI,CAAC;QACZ,KAAK,EAAE,GAAG,EAAE;YACR,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACvB,KAAK,CAAC,KAAK,GAAG,OAAO,EAAE,CAAC;QAC5B,CAAC;KACJ,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACjB,CAAC,CAAC;AA0BF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAI,OAAgB,EAAE,OAA+B,EAAmB,EAAE;IAClG,MAAM,GAAG,GAAoB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;IAClD,UAAU,CAAC,IAAI,CAAC;QACZ,KAAK,EAAE,GAAG,EAAE;YACR,OAAO,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACrB,GAAG,CAAC,KAAK,GAAG,OAAO,EAAE,CAAC;QAC1B,CAAC;KACJ,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACf,CAAC,CAAC;AAiBF,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAoB,EAAE;IACnD,MAAM,KAAK,GAAG,UAAU,CAAC;IACzB,OAAO,GAAG,EAAE,CAAC,KAAK,KAAK,UAAU,CAAC;AACtC,CAAC,CAAC;AAIF,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAS,EAAE;IACxC,UAAU,IAAI,CAAC,CAAC;IAChB,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC7B,KAAK,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;AACL,CAAC,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const extensionApiVersion = "2.5.0";
1
+ export declare const extensionApiVersion = "2.8.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAmCA,eAAO,MAAM,mBAAmB,UAAU,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAuDA,eAAO,MAAM,mBAAmB,UAAU,CAAC"}
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const extensionApiVersion = "2.5.0";
1
+ export const extensionApiVersion = "2.8.0";
2
2
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAmCA,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAuDA,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentic/extension-api",
3
- "version": "1.214.0",
3
+ "version": "1.215.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",
@@ -28,12 +28,23 @@
28
28
  "@intentic/src": "./src/index.ts",
29
29
  "default": "./dist/index.js"
30
30
  }
31
+ },
32
+ "./protocol": {
33
+ "types": "./dist/protocol.d.ts",
34
+ "import": {
35
+ "@intentic/src": "./src/protocol.ts",
36
+ "default": "./dist/protocol.js"
37
+ },
38
+ "require": {
39
+ "@intentic/src": "./src/protocol.ts",
40
+ "default": "./dist/protocol.js"
41
+ }
31
42
  }
32
43
  },
33
44
  "dependencies": {
34
45
  "@orpc/contract": "1.14.13",
35
46
  "tslib": "2.8.1",
36
- "@intentic/sandbox-contract": "1.214.0"
47
+ "@intentic/sandbox-contract": "1.215.0"
37
48
  },
38
49
  "peerDependencies": {
39
50
  "vue": "3"
package/src/api.ts CHANGED
@@ -93,13 +93,23 @@ export interface ViewRegistration {
93
93
  // logs, its status, its consumption) — inspected occasionally rather than worked in, so it costs a tab in
94
94
  // a scrolling word-labelled strip instead of an icon in the rail's fixed budget.
95
95
  readonly surface: "rail" | "directory" | "sandbox";
96
- // Evidence-based detection over the public facts — one activation per sidebar element. Called on every
97
- // facts poll; a throwing detect contributes nothing that round.
96
+ /* Evidence-based detection over the public facts — one activation per sidebar element. Called on every
97
+ * facts poll; a throwing detect contributes nothing that round.
98
+ *
99
+ * MUST NOT WRITE REACTIVE STATE. This runs inside the host's render computed, so a `sandboxRef` (or any
100
+ * other `Ref`) written here is a computed mutating its own dependency: Vue re-runs it, it writes again,
101
+ * and the rail recurses until the frame is abandoned — taking every unrelated update queued behind it, so
102
+ * the symptom is a window that stops responding rather than one misbehaving tile.
103
+ *
104
+ * It IS the right place to notice things, though — it is the only callback that sees the live facts — so
105
+ * for the bookkeeping that notice produces (which connections a poller should ask about next round) use
106
+ * `sandboxValue`, which has the same lifetime and no observers. Same rule for `badge` below. */
98
107
  readonly detect: (repos: readonly RepoFacts[], capabilities: readonly CapabilityFacts[]) => Activation[];
99
108
  // What this activation's tile should say without being opened. Read inside the host's own computed, so
100
109
  // reading a ref here re-renders the tile when it changes — no push channel needed. Called on every render
101
110
  // of every surface that draws tiles, so it must be cheap and pure: derive from state the extension already
102
- // keeps, never fetch. A throwing badge simply yields none.
111
+ // keeps, never fetch, and never write a ref (see detect above — same computed, same recursion). A throwing
112
+ // badge simply yields none.
103
113
  //
104
114
  // Requires `badge: true` on the manifest's matching contributes.views entry — the host drops the function
105
115
  // otherwise, because a tile that can interrupt the user is a contribution the owner must have approved.
@@ -0,0 +1,169 @@
1
+ import type { Ref } from "vue";
2
+ import type { Disposable, IntenticApi } from "./api.js";
3
+ import { sandboxRef, sandboxScopeGuard } from "./scope.js";
4
+
5
+ /* WHAT AN EXTENSION DOES WHILE NONE OF IT IS ON SCREEN — the two pieces every surface that badges a rail tile
6
+ * turned out to need, and had been writing out by hand.
7
+ *
8
+ * A tile has to be able to say something before it is opened. That rules out the view's own query, which stops
9
+ * when the component unmounts, and it rules out the file-change push, which only reaches a query something is
10
+ * observing. So the state lives at module scope (scope.ts) and something refreshes it on a timer.
11
+ *
12
+ * Seven modules across six extensions arrived at the identical shape for that timer, and it carries five rules
13
+ * that are each invisible until they are broken:
14
+ *
15
+ * never reject it runs detached, so a throw is an unhandled rejection with no caller to report to — and
16
+ * that includes reading the host handle, which throws before activate() has bound one
17
+ * skip when down an unreachable daemon is not news; asking it is a failed request per tick, forever
18
+ * guard the await a read issued before a sandbox switch must not write its answer into the box after it
19
+ * keep the last a transient failure is not evidence that nothing is waiting, so a failed read changes
20
+ * nothing rather than blanking the tile
21
+ * stop the clock the interval is disposed with the extension, or a switched-off extension keeps polling
22
+ *
23
+ * Six of the seven copies got the third one wrong, which is what this pair exists to make impossible. What is
24
+ * NOT here is what each tile SAYS: the count, the tone and the wording are the whole point of each surface and
25
+ * differ deliberately, so `badge()` stays with the extension that owns the judgement. */
26
+
27
+ export interface SandboxPoll<T> {
28
+ /* The value, as sandbox-scoped module state (scope.ts). Read it from `badge()` or `detect()` and the host's
29
+ * own computed repaints when it moves; write to it directly for the local fold a "mark as seen" does, which
30
+ * is what clears a tile on the spot instead of at the next tick. */
31
+ readonly state: Ref<T>;
32
+ // Begin polling. Push the Disposable onto `context.subscriptions` and the clock stops with the extension.
33
+ start(): Disposable;
34
+ // Read now, off-cycle — for the moments that change the answer and should not wait out the interval: a
35
+ // connection appearing, a draft published, a run discarded.
36
+ refresh(): void;
37
+ }
38
+
39
+ export interface SandboxPollOptions<T> {
40
+ // The extension's own host handle (hostSlot). A function, not the api itself, because this is constructed
41
+ // at module load and nothing is bound until activate() runs.
42
+ readonly host: () => IntenticApi;
43
+ /* How often, in milliseconds. There is no default on purpose: the right interval is a claim about how fast
44
+ * the answer actually changes, and a surface that has not thought about it will inherit whatever number
45
+ * happened to be chosen here. A badge is glanced at, so the honest range is minutes, not seconds. */
46
+ readonly everyMs: number;
47
+ // The value before anything has been read, rebuilt on every sandbox switch (sandboxRef).
48
+ readonly initial: () => T;
49
+ /* The read. Gets the api and the value currently held — the second for a poll that ACCUMULATES rather than
50
+ * replaces, where one failed source must leave its own last answer standing beside the others. Throwing is
51
+ * fine and means "nothing changed": the value in hand is kept.
52
+ */
53
+ readonly read: (api: IntenticApi, previous: T) => Promise<T>;
54
+ /* Whether `start()` reads immediately as well as on the interval. Default true, because a tile that only
55
+ * badges a minute after login is a tile nobody trusts. Set false when the poll has nothing to ask until
56
+ * something else tells it what to ask about — deployments learns its connections from `detect()`. */
57
+ readonly immediate?: boolean;
58
+ // For a value that owns something the garbage collector will not take back; see sandboxRef.
59
+ readonly dispose?: (previous: T) => void;
60
+ }
61
+
62
+ export const sandboxPoll = <T>(options: SandboxPollOptions<T>): SandboxPoll<T> => {
63
+ const state = sandboxRef(options.initial, options.dispose);
64
+
65
+ const once = async (): Promise<void> => {
66
+ try {
67
+ const api = options.host();
68
+ if (!api.sandbox.reachable()) {
69
+ return;
70
+ }
71
+ const current = sandboxScopeGuard();
72
+ const next = await options.read(api, state.value);
73
+ if (!current()) {
74
+ return;
75
+ }
76
+ state.value = next;
77
+ } catch {
78
+ // Whatever went wrong — an unbound host, a refused route, a daemon mid-boot — the answer is the
79
+ // same: leave the last value standing. "We could not ask" is not "there is nothing there".
80
+ }
81
+ };
82
+
83
+ return {
84
+ state,
85
+ refresh: () => void once(),
86
+ start: () => {
87
+ if (options.immediate !== false) {
88
+ void once();
89
+ }
90
+ const timer = setInterval(() => void once(), options.everyMs);
91
+ return { dispose: () => clearInterval(timer) };
92
+ },
93
+ };
94
+ };
95
+
96
+ /* WHAT THE OWNER HAS ALREADY SEEN, as a file in the workspace.
97
+ *
98
+ * The rail's bar is that a badge means "something happened here that you do not already know about". Meeting it
99
+ * needs somewhere to record what they DO know, and three extensions independently chose the same home: a JSON
100
+ * object under `.intentic`, keyed by whatever identifies the thing. That is the right home — it survives a
101
+ * reload, it is shared across the owner's browsers, and it needs no setting nobody would ever type — but each
102
+ * of them then hand-wrote the same tolerant reader and the same careful write.
103
+ *
104
+ * KEY → MARK, where the mark is what makes the entry STALE. That is the whole vocabulary, and it covers both
105
+ * the ledgers that compare (a chore's evidence digest, a story's verdict — the same key with a different mark
106
+ * is news again) and the ones that only ask whether a key is present at all (a document set reviewed once).
107
+ * A presence-only ledger writes the acknowledgement time as its mark, which nothing reads and a human opening
108
+ * the file is glad of.
109
+ *
110
+ * The file is written by agents and editable by hand, so a missing, truncated or hand-mangled one reads as
111
+ * "nothing acknowledged". That direction is deliberate: bad bookkeeping may light a badge that should have been
112
+ * quiet, and must never hide one that should have been lit.
113
+ *
114
+ * BOTH WRITES ANSWER "did this take effect", which is the question the caller's NEXT line depends on. Marking
115
+ * something seen is almost always followed by folding it out of the badge locally, so the tile clears on the
116
+ * spot rather than at the next poll — and that fold is a write into sandbox-scoped state, so it must not happen
117
+ * when the acknowledgement itself was abandoned because the owner switched sandbox mid-operation. It would
118
+ * silence the NEW box's badge for a fact about the old one. `false` means only that: the scope moved. A ledger
119
+ * that already said what you asked it to say answers `true`, because it does. */
120
+ export interface SandboxLedger {
121
+ // Everything acknowledged so far. Absent, unparseable or not-an-object all read as nothing.
122
+ read(): Promise<Readonly<Record<string, string>>>;
123
+ /* Record these, leaving every other entry alone — the ordinary acknowledgement. No write happens when
124
+ * nothing moved: the file push would otherwise cost every connected browser a refetch for a file whose
125
+ * content is identical. */
126
+ mark(entries: Readonly<Record<string, string>>): Promise<boolean>;
127
+ /* Make these the WHOLE ledger, dropping anything not named. For a ledger whose keys go out of scope — a
128
+ * run that has scrolled past the scan window can never be seen again, and merging forever would grow the
129
+ * file without bound. Same no-op-when-unchanged rule as `mark`.
130
+ */
131
+ replace(entries: Readonly<Record<string, string>>): Promise<boolean>;
132
+ }
133
+
134
+ const sameEntries = (left: Readonly<Record<string, string>>, right: Readonly<Record<string, string>>): boolean =>
135
+ Object.keys(left).length === Object.keys(right).length && Object.entries(left).every(([key, mark]) => right[key] === mark);
136
+
137
+ export const sandboxLedger = (host: () => IntenticApi, path: string): SandboxLedger => {
138
+ const read = async (): Promise<Readonly<Record<string, string>>> => {
139
+ const parsed = await host().workspace.readJson<Record<string, unknown>>(path);
140
+ // Non-string values are dropped rather than coerced: a mark is a string, and anything else is somebody
141
+ // else's idea of what this file is for.
142
+ return Object.fromEntries(Object.entries(parsed ?? {}).filter((entry): entry is [string, string] => typeof entry[1] === `string`));
143
+ };
144
+
145
+ /* One writer for both verbs, and the scope guard lives HERE rather than at the call site — this is the
146
+ * only thing in an extension's background work that damages state on DISK when a sandbox switch lands
147
+ * mid-operation. Reading one workspace's acknowledgements and writing them into the tree of the workspace
148
+ * the owner has just moved to is bookkeeping filed in the wrong place, which no later poll corrects. */
149
+ const settle = async (next: (seen: Readonly<Record<string, string>>) => Readonly<Record<string, string>>): Promise<boolean> => {
150
+ const current = sandboxScopeGuard();
151
+ const seen = await read();
152
+ const wanted = next(seen);
153
+ // Already saying it. Nothing to write, and the caller's local fold is still right.
154
+ if (sameEntries(seen, wanted)) {
155
+ return true;
156
+ }
157
+ if (!current()) {
158
+ return false;
159
+ }
160
+ await host().workspace.write(path, `${JSON.stringify(wanted, undefined, 2)}\n`);
161
+ return true;
162
+ };
163
+
164
+ return {
165
+ read,
166
+ mark: async (entries) => settle((seen) => ({ ...seen, ...entries })),
167
+ replace: async (entries) => settle(() => entries),
168
+ };
169
+ };
package/src/index.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export * from "./api.js";
2
+ export * from "./background.js";
2
3
  export * from "./diff.js";
3
4
  export * from "./engines.js";
4
5
  export * from "./facts.js";
5
6
  export * from "./host.js";
6
7
  export * from "./route.js";
8
+ export * from "./scope.js";
7
9
  export * from "./server.js";
8
10
  export * from "./stream.js";
9
11
  export * from "./version.js";
@@ -0,0 +1,18 @@
1
+ /* THE HALF OF THIS PACKAGE THAT RUNS WITHOUT A BROWSER — the protocol version and the matcher that compares a
2
+ * manifest's `engines.intentic` against it.
3
+ *
4
+ * The root barrel is the EXTENSION-facing surface, and an extension runs in the app, so the barrel is free to
5
+ * reach for vue: `scope.ts` imports `ref` as a value, not a type. The daemon is the other consumer, and it
6
+ * needs exactly these two names — to refuse an incompatible extension (backend-supervisor), to stamp the
7
+ * version it provides (backend-host-main), to write an engines range into a scaffold. It has no vue and no
8
+ * reason to grow one, but `export *` loads every re-exported module eagerly, so importing the barrel for
9
+ * `extensionApiVersion` alone dragged `vue` into a Node process that could not resolve it — the daemon crashed
10
+ * on boot and the sandbox never became healthy.
11
+ *
12
+ * Hence this entry point. `@intentic/extension-api/protocol` is what host-side Node code imports; the root
13
+ * barrel keeps exporting both names too, so nothing about the published extension surface changes. Type-only
14
+ * imports from the root (`ExtensionServerApi` and friends) stay where they are — those are erased at compile
15
+ * time and never reach the loader. */
16
+
17
+ export { satisfiesEngines } from "./engines.js";
18
+ export { extensionApiVersion } from "./version.js";
package/src/scope.ts ADDED
@@ -0,0 +1,121 @@
1
+ import { ref, type Ref } from "vue";
2
+
3
+ /* STATE THAT BELONGS TO ONE SANDBOX, and the reason an extension cannot be left to remember that itself.
4
+ *
5
+ * Three tiers of client state exist in this app, and each needs a different answer to "what happens on a
6
+ * switch". Cached server state is keyed by `api.sandbox.key(...)`, so it is answered by construction. State
7
+ * inside a mounted component dies with the component. THIS is the third tier: module state owned by
8
+ * `activate()` — the badge counts, the document-presence maps, the poll results that must survive the view
9
+ * being unmounted, because a badge you only see once you have already navigated to the view is pointless.
10
+ *
11
+ * Nothing owned it. A rail tile filled by a ten-minute timer therefore kept the PREVIOUS sandbox's number for
12
+ * up to ten minutes after a switch, under the new sandbox's name — a badge is a claim addressed to the reader,
13
+ * and one describing a workspace they are no longer looking at is worse than no badge at all. It was not one
14
+ * extension's mistake either: every extension that badges had the identical shape, which is the signature of a
15
+ * missing primitive rather than of carelessness.
16
+ *
17
+ * So the host owns it. Declare the state through `sandboxRef` and the host empties it on every switch; there
18
+ * is no subscription to remember and no teardown to write.
19
+ *
20
+ * A MODULE-LEVEL REGISTRY IS CORRECT HERE, and that is worth saying because `hostSlot` in this same package
21
+ * warns against exactly that. The shell publishes ONE instance of this module to every bundle
22
+ * (extension-host/hostModules.ts), so a slot held here is shared by all of them — which made it wrong for a
23
+ * per-extension host handle and makes it right for this: one switch empties every extension's scope, and no
24
+ * extension can be missed. */
25
+
26
+ interface Registered {
27
+ readonly clear: () => void;
28
+ }
29
+
30
+ const registered: Registered[] = [];
31
+
32
+ /* Which scope the extensions are in, counted rather than named — this module cannot see the sandbox id, and
33
+ * does not need to. All any caller asks is "is this still the scope I started in", and a counter answers that
34
+ * without this package having to know what a sandbox is. */
35
+ let generation = 0;
36
+
37
+ /* MODULE STATE FOR ONE SANDBOX. `initial` is a factory, not a value, so each scope starts from a fresh object
38
+ * rather than sharing (and slowly mutating) one literal written at import time.
39
+ *
40
+ * const unseen = sandboxRef<readonly ChoreVerdict[]>(() => []);
41
+ *
42
+ * It is an ordinary `Ref` in every other respect: read it in a `badge()` or a `detect()` and the host's own
43
+ * computed re-renders the tile when it changes, exactly as before. READ, not write — see `sandboxValue`.
44
+ *
45
+ * `dispose` is for state that owns something the garbage collector will not take back — an object URL, a
46
+ * subscription. It is handed the value being dropped, once, at the moment the scope closes. Most callers need
47
+ * none: a list of verdicts is released by being replaced. */
48
+ export const sandboxRef = <T>(initial: () => T, dispose?: (previous: T) => void): Ref<T> => {
49
+ const state = ref(initial()) as Ref<T>;
50
+ registered.push({
51
+ clear: () => {
52
+ dispose?.(state.value);
53
+ state.value = initial();
54
+ },
55
+ });
56
+ return state;
57
+ };
58
+
59
+ // A sandbox-scoped box that nothing observes. Same shape as a `Ref` on purpose — `.value`, read and written —
60
+ // so moving state between the two is one word at the declaration and nothing at the call sites.
61
+ export interface SandboxValue<T> {
62
+ value: T;
63
+ }
64
+
65
+ /* MODULE STATE FOR ONE SANDBOX THAT NOTHING RENDERS — `sandboxRef`'s lifetime without its reactivity, for the
66
+ * bookkeeping a background poll keeps for ITSELF: which connections to ask about next round, the cursor a
67
+ * fetch resumes from, the id a retry belongs to.
68
+ *
69
+ * It exists because of where `detect()` and `badge()` are called from. Both run INSIDE the host's render
70
+ * computed — that is the whole mechanism by which a tile repaints when a poll lands — and a `Ref` WRITTEN from
71
+ * inside a computed is that computed mutating its own dependency. Vue re-runs it, the write happens again, and
72
+ * the rail recurses until Vue abandons the flush mid-frame. What the reader sees then is not one broken tile:
73
+ * every update queued behind the rail is dropped with the flush, so the whole window stops answering, and the
74
+ * console fills with a recursion error naming a component that is merely where the loop was noticed.
75
+ *
76
+ * So the division is by AUDIENCE, not by lifetime: `sandboxRef` for what a tile SHOWS, `sandboxValue` for what
77
+ * a poll REMEMBERS. Both are emptied on a switch by the same door, and writing this one from a render callback
78
+ * is safe precisely because there is nothing to invalidate. When a poll's own bookkeeping later turns out to
79
+ * be worth showing, promoting it is a one-word change — and the promotion is the moment to check that nothing
80
+ * writes it from `detect()`.
81
+ *
82
+ * `dispose` behaves exactly as it does on `sandboxRef`. */
83
+ export const sandboxValue = <T>(initial: () => T, dispose?: (previous: T) => void): SandboxValue<T> => {
84
+ const box: SandboxValue<T> = { value: initial() };
85
+ registered.push({
86
+ clear: () => {
87
+ dispose?.(box.value);
88
+ box.value = initial();
89
+ },
90
+ });
91
+ return box;
92
+ };
93
+
94
+ /* THE GUARD FOR WORK THAT WAS ALREADY IN FLIGHT WHEN THE SWITCH HAPPENED.
95
+ *
96
+ * Emptying the refs is not enough on its own. A poll that issued its request under the old sandbox resolves a
97
+ * moment after the switch, and writes the old box's answer into the fresh scope — the same wrong badge, just
98
+ * harder to reproduce. There is no way for this module to cancel that request, so it offers the one thing the
99
+ * caller needs instead: a way to ask, after the await, whether the answer is still wanted.
100
+ *
101
+ * Take it BEFORE the await, ask it AFTER:
102
+ *
103
+ * const current = sandboxScopeGuard();
104
+ * const report = await api.sandbox.fetch(query);
105
+ * if (!current()) return;
106
+ * unseen.value = assess(report);
107
+ *
108
+ * Two lines, and the failure it prevents is invisible without them. */
109
+ export const sandboxScopeGuard = (): (() => boolean) => {
110
+ const taken = generation;
111
+ return () => taken === generation;
112
+ };
113
+
114
+ /* THE HOST'S DOOR, called by the shell when the active sandbox changes — not by extensions, which have nothing
115
+ * to reset and no business resetting each other's. */
116
+ export const resetSandboxScope = (): void => {
117
+ generation += 1;
118
+ for (const entry of registered) {
119
+ entry.clear();
120
+ }
121
+ };
package/src/surface.json CHANGED
@@ -276,5 +276,196 @@
276
276
  ],
277
277
  "listener": ["automation", "events", "provider"],
278
278
  "sandboxApi": ["fetch", "json", "key", "origin", "reachable", "request", "role", "rpc"]
279
+ },
280
+ "2.6.0": {
281
+ "manifest": [
282
+ "$schema",
283
+ "art",
284
+ "category",
285
+ "contributes",
286
+ "engines",
287
+ "entry",
288
+ "icon",
289
+ "logo",
290
+ "name",
291
+ "permissions",
292
+ "publisher",
293
+ "server",
294
+ "version"
295
+ ],
296
+ "contributes": [
297
+ "agent",
298
+ "automationTemplates",
299
+ "bin",
300
+ "capabilities",
301
+ "commands",
302
+ "documents",
303
+ "environment",
304
+ "files",
305
+ "listener",
306
+ "processes",
307
+ "settings",
308
+ "viewers",
309
+ "views"
310
+ ],
311
+ "api": [
312
+ "apiVersion",
313
+ "chat",
314
+ "commands",
315
+ "documents",
316
+ "models",
317
+ "navigate",
318
+ "processes",
319
+ "route",
320
+ "sandbox",
321
+ "settings",
322
+ "terminal",
323
+ "theme",
324
+ "viewers",
325
+ "views",
326
+ "workspace"
327
+ ],
328
+ "listener": ["automation", "events", "provider"],
329
+ "sandboxApi": ["fetch", "json", "key", "origin", "reachable", "request", "role", "rpc"],
330
+ "moduleExports": [
331
+ "extensionApiVersion",
332
+ "flattenQuery",
333
+ "hostSlot",
334
+ "mergeQuery",
335
+ "readDaemonStream",
336
+ "resetSandboxScope",
337
+ "sandboxRef",
338
+ "sandboxScopeGuard",
339
+ "satisfiesEngines"
340
+ ]
341
+ },
342
+ "2.7.0": {
343
+ "manifest": [
344
+ "$schema",
345
+ "art",
346
+ "category",
347
+ "contributes",
348
+ "engines",
349
+ "entry",
350
+ "icon",
351
+ "logo",
352
+ "name",
353
+ "permissions",
354
+ "publisher",
355
+ "server",
356
+ "version"
357
+ ],
358
+ "contributes": [
359
+ "agent",
360
+ "automationTemplates",
361
+ "bin",
362
+ "capabilities",
363
+ "commands",
364
+ "documents",
365
+ "environment",
366
+ "files",
367
+ "listener",
368
+ "processes",
369
+ "settings",
370
+ "viewers",
371
+ "views"
372
+ ],
373
+ "api": [
374
+ "apiVersion",
375
+ "chat",
376
+ "commands",
377
+ "documents",
378
+ "models",
379
+ "navigate",
380
+ "processes",
381
+ "route",
382
+ "sandbox",
383
+ "settings",
384
+ "terminal",
385
+ "theme",
386
+ "viewers",
387
+ "views",
388
+ "workspace"
389
+ ],
390
+ "listener": ["automation", "events", "provider"],
391
+ "sandboxApi": ["fetch", "json", "key", "origin", "reachable", "request", "role", "rpc"],
392
+ "moduleExports": [
393
+ "extensionApiVersion",
394
+ "flattenQuery",
395
+ "hostSlot",
396
+ "mergeQuery",
397
+ "readDaemonStream",
398
+ "resetSandboxScope",
399
+ "sandboxLedger",
400
+ "sandboxPoll",
401
+ "sandboxRef",
402
+ "sandboxScopeGuard",
403
+ "satisfiesEngines"
404
+ ]
405
+ },
406
+ "2.8.0": {
407
+ "manifest": [
408
+ "$schema",
409
+ "art",
410
+ "category",
411
+ "contributes",
412
+ "engines",
413
+ "entry",
414
+ "icon",
415
+ "logo",
416
+ "name",
417
+ "permissions",
418
+ "publisher",
419
+ "server",
420
+ "version"
421
+ ],
422
+ "contributes": [
423
+ "agent",
424
+ "automationTemplates",
425
+ "bin",
426
+ "capabilities",
427
+ "commands",
428
+ "documents",
429
+ "environment",
430
+ "files",
431
+ "listener",
432
+ "processes",
433
+ "settings",
434
+ "viewers",
435
+ "views"
436
+ ],
437
+ "api": [
438
+ "apiVersion",
439
+ "chat",
440
+ "commands",
441
+ "documents",
442
+ "models",
443
+ "navigate",
444
+ "processes",
445
+ "route",
446
+ "sandbox",
447
+ "settings",
448
+ "terminal",
449
+ "theme",
450
+ "viewers",
451
+ "views",
452
+ "workspace"
453
+ ],
454
+ "listener": ["automation", "events", "provider"],
455
+ "sandboxApi": ["fetch", "json", "key", "origin", "reachable", "request", "role", "rpc"],
456
+ "moduleExports": [
457
+ "extensionApiVersion",
458
+ "flattenQuery",
459
+ "hostSlot",
460
+ "mergeQuery",
461
+ "readDaemonStream",
462
+ "resetSandboxScope",
463
+ "sandboxLedger",
464
+ "sandboxPoll",
465
+ "sandboxRef",
466
+ "sandboxScopeGuard",
467
+ "sandboxValue",
468
+ "satisfiesEngines"
469
+ ]
279
470
  }
280
471
  }
package/src/version.ts CHANGED
@@ -33,4 +33,24 @@
33
33
  // rather than a URL because the row is drawn before any code is cloned: a link would put a stranger's server in
34
34
  // the render path, track who is browsing, and rot after approval. Additive — a manifest that ships no drawing
35
35
  // falls to exactly the mark it had before.
36
- export const extensionApiVersion = "2.5.0";
36
+ // 2.6.0 gives module state an owner across a sandbox switch: `sandboxRef` declares state that belongs to ONE
37
+ // sandbox and `sandboxScopeGuard` protects the write of a read that was already in flight when the switch
38
+ // happened (scope.ts). The tier existed and nothing cleared it — every extension that badges a rail tile from a
39
+ // timer kept the previous sandbox's count under the new sandbox's name, which is the one thing a badge must
40
+ // never do. Additive: an extension that keeps no module state is unchanged, and the host resets the scope
41
+ // whether or not anything registered.
42
+ // 2.7.0 adds the two halves of work an extension does while none of it is on screen (background.ts):
43
+ // `sandboxPoll`, the timer behind a rail badge, and `sandboxLedger`, the file recording what the owner has
44
+ // already seen. Seven modules across six extensions had hand-written the same poller — five invisible rules
45
+ // each, and six of the seven had the sandbox-switch one wrong — and three had hand-written the same tolerant
46
+ // reader and careful write over the same shape of file. Additive: nothing is removed, and an extension that
47
+ // keeps polling by hand still runs.
48
+ // 2.8.0 splits sandbox-scoped module state by AUDIENCE: `sandboxValue` is `sandboxRef`'s lifetime without its
49
+ // reactivity, for what a background poll remembers for itself (which connections to ask about next round, the
50
+ // cursor a fetch resumes from) rather than what a tile shows. `detect()` and `badge()` both run inside the
51
+ // host's render computed, so a `Ref` written from either is a computed mutating its own dependency — Vue
52
+ // re-runs it, it writes again, and the rail recurses until the flush is abandoned mid-frame, dropping every
53
+ // unrelated update queued behind it. The symptom is a window that stops answering, blamed on whichever
54
+ // component the loop was noticed in, so the fix has to be a box nothing observes rather than a rule to
55
+ // remember. Additive: `sandboxRef` is unchanged, and both are emptied on a switch by the same door.
56
+ export const extensionApiVersion = "2.8.0";