@tangle-network/agent-app 0.42.14 → 0.42.15

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.
@@ -0,0 +1,73 @@
1
+ // src/harness/index.ts
2
+ import {
3
+ harnessSupportsModel,
4
+ modelProvider,
5
+ preferredHarnessForModel,
6
+ snapHarnessToModel as aiSnapHarnessToModel,
7
+ snapModelToHarness as aiSnapModelToHarness
8
+ } from "@tangle-network/agent-interface";
9
+ var KNOWN_HARNESSES = [
10
+ "opencode",
11
+ "claude-code",
12
+ "kimi-code",
13
+ "codex",
14
+ "amp",
15
+ "factory-droids",
16
+ "pi",
17
+ "hermes",
18
+ "forge",
19
+ "openclaw",
20
+ "acp",
21
+ "cursor",
22
+ "cli-base"
23
+ ];
24
+ var DEFAULT_HARNESS = "opencode";
25
+ var HARNESS_SET = new Set(KNOWN_HARNESSES);
26
+ function isHarness(value) {
27
+ return typeof value === "string" && HARNESS_SET.has(value);
28
+ }
29
+ function coerceHarness(value, fallback = DEFAULT_HARNESS) {
30
+ return isHarness(value) ? value : fallback;
31
+ }
32
+ function resolveSessionHarness(input = {}) {
33
+ const fallback = input.fallback ?? DEFAULT_HARNESS;
34
+ if (isHarness(input.sessionHarness)) {
35
+ const locked = input.sessionHarness;
36
+ const swapAttempted = isHarness(input.requested) && input.requested !== locked;
37
+ return { harness: locked, locked: true, swapAttempted };
38
+ }
39
+ const harness = coerceHarness(input.requested, coerceHarness(input.workspaceDefault, fallback));
40
+ return { harness, locked: false, swapAttempted: false };
41
+ }
42
+ function isModelCompatibleWithHarness(harness, modelId) {
43
+ return harnessSupportsModel(harness, modelId);
44
+ }
45
+ function snapModelToHarness(harness, modelId, canonicalIds) {
46
+ return aiSnapModelToHarness(harness, modelId, canonicalIds);
47
+ }
48
+ function snapHarnessToModel(harness, modelId) {
49
+ return aiSnapHarnessToModel(harness, modelId);
50
+ }
51
+ function assertHarnessModelCompatible(harness, modelId) {
52
+ if (!isModelCompatibleWithHarness(harness, modelId)) {
53
+ const provider = modelProvider(modelId);
54
+ const native = preferredHarnessForModel(modelId);
55
+ throw new Error(
56
+ `Harness "${harness}" cannot run model "${modelId}" (provider "${provider}"). Use ${native ?? "a router-backed harness (opencode)"} or an allowed model.`
57
+ );
58
+ }
59
+ }
60
+
61
+ export {
62
+ modelProvider,
63
+ KNOWN_HARNESSES,
64
+ DEFAULT_HARNESS,
65
+ isHarness,
66
+ coerceHarness,
67
+ resolveSessionHarness,
68
+ isModelCompatibleWithHarness,
69
+ snapModelToHarness,
70
+ snapHarnessToModel,
71
+ assertHarnessModelCompatible
72
+ };
73
+ //# sourceMappingURL=chunk-E7QYOOON.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/harness/index.ts"],"sourcesContent":["/**\n * Coding-agent harness selection — taxonomy, coercion, and the session-lock invariant.\n *\n * A \"harness\" is the coding-agent CLI a sandbox drives (opencode / codex /\n * claude-code / …). The shell governs WHICH harness a chat session uses and\n * enforces that a session is LOCKED to the harness it started with — the model\n * may change mid-session, the harness may not (swapping it mid-session would\n * orphan the session's running agent state). Every product otherwise hand-rolls\n * this and hard-codes a single harness; this is the one place the rule lives.\n *\n * Substrate-free: the harness list mirrors the sandbox SDK's `BackendType` as a\n * plain string union (no sandbox dependency). The consumer owns storage — which\n * harness a workspace defaults to, which one a session locked — and maps the\n * resolved value onto the SDK's `backend.type`.\n *\n * Harness↔model COMPATIBILITY (which models a harness can run, snapping) is NOT defined here — it\n * comes from `@tangle-network/agent-interface`, the single source of truth shared with the\n * sandbox-ui pickers and the cli-bridge backends. This module owns the harness TAXONOMY + the\n * session lock.\n */\n\nimport {\n harnessSupportsModel,\n modelProvider,\n preferredHarnessForModel,\n snapHarnessToModel as aiSnapHarnessToModel,\n snapModelToHarness as aiSnapModelToHarness,\n type HarnessType,\n} from '@tangle-network/agent-interface'\n\n/** The known coding-agent backends. Mirrors `@tangle-network/sandbox`'s\n * `BackendType`; kept structural so this module needs no sandbox dependency. */\nexport const KNOWN_HARNESSES = [\n 'opencode',\n 'claude-code',\n 'kimi-code',\n 'codex',\n 'amp',\n 'factory-droids',\n 'pi',\n 'hermes',\n 'forge',\n 'openclaw',\n 'acp',\n 'cursor',\n 'cli-base',\n] as const\n\nexport type Harness = (typeof KNOWN_HARNESSES)[number]\n\nexport const DEFAULT_HARNESS: Harness = 'opencode'\n\nconst HARNESS_SET: ReadonlySet<string> = new Set(KNOWN_HARNESSES)\n\nexport function isHarness(value: unknown): value is Harness {\n return typeof value === 'string' && HARNESS_SET.has(value)\n}\n\n/** Coerce an arbitrary value to a known harness, falling back (default `opencode`). */\nexport function coerceHarness(value: unknown, fallback: Harness = DEFAULT_HARNESS): Harness {\n return isHarness(value) ? value : fallback\n}\n\nexport interface ResolveSessionHarnessInput {\n /** The harness already locked to this session (recorded at its first turn). */\n sessionHarness?: unknown\n /** The harness requested now — a new session's choice, or a turn's attempt to switch. */\n requested?: unknown\n /** The workspace's default harness, used only when starting a fresh session. */\n workspaceDefault?: unknown\n /** Final fallback when nothing else resolves (default `opencode`). */\n fallback?: Harness\n}\n\nexport interface ResolvedSessionHarness {\n /** The harness to actually run — the locked one when the session already has it. */\n harness: Harness\n /** True when the session already had a locked harness (this turn did not pick it). */\n locked: boolean\n /** True when `requested` differs from the locked harness — a forbidden mid-session\n * swap the caller should reject or warn on. The lock always wins regardless. */\n swapAttempted: boolean\n}\n\n/**\n * Resolve the harness for a turn, enforcing the session lock.\n *\n * - **Session already started** (`sessionHarness` is a known harness): that harness\n * wins (`locked: true`); a differing `requested` sets `swapAttempted` so the caller\n * can reject the swap. The model is a separate per-turn concern and is unaffected.\n * - **Fresh session**: pick `requested → workspaceDefault → fallback`. The caller\n * persists the result as the session's lock for every subsequent turn.\n */\nexport function resolveSessionHarness(input: ResolveSessionHarnessInput = {}): ResolvedSessionHarness {\n const fallback = input.fallback ?? DEFAULT_HARNESS\n if (isHarness(input.sessionHarness)) {\n const locked = input.sessionHarness\n const swapAttempted = isHarness(input.requested) && input.requested !== locked\n return { harness: locked, locked: true, swapAttempted }\n }\n const harness = coerceHarness(input.requested, coerceHarness(input.workspaceDefault, fallback))\n return { harness, locked: false, swapAttempted: false }\n}\n\n/**\n * Harness ↔ model compatibility + snapping — delegated to `@tangle-network/agent-interface`.\n *\n * agent-app's `Harness` taxonomy is a superset of agent-interface's `HarnessType` (it carries\n * `forge`/`cursor`, which agent-interface doesn't list). Those extra runners have no provider lock\n * there, so they resolve as router-backed (any model) — the correct behavior — which makes the\n * `as HarnessType` casts safe. The snap helpers only ever return a vendor-locked harness or\n * `opencode`, all of which are valid `Harness` values.\n */\n\nexport { modelProvider }\n\n/** Provider-less ids (sentinels like \"default\", or a session's own config) are\n * compatible everywhere — every harness honors its own configuration. */\nexport function isModelCompatibleWithHarness(harness: Harness, modelId: string): boolean {\n return harnessSupportsModel(harness as HarnessType, modelId)\n}\n\n/** Keep `modelId` when the harness can run it; else the harness's best compatible\n * catalog id (preferred patterns in order, highest version). When nothing in the\n * catalog fits, return the original so the caller sees the incompatibility. */\nexport function snapModelToHarness(harness: Harness, modelId: string, canonicalIds: readonly string[]): string {\n return aiSnapModelToHarness(harness as HarnessType, modelId, canonicalIds)\n}\n\n/** Keep the harness when it can run `modelId`; else the model's native harness\n * (anthropic → claude-code, openai → codex, moonshot → kimi-code), falling back to opencode. */\nexport function snapHarnessToModel(harness: Harness, modelId: string): Harness {\n return aiSnapHarnessToModel(harness as HarnessType, modelId) as Harness\n}\n\n/** Fail-loud server guard: throw when a harness is asked to run a model it can't.\n * Call before dispatching a sandbox turn so a bypassed UI can't reach the sidecar\n * with an incompatible pair. */\nexport function assertHarnessModelCompatible(harness: Harness, modelId: string): void {\n if (!isModelCompatibleWithHarness(harness, modelId)) {\n const provider = modelProvider(modelId)\n const native = preferredHarnessForModel(modelId)\n throw new Error(\n `Harness \"${harness}\" cannot run model \"${modelId}\" (provider \"${provider}\"). ` +\n `Use ${native ?? 'a router-backed harness (opencode)'} or an allowed model.`,\n )\n }\n}\n"],"mappings":";AAqBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB,sBAAsB;AAAA,OAEjB;AAIA,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,IAAM,kBAA2B;AAExC,IAAM,cAAmC,IAAI,IAAI,eAAe;AAEzD,SAAS,UAAU,OAAkC;AAC1D,SAAO,OAAO,UAAU,YAAY,YAAY,IAAI,KAAK;AAC3D;AAGO,SAAS,cAAc,OAAgB,WAAoB,iBAA0B;AAC1F,SAAO,UAAU,KAAK,IAAI,QAAQ;AACpC;AAgCO,SAAS,sBAAsB,QAAoC,CAAC,GAA2B;AACpG,QAAM,WAAW,MAAM,YAAY;AACnC,MAAI,UAAU,MAAM,cAAc,GAAG;AACnC,UAAM,SAAS,MAAM;AACrB,UAAM,gBAAgB,UAAU,MAAM,SAAS,KAAK,MAAM,cAAc;AACxE,WAAO,EAAE,SAAS,QAAQ,QAAQ,MAAM,cAAc;AAAA,EACxD;AACA,QAAM,UAAU,cAAc,MAAM,WAAW,cAAc,MAAM,kBAAkB,QAAQ,CAAC;AAC9F,SAAO,EAAE,SAAS,QAAQ,OAAO,eAAe,MAAM;AACxD;AAgBO,SAAS,6BAA6B,SAAkB,SAA0B;AACvF,SAAO,qBAAqB,SAAwB,OAAO;AAC7D;AAKO,SAAS,mBAAmB,SAAkB,SAAiB,cAAyC;AAC7G,SAAO,qBAAqB,SAAwB,SAAS,YAAY;AAC3E;AAIO,SAAS,mBAAmB,SAAkB,SAA0B;AAC7E,SAAO,qBAAqB,SAAwB,OAAO;AAC7D;AAKO,SAAS,6BAA6B,SAAkB,SAAuB;AACpF,MAAI,CAAC,6BAA6B,SAAS,OAAO,GAAG;AACnD,UAAM,WAAW,cAAc,OAAO;AACtC,UAAM,SAAS,yBAAyB,OAAO;AAC/C,UAAM,IAAI;AAAA,MACR,YAAY,OAAO,uBAAuB,OAAO,gBAAgB,QAAQ,WAChE,UAAU,oCAAoC;AAAA,IACzD;AAAA,EACF;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  assertHarnessModelCompatible
3
- } from "./chunk-5VXPDXZJ.js";
3
+ } from "./chunk-E7QYOOON.js";
4
4
  import {
5
5
  base64UrlDecodeText,
6
6
  base64UrlEncodeText,
@@ -1431,4 +1431,4 @@ export {
1431
1431
  isTerminalPromptEvent,
1432
1432
  detectInteractiveQuestion
1433
1433
  };
1434
- //# sourceMappingURL=chunk-JLALKMDY.js.map
1434
+ //# sourceMappingURL=chunk-ILCF6KBD.js.map
@@ -1,3 +1,5 @@
1
+ export { modelProvider } from '@tangle-network/agent-interface';
2
+
1
3
  /**
2
4
  * Coding-agent harness selection — taxonomy, coercion, and the session-lock invariant.
3
5
  *
@@ -12,7 +14,13 @@
12
14
  * plain string union (no sandbox dependency). The consumer owns storage — which
13
15
  * harness a workspace defaults to, which one a session locked — and maps the
14
16
  * resolved value onto the SDK's `backend.type`.
17
+ *
18
+ * Harness↔model COMPATIBILITY (which models a harness can run, snapping) is NOT defined here — it
19
+ * comes from `@tangle-network/agent-interface`, the single source of truth shared with the
20
+ * sandbox-ui pickers and the cli-bridge backends. This module owns the harness TAXONOMY + the
21
+ * session lock.
15
22
  */
23
+
16
24
  /** The known coding-agent backends. Mirrors `@tangle-network/sandbox`'s
17
25
  * `BackendType`; kept structural so this module needs no sandbox dependency. */
18
26
  declare const KNOWN_HARNESSES: readonly ["opencode", "claude-code", "kimi-code", "codex", "amp", "factory-droids", "pi", "hermes", "forge", "openclaw", "acp", "cursor", "cli-base"];
@@ -50,28 +58,7 @@ interface ResolvedSessionHarness {
50
58
  * persists the result as the session's lock for every subsequent turn.
51
59
  */
52
60
  declare function resolveSessionHarness(input?: ResolveSessionHarnessInput): ResolvedSessionHarness;
53
- /**
54
- * Harness ↔ model compatibility policy — the ONE source of truth, server-side.
55
- *
56
- * Native CLI harnesses are vendor-locked: claude-code only drives Anthropic
57
- * models, codex only OpenAI, kimi-code only Moonshot. Router-backed harnesses
58
- * (opencode, etc.) accept any catalog model (`providers: null`). The pickers in
59
- * sandbox-ui keep the pair coherent in the UI; this is the same policy the SHELL
60
- * enforces so a bypassed/forged request can't pair claude-code with a gpt model
61
- * and fail at the sidecar. Operates on plain canonical ids ("provider/model") so
62
- * it stays substrate-free — no model-catalog or UI dependency.
63
- */
64
- interface HarnessModelPolicy {
65
- /** Canonical-id provider prefixes the harness can run; null = any. */
66
- providers: readonly string[] | null;
67
- /** Snap-target patterns, best first; highest version within a pattern wins. */
68
- preferred: readonly RegExp[];
69
- }
70
- declare const HARNESS_MODEL_POLICIES: Partial<Record<Harness, HarnessModelPolicy>>;
71
- /** Native harness for a model's provider (anthropic → claude-code, …). */
72
- declare const PROVIDER_PREFERRED_HARNESS: Record<string, Harness>;
73
- /** Provider prefix of a canonical id ("anthropic/claude-…" → "anthropic"). */
74
- declare function modelProvider(modelId: string): string | null;
61
+
75
62
  /** Provider-less ids (sentinels like "default", or a session's own config) are
76
63
  * compatible everywhere — every harness honors its own configuration. */
77
64
  declare function isModelCompatibleWithHarness(harness: Harness, modelId: string): boolean;
@@ -80,11 +67,11 @@ declare function isModelCompatibleWithHarness(harness: Harness, modelId: string)
80
67
  * catalog fits, return the original so the caller sees the incompatibility. */
81
68
  declare function snapModelToHarness(harness: Harness, modelId: string, canonicalIds: readonly string[]): string;
82
69
  /** Keep the harness when it can run `modelId`; else the model's native harness
83
- * (anthropic → claude-code, openai → codex), falling back to opencode. */
70
+ * (anthropic → claude-code, openai → codex, moonshot → kimi-code), falling back to opencode. */
84
71
  declare function snapHarnessToModel(harness: Harness, modelId: string): Harness;
85
72
  /** Fail-loud server guard: throw when a harness is asked to run a model it can't.
86
73
  * Call before dispatching a sandbox turn so a bypassed UI can't reach the sidecar
87
74
  * with an incompatible pair. */
88
75
  declare function assertHarnessModelCompatible(harness: Harness, modelId: string): void;
89
76
 
90
- export { DEFAULT_HARNESS, HARNESS_MODEL_POLICIES, type Harness, type HarnessModelPolicy, KNOWN_HARNESSES, PROVIDER_PREFERRED_HARNESS, type ResolveSessionHarnessInput, type ResolvedSessionHarness, assertHarnessModelCompatible, coerceHarness, isHarness, isModelCompatibleWithHarness, modelProvider, resolveSessionHarness, snapHarnessToModel, snapModelToHarness };
77
+ export { DEFAULT_HARNESS, type Harness, KNOWN_HARNESSES, type ResolveSessionHarnessInput, type ResolvedSessionHarness, assertHarnessModelCompatible, coerceHarness, isHarness, isModelCompatibleWithHarness, resolveSessionHarness, snapHarnessToModel, snapModelToHarness };
@@ -1,8 +1,6 @@
1
1
  import {
2
2
  DEFAULT_HARNESS,
3
- HARNESS_MODEL_POLICIES,
4
3
  KNOWN_HARNESSES,
5
- PROVIDER_PREFERRED_HARNESS,
6
4
  assertHarnessModelCompatible,
7
5
  coerceHarness,
8
6
  isHarness,
@@ -11,12 +9,10 @@ import {
11
9
  resolveSessionHarness,
12
10
  snapHarnessToModel,
13
11
  snapModelToHarness
14
- } from "../chunk-5VXPDXZJ.js";
12
+ } from "../chunk-E7QYOOON.js";
15
13
  export {
16
14
  DEFAULT_HARNESS,
17
- HARNESS_MODEL_POLICIES,
18
15
  KNOWN_HARNESSES,
19
- PROVIDER_PREFERRED_HARNESS,
20
16
  assertHarnessModelCompatible,
21
17
  coerceHarness,
22
18
  isHarness,
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export { AgentRuntime, AgentRuntimeModelConfig, AgentTurnOptions, AnySurfaceKind
10
10
  export { createTokenRecallChecker, producedFromToolEvents } from './eval/index.js';
11
11
  export { KnowledgeRequirementSpec, KnowledgeSignal, KnowledgeStateAccessor, SatisfiedByRule, buildKnowledgeRequirements, deriveSignals } from './knowledge/index.js';
12
12
  export { CreateKnowledgeLoopDeps, KnowledgeCandidate, KnowledgeDecider, KnowledgeDeciderInput, KnowledgeDecision, KnowledgeGateVerdict, KnowledgeLoop, KnowledgeLoopDriver, createKnowledgeLoop, createReviewerDecider, reviewCandidate } from './knowledge-loop/index.js';
13
- export { DEFAULT_HARNESS, HARNESS_MODEL_POLICIES, Harness, HarnessModelPolicy, KNOWN_HARNESSES, PROVIDER_PREFERRED_HARNESS, ResolveSessionHarnessInput, ResolvedSessionHarness, assertHarnessModelCompatible, coerceHarness, isHarness, isModelCompatibleWithHarness, modelProvider, resolveSessionHarness, snapHarnessToModel, snapModelToHarness } from './harness/index.js';
13
+ export { DEFAULT_HARNESS, Harness, KNOWN_HARNESSES, ResolveSessionHarnessInput, ResolvedSessionHarness, assertHarnessModelCompatible, coerceHarness, isHarness, isModelCompatibleWithHarness, resolveSessionHarness, snapHarnessToModel, snapModelToHarness } from './harness/index.js';
14
14
  export { AgentAppConfig, AgentIdentityConfig, AgentIntegrationsConfig, AgentKnowledgeConfig, AgentTaxonomyConfig, AgentUiConfig, KnowledgeLoopConfig, KnowledgeSourceSpec, agentAppConfigJsonSchema, defineAgentApp } from './config/index.js';
15
15
  export { D1Like, D1PreparedLike, DrizzleColumnLike, DrizzleSqliteCoreLike, PRESET_MIGRATION_SQL, PRESET_TABLES, PresetBillingOptions, PresetKnowledgeAccessorOptions, PresetToolHandlerOptions, VaultKv, createD1KnowledgeStateAccessor, createPresetDrizzleSchema, createPresetFieldCrypto, createPresetToolHandlers, createPresetWorkspaceKeyManager, createPresetWorkspaceKeyStore } from './preset-cloudflare/index.js';
16
16
  export { KeyCrypto, KeyProvisioner, PlanLimit, PlatformBalanceInfo, PlatformBalanceManager, PlatformBalanceManagerOptions, PlatformBillingClient, PlatformIdentity, PlatformProductUsage, SharedBillingState, TcloudKeyClient, WorkspaceKeyManager, WorkspaceKeyManagerOptions, WorkspaceKeyRecord, WorkspaceKeyStore, WorkspaceModelKeyUsage, createPlatformBalanceManager, createTcloudKeyProvisioner, createWorkspaceKeyManager } from './billing/index.js';
@@ -37,6 +37,7 @@ export { RunToolLoopOptions as AppToolLoopOptions, ToolLoopAssistantToolCall as
37
37
  export { CompletionRequirement, CompletionVerdict, CorrectnessChecker, ProducedState, RuntimeEventLike, SatisfiedBy, TaskGold, createLlmCorrectnessChecker, extractProducedState, verifyCompletion, weightedComposite } from '@tangle-network/agent-eval';
38
38
  export { F as FlowSpan, a as FlowTrace } from './flow-types-Cb_AblZs.js';
39
39
  export { StorageConfig } from '@tangle-network/sandbox';
40
+ export { modelProvider } from '@tangle-network/agent-interface';
40
41
  import '@tangle-network/agent-runtime/intelligence';
41
42
  import '@tangle-network/agent-knowledge';
42
43
  import 'zod';
package/dist/index.js CHANGED
@@ -284,12 +284,10 @@ import {
284
284
  verifySandboxTerminalToken,
285
285
  verifyTerminalProxyToken,
286
286
  writeProfileFilesToBox
287
- } from "./chunk-JLALKMDY.js";
287
+ } from "./chunk-ILCF6KBD.js";
288
288
  import {
289
289
  DEFAULT_HARNESS,
290
- HARNESS_MODEL_POLICIES,
291
290
  KNOWN_HARNESSES,
292
- PROVIDER_PREFERRED_HARNESS,
293
291
  assertHarnessModelCompatible,
294
292
  coerceHarness,
295
293
  isHarness,
@@ -298,7 +296,7 @@ import {
298
296
  resolveSessionHarness,
299
297
  snapHarnessToModel,
300
298
  snapModelToHarness
301
- } from "./chunk-5VXPDXZJ.js";
299
+ } from "./chunk-E7QYOOON.js";
302
300
  import {
303
301
  agentAppConfigJsonSchema,
304
302
  defineAgentApp
@@ -408,7 +406,6 @@ export {
408
406
  DEFAULT_TANGLE_ROUTER_BASE_URL,
409
407
  EXPORT_PRESETS,
410
408
  EmailContentSchema,
411
- HARNESS_MODEL_POLICIES,
412
409
  HubExecClient,
413
410
  ImageContentSchema,
414
411
  KNOWN_HARNESSES,
@@ -419,7 +416,6 @@ export {
419
416
  MissionConcurrencyError,
420
417
  PRESET_MIGRATION_SQL,
421
418
  PRESET_TABLES,
422
- PROVIDER_PREFERRED_HARNESS,
423
419
  RetryableStepError,
424
420
  SCENE_ELEMENT_KINDS,
425
421
  SCENE_OPERATION_TYPES,
@@ -1,4 +1,5 @@
1
1
  import { Harness } from '../harness/index.js';
2
+ import '@tangle-network/agent-interface';
2
3
 
3
4
  /**
4
5
  * Execution-mode dispatch — the shell entry that infers *how* to run an agent
package/dist/run/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  KNOWN_HARNESSES
3
- } from "../chunk-5VXPDXZJ.js";
3
+ } from "../chunk-E7QYOOON.js";
4
4
 
5
5
  // src/run/index.ts
6
6
  var ROUTER_HARNESS = "router";
@@ -4,6 +4,7 @@ import { a as ToolHeaderNames } from '../auth-DuptSkWh.js';
4
4
  import { f as AppToolName, c as AppToolContext } from '../types-BEOvc_ue.js';
5
5
  import { Harness } from '../harness/index.js';
6
6
  import { f as TangleExecutionEnvironment } from '../model-dF2h4xT9.js';
7
+ import '@tangle-network/agent-interface';
7
8
 
8
9
  type Outcome<T> = {
9
10
  succeeded: true;
@@ -47,8 +47,8 @@ import {
47
47
  verifySandboxTerminalToken,
48
48
  verifyTerminalProxyToken,
49
49
  writeProfileFilesToBox
50
- } from "../chunk-JLALKMDY.js";
51
- import "../chunk-5VXPDXZJ.js";
50
+ } from "../chunk-ILCF6KBD.js";
51
+ import "../chunk-E7QYOOON.js";
52
52
  import "../chunk-3II3AWHY.js";
53
53
  import "../chunk-7EVZUIHW.js";
54
54
  import "../chunk-7W5XSTUF.js";
@@ -4,6 +4,7 @@ import { S as StepAgentActivity } from '../agent-activity-C8ZG0F0M.js';
4
4
  import { a as FlowTrace } from '../flow-types-Cb_AblZs.js';
5
5
  import { C as CatalogModel } from '../model-catalog-BEAEVDaa.js';
6
6
  import { Harness } from '../harness/index.js';
7
+ import '@tangle-network/agent-interface';
7
8
 
8
9
  /**
9
10
  * Client-side chat-stream consumption — the NDJSON parse loop every agent
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  snapHarnessToModel,
6
6
  snapModelToHarness
7
- } from "../chunk-5VXPDXZJ.js";
7
+ } from "../chunk-E7QYOOON.js";
8
8
 
9
9
  // src/web-react/index.tsx
10
10
  import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef4, useState as useState6, memo } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.42.14",
3
+ "version": "0.42.15",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -334,6 +334,7 @@
334
334
  "@radix-ui/react-dialog": "^1.1.15",
335
335
  "@tangle-network/agent-eval": "^0.99.0",
336
336
  "@tangle-network/agent-integrations": "^0.44.0",
337
+ "@tangle-network/agent-interface": "^0.15.0",
337
338
  "@tangle-network/agent-knowledge": "^1.7.0",
338
339
  "@tangle-network/agent-runtime": "^0.76.0",
339
340
  "@tangle-network/sandbox": "^0.9.5",
@@ -366,6 +367,7 @@
366
367
  "@radix-ui/react-dialog": ">=1.1",
367
368
  "@tangle-network/agent-eval": ">=0.99.0",
368
369
  "@tangle-network/agent-integrations": ">=0.44.0",
370
+ "@tangle-network/agent-interface": ">=0.15.0",
369
371
  "@tangle-network/agent-knowledge": ">=1.7.0",
370
372
  "@tangle-network/agent-runtime": ">=0.76.0",
371
373
  "@tangle-network/sandbox": ">=0.9.4",
@@ -1,101 +0,0 @@
1
- // src/harness/index.ts
2
- var KNOWN_HARNESSES = [
3
- "opencode",
4
- "claude-code",
5
- "kimi-code",
6
- "codex",
7
- "amp",
8
- "factory-droids",
9
- "pi",
10
- "hermes",
11
- "forge",
12
- "openclaw",
13
- "acp",
14
- "cursor",
15
- "cli-base"
16
- ];
17
- var DEFAULT_HARNESS = "opencode";
18
- var HARNESS_SET = new Set(KNOWN_HARNESSES);
19
- function isHarness(value) {
20
- return typeof value === "string" && HARNESS_SET.has(value);
21
- }
22
- function coerceHarness(value, fallback = DEFAULT_HARNESS) {
23
- return isHarness(value) ? value : fallback;
24
- }
25
- function resolveSessionHarness(input = {}) {
26
- const fallback = input.fallback ?? DEFAULT_HARNESS;
27
- if (isHarness(input.sessionHarness)) {
28
- const locked = input.sessionHarness;
29
- const swapAttempted = isHarness(input.requested) && input.requested !== locked;
30
- return { harness: locked, locked: true, swapAttempted };
31
- }
32
- const harness = coerceHarness(input.requested, coerceHarness(input.workspaceDefault, fallback));
33
- return { harness, locked: false, swapAttempted: false };
34
- }
35
- var HARNESS_MODEL_POLICIES = {
36
- "claude-code": {
37
- providers: ["anthropic"],
38
- preferred: [/^anthropic\/claude-opus-[\d.-]+$/, /^anthropic\/claude-sonnet-[\d.-]+$/, /^anthropic\//]
39
- },
40
- codex: {
41
- providers: ["openai"],
42
- preferred: [/^openai\/gpt-\d+(\.\d+)?$/, /^openai\/gpt/, /^openai\//]
43
- },
44
- "kimi-code": { providers: ["moonshot"], preferred: [/^moonshot\//] }
45
- };
46
- var PROVIDER_PREFERRED_HARNESS = {
47
- anthropic: "claude-code",
48
- openai: "codex",
49
- moonshot: "kimi-code"
50
- };
51
- function modelProvider(modelId) {
52
- const slash = modelId.indexOf("/");
53
- return slash > 0 ? modelId.slice(0, slash) : null;
54
- }
55
- function isModelCompatibleWithHarness(harness, modelId) {
56
- const policy = HARNESS_MODEL_POLICIES[harness];
57
- if (!policy || policy.providers === null) return true;
58
- const provider = modelProvider(modelId);
59
- if (!provider) return true;
60
- return policy.providers.includes(provider);
61
- }
62
- var numericDesc = new Intl.Collator(void 0, { numeric: true, sensitivity: "base" });
63
- function snapModelToHarness(harness, modelId, canonicalIds) {
64
- if (isModelCompatibleWithHarness(harness, modelId)) return modelId;
65
- const policy = HARNESS_MODEL_POLICIES[harness];
66
- if (!policy) return modelId;
67
- for (const pattern of policy.preferred) {
68
- const matches = canonicalIds.filter((id) => pattern.test(id)).sort((a, b) => numericDesc.compare(b, a));
69
- if (matches.length > 0) return matches[0];
70
- }
71
- return canonicalIds.find((id) => isModelCompatibleWithHarness(harness, id)) ?? modelId;
72
- }
73
- function snapHarnessToModel(harness, modelId) {
74
- if (isModelCompatibleWithHarness(harness, modelId)) return harness;
75
- const provider = modelProvider(modelId);
76
- return provider && PROVIDER_PREFERRED_HARNESS[provider] || "opencode";
77
- }
78
- function assertHarnessModelCompatible(harness, modelId) {
79
- if (!isModelCompatibleWithHarness(harness, modelId)) {
80
- const provider = modelProvider(modelId);
81
- throw new Error(
82
- `Harness "${harness}" cannot run model "${modelId}" (provider "${provider}"). Use ${PROVIDER_PREFERRED_HARNESS[provider ?? ""] ?? "a router-backed harness (opencode)"} or an allowed model.`
83
- );
84
- }
85
- }
86
-
87
- export {
88
- KNOWN_HARNESSES,
89
- DEFAULT_HARNESS,
90
- isHarness,
91
- coerceHarness,
92
- resolveSessionHarness,
93
- HARNESS_MODEL_POLICIES,
94
- PROVIDER_PREFERRED_HARNESS,
95
- modelProvider,
96
- isModelCompatibleWithHarness,
97
- snapModelToHarness,
98
- snapHarnessToModel,
99
- assertHarnessModelCompatible
100
- };
101
- //# sourceMappingURL=chunk-5VXPDXZJ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/harness/index.ts"],"sourcesContent":["/**\n * Coding-agent harness selection — taxonomy, coercion, and the session-lock invariant.\n *\n * A \"harness\" is the coding-agent CLI a sandbox drives (opencode / codex /\n * claude-code / …). The shell governs WHICH harness a chat session uses and\n * enforces that a session is LOCKED to the harness it started with — the model\n * may change mid-session, the harness may not (swapping it mid-session would\n * orphan the session's running agent state). Every product otherwise hand-rolls\n * this and hard-codes a single harness; this is the one place the rule lives.\n *\n * Substrate-free: the harness list mirrors the sandbox SDK's `BackendType` as a\n * plain string union (no sandbox dependency). The consumer owns storage — which\n * harness a workspace defaults to, which one a session locked — and maps the\n * resolved value onto the SDK's `backend.type`.\n */\n\n/** The known coding-agent backends. Mirrors `@tangle-network/sandbox`'s\n * `BackendType`; kept structural so this module needs no sandbox dependency. */\nexport const KNOWN_HARNESSES = [\n 'opencode',\n 'claude-code',\n 'kimi-code',\n 'codex',\n 'amp',\n 'factory-droids',\n 'pi',\n 'hermes',\n 'forge',\n 'openclaw',\n 'acp',\n 'cursor',\n 'cli-base',\n] as const\n\nexport type Harness = (typeof KNOWN_HARNESSES)[number]\n\nexport const DEFAULT_HARNESS: Harness = 'opencode'\n\nconst HARNESS_SET: ReadonlySet<string> = new Set(KNOWN_HARNESSES)\n\nexport function isHarness(value: unknown): value is Harness {\n return typeof value === 'string' && HARNESS_SET.has(value)\n}\n\n/** Coerce an arbitrary value to a known harness, falling back (default `opencode`). */\nexport function coerceHarness(value: unknown, fallback: Harness = DEFAULT_HARNESS): Harness {\n return isHarness(value) ? value : fallback\n}\n\nexport interface ResolveSessionHarnessInput {\n /** The harness already locked to this session (recorded at its first turn). */\n sessionHarness?: unknown\n /** The harness requested now — a new session's choice, or a turn's attempt to switch. */\n requested?: unknown\n /** The workspace's default harness, used only when starting a fresh session. */\n workspaceDefault?: unknown\n /** Final fallback when nothing else resolves (default `opencode`). */\n fallback?: Harness\n}\n\nexport interface ResolvedSessionHarness {\n /** The harness to actually run — the locked one when the session already has it. */\n harness: Harness\n /** True when the session already had a locked harness (this turn did not pick it). */\n locked: boolean\n /** True when `requested` differs from the locked harness — a forbidden mid-session\n * swap the caller should reject or warn on. The lock always wins regardless. */\n swapAttempted: boolean\n}\n\n/**\n * Resolve the harness for a turn, enforcing the session lock.\n *\n * - **Session already started** (`sessionHarness` is a known harness): that harness\n * wins (`locked: true`); a differing `requested` sets `swapAttempted` so the caller\n * can reject the swap. The model is a separate per-turn concern and is unaffected.\n * - **Fresh session**: pick `requested → workspaceDefault → fallback`. The caller\n * persists the result as the session's lock for every subsequent turn.\n */\nexport function resolveSessionHarness(input: ResolveSessionHarnessInput = {}): ResolvedSessionHarness {\n const fallback = input.fallback ?? DEFAULT_HARNESS\n if (isHarness(input.sessionHarness)) {\n const locked = input.sessionHarness\n const swapAttempted = isHarness(input.requested) && input.requested !== locked\n return { harness: locked, locked: true, swapAttempted }\n }\n const harness = coerceHarness(input.requested, coerceHarness(input.workspaceDefault, fallback))\n return { harness, locked: false, swapAttempted: false }\n}\n\n/**\n * Harness ↔ model compatibility policy — the ONE source of truth, server-side.\n *\n * Native CLI harnesses are vendor-locked: claude-code only drives Anthropic\n * models, codex only OpenAI, kimi-code only Moonshot. Router-backed harnesses\n * (opencode, etc.) accept any catalog model (`providers: null`). The pickers in\n * sandbox-ui keep the pair coherent in the UI; this is the same policy the SHELL\n * enforces so a bypassed/forged request can't pair claude-code with a gpt model\n * and fail at the sidecar. Operates on plain canonical ids (\"provider/model\") so\n * it stays substrate-free — no model-catalog or UI dependency.\n */\nexport interface HarnessModelPolicy {\n /** Canonical-id provider prefixes the harness can run; null = any. */\n providers: readonly string[] | null\n /** Snap-target patterns, best first; highest version within a pattern wins. */\n preferred: readonly RegExp[]\n}\n\nexport const HARNESS_MODEL_POLICIES: Partial<Record<Harness, HarnessModelPolicy>> = {\n 'claude-code': {\n providers: ['anthropic'],\n preferred: [/^anthropic\\/claude-opus-[\\d.-]+$/, /^anthropic\\/claude-sonnet-[\\d.-]+$/, /^anthropic\\//],\n },\n codex: {\n providers: ['openai'],\n preferred: [/^openai\\/gpt-\\d+(\\.\\d+)?$/, /^openai\\/gpt/, /^openai\\//],\n },\n 'kimi-code': { providers: ['moonshot'], preferred: [/^moonshot\\//] },\n}\n\n/** Native harness for a model's provider (anthropic → claude-code, …). */\nexport const PROVIDER_PREFERRED_HARNESS: Record<string, Harness> = {\n anthropic: 'claude-code',\n openai: 'codex',\n moonshot: 'kimi-code',\n}\n\n/** Provider prefix of a canonical id (\"anthropic/claude-…\" → \"anthropic\"). */\nexport function modelProvider(modelId: string): string | null {\n const slash = modelId.indexOf('/')\n return slash > 0 ? modelId.slice(0, slash) : null\n}\n\n/** Provider-less ids (sentinels like \"default\", or a session's own config) are\n * compatible everywhere — every harness honors its own configuration. */\nexport function isModelCompatibleWithHarness(harness: Harness, modelId: string): boolean {\n const policy = HARNESS_MODEL_POLICIES[harness]\n if (!policy || policy.providers === null) return true\n const provider = modelProvider(modelId)\n if (!provider) return true\n return policy.providers.includes(provider)\n}\n\nconst numericDesc = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })\n\n/** Keep `modelId` when the harness can run it; else the harness's best compatible\n * catalog id (preferred patterns in order, highest version). When nothing in the\n * catalog fits, return the original so the caller sees the incompatibility. */\nexport function snapModelToHarness(harness: Harness, modelId: string, canonicalIds: readonly string[]): string {\n if (isModelCompatibleWithHarness(harness, modelId)) return modelId\n const policy = HARNESS_MODEL_POLICIES[harness]\n if (!policy) return modelId\n for (const pattern of policy.preferred) {\n const matches = canonicalIds.filter((id) => pattern.test(id)).sort((a, b) => numericDesc.compare(b, a))\n if (matches.length > 0) return matches[0]!\n }\n return canonicalIds.find((id) => isModelCompatibleWithHarness(harness, id)) ?? modelId\n}\n\n/** Keep the harness when it can run `modelId`; else the model's native harness\n * (anthropic → claude-code, openai → codex), falling back to opencode. */\nexport function snapHarnessToModel(harness: Harness, modelId: string): Harness {\n if (isModelCompatibleWithHarness(harness, modelId)) return harness\n const provider = modelProvider(modelId)\n return (provider && PROVIDER_PREFERRED_HARNESS[provider]) || 'opencode'\n}\n\n/** Fail-loud server guard: throw when a harness is asked to run a model it can't.\n * Call before dispatching a sandbox turn so a bypassed UI can't reach the sidecar\n * with an incompatible pair. */\nexport function assertHarnessModelCompatible(harness: Harness, modelId: string): void {\n if (!isModelCompatibleWithHarness(harness, modelId)) {\n const provider = modelProvider(modelId)\n throw new Error(\n `Harness \"${harness}\" cannot run model \"${modelId}\" (provider \"${provider}\"). ` +\n `Use ${PROVIDER_PREFERRED_HARNESS[provider ?? ''] ?? 'a router-backed harness (opencode)'} or an allowed model.`,\n )\n }\n}\n"],"mappings":"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","names":[]}