@gakim-digital/dexter-bridge 0.11.22 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gakim-digital/dexter-bridge",
3
- "version": "0.11.22",
3
+ "version": "0.11.24",
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": {
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
- && typeof harnessTools.verificationRepairPrompt === 'function'
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
- rawCompletion.status === successStatus
3013
- ? 'I’m completing the final Framer verification before I finish.'
3014
- : 'Nothing is blocking this change, so I’m continuing the work.',
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(
@@ -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(successStatus = 'completed') {
592
- return {
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
  }
@@ -239,6 +239,10 @@ export const CODEX_APP_SERVER_METHODS = {
239
239
  skillsExtraRootsSet: 'skills/extraRoots/set',
240
240
  };
241
241
 
242
+ // The first thread in a freshly woken hosted container loads Codex auth and
243
+ // config over the network and has taken longer than 30s.
244
+ const THREAD_START_TIMEOUT_MS = 90_000;
245
+
242
246
  export const CODEX_APP_SERVER_NOTIFICATIONS = {
243
247
  loginCompleted: 'account/login/completed',
244
248
  itemStarted: 'item/started',
@@ -1240,7 +1244,7 @@ export function createCodexAppServerAdapter({
1240
1244
  ...(model ? { model } : {}),
1241
1245
  modelProvider: params.modelProvider,
1242
1246
  },
1243
- { timeoutMs: 30000 },
1247
+ { timeoutMs: THREAD_START_TIMEOUT_MS },
1244
1248
  );
1245
1249
  } catch {
1246
1250
  response = null;
@@ -1248,7 +1252,7 @@ export function createCodexAppServerAdapter({
1248
1252
  }
1249
1253
  if (!response) {
1250
1254
  response = await active.request(CODEX_APP_SERVER_METHODS.threadStart, params, {
1251
- timeoutMs: 30000,
1255
+ timeoutMs: THREAD_START_TIMEOUT_MS,
1252
1256
  });
1253
1257
  }
1254
1258
  const threadId = response?.thread?.id || response?.threadId || response?.id;