@veris-ai/daytona-opencode 0.1.1 → 0.2.0-rc.1

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.
@@ -3,8 +3,10 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import type { PluginInput } from '@opencode-ai/plugin';
6
+ import { verisConfig } from './plugins/veris-config';
6
7
  export type { EventSessionDeleted, LogLevel, SandboxInfo, SessionInfo, ProjectSessionData } from './core/types';
7
8
  declare function daytonaPlugin(ctx: PluginInput): Promise<{
9
+ config: typeof verisConfig;
8
10
  tool: {
9
11
  bash: {
10
12
  description: string;
@@ -117,6 +119,15 @@ declare function daytonaPlugin(ctx: PluginInput): Promise<{
117
119
  service?: string;
118
120
  }, ctx: import("@opencode-ai/plugin").ToolContext): Promise<string>;
119
121
  };
122
+ verisTwin: {
123
+ description: string;
124
+ args: {
125
+ service: import("zod").ZodOptional<import("zod").ZodString>;
126
+ };
127
+ execute(args: {
128
+ service?: string;
129
+ }, ctx: import("@opencode-ai/plugin").ToolContext): Promise<string>;
130
+ };
120
131
  };
121
132
  event: (args: any) => Promise<void>;
122
133
  'experimental.chat.system.transform': (input: import("./core/types").ExperimentalChatSystemTransformInput, output: import("./core/types").ExperimentalChatSystemTransformOutput) => Promise<void>;
@@ -32,6 +32,7 @@ import { toast } from './core/toast';
32
32
  import { customTools } from './plugins/custom-tools';
33
33
  import { eventHandlers } from './plugins/session-events';
34
34
  import { systemPromptTransform } from './plugins/system-transform';
35
+ import { verisConfig } from './plugins/veris-config';
35
36
  const xdgDataDir = xdgData ?? join(homedir(), '.local', 'share');
36
37
  const LOG_FILE = join(xdgDataDir, 'opencode', 'log', 'daytona.log');
37
38
  const STORAGE_DIR = join(xdgDataDir, 'opencode', 'storage', 'daytona');
@@ -41,6 +42,7 @@ const sessionManager = new DaytonaSessionManager(process.env.DAYTONA_API_KEY ||
41
42
  async function daytonaPlugin(ctx) {
42
43
  toast.initialize(ctx.client?.tui);
43
44
  return {
45
+ config: verisConfig,
44
46
  tool: await customTools(ctx, sessionManager),
45
47
  event: await eventHandlers(ctx, sessionManager, REPO_PATH),
46
48
  'experimental.chat.system.transform': await systemPromptTransform(ctx, REPO_PATH),
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAA;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAErC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAIlE,MAAM,UAAU,GAAG,OAAO,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;AAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,CAAC,CAAA;AACnE,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,CAAC,CAAA;AACtE,MAAM,SAAS,GAAG,uBAAuB,CAAA;AAEzC,cAAc,CAAC,QAAQ,CAAC,CAAA;AACxB,MAAM,cAAc,GAAG,IAAI,qBAAqB,CAC9C,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,EACjC,WAAW,EACX,SAAS,EACT,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAC7B,CAAA;AAED,KAAK,UAAU,aAAa,CAAC,GAAgB;IAC3C,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACjC,OAAO;QACL,IAAI,EAAE,MAAM,WAAW,CAAC,GAAG,EAAE,cAAc,CAAC;QAC5C,KAAK,EAAE,MAAM,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,SAAS,CAAC;QAC1D,oCAAoC,EAAE,MAAM,qBAAqB,CAAC,GAAG,EAAE,SAAS,CAAC;QACjF,mFAAmF;QACnF,iFAAiF;QACjF,iFAAiF;QACjF,iFAAiF;QACjF,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAA;YACtE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,iFAAiF,CAAC,CAAA;YAChG,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,eAAe,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAA;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAErC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAIpD,MAAM,UAAU,GAAG,OAAO,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;AAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,CAAC,CAAA;AACnE,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,CAAC,CAAA;AACtE,MAAM,SAAS,GAAG,uBAAuB,CAAA;AAEzC,cAAc,CAAC,QAAQ,CAAC,CAAA;AACxB,MAAM,cAAc,GAAG,IAAI,qBAAqB,CAC9C,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,EACjC,WAAW,EACX,SAAS,EACT,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAC7B,CAAA;AAED,KAAK,UAAU,aAAa,CAAC,GAAgB;IAC3C,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACjC,OAAO;QACL,MAAM,EAAE,WAAW;QACnB,IAAI,EAAE,MAAM,WAAW,CAAC,GAAG,EAAE,cAAc,CAAC;QAC5C,KAAK,EAAE,MAAM,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,SAAS,CAAC;QAC1D,oCAAoC,EAAE,MAAM,qBAAqB,CAAC,GAAG,EAAE,SAAS,CAAC;QACjF,mFAAmF;QACnF,iFAAiF;QACjF,iFAAiF;QACjF,iFAAiF;QACjF,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAA;YACtE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,iFAAiF,CAAC,CAAA;YAChG,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,eAAe,aAAa,CAAA"}
@@ -35,6 +35,7 @@ import { toast } from './core/toast'
35
35
  import { customTools } from './plugins/custom-tools'
36
36
  import { eventHandlers } from './plugins/session-events'
37
37
  import { systemPromptTransform } from './plugins/system-transform'
38
+ import { verisConfig } from './plugins/veris-config'
38
39
 
39
40
  export type { EventSessionDeleted, LogLevel, SandboxInfo, SessionInfo, ProjectSessionData } from './core/types'
40
41
 
@@ -54,6 +55,7 @@ const sessionManager = new DaytonaSessionManager(
54
55
  async function daytonaPlugin(ctx: PluginInput) {
55
56
  toast.initialize(ctx.client?.tui)
56
57
  return {
58
+ config: verisConfig,
57
59
  tool: await customTools(ctx, sessionManager),
58
60
  event: await eventHandlers(ctx, sessionManager, REPO_PATH),
59
61
  'experimental.chat.system.transform': await systemPromptTransform(ctx, REPO_PATH),
@@ -119,4 +119,13 @@ export declare function customTools(ctx: PluginInput, sessionManager: DaytonaSes
119
119
  service?: string;
120
120
  }, ctx: import("@opencode-ai/plugin").ToolContext): Promise<string>;
121
121
  };
122
+ verisTwin: {
123
+ description: string;
124
+ args: {
125
+ service: import("zod").ZodOptional<import("zod").ZodString>;
126
+ };
127
+ execute(args: {
128
+ service?: string;
129
+ }, ctx: import("@opencode-ai/plugin").ToolContext): Promise<string>;
130
+ };
122
131
  }>;
@@ -32,6 +32,14 @@ export async function systemPromptTransform(ctx, repoPath) {
32
32
  'read what the twin actually received. If the receipt is empty, the change does not',
33
33
  'work yet — report that, do not report success. Cite the receipt when you claim an',
34
34
  'integration works.',
35
+ '',
36
+ 'The twin already exists and this plugin owns its lifecycle: it was created with the',
37
+ 'sandbox and is deleted with it. Do NOT create or delete Veris sandboxes. Run the',
38
+ 'verisTwin tool to get its id (every Veris MCP tool takes one) and to read a',
39
+ "service's manual before writing code against it. Nothing here runs under",
40
+ '`veris-proxy` — egress is already intercepted at the sandbox boundary, so any',
41
+ 'instruction to start a proxy or provision a sandbox is describing a different setup',
42
+ 'than this one.',
35
43
  ].join('\n'));
36
44
  };
37
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"system-transform.js","sourceRoot":"","sources":["system-transform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAgB,EAAE,QAAgB;IAC5E,OAAO,KAAK,EAAE,KAA2C,EAAE,MAA6C,EAAE,EAAE;QAC1G,MAAM,CAAC,MAAM,CAAC,IAAI,CAChB;YACE,gCAAgC;YAChC,oDAAoD;YACpD,8CAA8C,QAAQ,GAAG;YACzD,2CAA2C;YAC3C,gHAAgH;YAChH,+FAA+F;YAC/F,yFAAyF;YACzF,wGAAwG;YACxG,EAAE;YACF,eAAe;YACf,qFAAqF;YACrF,qFAAqF;YACrF,iFAAiF;YACjF,mFAAmF;YACnF,iFAAiF;YACjF,+EAA+E;YAC/E,EAAE;YACF,oFAAoF;YACpF,mFAAmF;YACnF,oFAAoF;YACpF,oFAAoF;YACpF,oFAAoF;YACpF,mFAAmF;YACnF,oBAAoB;SACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAA;IACH,CAAC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"system-transform.js","sourceRoot":"","sources":["system-transform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAgB,EAAE,QAAgB;IAC5E,OAAO,KAAK,EAAE,KAA2C,EAAE,MAA6C,EAAE,EAAE;QAC1G,MAAM,CAAC,MAAM,CAAC,IAAI,CAChB;YACE,gCAAgC;YAChC,oDAAoD;YACpD,8CAA8C,QAAQ,GAAG;YACzD,2CAA2C;YAC3C,gHAAgH;YAChH,+FAA+F;YAC/F,yFAAyF;YACzF,wGAAwG;YACxG,EAAE;YACF,eAAe;YACf,qFAAqF;YACrF,qFAAqF;YACrF,iFAAiF;YACjF,mFAAmF;YACnF,iFAAiF;YACjF,+EAA+E;YAC/E,EAAE;YACF,oFAAoF;YACpF,mFAAmF;YACnF,oFAAoF;YACpF,oFAAoF;YACpF,oFAAoF;YACpF,mFAAmF;YACnF,oBAAoB;YACpB,EAAE;YACF,qFAAqF;YACrF,kFAAkF;YAClF,6EAA6E;YAC7E,0EAA0E;YAC1E,+EAA+E;YAC/E,qFAAqF;YACrF,gBAAgB;SACjB,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAA;IACH,CAAC,CAAA;AACH,CAAC"}
@@ -37,6 +37,14 @@ export async function systemPromptTransform(ctx: PluginInput, repoPath: string)
37
37
  'read what the twin actually received. If the receipt is empty, the change does not',
38
38
  'work yet — report that, do not report success. Cite the receipt when you claim an',
39
39
  'integration works.',
40
+ '',
41
+ 'The twin already exists and this plugin owns its lifecycle: it was created with the',
42
+ 'sandbox and is deleted with it. Do NOT create or delete Veris sandboxes. Run the',
43
+ 'verisTwin tool to get its id (every Veris MCP tool takes one) and to read a',
44
+ "service's manual before writing code against it. Nothing here runs under",
45
+ '`veris-proxy` — egress is already intercepted at the sandbox boundary, so any',
46
+ 'instruction to start a proxy or provision a sandbox is describing a different setup',
47
+ 'than this one.',
40
48
  ].join('\n'),
41
49
  )
42
50
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Copyright Veris AI
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Wires the Veris MCP server into the session, and gates the two of its tools
6
+ * that are always wrong here.
7
+ *
8
+ * The MCP is the shared home for twin LIFECYCLE (get, promote, reset). Keeping
9
+ * it there rather than reimplementing those as plugin tools is what stops a
10
+ * second engine plugin from having to grow the same surface again: the only
11
+ * thing to copy is this file.
12
+ *
13
+ * Everything is `??=`, so a user's own opencode.json always wins and someone who
14
+ * already has `opencode-veris-sim` installed globally gets no double
15
+ * registration. The whole body is wrapped: a failed registration should surface
16
+ * as a missing MCP server, never as OpenCode failing to start.
17
+ */
18
+ import type { Hooks } from '@opencode-ai/plugin';
19
+ type OpencodeConfig = Parameters<NonNullable<Hooks['config']>>[0];
20
+ export declare function verisConfig(cfg: OpencodeConfig): Promise<void>;
21
+ export {};
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Copyright Veris AI
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Wires the Veris MCP server into the session, and gates the two of its tools
6
+ * that are always wrong here.
7
+ *
8
+ * The MCP is the shared home for twin LIFECYCLE (get, promote, reset). Keeping
9
+ * it there rather than reimplementing those as plugin tools is what stops a
10
+ * second engine plugin from having to grow the same surface again: the only
11
+ * thing to copy is this file.
12
+ *
13
+ * Everything is `??=`, so a user's own opencode.json always wins and someone who
14
+ * already has `opencode-veris-sim` installed globally gets no double
15
+ * registration. The whole body is wrapped: a failed registration should surface
16
+ * as a missing MCP server, never as OpenCode failing to start.
17
+ */
18
+ import { logger } from '../core/logger';
19
+ /**
20
+ * Defaults for the MCP's destructive tools. The plugin creates and owns this
21
+ * session's twin, which is what makes two of these never the right call:
22
+ *
23
+ * create_sandbox makes a twin the plugin does not know about. Traffic still
24
+ * goes to the plugin's twin and verisReceipt still reads the
25
+ * plugin's twin, so the agent would seed the orphan and then
26
+ * report success — the exact failure the receipt exists to
27
+ * catch, reintroduced one layer up.
28
+ * delete_sandbox destroys the running session's twin. The egress credential
29
+ * dies with it and every later call fails obscurely.
30
+ * promote_sandbox legitimate, but it freezes and scrubs the sandbox AND
31
+ * rewrites what every future run in the environment starts
32
+ * from. That is a human's decision.
33
+ * reset_sandbox legitimate, but it clears /veris/requests, which is the
34
+ * receipt.
35
+ */
36
+ const TOOL_PERMISSIONS = {
37
+ veris_create_sandbox: 'deny',
38
+ veris_delete_sandbox: 'deny',
39
+ veris_promote_sandbox: 'ask',
40
+ veris_reset_sandbox: 'ask',
41
+ };
42
+ const DEFAULT_API_BASE = 'https://svc.api.veris.ai';
43
+ export async function verisConfig(cfg) {
44
+ try {
45
+ // No key, no registration. Registering with an empty header instead would
46
+ // give a server that fails every call and reads as a Veris outage; the
47
+ // session manager already reports the missing credential properly.
48
+ const apiKey = process.env.VERIS_API_KEY;
49
+ if (!apiKey)
50
+ return;
51
+ cfg.mcp ??= {};
52
+ cfg.mcp.veris ??= {
53
+ type: 'remote',
54
+ url: `${process.env.VERIS_API_BASE || DEFAULT_API_BASE}/mcp`,
55
+ headers: { 'X-API-Key': apiKey },
56
+ // The Veris MCP authenticates by header. Without this, OAuth
57
+ // auto-detection can intercept the connection and prompt for a login
58
+ // that does not exist.
59
+ oauth: false,
60
+ };
61
+ // `permission` may legally be a bare 'ask' | 'allow' | 'deny' that applies
62
+ // to everything; only the object form takes per-tool keys. The bundled SDK
63
+ // types predate both that string form and the arbitrary-tool-name index
64
+ // signature the running config schema documents, hence the assertion.
65
+ if (typeof cfg.permission === 'string')
66
+ return;
67
+ const permission = (cfg.permission ??= {});
68
+ for (const [tool, action] of Object.entries(TOOL_PERMISSIONS)) {
69
+ permission[tool] ??= action;
70
+ }
71
+ }
72
+ catch (err) {
73
+ logger.warn(`[veris] MCP registration skipped: ${err instanceof Error ? err.message : String(err)}`);
74
+ }
75
+ }
76
+ //# sourceMappingURL=veris-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"veris-config.js","sourceRoot":"","sources":["veris-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAIvC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,gBAAgB,GAAmC;IACvD,oBAAoB,EAAE,MAAM;IAC5B,oBAAoB,EAAE,MAAM;IAC5B,qBAAqB,EAAE,KAAK;IAC5B,mBAAmB,EAAE,KAAK;CAC3B,CAAA;AAED,MAAM,gBAAgB,GAAG,0BAA0B,CAAA;AAEnD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAmB;IACnD,IAAI,CAAC;QACH,0EAA0E;QAC1E,uEAAuE;QACvE,mEAAmE;QACnE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAA;QACxC,IAAI,CAAC,MAAM;YAAE,OAAM;QAEnB,GAAG,CAAC,GAAG,KAAK,EAAE,CAAA;QACd,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK;YAChB,IAAI,EAAE,QAAQ;YACd,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,gBAAgB,MAAM;YAC5D,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;YAChC,6DAA6D;YAC7D,qEAAqE;YACrE,uBAAuB;YACvB,KAAK,EAAE,KAAK;SACb,CAAA;QAED,2EAA2E;QAC3E,2EAA2E;QAC3E,wEAAwE;QACxE,sEAAsE;QACtE,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ;YAAE,OAAM;QAC9C,MAAM,UAAU,GAAG,CAAC,GAAG,CAAC,UAAU,KAAK,EAAE,CAA4B,CAAA;QACrE,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC9D,UAAU,CAAC,IAAI,CAAC,KAAK,MAAM,CAAA;QAC7B,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,qCAAqC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACtG,CAAC;AACH,CAAC"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Copyright Veris AI
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Wires the Veris MCP server into the session, and gates the two of its tools
6
+ * that are always wrong here.
7
+ *
8
+ * The MCP is the shared home for twin LIFECYCLE (get, promote, reset). Keeping
9
+ * it there rather than reimplementing those as plugin tools is what stops a
10
+ * second engine plugin from having to grow the same surface again: the only
11
+ * thing to copy is this file.
12
+ *
13
+ * Everything is `??=`, so a user's own opencode.json always wins and someone who
14
+ * already has `opencode-veris-sim` installed globally gets no double
15
+ * registration. The whole body is wrapped: a failed registration should surface
16
+ * as a missing MCP server, never as OpenCode failing to start.
17
+ */
18
+
19
+ import type { Hooks } from '@opencode-ai/plugin'
20
+ import { logger } from '../core/logger'
21
+
22
+ type OpencodeConfig = Parameters<NonNullable<Hooks['config']>>[0]
23
+
24
+ /**
25
+ * Defaults for the MCP's destructive tools. The plugin creates and owns this
26
+ * session's twin, which is what makes two of these never the right call:
27
+ *
28
+ * create_sandbox makes a twin the plugin does not know about. Traffic still
29
+ * goes to the plugin's twin and verisReceipt still reads the
30
+ * plugin's twin, so the agent would seed the orphan and then
31
+ * report success — the exact failure the receipt exists to
32
+ * catch, reintroduced one layer up.
33
+ * delete_sandbox destroys the running session's twin. The egress credential
34
+ * dies with it and every later call fails obscurely.
35
+ * promote_sandbox legitimate, but it freezes and scrubs the sandbox AND
36
+ * rewrites what every future run in the environment starts
37
+ * from. That is a human's decision.
38
+ * reset_sandbox legitimate, but it clears /veris/requests, which is the
39
+ * receipt.
40
+ */
41
+ const TOOL_PERMISSIONS: Record<string, 'ask' | 'deny'> = {
42
+ veris_create_sandbox: 'deny',
43
+ veris_delete_sandbox: 'deny',
44
+ veris_promote_sandbox: 'ask',
45
+ veris_reset_sandbox: 'ask',
46
+ }
47
+
48
+ const DEFAULT_API_BASE = 'https://svc.api.veris.ai'
49
+
50
+ export async function verisConfig(cfg: OpencodeConfig): Promise<void> {
51
+ try {
52
+ // No key, no registration. Registering with an empty header instead would
53
+ // give a server that fails every call and reads as a Veris outage; the
54
+ // session manager already reports the missing credential properly.
55
+ const apiKey = process.env.VERIS_API_KEY
56
+ if (!apiKey) return
57
+
58
+ cfg.mcp ??= {}
59
+ cfg.mcp.veris ??= {
60
+ type: 'remote',
61
+ url: `${process.env.VERIS_API_BASE || DEFAULT_API_BASE}/mcp`,
62
+ headers: { 'X-API-Key': apiKey },
63
+ // The Veris MCP authenticates by header. Without this, OAuth
64
+ // auto-detection can intercept the connection and prompt for a login
65
+ // that does not exist.
66
+ oauth: false,
67
+ }
68
+
69
+ // `permission` may legally be a bare 'ask' | 'allow' | 'deny' that applies
70
+ // to everything; only the object form takes per-tool keys. The bundled SDK
71
+ // types predate both that string form and the arbitrary-tool-name index
72
+ // signature the running config schema documents, hence the assertion.
73
+ if (typeof cfg.permission === 'string') return
74
+ const permission = (cfg.permission ??= {}) as Record<string, unknown>
75
+ for (const [tool, action] of Object.entries(TOOL_PERMISSIONS)) {
76
+ permission[tool] ??= action
77
+ }
78
+ } catch (err) {
79
+ logger.warn(`[veris] MCP registration skipped: ${err instanceof Error ? err.message : String(err)}`)
80
+ }
81
+ }
@@ -2,7 +2,7 @@
2
2
  * Copyright Veris AI
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  *
5
- * The eleventh tool, and the only one this fork adds.
5
+ * The tool this fork exists for.
6
6
  *
7
7
  * Every other tool tells the agent what its own code did. This one tells it
8
8
  * what the VENDOR received — read from the twin, not from the sandbox — which
@@ -2,7 +2,7 @@
2
2
  * Copyright Veris AI
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  *
5
- * The eleventh tool, and the only one this fork adds.
5
+ * The tool this fork exists for.
6
6
  *
7
7
  * Every other tool tells the agent what its own code did. This one tells it
8
8
  * what the VENDOR received — read from the twin, not from the sandbox — which
@@ -2,7 +2,7 @@
2
2
  * Copyright Veris AI
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  *
5
- * The eleventh tool, and the only one this fork adds.
5
+ * The tool this fork exists for.
6
6
  *
7
7
  * Every other tool tells the agent what its own code did. This one tells it
8
8
  * what the VENDOR received — read from the twin, not from the sandbox — which
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Copyright Veris AI
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Where the agent finds out which twin is its own.
6
+ *
7
+ * Every tool on the Veris MCP takes a `sandbox_id`, and the twin's id is not
8
+ * something the agent can discover from inside the sandbox — it is held by the
9
+ * session manager on the host, keyed by session. That mapping is the one thing
10
+ * an MCP server cannot know, which is why this is a plugin tool and the
11
+ * lifecycle operations are not.
12
+ *
13
+ * It is also the reachable way in to a service's manual, which is the document
14
+ * Veris expects you to read before designing anything against a twin.
15
+ */
16
+ import { z } from 'zod';
17
+ import type { PluginInput } from '@opencode-ai/plugin';
18
+ import type { ToolContext } from '@opencode-ai/plugin/tool';
19
+ import type { DaytonaSessionManager } from '../core/session-manager';
20
+ export declare const verisTwinTool: (sessionManager: DaytonaSessionManager, projectId: string, worktree: string, pluginCtx: PluginInput) => {
21
+ description: string;
22
+ args: {
23
+ service: z.ZodOptional<z.ZodString>;
24
+ };
25
+ execute(args: {
26
+ service?: string;
27
+ }, ctx: ToolContext): Promise<string>;
28
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Copyright Veris AI
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Where the agent finds out which twin is its own.
6
+ *
7
+ * Every tool on the Veris MCP takes a `sandbox_id`, and the twin's id is not
8
+ * something the agent can discover from inside the sandbox — it is held by the
9
+ * session manager on the host, keyed by session. That mapping is the one thing
10
+ * an MCP server cannot know, which is why this is a plugin tool and the
11
+ * lifecycle operations are not.
12
+ *
13
+ * It is also the reachable way in to a service's manual, which is the document
14
+ * Veris expects you to read before designing anything against a twin.
15
+ */
16
+ import { z } from 'zod';
17
+ import { isVerisSandbox } from '@veris-ai/daytona';
18
+ export const verisTwinTool = (sessionManager, projectId, worktree, pluginCtx) => ({
19
+ description: "Identify this session's Veris twin: its id, and the vendor services it answers for. " +
20
+ 'Call it before any Veris MCP tool, which all take a sandbox_id — this is where that id ' +
21
+ 'comes from, and creating a new Veris sandbox instead would give you one nothing else ' +
22
+ "in the session is using. Pass a service name to read that service's manual: what the " +
23
+ 'twin models and how its data is shaped, which is worth reading before writing code ' +
24
+ 'against it.',
25
+ args: {
26
+ service: z
27
+ .string()
28
+ .optional()
29
+ .describe("Service name (e.g. \"stripe\") to read its manual. Omit to list the twin's services."),
30
+ },
31
+ async execute(args, ctx) {
32
+ const sandbox = await sessionManager.getSandbox(ctx.sessionID, projectId, worktree, pluginCtx);
33
+ // Without this OpenCode renders the call as "verisTwin Unknown".
34
+ const title = (t) => ctx.metadata({ title: t });
35
+ if (!isVerisSandbox(sandbox)) {
36
+ title('no twin attached');
37
+ return ('No Veris twin is attached to this sandbox.\n' +
38
+ 'Set VERIS_API_KEY and VERIS_ENVIRONMENT_ID and start a new session to get one.');
39
+ }
40
+ const twinId = sandbox.verisSandboxId;
41
+ if (args.service) {
42
+ const manual = await sandbox.veris.manual(args.service);
43
+ title(`${args.service} manual`);
44
+ return `Manual for '${args.service}' (twin ${twinId}):\n\n${manual}`;
45
+ }
46
+ const services = await sandbox.veris.services();
47
+ title(`twin ${twinId} — ${services.length} service(s)`);
48
+ if (services.length === 0) {
49
+ return (`Veris twin ${twinId} answers for no services.\n\n` +
50
+ 'Its environment has none configured, so no vendor call from this sandbox will be ' +
51
+ 'intercepted. Check VERIS_ENVIRONMENT_ID.');
52
+ }
53
+ const rows = services
54
+ .map((s) => ` ${s.name} (${s.status}) -> ${s.url}`)
55
+ .join('\n');
56
+ return (`Veris twin ${twinId}\n\n` +
57
+ `${services.length} service(s):\n${rows}\n\n` +
58
+ 'Pass a service name to this tool to read its manual. Use the twin id above as ' +
59
+ "sandbox_id for Veris MCP tools; do not create a new Veris sandbox — this session's " +
60
+ 'twin is created and deleted for you.');
61
+ },
62
+ });
63
+ //# sourceMappingURL=veris-twin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"veris-twin.js","sourceRoot":"","sources":["veris-twin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGlD,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,cAAqC,EACrC,SAAiB,EACjB,QAAgB,EAChB,SAAsB,EACtB,EAAE,CAAC,CAAC;IACJ,WAAW,EACT,sFAAsF;QACtF,yFAAyF;QACzF,uFAAuF;QACvF,uFAAuF;QACvF,qFAAqF;QACrF,aAAa;IACf,IAAI,EAAE;QACJ,OAAO,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,sFAAsF,CAAC;KACpG;IACD,KAAK,CAAC,OAAO,CAAC,IAA0B,EAAE,GAAgB;QACxD,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAA;QAE9F,iEAAiE;QACjE,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;QAEvD,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,KAAK,CAAC,kBAAkB,CAAC,CAAA;YACzB,OAAO,CACL,8CAA8C;gBAC9C,gFAAgF,CACjF,CAAA;QACH,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,cAAc,CAAA;QAErC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACvD,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,SAAS,CAAC,CAAA;YAC/B,OAAO,eAAe,IAAI,CAAC,OAAO,WAAW,MAAM,SAAS,MAAM,EAAE,CAAA;QACtE,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAA;QAC/C,KAAK,CAAC,QAAQ,MAAM,MAAM,QAAQ,CAAC,MAAM,aAAa,CAAC,CAAA;QAEvD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,CACL,cAAc,MAAM,+BAA+B;gBACnD,mFAAmF;gBACnF,0CAA0C,CAC3C,CAAA;QACH,CAAC;QAED,MAAM,IAAI,GAAG,QAAQ;aAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;aACnD,IAAI,CAAC,IAAI,CAAC,CAAA;QAEb,OAAO,CACL,cAAc,MAAM,MAAM;YAC1B,GAAG,QAAQ,CAAC,MAAM,iBAAiB,IAAI,MAAM;YAC7C,gFAAgF;YAChF,qFAAqF;YACrF,sCAAsC,CACvC,CAAA;IACH,CAAC;CACF,CAAC,CAAA"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Copyright Veris AI
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Where the agent finds out which twin is its own.
6
+ *
7
+ * Every tool on the Veris MCP takes a `sandbox_id`, and the twin's id is not
8
+ * something the agent can discover from inside the sandbox — it is held by the
9
+ * session manager on the host, keyed by session. That mapping is the one thing
10
+ * an MCP server cannot know, which is why this is a plugin tool and the
11
+ * lifecycle operations are not.
12
+ *
13
+ * It is also the reachable way in to a service's manual, which is the document
14
+ * Veris expects you to read before designing anything against a twin.
15
+ */
16
+
17
+ import { z } from 'zod'
18
+ import type { PluginInput } from '@opencode-ai/plugin'
19
+ import type { ToolContext } from '@opencode-ai/plugin/tool'
20
+ import { isVerisSandbox } from '@veris-ai/daytona'
21
+ import type { DaytonaSessionManager } from '../core/session-manager'
22
+
23
+ export const verisTwinTool = (
24
+ sessionManager: DaytonaSessionManager,
25
+ projectId: string,
26
+ worktree: string,
27
+ pluginCtx: PluginInput,
28
+ ) => ({
29
+ description:
30
+ "Identify this session's Veris twin: its id, and the vendor services it answers for. " +
31
+ 'Call it before any Veris MCP tool, which all take a sandbox_id — this is where that id ' +
32
+ 'comes from, and creating a new Veris sandbox instead would give you one nothing else ' +
33
+ "in the session is using. Pass a service name to read that service's manual: what the " +
34
+ 'twin models and how its data is shaped, which is worth reading before writing code ' +
35
+ 'against it.',
36
+ args: {
37
+ service: z
38
+ .string()
39
+ .optional()
40
+ .describe("Service name (e.g. \"stripe\") to read its manual. Omit to list the twin's services."),
41
+ },
42
+ async execute(args: { service?: string }, ctx: ToolContext) {
43
+ const sandbox = await sessionManager.getSandbox(ctx.sessionID, projectId, worktree, pluginCtx)
44
+
45
+ // Without this OpenCode renders the call as "verisTwin Unknown".
46
+ const title = (t: string) => ctx.metadata({ title: t })
47
+
48
+ if (!isVerisSandbox(sandbox)) {
49
+ title('no twin attached')
50
+ return (
51
+ 'No Veris twin is attached to this sandbox.\n' +
52
+ 'Set VERIS_API_KEY and VERIS_ENVIRONMENT_ID and start a new session to get one.'
53
+ )
54
+ }
55
+
56
+ const twinId = sandbox.verisSandboxId
57
+
58
+ if (args.service) {
59
+ const manual = await sandbox.veris.manual(args.service)
60
+ title(`${args.service} manual`)
61
+ return `Manual for '${args.service}' (twin ${twinId}):\n\n${manual}`
62
+ }
63
+
64
+ const services = await sandbox.veris.services()
65
+ title(`twin ${twinId} — ${services.length} service(s)`)
66
+
67
+ if (services.length === 0) {
68
+ return (
69
+ `Veris twin ${twinId} answers for no services.\n\n` +
70
+ 'Its environment has none configured, so no vendor call from this sandbox will be ' +
71
+ 'intercepted. Check VERIS_ENVIRONMENT_ID.'
72
+ )
73
+ }
74
+
75
+ const rows = services
76
+ .map((s) => ` ${s.name} (${s.status}) -> ${s.url}`)
77
+ .join('\n')
78
+
79
+ return (
80
+ `Veris twin ${twinId}\n\n` +
81
+ `${services.length} service(s):\n${rows}\n\n` +
82
+ 'Pass a service name to this tool to read its manual. Use the twin id above as ' +
83
+ "sandbox_id for Veris MCP tools; do not create a new Veris sandbox — this session's " +
84
+ 'twin is created and deleted for you.'
85
+ )
86
+ },
87
+ })
@@ -116,4 +116,13 @@ export declare function createDaytonaTools(sessionManager: DaytonaSessionManager
116
116
  service?: string;
117
117
  }, ctx: import("@opencode-ai/plugin").ToolContext): Promise<string>;
118
118
  };
119
+ verisTwin: {
120
+ description: string;
121
+ args: {
122
+ service: import("zod").ZodOptional<import("zod").ZodString>;
123
+ };
124
+ execute(args: {
125
+ service?: string;
126
+ }, ctx: import("@opencode-ai/plugin").ToolContext): Promise<string>;
127
+ };
119
128
  };
@@ -16,6 +16,7 @@ import { grepTool } from './tools/grep';
16
16
  import { getPreviewURLTool } from './tools/get-preview-url';
17
17
  import { gitSyncTool } from './tools/git-sync';
18
18
  import { verisReceiptTool } from './tools/veris-receipt';
19
+ import { verisTwinTool } from './tools/veris-twin';
19
20
  export function createDaytonaTools(sessionManager, projectId, worktree, pluginCtx) {
20
21
  const repoPath = sessionManager.repoPath;
21
22
  return {
@@ -30,6 +31,7 @@ export function createDaytonaTools(sessionManager, projectId, worktree, pluginCt
30
31
  getPreviewURL: getPreviewURLTool(sessionManager, projectId, worktree, pluginCtx),
31
32
  gitSync: gitSyncTool(sessionManager, projectId, worktree, pluginCtx),
32
33
  verisReceipt: verisReceiptTool(sessionManager, projectId, worktree, pluginCtx),
34
+ verisTwin: verisTwinTool(sessionManager, projectId, worktree, pluginCtx),
33
35
  };
34
36
  }
35
37
  //# sourceMappingURL=tools.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["tools.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAKxD,MAAM,UAAU,kBAAkB,CAChC,cAAqC,EACrC,SAAiB,EACjB,QAAgB,EAChB,SAAsB;IAEtB,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAA;IACxC,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC;QACxE,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC9D,KAAK,EAAE,SAAS,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAChE,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC9D,SAAS,EAAE,aAAa,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QACxE,EAAE,EAAE,MAAM,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC1D,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC9D,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC9D,aAAa,EAAE,iBAAiB,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAChF,OAAO,EAAE,WAAW,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QACpE,YAAY,EAAE,gBAAgB,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;KAC/E,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["tools.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAKlD,MAAM,UAAU,kBAAkB,CAChC,cAAqC,EACrC,SAAiB,EACjB,QAAgB,EAChB,SAAsB;IAEtB,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAA;IACxC,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC;QACxE,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC9D,KAAK,EAAE,SAAS,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAChE,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC9D,SAAS,EAAE,aAAa,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QACxE,EAAE,EAAE,MAAM,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC1D,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC9D,IAAI,EAAE,QAAQ,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC9D,aAAa,EAAE,iBAAiB,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAChF,OAAO,EAAE,WAAW,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QACpE,YAAY,EAAE,gBAAgB,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC9E,SAAS,EAAE,aAAa,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC;KACzE,CAAA;AACH,CAAC"}
@@ -18,6 +18,7 @@ import { grepTool } from './tools/grep'
18
18
  import { getPreviewURLTool } from './tools/get-preview-url'
19
19
  import { gitSyncTool } from './tools/git-sync'
20
20
  import { verisReceiptTool } from './tools/veris-receipt'
21
+ import { verisTwinTool } from './tools/veris-twin'
21
22
 
22
23
  import type { DaytonaSessionManager } from './core/session-manager'
23
24
  import type { PluginInput } from '@opencode-ai/plugin'
@@ -41,5 +42,6 @@ export function createDaytonaTools(
41
42
  getPreviewURL: getPreviewURLTool(sessionManager, projectId, worktree, pluginCtx),
42
43
  gitSync: gitSyncTool(sessionManager, projectId, worktree, pluginCtx),
43
44
  verisReceipt: verisReceiptTool(sessionManager, projectId, worktree, pluginCtx),
45
+ verisTwin: verisTwinTool(sessionManager, projectId, worktree, pluginCtx),
44
46
  }
45
47
  }
package/README.md CHANGED
@@ -25,7 +25,7 @@ Then `opencode`. No image to build, no network configuration, nothing to start.
25
25
 
26
26
  The ten sandbox-backed tools from [`@daytona/opencode`](https://www.npmjs.com/package/@daytona/opencode)
27
27
  — bash, read, write, edit, multiedit, ls, glob, grep, getPreviewURL, gitSync —
28
- plus one.
28
+ plus two, and the Veris MCP server.
29
29
 
30
30
  **`verisReceipt`** reports what the twin *received*.
31
31
 
@@ -52,6 +52,41 @@ it. Do not report this change as working.
52
52
  The system prompt tells the agent to check it before claiming an integration
53
53
  works. You can also just ask for it.
54
54
 
55
+ **`verisTwin`** names the twin and what it answers for — and with a service
56
+ argument, returns that service's manual: what it models and how its data is
57
+ shaped.
58
+
59
+ ```
60
+ Veris twin sbx_a1b2c3
61
+
62
+ 1 service(s):
63
+ stripe (ready) -> https://gw.api.veris.ai/stripe
64
+ ```
65
+
66
+ ### The Veris MCP
67
+
68
+ The plugin also registers Veris's MCP server, so the agent can manage the twin's
69
+ lifecycle — read the environment, promote a sandbox to the environment's
70
+ baseline, reset it. Nothing to configure: it uses the `VERIS_API_KEY` you
71
+ already set, and is skipped entirely if that is unset.
72
+
73
+ Two of its tools are denied by default, because this plugin creates and owns the
74
+ session's twin:
75
+
76
+ | tool | default | why |
77
+ |---|---|---|
78
+ | `create_sandbox` | `deny` | makes a twin nothing else in the session uses — the agent would seed it and report success while traffic and receipts went elsewhere |
79
+ | `delete_sandbox` | `deny` | destroys the running session's twin |
80
+ | `promote_sandbox` | `ask` | rewrites what every future run in the environment starts from |
81
+ | `reset_sandbox` | `ask` | clears the request log the receipt is read from |
82
+
83
+ Set them yourself in `opencode.json` and your values win; the plugin only fills
84
+ in what you have not.
85
+
86
+ Every one of these calls is made from your machine, never from inside the
87
+ sandbox. The twin's control plane is deliberately unreachable from the sandbox:
88
+ code that could reach `/veris/reset` could erase its own receipt.
89
+
55
90
  ## Why the sandbox cannot reach the real vendor
56
91
 
57
92
  The sandbox is created with a `domainAllowList` — the vendor hostnames the twin
@@ -67,11 +102,26 @@ if you want the same thing without an agent.
67
102
  ## Relationship to `@daytona/opencode`
68
103
 
69
104
  This is a fork of `@daytona/opencode` 0.192.0 (Apache-2.0, Copyright Daytona
70
- Platforms Inc.), and a deliberately small one: one changed import, one added
71
- tool, one paragraph in the system prompt, and a check that the Veris
72
- coordinates are set. The ten inherited tools, the git-sync flow and the session
105
+ Platforms Inc.), and a deliberately small one: one changed import, two added
106
+ tools, a config hook, two paragraphs in the system prompt, and a check that the
107
+ Veris coordinates are set. The ten inherited tools, the git-sync flow and the session
73
108
  bookkeeping are untouched, so upstream changes stay easy to take.
74
109
 
110
+ ## Using the veris-sim skills alongside
111
+
112
+ [`opencode-veris-sim`](https://www.npmjs.com/package/opencode-veris-sim) carries
113
+ Veris's reference material on twins — schemas, seeding, faults, webhooks — and
114
+ composes with this plugin:
115
+
116
+ ```sh
117
+ opencode plugin opencode-veris-sim -g
118
+ ```
119
+
120
+ Its `/veris-sim:setup|build|fix` commands describe a different setup: your code
121
+ running on your own machine under `veris-proxy`. In a session backed by this
122
+ plugin the sandbox is the boundary and there is no proxy to start, so those
123
+ commands' steps do not apply. The reference material does.
124
+
75
125
  ## Limitations
76
126
 
77
127
  - **Requires a Veris control plane that serves an HTTP CONNECT gateway.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veris-ai/daytona-opencode",
3
- "version": "0.1.1",
3
+ "version": "0.2.0-rc.1",
4
4
  "description": "Run every OpenCode session in a Daytona sandbox whose vendor API calls are answered by a Veris twin. One line in opencode.json: unmodified code, real hostnames, and a receipt of what the vendor actually received.",
5
5
  "main": "./.opencode/plugin/index.js",
6
6
  "types": "./.opencode/plugin/index.d.ts",
@@ -33,10 +33,11 @@
33
33
  "scripts": {
34
34
  "build": "tsc",
35
35
  "typecheck": "tsc --noEmit",
36
- "prepublishOnly": "npm run build"
36
+ "prepublishOnly": "npm run build",
37
+ "test": "vitest run"
37
38
  },
38
39
  "dependencies": {
39
- "@veris-ai/daytona": "^0.1.1",
40
+ "@veris-ai/daytona": "^0.2.0-rc.1",
40
41
  "@daytona/sdk": "^0.204.1",
41
42
  "@opencode-ai/plugin": "1.1.59",
42
43
  "xdg-basedir": "^5.1.0",
@@ -44,7 +45,8 @@
44
45
  },
45
46
  "devDependencies": {
46
47
  "@types/node": "^22",
47
- "typescript": "^5.6.0"
48
+ "typescript": "^5.6.0",
49
+ "vitest": "^3.2.7"
48
50
  },
49
51
  "publishConfig": {
50
52
  "access": "public"