@tangle-network/agent-app 0.1.5 → 0.1.6

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,43 @@
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
+
36
+ export {
37
+ KNOWN_HARNESSES,
38
+ DEFAULT_HARNESS,
39
+ isHarness,
40
+ coerceHarness,
41
+ resolveSessionHarness
42
+ };
43
+ //# sourceMappingURL=chunk-SD2H4FWY.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\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"],"mappings":";AAkBO,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;","names":[]}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Coding-agent harness selection — taxonomy, coercion, and the session-lock invariant.
3
+ *
4
+ * A "harness" is the coding-agent CLI a sandbox drives (opencode / codex /
5
+ * claude-code / …). The shell governs WHICH harness a chat session uses and
6
+ * enforces that a session is LOCKED to the harness it started with — the model
7
+ * may change mid-session, the harness may not (swapping it mid-session would
8
+ * orphan the session's running agent state). Every product otherwise hand-rolls
9
+ * this and hard-codes a single harness; this is the one place the rule lives.
10
+ *
11
+ * Substrate-free: the harness list mirrors the sandbox SDK's `BackendType` as a
12
+ * plain string union (no sandbox dependency). The consumer owns storage — which
13
+ * harness a workspace defaults to, which one a session locked — and maps the
14
+ * resolved value onto the SDK's `backend.type`.
15
+ */
16
+ /** The known coding-agent backends. Mirrors `@tangle-network/sandbox`'s
17
+ * `BackendType`; kept structural so this module needs no sandbox dependency. */
18
+ declare const KNOWN_HARNESSES: readonly ["opencode", "claude-code", "kimi-code", "codex", "amp", "factory-droids", "pi", "hermes", "forge", "openclaw", "acp", "cursor", "cli-base"];
19
+ type Harness = (typeof KNOWN_HARNESSES)[number];
20
+ declare const DEFAULT_HARNESS: Harness;
21
+ declare function isHarness(value: unknown): value is Harness;
22
+ /** Coerce an arbitrary value to a known harness, falling back (default `opencode`). */
23
+ declare function coerceHarness(value: unknown, fallback?: Harness): Harness;
24
+ interface ResolveSessionHarnessInput {
25
+ /** The harness already locked to this session (recorded at its first turn). */
26
+ sessionHarness?: unknown;
27
+ /** The harness requested now — a new session's choice, or a turn's attempt to switch. */
28
+ requested?: unknown;
29
+ /** The workspace's default harness, used only when starting a fresh session. */
30
+ workspaceDefault?: unknown;
31
+ /** Final fallback when nothing else resolves (default `opencode`). */
32
+ fallback?: Harness;
33
+ }
34
+ interface ResolvedSessionHarness {
35
+ /** The harness to actually run — the locked one when the session already has it. */
36
+ harness: Harness;
37
+ /** True when the session already had a locked harness (this turn did not pick it). */
38
+ locked: boolean;
39
+ /** True when `requested` differs from the locked harness — a forbidden mid-session
40
+ * swap the caller should reject or warn on. The lock always wins regardless. */
41
+ swapAttempted: boolean;
42
+ }
43
+ /**
44
+ * Resolve the harness for a turn, enforcing the session lock.
45
+ *
46
+ * - **Session already started** (`sessionHarness` is a known harness): that harness
47
+ * wins (`locked: true`); a differing `requested` sets `swapAttempted` so the caller
48
+ * can reject the swap. The model is a separate per-turn concern and is unaffected.
49
+ * - **Fresh session**: pick `requested → workspaceDefault → fallback`. The caller
50
+ * persists the result as the session's lock for every subsequent turn.
51
+ */
52
+ declare function resolveSessionHarness(input?: ResolveSessionHarnessInput): ResolvedSessionHarness;
53
+
54
+ export { DEFAULT_HARNESS, type Harness, KNOWN_HARNESSES, type ResolveSessionHarnessInput, type ResolvedSessionHarness, coerceHarness, isHarness, resolveSessionHarness };
@@ -0,0 +1,15 @@
1
+ import {
2
+ DEFAULT_HARNESS,
3
+ KNOWN_HARNESSES,
4
+ coerceHarness,
5
+ isHarness,
6
+ resolveSessionHarness
7
+ } from "../chunk-SD2H4FWY.js";
8
+ export {
9
+ DEFAULT_HARNESS,
10
+ KNOWN_HARNESSES,
11
+ coerceHarness,
12
+ isHarness,
13
+ resolveSessionHarness
14
+ };
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export { AppToolLoopOptions, LoopEvent, LoopToolCall, OpenAICompatStreamTurnOpti
6
6
  export { createTokenRecallChecker, producedFromToolEvents } from './eval/index.js';
7
7
  export { KnowledgeRequirementSpec, KnowledgeSignal, KnowledgeStateAccessor, SatisfiedByRule, buildKnowledgeRequirements, deriveSignals } from './knowledge/index.js';
8
8
  export { CreateKnowledgeLoopDeps, KnowledgeCandidate, KnowledgeDecider, KnowledgeDeciderInput, KnowledgeDecision, KnowledgeGateVerdict, KnowledgeLoop, KnowledgeLoopDriver, createKnowledgeLoop, createReviewerDecider, reviewCandidate } from './knowledge-loop/index.js';
9
+ export { DEFAULT_HARNESS, Harness, KNOWN_HARNESSES, ResolveSessionHarnessInput, ResolvedSessionHarness, coerceHarness, isHarness, resolveSessionHarness } from './harness/index.js';
9
10
  export { AgentAppConfig, AgentDelegationConfig, AgentIdentityConfig, AgentIntegrationsConfig, AgentKnowledgeConfig, AgentTaxonomyConfig, AgentUiConfig, KnowledgeLoopConfig, KnowledgeSourceSpec, agentAppConfigJsonSchema, defineAgentApp } from './config/index.js';
10
11
  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';
11
12
  export { KeyCrypto, KeyProvisioner, PlanLimit, PlatformBalanceInfo, PlatformBalanceManager, PlatformBalanceManagerOptions, PlatformBillingClient, PlatformIdentity, PlatformProductUsage, SharedBillingState, WorkspaceKeyManager, WorkspaceKeyManagerOptions, WorkspaceKeyRecord, WorkspaceKeyStore, WorkspaceModelKeyUsage, createPlatformBalanceManager, createWorkspaceKeyManager } from './billing/index.js';
package/dist/index.js CHANGED
@@ -1,3 +1,13 @@
1
+ import {
2
+ redactForIngestion
3
+ } from "./chunk-C5CREGT2.js";
4
+ import {
5
+ DEFAULT_HARNESS,
6
+ KNOWN_HARNESSES,
7
+ coerceHarness,
8
+ isHarness,
9
+ resolveSessionHarness
10
+ } from "./chunk-SD2H4FWY.js";
1
11
  import {
2
12
  agentAppConfigJsonSchema,
3
13
  defineAgentApp
@@ -56,9 +66,6 @@ import {
56
66
  parseJsonObjectBody,
57
67
  requireString
58
68
  } from "./chunk-CN75FIPT.js";
59
- import {
60
- redactForIngestion
61
- } from "./chunk-C5CREGT2.js";
62
69
  import {
63
70
  APP_TOOL_NAMES,
64
71
  DEFAULT_APP_TOOL_PATHS,
@@ -115,11 +122,13 @@ import {
115
122
  export {
116
123
  APP_TOOL_NAMES,
117
124
  DEFAULT_APP_TOOL_PATHS,
125
+ DEFAULT_HARNESS,
118
126
  DEFAULT_HEADER_NAMES,
119
127
  DEFAULT_TANGLE_ROUTER_BASE_URL,
120
128
  DELEGATION_MCP_SERVER_KEY,
121
129
  DELEGATION_TOOLS,
122
130
  HubExecClient,
131
+ KNOWN_HARNESSES,
123
132
  PRESET_MIGRATION_SQL,
124
133
  PRESET_TABLES,
125
134
  ToolInputError,
@@ -136,6 +145,7 @@ export {
136
145
  buildKnowledgeRequirements,
137
146
  buildUserTextParts,
138
147
  checkRateLimit,
148
+ coerceHarness,
139
149
  createAppToolRuntimeExecutor,
140
150
  createBrokerTokenProvider,
141
151
  createCapabilityToken,
@@ -173,6 +183,7 @@ export {
173
183
  handleAppToolRequest,
174
184
  invokeIntegrationHub,
175
185
  isAppToolName,
186
+ isHarness,
176
187
  mergePersistedPart,
177
188
  messageHasTurnId,
178
189
  normalizeClientTurnId,
@@ -187,6 +198,7 @@ export {
187
198
  requireString,
188
199
  resolveChatTurn,
189
200
  resolveIntegrationAction,
201
+ resolveSessionHarness,
190
202
  resolveTangleModelConfig,
191
203
  resolveToolId,
192
204
  resolveToolName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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": [
@@ -76,6 +76,11 @@
76
76
  "import": "./dist/knowledge-loop/index.js",
77
77
  "default": "./dist/knowledge-loop/index.js"
78
78
  },
79
+ "./harness": {
80
+ "types": "./dist/harness/index.d.ts",
81
+ "import": "./dist/harness/index.js",
82
+ "default": "./dist/harness/index.js"
83
+ },
79
84
  "./preset-cloudflare": {
80
85
  "types": "./dist/preset-cloudflare/index.d.ts",
81
86
  "import": "./dist/preset-cloudflare/index.js",