@intentic/extension-api 1.248.0 → 1.249.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +9 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js.map +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/facts.d.ts.map +1 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/route.d.ts.map +1 -1
- package/dist/route.js.map +1 -1
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
- package/src/api.ts +154 -384
- package/src/background.ts +25 -127
- package/src/diff.ts +16 -47
- package/src/facts.ts +9 -22
- package/src/host.ts +3 -13
- package/src/protocol.ts +3 -15
- package/src/route.ts +6 -14
- package/src/scope.ts +12 -73
- package/src/server.ts +14 -39
- package/src/stream.ts +7 -12
- package/src/surface.json +84 -0
- package/src/version.ts +6 -1
package/src/api.ts
CHANGED
|
@@ -4,247 +4,136 @@ import type { Component } from "vue";
|
|
|
4
4
|
import type { DiffPayload } from "./diff.js";
|
|
5
5
|
import type { CapabilityFacts, RepoFacts } from "./facts.js";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* onto context.subscriptions so deactivation can unwind it. */
|
|
7
|
+
// The host API an extension programs against; there is no ambient global. Arrives as
|
|
8
|
+
// activate(api, context); everything registered returns a Disposable pushed onto context.subscriptions.
|
|
10
9
|
|
|
11
10
|
export interface Disposable {
|
|
12
11
|
dispose(): void;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
// One sidebar element a view contributes
|
|
16
|
-
//
|
|
17
|
-
// `repo` (+ props) bound.
|
|
14
|
+
// One sidebar element a view contributes, routed at /ext/<viewId>/<key> and rendered by the view's
|
|
15
|
+
// component with `repo` (+ props) bound.
|
|
18
16
|
export interface Activation {
|
|
19
|
-
// Stable per-view key (usually the repo name)
|
|
17
|
+
// Stable per-view key (usually the repo name); the route segment, so deep links survive reloads.
|
|
20
18
|
readonly key: string;
|
|
21
19
|
readonly title: string;
|
|
22
|
-
// An icon name from the host's icon set; absent
|
|
20
|
+
// An icon name from the host's icon set; absent renders the title's initials.
|
|
23
21
|
readonly icon?: string | undefined;
|
|
24
|
-
//
|
|
25
|
-
// for capability-driven elements, which aren't rooted at any repo.
|
|
22
|
+
// Absent for capability-driven elements, which have no repo to root at.
|
|
26
23
|
readonly repo?: string | undefined;
|
|
27
24
|
readonly props?: Record<string, unknown> | undefined;
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
// What a sidebar
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// A badge is a claim on the user's attention, so the bar is the same one the core surfaces already hold
|
|
34
|
-
// themselves to: it must mean "something happened here that you don't already know about", never "here is a
|
|
35
|
-
// statistic". A count that is lit most of the day teaches the user to stop seeing the rail.
|
|
36
|
-
//
|
|
37
|
-
// AND IT IS WHAT PUTS THE TILE THERE. Beyond the host's few permanent areas, a rail tile holds its seat while
|
|
38
|
-
// this is non-empty (or while the reader has pinned it, or is standing in it) and is reached through the rail's
|
|
39
|
-
// More menu otherwise, which lists every area whether or not it is seated, as does the palette. So `detect()`
|
|
40
|
-
// says whether the area EXISTS, which is what makes its route, its menu rows and its command work, and this says
|
|
41
|
-
// whether it is currently worth one of the column's roughly nine seats. Badging all day now costs a seat as well
|
|
42
|
-
// as the reader's trust.
|
|
27
|
+
// What a sidebar tile may say before it's opened: a number and how alarmed to be. Non-empty also keeps
|
|
28
|
+
// the tile seated in the rail instead of the More menu.
|
|
43
29
|
export interface ViewBadge {
|
|
44
|
-
// How many
|
|
45
|
-
// different afternoons. Omitted or 0 ⇒ no number. The host renders anything above 99 as "99+".
|
|
30
|
+
// How many; omitted or 0 means no number. The host renders anything above 99 as "99+".
|
|
46
31
|
readonly count?: number | undefined;
|
|
47
|
-
// A glyph
|
|
48
|
-
// nothing about what the user does with it: one click either way. "There is committed work here waiting to
|
|
49
|
-
// be sent" is the whole message, and a number beside it would be read in the unit `count` established,
|
|
50
|
-
// so the amount goes in the tooltip and the glyph carries the kind. A badge with neither renders nothing.
|
|
32
|
+
// A glyph shown instead of a number, for a pending action whose size doesn't matter. Neither renders nothing.
|
|
51
33
|
readonly mark?: string | undefined;
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
// `neutral` is an INVENTORY: true most of the day, nothing owed, quiet ink. `info` is the resting tone for
|
|
59
|
-
// work the user can act on (unread agents, uncommitted changes); `warning` marks a risk they are carrying
|
|
60
|
-
// (an exposed port); `danger` means something is BROKEN, reach for it sparingly, its whole value is that it
|
|
61
|
-
// is rare enough to still mean something. Absent still means `info`, so a badge that says nothing about its
|
|
62
|
-
// tone is still assumed to be asking for something.
|
|
34
|
+
// neutral: an inventory, nothing owed.
|
|
35
|
+
// info: the resting tone for actionable work.
|
|
36
|
+
// warning: a risk being carried.
|
|
37
|
+
// danger: something is broken; use sparingly.
|
|
38
|
+
// Absent means info.
|
|
63
39
|
readonly tone?: "neutral" | "info" | "warning" | "danger" | undefined;
|
|
64
|
-
//
|
|
65
|
-
// AFTER the view's own name, "Agents · 3 need you" on the rail, the chip's text in the mobile menu, so
|
|
66
|
-
// phrase it as the continuation of a label, not as a standalone sentence that repeats the view.
|
|
40
|
+
// What happened and how much; rendered after the view's name, so phrase it as a continuation, not a sentence.
|
|
67
41
|
readonly tooltip?: string | undefined;
|
|
68
42
|
}
|
|
69
43
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*
|
|
73
|
-
* It is deliberately the shape a vue-query `useQuery` already takes, because that is the point: the entry a
|
|
74
|
-
* view warms, the entry its badge fills from a timer and the entry the view's own `useQuery` observes are ONE
|
|
75
|
-
* entry, and they are one entry because all three name it the same way. Two of the three used to be separate
|
|
76
|
-
* reads of the same route in this app's own extensions, the tile fetched the report every ten minutes and kept
|
|
77
|
-
* it privately, and the view it badged for started from nothing every time it was opened.
|
|
78
|
-
*
|
|
79
|
-
* The caching terms are optional and yours: `staleTime` is how long an answer stays believable without a
|
|
80
|
-
* refetch (Infinity for something only an invalidation can make wrong), `gcTime` how long it survives with
|
|
81
|
-
* nothing observing it. Both default to the host's. */
|
|
44
|
+
// One cached read, shared by `ViewRegistration.warm` and `api.sandbox.fetch`. `staleTime`/`gcTime` are
|
|
45
|
+
// optional; both default to the host's.
|
|
82
46
|
export interface HostQuery<T = unknown> {
|
|
83
|
-
//
|
|
47
|
+
// Must be scoped by api.sandbox.key(...), so nothing bleeds across a sandbox switch.
|
|
84
48
|
readonly queryKey: readonly unknown[];
|
|
85
49
|
readonly queryFn: () => Promise<T>;
|
|
86
50
|
readonly staleTime?: number | undefined;
|
|
87
51
|
readonly gcTime?: number | undefined;
|
|
88
52
|
}
|
|
89
53
|
|
|
90
|
-
// A view's runtime registration
|
|
91
|
-
//
|
|
54
|
+
// A view's runtime registration; `id`, `label` and `surface` must match a `contributes.views` entry in
|
|
55
|
+
// the approved manifest.
|
|
92
56
|
export interface ViewRegistration {
|
|
93
57
|
readonly id: string;
|
|
94
|
-
// The view family's human name
|
|
95
|
-
// panel's surface switch when a repo activates several.
|
|
58
|
+
// The view family's human name, distinct from an Activation's per-repo `title`.
|
|
96
59
|
readonly label: string;
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// logs, its status, its consumption), inspected occasionally rather than worked in, so it costs a tab in
|
|
101
|
-
// a scrolling word-labelled strip instead of an icon in the rail's fixed budget.
|
|
60
|
+
// rail: the always-visible left column, a permanently seated tile.
|
|
61
|
+
// directory: a per-repo panel opened from the Workspace tree.
|
|
62
|
+
// sandbox: a tab on the Sandbox hub, for inspecting the box itself.
|
|
102
63
|
readonly surface: "rail" | "directory" | "sandbox";
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*
|
|
106
|
-
* MUST NOT WRITE REACTIVE STATE. This runs inside the host's render computed, so a `sandboxRef` (or any
|
|
107
|
-
* other `Ref`) written here is a computed mutating its own dependency: Vue re-runs it, it writes again,
|
|
108
|
-
* and the rail recurses until the frame is abandoned, taking every unrelated update queued behind it, so
|
|
109
|
-
* the symptom is a window that stops responding rather than one misbehaving tile.
|
|
110
|
-
*
|
|
111
|
-
* It IS the right place to notice things, though, it is the only callback that sees the live facts, so
|
|
112
|
-
* for the bookkeeping that notice produces (which connections a poller should ask about next round) use
|
|
113
|
-
* `sandboxValue`, which has the same lifetime and no observers. Same rule for `badge` below. */
|
|
64
|
+
// Evidence-based detection over the public facts; a throwing detect contributes nothing that round.
|
|
65
|
+
// Must not write reactive state (runs inside the host's render computed).
|
|
114
66
|
readonly detect: (repos: readonly RepoFacts[], capabilities: readonly CapabilityFacts[]) => Activation[];
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
// of every surface that draws tiles, so it must be cheap and pure: derive from state the extension already
|
|
118
|
-
// keeps, never fetch, and never write a ref (see detect above, same computed, same recursion). A throwing
|
|
119
|
-
// badge simply yields none.
|
|
120
|
-
//
|
|
121
|
-
// Requires `badge: true` on the manifest's matching contributes.views entry, the host drops the function
|
|
122
|
-
// otherwise, because a tile that can interrupt the user is a contribution the owner must have approved.
|
|
123
|
-
// The source has to stay alive while the view is UNMOUNTED (a badge you only see once you have already
|
|
124
|
-
// navigated to the view is pointless), so it belongs in module state owned by activate(), not in the view.
|
|
67
|
+
// Read inside the host's computed, so a ref read here re-renders the tile; called on every render, so
|
|
68
|
+
// keep it cheap, pure and non-writing. Requires `badge: true` on the manifest entry.
|
|
125
69
|
readonly badge?: ((activation: Activation) => ViewBadge | undefined) | undefined;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
*
|
|
129
|
-
* A rail tile is at the far end of the loader's priority order (the user is not there, they might GO
|
|
130
|
-
* there), so this is a wish and never a guarantee: on a workspace busy enough that nothing is spare, none
|
|
131
|
-
* of it is read and the view costs exactly what it cost before. Nothing here is user-visible, nothing
|
|
132
|
-
* retries, and a failed warm is simply a warm that did not happen.
|
|
133
|
-
*
|
|
134
|
-
* DECLARE THE QUERY, not a function that fetches it, that is the whole reason this takes a HostQuery. The
|
|
135
|
-
* host's own wishes used to carry a cache key and a separate "how to read it" callback, and for most of
|
|
136
|
-
* them the callback fetched the data and returned it to its caller without ever filing it under that key.
|
|
137
|
-
* The wish could then never be satisfied, and since the loader always takes the first unsatisfied wish, one
|
|
138
|
-
* of them was enough to park it for the whole session. Handing over the query makes the two halves the same
|
|
139
|
-
* object. Use the SAME key your view's `useQuery` reads (api.sandbox.key(...)), or you are warming an entry
|
|
140
|
-
* nothing will look in.
|
|
141
|
-
*
|
|
142
|
-
* Called on every beat of the loader, so it must be cheap and pure, derive from state you already keep,
|
|
143
|
-
* never fetch. A throwing warm contributes nothing that beat. */
|
|
70
|
+
// Declare the query (not a fetcher) the loader should read ahead of need; use the same key your view's
|
|
71
|
+
// `useQuery` reads. A wish, never a guarantee; called every loader beat, so keep it cheap and pure.
|
|
144
72
|
readonly warm?: (() => readonly HostQuery[]) | undefined;
|
|
145
73
|
// A fallback view's activations are dropped for repos already claimed by a non-fallback one.
|
|
146
74
|
readonly fallback?: true | undefined;
|
|
147
|
-
// An
|
|
148
|
-
// runner, a docs browser. Its activations render and mark the directory manageable exactly like any other,
|
|
149
|
-
// but they do not claim the repo, so the fallback view (the raw dev-server preview) survives alongside.
|
|
150
|
-
// Claiming is for a view that subsumes the fallback: `apps` renders the preview URLs itself, so dropping
|
|
151
|
-
// the preview tile beside it is right; a test runner renders no preview, so dropping it would be a loss.
|
|
75
|
+
// An auxiliary view adds a surface beside the repo's other views instead of claiming it.
|
|
152
76
|
readonly auxiliary?: true | undefined;
|
|
153
77
|
// Lazily imported root component, rendered with `repo` (+ props) bound.
|
|
154
78
|
readonly view: () => Promise<Component>;
|
|
155
79
|
}
|
|
156
80
|
|
|
157
|
-
// A custom file viewer's
|
|
158
|
-
//
|
|
159
|
-
// viewer, gets its content, and renders `component` with `{ path, text?, blob?, src? }` bound, which of the
|
|
160
|
-
// three content props is filled is decided by the manifest's `fetch` (see ViewerContributionSchema).
|
|
81
|
+
// A custom file viewer's registration; `id` must match a `contributes.viewers` manifest entry. The host
|
|
82
|
+
// resolves an open file to it and renders `component` with the fetched content bound.
|
|
161
83
|
export interface ViewerRegistration {
|
|
162
84
|
readonly id: string;
|
|
163
85
|
readonly component: () => Promise<Component>;
|
|
164
86
|
}
|
|
165
87
|
|
|
166
|
-
// What a directory row offers when a provider has a document for it: the icon the
|
|
167
|
-
//
|
|
168
|
-
// host's set renders nothing rather than failing the registration.
|
|
88
|
+
// What a directory row offers when a provider has a document for it: the tree's icon, and the tab's
|
|
89
|
+
// title. An unknown `icon` renders nothing rather than failing the registration.
|
|
169
90
|
export interface DocumentOffer {
|
|
170
91
|
readonly icon: string;
|
|
171
|
-
// Names the
|
|
92
|
+
// Names the action on the row ("Open architecture doc"); read as a tooltip on an icon.
|
|
172
93
|
readonly tooltip: string;
|
|
173
|
-
// The tab's label
|
|
94
|
+
// The tab's label; keep it short, the strip already shows the directory's name beside it.
|
|
174
95
|
readonly title: string;
|
|
175
|
-
|
|
176
|
-
* because a permanent column of them is what stops the eye reading names, but that rule assumes an icon is
|
|
177
|
-
* an ACTION you already know you want. An offer that is EVIDENCE is the opposite case: "there is a page about
|
|
178
|
-
* this package" is a fact nobody can act on until they see it, and finding it by sweeping fifty-five rows with
|
|
179
|
-
* the mouse is not finding it. Such an offer sets this, and the row carries it dimmed until hover.
|
|
180
|
-
*
|
|
181
|
-
* Left off (the default) by an offer every directory of its kind gets, a repo's git history is always there,
|
|
182
|
-
* so a permanent glyph states nothing and costs the same attention. */
|
|
96
|
+
// Keeps the icon visible outside hover, for evidence rather than an action found by sweeping.
|
|
183
97
|
readonly evidence?: boolean;
|
|
184
98
|
}
|
|
185
99
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
* PATH-KEYED, which is the whole reason it is not a `view`. `detect()` on a ViewRegistration answers per REPO
|
|
189
|
-
* off the daemon's facts, and that is the wrong grain for a document: a monorepo is one repo with fifty-five
|
|
190
|
-
* documented packages. So this asks per directory instead, and the Workspace tree, not the rail, not a routed
|
|
191
|
-
* area, is where the answer lands.
|
|
192
|
-
*
|
|
193
|
-
* The host owns the tab. A provider says "yes, and here is what to call it"; opening it mounts `view` with the
|
|
194
|
-
* path bound, in the editor area beside the files it describes. That placement is the point: documentation about
|
|
195
|
-
* a package belongs next to the package, not behind a navigation away from it. */
|
|
100
|
+
// An extension's answer to "there is something to read about this directory", keyed by path rather
|
|
101
|
+
// than repo. The host owns the tab; `view` mounts beside the files it describes.
|
|
196
102
|
export interface DocumentProviderRegistration {
|
|
197
103
|
// Must match a `contributes.documents` entry in the approved manifest.
|
|
198
104
|
readonly id: string;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
* must be a LOOKUP and never a fetch, derive it from state the extension already keeps. Reading a ref in
|
|
202
|
-
* here is what repaints the tree when documents land, the same contract (and the same reason) as
|
|
203
|
-
* ViewRegistration.badge; and like badge, that state has to outlive the view being unmounted, so it belongs
|
|
204
|
-
* in module state owned by activate(). A throwing detect simply offers nothing for that row. */
|
|
105
|
+
// Whether this provider has a document for a path ("" is the workspace root). Called on every tree
|
|
106
|
+
// render, so it must be a lookup, never a fetch; a throwing detect offers nothing.
|
|
205
107
|
readonly detect: (path: string) => DocumentOffer | undefined;
|
|
206
108
|
// Lazily imported component, rendered with `path` bound.
|
|
207
109
|
readonly view: () => Promise<Component>;
|
|
208
110
|
}
|
|
209
111
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
* duplication `api.models` exists to end.
|
|
213
|
-
*
|
|
214
|
-
* Everything after the label is optional because they are pins, and the unpinned state is the one most callers
|
|
215
|
-
* want: absent means "whatever the daemon resolves", which is what keeps a saved choice working after an account
|
|
216
|
-
* is disconnected or a harness gains a provider. A caller that only cares which model runs can ignore all of
|
|
217
|
-
* them and lose nothing. */
|
|
112
|
+
// Everything that decides who serves a turn. `label` lets a view show the choice without its own
|
|
113
|
+
// catalog; everything after it is an optional pin (absent means the daemon resolves it).
|
|
218
114
|
export interface PickedModel {
|
|
219
|
-
// An
|
|
220
|
-
// Open on purpose: the set grows with what the sandbox has connected, and an extension only carries it.
|
|
115
|
+
// An AgentProvider, a model endpoint id, or an installed ACP agent's id; the set grows with what's connected.
|
|
221
116
|
readonly provider: string;
|
|
222
117
|
readonly model: string;
|
|
223
118
|
readonly label: string;
|
|
224
|
-
|
|
225
|
-
* first. It is on the pick rather than left to the daemon because the surfaces that start UNATTENDED runs are
|
|
226
|
-
* the ones that need it: nobody is watching at 6am, so a first account that has run out of headroom (or whose
|
|
227
|
-
* organization switched the plan off) is a run that errors every time until someone reads the row. */
|
|
119
|
+
// Which connected account runs the turn; absent means whichever comes first.
|
|
228
120
|
readonly account?: string | undefined;
|
|
229
|
-
|
|
230
|
-
* recognises ("Claude" is what three unrenamed accounts are all called).
|
|
231
|
-
*
|
|
232
|
-
* Absent means the shell cannot name it, which covers BOTH a pin whose credential has been disconnected and
|
|
233
|
-
* an account list this sandbox has not been read for yet. Do not render the first from the second: they are
|
|
234
|
-
* the same absence, and a view that reads it as "this automation is broken" says so about every row while the
|
|
235
|
-
* daemon is merely still starting. Show the name when there is one, and nothing when there isn't. */
|
|
121
|
+
// What the shell calls that account; absent covers both a disconnected pin and an unread account list.
|
|
236
122
|
readonly accountLabel?: string | undefined;
|
|
237
|
-
// `native` or `claude-code
|
|
238
|
-
// model ids under either. Absent ⇒ native, which for every other provider is the only answer there is.
|
|
123
|
+
// `native` or `claude-code`; absent means native, the only answer for most providers.
|
|
239
124
|
readonly harness?: string | undefined;
|
|
240
|
-
|
|
241
|
-
* model's own default. Pass it on the turn you start (`effort`) exactly as you pass `model`: the sandbox
|
|
242
|
-
* fills a pinned entry's tier in only for a run that named NO model, so a run your caret re-pointed and that
|
|
243
|
-
* dropped this would quietly fall back to the provider's default tier. */
|
|
125
|
+
// One of the provider's reasoning tiers ("low", "high", "max"); absent means the model's own default.
|
|
244
126
|
readonly effort?: string | undefined;
|
|
245
|
-
// What the shell calls that tier ("X-High")
|
|
246
|
-
// and for a runtime that owns its own reasoning settings and publishes no scale.
|
|
127
|
+
// What the shell calls that tier ("X-High"); absent whenever `effort` is.
|
|
247
128
|
readonly effortLabel?: string | undefined;
|
|
129
|
+
/* WHETHER THE MODEL REASONS BEFORE IT ANSWERS, where that is a choice it offers. Three states and not two:
|
|
130
|
+
* absent means nobody said, and the turn goes out with no thinking field so the model's own default decides,
|
|
131
|
+
* which is NOT the same as `false`. The distinction is load-bearing — Claude refuses its top effort tier
|
|
132
|
+
* beside thinking explicitly disabled — so pass it on the turn exactly as it arrives. */
|
|
133
|
+
readonly thinking?: boolean | undefined;
|
|
134
|
+
/* WHETHER THE WORK IS BOUGHT AT THE FASTER RATE, for a higher price. A request rather than a promise: the
|
|
135
|
+
* harness answers, and may decline (the plan, the account's own settings, the model). Absent ⇒ standard. */
|
|
136
|
+
readonly fast?: boolean | undefined;
|
|
248
137
|
}
|
|
249
138
|
|
|
250
139
|
export type SettingValue = string | number | boolean;
|
|
@@ -257,30 +146,22 @@ export interface ProcessStatus {
|
|
|
257
146
|
}
|
|
258
147
|
|
|
259
148
|
export interface IntenticApi {
|
|
260
|
-
// The host's @intentic/extension-api version,
|
|
149
|
+
// The host's @intentic/extension-api version, checked against `engines.intentic`.
|
|
261
150
|
readonly apiVersion: string;
|
|
262
151
|
readonly views: {
|
|
263
152
|
register(view: ViewRegistration): Disposable;
|
|
264
153
|
};
|
|
265
|
-
// Custom file viewers (contributes.viewers)
|
|
266
|
-
//
|
|
154
|
+
// Custom file viewers (contributes.viewers); the host owns the fetch and open-file lifecycle and
|
|
155
|
+
// renders the registered component. See ViewerRegistration.
|
|
267
156
|
readonly viewers: {
|
|
268
157
|
register(viewer: ViewerRegistration): Disposable;
|
|
269
158
|
};
|
|
270
|
-
// Per-directory documents (contributes.documents)
|
|
271
|
-
//
|
|
159
|
+
// Per-directory documents (contributes.documents); the extension says which directories it can
|
|
160
|
+
// explain, the host draws the tree affordance and owns the tab. See DocumentProviderRegistration.
|
|
272
161
|
readonly documents: {
|
|
273
162
|
register(provider: DocumentProviderRegistration): Disposable;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
* The row is the ordinary way in, so this is for the directories that have no row: the workspace root,
|
|
277
|
-
* which the tree renders the contents of rather than a line for. Without it a command contributed
|
|
278
|
-
* alongside a document provider, "Show Git History" in the palette, has nothing it can actually open.
|
|
279
|
-
*
|
|
280
|
-
* `id` must be one of this extension's registered providers, and the provider must have an offer for
|
|
281
|
-
* `path` (the same `detect()` the tree asks); a provider that has nothing to say about the directory
|
|
282
|
-
* opens nothing rather than an empty tab. The title and glyph come from that offer, so the tab reads
|
|
283
|
-
* exactly as it would have from the row. */
|
|
163
|
+
// Opens one of this extension's documents for a path with no row of its own (e.g. the workspace root).
|
|
164
|
+
// `id` must be a registered provider with an offer for `path`, or nothing opens.
|
|
284
165
|
open(id: string, path: string): void;
|
|
285
166
|
};
|
|
286
167
|
readonly commands: {
|
|
@@ -294,159 +175,78 @@ export interface IntenticApi {
|
|
|
294
175
|
set(key: string, value: SettingValue): Promise<void>;
|
|
295
176
|
onDidChange(listener: (key: string) => void): Disposable;
|
|
296
177
|
};
|
|
297
|
-
// The authenticated transport to the sandbox daemon
|
|
298
|
-
//
|
|
299
|
-
// allowlist, so a call to an undeclared method+path throws rather than reaching the whole daemon.
|
|
178
|
+
// The authenticated transport to the sandbox daemon; auth is injected host-side. Every door is gated
|
|
179
|
+
// by the manifest's `permissions.sandbox` allowlist.
|
|
300
180
|
readonly sandbox: {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
* the declared output shape, both checked at build time.
|
|
304
|
-
*
|
|
305
|
-
* This is the door to reach for. `request`/`json` below take a path string, which means every caller
|
|
306
|
-
* re-derives what this already knows: the method, the escaping, the query encoding, and the shape of the
|
|
307
|
-
* answer, the last of those as an unchecked assertion that keeps compiling long after the daemon's reply
|
|
308
|
-
* has changed underneath it. Thirteen extensions between them hand-wrote a hundred such calls and
|
|
309
|
-
* re-validated half the responses against the very schemas the contract had already declared.
|
|
310
|
-
*
|
|
311
|
-
* Gated identically, and on the same evidence: the host resolves the procedure to its method and concrete
|
|
312
|
-
* path and checks THAT against `permissions.sandbox`, so a manifest neither gains nor loses reach by an
|
|
313
|
-
* extension switching doors, and the usage record stays comparable across both. */
|
|
181
|
+
// The daemon's contract, typed: a call names a procedure instead of a URL, checked at build time.
|
|
182
|
+
// Gated the same way as `request`/`json`, on the resolved method and path.
|
|
314
183
|
readonly rpc: ContractRouterClient<typeof sandboxContract>;
|
|
315
184
|
request(path: string, init?: RequestInit): Promise<Response>;
|
|
316
185
|
json<T>(path: string, init?: RequestInit): Promise<T>;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
*
|
|
320
|
-
* Concurrent callers of one key share a single request, and a caller inside `staleTime` is answered
|
|
321
|
-
* from cache with no round trip at all. Which is what makes this worth using over `json`: a badge that
|
|
322
|
-
* polls a route on its own timer and hands the answer only to itself makes the view it badges for pay
|
|
323
|
-
* for the same read again on open. Through here, the badge's poll IS the view's first paint.
|
|
324
|
-
*
|
|
325
|
-
* The route is gated exactly as `json` is, `queryFn` is your function, and whatever it calls carries
|
|
326
|
-
* its own manifest check. */
|
|
186
|
+
// Reads through the host's cache from outside a component (e.g. a module-level badge timer);
|
|
187
|
+
// concurrent callers of one key share a request. Gated exactly as `json` is.
|
|
327
188
|
fetch<T>(query: HostQuery<T>): Promise<T>;
|
|
328
|
-
// Whether the active sandbox is
|
|
329
|
-
// drives host-provided vue-query `enabled` options.
|
|
189
|
+
// Whether the active sandbox is reachable; reactive when read inside a computed.
|
|
330
190
|
reachable(): boolean;
|
|
331
|
-
// A cache key scoped to the
|
|
332
|
-
// key, so caches never bleed across a sandbox switch.
|
|
191
|
+
// A cache key scoped to the active sandbox; required prefix for every host-provided vue-query key.
|
|
333
192
|
key(...parts: readonly string[]): readonly unknown[];
|
|
334
|
-
// The daemon's
|
|
335
|
-
// endpoints. Undefined until the sandbox has registered its address. Not needed for `request`/`json`
|
|
336
|
-
// (those take a path and inject auth), only when the raw origin must be shown to the user.
|
|
193
|
+
// The daemon's public tunnel origin, for externally-shareable URLs; undefined until the sandbox is registered.
|
|
337
194
|
origin(): string | undefined;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
* is independently floored by the daemon, so what this gates is whether an Approve button renders, never
|
|
341
|
-
* whether the call would succeed. A view that shows a viewer buttons the daemon will refuse teaches them
|
|
342
|
-
* that buttons lie; this is how a view says less instead.
|
|
343
|
-
*
|
|
344
|
-
* The first consumer is the approvals queue (approve/reject are maintainer-and-up), and it existed as a
|
|
345
|
-
* private composable before it was public API, which is the pattern this package's history warns about:
|
|
346
|
-
* a surface only its own app needs is a surface nobody else can build the same feature on. */
|
|
195
|
+
// The signed-in user's trust tier, reactive like `reachable`. For affordances only: every route is
|
|
196
|
+
// still floored by the daemon independently.
|
|
347
197
|
role(): "owner" | "maintainer" | "collaborator" | "viewer";
|
|
348
198
|
};
|
|
349
199
|
readonly workspace: {
|
|
350
200
|
repos(): readonly RepoFacts[];
|
|
351
201
|
capabilities(): readonly CapabilityFacts[];
|
|
352
202
|
onDidChange(listener: () => void): Disposable;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
* Separate from `contributes.files` because no file contribution could ever carry it: the daemon's
|
|
356
|
-
* watcher descent-ignores `.git`, so a changed ref produces no `workspaceChanged` path to match a prefix
|
|
357
|
-
* against. The daemon diffs the git dirs itself and pushes the repos that moved, exactly as it does for
|
|
358
|
-
* the repo SET (which the same watcher cannot see either, and for the same reason).
|
|
359
|
-
*
|
|
360
|
-
* This matters most for work the user did not do: an agent commits, rebases or lands out-of-band, with no
|
|
361
|
-
* HTTP mutation in this browser to hang an invalidate on. Without this a git surface is only ever as fresh
|
|
362
|
-
* as the last thing the user clicked. `repos` are root-relative ids ("root" is the workspace repo itself).
|
|
363
|
-
*/
|
|
203
|
+
// Fires when a ref moves in a repo (commit, branch, checkout, rebase) — a change `.git` watching can't
|
|
204
|
+
// see as a file path. `repos` are root-relative ids.
|
|
364
205
|
onDidChangeRefs(listener: (repos: readonly string[]) => void): Disposable;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
* write the prefix matching yourself. `paths` are the matching ones, workspace-root-relative.
|
|
368
|
-
*
|
|
369
|
-
* The declaration already made the daemon's push evict the query keys it names, and for anything the
|
|
370
|
-
* user is LOOKING at that is the whole story. It is not the story for what an extension does while none
|
|
371
|
-
* of it is on screen: an eviction only reaches a query something is observing, and a rail badge is by
|
|
372
|
-
* definition read with nothing mounted, so the tile went on saying whatever it last said until its own
|
|
373
|
-
* timer came round. Every badge in the workspace was therefore as fresh as its interval, up to ten
|
|
374
|
-
* minutes for the slowest, and a queue the owner had just emptied kept claiming its old count.
|
|
375
|
-
*
|
|
376
|
-
* You are unlikely to need this directly: `sandboxPoll` already wakes on it (background.ts), which is
|
|
377
|
-
* what turns a declared file binding into a badge that moves with the write. Reach for it when you keep
|
|
378
|
-
* background state some other way.
|
|
379
|
-
*
|
|
380
|
-
* FIRES WITH YOUR WHOLE DECLARATION when the host cannot say what moved: past a few hundred paths the
|
|
381
|
-
* daemon stops listing them, and a reconnected stream may have missed frames outright. Both are "assume
|
|
382
|
-
* yours changed", which is the safe direction for a badge, one re-read against a badge that is silently
|
|
383
|
-
* wrong for as long as the connection was away. */
|
|
206
|
+
// Fires for a write under your declared `contributes.files` prefixes; `paths` are the matching ones.
|
|
207
|
+
// Fires with your whole declaration when the host can't say what moved.
|
|
384
208
|
onDidChangeFiles(listener: (paths: readonly string[]) => void): Disposable;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
* The shell owns the strip, the viewer, the close orchestration and the edit-buffer bookkeeping; the
|
|
388
|
-
* extension owns only the question of what changed. Re-opening the same `key`+`scope`+`path` focuses the
|
|
389
|
-
* tab that is already open rather than stacking a second copy, see DiffPayload for how that identity is
|
|
390
|
-
* built. On mobile, where there is no strip, the host navigates to the diff instead.
|
|
391
|
-
*/
|
|
209
|
+
// Opens a diff in the editor area, beside the files it's about. Re-opening the same key+scope+path
|
|
210
|
+
// focuses the existing tab instead of stacking a new one.
|
|
392
211
|
openDiff(payload: DiffPayload): void;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
* Refreshes, never opens: a tab the user has since closed or replaced takes nothing, and the content is
|
|
396
|
-
* simply dropped. That is what makes the pending open safe to use for a slow source, a reader who has
|
|
397
|
-
* moved on to another file is never yanked back to this one, and never has it appear under them.
|
|
398
|
-
*/
|
|
212
|
+
// Fills a diff opened with `pending`. Refreshes only: a tab the user has since closed or replaced takes
|
|
213
|
+
// nothing.
|
|
399
214
|
fillDiff(payload: DiffPayload): void;
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
* reports, a documentation set's staging tree, the "what has the rail badge already shown" file each of
|
|
404
|
-
* them keeps. That is the right home, it survives a reload, it is shared across the owner's browsers,
|
|
405
|
-
* and the agent writing into it out-of-band is the whole point, but it left every extension spelling
|
|
406
|
-
* `sandbox.json(\`/workspace/file?path=${encodeURIComponent(path)}\`)` and then parsing the envelope out
|
|
407
|
-
* of the answer. Three extensions had five byte-identical copies of that one function.
|
|
408
|
-
*
|
|
409
|
-
* Gated exactly as `sandbox.request`/`sandbox.json` are: these go through the same permission check, so
|
|
410
|
-
* an extension still declares `GET /workspace/file` and `POST /workspace/upload` in its manifest and one
|
|
411
|
-
* that doesn't is still refused. This removes the encoding, not the grant. */
|
|
412
|
-
// The file's text, or undefined when it is not there. Absent is the ordinary FIRST state for most of what
|
|
413
|
-
// extensions keep, nothing has been acknowledged because nothing has been seen, so it is a value here,
|
|
414
|
-
// not a throw every caller would have to wrap. The daemon reports it the same way (a 200 that says the
|
|
415
|
-
// path holds nothing), so a poll over files that do not exist yet is silent rather than a page of failed
|
|
416
|
-
// requests in the owner's console.
|
|
215
|
+
// Reads and writes workspace files without the encoding boilerplate. Gated exactly as
|
|
216
|
+
// `sandbox.request`/`sandbox.json`: the manifest grant is unchanged.
|
|
217
|
+
// The file's text, or undefined when it doesn't exist — a valid first state, not a thrown error.
|
|
417
218
|
file(path: string): Promise<string | undefined>;
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
* One tolerant reader rather than one per caller. These files are written by agents and editable by
|
|
421
|
-
* hand, so a half-written or hand-mangled one is a case that WILL happen, and "skip it" is the right
|
|
422
|
-
* answer everywhere: one bad file must never blank the surface that reads it. Arrays answer undefined
|
|
423
|
-
* too, every caller of this wants a record. */
|
|
219
|
+
// Parsed as a JSON object, or undefined if absent, truncated, or not an object. One tolerant reader:
|
|
220
|
+
// a bad file is skipped, never a thrown error.
|
|
424
221
|
readJson<T>(path: string): Promise<T | undefined>;
|
|
425
|
-
//
|
|
426
|
-
//
|
|
222
|
+
// Creates or replaces a file. Throws on failure, unlike the reads: a silent no-op would lose what the
|
|
223
|
+
// caller was told was saved.
|
|
427
224
|
write(path: string, body: string): Promise<void>;
|
|
428
225
|
};
|
|
429
|
-
// The extension's
|
|
226
|
+
// The extension's own declared background processes; names outside the manifest are refused.
|
|
430
227
|
readonly processes: {
|
|
431
228
|
status(name: string): Promise<ProcessStatus>;
|
|
432
229
|
start(name: string): Promise<void>;
|
|
433
230
|
stop(name: string): Promise<void>;
|
|
434
231
|
};
|
|
435
|
-
// The shell's
|
|
436
|
-
// server, an agent terminal); the host owns the panel itself.
|
|
232
|
+
// The shell's one global terminal panel; extensions aim it at a tmux session, the host owns the panel.
|
|
437
233
|
readonly terminal: {
|
|
438
|
-
//
|
|
234
|
+
// Opens the panel focused on a tmux session, starting or attaching it.
|
|
439
235
|
open(session: string): void;
|
|
440
|
-
//
|
|
236
|
+
// Shows or hides the panel without focusing a session.
|
|
441
237
|
setOpen(open: boolean): void;
|
|
442
238
|
};
|
|
443
|
-
// The shell's chat, the way `terminal` is the shell's
|
|
444
|
-
// the host owns the tab.
|
|
445
|
-
// history, an audit row, where "why did it do that" is only answerable by reading the transcript.
|
|
239
|
+
// The shell's chat, the way `terminal` is the shell's terminal panel: the extension names a
|
|
240
|
+
// transcript, the host owns the tab.
|
|
446
241
|
readonly chat: {
|
|
447
|
-
//
|
|
448
|
-
//
|
|
242
|
+
// Opens (or focuses) the tab for a stored session id. A session the daemon no longer holds opens an
|
|
243
|
+
// empty tab rather than failing.
|
|
449
244
|
openSession(sessionId: string): void;
|
|
245
|
+
/* Open (or focus) the docked chat for a fleet agent by its id: the same thing a card press on the
|
|
246
|
+
* agents board does. The agent's conversation appears in the chat panel beside the current view
|
|
247
|
+
* rather than navigating away from it. An agent the roster does not hold yet (archived, or between
|
|
248
|
+
* a start and the first roster frame) is looked up before opening. */
|
|
249
|
+
openAgent(agentId: string): void;
|
|
450
250
|
/* AIM A NEW CHAT AT A WORKFLOW: the host opens a session exactly as "New agent" does, with the
|
|
451
251
|
* composer's workflow badge set to this design, so the next message the user types becomes that run's
|
|
452
252
|
* request instead of a turn on the chat.
|
|
@@ -461,68 +261,46 @@ export interface IntenticApi {
|
|
|
461
261
|
* nothing here can name a `Workflow` type.
|
|
462
262
|
*/
|
|
463
263
|
composeWorkflow(workflowId: string): void;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
* types becomes the loop's GOAL and Send starts it running.
|
|
467
|
-
*
|
|
468
|
-
* It is a separate call rather than a flag on that one because the two badges are separate picks that
|
|
469
|
-
* cannot both be armed, and a single "compose with this id" would have had to guess which kind an id
|
|
470
|
-
* was. A loop id, not a running loop's, nothing has started, and nothing is spent until the send.
|
|
471
|
-
*/
|
|
264
|
+
// Like `composeWorkflow`, but arms the composer's loop badge: the next message becomes the loop's
|
|
265
|
+
// goal, and Send starts it running.
|
|
472
266
|
composeLoop(loopId: string): void;
|
|
473
267
|
};
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
*
|
|
477
|
-
* It is an API rather than a kit component because the picker is not a widget, it is a live read of every
|
|
478
|
-
* connected provider's catalog, which credentials the sandbox actually holds, and what each model can do.
|
|
479
|
-
* An extension that rendered its own control could only ever offer a worse list: the acceptance view's did,
|
|
480
|
-
* fetching one provider's models behind a second dropdown for the provider itself, and so it happily
|
|
481
|
-
* offered models the sandbox had no credential for, a run that fails on a credential error minutes later.
|
|
482
|
-
*
|
|
483
|
-
* IT COVERS THE WHOLE CHOICE, provider, account, harness, model, because covering three quarters of it is
|
|
484
|
-
* what produced the copy this API exists to prevent. The automations form asked all four questions, found an
|
|
485
|
-
* API that answered three, and hand-rolled ROWS OF CHIPS for every one of them to keep its own fields
|
|
486
|
-
* consistent with each other: a static provider list that offered providers the sandbox had no credential for
|
|
487
|
-
* and could not offer a model endpoint or an ACP agent at all, and a model row eleven chips wide that grew
|
|
488
|
-
* with every release. Partial coverage does not buy partial reuse; it buys none. */
|
|
268
|
+
// Which model a run this extension starts will spend; the host owns the picker, a live read of every
|
|
269
|
+
// connected provider's catalog. Covers provider, account, harness and model together.
|
|
489
270
|
readonly models: {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
* read inside a computed.
|
|
493
|
-
*
|
|
494
|
-
* `role` is the job, as the sandbox names it — "pipeline-fix", "maintenance-chore", "documentation-run",
|
|
495
|
-
* "acceptance-run", "deployment-fix" and the rest. There is a list per job rather than one for
|
|
496
|
-
* unattended work as a class, because a documentation sweep and a red production pipeline are not the
|
|
497
|
-
* same spend and the owner is the one who gets to say so. Pass the role your surface's turns send as
|
|
498
|
-
* `runRole`, or the two will disagree about which model a click costs. An unknown role is not an error:
|
|
499
|
-
* it answers with the owner's own chat model, the same floor an unpinned job gets. */
|
|
271
|
+
// The sandbox's default model for a job `role` (e.g. "acceptance-run"), falling back to the owner's
|
|
272
|
+
// chat model; reactive in a computed.
|
|
500
273
|
agentRun(role: string): PickedModel;
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
* that SAVE a choice rather than spend it immediately: without it every one of them would keep a catalog
|
|
504
|
-
* to pretty-print its own stored ids, which is the duplication this API exists to end, and it would go
|
|
505
|
-
* stale the day a provider renames a model or the owner disconnects an account.
|
|
506
|
-
*
|
|
507
|
-
* Reactive when read inside a computed, a model that lands in the catalog, or an account that stops being
|
|
508
|
-
* connected, changes what a stored pin should say about itself. */
|
|
274
|
+
// Renders a stored pin (bare ids) back into a display-ready `PickedModel`, so a saved choice doesn't
|
|
275
|
+
// need its own catalog. Reactive: reflects a renamed model or disconnected account.
|
|
509
276
|
describe(selection: {
|
|
510
277
|
readonly provider: string;
|
|
511
278
|
readonly model: string;
|
|
512
279
|
readonly account?: string | undefined;
|
|
513
280
|
readonly harness?: string | undefined;
|
|
514
281
|
readonly effort?: string | undefined;
|
|
282
|
+
readonly thinking?: boolean | undefined;
|
|
283
|
+
readonly fast?: boolean | undefined;
|
|
515
284
|
}): PickedModel;
|
|
516
285
|
/* Open the picker over `anchor`, a popover on desktop, a sheet on mobile, starting on the selection the
|
|
517
286
|
* caller is holding. Resolves with the pick, or undefined if it was dismissed. A second call supersedes
|
|
518
287
|
* the first, resolving it as a dismissal.
|
|
519
288
|
*
|
|
520
|
-
* A
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
289
|
+
* IT IS A FORM AND IT ENDS IN A PRESS. Every row in the panel — the model list included — updates the
|
|
290
|
+
* open selection and leaves the panel open; the bar at the bottom, carrying your `action` as its label,
|
|
291
|
+
* is what resolves this promise. Nothing else does: Escape, a click outside and the sheet's backdrop are
|
|
292
|
+
* all a plain cancel, resolving undefined and keeping nothing, so a caller never has to guess whether a
|
|
293
|
+
* dismissal meant "as you were" or "yes, but from over there".
|
|
294
|
+
*
|
|
295
|
+
* The panel used to settle on the model row instead, which cost the callers that spend money a second
|
|
296
|
+
* click (leave the panel, then press the thing that starts the run) and made backing out of an effort
|
|
297
|
+
* change impossible. If your surface merely stores the answer, name your verb accordingly ("Use this
|
|
298
|
+
* model" is the default) and the press reads as the save it is.
|
|
299
|
+
*
|
|
300
|
+
* Picking a model under a DIFFERENT provider clears the account and harness with it: an account id is one
|
|
301
|
+
* provider's store key, so carrying it across would pin the work to an account that provider does not
|
|
302
|
+
* have. The RUN SETTINGS survive, because effort, thinking and speed are questions every model answers
|
|
303
|
+
* for itself. */
|
|
526
304
|
pick(options: {
|
|
527
305
|
readonly anchor: HTMLElement;
|
|
528
306
|
readonly provider: string;
|
|
@@ -530,41 +308,33 @@ export interface IntenticApi {
|
|
|
530
308
|
readonly account?: string | undefined;
|
|
531
309
|
readonly harness?: string | undefined;
|
|
532
310
|
readonly effort?: string | undefined;
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
*
|
|
537
|
-
|
|
311
|
+
readonly thinking?: boolean | undefined;
|
|
312
|
+
readonly fast?: boolean | undefined;
|
|
313
|
+
/* THE VERB ON THE PANEL'S OWN BUTTON — "Fix with agent", "Run all 21 stories", "Save this step".
|
|
314
|
+
* Yours, because only you know what the press does, and it is what lets configuring a run and
|
|
315
|
+
* starting it be one act instead of two. Defaults to "Use this model", which is honest for a form
|
|
316
|
+
* that is only storing the answer. */
|
|
317
|
+
readonly action?: string | undefined;
|
|
318
|
+
/* OFFER THE MODEL'S OWN RUN SETTINGS — reasoning effort, extended thinking, speed — for a caller
|
|
319
|
+
* that will carry them onto the turn it starts or the pin it stores. Off by default, and
|
|
320
|
+
* deliberately: a form that keeps a model and none of these (a workflow step) would be showing
|
|
321
|
+
* controls whose answers it drops, which is worse than showing none. Every <AgentRunButton> asks for
|
|
322
|
+
* them through `useAgentRunPick`, so a surface using that gets them already. */
|
|
323
|
+
readonly chooseRun?: boolean;
|
|
538
324
|
}): Promise<PickedModel | undefined>;
|
|
539
325
|
};
|
|
540
|
-
//
|
|
326
|
+
// Navigates the shell to an app path (e.g. "/capabilities", "/ext/<view>/<key>").
|
|
541
327
|
readonly navigate: (path: string) => void;
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
*
|
|
545
|
-
* Every row and card in this app that goes somewhere has a URL behind it, and a view that only calls
|
|
546
|
-
* `navigate` throws all of it away: nothing under the pointer in the status bar, nothing in the browser's
|
|
547
|
-
* own right-click menu, nothing to copy, and Ctrl/⌘-click navigating the tab the user is reading instead
|
|
548
|
-
* of opening a second one. So a navigational row renders as `<a :href="api.href(path)">` and calls
|
|
549
|
-
* `navigate` from its click handler: guarded with `browserOwnsClick` (@intentic/extension-ui) so a
|
|
550
|
-
* modified click is left to the browser. */
|
|
328
|
+
// The same path as a browser address, for `<a href>` so a navigational row is a real link, not just a
|
|
329
|
+
// click handler. Use with `browserOwnsClick` for modified clicks.
|
|
551
330
|
readonly href: (path: string) => string;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
* A view's own route space is the QUERY, not extra path segments: `/ext/:ext/:key?` is the whole route, and
|
|
555
|
-
* the `:key` segment already means "which activation" (one per repo). A view with internal navigation, a
|
|
556
|
-
* document browser, a selected run, an open file, therefore has nowhere in the path to put it, and without
|
|
557
|
-
* this it could only hold that state in memory, where a reload loses it and a link cannot carry it.
|
|
558
|
-
*
|
|
559
|
-
* Reading is reactive: read inside a computed and the view re-renders when the URL moves, which lets a view
|
|
560
|
-
* DERIVE its state from the query rather than mirror it in a ref (mirroring needs two watchers that can fight
|
|
561
|
-
* each other). Back and forward then work for free, because the URL is the state. */
|
|
331
|
+
// The URL as a view's own state, so what's on screen can be linked. A view's route space is the query
|
|
332
|
+
// only (`:key` already names the activation).
|
|
562
333
|
readonly route: {
|
|
563
|
-
// The current query, flattened
|
|
334
|
+
// The current query, flattened; a repeated key takes its first value.
|
|
564
335
|
query(): Readonly<Record<string, string>>;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
* another document is exactly what Back ought to undo. Other views' params are left alone. */
|
|
336
|
+
// Merges a patch in; a key set to `undefined` is removed. Replaces the history entry by default, or
|
|
337
|
+
// pushes one when `push` is set.
|
|
568
338
|
setQuery(patch: Readonly<Record<string, string | undefined>>, options?: { readonly push?: boolean }): void;
|
|
569
339
|
};
|
|
570
340
|
readonly theme: {
|
|
@@ -579,8 +349,8 @@ export interface ExtensionContext {
|
|
|
579
349
|
readonly subscriptions: Disposable[];
|
|
580
350
|
}
|
|
581
351
|
|
|
582
|
-
// The
|
|
583
|
-
//
|
|
352
|
+
// The bundle's default (or named) exports: `activate` runs once after the engines check, `deactivate`
|
|
353
|
+
// runs before the host discards the extension.
|
|
584
354
|
export interface ExtensionModule {
|
|
585
355
|
activate(api: IntenticApi, context: ExtensionContext): void | Promise<void>;
|
|
586
356
|
deactivate?(): void | Promise<void>;
|