@intentic/sandbox-contract 1.175.0 → 1.176.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.
Files changed (57) hide show
  1. package/dist/agent-catalog.d.ts +4 -0
  2. package/dist/agent-catalog.d.ts.map +1 -1
  3. package/dist/agent-catalog.js +7 -0
  4. package/dist/agent-catalog.js.map +1 -1
  5. package/dist/contracts/capabilities.contract.d.ts +42 -0
  6. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  7. package/dist/contracts/endpoints.contract.d.ts +18 -0
  8. package/dist/contracts/endpoints.contract.d.ts.map +1 -0
  9. package/dist/contracts/endpoints.contract.js +6 -0
  10. package/dist/contracts/endpoints.contract.js.map +1 -0
  11. package/dist/contracts/extensions.contract.d.ts +4 -0
  12. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  13. package/dist/contracts/git.contract.d.ts +5 -1
  14. package/dist/contracts/git.contract.d.ts.map +1 -1
  15. package/dist/contracts/host.contract.d.ts +36 -0
  16. package/dist/contracts/host.contract.d.ts.map +1 -0
  17. package/dist/contracts/host.contract.js +10 -0
  18. package/dist/contracts/host.contract.js.map +1 -0
  19. package/dist/contracts/settings.contract.d.ts +6 -4
  20. package/dist/contracts/settings.contract.d.ts.map +1 -1
  21. package/dist/contracts/system.contract.d.ts +14 -14
  22. package/dist/contracts/workspace.contract.d.ts +9 -0
  23. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  24. package/dist/contracts/workspace.contract.js +2 -1
  25. package/dist/contracts/workspace.contract.js.map +1 -1
  26. package/dist/host-protocol.d.ts +10 -0
  27. package/dist/host-protocol.d.ts.map +1 -0
  28. package/dist/host-protocol.js +9 -0
  29. package/dist/host-protocol.js.map +1 -0
  30. package/dist/index.d.ts +86 -6
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +5 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/quick-model.d.ts +3 -3
  35. package/dist/quick-model.d.ts.map +1 -1
  36. package/dist/quick-model.js.map +1 -1
  37. package/dist/schemas.d.ts +209 -7
  38. package/dist/schemas.d.ts.map +1 -1
  39. package/dist/schemas.js +48 -3
  40. package/dist/schemas.js.map +1 -1
  41. package/package.json +3 -3
  42. package/src/agent-catalog.test.ts +35 -0
  43. package/src/agent-catalog.ts +31 -3
  44. package/src/contracts/endpoints.contract.ts +10 -0
  45. package/src/contracts/host.contract.ts +33 -0
  46. package/src/contracts/workspace.contract.ts +5 -0
  47. package/src/host-protocol.ts +36 -0
  48. package/src/index.ts +7 -1
  49. package/src/quick-model.test.ts +27 -0
  50. package/src/quick-model.ts +22 -12
  51. package/src/schemas.ts +211 -23
  52. package/dist/effects.d.ts +0 -42
  53. package/dist/effects.d.ts.map +0 -1
  54. package/dist/effects.js +0 -79
  55. package/dist/effects.js.map +0 -1
  56. package/src/effects.test.ts +0 -132
  57. package/src/effects.ts +0 -138
package/dist/effects.js DELETED
@@ -1,79 +0,0 @@
1
- const filled = (value) => typeof value === "string" && value.length > 0;
2
- const hasToken = (config) => filled(config["token"]) || config["hasToken"] === true;
3
- const cloneUrl = (config) => (filled(config["url"]) ? String(config["url"]) : undefined);
4
- export const capabilityEffects = (input) => {
5
- switch (input.kind) {
6
- case "devops":
7
- return [{ kind: "scaffold", repos: ["intent", "desired-state"] }];
8
- case "monorepo":
9
- return [{ kind: "scaffold", repos: input.id === undefined || input.id.length === 0 ? [] : [input.id] }];
10
- case "mcp": {
11
- const effects = [{ kind: "mcp" }];
12
- if (hasToken(input.config)) {
13
- effects.push({ kind: "secret", exposure: "disk" });
14
- }
15
- return effects;
16
- }
17
- case "service":
18
- return [{ kind: "deploy", provisions: true }];
19
- case "integration":
20
- return [{ kind: "deploy", provisions: false }];
21
- case "cli": {
22
- const effects = [{ kind: "skill", name: input.id }];
23
- const secret = input.connector === undefined ? input.config["hasSecret"] === true : input.connector.fields.some((field) => field.secret === true);
24
- if (secret) {
25
- effects.push({ kind: "secret", exposure: "agent-env" });
26
- }
27
- if (input.connector?.fragment !== undefined) {
28
- effects.push({ kind: "image" });
29
- }
30
- return effects;
31
- }
32
- case "plugin": {
33
- const effects = [{ kind: "clone", url: cloneUrl(input.config) }];
34
- if (hasToken(input.config)) {
35
- effects.push({ kind: "secret", exposure: "disk" });
36
- }
37
- return effects;
38
- }
39
- case "extension": {
40
- const effects = [{ kind: "trusted-code" }, { kind: "clone", url: cloneUrl(input.config) }];
41
- if (hasToken(input.config)) {
42
- effects.push({ kind: "secret", exposure: "disk" });
43
- }
44
- const contributes = input.manifest?.contributes;
45
- if (contributes?.environment !== undefined) {
46
- effects.push({ kind: "image" });
47
- }
48
- if (contributes?.processes !== undefined && contributes.processes.length > 0) {
49
- effects.push({ kind: "process", names: contributes.processes.map((process) => process.name) });
50
- }
51
- return effects;
52
- }
53
- case "ssh":
54
- return [
55
- { kind: "secret", exposure: "disk" },
56
- { kind: "skill", name: "ssh" },
57
- ];
58
- case "vpn":
59
- return [{ kind: "secret", exposure: "disk" }, { kind: "skill", name: "vpn" }, { kind: "image" }, { kind: "runtime", level: "net-admin" }];
60
- case "docker":
61
- return [{ kind: "image" }, { kind: "runtime", level: "privileged" }, { kind: "process", names: ["dockerd"] }];
62
- case "browser": {
63
- const effects = [{ kind: "skill", name: input.id }, { kind: "image" }];
64
- const platform = input.config["platform"];
65
- if (typeof platform === "string") {
66
- effects.push({ kind: "profile", platform });
67
- }
68
- return effects;
69
- }
70
- case "agent": {
71
- const effects = [{ kind: "process", names: input.id === undefined || input.id.length === 0 ? [] : [input.id] }];
72
- if (filled(input.config["env"]) || input.config["hasSecret"] === true) {
73
- effects.push({ kind: "secret", exposure: "disk" });
74
- }
75
- return effects;
76
- }
77
- }
78
- };
79
- //# sourceMappingURL=effects.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"effects.js","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":"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"}
@@ -1,132 +0,0 @@
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
- });
package/src/effects.ts DELETED
@@ -1,138 +0,0 @@
1
- import type { ConnectorContribution, ExtensionManifest } from "@intentic/extension-api";
2
- import type { CapabilityKind } from "./schemas.js";
3
-
4
- /* What adding a capability DOES to the sandbox, as data — the structured counterpart of the handlers' side
5
- * effects (the sandbox's capabilities/handlers/*), rendered by the web as the "This will add to your sandbox"
6
- * panel before an add and as per-instance effect strips after. Derived, not declared per card: config-dependent
7
- * effects (a plugin's clone URL, the SQL card's engine-dependent client image) and connector/extension-declared
8
- * ones (secret fields, image fragments, processes) are computed from the same contribution data the handlers
9
- * consume, so there is no per-card effects list to drift. The streamed apply log stays the post-apply record;
10
- * this is the pre-add disclosure. */
11
-
12
- export type CapabilityEffect =
13
- // Writes .claude/skills/<name>/SKILL.md, auto-loaded by the agent next turn. `name` is the instance id for
14
- // cli/browser (per-instance skills), the fixed shared skill for ssh/vpn; absent while the instance is unnamed.
15
- | { readonly kind: "skill"; readonly name?: string | undefined }
16
- // Stores a credential in the sandbox. "agent-env": injected into the agent's environment each turn, never
17
- // written to a file (cli). "disk": a 0600 file or a denylisted manifest field (ssh key, vpn conf, git token).
18
- | { readonly kind: "secret"; readonly exposure: "agent-env" | "disk" }
19
- // Git-clones a repo into .intentic/plugins|extensions/<id>. `url` absent until the form field is filled.
20
- | { readonly kind: "clone"; readonly url?: string | undefined }
21
- // Bakes a Dockerfile fragment into the sandbox image overlay — needs a one-time owner rebuild.
22
- | { readonly kind: "image" }
23
- // The baked fragment carries a privileged runtime directive: "net-admin" (vpn NET_ADMIN + tun) or
24
- // "privileged" (docker — the full --privileged run its dockerd needs).
25
- | { readonly kind: "runtime"; readonly level: "net-admin" | "privileged" }
26
- // Runs long-lived background processes in the sandbox (an extension's declared processes).
27
- | { readonly kind: "process"; readonly names: readonly string[] }
28
- // Registers an mcp__<id>__ server the agent connects to next turn.
29
- | { readonly kind: "mcp" }
30
- // Scaffolds workspace repositories. Empty while a name-derived repo is still unnamed.
31
- | { readonly kind: "scaffold"; readonly repos: readonly string[] }
32
- // Writes a managed deploy.config.ts entry; `provisions` = also runs the infra apply job now (service).
33
- | { readonly kind: "deploy"; readonly provisions: boolean }
34
- // Extension code runs inside the app with the owner's session — the owner-only trust decision.
35
- | { readonly kind: "trusted-code" }
36
- // Keeps a logged-in Chromium profile under .intentic/browser/<platform> that the agent drives.
37
- | { readonly kind: "profile"; readonly platform: string };
38
-
39
- export interface CapabilityEffectInput {
40
- readonly kind: CapabilityKind;
41
- // The instance name — the skill name for cli/browser, the repo name for monorepo.
42
- readonly id?: string | undefined;
43
- // Live form values, or a CapabilitySummary's secret-stripped config echo (hasToken/hasSecret booleans).
44
- readonly config: Record<string, string | number | boolean | undefined>;
45
- // The cli provider's connector spec — the source of truth for its secret/fragment declarations.
46
- readonly connector?: ConnectorContribution | undefined;
47
- // An installed extension's manifest — resolves its process/image contributions (unknowable before install).
48
- readonly manifest?: ExtensionManifest | undefined;
49
- }
50
-
51
- const filled = (value: string | number | boolean | undefined): boolean => typeof value === "string" && value.length > 0;
52
- // A token either typed into the form (`token`) or echoed as present on an installed instance (`hasToken`).
53
- const hasToken = (config: CapabilityEffectInput["config"]): boolean => filled(config["token"]) || config["hasToken"] === true;
54
- const cloneUrl = (config: CapabilityEffectInput["config"]): string | undefined => (filled(config["url"]) ? String(config["url"]) : undefined);
55
-
56
- export const capabilityEffects = (input: CapabilityEffectInput): readonly CapabilityEffect[] => {
57
- switch (input.kind) {
58
- case "devops":
59
- return [{ kind: "scaffold", repos: ["intent", "desired-state"] }];
60
- case "monorepo":
61
- return [{ kind: "scaffold", repos: input.id === undefined || input.id.length === 0 ? [] : [input.id] }];
62
- case "mcp": {
63
- const effects: CapabilityEffect[] = [{ kind: "mcp" }];
64
- if (hasToken(input.config)) {
65
- effects.push({ kind: "secret", exposure: "disk" });
66
- }
67
- return effects;
68
- }
69
- case "service":
70
- return [{ kind: "deploy", provisions: true }];
71
- case "integration":
72
- return [{ kind: "deploy", provisions: false }];
73
- case "cli": {
74
- // Without the connector spec (extensions query pending / sandbox unreachable) fall back to the
75
- // echoed hasSecret — which the web also synthesizes from the card's own secret-marked fields, so
76
- // the secret row never waits on /extensions. The image row does wait: a connector's fragment
77
- // (postgres/mysql clients, discord's whisper) is spec data with no static counterpart on the card.
78
- const effects: CapabilityEffect[] = [{ kind: "skill", name: input.id }];
79
- const secret =
80
- input.connector === undefined ? input.config["hasSecret"] === true : input.connector.fields.some((field) => field.secret === true);
81
- if (secret) {
82
- effects.push({ kind: "secret", exposure: "agent-env" });
83
- }
84
- if (input.connector?.fragment !== undefined) {
85
- effects.push({ kind: "image" });
86
- }
87
- return effects;
88
- }
89
- case "plugin": {
90
- const effects: CapabilityEffect[] = [{ kind: "clone", url: cloneUrl(input.config) }];
91
- if (hasToken(input.config)) {
92
- effects.push({ kind: "secret", exposure: "disk" });
93
- }
94
- return effects;
95
- }
96
- case "extension": {
97
- const effects: CapabilityEffect[] = [{ kind: "trusted-code" }, { kind: "clone", url: cloneUrl(input.config) }];
98
- if (hasToken(input.config)) {
99
- effects.push({ kind: "secret", exposure: "disk" });
100
- }
101
- const contributes = input.manifest?.contributes;
102
- if (contributes?.environment !== undefined) {
103
- effects.push({ kind: "image" });
104
- }
105
- if (contributes?.processes !== undefined && contributes.processes.length > 0) {
106
- effects.push({ kind: "process", names: contributes.processes.map((process) => process.name) });
107
- }
108
- return effects;
109
- }
110
- case "ssh":
111
- return [
112
- { kind: "secret", exposure: "disk" },
113
- { kind: "skill", name: "ssh" },
114
- ];
115
- case "vpn":
116
- return [{ kind: "secret", exposure: "disk" }, { kind: "skill", name: "vpn" }, { kind: "image" }, { kind: "runtime", level: "net-admin" }];
117
- case "docker":
118
- // The engine is baked into the base image — the "image" effect here is the overlay rebuild that
119
- // applies the fragment's --privileged directive, not new tooling.
120
- return [{ kind: "image" }, { kind: "runtime", level: "privileged" }, { kind: "process", names: ["dockerd"] }];
121
- case "browser": {
122
- const effects: CapabilityEffect[] = [{ kind: "skill", name: input.id }, { kind: "image" }];
123
- const platform = input.config["platform"];
124
- if (typeof platform === "string") {
125
- effects.push({ kind: "profile", platform });
126
- }
127
- return effects;
128
- }
129
- case "agent": {
130
- // The spawned ACP subprocess is the standing consequence; a pasted env block is a stored credential.
131
- const effects: CapabilityEffect[] = [{ kind: "process", names: input.id === undefined || input.id.length === 0 ? [] : [input.id] }];
132
- if (filled(input.config["env"]) || input.config["hasSecret"] === true) {
133
- effects.push({ kind: "secret", exposure: "disk" });
134
- }
135
- return effects;
136
- }
137
- }
138
- };