@intentic/sandbox-contract 1.212.0 → 1.214.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/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +4 -4
- package/dist/agent-catalog.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +2 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +150 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +5 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +2 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/events.d.ts +5 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +1 -0
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +154 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/schemas.d.ts +124 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +39 -0
- package/dist/schemas.js.map +1 -1
- package/dist/workspace-state.d.ts +8 -5
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +7 -7
- package/dist/workspace-state.js.map +1 -1
- package/package.json +4 -4
- package/src/agent-catalog.ts +13 -6
- package/src/contracts/agents.contract.ts +12 -0
- package/src/events.ts +11 -3
- package/src/schemas.ts +126 -4
- package/src/workspace-state.test.ts +42 -1
- package/src/workspace-state.ts +128 -29
package/src/workspace-state.ts
CHANGED
|
@@ -60,11 +60,23 @@ export interface WorkspaceStateFile extends StateFile {
|
|
|
60
60
|
* the same reason. An ignore-pattern list would invert it: a credential store added next month would be
|
|
61
61
|
* committed on its first write, and nothing would have had to change for that to happen.
|
|
62
62
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* -
|
|
66
|
-
*
|
|
67
|
-
*
|
|
63
|
+
* WHAT EARNS IT is one question, asked of the entry rather than of its shape: does a change here change what
|
|
64
|
+
* this sandbox DOES? Two families answer yes.
|
|
65
|
+
* - CONFIGURATION — the small, slow-moving files that decide how the sandbox behaves: settings, personas,
|
|
66
|
+
* skills, automations, workflow designs, the environment overlay, which extensions are on.
|
|
67
|
+
* - AUTHORED CONTENT WHOSE CONSEQUENCES LEAVE THE SANDBOX — a workspace extension is code that runs in the
|
|
68
|
+
* app with a declared permission surface and a backend of its own; a post draft is words that go out
|
|
69
|
+
* under the owner's name. Neither is configuration, and reading `versioned` as config-only is what kept
|
|
70
|
+
* both of them out: an agent could write, and then run, a whole extension with a diff nowhere, and
|
|
71
|
+
* propose a public post that left no trace once declined. The rule was never "is this a setting", it was
|
|
72
|
+
* "can this be read, reverted and attributed" — and for the two things the AGENT authors on its own,
|
|
73
|
+
* that matters more than it does for a file a person edited on purpose.
|
|
74
|
+
*
|
|
75
|
+
* Two kinds of entry stay out on purpose even though they are `carry` and hold no secret:
|
|
76
|
+
* - LEDGERS (workflow runs, loop iterations, thread bookkeeping, permission-usage batches, held-wake
|
|
77
|
+
* queues), which are rewritten on a timer or several times per step. Tracking them buries the owner's
|
|
78
|
+
* code review under machine noise — one of them is written every few seconds while a browser has the app
|
|
79
|
+
* open. A queue is a ledger too: it records that something was ASKED, and is emptied when it is answered.
|
|
68
80
|
* - BULK (session transcripts, artifacts), which are hundreds of megabytes of constantly-rewritten
|
|
69
81
|
* content. They travel in a bundle; they do not belong in a diff.
|
|
70
82
|
* `versioned` is therefore NARROWER than `carry`, and the two answer different questions: carry is "does it
|
|
@@ -74,7 +86,10 @@ export interface WorkspaceStateFile extends StateFile {
|
|
|
74
86
|
* human- or agent-written TEXT that a workspace search should surface? Every `versioned` entry already is
|
|
75
87
|
* (a setting, a persona, a skill — things the agent is asked to find and edit), so this flag exists only
|
|
76
88
|
* for the entries that are authored content without being config: a draft awaiting approval, a staged
|
|
77
|
-
* README, an extension the agent wrote in place.
|
|
89
|
+
* README, an extension the agent wrote in place. Two of those three are now `versioned` as well, which makes
|
|
90
|
+
* the flag redundant to `SEARCHABLE_STATE_PATHS` on them and is why it stays anyway: searchability is a
|
|
91
|
+
* property of the content, and hanging it on `versioned` would mean a future decision to stop TRACKING a
|
|
92
|
+
* draft silently also stopped anyone FINDING one. Everything else under `.intentic` is machine state, and
|
|
78
93
|
* `SEARCHABLE_STATE_PATHS` below is what lets the search engine deny the rest BY DEFAULT instead of
|
|
79
94
|
* hand-keeping a deny list that goes stale the day a store is added (which is how a 98 kB loop ledger and
|
|
80
95
|
* whole third-party extension checkouts ended up ranking in code search). */
|
|
@@ -94,12 +109,31 @@ export interface WorkspaceStateFile extends StateFile {
|
|
|
94
109
|
const STATE_FILES = [
|
|
95
110
|
/* A capability add/remove recomposes the environment overlay and can add or drop a repo's panel.
|
|
96
111
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
|
|
112
|
+
* SPLIT ALREADY — and this entry's classification had not caught up, which is the whole of what changed here.
|
|
113
|
+
* It read `secret` on a claim that had stopped being true: that each entry's `config` carries that
|
|
114
|
+
* capability's credential, so the manifest is a secret in full. It does not. capabilities-store.ts's
|
|
115
|
+
* withSecretVault keeps credential VALUES off /work entirely — the manifest holds `__intentic_vaulted__`
|
|
116
|
+
* where one used to be, reads rehydrate so no caller noticed, and main.ts sweeps a hand-written value out at
|
|
117
|
+
* boot. What is left is the SHAPE of a connection: a kind, a URL, a username, a purpose, which permissions a
|
|
118
|
+
* connected computer was granted.
|
|
119
|
+
*
|
|
120
|
+
* WHICH KEYS THOSE ARE IS DERIVED, not listed a second time: `echo` already answers "what of this config may
|
|
121
|
+
* a browser see", and the credential keys are exactly its complement (capabilities/secret-fields.ts). A kind
|
|
122
|
+
* that starts withholding a new field starts vaulting it on the same commit. That is what makes this
|
|
123
|
+
* classification a property of the code rather than a promise to re-audit it — the reason the entry can be
|
|
124
|
+
* reclassified at all, and the reason a hand-kept "these fields are safe" list could not have earned it.
|
|
125
|
+
*
|
|
126
|
+
* `carry`, and this is the entry where that earns the most. composeEnvironment reads its Dockerfile fragments
|
|
127
|
+
* from here, so a bundle that dropped it arrived on a stock overlay with an import report listing every
|
|
128
|
+
* connection to re-add by hand. It now arrives listing them itself, each visibly unconnected and waiting for
|
|
129
|
+
* one credential apiece — the shape personas.json has had all along, for the same reason.
|
|
130
|
+
*
|
|
131
|
+
* `versioned`, which is the point. Connecting this sandbox to a deployment orchestrator, or granting a
|
|
132
|
+
* connected computer shell and screen control, is the largest change anyone makes to what it can DO, and it
|
|
133
|
+
* left a diff nowhere. One consequence worth stating rather than discovering: an identifier that pairs with a
|
|
134
|
+
* credential — Komodo's api key beside its api secret, which its own connector card calls "like a database
|
|
135
|
+
* user" — is echoed, and therefore lands in the diff exactly as a database username would. */
|
|
136
|
+
{ path: ".intentic/capabilities.json", invalidates: ["capabilities", "environment", "panels", "manifests"], portability: "carry", versioned: true },
|
|
103
137
|
|
|
104
138
|
/* Which workspace-derived recommendations the owner has said "not needed" to, and the evidence each was
|
|
105
139
|
* declined against. It rides the `capabilities` key because the catalog is what changes when one lands, and
|
|
@@ -159,10 +193,22 @@ const STATE_FILES = [
|
|
|
159
193
|
portability: "derived",
|
|
160
194
|
note: "Stamps of when each rule last did something; the new sandbox starts its own record.",
|
|
161
195
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
196
|
+
/* Written by the AGENT's file tools (the drafts skill), read by the owner's approval inbox — the one entry
|
|
197
|
+
* here whose whole point is that a change arrives from outside the browser that renders it. `authored`:
|
|
198
|
+
* a draft is text somebody wrote, and "find the reddit draft about X" is an ordinary search.
|
|
199
|
+
*
|
|
200
|
+
* `versioned` because a draft is the furthest-reaching thing the agent writes: these words go out under the
|
|
201
|
+
* owner's name, to an audience, and cannot be recalled. The approval inbox already gates that — but a gate is
|
|
202
|
+
* not a record. Declining one used to erase it, so the question "what has this agent tried to post" had no
|
|
203
|
+
* answer at all, and an approved post's own history (what was proposed, what the owner changed, when it
|
|
204
|
+
* actually went) lived only in a file nobody could diff.
|
|
205
|
+
*
|
|
206
|
+
* It costs almost nothing to track, which is why the ledger objection does not reach it: a draft is one small
|
|
207
|
+
* file per post, it is written a handful of times across its whole life (proposed → approved → posted, with
|
|
208
|
+
* `postedAt`/`postedUrl` stamped at the end), and it is KEPT afterwards rather than consumed — so tracking
|
|
209
|
+
* yields a durable record instead of the add/delete churn a queue would produce. Nothing in a draft is a
|
|
210
|
+
* credential: it is a platform, a target URL and the body itself, all of it bound for publication anyway. */
|
|
211
|
+
{ path: ".intentic/drafts/", invalidates: ["drafts"], portability: "carry", versioned: true, authored: true },
|
|
166
212
|
// ---- declared by the extension that renders them (contributes.files), not here ----
|
|
167
213
|
// The path is the DAEMON's (automations-store writes both), the query keys are the intentic.automations
|
|
168
214
|
// extension's. It declares them in its own manifest and the browser unions the two lists, so uninstalling
|
|
@@ -263,16 +309,33 @@ const STATE_FILES = [
|
|
|
263
309
|
why: "Thread bookkeeping (an inbound thread — a Doorbell visitor, a Discord or Slack channel — → sandbox conversation + provider session), written on EVERY inbound message. Nothing in the browser reads it: what a thread produces is a conversation, and the fleet board already learns about that from the agent registry's own push. Naming a key here would bill every connected browser a refetch per inbound message — the request storm this table's own note warns about — to refresh nothing it can see.",
|
|
264
310
|
portability: "carry",
|
|
265
311
|
},
|
|
266
|
-
/*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
312
|
+
/* SPLIT, so that "what an extension is configured to do" and "the token it does it with" stop being one file.
|
|
313
|
+
*
|
|
314
|
+
* This entry used to be `secret` and untracked, classed by what a value COULD hold: values are a primitive
|
|
315
|
+
* union an extension chooses the meaning of, and "an API key for the service I talk to" is squarely within
|
|
316
|
+
* it. That classification was honest about the risk and wrong about the file — it meant an extension's whole
|
|
317
|
+
* configuration was unreviewable because one of its keys might be a credential, AND the credential was in
|
|
318
|
+
* there anyway, in a file the workspace API does not lock. A turn could simply read it.
|
|
319
|
+
*
|
|
320
|
+
* A descriptor already says which keys those are (`contributes.settings[].secret`), so the values it names
|
|
321
|
+
* now live in the vault off /work and this file keeps the rest — the capability manifest's split, applied to
|
|
322
|
+
* the same problem one table over (extensions/extension-settings.ts holds it, and the reasoning). Reads
|
|
323
|
+
* rehydrate, so no caller changed.
|
|
324
|
+
*
|
|
325
|
+
* What the split earns: `carry`, because what is left is an extension's configuration and a bundle should
|
|
326
|
+
* arrive with it; and `versioned`, because turning an extension's behaviour on is a decision, and the file
|
|
327
|
+
* that records it can now be read without reading anybody's token. The boot sweep is what keeps that true of
|
|
328
|
+
* a file the agent can also edit — see vaultExtensionSettingSecrets.
|
|
329
|
+
*
|
|
330
|
+
* NO `note`, and the split is why: a note is printed by the import report beside a SKIPPED entry, so an entry
|
|
331
|
+
* that carries can never show one. "Re-enter the credentials" is now the vault's instruction to give, and the
|
|
332
|
+
* vault is under `.intentic/auth/` — which is skipped, and says so there. */
|
|
270
333
|
{
|
|
271
334
|
path: ".intentic/extension-settings.json",
|
|
272
335
|
invalidates: [],
|
|
273
336
|
why: "Held in a module-level shallowRef store per extension (web's extensionSettingsStore) with no query observer, and deliberately so: api.settings.get must answer SYNCHRONOUSLY from an extension's first activate() line, and the store outlives every component scope. A module-level QueryObserver is the one shape that would make invalidation refetch, and this app already ruled it out — it detaches on the queryClient.clear() at logout (see useSandbox's sandbox-list mirror). So a remote member's setting edit reaches this browser on its next load, not live.",
|
|
274
|
-
portability: "
|
|
275
|
-
|
|
337
|
+
portability: "carry",
|
|
338
|
+
versioned: true,
|
|
276
339
|
},
|
|
277
340
|
/* Unlike the settings file above it, the on/off switch IS observed by a query — the Extensions tab's list,
|
|
278
341
|
* which carries each row's switch position — so a flip made elsewhere (another member, the agent writing the
|
|
@@ -289,8 +352,21 @@ const STATE_FILES = [
|
|
|
289
352
|
* an extension and it is live for the daemon and every session at once, since .intentic is shared), so this
|
|
290
353
|
* push is what makes one appearing or changing show up on the Extensions tab while the owner watches.
|
|
291
354
|
* `authored` for the same reason as drafts: this is source the agent wrote and will be asked to find —
|
|
292
|
-
* unlike `.intentic/extensions/` below, which is CLONES of source that lives elsewhere.
|
|
293
|
-
|
|
355
|
+
* unlike `.intentic/extensions/` below, which is CLONES of source that lives elsewhere.
|
|
356
|
+
*
|
|
357
|
+
* `versioned` FOR THAT SAME REASON, which is the whole argument. Every other load path an extension can take
|
|
358
|
+
* is already reviewable by construction: a git-installed one is a sha in `capabilities.json` that an owner
|
|
359
|
+
* approved, a baked one shipped in the image. This one is neither — it is code that appears because an agent
|
|
360
|
+
* wrote a file, runs in the app on the owner's session, may register a rail tile, and may serve HTTP from a
|
|
361
|
+
* node process with the workspace under `node:fs` and whatever `permissions.daemon` names. Untracked, the
|
|
362
|
+
* switch that turns it on was in `git log` (extension-enablement.json, below) while the thing being switched
|
|
363
|
+
* on was not: a commit could record enabling something nobody else could read. It is also the one extension
|
|
364
|
+
* kind with no install moment to review at, so the diff is the only review there is.
|
|
365
|
+
*
|
|
366
|
+
* Not a ledger and not bulk: a handful of small authored files per extension, written when someone edits them.
|
|
367
|
+
* The daemon restarts the backend host on a change here, so an edit is already a consequential event — this
|
|
368
|
+
* makes it a legible one. */
|
|
369
|
+
{ path: ".intentic/workspace-extensions/", invalidates: ["extensions"], portability: "carry", versioned: true, authored: true },
|
|
294
370
|
/* What the registry comparison found per installed extension (update available / advisory / post-update
|
|
295
371
|
* health), written by the periodic check and by the update/revert transactions — pushed to the tab because
|
|
296
372
|
* an advisory that auto-disabled something must not wait for a reload to be seen. */
|
|
@@ -306,6 +382,17 @@ const STATE_FILES = [
|
|
|
306
382
|
why: "Which of the routes each extension DECLARED it has actually called — the evidence behind the permissions list on its row. The one entry here whose empty set is a RATE decision rather than an architectural one: every browser with the app open reports its batch on a timer, so wiring this to the `extensions` query would refetch the whole list every few seconds for a figure nobody is watching change. The tab reads it when it loads, which is when anyone is reading it.",
|
|
307
383
|
portability: "carry",
|
|
308
384
|
},
|
|
385
|
+
/* THE ONE ENTRY WHERE "HOLDS NO CREDENTIAL" IS TRUE AND `versioned` IS STILL WRONG, which is worth stating
|
|
386
|
+
* because it looks like the two above it: an email and a role per row, nothing to vault, and "who may drive
|
|
387
|
+
* this sandbox" is as consequential a fact as any this table tracks.
|
|
388
|
+
*
|
|
389
|
+
* It stays out for two reasons that are not about secrecy. It is a MIRROR — the platform's invite records are
|
|
390
|
+
* the grant, this is the copy the enforcer keeps so a grant it never received is never honoured, and a change
|
|
391
|
+
* here is the two disagreeing rather than anyone deciding something. Review of the decision already exists,
|
|
392
|
+
* on the Access tab, against the record that is authoritative. And tracking it would mean reclassifying it
|
|
393
|
+
* `carry` to satisfy the guard, which is the one thing it must never be: an access list that travelled would
|
|
394
|
+
* let a source sandbox hand itself the target's ownership. Widening the guard for this single entry is the
|
|
395
|
+
* worse trade — it protects every `identity` entry, and most of those ARE credentials. */
|
|
309
396
|
{
|
|
310
397
|
path: ".intentic/members.json",
|
|
311
398
|
invalidates: [],
|
|
@@ -318,13 +405,20 @@ const STATE_FILES = [
|
|
|
318
405
|
/* Keep credentials and conversation state in disjoint top-level trees. Provider homes are intentionally
|
|
319
406
|
* classified as a single secret unit: several CLIs mix OAuth, config, and provider-native thread metadata,
|
|
320
407
|
* and no generic export can safely distinguish those files. The broad root also makes a newly-added provider
|
|
321
|
-
* secret by construction instead of relying on another hand-maintained provider-name list.
|
|
408
|
+
* secret by construction instead of relying on another hand-maintained provider-name list.
|
|
409
|
+
*
|
|
410
|
+
* IT IS NO LONGER ONLY THE AI LOGINS. Both credential splits put their vault here — `capability-secrets.json`
|
|
411
|
+
* and `extension-secrets.json`, sited beside the provider homes precisely because this tree is already
|
|
412
|
+
* outside the file routes, the workspace walk and the search index (composition.ts sites them, and the two
|
|
413
|
+
* stores argue why). So this is now the ONE entry a secret-less bundle leaves behind, and its note is
|
|
414
|
+
* therefore the only place the owner is told what to re-enter: the manifests that name those connections
|
|
415
|
+
* travel, and would otherwise arrive looking complete. */
|
|
322
416
|
{
|
|
323
417
|
path: ".intentic/auth/",
|
|
324
418
|
invalidates: [],
|
|
325
|
-
why: "AI-provider credentials and runtime homes; each account is rendered through owner-gated provider routes.",
|
|
419
|
+
why: "AI-provider credentials and runtime homes, plus the capability and extension-settings secret vaults; each account is rendered through owner-gated provider routes.",
|
|
326
420
|
portability: "secret",
|
|
327
|
-
note: "Sign the agent's AI accounts in again on the Agent tab.",
|
|
421
|
+
note: "Sign the agent's AI accounts in again on the Agent tab, then re-enter each connection's credential on Capabilities and each extension's secret settings on Extensions — both arrived listed but unauthenticated.",
|
|
328
422
|
},
|
|
329
423
|
/* Agent session transcripts, rewritten on every streamed token.
|
|
330
424
|
*
|
|
@@ -498,8 +592,13 @@ export const VERSIONED_STATE_PATHS: readonly string[] = WORKSPACE_STATE_FILES.fi
|
|
|
498
592
|
* ledger is forgotten from, and the forgetting is silent — it ranked loop iteration history and cloned
|
|
499
593
|
* third-party extension source against the user's own code for months before this derivation existed.
|
|
500
594
|
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
595
|
+
* WHAT THE CREDENTIAL SPLITS MOVED ACROSS THIS LINE, since the note that used to sit here said the opposite and
|
|
596
|
+
* was worth replacing rather than deleting. `capabilities.json` was `secret` and unversioned, and the sentence
|
|
597
|
+
* celebrated that the index therefore stopped copying capability tokens into search text. It is `versioned` now
|
|
598
|
+
* and searchable — and the guarantee is unchanged, because the tokens are not in the file any more. The floor
|
|
599
|
+
* moved from "keep the index away from the file that holds credentials" to "the file holds none", which is the
|
|
600
|
+
* stronger of the two: it also holds for the shell, which never consulted this list at all. `auth/` — where those
|
|
601
|
+
* values went, both vaults included — is still denied, and is the entry that was doing the real work all along. */
|
|
503
602
|
export const SEARCHABLE_STATE_PATHS: readonly string[] = WORKSPACE_STATE_FILES.filter((file) => file.versioned || file.authored).map(
|
|
504
603
|
(file) => file.path,
|
|
505
604
|
);
|