@gakim-digital/dexter-bridge 0.11.6 → 0.11.8
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/package.json +1 -1
- package/src/agent.js +42 -2
- package/src/api.js +9 -0
- package/src/cli.js +105 -12
- package/src/config.js +1 -0
- package/src/framerAgentTools.js +62 -15
- package/src/runtimeProfiles.js +24 -0
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import crossSpawn from 'cross-spawn';
|
|
7
7
|
import {
|
|
8
8
|
getFramerProjectAuthorization,
|
|
9
|
+
isCompanionPairingInvalidError,
|
|
9
10
|
postRunEvent,
|
|
10
11
|
reportFramerProjectAuthorizationVerification,
|
|
11
12
|
} from './api.js';
|
|
@@ -1523,6 +1524,9 @@ async function callProviderAdapter(
|
|
|
1523
1524
|
if (controlRequestsCancellation(response)) await requestCancel();
|
|
1524
1525
|
})
|
|
1525
1526
|
.catch((error) => {
|
|
1527
|
+
if (isCompanionPairingInvalidError(error)) {
|
|
1528
|
+
void requestCancel();
|
|
1529
|
+
}
|
|
1526
1530
|
if (!finished) {
|
|
1527
1531
|
trace?.warn('adapter_activity_post_failed', {
|
|
1528
1532
|
error: errorMeta(error),
|
|
@@ -1562,6 +1566,10 @@ async function callProviderAdapter(
|
|
|
1562
1566
|
if (controlRequestsCancellation(response)) await requestCancel();
|
|
1563
1567
|
} catch (error) {
|
|
1564
1568
|
if (monitorAbort.signal.aborted) return;
|
|
1569
|
+
if (isCompanionPairingInvalidError(error)) {
|
|
1570
|
+
await requestCancel();
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1565
1573
|
trace?.warn('adapter_control_poll_failed', { error: errorMeta(error) });
|
|
1566
1574
|
}
|
|
1567
1575
|
}
|
|
@@ -2006,6 +2014,9 @@ async function executeModelTurnRun(run, send, agent, options = {}) {
|
|
|
2006
2014
|
if (controlRequestsCancellation(response)) requestCliCancel();
|
|
2007
2015
|
})
|
|
2008
2016
|
.catch((error) => {
|
|
2017
|
+
if (isCompanionPairingInvalidError(error)) {
|
|
2018
|
+
requestCliCancel();
|
|
2019
|
+
}
|
|
2009
2020
|
if (!cliFinished) {
|
|
2010
2021
|
options.trace?.warn('cli_activity_post_failed', {
|
|
2011
2022
|
error: errorMeta(error),
|
|
@@ -2044,6 +2055,10 @@ async function executeModelTurnRun(run, send, agent, options = {}) {
|
|
|
2044
2055
|
if (cliCancelled) return;
|
|
2045
2056
|
} catch (error) {
|
|
2046
2057
|
if (monitorAbort.signal.aborted) return;
|
|
2058
|
+
if (isCompanionPairingInvalidError(error)) {
|
|
2059
|
+
requestCliCancel();
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2047
2062
|
options.trace?.warn('cli_control_poll_failed', {
|
|
2048
2063
|
error: errorMeta(error),
|
|
2049
2064
|
});
|
|
@@ -2255,6 +2270,9 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2255
2270
|
}
|
|
2256
2271
|
})
|
|
2257
2272
|
.catch((error) => {
|
|
2273
|
+
if (isCompanionPairingInvalidError(error)) {
|
|
2274
|
+
void requestCancel('device_disconnect');
|
|
2275
|
+
}
|
|
2258
2276
|
if (!finished) {
|
|
2259
2277
|
options.trace?.warn?.('outcome_activity_post_failed', {
|
|
2260
2278
|
error: errorMeta(error),
|
|
@@ -2331,6 +2349,10 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2331
2349
|
await requestCancel(controlCancellationSource(response));
|
|
2332
2350
|
}
|
|
2333
2351
|
} catch (error) {
|
|
2352
|
+
if (isCompanionPairingInvalidError(error)) {
|
|
2353
|
+
await requestCancel('device_disconnect');
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2334
2356
|
if (!controlMonitorAbort.signal.aborted && !cancelRequested) {
|
|
2335
2357
|
options.trace?.warn?.('outcome_control_poll_failed', {
|
|
2336
2358
|
error: errorMeta(error),
|
|
@@ -2991,6 +3013,7 @@ export function checkCommand(command, args = ['--version'], timeoutMs = 10000) {
|
|
|
2991
3013
|
|
|
2992
3014
|
export async function checkAgentAvailability(agent, model, options = {}) {
|
|
2993
3015
|
const normalizedAgent = normalizeAgentName(agent);
|
|
3016
|
+
const modelCatalogRefreshedAt = nowIso();
|
|
2994
3017
|
if (normalizedAgent === 'dry-run') {
|
|
2995
3018
|
return {
|
|
2996
3019
|
ok: true,
|
|
@@ -3002,6 +3025,8 @@ export async function checkAgentAvailability(agent, model, options = {}) {
|
|
|
3002
3025
|
installed: true,
|
|
3003
3026
|
signedIn: true,
|
|
3004
3027
|
status: 'ready',
|
|
3028
|
+
modelCatalogSource: 'runtime',
|
|
3029
|
+
modelCatalogRefreshedAt,
|
|
3005
3030
|
};
|
|
3006
3031
|
}
|
|
3007
3032
|
const definition = definitionForAgent(normalizedAgent);
|
|
@@ -3025,6 +3050,8 @@ export async function checkAgentAvailability(agent, model, options = {}) {
|
|
|
3025
3050
|
installed: false,
|
|
3026
3051
|
signedIn: false,
|
|
3027
3052
|
status: 'unavailable',
|
|
3053
|
+
modelCatalogSource: 'fallback',
|
|
3054
|
+
modelCatalogRefreshedAt,
|
|
3028
3055
|
};
|
|
3029
3056
|
}
|
|
3030
3057
|
if (normalizedAgent === 'codex') {
|
|
@@ -3051,9 +3078,19 @@ export async function checkAgentAvailability(agent, model, options = {}) {
|
|
|
3051
3078
|
error:
|
|
3052
3079
|
detection.error
|
|
3053
3080
|
|| 'Codex is not signed in. Run `codex login` on this computer, then run the bridge command again.',
|
|
3081
|
+
modelCatalogSource: 'fallback',
|
|
3082
|
+
modelCatalogRefreshedAt,
|
|
3054
3083
|
};
|
|
3055
3084
|
}
|
|
3056
|
-
|
|
3085
|
+
let reportedModels = supportedModels;
|
|
3086
|
+
let modelCatalogSource = 'fallback';
|
|
3087
|
+
let modelCatalogError = null;
|
|
3088
|
+
try {
|
|
3089
|
+
reportedModels = await adapter.models();
|
|
3090
|
+
modelCatalogSource = 'runtime';
|
|
3091
|
+
} catch (error) {
|
|
3092
|
+
modelCatalogError = error?.message || String(error);
|
|
3093
|
+
}
|
|
3057
3094
|
return {
|
|
3058
3095
|
...runtime,
|
|
3059
3096
|
...detection,
|
|
@@ -3062,7 +3099,9 @@ export async function checkAgentAvailability(agent, model, options = {}) {
|
|
|
3062
3099
|
label: definition.label,
|
|
3063
3100
|
models: reportedModels.map((candidate) => candidate.id),
|
|
3064
3101
|
modelDetails: reportedModels,
|
|
3065
|
-
modelCatalogSource
|
|
3102
|
+
modelCatalogSource,
|
|
3103
|
+
modelCatalogRefreshedAt,
|
|
3104
|
+
...(modelCatalogError ? { modelCatalogError } : {}),
|
|
3066
3105
|
installed: true,
|
|
3067
3106
|
signedIn: true,
|
|
3068
3107
|
status: 'ready',
|
|
@@ -3108,6 +3147,7 @@ export async function checkAgentAvailability(agent, model, options = {}) {
|
|
|
3108
3147
|
models: reportedModels.map((candidate) => candidate.id),
|
|
3109
3148
|
modelDetails: reportedModels,
|
|
3110
3149
|
modelCatalogSource,
|
|
3150
|
+
modelCatalogRefreshedAt,
|
|
3111
3151
|
...(modelCatalogError ? { modelCatalogError } : {}),
|
|
3112
3152
|
installed: true,
|
|
3113
3153
|
};
|
package/src/api.js
CHANGED
|
@@ -16,6 +16,15 @@ export class DexterBridgeApiError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export function isCompanionPairingInvalidError(error) {
|
|
20
|
+
return error instanceof DexterBridgeApiError
|
|
21
|
+
&& error.status === 401
|
|
22
|
+
&& (
|
|
23
|
+
error.body?.code === 'FRAMER_COMPANION_NOT_PAIRED'
|
|
24
|
+
|| /not paired|pair again/i.test(error.message)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
function bearerHeaders(deviceToken) {
|
|
20
29
|
return deviceToken ? { Authorization: `Bearer ${deviceToken}` } : {};
|
|
21
30
|
}
|
package/src/cli.js
CHANGED
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
import { checkAgentAvailability, checkAllAgents, executeRun } from './agent.js';
|
|
20
20
|
import {
|
|
21
21
|
claimPairing,
|
|
22
|
-
DexterBridgeApiError,
|
|
23
22
|
heartbeat,
|
|
23
|
+
isCompanionPairingInvalidError,
|
|
24
24
|
pollRun,
|
|
25
25
|
} from './api.js';
|
|
26
26
|
import {
|
|
@@ -228,12 +228,7 @@ function wait(delayMs) {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
function isInvalidPairingError(error) {
|
|
231
|
-
return error
|
|
232
|
-
&& error.status === 401
|
|
233
|
-
&& (
|
|
234
|
-
error.body?.code === 'FRAMER_COMPANION_NOT_PAIRED'
|
|
235
|
-
|| /not paired|pair again/i.test(error.message)
|
|
236
|
-
);
|
|
231
|
+
return isCompanionPairingInvalidError(error);
|
|
237
232
|
}
|
|
238
233
|
|
|
239
234
|
function clearInvalidPairing(configDir, cause) {
|
|
@@ -266,7 +261,48 @@ function agentEnvironment(config = {}, baseEnv = process.env) {
|
|
|
266
261
|
return env;
|
|
267
262
|
}
|
|
268
263
|
|
|
269
|
-
|
|
264
|
+
function modelCatalogStatus(runtimeProfiles = []) {
|
|
265
|
+
return Object.fromEntries(
|
|
266
|
+
runtimeProfiles.flatMap((profile) => {
|
|
267
|
+
const agent =
|
|
268
|
+
profile.driver === 'codex-app-server'
|
|
269
|
+
? 'codex'
|
|
270
|
+
: profile.driver === 'claude-code-cli'
|
|
271
|
+
? 'claude-code'
|
|
272
|
+
: null;
|
|
273
|
+
return agent ? [[agent, profile.modelCatalog]] : [];
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function preserveLastKnownModelCatalog(previousProfiles = [], nextProfiles = []) {
|
|
279
|
+
const previousById = new Map(previousProfiles.map((profile) => [profile.id, profile]));
|
|
280
|
+
return nextProfiles.map((profile) => {
|
|
281
|
+
const previous = previousById.get(profile.id);
|
|
282
|
+
if (
|
|
283
|
+
!previous?.models?.length
|
|
284
|
+
|| profile.modelCatalog?.source === 'runtime'
|
|
285
|
+
|| !profile.modelCatalog?.error
|
|
286
|
+
) {
|
|
287
|
+
return profile;
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
...profile,
|
|
291
|
+
models: previous.models,
|
|
292
|
+
modelCatalog: {
|
|
293
|
+
...profile.modelCatalog,
|
|
294
|
+
source: 'stale',
|
|
295
|
+
modelCount: previous.models.length,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function inspectAvailability(
|
|
302
|
+
config = {},
|
|
303
|
+
runtimeSelection = null,
|
|
304
|
+
{ forceModelRefresh = false, previousRuntimeProfiles = [] } = {},
|
|
305
|
+
) {
|
|
270
306
|
try {
|
|
271
307
|
const bridgeEnv = agentEnvironment(config);
|
|
272
308
|
const checks =
|
|
@@ -275,22 +311,33 @@ async function inspectAvailability(config = {}, runtimeSelection = null) {
|
|
|
275
311
|
agents: [
|
|
276
312
|
await checkAgentAvailability(runtimeSelection, undefined, {
|
|
277
313
|
env: bridgeEnv,
|
|
314
|
+
forceModelRefresh,
|
|
278
315
|
}),
|
|
279
316
|
],
|
|
280
317
|
dryRun: null,
|
|
281
318
|
}
|
|
282
319
|
: runtimeSelection === 'opencode'
|
|
283
320
|
? { agents: [], dryRun: null }
|
|
284
|
-
: await checkAllAgents({ env: bridgeEnv });
|
|
321
|
+
: await checkAllAgents({ env: bridgeEnv, forceModelRefresh });
|
|
285
322
|
const allChecks = [...checks.agents, ...(checks.dryRun ? [checks.dryRun] : [])];
|
|
286
323
|
const available = allChecks.filter((check) => check.ok);
|
|
287
|
-
const
|
|
324
|
+
const discoveredRuntimeProfiles = await discoverRuntimeProfiles({
|
|
288
325
|
agentChecks: checks.agents,
|
|
289
326
|
config,
|
|
290
327
|
env: bridgeEnv,
|
|
291
328
|
runtimeSelection,
|
|
292
329
|
});
|
|
330
|
+
const runtimeProfiles = preserveLastKnownModelCatalog(
|
|
331
|
+
previousRuntimeProfiles,
|
|
332
|
+
discoveredRuntimeProfiles,
|
|
333
|
+
);
|
|
293
334
|
const profileModels = runtimeProfiles.flatMap((profile) => profile.models || []);
|
|
335
|
+
const catalogStatus = modelCatalogStatus(runtimeProfiles);
|
|
336
|
+
const refreshedAt = Object.values(catalogStatus)
|
|
337
|
+
.map((status) => status?.refreshedAt)
|
|
338
|
+
.filter(Boolean)
|
|
339
|
+
.sort()
|
|
340
|
+
.at(-1);
|
|
294
341
|
return {
|
|
295
342
|
metadata: {
|
|
296
343
|
availableAgents: available.map((check) => check.agent).join(','),
|
|
@@ -298,6 +345,8 @@ async function inspectAvailability(config = {}, runtimeSelection = null) {
|
|
|
298
345
|
availableModelDetails: JSON.stringify(
|
|
299
346
|
profileModels,
|
|
300
347
|
),
|
|
348
|
+
modelCatalogStatus: JSON.stringify(catalogStatus),
|
|
349
|
+
...(refreshedAt ? { modelCatalogRefreshedAt: refreshedAt } : {}),
|
|
301
350
|
agentVersions: available.map((check) => `${check.agent}=${check.version || 'unknown'}`).join(','),
|
|
302
351
|
bridgeCapabilities: BRIDGE_CAPABILITIES.join(','),
|
|
303
352
|
bridgeVersion: BRIDGE_VERSION,
|
|
@@ -555,7 +604,10 @@ async function startCommand({ apiBaseUrl, config, flags, configDir }) {
|
|
|
555
604
|
while (true) {
|
|
556
605
|
if (Date.now() >= nextCatalogRefreshAt) {
|
|
557
606
|
try {
|
|
558
|
-
const refreshedAvailability = await inspectAvailability(activeConfig, runtimeSelection
|
|
607
|
+
const refreshedAvailability = await inspectAvailability(activeConfig, runtimeSelection, {
|
|
608
|
+
forceModelRefresh: true,
|
|
609
|
+
previousRuntimeProfiles: availability.runtimeProfiles,
|
|
610
|
+
});
|
|
559
611
|
const refreshedRuntimeProfile = runtimeSelection
|
|
560
612
|
? requireAvailableRuntime(refreshedAvailability, runtimeSelection)
|
|
561
613
|
: null;
|
|
@@ -598,7 +650,9 @@ async function startCommand({ apiBaseUrl, config, flags, configDir }) {
|
|
|
598
650
|
status: error.status,
|
|
599
651
|
code: error.body?.code || null,
|
|
600
652
|
});
|
|
601
|
-
|
|
653
|
+
const disconnected = clearInvalidPairing(configDir, error);
|
|
654
|
+
console.log(disconnected.message);
|
|
655
|
+
return;
|
|
602
656
|
}
|
|
603
657
|
if (once) throw error;
|
|
604
658
|
pollFailureCount += 1;
|
|
@@ -610,6 +664,45 @@ async function startCommand({ apiBaseUrl, config, flags, configDir }) {
|
|
|
610
664
|
await wait(retryInMs);
|
|
611
665
|
continue;
|
|
612
666
|
}
|
|
667
|
+
if (poll?.control?.refreshModelCatalog) {
|
|
668
|
+
try {
|
|
669
|
+
const refreshedAvailability = await inspectAvailability(activeConfig, runtimeSelection, {
|
|
670
|
+
forceModelRefresh: true,
|
|
671
|
+
previousRuntimeProfiles: availability.runtimeProfiles,
|
|
672
|
+
});
|
|
673
|
+
availability = refreshedAvailability;
|
|
674
|
+
runtimeProfile = runtimeSelection
|
|
675
|
+
? requireAvailableRuntime(refreshedAvailability, runtimeSelection)
|
|
676
|
+
: null;
|
|
677
|
+
check = runtimeProfile
|
|
678
|
+
? null
|
|
679
|
+
: requireAvailableAgent(refreshedAvailability, agent);
|
|
680
|
+
model = runtimeProfile
|
|
681
|
+
? runtimeProfile.models.find((candidate) => candidate.id === requestedModel)?.id
|
|
682
|
+
|| runtimeProfile.models[0].id
|
|
683
|
+
: availableModel(check, requestedModel);
|
|
684
|
+
metadata = availability.metadata;
|
|
685
|
+
profiles = runtimeProfileMap(availability.runtimeProfiles);
|
|
686
|
+
await heartbeat(apiBaseUrl, {
|
|
687
|
+
deviceToken,
|
|
688
|
+
status: 'ready',
|
|
689
|
+
agent,
|
|
690
|
+
model,
|
|
691
|
+
metadata,
|
|
692
|
+
runtimeProfiles: availability.runtimeProfiles.map(publicRuntimeProfile),
|
|
693
|
+
allowInsecureHttp: flags['allow-insecure-http'],
|
|
694
|
+
});
|
|
695
|
+
pollLogger.info('model_catalog_refresh_completed', {
|
|
696
|
+
requestedAt: poll.control.refreshModelCatalog.requestedAt,
|
|
697
|
+
models: availability.runtimeProfiles.flatMap((profile) =>
|
|
698
|
+
profile.models.map((candidate) => candidate.id)),
|
|
699
|
+
});
|
|
700
|
+
} catch (error) {
|
|
701
|
+
pollLogger.warn('model_catalog_refresh_failed', { error: errorMeta(error) });
|
|
702
|
+
} finally {
|
|
703
|
+
nextCatalogRefreshAt = Date.now() + MODEL_CATALOG_REFRESH_MS;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
613
706
|
if (poll.run) {
|
|
614
707
|
console.log(`Claimed run ${poll.run.runId}.`);
|
|
615
708
|
const runtimeProfile = poll.run?.runtimeProfile || null;
|
package/src/config.js
CHANGED
package/src/framerAgentTools.js
CHANGED
|
@@ -503,7 +503,7 @@ const TOOL_BY_NAME = new Map(
|
|
|
503
503
|
]),
|
|
504
504
|
);
|
|
505
505
|
const sessionsByProject = new Map();
|
|
506
|
-
let
|
|
506
|
+
let framerSetupPromise = null;
|
|
507
507
|
|
|
508
508
|
function isRecord(value) {
|
|
509
509
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
@@ -537,6 +537,12 @@ function framerSkillRoot(env) {
|
|
|
537
537
|
].find((root) => fs.existsSync(path.join(root, 'SKILL.md'))) || null;
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
+
function isMissingFramerSkillError(error) {
|
|
541
|
+
return /could not find an installed framer skill|run [`'"]?@framer\/agent setup/i.test(
|
|
542
|
+
String(error?.message || ''),
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
540
546
|
function installedFramerAgentVersion() {
|
|
541
547
|
try {
|
|
542
548
|
let current = path.dirname(resolveFramerAgentCli());
|
|
@@ -1184,6 +1190,9 @@ export function createFramerAgentToolRuntime({
|
|
|
1184
1190
|
doctrineLoaded: false,
|
|
1185
1191
|
doctrineVersion: null,
|
|
1186
1192
|
};
|
|
1193
|
+
let framerSetupAttempts = 0;
|
|
1194
|
+
let framerSetupRecoveredSession = false;
|
|
1195
|
+
let framerSetupCompleted = hasInstalledFramerSkill(env);
|
|
1187
1196
|
const framerAgentVersion = installedFramerAgentVersion();
|
|
1188
1197
|
let operationSequence = 0;
|
|
1189
1198
|
let lastMutationSequence = 0;
|
|
@@ -1210,23 +1219,49 @@ export function createFramerAgentToolRuntime({
|
|
|
1210
1219
|
return result;
|
|
1211
1220
|
};
|
|
1212
1221
|
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1222
|
+
const ensureFramerSetup = async ({ force = false } = {}) => {
|
|
1223
|
+
const automaticSetupEnabled =
|
|
1224
|
+
runCli === runFramerAgentCli
|
|
1225
|
+
|| env.DEXTER_FRAMER_AUTO_SETUP === 'true';
|
|
1226
|
+
if (!force && !automaticSetupEnabled) return;
|
|
1227
|
+
if (!force && hasInstalledFramerSkill(env)) {
|
|
1228
|
+
framerSetupCompleted = true;
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
if (!framerSetupPromise) {
|
|
1232
|
+
framerSetupAttempts += 1;
|
|
1217
1233
|
reportActivity({
|
|
1218
1234
|
kind: 'status_update',
|
|
1219
1235
|
source: 'framer-agent',
|
|
1220
|
-
message: '
|
|
1236
|
+
message: 'Getting Framer ready.',
|
|
1221
1237
|
});
|
|
1222
|
-
|
|
1238
|
+
framerSetupPromise = executeCli(['setup'], {
|
|
1223
1239
|
timeoutMs: 30_000,
|
|
1224
|
-
})
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1240
|
+
})
|
|
1241
|
+
.then(() => {
|
|
1242
|
+
if (!hasInstalledFramerSkill(env)) {
|
|
1243
|
+
throw toolError(
|
|
1244
|
+
'FRAMER_AGENT_SETUP_FAILED',
|
|
1245
|
+
'Framer setup completed without installing the required Framer skill.',
|
|
1246
|
+
503,
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
framerSetupCompleted = true;
|
|
1250
|
+
})
|
|
1251
|
+
.catch((error) => {
|
|
1252
|
+
if (error?.code === 'FRAMER_AGENT_SETUP_FAILED') throw error;
|
|
1253
|
+
throw toolError(
|
|
1254
|
+
'FRAMER_AGENT_SETUP_FAILED',
|
|
1255
|
+
`Dexter could not prepare the Framer editing tools: ${String(error?.message || 'Framer setup failed.')}`,
|
|
1256
|
+
503,
|
|
1257
|
+
);
|
|
1258
|
+
})
|
|
1259
|
+
.finally(() => {
|
|
1260
|
+
framerSetupPromise = null;
|
|
1261
|
+
});
|
|
1228
1262
|
}
|
|
1229
|
-
await
|
|
1263
|
+
await framerSetupPromise;
|
|
1264
|
+
framerSetupCompleted = hasInstalledFramerSkill(env);
|
|
1230
1265
|
};
|
|
1231
1266
|
|
|
1232
1267
|
const projectIsAuthorizedLocally = async () => {
|
|
@@ -1351,7 +1386,7 @@ export function createFramerAgentToolRuntime({
|
|
|
1351
1386
|
};
|
|
1352
1387
|
|
|
1353
1388
|
const ensureSession = async () => {
|
|
1354
|
-
await
|
|
1389
|
+
await ensureFramerSetup();
|
|
1355
1390
|
if (sessionId) return sessionId;
|
|
1356
1391
|
if (sessionPromise) return sessionPromise;
|
|
1357
1392
|
sessionPromise = (async () => {
|
|
@@ -1397,7 +1432,14 @@ export function createFramerAgentToolRuntime({
|
|
|
1397
1432
|
if (authorization) {
|
|
1398
1433
|
await installProjectAuthorization(authorization);
|
|
1399
1434
|
}
|
|
1400
|
-
|
|
1435
|
+
try {
|
|
1436
|
+
created = await createSession();
|
|
1437
|
+
} catch (error) {
|
|
1438
|
+
if (!isMissingFramerSkillError(error)) throw error;
|
|
1439
|
+
await ensureFramerSetup({ force: true });
|
|
1440
|
+
created = await createSession();
|
|
1441
|
+
framerSetupRecoveredSession = true;
|
|
1442
|
+
}
|
|
1401
1443
|
} catch (error) {
|
|
1402
1444
|
if (isAuthorizationFailure(error)) {
|
|
1403
1445
|
await reportRejectedAuthorization(authorization, error);
|
|
@@ -2829,6 +2871,11 @@ export function createFramerAgentToolRuntime({
|
|
|
2829
2871
|
taskPlan: taskPlanSnapshot(),
|
|
2830
2872
|
verification: verification(),
|
|
2831
2873
|
framerAgentVersion,
|
|
2874
|
+
framerSetup: {
|
|
2875
|
+
completed: framerSetupCompleted,
|
|
2876
|
+
attempts: framerSetupAttempts,
|
|
2877
|
+
recoveredSession: framerSetupRecoveredSession,
|
|
2878
|
+
},
|
|
2832
2879
|
framerGuidanceLoaded: guidanceMetadata.loaded,
|
|
2833
2880
|
doctrineLoaded: promptMetadata.doctrineLoaded,
|
|
2834
2881
|
guidanceHash: guidanceMetadata.contentHash,
|
|
@@ -2863,5 +2910,5 @@ export function createFramerAgentToolRuntime({
|
|
|
2863
2910
|
|
|
2864
2911
|
export function __resetFramerAgentToolRuntimeForTests() {
|
|
2865
2912
|
sessionsByProject.clear();
|
|
2866
|
-
|
|
2913
|
+
framerSetupPromise = null;
|
|
2867
2914
|
}
|
package/src/runtimeProfiles.js
CHANGED
|
@@ -71,6 +71,20 @@ function normalizeCapabilities(value = {}) {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
function normalizeModelCatalog(value = {}) {
|
|
75
|
+
const source = ['runtime', 'fallback', 'stale', 'error'].includes(value.source)
|
|
76
|
+
? value.source
|
|
77
|
+
: 'fallback';
|
|
78
|
+
return {
|
|
79
|
+
source,
|
|
80
|
+
refreshedAt: text(value.refreshedAt, 80) || new Date().toISOString(),
|
|
81
|
+
modelCount: Number.isFinite(Number(value.modelCount))
|
|
82
|
+
? Math.max(0, Math.floor(Number(value.modelCount)))
|
|
83
|
+
: 0,
|
|
84
|
+
...(text(value.error, 500) ? { error: text(value.error, 500) } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
74
88
|
export function normalizeRuntimeProfile(value, { includeSecret = false } = {}) {
|
|
75
89
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
76
90
|
const driver = text(value.driver, 80);
|
|
@@ -110,6 +124,10 @@ export function normalizeRuntimeProfile(value, { includeSecret = false } = {}) {
|
|
|
110
124
|
continuationIdentity: text(value.continuationIdentity, 180) || stableIdentity(driver, instanceId),
|
|
111
125
|
capabilities: normalizeCapabilities(value.capabilities),
|
|
112
126
|
models,
|
|
127
|
+
modelCatalog: normalizeModelCatalog({
|
|
128
|
+
...(value.modelCatalog || {}),
|
|
129
|
+
modelCount: models.length,
|
|
130
|
+
}),
|
|
113
131
|
...(text(value.keyLast4, 16) ? { keyLast4: text(value.keyLast4, 16) } : {}),
|
|
114
132
|
...(text(value.command, 2048) ? { command: text(value.command, 2048) } : {}),
|
|
115
133
|
...(Array.isArray(value.args)
|
|
@@ -169,6 +187,12 @@ function profileFromAgentCheck(check) {
|
|
|
169
187
|
cancellation: true,
|
|
170
188
|
},
|
|
171
189
|
models: check.modelDetails || [],
|
|
190
|
+
modelCatalog: {
|
|
191
|
+
source: check.modelCatalogSource || 'fallback',
|
|
192
|
+
refreshedAt: check.modelCatalogRefreshedAt,
|
|
193
|
+
modelCount: check.modelDetails?.length || 0,
|
|
194
|
+
...(check.modelCatalogError ? { error: check.modelCatalogError } : {}),
|
|
195
|
+
},
|
|
172
196
|
});
|
|
173
197
|
}
|
|
174
198
|
|