@gakim-digital/dexter-bridge 0.12.0 → 0.12.1
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/framerTools.js +48 -4
package/package.json
CHANGED
package/src/framerTools.js
CHANGED
|
@@ -170,6 +170,14 @@ function toolError(code, message, statusCode = 400) {
|
|
|
170
170
|
return Object.assign(new Error(message), { code, statusCode });
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
// Framer answers "Framer stopped answering…" when a program gets no reply
|
|
174
|
+
// within its own 60-second limit; the MCP SDK reports its timeout as -32001.
|
|
175
|
+
const FRAMER_TIMEOUT_PATTERN = /stopped answering|request timed out/i;
|
|
176
|
+
|
|
177
|
+
function isFramerTimeout(error) {
|
|
178
|
+
return error?.code === -32001 || FRAMER_TIMEOUT_PATTERN.test(String(error?.message || ''));
|
|
179
|
+
}
|
|
180
|
+
|
|
173
181
|
function codeOutsideLiterals(code) {
|
|
174
182
|
let visible = '';
|
|
175
183
|
for (const token of jsTokens(String(code || ''))) {
|
|
@@ -547,6 +555,10 @@ export function createFramerAgentToolRuntime({
|
|
|
547
555
|
let closed = false;
|
|
548
556
|
const abortController = new AbortController();
|
|
549
557
|
let pendingRepair = null;
|
|
558
|
+
let consecutiveTimeouts = 0;
|
|
559
|
+
let fatalError = null;
|
|
560
|
+
let signalFatal;
|
|
561
|
+
const fatalInfrastructure = new Promise((resolve) => { signalFatal = resolve; });
|
|
550
562
|
|
|
551
563
|
// Access tokens last minutes; the API refreshes them. A token is reused
|
|
552
564
|
// until a minute before it expires, and replaced at once when Framer
|
|
@@ -613,6 +625,32 @@ export function createFramerAgentToolRuntime({
|
|
|
613
625
|
}
|
|
614
626
|
}
|
|
615
627
|
|
|
628
|
+
// When Framer stops running programs, every call waits out Framer's
|
|
629
|
+
// 60-second limit. Framer sometimes stalls once and recovers, so two
|
|
630
|
+
// timeouts in a row, counting the harness's own setup read, end the run
|
|
631
|
+
// instead of letting it wait through one timeout after another.
|
|
632
|
+
function noteFramerAnswer(error) {
|
|
633
|
+
if (!isFramerTimeout(error)) {
|
|
634
|
+
consecutiveTimeouts = 0;
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
consecutiveTimeouts += 1;
|
|
638
|
+
if (consecutiveTimeouts < 2) return;
|
|
639
|
+
if (!fatalError) {
|
|
640
|
+
fatalError = Object.assign(
|
|
641
|
+
toolError(
|
|
642
|
+
'FRAMER_NOT_RESPONDING',
|
|
643
|
+
mutationCalls > 0
|
|
644
|
+
? 'Framer stopped responding, so Dexter stopped. The changes made before that were kept; try again in a minute.'
|
|
645
|
+
: 'Framer isn’t responding right now, so Dexter stopped. Nothing was changed; try again in a minute.',
|
|
646
|
+
503,
|
|
647
|
+
),
|
|
648
|
+
{ retryable: true },
|
|
649
|
+
);
|
|
650
|
+
signalFatal(fatalError);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
616
654
|
function recordCall(name, category, status, extra = {}) {
|
|
617
655
|
toolCallCount += 1;
|
|
618
656
|
toolCalls.push({ name, category, status, durationMs: extra.durationMs || 0, ...extra });
|
|
@@ -729,6 +767,7 @@ export function createFramerAgentToolRuntime({
|
|
|
729
767
|
|
|
730
768
|
async function invokeUnsafe(name, args = {}) {
|
|
731
769
|
if (closed) throw toolError('FRAMER_AGENT_RUNTIME_CLOSED', 'This Framer run has finished.');
|
|
770
|
+
if (fatalError) throw fatalError;
|
|
732
771
|
if (!definitions.some((tool) => tool.name === name)) {
|
|
733
772
|
throw toolError('FRAMER_AGENT_TOOL_UNKNOWN', `Unknown Framer tool ${name}.`);
|
|
734
773
|
}
|
|
@@ -902,6 +941,7 @@ export function createFramerAgentToolRuntime({
|
|
|
902
941
|
throw toolError('FRAMER_AGENT_TOOL_UNKNOWN', `Unknown Framer tool ${name}.`);
|
|
903
942
|
}
|
|
904
943
|
recordCall(name, category, 'succeeded', { ...callExtra, durationMs: Date.now() - startedAt });
|
|
944
|
+
noteFramerAnswer(null);
|
|
905
945
|
const nudge = category === 'read' ? firstEditNudge() : null;
|
|
906
946
|
if (nudge) payload = { ...payload, nextStep: nudge };
|
|
907
947
|
reportActivity({
|
|
@@ -926,7 +966,8 @@ export function createFramerAgentToolRuntime({
|
|
|
926
966
|
message: error?.message,
|
|
927
967
|
durationMs: Date.now() - startedAt,
|
|
928
968
|
});
|
|
929
|
-
|
|
969
|
+
noteFramerAnswer(error);
|
|
970
|
+
throw fatalError || error;
|
|
930
971
|
}
|
|
931
972
|
}
|
|
932
973
|
|
|
@@ -975,7 +1016,10 @@ export function createFramerAgentToolRuntime({
|
|
|
975
1016
|
const [pages, contextPages, activePage] = await Promise.all([
|
|
976
1017
|
readDocsTolerant(GUIDANCE_PATHS),
|
|
977
1018
|
readDocsTolerant([CONTEXT_PATH]),
|
|
978
|
-
readActivePageOutline().catch(() =>
|
|
1019
|
+
readActivePageOutline().catch((error) => {
|
|
1020
|
+
noteFramerAnswer(error);
|
|
1021
|
+
return '';
|
|
1022
|
+
}),
|
|
979
1023
|
]);
|
|
980
1024
|
const inventory = [
|
|
981
1025
|
stripGuidance(contextPages.map((page) => page.content).join('\n\n')),
|
|
@@ -1234,8 +1278,8 @@ export function createFramerAgentToolRuntime({
|
|
|
1234
1278
|
files: [...guidance.files],
|
|
1235
1279
|
},
|
|
1236
1280
|
}),
|
|
1237
|
-
fatalInfrastructureError: () =>
|
|
1238
|
-
waitForFatalInfrastructure: () =>
|
|
1281
|
+
fatalInfrastructureError: () => fatalError,
|
|
1282
|
+
waitForFatalInfrastructure: () => fatalInfrastructure,
|
|
1239
1283
|
close: async ({ waitForPending = true } = {}) => {
|
|
1240
1284
|
closed = true;
|
|
1241
1285
|
abortController.abort();
|