@gakim-digital/dexter-bridge 0.11.4 → 0.11.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.
- package/README.md +20 -11
- package/package.json +3 -2
- package/src/agent.js +47 -16
- package/src/cli.js +37 -6
- package/src/config.js +6 -2
- package/src/framerAgentTools.js +353 -72
- package/src/framerBehaviorContract.js +516 -103
- package/src/protocol.js +34 -12
- package/src/providers/claudeAgentSdk.js +169 -0
- package/src/providers/codexAppServer.js +68 -12
- package/src/runtimeProfiles.js +27 -2
package/README.md
CHANGED
|
@@ -47,15 +47,18 @@ Native shell, filesystem, web, and unrelated-project access are disabled for
|
|
|
47
47
|
Framer outcome runs. Publishing is denied unless a future assignment explicitly
|
|
48
48
|
authorizes it.
|
|
49
49
|
|
|
50
|
-
Bridges advertise `framer-interaction-verification-
|
|
50
|
+
Bridges advertise `framer-interaction-verification-v3`,
|
|
51
51
|
`framer-behavior-contract-v1`, and `framer-mechanism-verification-v1` only
|
|
52
52
|
when interaction work is declared as observable behavior, checked against the
|
|
53
53
|
selected Framer mechanism before mutation, and verified from live canonical
|
|
54
|
-
Framer nodes afterward.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
Framer nodes afterward. Verification is tri-state: `verified`,
|
|
55
|
+
`contradicted`, or `unknown`. Unknown evidence cannot trigger a repair or
|
|
56
|
+
reject a mechanism. Native navigation is verified from link and component
|
|
57
|
+
control destinations instead of requiring synthetic frame events. Repeated
|
|
58
|
+
behavior is verified on one declared representative target before the
|
|
59
|
+
remaining targets are changed. Only a concrete contradiction may receive one
|
|
60
|
+
scoped `SET` repair. Repeated unchanged evidence is bounded and exits as a
|
|
61
|
+
partial result rather than looping.
|
|
59
62
|
|
|
60
63
|
Project connection is handled inside Dexter and completes before the model
|
|
61
64
|
starts:
|
|
@@ -196,6 +199,12 @@ DEXTER_BRIDGE_AGENT=claude-code npx @gakim-digital/dexter-bridge start
|
|
|
196
199
|
DEXTER_BRIDGE_AGENT=codex npx @gakim-digital/dexter-bridge start
|
|
197
200
|
```
|
|
198
201
|
|
|
202
|
+
The bridge discovers models from the signed-in local runtime instead of relying
|
|
203
|
+
on a release-time list. Claude Code models come from the official Agent SDK's
|
|
204
|
+
`supportedModels()` result, and Codex models come from every page of App
|
|
205
|
+
Server's `model/list` result. The desktop bridge refreshes this catalog during
|
|
206
|
+
its normal heartbeat loop; the CLI refreshes it every five minutes.
|
|
207
|
+
|
|
199
208
|
Codex App Server runs planning turns as a model-only engine in a dedicated
|
|
200
209
|
empty temporary workspace. For an App Builder outcome, it receives a disposable
|
|
201
210
|
snapshot and workspace-write access only inside that snapshot; network, app,
|
|
@@ -307,10 +316,10 @@ Recommended release env:
|
|
|
307
316
|
|
|
308
317
|
```bash
|
|
309
318
|
FRAMER_COMPANION_DOWNLOAD_URL=https://instawebai.com/dexter-bridge
|
|
310
|
-
NEXT_PUBLIC_DEXTER_BRIDGE_VERSION=0.11.
|
|
319
|
+
NEXT_PUBLIC_DEXTER_BRIDGE_VERSION=0.11.5
|
|
311
320
|
NEXT_PUBLIC_DEXTER_BRIDGE_DOWNLOAD_BASE_URL=https://instawebai.com/downloads/dexter-bridge
|
|
312
|
-
NEXT_PUBLIC_DEXTER_BRIDGE_MAC_ARM64_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.
|
|
313
|
-
NEXT_PUBLIC_DEXTER_BRIDGE_MAC_X64_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.
|
|
314
|
-
NEXT_PUBLIC_DEXTER_BRIDGE_MAC_ZIP_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.
|
|
315
|
-
NEXT_PUBLIC_DEXTER_BRIDGE_WIN_X64_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.
|
|
321
|
+
NEXT_PUBLIC_DEXTER_BRIDGE_MAC_ARM64_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.5-mac-arm64.dmg
|
|
322
|
+
NEXT_PUBLIC_DEXTER_BRIDGE_MAC_X64_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.5-mac-x64.dmg
|
|
323
|
+
NEXT_PUBLIC_DEXTER_BRIDGE_MAC_ZIP_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.5-mac-universal.zip
|
|
324
|
+
NEXT_PUBLIC_DEXTER_BRIDGE_WIN_X64_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.5-win-x64.exe
|
|
316
325
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gakim-digital/dexter-bridge",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
4
|
"description": "Local bridge for InstaWebAI and Dexter — runs Codex, Claude Code, or OpenCode on your machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,13 +32,14 @@
|
|
|
32
32
|
},
|
|
33
33
|
"license": "SEE LICENSE IN LICENSE",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@framer/agent": "0.0.44",
|
|
36
35
|
"@ai-sdk/anthropic": "^4.0.46",
|
|
37
36
|
"@ai-sdk/deepseek": "^3.0.37",
|
|
38
37
|
"@ai-sdk/google": "^4.0.58",
|
|
39
38
|
"@ai-sdk/openai": "^4.0.52",
|
|
40
39
|
"@ai-sdk/openai-compatible": "^3.0.41",
|
|
41
40
|
"@ai-sdk/xai": "^4.0.50",
|
|
41
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.73",
|
|
42
|
+
"@framer/agent": "0.0.44",
|
|
42
43
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
43
44
|
"@opencode-ai/sdk": "^1.18.25",
|
|
44
45
|
"@openrouter/ai-sdk-provider": "^3.0.0",
|
package/src/agent.js
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
normalizeOutcomeCompletion,
|
|
36
36
|
normalizeStructuredResponseContract,
|
|
37
37
|
outcomeOutputSchema,
|
|
38
|
+
outcomeSuccessStatus,
|
|
38
39
|
runSummary,
|
|
39
40
|
STRUCTURED_RESPONSE_CONTRACTS,
|
|
40
41
|
} from './protocol.js';
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
normalizeNativeSkills,
|
|
47
48
|
} from './nativeSkills.js';
|
|
48
49
|
import { createLocalAgentAdapter } from './providers/index.js';
|
|
50
|
+
import { discoverClaudeCodeModels } from './providers/claudeAgentSdk.js';
|
|
49
51
|
import {
|
|
50
52
|
collectOutcomeWorkspaceChanges,
|
|
51
53
|
materializeOutcomeReferenceAssets,
|
|
@@ -294,13 +296,6 @@ function argsWithRequiredAgentFlags(args, definition) {
|
|
|
294
296
|
return [...args, '--skip-git-repo-check'];
|
|
295
297
|
}
|
|
296
298
|
|
|
297
|
-
const CLAUDE_CLI_MODEL_IDS = {
|
|
298
|
-
fable: 'claude-fable-5',
|
|
299
|
-
opus: 'claude-opus-5',
|
|
300
|
-
sonnet: 'claude-sonnet-5',
|
|
301
|
-
haiku: 'claude-haiku-4-5-20251001',
|
|
302
|
-
};
|
|
303
|
-
|
|
304
299
|
function claudeModelEngineSystemPrompt(product) {
|
|
305
300
|
const name = normalizeBridgeProduct(product).name;
|
|
306
301
|
return [
|
|
@@ -342,8 +337,7 @@ function claudeOutcomeSystemPrompt(product) {
|
|
|
342
337
|
}
|
|
343
338
|
|
|
344
339
|
export function mapClaudeCliModelId(value) {
|
|
345
|
-
|
|
346
|
-
return CLAUDE_CLI_MODEL_IDS[raw.toLowerCase()] || raw;
|
|
340
|
+
return String(value || '').trim();
|
|
347
341
|
}
|
|
348
342
|
|
|
349
343
|
function argsWithSelectedModel(args, modelDefinition, definition) {
|
|
@@ -1324,6 +1318,14 @@ function adapterInvocationModel(run, selectedModel, agent) {
|
|
|
1324
1318
|
if (typeof runtimeInvocation === 'string' && runtimeInvocation.trim()) {
|
|
1325
1319
|
return runtimeInvocation.trim();
|
|
1326
1320
|
}
|
|
1321
|
+
const explicitModel = run?.companion?.model;
|
|
1322
|
+
if (
|
|
1323
|
+
explicitModel?.id === selectedModel?.id
|
|
1324
|
+
&& typeof explicitModel?.invocationName === 'string'
|
|
1325
|
+
&& explicitModel.invocationName.trim()
|
|
1326
|
+
) {
|
|
1327
|
+
return explicitModel.invocationName.trim();
|
|
1328
|
+
}
|
|
1327
1329
|
if (
|
|
1328
1330
|
run?.companion?.modelPolicy?.owner === 'harness'
|
|
1329
1331
|
&& typeof selectedModel?.invocationName === 'string'
|
|
@@ -1331,7 +1333,7 @@ function adapterInvocationModel(run, selectedModel, agent) {
|
|
|
1331
1333
|
) {
|
|
1332
1334
|
return selectedModel.invocationName.trim();
|
|
1333
1335
|
}
|
|
1334
|
-
const explicit =
|
|
1336
|
+
const explicit = explicitModel?.invocationName;
|
|
1335
1337
|
if (typeof explicit === 'string' && explicit.trim()) return explicit.trim();
|
|
1336
1338
|
if (selectedModel?.invocationName) return selectedModel.invocationName;
|
|
1337
1339
|
const id = run?.companion?.model?.id || run?.model;
|
|
@@ -2443,7 +2445,8 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2443
2445
|
onActivity: progress,
|
|
2444
2446
|
});
|
|
2445
2447
|
let prompt = '';
|
|
2446
|
-
const
|
|
2448
|
+
const successStatus = outcomeSuccessStatus(executionOutcome, product);
|
|
2449
|
+
const schema = outcomeOutputSchema(successStatus);
|
|
2447
2450
|
assertOutcomeResponseContract(
|
|
2448
2451
|
STRUCTURED_RESPONSE_CONTRACTS.OUTCOME,
|
|
2449
2452
|
schema,
|
|
@@ -2649,18 +2652,19 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2649
2652
|
let rawCompletion = normalizeOutcomeCompletion(
|
|
2650
2653
|
extractJsonObject(result?.text || result?.resultText || ''),
|
|
2651
2654
|
run?.outcome?.outcomeId,
|
|
2655
|
+
successStatus,
|
|
2652
2656
|
);
|
|
2653
2657
|
let synchronization = null;
|
|
2654
2658
|
if (harnessTools.directWorkspace) {
|
|
2655
2659
|
synchronization = await harnessTools.inspect();
|
|
2656
2660
|
} else if (
|
|
2657
|
-
rawCompletion.status ===
|
|
2661
|
+
rawCompletion.status === successStatus
|
|
2658
2662
|
|| harnessTools.hasPendingChanges()
|
|
2659
2663
|
) {
|
|
2660
2664
|
synchronization = await harnessTools.synchronize();
|
|
2661
2665
|
}
|
|
2662
2666
|
const repairPrompt =
|
|
2663
|
-
rawCompletion.status ===
|
|
2667
|
+
rawCompletion.status === successStatus
|
|
2664
2668
|
&& typeof harnessTools.verificationRepairPrompt === 'function'
|
|
2665
2669
|
? harnessTools.verificationRepairPrompt()
|
|
2666
2670
|
: null;
|
|
@@ -2696,6 +2700,7 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2696
2700
|
repairResult?.text || repairResult?.resultText || '',
|
|
2697
2701
|
),
|
|
2698
2702
|
run?.outcome?.outcomeId,
|
|
2703
|
+
successStatus,
|
|
2699
2704
|
);
|
|
2700
2705
|
synchronization = harnessTools.directWorkspace
|
|
2701
2706
|
? await harnessTools.inspect()
|
|
@@ -2749,7 +2754,7 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2749
2754
|
outcomeId: run?.outcome?.outcomeId,
|
|
2750
2755
|
model: selectedModelId,
|
|
2751
2756
|
status:
|
|
2752
|
-
normalized.status ===
|
|
2757
|
+
normalized.status === successStatus
|
|
2753
2758
|
? 'succeeded'
|
|
2754
2759
|
: normalized.status,
|
|
2755
2760
|
durationMs: Date.now() - callStartedAt,
|
|
@@ -3057,6 +3062,7 @@ export async function checkAgentAvailability(agent, model, options = {}) {
|
|
|
3057
3062
|
label: definition.label,
|
|
3058
3063
|
models: reportedModels.map((candidate) => candidate.id),
|
|
3059
3064
|
modelDetails: reportedModels,
|
|
3065
|
+
modelCatalogSource: 'runtime',
|
|
3060
3066
|
installed: true,
|
|
3061
3067
|
signedIn: true,
|
|
3062
3068
|
status: 'ready',
|
|
@@ -3070,14 +3076,39 @@ export async function checkAgentAvailability(agent, model, options = {}) {
|
|
|
3070
3076
|
platform: options.platform,
|
|
3071
3077
|
inspect: options.inspectAuthentication,
|
|
3072
3078
|
});
|
|
3079
|
+
let reportedModels = supportedModels;
|
|
3080
|
+
let modelCatalogSource = 'fallback';
|
|
3081
|
+
let modelCatalogError = null;
|
|
3082
|
+
if (authentication.ok && normalizedAgent === 'claude-code') {
|
|
3083
|
+
try {
|
|
3084
|
+
const discovered = await (
|
|
3085
|
+
options.discoverClaudeModels
|
|
3086
|
+
|| discoverClaudeCodeModels
|
|
3087
|
+
)({
|
|
3088
|
+
command: runtime.command,
|
|
3089
|
+
env: options.env,
|
|
3090
|
+
cacheKey: `${runtime.command}:${runtime.version || 'unknown'}`,
|
|
3091
|
+
forceRefresh: options.forceModelRefresh === true,
|
|
3092
|
+
queryFactory: options.claudeQueryFactory,
|
|
3093
|
+
});
|
|
3094
|
+
if (discovered.length > 0) {
|
|
3095
|
+
reportedModels = discovered;
|
|
3096
|
+
modelCatalogSource = 'runtime';
|
|
3097
|
+
}
|
|
3098
|
+
} catch (error) {
|
|
3099
|
+
modelCatalogError = error?.message || String(error);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3073
3102
|
return {
|
|
3074
3103
|
...runtime,
|
|
3075
3104
|
...authentication,
|
|
3076
3105
|
ok: runtime.ok && authentication.ok,
|
|
3077
3106
|
agent: definition.id,
|
|
3078
3107
|
label: definition.label,
|
|
3079
|
-
models:
|
|
3080
|
-
modelDetails:
|
|
3108
|
+
models: reportedModels.map((candidate) => candidate.id),
|
|
3109
|
+
modelDetails: reportedModels,
|
|
3110
|
+
modelCatalogSource,
|
|
3111
|
+
...(modelCatalogError ? { modelCatalogError } : {}),
|
|
3081
3112
|
installed: true,
|
|
3082
3113
|
};
|
|
3083
3114
|
}
|
package/src/cli.js
CHANGED
|
@@ -37,6 +37,8 @@ import {
|
|
|
37
37
|
runtimeProfileMap,
|
|
38
38
|
} from './runtimeProfiles.js';
|
|
39
39
|
|
|
40
|
+
const MODEL_CATALOG_REFRESH_MS = 5 * 60 * 1000;
|
|
41
|
+
|
|
40
42
|
function usage() {
|
|
41
43
|
return [
|
|
42
44
|
'Local Agent Bridge for Dexter and InstaWebAI',
|
|
@@ -508,20 +510,20 @@ async function startCommand({ apiBaseUrl, config, flags, configDir }) {
|
|
|
508
510
|
const waitMs = Number(flags['wait-ms'] || 25000);
|
|
509
511
|
const once = Boolean(flags.once);
|
|
510
512
|
const bridgeEnv = agentEnvironment(activeConfig);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
+
let availability = await inspectAvailability(activeConfig, runtimeSelection);
|
|
514
|
+
let runtimeProfile = runtimeSelection
|
|
513
515
|
? requireAvailableRuntime(availability, runtimeSelection)
|
|
514
516
|
: null;
|
|
515
|
-
|
|
517
|
+
let check = runtimeProfile
|
|
516
518
|
? null
|
|
517
519
|
: requireAvailableAgent(availability, agent);
|
|
518
|
-
|
|
520
|
+
let model = runtimeProfile
|
|
519
521
|
? runtimeProfile.models.find((candidate) => candidate.id === requestedModel)?.id
|
|
520
522
|
|| runtimeProfile.models[0].id
|
|
521
523
|
: availableModel(check, requestedModel);
|
|
522
|
-
|
|
524
|
+
let metadata = availability.metadata;
|
|
523
525
|
const pollLogger = createRunLogger({ runId: 'bridge-poll' });
|
|
524
|
-
|
|
526
|
+
let profiles = runtimeProfileMap(availability.runtimeProfiles);
|
|
525
527
|
const providerAdapters = new Map();
|
|
526
528
|
const providerAdapterForProfile = (runtimeProfile) => {
|
|
527
529
|
const profile = profiles.get(runtimeProfile?.id) || runtimeProfile;
|
|
@@ -537,6 +539,7 @@ async function startCommand({ apiBaseUrl, config, flags, configDir }) {
|
|
|
537
539
|
return adapter;
|
|
538
540
|
};
|
|
539
541
|
let pollFailureCount = 0;
|
|
542
|
+
let nextCatalogRefreshAt = Date.now() + MODEL_CATALOG_REFRESH_MS;
|
|
540
543
|
|
|
541
544
|
console.log(`${product.name} Bridge connected to ${apiBaseUrl}`);
|
|
542
545
|
console.log(
|
|
@@ -550,6 +553,34 @@ async function startCommand({ apiBaseUrl, config, flags, configDir }) {
|
|
|
550
553
|
|
|
551
554
|
try {
|
|
552
555
|
while (true) {
|
|
556
|
+
if (Date.now() >= nextCatalogRefreshAt) {
|
|
557
|
+
try {
|
|
558
|
+
const refreshedAvailability = await inspectAvailability(activeConfig, runtimeSelection);
|
|
559
|
+
const refreshedRuntimeProfile = runtimeSelection
|
|
560
|
+
? requireAvailableRuntime(refreshedAvailability, runtimeSelection)
|
|
561
|
+
: null;
|
|
562
|
+
const refreshedCheck = refreshedRuntimeProfile
|
|
563
|
+
? null
|
|
564
|
+
: requireAvailableAgent(refreshedAvailability, agent);
|
|
565
|
+
availability = refreshedAvailability;
|
|
566
|
+
runtimeProfile = refreshedRuntimeProfile;
|
|
567
|
+
check = refreshedCheck;
|
|
568
|
+
model = runtimeProfile
|
|
569
|
+
? runtimeProfile.models.find((candidate) => candidate.id === requestedModel)?.id
|
|
570
|
+
|| runtimeProfile.models[0].id
|
|
571
|
+
: availableModel(check, requestedModel);
|
|
572
|
+
metadata = availability.metadata;
|
|
573
|
+
profiles = runtimeProfileMap(availability.runtimeProfiles);
|
|
574
|
+
pollLogger.info('model_catalog_refreshed', {
|
|
575
|
+
models: availability.runtimeProfiles.flatMap((profile) =>
|
|
576
|
+
profile.models.map((candidate) => candidate.id)),
|
|
577
|
+
});
|
|
578
|
+
} catch (error) {
|
|
579
|
+
pollLogger.warn('model_catalog_refresh_failed', { error: errorMeta(error) });
|
|
580
|
+
} finally {
|
|
581
|
+
nextCatalogRefreshAt = Date.now() + MODEL_CATALOG_REFRESH_MS;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
553
584
|
let poll;
|
|
554
585
|
try {
|
|
555
586
|
poll = await pollRun(apiBaseUrl, {
|
package/src/config.js
CHANGED
|
@@ -25,6 +25,7 @@ export const BRIDGE_BUILD_FINGERPRINT = crypto
|
|
|
25
25
|
'./outcomeWorkspace.js',
|
|
26
26
|
'./runtimeProfiles.js',
|
|
27
27
|
'./providers/index.js',
|
|
28
|
+
'./providers/claudeAgentSdk.js',
|
|
28
29
|
'./providers/codexAppServer.js',
|
|
29
30
|
'./providers/codexStructuredOutput.js',
|
|
30
31
|
'./providers/directByok.js',
|
|
@@ -42,7 +43,7 @@ export const BRIDGE_CAPABILITIES = [
|
|
|
42
43
|
'framer-agent-v1',
|
|
43
44
|
'framer-project-bootstrap-v1',
|
|
44
45
|
'framer-project-verification-v1',
|
|
45
|
-
'framer-interaction-verification-
|
|
46
|
+
'framer-interaction-verification-v3',
|
|
46
47
|
'framer-behavior-contract-v1',
|
|
47
48
|
'framer-mechanism-verification-v1',
|
|
48
49
|
'project-shell-v1',
|
|
@@ -261,7 +262,10 @@ export function companionModelDefinition(value, agent = DEFAULT_BRIDGE_AGENT) {
|
|
|
261
262
|
const prefix = `${normalizedAgent}:`;
|
|
262
263
|
if (raw.startsWith(prefix)) {
|
|
263
264
|
const invocationName = raw.slice(prefix.length);
|
|
264
|
-
if (
|
|
265
|
+
if (
|
|
266
|
+
!invocationName.includes('..')
|
|
267
|
+
&& /^[a-z0-9][a-z0-9._/-]{0,253}$/.test(invocationName)
|
|
268
|
+
) {
|
|
265
269
|
return {
|
|
266
270
|
id: `${normalizedAgent}:${invocationName}`,
|
|
267
271
|
agent: normalizedAgent,
|