@intentic/sandbox-contract 1.126.0 → 1.133.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/contracts/activity.contract.d.ts +4 -4
- package/dist/contracts/activity.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +283 -252
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +4 -3
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +15 -13
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.d.ts +3 -3
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +21 -16
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/codex.contract.d.ts.map +1 -1
- package/dist/contracts/drafts.contract.d.ts +6 -6
- package/dist/contracts/drafts.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +4 -4
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.d.ts +6 -4
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/history.contract.d.ts +3 -3
- package/dist/contracts/history.contract.d.ts.map +1 -1
- package/dist/contracts/intentic.contract.d.ts.map +1 -1
- package/dist/contracts/inventory.contract.d.ts +20 -20
- package/dist/contracts/inventory.contract.d.ts.map +1 -1
- package/dist/contracts/logs.contract.d.ts.map +1 -1
- package/dist/contracts/panels.contract.d.ts +1 -1
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/ports.contract.d.ts +1 -1
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.d.ts +4 -4
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +1 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +26 -26
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +21 -21
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/effects.d.ts +1 -1
- package/dist/effects.d.ts.map +1 -1
- package/dist/effects.js +2 -0
- package/dist/effects.js.map +1 -1
- package/dist/events.d.ts +213 -29
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +5 -0
- package/dist/events.js.map +1 -1
- package/dist/hostnames.d.ts.map +1 -1
- package/dist/index.d.ts +417 -377
- package/dist/index.d.ts.map +1 -1
- package/dist/model-metadata.d.ts.map +1 -1
- package/dist/schemas.d.ts +221 -212
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +13 -6
- package/dist/schemas.js.map +1 -1
- package/dist/sse.d.ts.map +1 -1
- package/dist/tunnel-ids.d.ts.map +1 -1
- package/package.json +8 -6
- package/src/agent-catalog.ts +62 -0
- package/src/contracts/activity.contract.ts +9 -0
- package/src/contracts/agent.contract.ts +17 -0
- package/src/contracts/agents.contract.ts +28 -0
- package/src/contracts/automations.contract.ts +23 -0
- package/src/contracts/capabilities.contract.ts +31 -0
- package/src/contracts/claude.contract.ts +22 -0
- package/src/contracts/codex.contract.ts +23 -0
- package/src/contracts/drafts.contract.ts +11 -0
- package/src/contracts/extensions.contract.ts +28 -0
- package/src/contracts/git.contract.ts +64 -0
- package/src/contracts/grok.contract.ts +16 -0
- package/src/contracts/history.contract.ts +12 -0
- package/src/contracts/intentic.contract.ts +17 -0
- package/src/contracts/inventory.contract.ts +11 -0
- package/src/contracts/logs.contract.ts +9 -0
- package/src/contracts/panels.contract.ts +12 -0
- package/src/contracts/ports.contract.ts +11 -0
- package/src/contracts/secrets.contract.ts +15 -0
- package/src/contracts/sessions.contract.ts +14 -0
- package/src/contracts/settings.contract.ts +11 -0
- package/src/contracts/system.contract.ts +37 -0
- package/src/contracts/translator.contract.ts +20 -0
- package/src/contracts/workspace.contract.ts +74 -0
- package/src/effects.test.ts +132 -0
- package/src/effects.ts +138 -0
- package/src/events.ts +277 -0
- package/src/hostnames.test.ts +44 -0
- package/src/hostnames.ts +97 -0
- package/src/index.ts +84 -0
- package/src/model-metadata.ts +30 -0
- package/src/schemas.ts +1407 -0
- package/src/sse.ts +43 -0
- package/src/terminal-protocol.ts +16 -0
- package/src/tunnel-ids.ts +19 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import {
|
|
3
|
+
AccountIdSchema,
|
|
4
|
+
CodexDevicePollSchema,
|
|
5
|
+
CodexDeviceStartSchema,
|
|
6
|
+
CodexPollResultSchema,
|
|
7
|
+
ModelsSchema,
|
|
8
|
+
OauthAccountListSchema,
|
|
9
|
+
OkSchema,
|
|
10
|
+
} from "../schemas.js";
|
|
11
|
+
|
|
12
|
+
// ChatGPT (Codex) OAuth — OpenAI's device-code flow. `start` requests a one-time code + verification URL; the
|
|
13
|
+
// browser signs in and enters the code, while the UI polls `poll` (pending:true until sign-in completes, then
|
|
14
|
+
// the created account). The sandbox owns the credential (Codex's native auth.json, one dir per account).
|
|
15
|
+
// `accounts` lists the connected accounts; `disconnect` clears the one named by id.
|
|
16
|
+
export const codexContract = {
|
|
17
|
+
start: oc.route({ method: "POST", path: "/codex/oauth/start" }).output(CodexDeviceStartSchema),
|
|
18
|
+
poll: oc.route({ method: "POST", path: "/codex/oauth/poll" }).input(CodexDevicePollSchema).output(CodexPollResultSchema),
|
|
19
|
+
// OpenAI/Codex's live models for the model picker — the source of valid ids (see codex-models.ts).
|
|
20
|
+
models: oc.route({ method: "GET", path: "/codex/models" }).output(ModelsSchema),
|
|
21
|
+
accounts: oc.route({ method: "GET", path: "/codex/accounts" }).output(OauthAccountListSchema),
|
|
22
|
+
disconnect: oc.route({ method: "POST", path: "/codex/account/disconnect" }).input(AccountIdSchema).output(OkSchema),
|
|
23
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { DraftIdParamSchema, DraftsListSchema, DraftSummarySchema, OkSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// The sandbox's post-drafts queue (agent-proposed posts awaiting approval). The agent creates draft files
|
|
5
|
+
// directly — these routes are the OWNER's side: `list` is the approval inbox, `upsert` covers approve / edit /
|
|
6
|
+
// retry (all a plain re-post with a field changed, like the automations enabled toggle), `remove` is reject.
|
|
7
|
+
export const draftsContract = {
|
|
8
|
+
list: oc.route({ method: "GET", path: "/drafts" }).output(DraftsListSchema),
|
|
9
|
+
upsert: oc.route({ method: "POST", path: "/drafts" }).input(DraftSummarySchema).output(OkSchema),
|
|
10
|
+
remove: oc.route({ method: "DELETE", path: "/drafts/{id}" }).input(DraftIdParamSchema).output(OkSchema),
|
|
11
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import {
|
|
3
|
+
CapabilityIdParamSchema,
|
|
4
|
+
ExtensionProcessParamSchema,
|
|
5
|
+
ExtensionProcessStatusSchema,
|
|
6
|
+
ExtensionSettingsInputSchema,
|
|
7
|
+
ExtensionSettingsSchema,
|
|
8
|
+
ExtensionsListSchema,
|
|
9
|
+
OkSchema,
|
|
10
|
+
} from "../schemas.js";
|
|
11
|
+
|
|
12
|
+
// Installed extensions resolved to their approved manifests — what the web extension host boots from. The
|
|
13
|
+
// bundle itself is a plain Hono route (GET /extensions/{id}/bundle): raw ESM bytes are not an oRPC payload.
|
|
14
|
+
// `settings`/`setSettings` carry the extension's own contributes.settings values; keys the manifest never
|
|
15
|
+
// declared are refused, the same honesty rule the host applies to runtime view/command registrations.
|
|
16
|
+
export const extensionsContract = {
|
|
17
|
+
list: oc.route({ method: "GET", path: "/extensions" }).output(ExtensionsListSchema),
|
|
18
|
+
settings: oc.route({ method: "GET", path: "/extensions/{id}/settings" }).input(CapabilityIdParamSchema).output(ExtensionSettingsSchema),
|
|
19
|
+
setSettings: oc.route({ method: "POST", path: "/extensions/{id}/settings" }).input(ExtensionSettingsInputSchema).output(OkSchema),
|
|
20
|
+
// Declared background processes (contributes.processes): tmux-managed through the panel machinery
|
|
21
|
+
// (session `panel-ext-<id>-<name>`, PORT-assigned, optional tunneled preview route).
|
|
22
|
+
processStatus: oc
|
|
23
|
+
.route({ method: "GET", path: "/extensions/{id}/processes/{name}" })
|
|
24
|
+
.input(ExtensionProcessParamSchema)
|
|
25
|
+
.output(ExtensionProcessStatusSchema),
|
|
26
|
+
processStart: oc.route({ method: "POST", path: "/extensions/{id}/processes/{name}/start" }).input(ExtensionProcessParamSchema).output(OkSchema),
|
|
27
|
+
processStop: oc.route({ method: "POST", path: "/extensions/{id}/processes/{name}/stop" }).input(ExtensionProcessParamSchema).output(OkSchema),
|
|
28
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import {
|
|
3
|
+
CommitResultSchema,
|
|
4
|
+
CommitSchema,
|
|
5
|
+
DiscardSchema,
|
|
6
|
+
FileDiffSchema,
|
|
7
|
+
GitActionResultSchema,
|
|
8
|
+
GitBranchCreateSchema,
|
|
9
|
+
GitChangesSchema,
|
|
10
|
+
GitCheckoutSchema,
|
|
11
|
+
GitCommitActionSchema,
|
|
12
|
+
GitCommitDiffQuerySchema,
|
|
13
|
+
GitCommitDiffSchema,
|
|
14
|
+
GitCommitFileDiffQuerySchema,
|
|
15
|
+
GitFileDiffQuerySchema,
|
|
16
|
+
GitFileQuerySchema,
|
|
17
|
+
GitFileSchema,
|
|
18
|
+
GitFilesSchema,
|
|
19
|
+
GitFileWriteSchema,
|
|
20
|
+
GitLogQuerySchema,
|
|
21
|
+
GitLogSchema,
|
|
22
|
+
GitReposSchema,
|
|
23
|
+
GitResetSchema,
|
|
24
|
+
GitStatusSchema,
|
|
25
|
+
GitTagCreateSchema,
|
|
26
|
+
OkSchema,
|
|
27
|
+
PushSchema,
|
|
28
|
+
RepoParamSchema,
|
|
29
|
+
} from "../schemas.js";
|
|
30
|
+
|
|
31
|
+
// Per-repo git ops over the workspace repos: "root" (the /work repo) plus every discovered repo under /work
|
|
32
|
+
// ({repo} is the repo's root-relative dir, URL-encoded). An unknown {repo} is a handler-thrown
|
|
33
|
+
// NOT_FOUND; a path that escapes the repo is a BAD_REQUEST. `changes` is the workspace-wide review set the
|
|
34
|
+
// Changes panel renders; commit/discard take optional `paths` for per-file actions.
|
|
35
|
+
export const gitContract = {
|
|
36
|
+
changes: oc.route({ method: "GET", path: "/git/changes" }).output(GitChangesSchema),
|
|
37
|
+
// The git-history graph over one repo's real commits: the repo list (for the tree affordance + switcher),
|
|
38
|
+
// one repo's commit log, and lazy per-commit detail (changed files, then a file's before/after AT the
|
|
39
|
+
// commit). Read-only — commit/discard on the working tree stay the write path (above).
|
|
40
|
+
repos: oc.route({ method: "GET", path: "/git/repos" }).output(GitReposSchema),
|
|
41
|
+
log: oc.route({ method: "GET", path: "/git/{repo}/log" }).input(GitLogQuerySchema).output(GitLogSchema),
|
|
42
|
+
commitDiff: oc.route({ method: "GET", path: "/git/{repo}/commit-diff" }).input(GitCommitDiffQuerySchema).output(GitCommitDiffSchema),
|
|
43
|
+
commitFileDiff: oc.route({ method: "GET", path: "/git/{repo}/commit-file-diff" }).input(GitCommitFileDiffQuerySchema).output(FileDiffSchema),
|
|
44
|
+
// Write actions from the graph's commit context menu (VSCode "Git Graph" parity). Non-destructive refs
|
|
45
|
+
// (branch/tag) return Ok and let git's errors propagate; the sequence + HEAD-moving ops return a
|
|
46
|
+
// GitActionResult so a conflict/clean-apply failure is a value, not a 500. Read routes above.
|
|
47
|
+
createBranch: oc.route({ method: "POST", path: "/git/{repo}/branch" }).input(GitBranchCreateSchema).output(OkSchema),
|
|
48
|
+
createTag: oc.route({ method: "POST", path: "/git/{repo}/tag" }).input(GitTagCreateSchema).output(OkSchema),
|
|
49
|
+
checkout: oc.route({ method: "POST", path: "/git/{repo}/checkout" }).input(GitCheckoutSchema).output(GitActionResultSchema),
|
|
50
|
+
cherryPick: oc.route({ method: "POST", path: "/git/{repo}/cherry-pick" }).input(GitCommitActionSchema).output(GitActionResultSchema),
|
|
51
|
+
revert: oc.route({ method: "POST", path: "/git/{repo}/revert" }).input(GitCommitActionSchema).output(GitActionResultSchema),
|
|
52
|
+
drop: oc.route({ method: "POST", path: "/git/{repo}/drop" }).input(GitCommitActionSchema).output(GitActionResultSchema),
|
|
53
|
+
merge: oc.route({ method: "POST", path: "/git/{repo}/merge" }).input(GitCommitActionSchema).output(GitActionResultSchema),
|
|
54
|
+
rebase: oc.route({ method: "POST", path: "/git/{repo}/rebase" }).input(GitCommitActionSchema).output(GitActionResultSchema),
|
|
55
|
+
reset: oc.route({ method: "POST", path: "/git/{repo}/reset" }).input(GitResetSchema).output(GitActionResultSchema),
|
|
56
|
+
fileDiff: oc.route({ method: "GET", path: "/git/{repo}/file-diff" }).input(GitFileDiffQuerySchema).output(FileDiffSchema),
|
|
57
|
+
status: oc.route({ method: "GET", path: "/git/{repo}/status" }).input(RepoParamSchema).output(GitStatusSchema),
|
|
58
|
+
commit: oc.route({ method: "POST", path: "/git/{repo}/commit" }).input(CommitSchema).output(CommitResultSchema),
|
|
59
|
+
discard: oc.route({ method: "POST", path: "/git/{repo}/discard" }).input(DiscardSchema).output(OkSchema),
|
|
60
|
+
push: oc.route({ method: "POST", path: "/git/{repo}/push" }).input(PushSchema).output(OkSchema),
|
|
61
|
+
files: oc.route({ method: "GET", path: "/git/{repo}/files" }).input(RepoParamSchema).output(GitFilesSchema),
|
|
62
|
+
readFile: oc.route({ method: "GET", path: "/git/{repo}/file" }).input(GitFileQuerySchema).output(GitFileSchema),
|
|
63
|
+
writeFile: oc.route({ method: "PUT", path: "/git/{repo}/file" }).input(GitFileWriteSchema).output(OkSchema),
|
|
64
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { AccountIdSchema, DeviceStartSchema, ModelsSchema, OauthAccountListSchema, OkSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// xAI Grok (via OpenCode) uses subscription OAuth — the sandbox owns the credential (OpenCode persists the
|
|
5
|
+
// tokens and refreshes them). `start` authorizes xAI's headless device-code method and returns the verification
|
|
6
|
+
// URL + instructions (which carry the one-time code the user enters at x.ai); OpenCode then polls to completion
|
|
7
|
+
// — there is no paste-back — and the UI polls `accounts` until connected. `disconnect` clears the tokens.
|
|
8
|
+
// ponytail: OpenCode holds one xAI auth per data dir, so `accounts` is 0 or 1 — the list shape matches the
|
|
9
|
+
// other providers without paying for per-account OpenCode servers yet.
|
|
10
|
+
export const grokContract = {
|
|
11
|
+
start: oc.route({ method: "POST", path: "/grok/oauth/start" }).output(DeviceStartSchema),
|
|
12
|
+
// xAI's live models (via OpenCode) for the model picker — the source of truth for valid model ids.
|
|
13
|
+
models: oc.route({ method: "GET", path: "/grok/models" }).output(ModelsSchema),
|
|
14
|
+
accounts: oc.route({ method: "GET", path: "/grok/accounts" }).output(OauthAccountListSchema),
|
|
15
|
+
disconnect: oc.route({ method: "POST", path: "/grok/account/disconnect" }).input(AccountIdSchema).output(OkSchema),
|
|
16
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { FileDiffSchema, OkSchema, SnapshotDiffSchema, SnapshotFileDiffQuerySchema, SnapshotIdSchema, SnapshotsListSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// Workspace history: daemon-owned snapshots of /work with diff + restore. `diff` compares a checkpoint against
|
|
5
|
+
// the previous visible checkpoint (everything that happened since it, hidden interval captures included); an
|
|
6
|
+
// unknown id is a handler-thrown NOT_FOUND.
|
|
7
|
+
export const historyContract = {
|
|
8
|
+
list: oc.route({ method: "GET", path: "/history/snapshots" }).output(SnapshotsListSchema),
|
|
9
|
+
diff: oc.route({ method: "GET", path: "/history/diff" }).input(SnapshotIdSchema).output(SnapshotDiffSchema),
|
|
10
|
+
fileDiff: oc.route({ method: "GET", path: "/history/file-diff" }).input(SnapshotFileDiffQuerySchema).output(FileDiffSchema),
|
|
11
|
+
restore: oc.route({ method: "POST", path: "/history/restore" }).input(SnapshotIdSchema).output(OkSchema),
|
|
12
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { eventIterator, oc } from "@orpc/contract";
|
|
2
|
+
import { IntenticLineSchema } from "../events.js";
|
|
3
|
+
import { IntenticRunSchema, OkSchema } from "../schemas.js";
|
|
4
|
+
|
|
5
|
+
// Run the in-sandbox intentic CLI (resolve/plan/apply/deployments/…) and stream its ndjson lines as they
|
|
6
|
+
// arrive, so the UI sees live progress. A non-zero exit surfaces as a thrown error once the stream ends.
|
|
7
|
+
export const intenticContract = {
|
|
8
|
+
run: oc.route({ method: "POST", path: "/intentic" }).input(IntenticRunSchema).output(eventIterator(IntenticLineSchema)),
|
|
9
|
+
// Launch the minutes-long apply → adopt reconcile as a one-shot tmux job (session panel-infra-apply) and
|
|
10
|
+
// return immediately — progress is followed by attaching the terminal, not by holding this request open.
|
|
11
|
+
apply: oc.route({ method: "POST", path: "/intentic/apply" }).output(OkSchema),
|
|
12
|
+
// Tail the running (or just-finished) apply's structured event stream — the same ndjson lines the tmux pane
|
|
13
|
+
// renders as text, persisted to a durable file so the UI shows per-resource progress that survives a page
|
|
14
|
+
// refresh. Replays from the run's {kind:"start"} then follows live, closing on {kind:"exit"}. GET, like
|
|
15
|
+
// /events, because it takes no input; reuses the loose IntenticLine shape so no new schema is needed.
|
|
16
|
+
applyEvents: oc.route({ method: "GET", path: "/intentic/apply/events" }).output(eventIterator(IntenticLineSchema)),
|
|
17
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { AddInventoryInputSchema, InventoryListSchema, InventoryNameParamSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// The i.have.* / i.want.service entries in deploy.config.ts's managed region. add/remove rewrite the region and
|
|
5
|
+
// commit it (mirroring an agent edit) and return the full updated list so the UI re-renders from one response.
|
|
6
|
+
// Deploy-target hosts self-register out-of-band via the daemon's plain /enroll route (connect-host script).
|
|
7
|
+
export const inventoryContract = {
|
|
8
|
+
list: oc.route({ method: "GET", path: "/inventory" }).output(InventoryListSchema),
|
|
9
|
+
add: oc.route({ method: "POST", path: "/inventory" }).input(AddInventoryInputSchema).output(InventoryListSchema),
|
|
10
|
+
remove: oc.route({ method: "DELETE", path: "/inventory/{name}" }).input(InventoryNameParamSchema).output(InventoryListSchema),
|
|
11
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { LogReadQuerySchema, LogReadSchema, LogsListSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// Daemon-owned debug logs (historyRoot/logs): terminal pipe-pane captures, intentic CLI run logs, daemon.log.
|
|
5
|
+
// Read-only by design — the files are written by the daemon/tmux only, so the record stays trustworthy.
|
|
6
|
+
export const logsContract = {
|
|
7
|
+
list: oc.route({ method: "GET", path: "/logs" }).output(LogsListSchema),
|
|
8
|
+
read: oc.route({ method: "GET", path: "/logs/file" }).input(LogReadQuerySchema).output(LogReadSchema),
|
|
9
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { OkSchema, PanelRepoParamSchema, PanelsListSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// Per-repository dev servers + the content facts the web app's extensions detect on. `list` enumerates every
|
|
5
|
+
// repo with its runtime status + facts; `start`/`stop` drive the repo's dev server. Their tmux sessions list
|
|
6
|
+
// on the global GET /system/terminals (the web app's one terminal panel); the interactive I/O is the
|
|
7
|
+
// /system/terminal WebSocket.
|
|
8
|
+
export const panelsContract = {
|
|
9
|
+
list: oc.route({ method: "GET", path: "/panels" }).output(PanelsListSchema),
|
|
10
|
+
start: oc.route({ method: "POST", path: "/panels/{repo}/start" }).input(PanelRepoParamSchema).output(OkSchema),
|
|
11
|
+
stop: oc.route({ method: "POST", path: "/panels/{repo}/stop" }).input(PanelRepoParamSchema).output(OkSchema),
|
|
12
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { OkSchema, PortForwardResultSchema, PortParamSchema, PortsListSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// Listening TCP ports in the sandbox + explicit forwarding through the preview proxy (see the ports section in
|
|
5
|
+
// schemas.ts). `forward` is idempotent — re-forwarding a port returns its existing slot's URL; `unforward`
|
|
6
|
+
// frees the slot immediately (the hostname keeps resolving, the proxy just stops mapping it).
|
|
7
|
+
export const portsContract = {
|
|
8
|
+
list: oc.route({ method: "GET", path: "/ports" }).output(PortsListSchema),
|
|
9
|
+
forward: oc.route({ method: "POST", path: "/ports/forward" }).input(PortParamSchema).output(PortForwardResultSchema),
|
|
10
|
+
unforward: oc.route({ method: "POST", path: "/ports/unforward" }).input(PortParamSchema).output(OkSchema),
|
|
11
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { OkSchema, SecretInventorySchema, SecretKeyParamSchema, SecretKeysSchema, SecretRevealSchema, SecretSetSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// User-supplied env-var secrets, written to the sandbox's gitignored desired-state/.env (which
|
|
5
|
+
// `apply` reloads each run — no restart). `set` upserts one KEY=value, `remove` deletes it; `list` returns the
|
|
6
|
+
// keys present. `inventory` aggregates every secret store into one view (keys + status + provenance, never
|
|
7
|
+
// values). `reveal` is the single value-returning route — owner-only, POST so the key never sits in a URL.
|
|
8
|
+
// set/remove/list/reveal refuse until DevOps has scaffolded the desired-state repo; inventory always answers.
|
|
9
|
+
export const secretsContract = {
|
|
10
|
+
set: oc.route({ method: "POST", path: "/secrets" }).input(SecretSetSchema).output(OkSchema),
|
|
11
|
+
list: oc.route({ method: "GET", path: "/secrets" }).output(SecretKeysSchema),
|
|
12
|
+
remove: oc.route({ method: "DELETE", path: "/secrets/{key}" }).input(SecretKeyParamSchema).output(OkSchema),
|
|
13
|
+
inventory: oc.route({ method: "GET", path: "/secrets/inventory" }).output(SecretInventorySchema),
|
|
14
|
+
reveal: oc.route({ method: "POST", path: "/secrets/reveal" }).input(SecretKeyParamSchema).output(SecretRevealSchema),
|
|
15
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { SessionIdParamSchema, SessionsListSchema, SessionTranscriptSchema } from "../schemas.js";
|
|
4
|
+
|
|
5
|
+
// Past conversations in this workspace (the SDK-native session store, keyed on the workspace dir). `list`
|
|
6
|
+
// returns summaries for the history menu (filtered by `query` when the search box is used); `get` restores one
|
|
7
|
+
// transcript for display.
|
|
8
|
+
export const sessionsContract = {
|
|
9
|
+
list: oc
|
|
10
|
+
.route({ method: "GET", path: "/sessions" })
|
|
11
|
+
.input(z.object({ query: z.string().optional() }))
|
|
12
|
+
.output(SessionsListSchema),
|
|
13
|
+
get: oc.route({ method: "GET", path: "/sessions/{id}" }).input(SessionIdParamSchema).output(SessionTranscriptSchema),
|
|
14
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { CleanerSavingsSchema, OkSchema, SandboxSettingsSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// Per-sandbox agent settings (.intentic/settings.json). `get` returns the current flags with defaults applied
|
|
5
|
+
// when the file is absent; `set` overwrites them. `savings` reports the output-cleaner token savings aggregated
|
|
6
|
+
// from the live filter-stats ledger (the rtk-`gain` surface — read-only, so a UI card can show what's working).
|
|
7
|
+
export const settingsContract = {
|
|
8
|
+
get: oc.route({ method: "GET", path: "/settings" }).output(SandboxSettingsSchema),
|
|
9
|
+
set: oc.route({ method: "POST", path: "/settings" }).input(SandboxSettingsSchema).output(OkSchema),
|
|
10
|
+
savings: oc.route({ method: "GET", path: "/settings/savings" }).output(CleanerSavingsSchema),
|
|
11
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { eventIterator, oc } from "@orpc/contract";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { SystemEventSchema } from "../events.js";
|
|
4
|
+
import {
|
|
5
|
+
HostTunnelInputSchema,
|
|
6
|
+
HostTunnelSchema,
|
|
7
|
+
InfoSchema,
|
|
8
|
+
OkSchema,
|
|
9
|
+
PresenceReportSchema,
|
|
10
|
+
TerminalNameParamSchema,
|
|
11
|
+
TerminalsListSchema,
|
|
12
|
+
UsageSummarySchema,
|
|
13
|
+
} from "../schemas.js";
|
|
14
|
+
|
|
15
|
+
// Sandbox status + identity, the long-lived liveness stream, and the connect-token-relayed host tunnel.
|
|
16
|
+
// `events` interleaves heartbeat frames (browser holds it open to detect the sandbox dying instantly) with
|
|
17
|
+
// workspaceChanged batches (live file tree/viewer refresh) and presence roster snapshots until the request
|
|
18
|
+
// aborts. `clientId` is the tab's per-connection presence key; absent (an older web client) means the
|
|
19
|
+
// connection simply never joins the roster.
|
|
20
|
+
export const systemContract = {
|
|
21
|
+
info: oc.route({ method: "GET", path: "/info" }).output(InfoSchema),
|
|
22
|
+
events: oc
|
|
23
|
+
.route({ method: "GET", path: "/events" })
|
|
24
|
+
.input(z.object({ clientId: z.string().optional() }))
|
|
25
|
+
.output(eventIterator(SystemEventSchema)),
|
|
26
|
+
// A tab's activity self-report (view/session/file/idle), fanned back out to every member on /events.
|
|
27
|
+
presence: oc.route({ method: "POST", path: "/system/presence" }).input(PresenceReportSchema).output(OkSchema),
|
|
28
|
+
hostTunnel: oc.route({ method: "POST", path: "/system/host-tunnel" }).input(HostTunnelInputSchema).output(HostTunnelSchema),
|
|
29
|
+
// Per-account token/cost totals, aggregated from the activity log's turn.completed events.
|
|
30
|
+
usage: oc.route({ method: "GET", path: "/system/usage" }).output(UsageSummarySchema),
|
|
31
|
+
// The web-owned tmux sessions behind the terminal tabs. `terminals` enumerates them (the panel rebuilds a tab
|
|
32
|
+
// per name on load/reload); `killTerminal` destroys one when its tab's close button is clicked. The live I/O
|
|
33
|
+
// is the separate /system/terminal WebSocket — these are just the control plane. Bearer-authed like the rest
|
|
34
|
+
// (browser fetch sends the header), unlike the header-less WS route which app.ts exempts.
|
|
35
|
+
terminals: oc.route({ method: "GET", path: "/system/terminals" }).output(TerminalsListSchema),
|
|
36
|
+
killTerminal: oc.route({ method: "DELETE", path: "/system/terminals/{name}" }).input(TerminalNameParamSchema).output(OkSchema),
|
|
37
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { DeviceStartSchema, KeyedProviderSchema, OkSchema, TranslatorAccountsSchema } from "../schemas.js";
|
|
4
|
+
|
|
5
|
+
// Routed-provider subscriptions (Sandbox ▸ Agent). The bundled translator (CLIProxyAPI) runs a non-Claude model
|
|
6
|
+
// UNDER the Claude Code harness on the user's SUBSCRIPTION, so each provider connects via a device-code OAuth
|
|
7
|
+
// login (no API key). `accounts` reports which subscriptions are connected; `connect` starts a device login and
|
|
8
|
+
// returns the verification URL + one-time code (the translator polls to completion in the background, so the UI
|
|
9
|
+
// polls `accounts` until connected — there is no paste-back); `disconnect` clears a provider's tokens.
|
|
10
|
+
export const translatorContract = {
|
|
11
|
+
accounts: oc.route({ method: "GET", path: "/translator/accounts" }).output(TranslatorAccountsSchema),
|
|
12
|
+
connect: oc
|
|
13
|
+
.route({ method: "POST", path: "/translator/{provider}/connect" })
|
|
14
|
+
.input(z.object({ provider: KeyedProviderSchema }))
|
|
15
|
+
.output(DeviceStartSchema),
|
|
16
|
+
disconnect: oc
|
|
17
|
+
.route({ method: "POST", path: "/translator/{provider}/disconnect" })
|
|
18
|
+
.input(z.object({ provider: KeyedProviderSchema }))
|
|
19
|
+
.output(OkSchema),
|
|
20
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import {
|
|
3
|
+
AddAppsSchema,
|
|
4
|
+
AppParamSchema,
|
|
5
|
+
AppsListSchema,
|
|
6
|
+
CloneRepoSchema,
|
|
7
|
+
CloneResultSchema,
|
|
8
|
+
WorkspaceSearchResultSchema,
|
|
9
|
+
WorkspaceSearchQuerySchema,
|
|
10
|
+
OkSchema,
|
|
11
|
+
RepoAppsParamSchema,
|
|
12
|
+
ReposListSchema,
|
|
13
|
+
RunTestsSchema,
|
|
14
|
+
TemplatesListSchema,
|
|
15
|
+
WorkspaceChildrenQuerySchema,
|
|
16
|
+
WorkspaceChildrenSchema,
|
|
17
|
+
WorkspaceClassificationSchema,
|
|
18
|
+
WorkspaceDirSchema,
|
|
19
|
+
WorkspaceFileQuerySchema,
|
|
20
|
+
WorkspaceFileSchema,
|
|
21
|
+
WorkspaceGraphSchema,
|
|
22
|
+
WorkspaceMoveSchema,
|
|
23
|
+
WorkspaceSyncSchema,
|
|
24
|
+
WorkspaceTreeSchema,
|
|
25
|
+
} from "../schemas.js";
|
|
26
|
+
|
|
27
|
+
// The full /work view + extra-repo cloning. The binary preview (/workspace/raw) is intentionally NOT here — it
|
|
28
|
+
// stays a plain Hono route serving raw bytes with a Content-Type header (oRPC's request/response shape doesn't
|
|
29
|
+
// fit a streamed binary body). External MCP tools moved to the unified capabilities manifest (mcp kind).
|
|
30
|
+
export const workspaceContract = {
|
|
31
|
+
tree: oc.route({ method: "GET", path: "/workspace/tree" }).output(WorkspaceTreeSchema),
|
|
32
|
+
// Lazy-load one directory's children — the tree returns ignored dirs (node_modules, .git, …) without children,
|
|
33
|
+
// and the client fetches them here on expand so a giant node_modules can't blow the tree walk's entry budget.
|
|
34
|
+
children: oc.route({ method: "GET", path: "/workspace/children" }).input(WorkspaceChildrenQuerySchema).output(WorkspaceChildrenSchema),
|
|
35
|
+
file: oc.route({ method: "GET", path: "/workspace/file" }).input(WorkspaceFileQuerySchema).output(WorkspaceFileSchema),
|
|
36
|
+
// Ranked groups, match-reason tags, freshness, resumable cursor. `mode` narrows to one verb; default is
|
|
37
|
+
// auto-mode fusion. (Implementation detail: the daemon backs this with a resident in-process iq engine.)
|
|
38
|
+
search: oc.route({ method: "GET", path: "/workspace/search" }).input(WorkspaceSearchQuerySchema).output(WorkspaceSearchResultSchema),
|
|
39
|
+
// Deterministic, no-LLM classification of the dropped workspace into coarse buckets (repositories / documents
|
|
40
|
+
// / media / archives / other). Read-only proposal: the browser renders it and applies accepted moves via the
|
|
41
|
+
// existing /workspace/move route — this route never touches the tree.
|
|
42
|
+
classify: oc.route({ method: "GET", path: "/workspace/classify" }).output(WorkspaceClassificationSchema),
|
|
43
|
+
// Direct file management the browser drives against the /work tree (byte writes go through POST
|
|
44
|
+
// /workspace/upload). oRPC's OpenAPI codec reads non-GET input from the JSON body, so delete sends {path}
|
|
45
|
+
// in the body too (not the query) — same as the POST routes.
|
|
46
|
+
mkdir: oc.route({ method: "POST", path: "/workspace/dir" }).input(WorkspaceDirSchema).output(OkSchema),
|
|
47
|
+
delete: oc.route({ method: "DELETE", path: "/workspace/entry" }).input(WorkspaceFileQuerySchema).output(OkSchema),
|
|
48
|
+
move: oc.route({ method: "POST", path: "/workspace/move" }).input(WorkspaceMoveSchema).output(OkSchema),
|
|
49
|
+
copy: oc.route({ method: "POST", path: "/workspace/copy" }).input(WorkspaceMoveSchema).output(OkSchema),
|
|
50
|
+
repos: oc.route({ method: "GET", path: "/workspace/repos" }).output(ReposListSchema),
|
|
51
|
+
addRepo: oc.route({ method: "POST", path: "/workspace/repos" }).input(CloneRepoSchema).output(CloneResultSchema),
|
|
52
|
+
// Force-fetch + guarded fast-forward every repo with a remote (mutates the tree ⇒ POST). The turn hook syncs
|
|
53
|
+
// automatically each turn; this is the on-demand refresh (and how you re-sync a dirty/diverged repo after committing).
|
|
54
|
+
sync: oc.route({ method: "POST", path: "/workspace/sync" }).output(WorkspaceSyncSchema),
|
|
55
|
+
// The addable app types the configured source repo offers (its templates.json) — drives the apps
|
|
56
|
+
// extension's Add-app picker.
|
|
57
|
+
templates: oc.route({ method: "GET", path: "/workspace/templates" }).output(TemplatesListSchema),
|
|
58
|
+
// Per-monorepo apps, driven by the web app's apps extension (owner-authed; {repo} is validated in the
|
|
59
|
+
// handler): add one or more apps into an existing monorepo, list them with per-app preview URL + status,
|
|
60
|
+
// and start/stop each app's preview dev server. `addApps` kicks off a one-shot tmux job (session
|
|
61
|
+
// panel-<repo>--add_apps) that runs `intentic add-app` — the attachable terminal is the progress/error
|
|
62
|
+
// surface; the extension polls the session's `running` for completion. It returns immediately (an ack).
|
|
63
|
+
addApps: oc.route({ method: "POST", path: "/workspace/repos/{repo}/apps" }).input(AddAppsSchema).output(OkSchema),
|
|
64
|
+
appsList: oc.route({ method: "GET", path: "/workspace/repos/{repo}/apps" }).input(RepoAppsParamSchema).output(AppsListSchema),
|
|
65
|
+
// The monorepo's workspace package dependency graph (pnpm-workspace.yaml globs + per-package package.json
|
|
66
|
+
// workspace deps) — drives the apps extension's Dependencies view.
|
|
67
|
+
packageGraph: oc.route({ method: "GET", path: "/workspace/repos/{repo}/graph" }).input(RepoAppsParamSchema).output(WorkspaceGraphSchema),
|
|
68
|
+
startApp: oc.route({ method: "POST", path: "/workspace/repos/{repo}/apps/{app}/start" }).input(AppParamSchema).output(OkSchema),
|
|
69
|
+
stopApp: oc.route({ method: "POST", path: "/workspace/repos/{repo}/apps/{app}/stop" }).input(AppParamSchema).output(OkSchema),
|
|
70
|
+
// Run vitest for the given repo-relative project dirs in a one-shot tmux panel session
|
|
71
|
+
// (panel-<repo>--<session>) — drives the apps extension's per-app / per-package / library Run-tests actions.
|
|
72
|
+
// Mirrors addApps: it returns an ack; the attachable terminal is the result surface.
|
|
73
|
+
runTests: oc.route({ method: "POST", path: "/workspace/repos/{repo}/tests" }).input(RunTestsSchema).output(OkSchema),
|
|
74
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { ConnectorContribution, ExtensionManifest } from "@intentic/extension-api";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { capabilityEffects } from "./effects.js";
|
|
4
|
+
import { CapabilityKindSchema } from "./schemas.js";
|
|
5
|
+
|
|
6
|
+
const connector = (overrides?: Partial<ConnectorContribution>): ConnectorContribution => ({
|
|
7
|
+
provider: "github",
|
|
8
|
+
kind: "cli",
|
|
9
|
+
catalog: { name: "GitHub", description: "Issues and PRs.", category: "code" },
|
|
10
|
+
fields: [{ key: "token", label: "Token", secret: true }],
|
|
11
|
+
env: { GITHUB_TOKEN: "${token}" },
|
|
12
|
+
skill: "skills/github/SKILL.md",
|
|
13
|
+
...overrides,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const manifest = (contributes: ExtensionManifest["contributes"]): ExtensionManifest => ({
|
|
17
|
+
publisher: "acme",
|
|
18
|
+
name: "tools",
|
|
19
|
+
version: "1.0.0",
|
|
20
|
+
engines: { intentic: "^0.2.0" },
|
|
21
|
+
contributes,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("capabilityEffects", () => {
|
|
25
|
+
// Pins the CapabilityKind union to the deriver: a 13th kind must declare its effects to pass.
|
|
26
|
+
it("yields at least one effect for every kind", () => {
|
|
27
|
+
for (const kind of CapabilityKindSchema.options) {
|
|
28
|
+
expect(capabilityEffects({ kind, config: {} }).length).toBeGreaterThan(0);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("interpolates the plugin clone url and token from form state and from the echo", () => {
|
|
33
|
+
expect(capabilityEffects({ kind: "plugin", config: {} })).toEqual([{ kind: "clone", url: undefined }]);
|
|
34
|
+
expect(capabilityEffects({ kind: "plugin", config: { url: "https://github.com/o/p", token: "t" } })).toEqual([
|
|
35
|
+
{ kind: "clone", url: "https://github.com/o/p" },
|
|
36
|
+
{ kind: "secret", exposure: "disk" },
|
|
37
|
+
]);
|
|
38
|
+
expect(capabilityEffects({ kind: "plugin", config: { url: "https://github.com/o/p", hasToken: true } })).toContainEqual({
|
|
39
|
+
kind: "secret",
|
|
40
|
+
exposure: "disk",
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("stores an mcp token only when one is present", () => {
|
|
45
|
+
expect(capabilityEffects({ kind: "mcp", config: { url: "https://mcp.example.com" } })).toEqual([{ kind: "mcp" }]);
|
|
46
|
+
expect(capabilityEffects({ kind: "mcp", config: { url: "https://mcp.example.com", token: "t" } })).toContainEqual({
|
|
47
|
+
kind: "secret",
|
|
48
|
+
exposure: "disk",
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("always stores an ssh credential on disk, for both auth modes", () => {
|
|
53
|
+
for (const auth of ["key", "password"]) {
|
|
54
|
+
expect(capabilityEffects({ kind: "ssh", config: { auth } })).toEqual([
|
|
55
|
+
{ kind: "secret", exposure: "disk" },
|
|
56
|
+
{ kind: "skill", name: "ssh" },
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("scaffolds the monorepo under the instance name", () => {
|
|
62
|
+
expect(capabilityEffects({ kind: "monorepo", id: "shop", config: {} })).toEqual([{ kind: "scaffold", repos: ["shop"] }]);
|
|
63
|
+
expect(capabilityEffects({ kind: "monorepo", config: {} })).toEqual([{ kind: "scaffold", repos: [] }]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("derives cli secret and image from the connector spec", () => {
|
|
67
|
+
const github = capabilityEffects({ kind: "cli", id: "github", config: { provider: "github" }, connector: connector() });
|
|
68
|
+
expect(github).toEqual([
|
|
69
|
+
{ kind: "skill", name: "github" },
|
|
70
|
+
{ kind: "secret", exposure: "agent-env" },
|
|
71
|
+
]);
|
|
72
|
+
const postgres = capabilityEffects({
|
|
73
|
+
kind: "cli",
|
|
74
|
+
id: "db",
|
|
75
|
+
config: { provider: "postgres" },
|
|
76
|
+
connector: connector({ provider: "postgres", fragment: "env/postgres.Dockerfile" }),
|
|
77
|
+
});
|
|
78
|
+
expect(postgres).toContainEqual({ kind: "image" });
|
|
79
|
+
// Discord's whisper fragment is connector spec data like any other — the image effect derives from it.
|
|
80
|
+
const discord = capabilityEffects({
|
|
81
|
+
kind: "cli",
|
|
82
|
+
id: "discord",
|
|
83
|
+
config: { provider: "discord" },
|
|
84
|
+
connector: connector({ provider: "discord", fragment: "env/whisper.Dockerfile" }),
|
|
85
|
+
});
|
|
86
|
+
expect(discord).toContainEqual({ kind: "image" });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("falls back to the echoed hasSecret when no connector spec is at hand", () => {
|
|
90
|
+
expect(capabilityEffects({ kind: "cli", id: "github", config: { provider: "github", hasSecret: true } })).toContainEqual({
|
|
91
|
+
kind: "secret",
|
|
92
|
+
exposure: "agent-env",
|
|
93
|
+
});
|
|
94
|
+
expect(capabilityEffects({ kind: "cli", id: "github", config: { provider: "github" } })).toEqual([{ kind: "skill", name: "github" }]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("derives extension process and image effects from the installed manifest", () => {
|
|
98
|
+
const bare = capabilityEffects({ kind: "extension", config: { url: "https://github.com/o/e" } });
|
|
99
|
+
expect(bare).toEqual([{ kind: "trusted-code" }, { kind: "clone", url: "https://github.com/o/e" }]);
|
|
100
|
+
const full = capabilityEffects({
|
|
101
|
+
kind: "extension",
|
|
102
|
+
config: { url: "https://github.com/o/e", hasToken: true },
|
|
103
|
+
manifest: manifest({
|
|
104
|
+
processes: [{ name: "gateway", command: "node gateway.js" }],
|
|
105
|
+
environment: { fragment: "env/tools.Dockerfile" },
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
expect(full).toContainEqual({ kind: "secret", exposure: "disk" });
|
|
109
|
+
expect(full).toContainEqual({ kind: "image" });
|
|
110
|
+
expect(full).toContainEqual({ kind: "process", names: ["gateway"] });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("marks vpn as a privileged-runtime image change", () => {
|
|
114
|
+
expect(capabilityEffects({ kind: "vpn", config: {} })).toContainEqual({ kind: "runtime", level: "net-admin" });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("marks docker as a fully privileged runtime rebuild running dockerd", () => {
|
|
118
|
+
expect(capabilityEffects({ kind: "docker", config: {} })).toEqual([
|
|
119
|
+
{ kind: "image" },
|
|
120
|
+
{ kind: "runtime", level: "privileged" },
|
|
121
|
+
{ kind: "process", names: ["dockerd"] },
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("keeps a browser profile per platform", () => {
|
|
126
|
+
expect(capabilityEffects({ kind: "browser", id: "reddit", config: { platform: "reddit" } })).toEqual([
|
|
127
|
+
{ kind: "skill", name: "reddit" },
|
|
128
|
+
{ kind: "image" },
|
|
129
|
+
{ kind: "profile", platform: "reddit" },
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
});
|