@tangle-network/agent-app 0.60.172 → 0.60.174
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/dist/assistant/index.js +4 -4
- package/dist/chat-react/index.js +3 -3
- package/dist/chat-routes/index.js +2 -2
- package/dist/{chunk-KICTB6RK.js → chunk-F2BXNZAH.js} +2 -2
- package/dist/{chunk-CRHXK3QT.js → chunk-GYQTJA6N.js} +3 -3
- package/dist/{chunk-PZHZV6QY.js → chunk-PP5KIVHL.js} +2 -2
- package/dist/{chunk-RCRC2JKP.js → chunk-QW4UOEPH.js} +2 -2
- package/dist/{chunk-MXNVSY6J.js → chunk-VBRUG4N5.js} +47 -12
- package/dist/chunk-VBRUG4N5.js.map +1 -0
- package/dist/harness/index.d.ts +33 -74
- package/dist/harness/index.js +3 -1
- package/dist/harness/session-selection.d.ts +35 -0
- package/dist/integrations-react/index.d.ts +1 -1
- package/dist/integrations-react/index.js +18 -14
- package/dist/integrations-react/index.js.map +1 -1
- package/dist/integrations-react/panel.d.ts +3 -1
- package/dist/integrations-react/popup.d.ts +1 -1
- package/dist/integrations-react/projection.d.ts +15 -3
- package/dist/integrations-react/use-hub-integrations.d.ts +4 -2
- package/dist/sandbox/index.js +2 -2
- package/dist/skills-placement/index.js +1 -1
- package/dist/web-react/agent-profile-editor.js +3 -3
- package/dist/web-react/index.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-MXNVSY6J.js.map +0 -1
- /package/dist/{chunk-KICTB6RK.js.map → chunk-F2BXNZAH.js.map} +0 -0
- /package/dist/{chunk-CRHXK3QT.js.map → chunk-GYQTJA6N.js.map} +0 -0
- /package/dist/{chunk-PZHZV6QY.js.map → chunk-PP5KIVHL.js.map} +0 -0
- /package/dist/{chunk-RCRC2JKP.js.map → chunk-QW4UOEPH.js.map} +0 -0
package/dist/assistant/index.js
CHANGED
|
@@ -8,15 +8,15 @@ import "../chunk-FBVLEGEG.js";
|
|
|
8
8
|
import "../chunk-CW6F6GDP.js";
|
|
9
9
|
import "../chunk-ILZQAXJH.js";
|
|
10
10
|
import "../chunk-E4AY62T2.js";
|
|
11
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-GYQTJA6N.js";
|
|
12
12
|
import "../chunk-Y6PRDUFJ.js";
|
|
13
13
|
import "../chunk-GEYACSFW.js";
|
|
14
14
|
import {
|
|
15
15
|
ChatComposer
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-PP5KIVHL.js";
|
|
17
17
|
import "../chunk-L7MB2LLM.js";
|
|
18
18
|
import "../chunk-SJWIZT7B.js";
|
|
19
|
-
import "../chunk-
|
|
19
|
+
import "../chunk-F2BXNZAH.js";
|
|
20
20
|
import "../chunk-R5K5ML3Q.js";
|
|
21
21
|
import {
|
|
22
22
|
ModelPicker
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from "../chunk-LSUELMHY.js";
|
|
28
28
|
import "../chunk-FQYAJYPW.js";
|
|
29
29
|
import "../chunk-WTC6AOPL.js";
|
|
30
|
-
import "../chunk-
|
|
30
|
+
import "../chunk-VBRUG4N5.js";
|
|
31
31
|
import "../chunk-B7CL7ENS.js";
|
|
32
32
|
import "../chunk-PV33JZKV.js";
|
|
33
33
|
import "../chunk-AMUXRAUO.js";
|
package/dist/chat-react/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ComposerModeControls,
|
|
3
3
|
EntryComposer
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-PP5KIVHL.js";
|
|
5
5
|
import "../chunk-L7MB2LLM.js";
|
|
6
6
|
import "../chunk-SJWIZT7B.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-F2BXNZAH.js";
|
|
8
8
|
import "../chunk-R5K5ML3Q.js";
|
|
9
9
|
import "../chunk-AA4FKEQW.js";
|
|
10
10
|
import "../chunk-RASGKEAL.js";
|
|
11
11
|
import "../chunk-WTC6AOPL.js";
|
|
12
|
-
import "../chunk-
|
|
12
|
+
import "../chunk-VBRUG4N5.js";
|
|
13
13
|
import "../chunk-AMUXRAUO.js";
|
|
14
14
|
import "../chunk-YHRWZZJN.js";
|
|
15
15
|
import "../chunk-TXD5HXLE.js";
|
|
@@ -42,9 +42,9 @@ import {
|
|
|
42
42
|
flattenHistory,
|
|
43
43
|
readSandboxBinaryBytes,
|
|
44
44
|
statSandboxFileSize
|
|
45
|
-
} from "../chunk-
|
|
45
|
+
} from "../chunk-QW4UOEPH.js";
|
|
46
46
|
import "../chunk-4LBCTFBP.js";
|
|
47
|
-
import "../chunk-
|
|
47
|
+
import "../chunk-VBRUG4N5.js";
|
|
48
48
|
import "../chunk-LWSJK546.js";
|
|
49
49
|
import "../chunk-74WXGIEK.js";
|
|
50
50
|
import "../chunk-6A7MYOUI.js";
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
snapHarnessToModel,
|
|
17
17
|
snapModelToHarness
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-VBRUG4N5.js";
|
|
19
19
|
|
|
20
20
|
// src/web-react/composer-file-accept.ts
|
|
21
21
|
function isAcceptedFileType(file, accept) {
|
|
@@ -576,4 +576,4 @@ export {
|
|
|
576
576
|
HarnessPicker,
|
|
577
577
|
AgentSessionControls
|
|
578
578
|
};
|
|
579
|
-
//# sourceMappingURL=chunk-
|
|
579
|
+
//# sourceMappingURL=chunk-F2BXNZAH.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HarnessPicker,
|
|
3
3
|
filterAcceptedFiles
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-F2BXNZAH.js";
|
|
5
5
|
import {
|
|
6
6
|
EffortPicker,
|
|
7
7
|
ModelPicker,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
isModelCompatibleWithHarness,
|
|
15
15
|
snapModelToHarness
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VBRUG4N5.js";
|
|
17
17
|
import {
|
|
18
18
|
isCommitSha,
|
|
19
19
|
parseGitHubLocation,
|
|
@@ -2214,4 +2214,4 @@ export {
|
|
|
2214
2214
|
AGENT_PROFILE_EDITOR_SECTIONS,
|
|
2215
2215
|
AgentProfileEditor
|
|
2216
2216
|
};
|
|
2217
|
-
//# sourceMappingURL=chunk-
|
|
2217
|
+
//# sourceMappingURL=chunk-GYQTJA6N.js.map
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
filterAcceptedFiles,
|
|
11
11
|
isAcceptedFileType,
|
|
12
12
|
renamePastedImages
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-F2BXNZAH.js";
|
|
14
14
|
import {
|
|
15
15
|
OVERLAY_SHADOW,
|
|
16
16
|
POPOVER_OPTION_FOCUS,
|
|
@@ -1552,4 +1552,4 @@ export {
|
|
|
1552
1552
|
ComposerModeControls,
|
|
1553
1553
|
EntryComposer
|
|
1554
1554
|
};
|
|
1555
|
-
//# sourceMappingURL=chunk-
|
|
1555
|
+
//# sourceMappingURL=chunk-PP5KIVHL.js.map
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
DEFAULT_HARNESS,
|
|
14
14
|
assertHarnessModelCompatible,
|
|
15
15
|
isHarness
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VBRUG4N5.js";
|
|
17
17
|
import {
|
|
18
18
|
assertProfilePromptWithinBudget,
|
|
19
19
|
fingerprintAgentProfile
|
|
@@ -3343,4 +3343,4 @@ export {
|
|
|
3343
3343
|
isTerminalPromptEvent,
|
|
3344
3344
|
detectInteractiveQuestion
|
|
3345
3345
|
};
|
|
3346
|
-
//# sourceMappingURL=chunk-
|
|
3346
|
+
//# sourceMappingURL=chunk-QW4UOEPH.js.map
|
|
@@ -8,6 +8,50 @@ import {
|
|
|
8
8
|
snapHarnessToModel as aiSnapHarnessToModel,
|
|
9
9
|
snapModelToHarness as aiSnapModelToHarness
|
|
10
10
|
} from "@tangle-network/agent-interface";
|
|
11
|
+
|
|
12
|
+
// src/harness/session-selection.ts
|
|
13
|
+
var HarnessSelectionError = class extends Error {
|
|
14
|
+
code;
|
|
15
|
+
harness;
|
|
16
|
+
source;
|
|
17
|
+
constructor(code, value, source) {
|
|
18
|
+
const harness = typeof value === "string" ? value : null;
|
|
19
|
+
super(`${code}: ${source} harness ${harness === null ? "<invalid>" : JSON.stringify(harness)}`);
|
|
20
|
+
this.name = "HarnessSelectionError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.harness = harness;
|
|
23
|
+
this.source = source;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
function selectSessionHarness(input, knownHarnesses, defaultHarness) {
|
|
27
|
+
const known = new Set(knownHarnesses);
|
|
28
|
+
const resolve = (value2, source2) => {
|
|
29
|
+
if (typeof value2 !== "string" || !known.has(value2)) {
|
|
30
|
+
throw new HarnessSelectionError("harness_unknown", value2, source2);
|
|
31
|
+
}
|
|
32
|
+
return value2;
|
|
33
|
+
};
|
|
34
|
+
const admit = (harness2, source2, existing) => {
|
|
35
|
+
if (input.availability === void 0) return;
|
|
36
|
+
const allowed = existing ? input.availability.existingSessions ?? input.availability.newSessions : input.availability.newSessions;
|
|
37
|
+
if (!allowed.includes(harness2)) {
|
|
38
|
+
throw new HarnessSelectionError("harness_unavailable", harness2, source2);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
if (input.sessionHarness !== void 0 && input.sessionHarness !== null) {
|
|
42
|
+
const harness2 = resolve(input.sessionHarness, "session");
|
|
43
|
+
admit(harness2, "session", true);
|
|
44
|
+
const requested = input.requested === void 0 || input.requested === null ? void 0 : resolve(input.requested, "requested");
|
|
45
|
+
return { harness: harness2, locked: true, swapAttempted: requested !== void 0 && requested !== harness2 };
|
|
46
|
+
}
|
|
47
|
+
const source = input.requested !== void 0 && input.requested !== null ? "requested" : input.workspaceDefault !== void 0 && input.workspaceDefault !== null ? "workspace" : "fallback";
|
|
48
|
+
const value = source === "requested" ? input.requested : source === "workspace" ? input.workspaceDefault : input.fallback ?? defaultHarness;
|
|
49
|
+
const harness = resolve(value, source);
|
|
50
|
+
admit(harness, source, false);
|
|
51
|
+
return { harness, locked: false, swapAttempted: false };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/harness/index.ts
|
|
11
55
|
var NON_BACKEND_HARNESSES = ["gemini"];
|
|
12
56
|
var nonBackendHarnesses = new Set(NON_BACKEND_HARNESSES);
|
|
13
57
|
var KNOWN_HARNESSES = [
|
|
@@ -24,14 +68,7 @@ function coerceHarness(value, fallback = DEFAULT_HARNESS) {
|
|
|
24
68
|
return isHarness(value) ? value : fallback;
|
|
25
69
|
}
|
|
26
70
|
function resolveSessionHarness(input = {}) {
|
|
27
|
-
|
|
28
|
-
if (isHarness(input.sessionHarness)) {
|
|
29
|
-
const locked = input.sessionHarness;
|
|
30
|
-
const swapAttempted = isHarness(input.requested) && input.requested !== locked;
|
|
31
|
-
return { harness: locked, locked: true, swapAttempted };
|
|
32
|
-
}
|
|
33
|
-
const harness = coerceHarness(input.requested, coerceHarness(input.workspaceDefault, fallback));
|
|
34
|
-
return { harness, locked: false, swapAttempted: false };
|
|
71
|
+
return selectSessionHarness(input, KNOWN_HARNESSES, DEFAULT_HARNESS);
|
|
35
72
|
}
|
|
36
73
|
function configHarness(value, where) {
|
|
37
74
|
if (!isHarness(value)) throw new Error(`${where} names an unknown harness: ${String(value)}`);
|
|
@@ -93,9 +130,6 @@ var HARNESS_CAPABILITIES = {
|
|
|
93
130
|
prime: { subagents: true, mcp: "native", toolControls: "runtime", skillsDir: ".prime/agent/skills" },
|
|
94
131
|
hermes: { subagents: false, mcp: "native", toolControls: "runtime", skillsDir: null },
|
|
95
132
|
openclaw: { subagents: false, mcp: "config-file", toolControls: "runtime", skillsDir: "skills" },
|
|
96
|
-
// Harnesses the platform placement map does not bridge yet: conservative
|
|
97
|
-
// facts — assert nothing they have not proven. Each upgrades to its real
|
|
98
|
-
// facts when the materializer grows support for it.
|
|
99
133
|
acp: { subagents: false, mcp: "native", toolControls: "runtime", skillsDir: null },
|
|
100
134
|
amp: { subagents: false, mcp: "native", toolControls: "runtime", skillsDir: null },
|
|
101
135
|
"factory-droids": { subagents: false, mcp: "native", toolControls: "runtime", skillsDir: null },
|
|
@@ -110,6 +144,7 @@ function harnessCapabilities(harness) {
|
|
|
110
144
|
}
|
|
111
145
|
|
|
112
146
|
export {
|
|
147
|
+
HarnessSelectionError,
|
|
113
148
|
modelProvider,
|
|
114
149
|
KNOWN_HARNESSES,
|
|
115
150
|
DEFAULT_HARNESS,
|
|
@@ -123,4 +158,4 @@ export {
|
|
|
123
158
|
assertHarnessModelCompatible,
|
|
124
159
|
harnessCapabilities
|
|
125
160
|
};
|
|
126
|
-
//# sourceMappingURL=chunk-
|
|
161
|
+
//# sourceMappingURL=chunk-VBRUG4N5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/harness/index.ts","../src/harness/session-selection.ts"],"sourcesContent":["/**\n * Coding-agent harness selection, model compatibility, and the session lock.\n *\n * Known identities are not a deployment's available runners. Products supply\n * server-owned availability to resolveSessionHarness. Historical session IDs\n * remain intact when their runner is unavailable; a harness change requires an\n * explicit transfer, not a fallback or a rewritten session lock.\n *\n * Model compatibility is owned by @tangle-network/agent-interface. The shell\n * owns session selection; the consumer owns storage and deployment policy.\n */\n\nimport {\n harnessProviders,\n harnessSupportsModel,\n harnessTypeSchema,\n modelProvider,\n preferredHarnessForModel,\n snapHarnessToModel as aiSnapHarnessToModel,\n snapModelToHarness as aiSnapModelToHarness,\n type HarnessType,\n} from '@tangle-network/agent-interface'\nimport {\n selectSessionHarness,\n type HarnessAvailability as SelectionAvailability,\n type SessionHarnessSelection,\n type SessionHarnessSelectionInput,\n} from './session-selection'\n\nexport { HarnessSelectionError } from './session-selection'\n\n/** Canonical identities that have no direct Sandbox backend. */\nconst NON_BACKEND_HARNESSES = ['gemini'] as const satisfies readonly HarnessType[]\nconst nonBackendHarnesses = new Set<string>(NON_BACKEND_HARNESSES)\n\n/** A recognized coding-agent backend, not a promise that it is installed. */\nexport type Harness = Exclude<HarnessType, (typeof NON_BACKEND_HARNESSES)[number]>\n\n/**\n * Recognized backend identities, including IDs needed by historical sessions.\n * Use deployment availability, not membership here, to offer or admit new runs.\n */\nexport const KNOWN_HARNESSES: readonly Harness[] = [\n ...harnessTypeSchema.options.filter(\n (harness): harness is Exclude<HarnessType, (typeof NON_BACKEND_HARNESSES)[number]> =>\n !nonBackendHarnesses.has(harness),\n ),\n]\n\n/** Default for an absent choice; never a replacement for an invalid explicit ID. */\nexport const DEFAULT_HARNESS: Harness = 'opencode'\nconst HARNESS_SET: ReadonlySet<string> = new Set(KNOWN_HARNESSES)\n\n/** Determine whether a value is a recognized identity, independently of availability. */\nexport function isHarness(value: unknown): value is Harness {\n return typeof value === 'string' && HARNESS_SET.has(value)\n}\n\n/** Display/config convenience only. Never use coercion to admit an execution or restore a lock. */\nexport function coerceHarness(value: unknown, fallback: Harness = DEFAULT_HARNESS): Harness {\n return isHarness(value) ? value : fallback\n}\n\n/** Server-owned start and resume sets. Existing-only entries are draining. */\nexport type HarnessAvailability = SelectionAvailability<Harness>\n\n/** A saved lock, explicit choice, workspace default, and optional deployment availability. */\nexport type ResolveSessionHarnessInput = SessionHarnessSelectionInput<Harness>\n\n/** The selected identity and the existing session-lock/swap-attempt result. */\nexport type ResolvedSessionHarness = SessionHarnessSelection<Harness>\n\n/**\n * Resolve requested → workspaceDefault → fallback only for a genuinely new session.\n * A non-null saved identity always takes the lock path. Unknown/retired identities\n * and invalid explicit requests throw HarnessSelectionError instead of falling back.\n * A known different request still reports swapAttempted while preserving the lock.\n * The caller must reject that attempt before execution, as in the existing contract.\n */\nexport function resolveSessionHarness(input: ResolveSessionHarnessInput = {}): ResolvedSessionHarness {\n return selectSessionHarness(input, KNOWN_HARNESSES, DEFAULT_HARNESS)\n}\n\n/**\n * An app's harness configuration, read from its deploy configuration (for example the env vars\n * `AGENT_HARNESS` and `AGENT_HARNESS_WORKSPACES`), so no product hard-codes `backend.type`.\n */\nexport interface HarnessConfig {\n /** The app default for new sessions. Unset or blank means {@link DEFAULT_HARNESS}. */\n default?: string\n /** Per-workspace choices: a JSON object mapping a workspace id to a harness. */\n workspaces?: string\n}\n\nfunction configHarness(value: unknown, where: string): Harness {\n if (!isHarness(value)) throw new Error(`${where} names an unknown harness: ${String(value)}`)\n return value\n}\n\n/**\n * The harness a workspace's new sessions start on: its own choice, else the app default, else\n * {@link DEFAULT_HARNESS}. Pass the result to {@link resolveSessionHarness} as `workspaceDefault`\n * so a session that already ran keeps its harness. A value naming an unknown harness throws:\n * a configuration typo must fail loudly, never run a different harness than the one configured.\n */\nexport function configuredHarness(config: HarnessConfig, workspaceId?: string): Harness {\n const appDefault = config.default?.trim()\n ? configHarness(config.default.trim(), 'The harness default') : DEFAULT_HARNESS\n const raw = config.workspaces?.trim()\n if (!raw) return appDefault\n let choices: unknown\n try {\n choices = JSON.parse(raw)\n } catch {\n throw new Error('The per-workspace harness configuration is not JSON')\n }\n if (!choices || typeof choices !== 'object' || Array.isArray(choices)) {\n throw new Error('The per-workspace harness configuration must map workspace ids to harnesses')\n }\n // Every entry is checked, not only the caller's, so one bad entry fails every workspace's\n // first turn after the deploy instead of hiding until that workspace sends a message.\n const parsed = new Map(Object.entries(choices).map(([id, value]) =>\n [id, configHarness(value, `The harness for workspace ${id}`)]))\n return (workspaceId === undefined ? undefined : parsed.get(workspaceId)) ?? appDefault\n}\n\n/** Harness-model compatibility is delegated to the Interface owner. */\nexport { modelProvider }\n\n/** A resolved model and the transport provider that will execute it. */\nexport interface HarnessModelSelection {\n model: string\n provider?: string\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 return harnessSupportsModel(harness, 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, 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 const snapped = aiSnapHarnessToModel(harness, modelId)\n if (!isHarness(snapped)) {\n throw new Error(\n `Harness \"${harness}\" snapped to \"${snapped}\" for model \"${modelId}\", which this platform has no backend for.`,\n )\n }\n return snapped\n}\n\n/** Fail-loud server guard: throw when a harness is asked to run a model it can't.\n * A resolved model tuple supplies provider evidence when its id is unqualified.\n * Call before dispatching a sandbox turn so a bypassed UI cannot reach the\n * sidecar with an incompatible or unproven pair. */\nexport function assertHarnessModelCompatible(\n harness: Harness,\n selection: string | HarnessModelSelection,\n): void {\n const modelId = typeof selection === 'string' ? selection : selection.model\n const transportProvider = typeof selection === 'string' ? undefined : selection.provider\n const providers = harnessProviders(harness)\n const provider = modelProvider(modelId) ?? transportProvider ?? null\n // An omitted/default model is deliberately left to the harness. Once a\n // caller names a model, however, a vendor-locked harness must have provider\n // evidence. Treating a router provider as compatible with every native CLI\n // is how a bare Gemini model reached Claude Code and produced \"Not logged in\".\n const isDefaultSentinel = modelId.trim() === '' || modelId.trim() === 'default'\n const compatible = isDefaultSentinel\n || providers === null\n || (provider !== null && providers.includes(provider))\n if (!compatible) {\n const native = preferredHarnessForModel(modelId)\n const providerDescription = provider ?? 'unqualified'\n throw new Error(\n `Harness \"${harness}\" cannot run model \"${modelId}\" (provider \"${providerDescription}\"). ` +\n `Use ${native ?? 'a router-backed harness (opencode)'} or an allowed model.`,\n )\n }\n}\n\n// Profile-support facts remain here until the materializer supplies its owned descriptor.\n// Do not confuse these facts with deployment availability.\n\n/** What a harness can express, for profile assembly. */\nexport interface HarnessCapabilities {\n /** Lower profile.subagents into native cwd files; false means materialization refuses them. */\n subagents: boolean\n /** How profile.mcp reaches the harness. */\n mcp: 'native' | 'config-file' | 'extension' | 'none'\n /** Whether tool controls are checked before MCP tools register. */\n toolControls: 'startup' | 'runtime'\n /** cwd-relative skill directory, or null when skills must be inlined. */\n skillsDir: string | null\n}\n\nconst HARNESS_CAPABILITIES: Record<Harness, HarnessCapabilities> = {\n 'claude-code': { subagents: true, mcp: 'native', toolControls: 'runtime', skillsDir: '.claude/skills' },\n nanoclaw: { subagents: true, mcp: 'native', toolControls: 'runtime', skillsDir: '.claude/skills' },\n opencode: { subagents: true, mcp: 'config-file', toolControls: 'runtime', skillsDir: '.opencode/skills' },\n codex: { subagents: true, mcp: 'config-file', toolControls: 'runtime', skillsDir: '.codex/skills' },\n 'kimi-code': { subagents: false, mcp: 'config-file', toolControls: 'runtime', skillsDir: '.kimi/skills' },\n pi: { subagents: false, mcp: 'extension', toolControls: 'startup', skillsDir: '.pi/skills' },\n prime: { subagents: true, mcp: 'native', toolControls: 'runtime', skillsDir: '.prime/agent/skills' },\n hermes: { subagents: false, mcp: 'native', toolControls: 'runtime', skillsDir: null },\n openclaw: { subagents: false, mcp: 'config-file', toolControls: 'runtime', skillsDir: 'skills' },\n acp: { subagents: false, mcp: 'native', toolControls: 'runtime', skillsDir: null },\n amp: { subagents: false, mcp: 'native', toolControls: 'runtime', skillsDir: null },\n 'factory-droids': { subagents: false, mcp: 'native', toolControls: 'runtime', skillsDir: null },\n forge: { subagents: false, mcp: 'native', toolControls: 'runtime', skillsDir: null },\n cursor: { subagents: false, mcp: 'native', toolControls: 'runtime', skillsDir: null },\n 'cli-base': { subagents: false, mcp: 'native', toolControls: 'runtime', skillsDir: null },\n}\n\n/** Profile-support facts, independent of whether this deployment enables the harness. */\nexport function harnessCapabilities(harness: Harness): HarnessCapabilities {\n const caps = HARNESS_CAPABILITIES[harness]\n if (!caps) throw new Error(`harnessCapabilities: no capability facts for \"${harness}\"`)\n return caps\n}\n","/** Pure session admission. Harness identity is not deployment availability. */\nexport interface HarnessAvailability<H extends string> {\n /** Server-owned selection for new sessions. An empty list disables all starts. */\n readonly newSessions: readonly H[]\n /** Includes draining harnesses. Omission uses newSessions, never all known IDs. */\n readonly existingSessions?: readonly H[]\n}\n\nexport interface SessionHarnessSelectionInput<H extends string> {\n sessionHarness?: unknown\n requested?: unknown\n workspaceDefault?: unknown\n fallback?: H\n /** Omit only when the caller deliberately admits every known harness. */\n availability?: HarnessAvailability<H>\n}\n\nexport interface SessionHarnessSelection<H extends string> {\n harness: H\n locked: boolean\n swapAttempted: boolean\n}\n\ntype SelectionSource = 'session' | 'requested' | 'workspace' | 'fallback'\n\n/** An admission failure, never permission to retry on another harness. */\nexport class HarnessSelectionError extends Error {\n readonly code: 'harness_unknown' | 'harness_unavailable'\n readonly harness: string | null\n readonly source: SelectionSource\n\n constructor(\n code: 'harness_unknown' | 'harness_unavailable',\n value: unknown,\n source: SelectionSource,\n ) {\n const harness = typeof value === 'string' ? value : null\n super(`${code}: ${source} harness ${harness === null ? '<invalid>' : JSON.stringify(harness)}`)\n this.name = 'HarnessSelectionError'\n this.code = code\n this.harness = harness\n this.source = source\n }\n}\n\n/**\n * Null/undefined mean no choice. Every other value is an explicit choice and must\n * validate. A retired or unrecognized saved identity must never become a new session.\n * Availability is supplied by the authenticated server, not by a request body.\n */\nexport function selectSessionHarness<H extends string>(\n input: SessionHarnessSelectionInput<H>,\n knownHarnesses: readonly H[],\n defaultHarness: H,\n): SessionHarnessSelection<H> {\n const known = new Set<string>(knownHarnesses)\n const resolve = (value: unknown, source: SelectionSource): H => {\n if (typeof value !== 'string' || !known.has(value)) {\n throw new HarnessSelectionError('harness_unknown', value, source)\n }\n return value as H\n }\n const admit = (harness: H, source: SelectionSource, existing: boolean): void => {\n if (input.availability === undefined) return\n const allowed = existing\n ? input.availability.existingSessions ?? input.availability.newSessions\n : input.availability.newSessions\n if (!allowed.includes(harness)) {\n throw new HarnessSelectionError('harness_unavailable', harness, source)\n }\n }\n\n if (input.sessionHarness !== undefined && input.sessionHarness !== null) {\n const harness = resolve(input.sessionHarness, 'session')\n admit(harness, 'session', true)\n // A known different request remains a swap attempt; it is not executed. An\n // invalid request is refused rather than being misreported as no swap.\n const requested = input.requested === undefined || input.requested === null\n ? undefined : resolve(input.requested, 'requested')\n return { harness, locked: true, swapAttempted: requested !== undefined && requested !== harness }\n }\n\n const source: SelectionSource = input.requested !== undefined && input.requested !== null\n ? 'requested'\n : input.workspaceDefault !== undefined && input.workspaceDefault !== null\n ? 'workspace' : 'fallback'\n const value = source === 'requested' ? input.requested\n : source === 'workspace' ? input.workspaceDefault : input.fallback ?? defaultHarness\n const harness = resolve(value, source)\n admit(harness, source, false)\n return { harness, locked: false, swapAttempted: false }\n}\n"],"mappings":"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","names":["value","source","harness"]}
|
package/dist/harness/index.d.ts
CHANGED
|
@@ -1,73 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Coding-agent harness selection
|
|
2
|
+
* Coding-agent harness selection, model compatibility, and the session lock.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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.
|
|
4
|
+
* Known identities are not a deployment's available runners. Products supply
|
|
5
|
+
* server-owned availability to resolveSessionHarness. Historical session IDs
|
|
6
|
+
* remain intact when their runner is unavailable; a harness change requires an
|
|
7
|
+
* explicit transfer, not a fallback or a rewritten session lock.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* owns storage — which harness a workspace defaults to, which one a session locked — and maps the
|
|
14
|
-
* resolved value onto the SDK's `backend.type`.
|
|
15
|
-
*
|
|
16
|
-
* Harness↔model COMPATIBILITY (which models a harness can run, snapping) is NOT defined here — it
|
|
17
|
-
* comes from `@tangle-network/agent-interface`, the single source of truth shared with the
|
|
18
|
-
* sandbox-ui pickers and the cli-bridge backends. This module owns the harness TAXONOMY + the
|
|
19
|
-
* session lock, and its server assertion also checks resolved transport-provider evidence.
|
|
9
|
+
* Model compatibility is owned by @tangle-network/agent-interface. The shell
|
|
10
|
+
* owns session selection; the consumer owns storage and deployment policy.
|
|
20
11
|
*/
|
|
21
12
|
import { modelProvider, type HarnessType } from '@tangle-network/agent-interface';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* offering it would resolve a session onto a runner that cannot start.
|
|
26
|
-
*/
|
|
13
|
+
import { type HarnessAvailability as SelectionAvailability, type SessionHarnessSelection, type SessionHarnessSelectionInput } from './session-selection';
|
|
14
|
+
export { HarnessSelectionError } from './session-selection';
|
|
15
|
+
/** Canonical identities that have no direct Sandbox backend. */
|
|
27
16
|
declare const NON_BACKEND_HARNESSES: readonly ["gemini"];
|
|
28
|
-
/** A coding-agent backend
|
|
17
|
+
/** A recognized coding-agent backend, not a promise that it is installed. */
|
|
29
18
|
export type Harness = Exclude<HarnessType, (typeof NON_BACKEND_HARNESSES)[number]>;
|
|
30
19
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* an edit here, and kept structural so this module still needs no sandbox dependency.
|
|
20
|
+
* Recognized backend identities, including IDs needed by historical sessions.
|
|
21
|
+
* Use deployment availability, not membership here, to offer or admit new runs.
|
|
34
22
|
*/
|
|
35
23
|
export declare const KNOWN_HARNESSES: readonly Harness[];
|
|
36
|
-
/**
|
|
24
|
+
/** Default for an absent choice; never a replacement for an invalid explicit ID. */
|
|
37
25
|
export declare const DEFAULT_HARNESS: Harness;
|
|
38
|
-
/** Determine
|
|
26
|
+
/** Determine whether a value is a recognized identity, independently of availability. */
|
|
39
27
|
export declare function isHarness(value: unknown): value is Harness;
|
|
40
|
-
/**
|
|
28
|
+
/** Display/config convenience only. Never use coercion to admit an execution or restore a lock. */
|
|
41
29
|
export declare function coerceHarness(value: unknown, fallback?: Harness): Harness;
|
|
42
|
-
/**
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/** The workspace's default harness, used only when starting a fresh session. */
|
|
49
|
-
workspaceDefault?: unknown;
|
|
50
|
-
/** Final fallback when nothing else resolves (default `opencode`). */
|
|
51
|
-
fallback?: Harness;
|
|
52
|
-
}
|
|
53
|
-
/** Represent resolved session state including harness, lock status, and swap attempt flag */
|
|
54
|
-
export interface ResolvedSessionHarness {
|
|
55
|
-
/** The harness to actually run — the locked one when the session already has it. */
|
|
56
|
-
harness: Harness;
|
|
57
|
-
/** True when the session already had a locked harness (this turn did not pick it). */
|
|
58
|
-
locked: boolean;
|
|
59
|
-
/** True when `requested` differs from the locked harness — a forbidden mid-session
|
|
60
|
-
* swap the caller should reject or warn on. The lock always wins regardless. */
|
|
61
|
-
swapAttempted: boolean;
|
|
62
|
-
}
|
|
30
|
+
/** Server-owned start and resume sets. Existing-only entries are draining. */
|
|
31
|
+
export type HarnessAvailability = SelectionAvailability<Harness>;
|
|
32
|
+
/** A saved lock, explicit choice, workspace default, and optional deployment availability. */
|
|
33
|
+
export type ResolveSessionHarnessInput = SessionHarnessSelectionInput<Harness>;
|
|
34
|
+
/** The selected identity and the existing session-lock/swap-attempt result. */
|
|
35
|
+
export type ResolvedSessionHarness = SessionHarnessSelection<Harness>;
|
|
63
36
|
/**
|
|
64
|
-
* Resolve
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* - **Fresh session**: pick `requested → workspaceDefault → fallback`. The caller
|
|
70
|
-
* persists the result as the session's lock for every subsequent turn.
|
|
37
|
+
* Resolve requested → workspaceDefault → fallback only for a genuinely new session.
|
|
38
|
+
* A non-null saved identity always takes the lock path. Unknown/retired identities
|
|
39
|
+
* and invalid explicit requests throw HarnessSelectionError instead of falling back.
|
|
40
|
+
* A known different request still reports swapAttempted while preserving the lock.
|
|
41
|
+
* The caller must reject that attempt before execution, as in the existing contract.
|
|
71
42
|
*/
|
|
72
43
|
export declare function resolveSessionHarness(input?: ResolveSessionHarnessInput): ResolvedSessionHarness;
|
|
73
44
|
/**
|
|
@@ -87,13 +58,7 @@ export interface HarnessConfig {
|
|
|
87
58
|
* a configuration typo must fail loudly, never run a different harness than the one configured.
|
|
88
59
|
*/
|
|
89
60
|
export declare function configuredHarness(config: HarnessConfig, workspaceId?: string): Harness;
|
|
90
|
-
/**
|
|
91
|
-
* Harness ↔ model compatibility + snapping — delegated to `@tangle-network/agent-interface`.
|
|
92
|
-
*
|
|
93
|
-
* `Harness` is the dispatchable subset of `HarnessType`: this shell drops only canonical harnesses
|
|
94
|
-
* for which the platform has no backend. Compatibility therefore comes entirely from Interface;
|
|
95
|
-
* this package keeps no second harness taxonomy.
|
|
96
|
-
*/
|
|
61
|
+
/** Harness-model compatibility is delegated to the Interface owner. */
|
|
97
62
|
export { modelProvider };
|
|
98
63
|
/** A resolved model and the transport provider that will execute it. */
|
|
99
64
|
export interface HarnessModelSelection {
|
|
@@ -117,20 +82,14 @@ export declare function snapHarnessToModel(harness: Harness, modelId: string): H
|
|
|
117
82
|
export declare function assertHarnessModelCompatible(harness: Harness, selection: string | HarnessModelSelection): void;
|
|
118
83
|
/** What a harness can express, for profile assembly. */
|
|
119
84
|
export interface HarnessCapabilities {
|
|
120
|
-
/** Lower
|
|
121
|
-
* False: a profile carrying subagents is refused at materialization —
|
|
122
|
-
* builders must not add them. */
|
|
85
|
+
/** Lower profile.subagents into native cwd files; false means materialization refuses them. */
|
|
123
86
|
subagents: boolean;
|
|
124
|
-
/** How
|
|
87
|
+
/** How profile.mcp reaches the harness. */
|
|
125
88
|
mcp: 'native' | 'config-file' | 'extension' | 'none';
|
|
126
|
-
/**
|
|
127
|
-
* MCP-carried verbs is checked before MCP tools register (spawn-time
|
|
128
|
-
* validation refuses unknown names); 'runtime' tolerates them. */
|
|
89
|
+
/** Whether tool controls are checked before MCP tools register. */
|
|
129
90
|
toolControls: 'startup' | 'runtime';
|
|
130
|
-
/** cwd-relative directory
|
|
131
|
-
* when the harness has no cwd skill primitive (falls back to inline). */
|
|
91
|
+
/** cwd-relative skill directory, or null when skills must be inlined. */
|
|
132
92
|
skillsDir: string | null;
|
|
133
93
|
}
|
|
134
|
-
/**
|
|
135
|
-
* an entry; the map is exhaustive by construction (tests assert parity). */
|
|
94
|
+
/** Profile-support facts, independent of whether this deployment enables the harness. */
|
|
136
95
|
export declare function harnessCapabilities(harness: Harness): HarnessCapabilities;
|
package/dist/harness/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_HARNESS,
|
|
3
|
+
HarnessSelectionError,
|
|
3
4
|
KNOWN_HARNESSES,
|
|
4
5
|
assertHarnessModelCompatible,
|
|
5
6
|
coerceHarness,
|
|
@@ -11,9 +12,10 @@ import {
|
|
|
11
12
|
resolveSessionHarness,
|
|
12
13
|
snapHarnessToModel,
|
|
13
14
|
snapModelToHarness
|
|
14
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-VBRUG4N5.js";
|
|
15
16
|
export {
|
|
16
17
|
DEFAULT_HARNESS,
|
|
18
|
+
HarnessSelectionError,
|
|
17
19
|
KNOWN_HARNESSES,
|
|
18
20
|
assertHarnessModelCompatible,
|
|
19
21
|
coerceHarness,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Pure session admission. Harness identity is not deployment availability. */
|
|
2
|
+
export interface HarnessAvailability<H extends string> {
|
|
3
|
+
/** Server-owned selection for new sessions. An empty list disables all starts. */
|
|
4
|
+
readonly newSessions: readonly H[];
|
|
5
|
+
/** Includes draining harnesses. Omission uses newSessions, never all known IDs. */
|
|
6
|
+
readonly existingSessions?: readonly H[];
|
|
7
|
+
}
|
|
8
|
+
export interface SessionHarnessSelectionInput<H extends string> {
|
|
9
|
+
sessionHarness?: unknown;
|
|
10
|
+
requested?: unknown;
|
|
11
|
+
workspaceDefault?: unknown;
|
|
12
|
+
fallback?: H;
|
|
13
|
+
/** Omit only when the caller deliberately admits every known harness. */
|
|
14
|
+
availability?: HarnessAvailability<H>;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionHarnessSelection<H extends string> {
|
|
17
|
+
harness: H;
|
|
18
|
+
locked: boolean;
|
|
19
|
+
swapAttempted: boolean;
|
|
20
|
+
}
|
|
21
|
+
type SelectionSource = 'session' | 'requested' | 'workspace' | 'fallback';
|
|
22
|
+
/** An admission failure, never permission to retry on another harness. */
|
|
23
|
+
export declare class HarnessSelectionError extends Error {
|
|
24
|
+
readonly code: 'harness_unknown' | 'harness_unavailable';
|
|
25
|
+
readonly harness: string | null;
|
|
26
|
+
readonly source: SelectionSource;
|
|
27
|
+
constructor(code: 'harness_unknown' | 'harness_unavailable', value: unknown, source: SelectionSource);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Null/undefined mean no choice. Every other value is an explicit choice and must
|
|
31
|
+
* validate. A retired or unrecognized saved identity must never become a new session.
|
|
32
|
+
* Availability is supplied by the authenticated server, not by a request body.
|
|
33
|
+
*/
|
|
34
|
+
export declare function selectSessionHarness<H extends string>(input: SessionHarnessSelectionInput<H>, knownHarnesses: readonly H[], defaultHarness: H): SessionHarnessSelection<H>;
|
|
35
|
+
export {};
|
|
@@ -3,4 +3,4 @@ export { createHubIntegrationsClient, type HubIntegrationsClient, type HubIntegr
|
|
|
3
3
|
export { connectionTone, HubIntegrationsPanel, hubConnectionStatus, type HubAccountStatus, type HubConnectionState, type HubIntegrationsAccounts, type HubIntegrationsPanelProps } from './panel';
|
|
4
4
|
export { HUB_CONNECT_CHANNEL, HUB_CONNECTED_MESSAGE_TYPE, HUB_CONNECT_FAILED_MESSAGE_TYPE, POPUP_TIMEOUT_MS } from './popup';
|
|
5
5
|
export { useHubIntegrations, type HubHostConnect, type HubWriteReceipt, type UseHubIntegrationsOptions } from './use-hub-integrations';
|
|
6
|
-
export { type HubIntegrationCapabilities, type HubIntegrationCapability } from './projection';
|
|
6
|
+
export { type HubActionPolicy, type HubActionPolicyDisplay, type HubIntegrationCapabilities, type HubIntegrationCapability } from './projection';
|
|
@@ -102,7 +102,7 @@ function waitForConnection(input) {
|
|
|
102
102
|
candidate.onmessage = (event) => {
|
|
103
103
|
const message = event.data;
|
|
104
104
|
if (!message || message.provider !== input.providerId || message.nonce !== input.nonce || message.context !== input.contextToken) return;
|
|
105
|
-
if (message.type === HUB_CONNECT_FAILED_MESSAGE_TYPE) finish("
|
|
105
|
+
if (message.type === HUB_CONNECT_FAILED_MESSAGE_TYPE) finish("failed");
|
|
106
106
|
if (message.type === HUB_CONNECTED_MESSAGE_TYPE) {
|
|
107
107
|
if (pollId !== void 0) window.clearTimeout(pollId);
|
|
108
108
|
void check();
|
|
@@ -187,7 +187,7 @@ function HubConnectCallbackPage({ returnHref }) {
|
|
|
187
187
|
setPhase("failed");
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
|
-
const failure = params.get("error_description") || params.get("error");
|
|
190
|
+
const failure = params.get("error_description") || params.get("error") || (params.get("hub_connection") === "error" ? params.get("hub_error") || "Please restart the connection flow." : null);
|
|
191
191
|
try {
|
|
192
192
|
const channel = new BroadcastChannel(HUB_CONNECT_CHANNEL);
|
|
193
193
|
channel.postMessage({ type: failure ? HUB_CONNECT_FAILED_MESSAGE_TYPE : HUB_CONNECTED_MESSAGE_TYPE, provider, nonce, context });
|
|
@@ -352,7 +352,7 @@ var OPTIONS = [
|
|
|
352
352
|
{ value: "ask", label: "Ask" },
|
|
353
353
|
{ value: "deny", label: "Deny" }
|
|
354
354
|
];
|
|
355
|
-
function permissionDetails(tools, policies, connectionId, can) {
|
|
355
|
+
function permissionDetails(tools, policies, connectionId, can, getActionPolicy) {
|
|
356
356
|
const overrides = new Map(policies.filter((policy) => policy.connectionId === connectionId).map((policy) => [policy.actionPath, policy.decision]));
|
|
357
357
|
const groups = [
|
|
358
358
|
{ id: "writes", title: "Write and unknown actions", rows: [] },
|
|
@@ -362,16 +362,17 @@ function permissionDetails(tools, policies, connectionId, can) {
|
|
|
362
362
|
const reads = [];
|
|
363
363
|
for (const tool of tools) {
|
|
364
364
|
const override = overrides.get(tool.path);
|
|
365
|
+
const enforced = getActionPolicy?.(tool, override);
|
|
365
366
|
const row = {
|
|
366
367
|
actionPath: tool.path,
|
|
367
368
|
title: tool.title ?? tool.path,
|
|
368
369
|
riskLabel: tool.risk ?? "unknown",
|
|
369
370
|
riskTone: tool.risk === "destructive" ? "error" : tool.risk === "write" || tool.risk === void 0 ? "warning" : "neutral",
|
|
370
|
-
decision: override ?? null,
|
|
371
|
-
decisionOptions: OPTIONS,
|
|
372
|
-
sourceLabel: override ? "Stored override" : "No stored override",
|
|
373
|
-
canReset: override !== void 0 && can({ operation: "policy.reset", connectionId, actionPath: tool.path }),
|
|
374
|
-
disabled: !can({ operation: "policy.set", connectionId, actionPath: tool.path })
|
|
371
|
+
decision: enforced?.decision ?? override ?? null,
|
|
372
|
+
decisionOptions: enforced ? [{ value: enforced.decision, label: enforced.label }] : OPTIONS,
|
|
373
|
+
sourceLabel: enforced?.sourceLabel ?? (override ? "Stored override" : "No stored override"),
|
|
374
|
+
canReset: !enforced && override !== void 0 && can({ operation: "policy.reset", connectionId, actionPath: tool.path }),
|
|
375
|
+
disabled: !!enforced || !can({ operation: "policy.set", connectionId, actionPath: tool.path })
|
|
375
376
|
};
|
|
376
377
|
if (tool.risk === "read") reads.push(row);
|
|
377
378
|
else writes.push(row);
|
|
@@ -399,7 +400,7 @@ function updateUrl(providerId, connectionId) {
|
|
|
399
400
|
else url.searchParams.delete("connection");
|
|
400
401
|
window.history.pushState(window.history.state, "", url);
|
|
401
402
|
}
|
|
402
|
-
function useHubIntegrations({ identity, client, can, callbackPath, onUnsupportedConnect, hostConnect }) {
|
|
403
|
+
function useHubIntegrations({ identity, client, can, callbackPath, onUnsupportedConnect, hostConnect, getActionPolicy }) {
|
|
403
404
|
const scope = contextKey(identity);
|
|
404
405
|
const activeScope = useRef(scope);
|
|
405
406
|
activeScope.current = scope;
|
|
@@ -544,7 +545,7 @@ function useHubIntegrations({ identity, client, can, callbackPath, onUnsupported
|
|
|
544
545
|
connections.retry();
|
|
545
546
|
setDialog(null);
|
|
546
547
|
setConnectStatus({ scope, status: "connected", message: "Connection verified." });
|
|
547
|
-
} else setConnectStatus({ scope, status: "failed", message: result === "blocked" ? "Allow the popup and try again." : result === "unverified" ? "Could not verify a new connection. Refresh and try again." : result === "cancelled" ? "Connection was cancelled." : "This connection flow is unavailable." });
|
|
548
|
+
} else setConnectStatus({ scope, status: "failed", providerId: target.providerId, message: result === "blocked" ? "Allow the popup and try again." : result === "unverified" ? "Could not verify a new connection. Refresh and try again." : result === "failed" ? "Could not connect your account. Please try again." : result === "cancelled" ? "Connection was cancelled." : "This connection flow is unavailable." });
|
|
548
549
|
return result;
|
|
549
550
|
}, [can, connections, client, identity, callbackPath, scope]);
|
|
550
551
|
const beginConnect = useCallback((target) => {
|
|
@@ -580,7 +581,7 @@ function useHubIntegrations({ identity, client, can, callbackPath, onUnsupported
|
|
|
580
581
|
pendingPopup.current?.abort();
|
|
581
582
|
pendingPopup.current = null;
|
|
582
583
|
setDialog(null);
|
|
583
|
-
setConnectStatus({ scope, status: "failed", message: "Connection was cancelled." });
|
|
584
|
+
setConnectStatus((current) => ({ scope, status: "failed", providerId: current.scope === scope ? current.providerId : void 0, message: "Connection was cancelled." }));
|
|
584
585
|
}, [scope]);
|
|
585
586
|
const rows = useMemo(
|
|
586
587
|
() => catalogRows(
|
|
@@ -593,7 +594,7 @@ function useHubIntegrations({ identity, client, can, callbackPath, onUnsupported
|
|
|
593
594
|
),
|
|
594
595
|
[providerList, connectionList, visibleSelection.providerId, visibleSelection.connectionId, can, onUnsupportedConnect, hostConnect]
|
|
595
596
|
);
|
|
596
|
-
const details = detail.status === "ready" && connection ? permissionDetails(detail.value.tools, detail.value.policies, connection.id, can) : void 0;
|
|
597
|
+
const details = detail.status === "ready" && connection ? permissionDetails(detail.value.tools, detail.value.policies, connection.id, can, (tool, storedDecision) => getActionPolicy?.({ connection, tool, storedDecision })) : void 0;
|
|
597
598
|
return {
|
|
598
599
|
providers,
|
|
599
600
|
connections,
|
|
@@ -795,6 +796,7 @@ function HubIntegrationsPanel(props) {
|
|
|
795
796
|
}, [hub.connectStatus.status, scope]);
|
|
796
797
|
const connectedProviders = new Set(connectionList.filter((connection) => connection.status !== "revoked").map((connection) => connection.providerId));
|
|
797
798
|
const catalogRows2 = props.accounts && hub.connections.status === "error" ? [] : props.accounts ? rows.filter((row) => !connectedProviders.has(row.providerId)).map((row) => ({ ...row, connections: [], selectedConnectionId: null })) : rows;
|
|
799
|
+
const retryProvider = hub.providers.status === "ready" ? hub.providers.value.find((provider) => provider.providerId === hub.connectStatus.providerId) : void 0;
|
|
798
800
|
const selectedConnection = hub.connection;
|
|
799
801
|
const detailError = hub.detail.status === "error" ? hub.detail.message : hub.detail.status === "ready" && hub.detail.value.truncated ? `Showing at most ${hub.detail.value.tools.length} actions. The provider catalog may be incomplete.` : null;
|
|
800
802
|
const detailsByConnectionId = useMemo2(() => hub.connection ? {
|
|
@@ -841,7 +843,6 @@ function HubIntegrationsPanel(props) {
|
|
|
841
843
|
loading: hub.providers.status === "loading" || hub.connections.status === "loading",
|
|
842
844
|
error,
|
|
843
845
|
actionError: mutationError,
|
|
844
|
-
connectError: hub.connectStatus.status === "failed" ? hub.connectStatus.message : null,
|
|
845
846
|
busyProviderId: hub.connectStatus.status === "pending" ? hub.connectStatus.providerId ?? null : null,
|
|
846
847
|
reserveConnectionContext: true,
|
|
847
848
|
emptyCatalogLabel: props.accounts && rows.length > 0 ? "Every available integration has a connected account. Manage an account to connect another." : void 0
|
|
@@ -855,7 +856,10 @@ function HubIntegrationsPanel(props) {
|
|
|
855
856
|
hub.writeState.status === "pending" ? /* @__PURE__ */ jsx2("p", { role: "status", children: "Saving Hub settings\u2026" }) : null,
|
|
856
857
|
mutationError ? /* @__PURE__ */ jsx2("p", { role: "alert", children: mutationError }) : null,
|
|
857
858
|
writeMessage ? /* @__PURE__ */ jsx2("p", { role: writeWarning ? "alert" : "status", children: writeMessage }) : null,
|
|
858
|
-
hub.connectStatus.
|
|
859
|
+
hub.connectStatus.status === "failed" ? /* @__PURE__ */ jsxs2("div", { role: "alert", className: "mb-4 flex flex-wrap items-center gap-3 text-sm text-destructive", children: [
|
|
860
|
+
/* @__PURE__ */ jsx2("p", { "data-testid": "integration-connect-error", children: hub.connectStatus.message }),
|
|
861
|
+
retryProvider ? /* @__PURE__ */ jsx2(Button, { type: "button", variant: "outline", disabled: pending, onClick: () => hub.beginConnect(retryProvider), children: "Retry connection" }) : null
|
|
862
|
+
] }) : hub.connectStatus.message && hub.provider ? /* @__PURE__ */ jsx2("p", { role: "status", children: hub.connectStatus.message }) : null,
|
|
859
863
|
hub.provider ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
860
864
|
props.accounts && selectedConnection || selectedRow?.canConnect ? /* @__PURE__ */ jsxs2("div", { className: "mb-4 flex min-w-0 flex-wrap items-center gap-2", children: [
|
|
861
865
|
props.accounts && selectedConnection ? /* @__PURE__ */ jsxs2(
|