@tangle-network/agent-interface 0.10.1 → 0.11.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.
package/LICENSE CHANGED
@@ -1,11 +1,21 @@
1
- Copyright (c) 2025 Tangle Network
1
+ MIT License
2
2
 
3
- All rights reserved.
3
+ Copyright (c) 2026 Tangle Network
4
4
 
5
- This software and associated documentation files (the "Software") are proprietary
6
- and confidential. No part of this Software may be reproduced, distributed, or
7
- transmitted in any form or by any means, including photocopying, recording, or
8
- other electronic or mechanical methods, without the prior written permission of
9
- Tangle Network.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
10
11
 
11
- For licensing inquiries, contact: hello@tangle.tools
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # @tangle-network/agent-interface
2
+
3
+ Shared TypeScript types and zod schemas that define the contract between Tangle
4
+ agents, the sidecar, and provider adapters: capabilities, agent profiles,
5
+ message parts, and harness descriptors. This is the canonical home for those
6
+ shapes; higher-level packages import from here rather than redefining them.
7
+
8
+ The only runtime dependency is `zod` (used for the schema exports).
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pnpm add @tangle-network/agent-interface
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ```ts
19
+ import type { BackendCapabilities, ProviderCapabilities } from "@tangle-network/agent-interface";
20
+
21
+ const caps: ProviderCapabilities = {
22
+ supportsVision: true,
23
+ supportsLogprobs: false,
24
+ supportsToolCalls: true,
25
+ supportsComputerUse: false,
26
+ };
27
+ ```
28
+
29
+ ## License
30
+
31
+ MIT
package/dist/index.d.ts CHANGED
@@ -214,6 +214,13 @@ export type ToolInvocation = {
214
214
  toolName: string;
215
215
  input: unknown;
216
216
  result?: unknown;
217
+ /**
218
+ * True when the tool call failed (errored, timed out, or was rejected).
219
+ * Failed tools are recorded — not dropped — so the run outcome can reflect
220
+ * them. Consumers deriving success must treat any `isError: true` invocation
221
+ * as a failure signal.
222
+ */
223
+ isError?: boolean;
217
224
  };
218
225
  export type TokenUsage = {
219
226
  inputTokens: number;
@@ -582,3 +589,5 @@ export * from "./agent-profile.js";
582
589
  export * from "./harness.js";
583
590
  export * from "./harness-capabilities.js";
584
591
  export * from "./profile-schema.js";
592
+ export * from "./profile-security.js";
593
+ export * from "./sandbox-size.js";
package/dist/index.js CHANGED
@@ -126,3 +126,5 @@ export * from "./agent-profile.js";
126
126
  export * from "./harness.js";
127
127
  export * from "./harness-capabilities.js";
128
128
  export * from "./profile-schema.js";
129
+ export * from "./profile-security.js";
130
+ export * from "./sandbox-size.js";
@@ -1,17 +1,19 @@
1
1
  import { z } from "zod";
2
+ import type { AgentProfile } from "./agent-profile.js";
3
+ import { type SandboxSizePreset } from "./sandbox-size.js";
2
4
  export declare const agentProfilePermissionValueSchema: z.ZodEnum<{
3
5
  allow: "allow";
4
- ask: "ask";
5
6
  deny: "deny";
7
+ ask: "ask";
6
8
  }>;
7
9
  export declare const agentProfilePermissionSchema: z.ZodUnion<readonly [z.ZodEnum<{
8
10
  allow: "allow";
9
- ask: "ask";
10
11
  deny: "deny";
12
+ ask: "ask";
11
13
  }>, z.ZodRecord<z.ZodString, z.ZodEnum<{
12
14
  allow: "allow";
13
- ask: "ask";
14
15
  deny: "deny";
16
+ ask: "ask";
15
17
  }>>]>;
16
18
  export declare const agentProfileResourceRefSchema: z.ZodUnion<readonly [z.ZodObject<{
17
19
  kind: z.ZodLiteral<"inline">;
@@ -117,10 +119,10 @@ export declare const agentProfileModelHintsSchema: z.ZodObject<{
117
119
  small: z.ZodOptional<z.ZodString>;
118
120
  provider: z.ZodOptional<z.ZodString>;
119
121
  reasoningEffort: z.ZodOptional<z.ZodEnum<{
122
+ medium: "medium";
120
123
  none: "none";
121
124
  minimal: "minimal";
122
125
  low: "low";
123
- medium: "medium";
124
126
  high: "high";
125
127
  xhigh: "xhigh";
126
128
  ultracode: "ultracode";
@@ -138,12 +140,12 @@ export declare const agentSubagentProfileSchema: z.ZodObject<{
138
140
  tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
139
141
  permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
140
142
  allow: "allow";
141
- ask: "ask";
142
143
  deny: "deny";
144
+ ask: "ask";
143
145
  }>, z.ZodRecord<z.ZodString, z.ZodEnum<{
144
146
  allow: "allow";
145
- ask: "ask";
146
147
  deny: "deny";
148
+ ask: "ask";
147
149
  }>>]>>>;
148
150
  maxSteps: z.ZodOptional<z.ZodNumber>;
149
151
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -162,12 +164,12 @@ export declare const agentProfileModeSchema: z.ZodObject<{
162
164
  tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
163
165
  permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
164
166
  allow: "allow";
165
- ask: "ask";
166
167
  deny: "deny";
168
+ ask: "ask";
167
169
  }>, z.ZodRecord<z.ZodString, z.ZodEnum<{
168
170
  allow: "allow";
169
- ask: "ask";
170
171
  deny: "deny";
172
+ ask: "ask";
171
173
  }>>]>>>;
172
174
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
173
175
  }, z.core.$strip>;
@@ -216,10 +218,10 @@ export declare const agentProfileSchema: z.ZodObject<{
216
218
  small: z.ZodOptional<z.ZodString>;
217
219
  provider: z.ZodOptional<z.ZodString>;
218
220
  reasoningEffort: z.ZodOptional<z.ZodEnum<{
221
+ medium: "medium";
219
222
  none: "none";
220
223
  minimal: "minimal";
221
224
  low: "low";
222
- medium: "medium";
223
225
  high: "high";
224
226
  xhigh: "xhigh";
225
227
  ultracode: "ultracode";
@@ -228,12 +230,12 @@ export declare const agentProfileSchema: z.ZodObject<{
228
230
  }, z.core.$strip>>;
229
231
  permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
230
232
  allow: "allow";
231
- ask: "ask";
232
233
  deny: "deny";
234
+ ask: "ask";
233
235
  }>, z.ZodRecord<z.ZodString, z.ZodEnum<{
234
236
  allow: "allow";
235
- ask: "ask";
236
237
  deny: "deny";
238
+ ask: "ask";
237
239
  }>>]>>>;
238
240
  tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
239
241
  mcp: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -263,12 +265,12 @@ export declare const agentProfileSchema: z.ZodObject<{
263
265
  tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
264
266
  permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
265
267
  allow: "allow";
266
- ask: "ask";
267
268
  deny: "deny";
269
+ ask: "ask";
268
270
  }>, z.ZodRecord<z.ZodString, z.ZodEnum<{
269
271
  allow: "allow";
270
- ask: "ask";
271
272
  deny: "deny";
273
+ ask: "ask";
272
274
  }>>]>>>;
273
275
  maxSteps: z.ZodOptional<z.ZodNumber>;
274
276
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -360,12 +362,12 @@ export declare const agentProfileSchema: z.ZodObject<{
360
362
  tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
361
363
  permissions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<{
362
364
  allow: "allow";
363
- ask: "ask";
364
365
  deny: "deny";
366
+ ask: "ask";
365
367
  }>, z.ZodRecord<z.ZodString, z.ZodEnum<{
366
368
  allow: "allow";
367
- ask: "ask";
368
369
  deny: "deny";
370
+ ask: "ask";
369
371
  }>>]>>>;
370
372
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
371
373
  }, z.core.$strip>>>;
@@ -378,3 +380,24 @@ export declare const agentProfileSchema: z.ZodObject<{
378
380
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
379
381
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodUndefined]>>>;
380
382
  }, z.core.$strip>;
383
+ /**
384
+ * A registered capability: a stable id paired with its canonical
385
+ * {@link AgentProfile}. `definition` is the full profile (prompt/model/tools/
386
+ * mcp/permissions), so a capability carries the whole agent shape, not just a
387
+ * system prompt. The platform capability registry validates and stores these.
388
+ */
389
+ export interface Capability {
390
+ /** Stable, deterministic id — the key a workflow's `agent.run.profile` names. */
391
+ id: string;
392
+ /** The canonical agent profile (prompt/model/tools/mcp/permissions). */
393
+ definition: AgentProfile;
394
+ /**
395
+ * Recommended compute tier for a sandbox running this capability. A dispatcher
396
+ * uses it as the size DEFAULT when the caller does not pick one — so a
397
+ * capability that only ever does thin work defaults to a small box instead of
398
+ * a maxed one. The caller may always override it per dispatch. Omitted → the
399
+ * dispatcher's own default tier.
400
+ */
401
+ recommendedSize?: SandboxSizePreset;
402
+ }
403
+ export declare const capabilitySchema: z.ZodType<Capability>;
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { SANDBOX_SIZE_PRESET_NAMES, } from "./sandbox-size.js";
2
3
  export const agentProfilePermissionValueSchema = z.enum([
3
4
  "allow",
4
5
  "deny",
@@ -126,3 +127,8 @@ export const agentProfileSchema = z.object({
126
127
  });
127
128
  const _agentProfileSchemaMatchesInterface = true;
128
129
  void _agentProfileSchemaMatchesInterface;
130
+ export const capabilitySchema = z.object({
131
+ id: z.string().min(1),
132
+ definition: agentProfileSchema,
133
+ recommendedSize: z.enum(SANDBOX_SIZE_PRESET_NAMES).optional(),
134
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Security validation for inline {@link AgentProfile} values.
3
+ *
4
+ * When a caller sends a full profile inline (rather than naming a curated,
5
+ * trusted capability), the profile is author-controlled and may declare surfaces
6
+ * that execute code OUTSIDE the agent's own reasoning — a stdio/local MCP server
7
+ * (an arbitrary command spawned at startup) or a hook (a shell command run
8
+ * automatically around the turn). A sandbox isolates the workload, but these
9
+ * surfaces run unattended on the owner's behalf, so a cloud dispatcher must gate
10
+ * them before materializing the profile.
11
+ *
12
+ * This validates the CANONICAL agent-interface shape (`mcp` keyed by
13
+ * `AgentProfileMcpServer`, `hooks` keyed by `AgentProfileHookCommand[]`) — the
14
+ * provider-neutral contract every backend translates from. The opencode-native
15
+ * `validateProfileSecurity` (in `sdk-provider-opencode`) operates on opencode's
16
+ * own profile shape and is a different layer; this is the one to use at the
17
+ * application boundary where profiles are still provider-neutral.
18
+ */
19
+ import type { AgentProfile, AgentProfileValidationResult } from "./agent-profile.js";
20
+ /** Policy for {@link validateAgentProfileSecurity}. */
21
+ export interface AgentProfileSecurityPolicy {
22
+ /**
23
+ * Allow stdio/local MCP servers (a spawned local process). Off in cloud: an
24
+ * inline profile must not start arbitrary commands at MCP init.
25
+ */
26
+ allowLocalMcp: boolean;
27
+ /**
28
+ * Allow lifecycle hooks (shell commands run automatically around the turn).
29
+ * Off in cloud: hooks are author-controlled shell outside the agent's loop.
30
+ */
31
+ allowHooks: boolean;
32
+ /**
33
+ * Glob allowlist for REMOTE MCP hosts (http/sse `url` hostnames). A set list
34
+ * rejects any host that matches no pattern; an empty list (`[]`) blocks ALL
35
+ * remote MCP.
36
+ *
37
+ * SECURITY: `undefined` leaves remote MCP hosts UNRESTRICTED — it does NOT
38
+ * protect against SSRF (an inline profile could point an MCP server at, e.g.,
39
+ * `http://169.254.169.254/` cloud metadata or an internal address). When this
40
+ * is `undefined`, the caller MUST enforce SSRF/egress controls at the network
41
+ * layer, or set a restrictive allowlist (or `[]`) to fail closed.
42
+ */
43
+ allowedMcpHosts?: string[];
44
+ /**
45
+ * Allow `connections` (hub-managed integration grants). `undefined` leaves them
46
+ * allowed — a connection is a legitimate inline-profile feature where the host
47
+ * wires it. A host that does NOT support inline connection grants (e.g. a
48
+ * surface that grants hub access through a separate, audited path) sets this
49
+ * `false` so an inline profile cannot smuggle hub access through the profile.
50
+ */
51
+ allowConnections?: boolean;
52
+ }
53
+ /**
54
+ * Default cloud policy: block the two unattended-code surfaces (local MCP,
55
+ * hooks); leave remote MCP and everything else to the profile. Deliberately
56
+ * narrow — it gates code execution paths, not the agent's normal tools/edits,
57
+ * which the sandbox already isolates.
58
+ *
59
+ * NOTE: this default leaves `allowedMcpHosts` undefined, so REMOTE MCP hosts are
60
+ * unrestricted — it is NOT an SSRF guard (see `allowedMcpHosts`). A surface that
61
+ * must fail closed against arbitrary MCP egress should set `allowedMcpHosts`
62
+ * (e.g. `[]` to block all remote MCP), as the workflow inline-profile policy does.
63
+ */
64
+ export declare const DEFAULT_CLOUD_AGENT_PROFILE_SECURITY_POLICY: AgentProfileSecurityPolicy;
65
+ /**
66
+ * Validate an inline profile against a security policy. Returns `ok: false` with
67
+ * `error`-level issues when the profile declares a blocked surface; warnings do
68
+ * not fail. Pure and synchronous — safe to run at author (compile) time and
69
+ * again at dispatch as defense in depth.
70
+ */
71
+ export declare function validateAgentProfileSecurity(profile: AgentProfile, policy?: AgentProfileSecurityPolicy): AgentProfileValidationResult;
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Security validation for inline {@link AgentProfile} values.
3
+ *
4
+ * When a caller sends a full profile inline (rather than naming a curated,
5
+ * trusted capability), the profile is author-controlled and may declare surfaces
6
+ * that execute code OUTSIDE the agent's own reasoning — a stdio/local MCP server
7
+ * (an arbitrary command spawned at startup) or a hook (a shell command run
8
+ * automatically around the turn). A sandbox isolates the workload, but these
9
+ * surfaces run unattended on the owner's behalf, so a cloud dispatcher must gate
10
+ * them before materializing the profile.
11
+ *
12
+ * This validates the CANONICAL agent-interface shape (`mcp` keyed by
13
+ * `AgentProfileMcpServer`, `hooks` keyed by `AgentProfileHookCommand[]`) — the
14
+ * provider-neutral contract every backend translates from. The opencode-native
15
+ * `validateProfileSecurity` (in `sdk-provider-opencode`) operates on opencode's
16
+ * own profile shape and is a different layer; this is the one to use at the
17
+ * application boundary where profiles are still provider-neutral.
18
+ */
19
+ /**
20
+ * Default cloud policy: block the two unattended-code surfaces (local MCP,
21
+ * hooks); leave remote MCP and everything else to the profile. Deliberately
22
+ * narrow — it gates code execution paths, not the agent's normal tools/edits,
23
+ * which the sandbox already isolates.
24
+ *
25
+ * NOTE: this default leaves `allowedMcpHosts` undefined, so REMOTE MCP hosts are
26
+ * unrestricted — it is NOT an SSRF guard (see `allowedMcpHosts`). A surface that
27
+ * must fail closed against arbitrary MCP egress should set `allowedMcpHosts`
28
+ * (e.g. `[]` to block all remote MCP), as the workflow inline-profile policy does.
29
+ */
30
+ export const DEFAULT_CLOUD_AGENT_PROFILE_SECURITY_POLICY = {
31
+ allowLocalMcp: false,
32
+ allowHooks: false,
33
+ };
34
+ /**
35
+ * Match one DNS label against one pattern label, where `*` matches any run of
36
+ * characters WITHIN the label (never a `.`). Split-on-`*` with linear
37
+ * prefix/suffix/in-order scanning — deliberately NOT a constructed `RegExp`, so
38
+ * on this security boundary it stays provably linear with no catastrophic-
39
+ * backtracking surface whatever pattern an allowlist carries.
40
+ */
41
+ function matchLabel(label, pattern) {
42
+ const segments = pattern.split("*");
43
+ if (segments.length === 1)
44
+ return label === segments[0]; // no wildcard
45
+ const first = segments[0];
46
+ const last = segments[segments.length - 1];
47
+ if (!label.startsWith(first) || !label.endsWith(last))
48
+ return false;
49
+ // Prefix and suffix may not overlap (e.g. `aa*aa` must not match `aaa`).
50
+ if (first.length + last.length > label.length)
51
+ return false;
52
+ let cursor = first.length;
53
+ const suffixStart = label.length - last.length;
54
+ for (let i = 1; i < segments.length - 1; i += 1) {
55
+ const seg = segments[i];
56
+ if (seg.length === 0)
57
+ continue;
58
+ const found = label.indexOf(seg, cursor);
59
+ if (found === -1 || found + seg.length > suffixStart)
60
+ return false;
61
+ cursor = found + seg.length;
62
+ }
63
+ return true;
64
+ }
65
+ /**
66
+ * Case-insensitive host glob (`*` only) for allowlists, matched at DNS LABEL
67
+ * granularity: the host and pattern are split on `.` and matched label-by-label,
68
+ * and a `*` matches within a single label only — it never crosses a `.`. So
69
+ * `*.example.com` matches `api.example.com` but NOT `evil-example.com` (no dot
70
+ * boundary), `a.b.example.com` (extra label), or the bare apex `example.com`
71
+ * (missing label). This prevents a wildcard from reaching an unintended sibling
72
+ * or deeper domain on the security boundary.
73
+ *
74
+ * An IPv6 host (the hostname contains `:`) is matched EXACTLY — never split or
75
+ * globbed — since `.`-label semantics don't apply to it (and an IPv4-mapped form
76
+ * like `::ffff:1.2.3.4` contains dots that would otherwise glob unpredictably).
77
+ */
78
+ function matchHostGlob(host, pattern) {
79
+ const h = host.toLowerCase();
80
+ const p = pattern.toLowerCase();
81
+ if (h.includes(":") || p.includes(":"))
82
+ return h === p;
83
+ const hostLabels = h.split(".");
84
+ const patternLabels = p.split(".");
85
+ if (hostLabels.length !== patternLabels.length)
86
+ return false;
87
+ return patternLabels.every((label, i) => matchLabel(hostLabels[i], label));
88
+ }
89
+ /**
90
+ * A local/stdio MCP server spawns a process; a remote one connects over the
91
+ * network. ANY `command` makes it local — a spawnable process command is the
92
+ * thing being gated, and it stays dangerous whatever `transport` is declared
93
+ * alongside it (pairing `command` with `transport: "sse"`/`"http"` must not slip
94
+ * it past as "remote"). `transport: "stdio"` is local even with no command.
95
+ */
96
+ function isLocalMcpServer(server) {
97
+ return server.command !== undefined || server.transport === "stdio";
98
+ }
99
+ /**
100
+ * Validate an inline profile against a security policy. Returns `ok: false` with
101
+ * `error`-level issues when the profile declares a blocked surface; warnings do
102
+ * not fail. Pure and synchronous — safe to run at author (compile) time and
103
+ * again at dispatch as defense in depth.
104
+ */
105
+ export function validateAgentProfileSecurity(profile, policy = DEFAULT_CLOUD_AGENT_PROFILE_SECURITY_POLICY) {
106
+ const issues = [];
107
+ for (const [name, server] of Object.entries(profile.mcp ?? {})) {
108
+ if (isLocalMcpServer(server)) {
109
+ if (!policy.allowLocalMcp) {
110
+ issues.push({
111
+ level: "error",
112
+ code: "BLOCKED_LOCAL_MCP",
113
+ message: `local/stdio MCP server '${name}' is not allowed (it spawns an arbitrary process)`,
114
+ path: `mcp.${name}`,
115
+ });
116
+ }
117
+ continue;
118
+ }
119
+ if (policy.allowedMcpHosts) {
120
+ // An allowlist is active, so a non-local server MUST present a matchable
121
+ // host — a missing/empty `url` cannot be allowlist-checked, so it fails
122
+ // closed rather than slipping through unvalidated.
123
+ if (!server.url) {
124
+ issues.push({
125
+ level: "error",
126
+ code: "INVALID_MCP_URL",
127
+ message: `remote MCP server '${name}' has no url to check against the allowlist`,
128
+ path: `mcp.${name}`,
129
+ });
130
+ continue;
131
+ }
132
+ let host;
133
+ try {
134
+ host = new URL(server.url).hostname;
135
+ }
136
+ catch {
137
+ issues.push({
138
+ level: "error",
139
+ code: "INVALID_MCP_URL",
140
+ message: `MCP server '${name}' has an invalid url: ${server.url}`,
141
+ path: `mcp.${name}`,
142
+ });
143
+ continue;
144
+ }
145
+ if (!policy.allowedMcpHosts.some((p) => matchHostGlob(host, p))) {
146
+ issues.push({
147
+ level: "error",
148
+ code: "BLOCKED_REMOTE_MCP_HOST",
149
+ message: `remote MCP host '${host}' is not in the allowlist`,
150
+ path: `mcp.${name}`,
151
+ });
152
+ }
153
+ }
154
+ }
155
+ if (!policy.allowHooks &&
156
+ profile.hooks &&
157
+ Object.keys(profile.hooks).length > 0) {
158
+ issues.push({
159
+ level: "error",
160
+ code: "BLOCKED_HOOKS",
161
+ message: "hooks are not allowed (they run author-controlled shell commands automatically)",
162
+ path: "hooks",
163
+ });
164
+ }
165
+ if (policy.allowConnections === false &&
166
+ profile.connections &&
167
+ profile.connections.length > 0) {
168
+ issues.push({
169
+ level: "error",
170
+ code: "BLOCKED_CONNECTIONS",
171
+ message: "hub connections are not allowed in an inline profile here — grant hub access through this surface's supported connection path instead",
172
+ path: "connections",
173
+ });
174
+ }
175
+ return {
176
+ ok: !issues.some((i) => i.level === "error"),
177
+ issues,
178
+ };
179
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Portable sandbox size vocabulary.
3
+ *
4
+ * A size preset is a provider-neutral NAME for a compute tier (cpu / memory /
5
+ * disk). This module owns only the vocabulary — the names and their smallest→
6
+ * largest ordering — so the lowest shared layer can reference a size without
7
+ * depending on the sandbox SDK (`Capability.recommendedSize` lives here, and
8
+ * agent-interface must not depend on `@tangle-network/sandbox`).
9
+ *
10
+ * The concrete cpu/memory/disk numbers for each preset are the sandbox SDK's
11
+ * single source of truth (`@tangle-network/sandbox` → `SANDBOX_SIZE_PRESETS`),
12
+ * which imports these names. Mirrors how this package owns the `ReasoningEffort`
13
+ * vocabulary while backends own its native mapping.
14
+ */
15
+ /** Compute tiers, ordered smallest → largest. */
16
+ export declare const SANDBOX_SIZE_PRESET_NAMES: readonly ["nano", "small", "medium", "large"];
17
+ /**
18
+ * A named compute tier for a sandbox. `nano` suits thin glue work (a single API
19
+ * call, a notify); `large` suits heavy builds over big repositories. Sizing is a
20
+ * per-task decision — a thin workflow step should not provision a maxed box.
21
+ */
22
+ export type SandboxSizePreset = (typeof SANDBOX_SIZE_PRESET_NAMES)[number];
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Portable sandbox size vocabulary.
3
+ *
4
+ * A size preset is a provider-neutral NAME for a compute tier (cpu / memory /
5
+ * disk). This module owns only the vocabulary — the names and their smallest→
6
+ * largest ordering — so the lowest shared layer can reference a size without
7
+ * depending on the sandbox SDK (`Capability.recommendedSize` lives here, and
8
+ * agent-interface must not depend on `@tangle-network/sandbox`).
9
+ *
10
+ * The concrete cpu/memory/disk numbers for each preset are the sandbox SDK's
11
+ * single source of truth (`@tangle-network/sandbox` → `SANDBOX_SIZE_PRESETS`),
12
+ * which imports these names. Mirrors how this package owns the `ReasoningEffort`
13
+ * vocabulary while backends own its native mapping.
14
+ */
15
+ /** Compute tiers, ordered smallest → largest. */
16
+ export const SANDBOX_SIZE_PRESET_NAMES = [
17
+ "nano",
18
+ "small",
19
+ "medium",
20
+ "large",
21
+ ];
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-interface",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
4
4
  "type": "module",
5
+ "license": "MIT",
5
6
  "main": "./dist/index.js",
6
7
  "types": "./dist/index.d.ts",
7
8
  "exports": {
@@ -11,8 +12,19 @@
11
12
  "default": "./dist/index.js"
12
13
  }
13
14
  },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/tangle-network/agent-sdk.git",
18
+ "directory": "packages/agent-interface"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public",
22
+ "registry": "https://registry.npmjs.org"
23
+ },
14
24
  "files": [
15
- "dist"
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
16
28
  ],
17
29
  "dependencies": {
18
30
  "zod": "4.4.3"
@@ -23,6 +35,7 @@
23
35
  },
24
36
  "scripts": {
25
37
  "build": "tsc -p tsconfig.json",
38
+ "check-types": "tsc --noEmit",
26
39
  "clean": "rm -rf dist"
27
40
  }
28
41
  }