@gakim-digital/dexter-bridge 0.11.23 → 0.11.24
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 +13 -6
- package/src/framerAgentTools.js +40 -0
- package/src/protocol.js +15 -3
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -2738,7 +2738,9 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2738
2738
|
});
|
|
2739
2739
|
let prompt = '';
|
|
2740
2740
|
const successStatus = outcomeSuccessStatus(executionOutcome, product);
|
|
2741
|
-
const schema = outcomeOutputSchema(successStatus
|
|
2741
|
+
const schema = outcomeOutputSchema(successStatus, {
|
|
2742
|
+
reportsProjectChange: framerAgentOutcome,
|
|
2743
|
+
});
|
|
2742
2744
|
assertOutcomeResponseContract(
|
|
2743
2745
|
STRUCTURED_RESPONSE_CONTRACTS.OUTCOME,
|
|
2744
2746
|
schema,
|
|
@@ -2996,10 +2998,13 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2996
2998
|
) {
|
|
2997
2999
|
synchronization = await harnessTools.synchronize();
|
|
2998
3000
|
}
|
|
3001
|
+
const unappliedChangePrompt =
|
|
3002
|
+
rawCompletion.status === successStatus
|
|
3003
|
+
? harnessTools.unappliedChangePrompt?.(rawCompletion) || null
|
|
3004
|
+
: null;
|
|
2999
3005
|
const repairPrompt =
|
|
3000
3006
|
rawCompletion.status === successStatus
|
|
3001
|
-
|
|
3002
|
-
? harnessTools.verificationRepairPrompt()
|
|
3007
|
+
? harnessTools.verificationRepairPrompt?.() || unappliedChangePrompt
|
|
3003
3008
|
: harnessTools.unblockedFailurePrompt?.(rawCompletion) || null;
|
|
3004
3009
|
if (
|
|
3005
3010
|
repairPrompt
|
|
@@ -3009,9 +3014,11 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
3009
3014
|
kind: 'status_update',
|
|
3010
3015
|
source: 'harness',
|
|
3011
3016
|
message:
|
|
3012
|
-
|
|
3013
|
-
? 'I’m
|
|
3014
|
-
:
|
|
3017
|
+
repairPrompt === unappliedChangePrompt
|
|
3018
|
+
? 'Nothing has changed in Framer yet, so I’m making the change before I finish.'
|
|
3019
|
+
: rawCompletion.status === successStatus
|
|
3020
|
+
? 'I’m completing the final Framer verification before I finish.'
|
|
3021
|
+
: 'Nothing is blocking this change, so I’m continuing the work.',
|
|
3015
3022
|
});
|
|
3016
3023
|
const repairResult = await Promise.race([
|
|
3017
3024
|
executeProviderOutcome(
|
package/src/framerAgentTools.js
CHANGED
|
@@ -22,6 +22,8 @@ const MAX_MODEL_RESULT_BYTES = 120 * 1024;
|
|
|
22
22
|
const MAX_CODE_FILE_CONTENT_CHARS = 200_000;
|
|
23
23
|
const MAX_SVG_MARKUP_CHARS = 200_000;
|
|
24
24
|
const MAX_SVG_ATTRIBUTES = 24;
|
|
25
|
+
// Dexter reads this check name to record a claimed-but-unapplied change.
|
|
26
|
+
export const UNAPPLIED_CHANGE_CHECK = 'Harness change check';
|
|
25
27
|
const UNSAFE_SVG_PATTERN =
|
|
26
28
|
/<\s*(?:script|foreignObject|iframe)\b|\son[a-z]+\s*=|javascript:|(?:xlink:)?href\s*=\s*["']\s*(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
27
29
|
const MUTATION_TOOL_NAMES = new Set([
|
|
@@ -2135,7 +2137,31 @@ export function createFramerAgentToolRuntime({
|
|
|
2135
2137
|
};
|
|
2136
2138
|
};
|
|
2137
2139
|
|
|
2140
|
+
// The run claims a project change, yet no change was applied.
|
|
2141
|
+
const claimsUnappliedChange = (completion) =>
|
|
2142
|
+
completion?.status === 'completed'
|
|
2143
|
+
&& completion?.projectChanged === true
|
|
2144
|
+
&& mutationCalls === 0;
|
|
2145
|
+
|
|
2138
2146
|
const finalizeCompletion = (completion) => {
|
|
2147
|
+
if (claimsUnappliedChange(completion)) {
|
|
2148
|
+
return {
|
|
2149
|
+
...completion,
|
|
2150
|
+
status: 'failed',
|
|
2151
|
+
projectChanged: false,
|
|
2152
|
+
summary:
|
|
2153
|
+
'I didn’t change anything in Framer this time, even though I was about to say I had. Please try again, or select the element you want changed.',
|
|
2154
|
+
checks: [
|
|
2155
|
+
...(Array.isArray(completion?.checks) ? completion.checks : []),
|
|
2156
|
+
{
|
|
2157
|
+
command: UNAPPLIED_CHANGE_CHECK,
|
|
2158
|
+
status: 'failed',
|
|
2159
|
+
output: 'The summary reported a project change, but this run applied none.',
|
|
2160
|
+
},
|
|
2161
|
+
],
|
|
2162
|
+
blockedReason: null,
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2139
2165
|
if (completion?.status !== 'completed' || mutationCalls === 0) {
|
|
2140
2166
|
return completion;
|
|
2141
2167
|
}
|
|
@@ -2175,6 +2201,19 @@ export function createFramerAgentToolRuntime({
|
|
|
2175
2201
|
};
|
|
2176
2202
|
};
|
|
2177
2203
|
|
|
2204
|
+
// A summary must never describe a change the project did not receive. A
|
|
2205
|
+
// run that claims one gets a single prompt to make the change or say what
|
|
2206
|
+
// it could not find.
|
|
2207
|
+
const unappliedChangePrompt = (completion) => {
|
|
2208
|
+
if (!claimsUnappliedChange(completion)) return null;
|
|
2209
|
+
return [
|
|
2210
|
+
'Your summary says you changed the Framer project, but this run applied no change: nothing in the project is different yet.',
|
|
2211
|
+
'Continue the same task: find every target the user meant, apply the change, verify it, then return the required JSON outcome.',
|
|
2212
|
+
'Text on buttons, cards, and other components often lives in a component instance control, such as a label, rather than in a text layer; read the instances and their controls to find it.',
|
|
2213
|
+
'If you cannot find or change a target, name it in the summary and set projectChanged to false. Never describe a change that was not applied.',
|
|
2214
|
+
].join(' ');
|
|
2215
|
+
};
|
|
2216
|
+
|
|
2178
2217
|
// A model that reports failure without changing anything and without a
|
|
2179
2218
|
// refused plan or edit gave up on its own. It gets one prompt to continue.
|
|
2180
2219
|
const unblockedFailurePrompt = (completion) => {
|
|
@@ -3186,6 +3225,7 @@ export function createFramerAgentToolRuntime({
|
|
|
3186
3225
|
inspect: inspectFinalState,
|
|
3187
3226
|
finalizeCompletion,
|
|
3188
3227
|
verificationRepairPrompt,
|
|
3228
|
+
unappliedChangePrompt,
|
|
3189
3229
|
unblockedFailurePrompt,
|
|
3190
3230
|
sourceHash: () => null,
|
|
3191
3231
|
hasPendingChanges: () => false,
|
package/src/protocol.js
CHANGED
|
@@ -497,8 +497,9 @@ export function buildFramerOutcomePrompt(outcome = {}, product, promptContext =
|
|
|
497
497
|
'Dexter will not perform a second design verification after you finish. Structural verification must be described honestly and must never be called runtime-tested unless an interaction was actually exercised.',
|
|
498
498
|
'If Framer authorization or a required user decision is missing, return blocked with a concise explanation. Otherwise return completed only after you have finished and verified the work.',
|
|
499
499
|
'A greeting, thanks, or question that needs no project change is not blocked: answer it in summary without reading or editing the project and return completed.',
|
|
500
|
+
'Set projectChanged to true only when this run applied a change to the Framer project. The summary must describe only changes that were applied; if you could not find or change something, say so plainly instead.',
|
|
500
501
|
'When finished, return exactly one JSON object and no markdown.',
|
|
501
|
-
'{"status":"completed|blocked|failed","summary":"one or two short, non-technical sentences for the designer","checks":[{"command":"what you verified","status":"passed|failed|skipped","output":"short result"}],"blockedReason":null}',
|
|
502
|
+
'{"status":"completed|blocked|failed","summary":"one or two short, non-technical sentences for the designer","checks":[{"command":"what you verified","status":"passed|failed|skipped","output":"short result"}],"blockedReason":null,"projectChanged":false}',
|
|
502
503
|
doctrine
|
|
503
504
|
? `\nDexter design and execution doctrine (${String(promptContext.doctrineVersion || 'current')}):\n${doctrine}`
|
|
504
505
|
: '',
|
|
@@ -588,8 +589,11 @@ export function buildOutcomePrompt(outcome = {}, product, promptContext = {}) {
|
|
|
588
589
|
].join('\n');
|
|
589
590
|
}
|
|
590
591
|
|
|
591
|
-
export function outcomeOutputSchema(
|
|
592
|
-
|
|
592
|
+
export function outcomeOutputSchema(
|
|
593
|
+
successStatus = 'completed',
|
|
594
|
+
{ reportsProjectChange = false } = {},
|
|
595
|
+
) {
|
|
596
|
+
const schema = {
|
|
593
597
|
type: 'object',
|
|
594
598
|
properties: {
|
|
595
599
|
status: {
|
|
@@ -621,6 +625,13 @@ export function outcomeOutputSchema(successStatus = 'completed') {
|
|
|
621
625
|
required: ['status', 'summary', 'checks', 'blockedReason'],
|
|
622
626
|
additionalProperties: false,
|
|
623
627
|
};
|
|
628
|
+
// Framer runs say whether their summary reports a project change, so the
|
|
629
|
+
// harness can refuse a summary that claims a change nothing applied.
|
|
630
|
+
if (reportsProjectChange) {
|
|
631
|
+
schema.properties.projectChanged = { type: 'boolean' };
|
|
632
|
+
schema.required.push('projectChanged');
|
|
633
|
+
}
|
|
634
|
+
return schema;
|
|
624
635
|
}
|
|
625
636
|
|
|
626
637
|
export function normalizeOutcomeCompletion(
|
|
@@ -673,5 +684,6 @@ export function normalizeOutcomeCompletion(
|
|
|
673
684
|
: null,
|
|
674
685
|
synchronized: false,
|
|
675
686
|
sourceHash: null,
|
|
687
|
+
...(typeof raw.projectChanged === 'boolean' ? { projectChanged: raw.projectChanged } : {}),
|
|
676
688
|
};
|
|
677
689
|
}
|