@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 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-v1`,
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. Repeated behavior is verified on one representative
55
- target before the remaining targets are changed. Declarative `hoverEffect`,
56
- `tapEffect`, and `appearEffect` styling uses the `native_effect` mechanism and
57
- is verified from the exact reflected property on each target. A failed
58
- representative may receive scoped `SET` repairs without unlocking fan-out.
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.1
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.1-mac-arm64.dmg
313
- NEXT_PUBLIC_DEXTER_BRIDGE_MAC_X64_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.1-mac-x64.dmg
314
- NEXT_PUBLIC_DEXTER_BRIDGE_MAC_ZIP_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.1-mac-universal.zip
315
- NEXT_PUBLIC_DEXTER_BRIDGE_WIN_X64_URL=https://instawebai.com/downloads/dexter-bridge/Dexter-Bridge-0.11.1-win-x64.exe
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.4",
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
- const raw = String(value || '').trim();
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 = run?.companion?.model?.invocationName;
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 schema = outcomeOutputSchema();
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 === 'completed'
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 === 'completed'
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 === 'completed'
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: supportedModels.map((candidate) => candidate.id),
3080
- modelDetails: supportedModels,
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
- const availability = await inspectAvailability(activeConfig, runtimeSelection);
512
- const runtimeProfile = runtimeSelection
513
+ let availability = await inspectAvailability(activeConfig, runtimeSelection);
514
+ let runtimeProfile = runtimeSelection
513
515
  ? requireAvailableRuntime(availability, runtimeSelection)
514
516
  : null;
515
- const check = runtimeProfile
517
+ let check = runtimeProfile
516
518
  ? null
517
519
  : requireAvailableAgent(availability, agent);
518
- const model = runtimeProfile
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
- const metadata = availability.metadata;
524
+ let metadata = availability.metadata;
523
525
  const pollLogger = createRunLogger({ runId: 'bridge-poll' });
524
- const profiles = runtimeProfileMap(availability.runtimeProfiles);
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-v1',
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 (/^[a-z0-9][a-z0-9._-]{0,159}$/.test(invocationName)) {
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,