@intentic/extension-api 1.214.0 → 1.216.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 +66 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/background.d.ts +23 -0
- package/dist/background.d.ts.map +1 -0
- package/dist/background.js +57 -0
- package/dist/background.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts +3 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +3 -0
- package/dist/protocol.js.map +1 -0
- package/dist/scope.d.ts +9 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +34 -0
- package/dist/scope.js.map +1 -0
- 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 +13 -2
- package/src/api.ts +13 -3
- package/src/background.ts +169 -0
- package/src/index.ts +2 -0
- package/src/protocol.ts +18 -0
- package/src/scope.ts +121 -0
- package/src/surface.json +191 -0
- package/src/version.ts +21 -1
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":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AACrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAM7D,MAAM,WAAW,UAAU;IACvB,OAAO,IAAI,IAAI,CAAC;CACnB;AAKD,MAAM,WAAW,UAAU;IAEvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAGnC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACxD;AAQD,MAAM,WAAW,SAAS;IAGtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAKpC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAYnC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IAItE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAcD,MAAM,WAAW,SAAS,CAAC,CAAC,GAAG,OAAO;IAElC,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAID,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAGpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAMvB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/protocol.js
ADDED
|
@@ -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"}
|
package/dist/scope.d.ts
ADDED
|
@@ -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.
|
|
1
|
+
export declare const extensionApiVersion = "2.8.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":"AAuDA,eAAO,MAAM,mBAAmB,UAAU,CAAC"}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const extensionApiVersion = "2.
|
|
1
|
+
export const extensionApiVersion = "2.8.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":"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.
|
|
3
|
+
"version": "1.216.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.
|
|
47
|
+
"@intentic/sandbox-contract": "1.216.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
|
-
|
|
97
|
-
|
|
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
|
|
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";
|
package/src/protocol.ts
ADDED
|
@@ -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
|
-
|
|
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";
|