shotops-mcp 0.9.5 → 0.9.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/dist/local.js CHANGED
@@ -24,7 +24,7 @@
24
24
  }
25
25
  await visit('')
26
26
  return hash.digest('hex')
27
- })(sourceDirectory) !== "05d27ff2f672ea1fb99121eb514c734ec24b952b84ee842baa15d9cf8a0e9e89") {
27
+ })(sourceDirectory) !== "5df467e22a054acf0779df03013843f6f55d0a3e8cb9f87ecca7dca8eef02f46") {
28
28
  console.error('shotops-mcp: dist/local.js is stale; run npm run build in shotops-mcp.');
29
29
  }
30
30
  } catch { /* An unreadable checkout must not break an otherwise working CLI. */ }
@@ -1892,6 +1892,12 @@ var init_fonts = __esm({
1892
1892
  });
1893
1893
 
1894
1894
  // ../mockup-engine/appstore/refineIntent.ts
1895
+ function requestsBroaderDeviceScope(instruction) {
1896
+ return BROADER_DEVICE_SCOPE.test(instruction) || NUMBERED_SIBLING_SCOPE.test(instruction);
1897
+ }
1898
+ function canRepairLayoutForFocus(focus, instruction) {
1899
+ return focus?.kind !== "device" || requestsBroaderDeviceScope(instruction);
1900
+ }
1895
1901
  function structuralOperationTargets(operation) {
1896
1902
  switch (operation.op) {
1897
1903
  case "frame.add":
@@ -1931,7 +1937,7 @@ function resolveAiNumber(input, current, range) {
1931
1937
  if (raw === void 0 || !Number.isFinite(raw)) return void 0;
1932
1938
  return Math.min(Math.max(raw, range.min), range.max);
1933
1939
  }
1934
- var MAX_REFINE_PROMPT_LENGTH, AI_COLORWAY_IDS, AI_CLAY_TONES, AI_SHOT_RANGES, AI_SCALE_RANGE, AI_CAPTION_FONT_IDS, AI_CAPTION_RANGES, AI_GRADIENT_STOPS;
1940
+ var MAX_REFINE_PROMPT_LENGTH, AI_COLORWAY_IDS, AI_CLAY_TONES, AI_SHOT_RANGES, AI_SCALE_RANGE, AI_CAPTION_FONT_IDS, AI_CAPTION_RANGES, AI_GRADIENT_STOPS, BROADER_DEVICE_SCOPE, NUMBERED_SIBLING_SCOPE;
1935
1941
  var init_refineIntent = __esm({
1936
1942
  "../mockup-engine/appstore/refineIntent.ts"() {
1937
1943
  "use strict";
@@ -1949,6 +1955,8 @@ var init_refineIntent = __esm({
1949
1955
  AI_CAPTION_FONT_IDS = CAPTION_FONT_IDS;
1950
1956
  AI_CAPTION_RANGES = CAPTION_STYLE_BOUNDS;
1951
1957
  AI_GRADIENT_STOPS = { min: 1, max: 6 };
1958
+ BROADER_DEVICE_SCOPE = /\b(?:all|every|each|both|entire|whole|across|throughout|other|others|rest|remaining|project|strip|frames|screenshots|screens|devices|phones|captions)\b/i;
1959
+ NUMBERED_SIBLING_SCOPE = /\b(?:frame|device|phone)\s+\d+\b/i;
1952
1960
  }
1953
1961
  });
1954
1962
 
@@ -3259,7 +3267,7 @@ function presetCaptionStyle(preset) {
3259
3267
  return { ...preset.caption };
3260
3268
  }
3261
3269
  function presetShotLook(preset) {
3262
- return { ...preset.shotLook };
3270
+ return { ...preset.shotLook, ...preset.shotLook.angle !== void 0 ? { cameraPos: null } : {} };
3263
3271
  }
3264
3272
  function expandLookChoice(designPreset, layout) {
3265
3273
  const preset = designPreset === void 0 ? void 0 : designPresetById(designPreset);
@@ -7117,8 +7125,8 @@ var init_agentCapabilities = __esm({
7117
7125
  studioCommands: ["addPanel"],
7118
7126
  wire: "structuralOperations: frame.add",
7119
7127
  targets: ["project"],
7120
- requiredInputs: ["afterFrameId (optional)"],
7121
- limits: ["A frame created this cycle can only be addressed in the next one, after Studio reports its id."],
7128
+ requiredInputs: ["afterFrameId (optional)", "frameRef (optional, patch-local alias)"],
7129
+ limits: ["Declare frameRef to address the new frame in later ordered operations of the same patch. Without frameRef, wait until the next cycle reports its id."],
7122
7130
  evaluationCases: ["structure-add-closing-frame"]
7123
7131
  },
7124
7132
  {
@@ -7566,8 +7574,8 @@ function validateAgentOutcome(outcome) {
7566
7574
  if (outcome.status === "applied_with_remaining" && !outcome.remaining) {
7567
7575
  problems.push("applied_with_remaining must name what is still outstanding.");
7568
7576
  }
7569
- if (outcome.question !== void 0 && outcome.status !== "needs_clarification") {
7570
- problems.push(`Only needs_clarification carries a question, not ${outcome.status}.`);
7577
+ if (outcome.question !== void 0 && outcome.status !== "needs_clarification" && outcome.status !== "needs_asset") {
7578
+ problems.push(`Only needs_clarification and needs_asset carry a question, not ${outcome.status}.`);
7571
7579
  }
7572
7580
  if (outcome.status === "needs_clarification" && !outcome.question) {
7573
7581
  problems.push("needs_clarification must ask exactly one focused question.");
@@ -7575,6 +7583,16 @@ function validateAgentOutcome(outcome) {
7575
7583
  if (outcome.capabilityId !== void 0 && !capabilityById(outcome.capabilityId)) {
7576
7584
  problems.push(`Outcome names capability ${JSON.stringify(outcome.capabilityId)}, which the catalog does not list.`);
7577
7585
  }
7586
+ if (outcome.capabilityIds !== void 0) {
7587
+ if (!Array.isArray(outcome.capabilityIds) || outcome.capabilityIds.length > MAX_OUTCOME_CAPABILITY_IDS) {
7588
+ problems.push(`capabilityIds must be a list of at most ${MAX_OUTCOME_CAPABILITY_IDS} catalog ids.`);
7589
+ } else {
7590
+ for (const id of outcome.capabilityIds) {
7591
+ if (typeof id !== "string" || !capabilityById(id)) problems.push(`Outcome lists capability ${JSON.stringify(id)}, which the catalog does not list.`);
7592
+ }
7593
+ if (new Set(outcome.capabilityIds).size !== outcome.capabilityIds.length) problems.push("capabilityIds repeats an id.");
7594
+ }
7595
+ }
7578
7596
  problems.push(...boundProblems("headline", outcome.headline, AGENT_OUTCOME_BOUNDS.headline, true));
7579
7597
  problems.push(...boundProblems("detail", outcome.detail, AGENT_OUTCOME_BOUNDS.detail, false));
7580
7598
  problems.push(...boundProblems("remaining", outcome.remaining, AGENT_OUTCOME_BOUNDS.remaining, false));
@@ -7591,6 +7609,77 @@ function boundProblems(field, value, maximum, required) {
7591
7609
  if (value.length > maximum) return [`${field} is ${value.length} characters, over its ${maximum} bound.`];
7592
7610
  return [];
7593
7611
  }
7612
+ function capabilitiesForPatch(patch) {
7613
+ const ids = [];
7614
+ if (!patch) return ids;
7615
+ const add = (id) => {
7616
+ if (ids.indexOf(id) < 0) ids.push(id);
7617
+ };
7618
+ if (patch.captions) add("caption.text");
7619
+ if (patch.screenOrder) add("frame.reorder");
7620
+ if (patch.designPreset) add("look.design-preset");
7621
+ if (patch.layout) add("look.layout-template");
7622
+ if (patch.outputs) add("output.membership");
7623
+ if (patch.locales) add("locale.membership");
7624
+ if (patch.shots?.some((shot) => Object.keys(shot).length > 0)) add("device.look");
7625
+ if (patch.devices?.some((device) => Object.keys(device.patch).length > 0)) add("device.look");
7626
+ if (patch.captionStyle && Object.keys(patch.captionStyle).length > 0) add("caption.style");
7627
+ if (patch.background && Object.keys(patch.background).length > 0) add("strip.background");
7628
+ patch.frames?.forEach((frame) => {
7629
+ if (frame.captionStyle && Object.keys(frame.captionStyle).length > 0) add("caption.style");
7630
+ if (frame.background && Object.keys(frame.background).length > 0) add("frame.background");
7631
+ });
7632
+ patch.captionOperations?.forEach((operation) => {
7633
+ if (operation.operation === "add") add("caption.add");
7634
+ else if (operation.operation === "remove") add("caption.remove");
7635
+ else add(operation.frameId === operation.toFrameId ? "caption.reorder" : "caption.move-to-frame");
7636
+ });
7637
+ patch.frameOperations?.forEach((operation) => {
7638
+ if (operation.operation === "add") add("frame.add");
7639
+ else if (operation.operation === "remove") add("frame.remove");
7640
+ else if (operation.operation === "move") add("frame.reorder");
7641
+ else add("frame.split");
7642
+ });
7643
+ patch.structuralOperations?.forEach((operation) => add(capabilityForStructuralOperation(operation)));
7644
+ return ids;
7645
+ }
7646
+ function capabilityForStructuralOperation(operation) {
7647
+ switch (operation.op) {
7648
+ case "frame.add":
7649
+ return "frame.add";
7650
+ case "frame.remove":
7651
+ return "frame.remove";
7652
+ case "frame.move":
7653
+ return "frame.reorder";
7654
+ case "frame.split":
7655
+ return "frame.split";
7656
+ case "frame.combine":
7657
+ return "frame.combine";
7658
+ case "frame.template-reset":
7659
+ return "look.layout-template";
7660
+ case "caption.add":
7661
+ return "caption.add";
7662
+ case "caption.remove":
7663
+ return "caption.remove";
7664
+ // One `caption.move` covers both Studio controls: staying inside a frame is a reorder, crossing
7665
+ // to another is a move. The catalog names them separately because the user experiences them
7666
+ // separately.
7667
+ case "caption.move":
7668
+ return operation.frameId === operation.toFrameId ? "caption.reorder" : "caption.move-to-frame";
7669
+ case "device.move":
7670
+ return "device.move-to-frame";
7671
+ case "device.reorder":
7672
+ return "device.reorder";
7673
+ case "device.remove":
7674
+ return "device.remove";
7675
+ case "device.add":
7676
+ return "device.add";
7677
+ // The catalog names this one for what the user experiences — the pixels change, the device does
7678
+ // not — so the id deliberately does not match the wire's `device.replace`.
7679
+ case "device.replace":
7680
+ return "device.replace-pixels";
7681
+ }
7682
+ }
7594
7683
  function applicationOutcome(facts) {
7595
7684
  if (facts.conflicted) {
7596
7685
  return {
@@ -7654,7 +7743,14 @@ function cancelledOutcome() {
7654
7743
  changed: []
7655
7744
  };
7656
7745
  }
7657
- var AGENT_OUTCOME_STATUSES, AGENT_MUTATING_STATUSES, AGENT_OUTCOME_REASONS, AGENT_OUTCOME_BOUNDS;
7746
+ function withTouchedCapabilities(outcome, touched) {
7747
+ const ids = [];
7748
+ for (const id of touched) if (capabilityById(id) && ids.indexOf(id) < 0) ids.push(id);
7749
+ if (outcome.capabilityId && capabilityById(outcome.capabilityId) && ids.indexOf(outcome.capabilityId) < 0) ids.push(outcome.capabilityId);
7750
+ if (ids.length === 0) return outcome;
7751
+ return { ...outcome, capabilityIds: ids.slice(0, MAX_OUTCOME_CAPABILITY_IDS) };
7752
+ }
7753
+ var AGENT_OUTCOME_STATUSES, AGENT_MUTATING_STATUSES, AGENT_OUTCOME_REASONS, AGENT_OUTCOME_BOUNDS, MAX_OUTCOME_CAPABILITY_IDS;
7658
7754
  var init_agentOutcome = __esm({
7659
7755
  "../mockup-engine/appstore/agentOutcome.ts"() {
7660
7756
  "use strict";
@@ -7693,9 +7789,39 @@ var init_agentOutcome = __esm({
7693
7789
  remaining: 300,
7694
7790
  /** The label on the single next step. */
7695
7791
  nextStep: 120,
7696
- /** The focused question on `needs_clarification`. */
7697
- question: 240
7792
+ /** Transport ceiling for a complete assistant question or screenshot request; never clip it. */
7793
+ question: 32e3
7698
7794
  };
7795
+ MAX_OUTCOME_CAPABILITY_IDS = 16;
7796
+ }
7797
+ });
7798
+
7799
+ // ../mockup-engine/appstore/agentUnmetActions.ts
7800
+ function normalizeIds(ids) {
7801
+ return Array.from(new Set(ids)).sort().slice(0, MAX_UNMET_ACTIONS_PER_TURN);
7802
+ }
7803
+ function classifiedDemand(ids) {
7804
+ return { classification: "classified", actionIds: normalizeIds(ids) };
7805
+ }
7806
+ var AGENT_UNMET_ACTION_UNKNOWN, MAX_UNMET_ACTIONS_PER_TURN, AGENT_UNMET_ACTIONS, CAPABILITY_IDS, AGENT_UNMET_ACTION_VOCABULARY, NOT_MEASURED_DEMAND;
7807
+ var init_agentUnmetActions = __esm({
7808
+ "../mockup-engine/appstore/agentUnmetActions.ts"() {
7809
+ "use strict";
7810
+ init_agentCapabilities();
7811
+ AGENT_UNMET_ACTION_UNKNOWN = "unknown";
7812
+ MAX_UNMET_ACTIONS_PER_TURN = 8;
7813
+ AGENT_UNMET_ACTIONS = [
7814
+ { id: "image.generate", summary: "Generate or paint new imagery rather than arranging supplied screenshots." },
7815
+ { id: "animation.create", summary: "Animate a screen, produce a video, or export motion instead of stills." },
7816
+ { id: "device.unsupported-model", summary: "Use a device model ShotOps does not offer." }
7817
+ ];
7818
+ CAPABILITY_IDS = AGENT_CAPABILITIES.map((capability) => capability.id);
7819
+ AGENT_UNMET_ACTION_VOCABULARY = [
7820
+ ...CAPABILITY_IDS,
7821
+ ...AGENT_UNMET_ACTIONS.map((action) => action.id),
7822
+ AGENT_UNMET_ACTION_UNKNOWN
7823
+ ];
7824
+ NOT_MEASURED_DEMAND = { classification: "not_measured", actionIds: [] };
7699
7825
  }
7700
7826
  });
7701
7827
 
@@ -8164,7 +8290,7 @@ function applyFrameOperation(state, operation) {
8164
8290
  if (operation.operation === "add") {
8165
8291
  if (operation.afterFrameId !== void 0 && panelIndex(state, operation.afterFrameId) < 0) return null;
8166
8292
  const frameId = nextAgentPanelId(state);
8167
- const added = addPanel(state, frameId);
8293
+ const added = inheritFrameTemplate(addPanel(state, frameId), frameId, operation.afterFrameId ?? state.panels[state.panels.length - 1]?.id);
8168
8294
  return operation.afterFrameId === void 0 ? added : movePanelAfter(added, frameId, operation.afterFrameId);
8169
8295
  }
8170
8296
  if (operation.operation === "remove") {
@@ -8187,7 +8313,17 @@ function applyFrameOperation(state, operation) {
8187
8313
  if (!moved) return null;
8188
8314
  next = moved;
8189
8315
  }
8190
- return next;
8316
+ return inheritFrameTemplate(next, newFrameId, operation.frameId);
8317
+ }
8318
+ function inheritFrameTemplate(state, panelId, fromPanelId) {
8319
+ const source = fromPanelId === void 0 ? void 0 : state.frameTemplates?.[fromPanelId];
8320
+ if (!source) return state;
8321
+ const baseline = captureFrameTemplateBaseline(
8322
+ { shots: state.shots, captionText: state.captionText, captionSlotIds: captionSlotIds(state), baseLocale: state.baseLocale },
8323
+ panelId,
8324
+ source.templateId
8325
+ );
8326
+ return { ...state, frameTemplates: { ...state.frameTemplates ?? {}, [panelId]: baseline } };
8191
8327
  }
8192
8328
  function addCaptionLayer(state, panelId, text3, style2) {
8193
8329
  const source = state.captionText[state.baseLocale] ?? {};
@@ -8496,16 +8632,39 @@ function applyStructuralOperation(state, operation, assets = [], bindings) {
8496
8632
  function applyStructuralOperations(state, patch, assets = []) {
8497
8633
  let next = state;
8498
8634
  const assetBindings = [];
8499
- for (const operation of normalizeStructuralOperations(patch)) {
8635
+ const frameRefs = /* @__PURE__ */ new Map();
8636
+ const retiredRefs = /* @__PURE__ */ new Set();
8637
+ for (const raw of normalizeStructuralOperations(patch)) {
8638
+ const ref = raw.op === "frame.add" ? raw.frameRef : void 0;
8639
+ if (ref !== void 0 && (!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(ref) || frameRefs.has(ref) || state.panels.some((panel) => panel.id === ref) || state.shots.some((shot) => shot.id === ref))) return null;
8640
+ if (structuralOperationTargets(raw).frameIds.some((id) => retiredRefs.has(id))) return null;
8641
+ const resolve14 = (id) => frameRefs.get(id) ?? id;
8642
+ const operation = {
8643
+ ...raw,
8644
+ ..."frameId" in raw ? { frameId: resolve14(raw.frameId) } : {},
8645
+ ..."toFrameId" in raw ? { toFrameId: resolve14(raw.toFrameId) } : {},
8646
+ ..."fromFrameId" in raw ? { fromFrameId: resolve14(raw.fromFrameId) } : {},
8647
+ ..."afterFrameId" in raw && raw.afterFrameId !== void 0 ? { afterFrameId: resolve14(raw.afterFrameId) } : {}
8648
+ };
8500
8649
  const applied = applyStructuralOperation(next, operation, assets, assetBindings);
8501
8650
  if (!applied) return null;
8651
+ if (ref !== void 0) {
8652
+ const created = applied.panels.find((panel) => !next.panels.some((previous) => previous.id === panel.id));
8653
+ if (!created) return null;
8654
+ frameRefs.set(ref, created.id);
8655
+ }
8656
+ frameRefs.forEach((id, alias) => {
8657
+ if (!applied.panels.some((panel) => panel.id === id)) retiredRefs.add(alias);
8658
+ });
8502
8659
  next = applied;
8503
8660
  }
8504
- return { state: next, assetBindings };
8661
+ const survivingRefs = Array.from(frameRefs.entries()).filter(([alias]) => !retiredRefs.has(alias));
8662
+ return { state: next, assetBindings, ...survivingRefs.length > 0 ? { frameRefs: Object.fromEntries(survivingRefs) } : {} };
8505
8663
  }
8506
8664
  var init_refinementStructure = __esm({
8507
8665
  "../mockup-engine/appstore/refinementStructure.ts"() {
8508
8666
  "use strict";
8667
+ init_refineIntent();
8509
8668
  init_defaults();
8510
8669
  init_recordTransforms();
8511
8670
  init_shot();
@@ -8877,7 +9036,7 @@ function applyProjectRefinementWithAssets(state, patch, panelIds, assets = []) {
8877
9036
  next = { ...next, panels, shots: shotsInPanelOrder(next.shots, panels) };
8878
9037
  }
8879
9038
  }
8880
- return { state: next, assetBindings: structured.assetBindings };
9039
+ return { state: next, assetBindings: structured.assetBindings, ...structured.frameRefs ? { frameRefs: structured.frameRefs } : {} };
8881
9040
  }
8882
9041
  var init_refinement = __esm({
8883
9042
  "../mockup-engine/appstore/refinement.ts"() {
@@ -8964,6 +9123,67 @@ var init_appStoreLocales = __esm({
8964
9123
  }
8965
9124
  });
8966
9125
 
9126
+ // ../mockup-engine/appstore/agentTurnRepetition.ts
9127
+ function creationOf(operation) {
9128
+ if (!CREATING_OPS.has(operation.op)) return null;
9129
+ const target = "frameId" in operation ? operation.frameId : "toFrameId" in operation ? operation.toFrameId : "afterFrameId" in operation && operation.afterFrameId ? `after:${operation.afterFrameId}` : "strip";
9130
+ return { op: operation.op, target };
9131
+ }
9132
+ function contextualCreations(operations, resolved = {}) {
9133
+ const refs = /* @__PURE__ */ new Map();
9134
+ return operations.map((operation) => {
9135
+ const creation = creationOf(operation);
9136
+ const target = creation?.target;
9137
+ const normalized = creation && target !== void 0 ? {
9138
+ ...creation,
9139
+ target: target.startsWith("after:") ? `after:${refs.get(target.slice(6)) ?? target.slice(6)}` : refs.get(target) ?? target
9140
+ } : null;
9141
+ if (operation.op === "frame.add" && operation.frameRef && normalized) {
9142
+ refs.set(operation.frameRef, Object.prototype.hasOwnProperty.call(resolved, operation.frameRef) ? resolved[operation.frameRef] : `created-frame:${normalized.target}`);
9143
+ }
9144
+ return normalized;
9145
+ });
9146
+ }
9147
+ function creationsIn(patch, frameRefs) {
9148
+ const operations = patch.structuralOperations ?? [];
9149
+ const symbolic = contextualCreations(operations).flatMap((creation) => creation ? [creation] : []);
9150
+ const durable = frameRefs ? contextualCreations(operations, frameRefs).flatMap((creation) => creation ? [creation] : []) : [];
9151
+ return symbolic.concat(durable.filter((creation) => !symbolic.some((entry) => entry.op === creation.op && entry.target === creation.target)));
9152
+ }
9153
+ function dropRepeatedCreations(patch, already) {
9154
+ const operations = patch.structuralOperations;
9155
+ if (!operations || operations.length === 0 || already.length === 0) return { patch, dropped: [] };
9156
+ const seen = new Set(already.map((creation) => `${creation.op} ${creation.target}`));
9157
+ const dropped = [];
9158
+ const creations = contextualCreations(operations);
9159
+ const unavailableRefs = /* @__PURE__ */ new Set();
9160
+ const kept = operations.filter((operation, index) => {
9161
+ const creation = creations[index];
9162
+ const dependent = structuralOperationTargets(operation).frameIds.some((id) => unavailableRefs.has(id));
9163
+ if (!dependent && (!creation || !seen.has(`${creation.op} ${creation.target}`))) return true;
9164
+ if (operation.op === "frame.add" && operation.frameRef) unavailableRefs.add(operation.frameRef);
9165
+ if (creation) dropped.push(creation);
9166
+ return false;
9167
+ });
9168
+ if (dropped.length === 0) return { patch, dropped };
9169
+ const { structuralOperations: _dropped, ...rest } = patch;
9170
+ void _dropped;
9171
+ return { patch: kept.length > 0 ? { ...rest, structuralOperations: kept } : rest, dropped };
9172
+ }
9173
+ var CREATING_OPS;
9174
+ var init_agentTurnRepetition = __esm({
9175
+ "../mockup-engine/appstore/agentTurnRepetition.ts"() {
9176
+ "use strict";
9177
+ init_refineIntent();
9178
+ CREATING_OPS = /* @__PURE__ */ new Set([
9179
+ "frame.add",
9180
+ "frame.split",
9181
+ "caption.add",
9182
+ "device.add"
9183
+ ]);
9184
+ }
9185
+ });
9186
+
8967
9187
  // ../mockup-engine/appstore/agentTurnCoordinator.ts
8968
9188
  function phaseFor(cycle) {
8969
9189
  return cycle === 1 ? "applying" : cycle === 2 ? "inspecting" : "adjusting";
@@ -9008,6 +9228,7 @@ async function runAgentTurn(input, ports) {
9008
9228
  if (!turnId) throw new Error("Agent turn id must not be empty");
9009
9229
  const emit = (event) => ports.emit({ ...event, turnId });
9010
9230
  const now = input.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
9231
+ const layoutRepairable = canRepairLayoutForFocus(input.focus, instruction);
9011
9232
  const loaded = await ports.loadProject();
9012
9233
  const before = loaded.state;
9013
9234
  let working = before;
@@ -9018,9 +9239,13 @@ async function runAgentTurn(input, ports) {
9018
9239
  let best = null;
9019
9240
  let selected = null;
9020
9241
  let workingAssetBindings = [];
9242
+ const unmetActions = /* @__PURE__ */ new Set();
9243
+ let demandClassified = false;
9021
9244
  let interpretation = "";
9022
9245
  let inspection = "";
9023
9246
  let remainingMismatch = null;
9247
+ const appliedCreations = [];
9248
+ const touchedCapabilities = /* @__PURE__ */ new Set();
9024
9249
  let terminalOutcome = null;
9025
9250
  let invalid2 = false;
9026
9251
  let cancelled = false;
@@ -9049,7 +9274,14 @@ async function runAgentTurn(input, ports) {
9049
9274
  focus: input.focus,
9050
9275
  state: working,
9051
9276
  rendered,
9052
- ...cycle > 1 ? { previous: { actualDiff: selected?.changed ?? [], inspection, cycle: cycle - 1 } } : {}
9277
+ ...cycle > 1 ? {
9278
+ previous: {
9279
+ actualDiff: selected?.changed ?? [],
9280
+ inspection,
9281
+ cycle: cycle - 1,
9282
+ appliedOperations: [...appliedCreations]
9283
+ }
9284
+ } : {}
9053
9285
  });
9054
9286
  } catch {
9055
9287
  terminalOutcome = failureOutcome(
@@ -9058,6 +9290,10 @@ async function runAgentTurn(input, ports) {
9058
9290
  );
9059
9291
  break;
9060
9292
  }
9293
+ if (authorization.demand?.classification === "classified") {
9294
+ demandClassified = true;
9295
+ for (const actionId of authorization.demand.actionIds) unmetActions.add(actionId);
9296
+ }
9061
9297
  interpretation ||= authorization.interpretation?.trim() || instruction;
9062
9298
  inspection = authorization.inspection?.trim() || "Inspected the current composed result.";
9063
9299
  const pass = {
@@ -9085,7 +9321,12 @@ async function runAgentTurn(input, ports) {
9085
9321
  }
9086
9322
  remainingMismatch = authorization.remainingMismatch?.trim() || authorization.followUp?.trim() || null;
9087
9323
  for (const change of authorization.broaderChanges ?? []) broaderChanges.add(change);
9324
+ if (authorization.patch && cycle > 1) {
9325
+ const guarded = dropRepeatedCreations(authorization.patch, appliedCreations);
9326
+ if (guarded.dropped.length > 0) authorization = { ...authorization, patch: guarded.patch };
9327
+ }
9088
9328
  if (authorization.patch) {
9329
+ for (const id of capabilitiesForPatch(authorization.patch)) touchedCapabilities.add(id);
9089
9330
  const application = applyProjectRefinementWithAssets(
9090
9331
  working,
9091
9332
  authorization.patch,
@@ -9097,6 +9338,7 @@ async function runAgentTurn(input, ports) {
9097
9338
  break;
9098
9339
  }
9099
9340
  working = application.state;
9341
+ appliedCreations.push(...creationsIn(authorization.patch, application.frameRefs));
9100
9342
  workingAssetBindings = [...workingAssetBindings, ...application.assetBindings];
9101
9343
  checkpointId += 1;
9102
9344
  const checkpoint = {
@@ -9123,12 +9365,14 @@ async function runAgentTurn(input, ports) {
9123
9365
  });
9124
9366
  }
9125
9367
  const requiresRenderedInspection = cycle === 1 && authorization.patch != null;
9126
- if (authorization.complete === true && !requiresRenderedInspection) {
9368
+ const unresolvedLayout = layoutRepairable && authorization.complete === true && !requiresRenderedInspection && authorization.patch == null ? ports.unresolvedLayout?.(rendered)?.trim() || null : null;
9369
+ if (authorization.complete === true && !requiresRenderedInspection && !(unresolvedLayout && cycle < AGENT_TURN_PASS_CAP)) {
9127
9370
  if (selected?.changed.length) best = selected;
9128
- remainingMismatch = null;
9371
+ remainingMismatch = unresolvedLayout;
9129
9372
  break;
9130
9373
  }
9131
- if (authorization.followUp && !authorization.patch) break;
9374
+ if (unresolvedLayout) remainingMismatch = unresolvedLayout;
9375
+ else if (authorization.followUp && !authorization.patch) break;
9132
9376
  if (cycle < AGENT_TURN_PASS_CAP) {
9133
9377
  try {
9134
9378
  rendered = await ports.renderComposed(working, cycle);
@@ -9145,11 +9389,14 @@ async function runAgentTurn(input, ports) {
9145
9389
  selected = best;
9146
9390
  const selectedState = selected?.state ?? before;
9147
9391
  await emit({ type: "draft_selected", checkpointId: selected?.id ?? 0, state: selectedState });
9148
- let outcome = terminalOutcome ?? applicationOutcome({
9149
- changed: selected?.changed ?? [],
9150
- invalid: invalid2,
9151
- gaps: selected?.gaps ?? []
9152
- });
9392
+ let outcome = withTouchedCapabilities(
9393
+ terminalOutcome ?? applicationOutcome({
9394
+ changed: selected?.changed ?? [],
9395
+ invalid: invalid2,
9396
+ gaps: selected?.gaps ?? []
9397
+ }),
9398
+ Array.from(touchedCapabilities)
9399
+ );
9153
9400
  let committed = false;
9154
9401
  let commitData;
9155
9402
  if (outcome.status === "applied" || outcome.status === "applied_with_remaining") {
@@ -9166,7 +9413,10 @@ async function runAgentTurn(input, ports) {
9166
9413
  commitData = commit.data;
9167
9414
  await emit({ type: "commit_succeeded", data: commit.data, outcome });
9168
9415
  } else {
9169
- outcome = commit.reason === "stale_version" ? applicationOutcome({ changed: [], conflicted: true }) : failureOutcome("service_unavailable", "The Agent could not save this change. Nothing changed.");
9416
+ outcome = withTouchedCapabilities(
9417
+ commit.reason === "stale_version" ? applicationOutcome({ changed: [], conflicted: true }) : failureOutcome("service_unavailable", "The Agent could not save this change. Nothing changed."),
9418
+ Array.from(touchedCapabilities)
9419
+ );
9170
9420
  await emit({ type: "draft_rolled_back", outcome });
9171
9421
  }
9172
9422
  } else {
@@ -9200,7 +9450,8 @@ async function runAgentTurn(input, ports) {
9200
9450
  state: committed ? selectedState : before,
9201
9451
  expectedVersion: loaded.expectedVersion,
9202
9452
  committed,
9203
- ...commitData === void 0 ? {} : { commitData }
9453
+ ...commitData === void 0 ? {} : { commitData },
9454
+ demand: demandClassified ? classifiedDemand(unmetActions) : NOT_MEASURED_DEMAND
9204
9455
  };
9205
9456
  assertOutcome(outcome);
9206
9457
  await emit({ type: "turn_completed", result });
@@ -9212,10 +9463,13 @@ var init_agentTurnCoordinator = __esm({
9212
9463
  "../mockup-engine/appstore/agentTurnCoordinator.ts"() {
9213
9464
  "use strict";
9214
9465
  init_agentOutcome();
9466
+ init_refineIntent();
9215
9467
  init_refinement();
9468
+ init_agentUnmetActions();
9216
9469
  init_recordTransforms();
9217
9470
  init_appStoreLocales();
9218
9471
  init_agentAssetCustody();
9472
+ init_agentTurnRepetition();
9219
9473
  AGENT_TURN_PASS_CAP = 3;
9220
9474
  EMPTY_USAGE = {
9221
9475
  inputTokens: 0,
@@ -9263,7 +9517,9 @@ var init_agentTurnConformance = __esm({
9263
9517
  panelId: "p1",
9264
9518
  frameNodeId: "frame-1",
9265
9519
  frameName: "home.png",
9266
- bytes: new Uint8Array(),
9520
+ // Real pixels, because a surface derives its vision list from THIS state: a shot with no
9521
+ // bytes is a blank frame, which every door drops from the turn (#829).
9522
+ bytes: new Uint8Array([1, 2, 3]),
9267
9523
  look: { ...DEFAULT_LOOK },
9268
9524
  thumb: null,
9269
9525
  thumbStale: false
@@ -9331,6 +9587,8 @@ var init_agentTurnConformance = __esm({
9331
9587
  outcome: {
9332
9588
  status: "applied",
9333
9589
  headline: "Restyled 1 device",
9590
+ // #840 — every capability the drafts touched, derived from the patch, travels on the outcome.
9591
+ capabilityIds: ["device.look"],
9334
9592
  changed: ["Restyled 1 device"]
9335
9593
  },
9336
9594
  stateDiff: {
@@ -9399,6 +9657,7 @@ var init_agentTurnConformance = __esm({
9399
9657
  outcome: {
9400
9658
  status: "applied",
9401
9659
  headline: "Replaced the screenshot on 1 device",
9660
+ capabilityIds: ["device.replace-pixels"],
9402
9661
  changed: ["Replaced the screenshot on 1 device"]
9403
9662
  },
9404
9663
  stateDiff: {
@@ -9461,6 +9720,7 @@ var init_appstore = __esm({
9461
9720
  init_agentAssetCustody();
9462
9721
  init_agentCapabilities();
9463
9722
  init_agentOutcome();
9723
+ init_agentUnmetActions();
9464
9724
  init_agentTurnCoordinator();
9465
9725
  init_agentTurnConformance();
9466
9726
  init_refinement();
@@ -11136,6 +11396,33 @@ var init_production = __esm({
11136
11396
  }
11137
11397
  });
11138
11398
 
11399
+ // ../product/accountRights.ts
11400
+ var ACCOUNT_TRAFFIC_CLASSES, RECORDED_TRAFFIC_CLASSES;
11401
+ var init_accountRights = __esm({
11402
+ "../product/accountRights.ts"() {
11403
+ "use strict";
11404
+ ACCOUNT_TRAFFIC_CLASSES = ["customer", "owner", "benchmark"];
11405
+ RECORDED_TRAFFIC_CLASSES = [...ACCOUNT_TRAFFIC_CLASSES, "unclassified"];
11406
+ }
11407
+ });
11408
+
11409
+ // ../api/_lib/authorization/accountAccess.ts
11410
+ var init_accountAccess = __esm({
11411
+ "../api/_lib/authorization/accountAccess.ts"() {
11412
+ "use strict";
11413
+ init_accountRights();
11414
+ init_supabaseRest();
11415
+ }
11416
+ });
11417
+
11418
+ // ../api/_lib/authorization/operatorAudit.ts
11419
+ var init_operatorAudit = __esm({
11420
+ "../api/_lib/authorization/operatorAudit.ts"() {
11421
+ "use strict";
11422
+ init_supabaseRest();
11423
+ }
11424
+ });
11425
+
11139
11426
  // ../api/_lib/log.ts
11140
11427
  import { AsyncLocalStorage } from "node:async_hooks";
11141
11428
  function currentRequestId() {
@@ -11168,6 +11455,7 @@ var JWKS_TTL_MS;
11168
11455
  var init_supabaseAuth = __esm({
11169
11456
  "../api/_lib/supabaseAuth.ts"() {
11170
11457
  "use strict";
11458
+ init_accountAccess();
11171
11459
  JWKS_TTL_MS = 10 * 60 * 1e3;
11172
11460
  }
11173
11461
  });
@@ -11214,6 +11502,8 @@ var init_polarWebhook = __esm({
11214
11502
  var init_billingReconcile = __esm({
11215
11503
  "../api/_lib/billingReconcile.ts"() {
11216
11504
  "use strict";
11505
+ init_accountAccess();
11506
+ init_operatorAudit();
11217
11507
  init_log();
11218
11508
  init_supabaseAuth();
11219
11509
  init_entitlement();
@@ -11224,10 +11514,62 @@ var init_billingReconcile = __esm({
11224
11514
  }
11225
11515
  });
11226
11516
 
11517
+ // ../api/_lib/providerUsage.ts
11518
+ var init_providerUsage = __esm({
11519
+ "../api/_lib/providerUsage.ts"() {
11520
+ "use strict";
11521
+ }
11522
+ });
11523
+
11524
+ // ../api/_lib/providerCost.ts
11525
+ var init_providerCost = __esm({
11526
+ "../api/_lib/providerCost.ts"() {
11527
+ "use strict";
11528
+ }
11529
+ });
11530
+
11531
+ // ../api/_lib/providerSpendStore.ts
11532
+ import { z as z8 } from "zod";
11533
+ var nullableCount, decimalMoney, evidenceSchema;
11534
+ var init_providerSpendStore = __esm({
11535
+ "../api/_lib/providerSpendStore.ts"() {
11536
+ "use strict";
11537
+ init_providerCost();
11538
+ init_providerUsage();
11539
+ init_supabaseRest();
11540
+ nullableCount = z8.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).nullable();
11541
+ decimalMoney = z8.string().regex(/^\d{1,19}$/).refine((n) => BigInt(n) <= 9223372036854775807n);
11542
+ evidenceSchema = z8.object({
11543
+ state: z8.enum(["estimated", "partial", "unknown", "not_incurred"]),
11544
+ amountNanoUsd: decimalMoney.nullable(),
11545
+ missing: z8.array(z8.enum(["rate", "usage", "input", "input_breakdown", "inconsistent_input", "cache_read", "cache_write", "cache_write_ttl", "output", "multiple_provider_attempts", "provider_attempts_unknown", "amount_overflow"])),
11546
+ usage: z8.object({ inputTokens: nullableCount, cacheReadTokens: nullableCount, cacheWriteTokens: nullableCount, outputTokens: nullableCount, cacheWriteTtl: z8.enum(["5m", "1h"]).nullable() }).strict().nullable(),
11547
+ rate: z8.object({
11548
+ version: z8.string().regex(/^[a-zA-Z0-9._-]{1,100}$/),
11549
+ effectiveAt: z8.string().datetime(),
11550
+ currency: z8.literal("USD"),
11551
+ model: z8.literal("anthropic/claude-sonnet-4.6"),
11552
+ basis: z8.literal("gateway-list-standard"),
11553
+ region: z8.enum(["global", "us", "eu"]),
11554
+ nanoUsdPerMillion: z8.object({ input: decimalMoney, output: decimalMoney, cacheRead: decimalMoney, cacheWrite5m: decimalMoney, cacheWrite1h: decimalMoney }).strict(),
11555
+ sources: z8.array(z8.enum(["https://platform.claude.com/docs/en/about-claude/pricing", "https://ai-gateway.vercel.sh/v1/models"]))
11556
+ }).strict().nullable(),
11557
+ gateway: z8.object({ generationId: z8.string().regex(/^gen_[a-zA-Z0-9_-]{1,100}$/).nullable(), costUsd: z8.string().regex(/^\d{1,12}(\.\d{1,18})?$/).nullable(), marketCostUsd: z8.string().regex(/^\d{1,12}(\.\d{1,18})?$/).nullable(), attempts: nullableCount, route: z8.string().regex(/^[a-zA-Z0-9_-]{1,40}$/).nullable() }).strict().nullable(),
11558
+ noAttempt: z8.literal(true).optional()
11559
+ }).strict().superRefine((e, context) => {
11560
+ if ((e.state === "estimated" || e.state === "partial") && e.amountNanoUsd === null || e.state === "unknown" && e.amountNanoUsd !== null || e.state === "not_incurred" && (e.amountNanoUsd !== "0" || e.noAttempt !== true)) {
11561
+ context.addIssue({ code: "custom", message: "inconsistent spend state" });
11562
+ }
11563
+ });
11564
+ }
11565
+ });
11566
+
11227
11567
  // ../api/_lib/aiGenerationStore.ts
11228
11568
  var init_aiGenerationStore = __esm({
11229
11569
  "../api/_lib/aiGenerationStore.ts"() {
11230
11570
  "use strict";
11571
+ init_providerUsage();
11572
+ init_providerSpendStore();
11231
11573
  init_supabaseRest();
11232
11574
  }
11233
11575
  });
@@ -11716,7 +12058,7 @@ var init_package = __esm({
11716
12058
  "package.json"() {
11717
12059
  package_default = {
11718
12060
  name: "shotops-mcp",
11719
- version: "0.9.5",
12061
+ version: "0.9.6",
11720
12062
  private: false,
11721
12063
  type: "module",
11722
12064
  description: "The bundle-emitting MCP server over the @engine/@sync spine. Exposes ShotOps tools to ChatGPT, Claude Code, Cursor, and CI over Streamable HTTP with OAuth or personal API tokens. Hosted and MCP tool paths never accept, store, or forward a store-signing credential; the explicit local release CLI validates the user's existing key directly with Apple and records only its path. Renders via headless Playwright Chromium, the engine's native non-browser habitat (same path as mockup-mcp). Also ships as a free LOCAL stdio server (`npx shotops-mcp`) \u2014 same render, on your own machine, no account needed.",
@@ -13926,14 +14268,21 @@ function connectHostedBridge(token2) {
13926
14268
  async refineProject(input) {
13927
14269
  return callOp(token2, "refine_project", input);
13928
14270
  },
13929
- async refinePass(projectId, pass, turnKey) {
13930
- const body = JSON.stringify({ op: "refine_pass", project: projectId, turnKey, pass });
14271
+ async refinePass(projectId, pass, turnKey, benchmark, attachments) {
14272
+ const args = {
14273
+ project: projectId,
14274
+ turnKey,
14275
+ pass,
14276
+ ...benchmark === void 0 ? {} : { benchmark },
14277
+ ...attachments === void 0 ? {} : { attachments }
14278
+ };
14279
+ const body = JSON.stringify({ op: "refine_pass", ...args });
13931
14280
  if (body.length > MAX_PASS_BODY_BYTES) {
13932
14281
  throw new Error(
13933
14282
  "This project has too many screenshots for one Agent pass. Refine a smaller selection, or run the Agent from ShotOps in the browser."
13934
14283
  );
13935
14284
  }
13936
- return callOp(token2, "refine_pass", { project: projectId, turnKey, pass });
14285
+ return callOp(token2, "refine_pass", args);
13937
14286
  },
13938
14287
  async settleRefineTurn(projectId, settlement) {
13939
14288
  const res = await callOp(token2, "refine_finalize", {
@@ -14004,6 +14353,23 @@ var init_hostedBridge = __esm({
14004
14353
  }
14005
14354
  });
14006
14355
 
14356
+ // ../mockup-engine/layoutFindings.ts
14357
+ function describeUnresolvedLayout(findings) {
14358
+ if (!findings || findings.length === 0) return null;
14359
+ const frames = Array.from(new Set(findings.flatMap((finding) => finding.frames))).sort((left, right) => left - right).map((index) => `Frame ${index + 1}`);
14360
+ const where = frames.length === 0 ? "" : frames.length === 1 ? ` in ${frames[0]}` : ` in ${frames.slice(0, -1).join(", ")} and ${frames[frames.length - 1]}`;
14361
+ if (findings.length === 1) {
14362
+ const what = findings[0].kind === "caption-device" ? "A caption still overlaps a device" : findings[0].kind === "caption-caption" ? "Two captions still overlap" : "Two devices still collide";
14363
+ return `${what}${where}.`;
14364
+ }
14365
+ return `${findings.length} layout overlaps still remain${where}.`;
14366
+ }
14367
+ var init_layoutFindings = __esm({
14368
+ "../mockup-engine/layoutFindings.ts"() {
14369
+ "use strict";
14370
+ }
14371
+ });
14372
+
14007
14373
  // ../api/_lib/invalidRequest.ts
14008
14374
  function safeFailureDiagnostic(error) {
14009
14375
  const errorTypes = [];
@@ -14038,57 +14404,57 @@ var init_invalidRequest = __esm({
14038
14404
  });
14039
14405
 
14040
14406
  // ../api/_lib/aiShotProjection.ts
14041
- import { z as z8 } from "zod";
14407
+ import { z as z9 } from "zod";
14042
14408
  var deviceHexSchema, API_AI_SHOT_PROJECTION, API_AI_SHOT_INCLUDED_ENTRIES, API_AI_SHOT_PATCH_SHAPE, apiAiShotPatchSchema, API_AI_SHOT_PROMPT_INVENTORY;
14043
14409
  var init_aiShotProjection = __esm({
14044
14410
  "../api/_lib/aiShotProjection.ts"() {
14045
14411
  "use strict";
14046
14412
  init_refineIntent();
14047
- deviceHexSchema = z8.string().trim().regex(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
14413
+ deviceHexSchema = z9.string().trim().regex(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
14048
14414
  API_AI_SHOT_PROJECTION = {
14049
14415
  orbit: {
14050
14416
  exposure: "included",
14051
- schema: z8.number().min(AI_SHOT_RANGES.orbit.min).max(AI_SHOT_RANGES.orbit.max).optional(),
14417
+ schema: z9.number().min(AI_SHOT_RANGES.orbit.min).max(AI_SHOT_RANGES.orbit.max).optional(),
14052
14418
  prompt: `orbit: camera azimuth in degrees, ${AI_SHOT_RANGES.orbit.min} to ${AI_SHOT_RANGES.orbit.max}. 0 is straight on, negative swings the device left, positive right. Subtle is \xB15\u201320.`
14053
14419
  },
14054
14420
  roll: {
14055
14421
  exposure: "included",
14056
- schema: z8.number().min(AI_SHOT_RANGES.roll.min).max(AI_SHOT_RANGES.roll.max).optional(),
14422
+ schema: z9.number().min(AI_SHOT_RANGES.roll.min).max(AI_SHOT_RANGES.roll.max).optional(),
14057
14423
  prompt: `roll: clock-hand tilt, ${AI_SHOT_RANGES.roll.min} to ${AI_SHOT_RANGES.roll.max} degrees. 0 is upright. Subtle is \xB12\u20138.`
14058
14424
  },
14059
14425
  angle: {
14060
14426
  exposure: "included",
14061
- schema: z8.enum(["front", "left", "right"]).optional(),
14427
+ schema: z9.enum(["front", "left", "right"]).optional(),
14062
14428
  prompt: "angle: front | left | right \u2014 a camera PRESET. Setting it discards any orbit value on that frame, so send orbit OR angle, never both."
14063
14429
  },
14064
14430
  phoneHeight: {
14065
14431
  exposure: "included",
14066
- schema: z8.number().min(AI_SHOT_RANGES.phoneHeight.min).max(AI_SHOT_RANGES.phoneHeight.max).optional(),
14432
+ schema: z9.number().min(AI_SHOT_RANGES.phoneHeight.min).max(AI_SHOT_RANGES.phoneHeight.max).optional(),
14067
14433
  prompt: `phoneHeight: ${AI_SHOT_RANGES.phoneHeight.min}\u2013${AI_SHOT_RANGES.phoneHeight.max} (% of panel height; over 100 bleeds off an edge)`
14068
14434
  },
14069
14435
  hOffset: {
14070
14436
  exposure: "included",
14071
- schema: z8.number().min(AI_SHOT_RANGES.hOffset.min).max(AI_SHOT_RANGES.hOffset.max).optional(),
14437
+ schema: z9.number().min(AI_SHOT_RANGES.hOffset.min).max(AI_SHOT_RANGES.hOffset.max).optional(),
14072
14438
  prompt: `hOffset: ${AI_SHOT_RANGES.hOffset.min}\u2013${AI_SHOT_RANGES.hOffset.max} horizontal position, 0 is centred`
14073
14439
  },
14074
14440
  vOffset: {
14075
14441
  exposure: "included",
14076
- schema: z8.number().min(AI_SHOT_RANGES.vOffset.min).max(AI_SHOT_RANGES.vOffset.max).optional(),
14442
+ schema: z9.number().min(AI_SHOT_RANGES.vOffset.min).max(AI_SHOT_RANGES.vOffset.max).optional(),
14077
14443
  prompt: `vOffset: ${AI_SHOT_RANGES.vOffset.min}\u2013${AI_SHOT_RANGES.vOffset.max} vertical position, 0 is centred`
14078
14444
  },
14079
14445
  material: {
14080
14446
  exposure: "included",
14081
- schema: z8.enum(["real", "clay"]).optional(),
14447
+ schema: z9.enum(["real", "clay"]).optional(),
14082
14448
  prompt: `material: real | clay. colorway (real only): ${AI_COLORWAY_IDS.join(" | ")}. clayTone (clay only): ${AI_CLAY_TONES.join(" | ")}.`
14083
14449
  },
14084
14450
  colorway: {
14085
14451
  exposure: "included",
14086
- schema: z8.enum(AI_COLORWAY_IDS).optional(),
14452
+ schema: z9.enum(AI_COLORWAY_IDS).optional(),
14087
14453
  prompt: `material: real | clay. colorway (real only): ${AI_COLORWAY_IDS.join(" | ")}. clayTone (clay only): ${AI_CLAY_TONES.join(" | ")}.`
14088
14454
  },
14089
14455
  clayTone: {
14090
14456
  exposure: "included",
14091
- schema: z8.enum(AI_CLAY_TONES).optional(),
14457
+ schema: z9.enum(AI_CLAY_TONES).optional(),
14092
14458
  prompt: `material: real | clay. colorway (real only): ${AI_COLORWAY_IDS.join(" | ")}. clayTone (clay only): ${AI_CLAY_TONES.join(" | ")}.`
14093
14459
  },
14094
14460
  customColor: {
@@ -14103,37 +14469,37 @@ var init_aiShotProjection = __esm({
14103
14469
  },
14104
14470
  finish: {
14105
14471
  exposure: "included",
14106
- schema: z8.number().min(AI_SHOT_RANGES.finish.min).max(AI_SHOT_RANGES.finish.max).optional(),
14472
+ schema: z9.number().min(AI_SHOT_RANGES.finish.min).max(AI_SHOT_RANGES.finish.max).optional(),
14107
14473
  prompt: `finish: ${AI_SHOT_RANGES.finish.min}\u2013${AI_SHOT_RANGES.finish.max} (0 matte, 1 glossy). clearcoat: ${AI_SHOT_RANGES.clearcoat.min}\u2013${AI_SHOT_RANGES.clearcoat.max} lacquer sheen on top.`
14108
14474
  },
14109
14475
  clearcoat: {
14110
14476
  exposure: "included",
14111
- schema: z8.number().min(AI_SHOT_RANGES.clearcoat.min).max(AI_SHOT_RANGES.clearcoat.max).optional(),
14477
+ schema: z9.number().min(AI_SHOT_RANGES.clearcoat.min).max(AI_SHOT_RANGES.clearcoat.max).optional(),
14112
14478
  prompt: `finish: ${AI_SHOT_RANGES.finish.min}\u2013${AI_SHOT_RANGES.finish.max} (0 matte, 1 glossy). clearcoat: ${AI_SHOT_RANGES.clearcoat.min}\u2013${AI_SHOT_RANGES.clearcoat.max} lacquer sheen on top.`
14113
14479
  },
14114
14480
  flatScreen: {
14115
14481
  exposure: "included",
14116
- schema: z8.boolean().optional(),
14482
+ schema: z9.boolean().optional(),
14117
14483
  prompt: "flatScreen, glare, lighting, reflections, clipToFrame: true | false."
14118
14484
  },
14119
14485
  glare: {
14120
14486
  exposure: "included",
14121
- schema: z8.boolean().optional(),
14487
+ schema: z9.boolean().optional(),
14122
14488
  prompt: "flatScreen, glare, lighting, reflections, clipToFrame: true | false."
14123
14489
  },
14124
14490
  lighting: {
14125
14491
  exposure: "included",
14126
- schema: z8.boolean().optional(),
14492
+ schema: z9.boolean().optional(),
14127
14493
  prompt: "flatScreen, glare, lighting, reflections, clipToFrame: true | false."
14128
14494
  },
14129
14495
  reflections: {
14130
14496
  exposure: "included",
14131
- schema: z8.boolean().optional(),
14497
+ schema: z9.boolean().optional(),
14132
14498
  prompt: "flatScreen, glare, lighting, reflections, clipToFrame: true | false."
14133
14499
  },
14134
14500
  clipToFrame: {
14135
14501
  exposure: "included",
14136
- schema: z8.boolean().optional(),
14502
+ schema: z9.boolean().optional(),
14137
14503
  prompt: "flatScreen, glare, lighting, reflections, clipToFrame: true | false."
14138
14504
  }
14139
14505
  };
@@ -14141,7 +14507,7 @@ var init_aiShotProjection = __esm({
14141
14507
  API_AI_SHOT_PATCH_SHAPE = Object.fromEntries(
14142
14508
  API_AI_SHOT_INCLUDED_ENTRIES.map(([field, projection]) => [field, projection.schema])
14143
14509
  );
14144
- apiAiShotPatchSchema = z8.object(API_AI_SHOT_PATCH_SHAPE);
14510
+ apiAiShotPatchSchema = z9.object(API_AI_SHOT_PATCH_SHAPE);
14145
14511
  API_AI_SHOT_PROMPT_INVENTORY = [
14146
14512
  ...new Set(API_AI_SHOT_INCLUDED_ENTRIES.map(([, projection]) => projection.prompt))
14147
14513
  ];
@@ -14171,8 +14537,8 @@ var init_visionGeneration = __esm({
14171
14537
  });
14172
14538
 
14173
14539
  // ../api/_lib/refineProjectContracts.ts
14174
- import { z as z9 } from "zod";
14175
- var localeIds, MAX_DURABLE_MODEL_PROSE, hexSchema, currentColorSchema, shotPatchSchema, aiNumberSchema, captionStylePatchSchema, MAX_WIRE_GRADIENT_STOPS, gradientStopSchema, backgroundPatchSchema, stripBackgroundPatchSchema, framePatchSchema, currentCaptionStyleSchema, currentGradientStopSchema, currentStops, currentBackgroundSchema, currentStripBackgroundSchema, currentShotSchema, currentFramePatchSchema, localeCodeSchema, focusSchema, currentFrameSchema, captionOperationSchema, frameOperationSchema, frameIdSchema, assetIdSchema, structuralOperationSchema, attachmentSchema, visionScreenshotSchema, requestSchema, legacyFinalizeSchema, durableUsageSchema, durablePassSchema, durableStateDiffSchema, agentOutcomeSchema, durableFinalizeSchema, revertSchema, finalizeSchema, createSessionSchema, historySchema, REFINE_FIELD_LABELS;
14540
+ import { z as z10 } from "zod";
14541
+ var localeIds, MAX_DURABLE_MODEL_PROSE, hexSchema, currentColorSchema, shotPatchSchema, aiNumberSchema, captionStylePatchSchema, MAX_WIRE_GRADIENT_STOPS, gradientStopSchema, backgroundPatchSchema, stripBackgroundPatchSchema, framePatchSchema, currentCaptionStyleSchema, currentGradientStopSchema, currentStops, currentBackgroundSchema, currentStripBackgroundSchema, currentShotSchema, currentFramePatchSchema, localeCodeSchema, focusSchema, currentFrameSchema, captionOperationSchema, frameOperationSchema, frameIdSchema, assetIdSchema, structuralOperationSchema, attachmentSchema, refinementAttachmentsSchema, visionScreenshotSchema, requestSchema, legacyFinalizeSchema, durableUsageSchema, durablePassSchema, durableStateDiffSchema, agentOutcomeSchema, durableFinalizeSchema, revertSchema, finalizeSchema, createSessionSchema, historySchema, REFINE_FIELD_LABELS;
14176
14542
  var init_refineProjectContracts = __esm({
14177
14543
  "../api/_lib/refineProjectContracts.ts"() {
14178
14544
  "use strict";
@@ -14184,229 +14550,237 @@ var init_refineProjectContracts = __esm({
14184
14550
  init_aiShotProjection();
14185
14551
  localeIds = APP_STORE_LOCALES;
14186
14552
  MAX_DURABLE_MODEL_PROSE = 32e3;
14187
- hexSchema = z9.string().trim().regex(/^#?([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/);
14188
- currentColorSchema = z9.string().trim().max(64);
14553
+ hexSchema = z10.string().trim().regex(/^#?([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/);
14554
+ currentColorSchema = z10.string().trim().max(64);
14189
14555
  shotPatchSchema = apiAiShotPatchSchema;
14190
- aiNumberSchema = z9.union([z9.number().finite(), z9.object({ scale: z9.number().finite() })]);
14191
- captionStylePatchSchema = z9.object({
14192
- fontId: z9.enum(AI_CAPTION_FONT_IDS).optional(),
14556
+ aiNumberSchema = z10.union([z10.number().finite(), z10.object({ scale: z10.number().finite() })]);
14557
+ captionStylePatchSchema = z10.object({
14558
+ fontId: z10.enum(AI_CAPTION_FONT_IDS).optional(),
14193
14559
  sizePt: aiNumberSchema.optional(),
14194
14560
  color: hexSchema.optional(),
14195
- align: z9.enum(["left", "center", "right"]).optional(),
14561
+ align: z10.enum(["left", "center", "right"]).optional(),
14196
14562
  maxWidth: aiNumberSchema.optional(),
14197
14563
  lineHeight: aiNumberSchema.optional(),
14198
- band: z9.enum(["top", "bottom"]).optional(),
14564
+ band: z10.enum(["top", "bottom"]).optional(),
14199
14565
  bandInset: aiNumberSchema.optional(),
14200
14566
  bandCenter: aiNumberSchema.optional(),
14201
14567
  reserveLines: aiNumberSchema.optional()
14202
14568
  });
14203
14569
  MAX_WIRE_GRADIENT_STOPS = 32;
14204
- gradientStopSchema = z9.object({ color: hexSchema, at: z9.number().finite() });
14205
- backgroundPatchSchema = z9.object({
14206
- stops: z9.array(gradientStopSchema).max(MAX_WIRE_GRADIENT_STOPS).optional(),
14207
- angle: z9.number().finite().optional()
14570
+ gradientStopSchema = z10.object({ color: hexSchema, at: z10.number().finite() });
14571
+ backgroundPatchSchema = z10.object({
14572
+ stops: z10.array(gradientStopSchema).max(MAX_WIRE_GRADIENT_STOPS).optional(),
14573
+ angle: z10.number().finite().optional()
14208
14574
  });
14209
14575
  stripBackgroundPatchSchema = backgroundPatchSchema.extend({
14210
- shadow: z9.boolean().optional(),
14211
- floorReflection: z9.boolean().optional()
14576
+ shadow: z10.boolean().optional(),
14577
+ floorReflection: z10.boolean().optional()
14212
14578
  });
14213
- framePatchSchema = z9.object({
14579
+ framePatchSchema = z10.object({
14214
14580
  captionStyle: captionStylePatchSchema.optional(),
14215
14581
  background: backgroundPatchSchema.optional()
14216
14582
  });
14217
14583
  currentCaptionStyleSchema = captionStylePatchSchema.extend({
14218
14584
  color: currentColorSchema.optional()
14219
14585
  });
14220
- currentGradientStopSchema = z9.object({ color: currentColorSchema, at: z9.number().finite() });
14221
- currentStops = z9.array(currentGradientStopSchema).max(MAX_WIRE_GRADIENT_STOPS).optional();
14586
+ currentGradientStopSchema = z10.object({ color: currentColorSchema, at: z10.number().finite() });
14587
+ currentStops = z10.array(currentGradientStopSchema).max(MAX_WIRE_GRADIENT_STOPS).optional();
14222
14588
  currentBackgroundSchema = backgroundPatchSchema.extend({ stops: currentStops });
14223
14589
  currentStripBackgroundSchema = stripBackgroundPatchSchema.extend({ stops: currentStops });
14224
14590
  currentShotSchema = shotPatchSchema.extend({
14225
14591
  customColor: currentColorSchema.optional(),
14226
14592
  clayCustom: currentColorSchema.optional()
14227
14593
  });
14228
- currentFramePatchSchema = z9.object({
14594
+ currentFramePatchSchema = z10.object({
14229
14595
  captionStyle: currentCaptionStyleSchema.optional(),
14230
14596
  background: currentBackgroundSchema.optional()
14231
14597
  });
14232
- localeCodeSchema = z9.string().trim().regex(/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/);
14233
- focusSchema = z9.discriminatedUnion("kind", [
14234
- z9.object({ kind: z9.literal("project"), label: z9.string().trim().min(1).max(180) }),
14235
- z9.object({
14236
- kind: z9.literal("frame"),
14237
- frameId: z9.string().trim().min(1).max(180),
14238
- label: z9.string().trim().min(1).max(180)
14598
+ localeCodeSchema = z10.string().trim().regex(/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/);
14599
+ focusSchema = z10.discriminatedUnion("kind", [
14600
+ z10.object({ kind: z10.literal("project"), label: z10.string().trim().min(1).max(180) }),
14601
+ z10.object({
14602
+ kind: z10.literal("frame"),
14603
+ frameId: z10.string().trim().min(1).max(180),
14604
+ label: z10.string().trim().min(1).max(180)
14239
14605
  }),
14240
- z9.object({
14241
- kind: z9.literal("device"),
14242
- frameId: z9.string().trim().min(1).max(180),
14243
- deviceId: z9.string().trim().min(1).max(180),
14244
- label: z9.string().trim().min(1).max(180)
14606
+ z10.object({
14607
+ kind: z10.literal("device"),
14608
+ frameId: z10.string().trim().min(1).max(180),
14609
+ deviceId: z10.string().trim().min(1).max(180),
14610
+ label: z10.string().trim().min(1).max(180)
14245
14611
  }),
14246
- z9.object({
14247
- kind: z9.literal("caption"),
14248
- frameId: z9.string().trim().min(1).max(180),
14249
- captionIndex: z9.number().int().min(0).max(32),
14250
- label: z9.string().trim().min(1).max(240)
14612
+ z10.object({
14613
+ kind: z10.literal("caption"),
14614
+ frameId: z10.string().trim().min(1).max(180),
14615
+ captionIndex: z10.number().int().min(0).max(32),
14616
+ label: z10.string().trim().min(1).max(240)
14251
14617
  })
14252
14618
  ]);
14253
- currentFrameSchema = z9.object({
14254
- id: z9.string().trim().min(1).max(180),
14255
- label: z9.string().trim().min(1).max(180),
14256
- sourceName: z9.string().trim().min(1).max(180).nullable(),
14257
- devices: z9.array(z9.object({
14258
- id: z9.string().trim().min(1).max(180),
14259
- label: z9.string().trim().min(1).max(180),
14619
+ currentFrameSchema = z10.object({
14620
+ id: z10.string().trim().min(1).max(180),
14621
+ label: z10.string().trim().min(1).max(180),
14622
+ sourceName: z10.string().trim().min(1).max(180).nullable(),
14623
+ devices: z10.array(z10.object({
14624
+ id: z10.string().trim().min(1).max(180),
14625
+ label: z10.string().trim().min(1).max(180),
14260
14626
  look: currentShotSchema
14261
14627
  })).max(20),
14262
- captions: z9.array(z9.object({
14263
- index: z9.number().int().min(0).max(32),
14264
- label: z9.string().trim().min(1).max(180),
14265
- text: z9.string().max(240),
14628
+ captions: z10.array(z10.object({
14629
+ index: z10.number().int().min(0).max(32),
14630
+ label: z10.string().trim().min(1).max(180),
14631
+ text: z10.string().max(240),
14266
14632
  style: currentCaptionStyleSchema
14267
14633
  })).max(32),
14268
- background: currentBackgroundSchema.nullable()
14634
+ background: currentBackgroundSchema.nullable(),
14635
+ // The per-screenshot slot this frame's pixels occupy, or null for a frame with no screenshot yet.
14636
+ // Optional so a client that predates it still parses; the prompt then numbers by position.
14637
+ screenshotIndex: z10.number().int().min(0).max(31).nullable().optional()
14269
14638
  });
14270
- captionOperationSchema = z9.discriminatedUnion("operation", [
14271
- z9.object({
14272
- operation: z9.literal("add"),
14273
- frameId: z9.string().trim().min(1).max(180),
14274
- afterIndex: z9.number().int().min(-1).max(32).optional(),
14275
- text: z9.string().trim().max(240),
14639
+ captionOperationSchema = z10.discriminatedUnion("operation", [
14640
+ z10.object({
14641
+ operation: z10.literal("add"),
14642
+ frameId: z10.string().trim().min(1).max(180),
14643
+ afterIndex: z10.number().int().min(-1).max(32).optional(),
14644
+ text: z10.string().trim().max(240),
14276
14645
  style: captionStylePatchSchema.optional()
14277
14646
  }),
14278
- z9.object({
14279
- operation: z9.literal("remove"),
14280
- frameId: z9.string().trim().min(1).max(180),
14281
- index: z9.number().int().min(0).max(32)
14647
+ z10.object({
14648
+ operation: z10.literal("remove"),
14649
+ frameId: z10.string().trim().min(1).max(180),
14650
+ index: z10.number().int().min(0).max(32)
14282
14651
  }),
14283
- z9.object({
14284
- operation: z9.literal("move"),
14285
- frameId: z9.string().trim().min(1).max(180),
14286
- index: z9.number().int().min(0).max(32),
14287
- toFrameId: z9.string().trim().min(1).max(180),
14288
- toIndex: z9.number().int().min(0).max(32).optional()
14652
+ z10.object({
14653
+ operation: z10.literal("move"),
14654
+ frameId: z10.string().trim().min(1).max(180),
14655
+ index: z10.number().int().min(0).max(32),
14656
+ toFrameId: z10.string().trim().min(1).max(180),
14657
+ toIndex: z10.number().int().min(0).max(32).optional()
14289
14658
  })
14290
14659
  ]);
14291
- frameOperationSchema = z9.discriminatedUnion("operation", [
14292
- z9.object({
14293
- operation: z9.literal("add"),
14294
- afterFrameId: z9.string().trim().min(1).max(180).optional()
14660
+ frameOperationSchema = z10.discriminatedUnion("operation", [
14661
+ z10.object({
14662
+ operation: z10.literal("add"),
14663
+ afterFrameId: z10.string().trim().min(1).max(180).optional()
14295
14664
  }),
14296
- z9.object({
14297
- operation: z9.literal("remove"),
14298
- frameId: z9.string().trim().min(1).max(180)
14665
+ z10.object({
14666
+ operation: z10.literal("remove"),
14667
+ frameId: z10.string().trim().min(1).max(180)
14299
14668
  }),
14300
- z9.object({
14301
- operation: z9.literal("move"),
14302
- frameId: z9.string().trim().min(1).max(180),
14303
- afterFrameId: z9.string().trim().min(1).max(180).optional()
14669
+ z10.object({
14670
+ operation: z10.literal("move"),
14671
+ frameId: z10.string().trim().min(1).max(180),
14672
+ afterFrameId: z10.string().trim().min(1).max(180).optional()
14304
14673
  }),
14305
- z9.object({
14306
- operation: z9.literal("split"),
14307
- frameId: z9.string().trim().min(1).max(180),
14308
- deviceIds: z9.array(z9.string().trim().min(1).max(180)).min(1).max(20)
14674
+ z10.object({
14675
+ operation: z10.literal("split"),
14676
+ frameId: z10.string().trim().min(1).max(180),
14677
+ deviceIds: z10.array(z10.string().trim().min(1).max(180)).min(1).max(20)
14309
14678
  })
14310
14679
  ]);
14311
- frameIdSchema = z9.string().trim().min(1).max(180);
14312
- assetIdSchema = z9.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9_-]*$/);
14313
- structuralOperationSchema = z9.discriminatedUnion("op", [
14314
- z9.object({ op: z9.literal("frame.add"), afterFrameId: frameIdSchema.optional() }),
14315
- z9.object({ op: z9.literal("frame.remove"), frameId: frameIdSchema }),
14316
- z9.object({ op: z9.literal("frame.move"), frameId: frameIdSchema, afterFrameId: frameIdSchema.optional() }),
14317
- z9.object({
14318
- op: z9.literal("frame.split"),
14680
+ frameIdSchema = z10.string().trim().min(1).max(180);
14681
+ assetIdSchema = z10.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9_-]*$/);
14682
+ structuralOperationSchema = z10.discriminatedUnion("op", [
14683
+ z10.object({
14684
+ op: z10.literal("frame.add"),
14685
+ afterFrameId: frameIdSchema.optional(),
14686
+ frameRef: z10.string().min(1).max(64).regex(/^[A-Za-z][A-Za-z0-9_-]*$/).describe("Patch-local frame alias, unique and different from every existing frame/device id. Later ordered operations may use it as a frame target. Never persisted.").optional()
14687
+ }),
14688
+ z10.object({ op: z10.literal("frame.remove"), frameId: frameIdSchema }),
14689
+ z10.object({ op: z10.literal("frame.move"), frameId: frameIdSchema, afterFrameId: frameIdSchema.optional() }),
14690
+ z10.object({
14691
+ op: z10.literal("frame.split"),
14319
14692
  frameId: frameIdSchema,
14320
- deviceIds: z9.array(frameIdSchema).min(1).max(20)
14693
+ deviceIds: z10.array(frameIdSchema).min(1).max(20)
14321
14694
  }),
14322
- z9.object({ op: z9.literal("frame.combine"), frameId: frameIdSchema, fromFrameId: frameIdSchema }),
14323
- z9.object({ op: z9.literal("frame.template-reset"), frameId: frameIdSchema }),
14324
- z9.object({
14325
- op: z9.literal("caption.add"),
14695
+ z10.object({ op: z10.literal("frame.combine"), frameId: frameIdSchema, fromFrameId: frameIdSchema }),
14696
+ z10.object({ op: z10.literal("frame.template-reset"), frameId: frameIdSchema }),
14697
+ z10.object({
14698
+ op: z10.literal("caption.add"),
14326
14699
  frameId: frameIdSchema,
14327
- afterIndex: z9.number().int().min(-1).max(32).optional(),
14328
- text: z9.string().trim().max(240),
14700
+ afterIndex: z10.number().int().min(-1).max(32).optional(),
14701
+ text: z10.string().trim().max(240),
14329
14702
  style: captionStylePatchSchema.optional()
14330
14703
  }),
14331
- z9.object({ op: z9.literal("caption.remove"), frameId: frameIdSchema, index: z9.number().int().min(0).max(32) }),
14332
- z9.object({
14333
- op: z9.literal("caption.move"),
14704
+ z10.object({ op: z10.literal("caption.remove"), frameId: frameIdSchema, index: z10.number().int().min(0).max(32) }),
14705
+ z10.object({
14706
+ op: z10.literal("caption.move"),
14334
14707
  frameId: frameIdSchema,
14335
- index: z9.number().int().min(0).max(32),
14708
+ index: z10.number().int().min(0).max(32),
14336
14709
  toFrameId: frameIdSchema,
14337
- toIndex: z9.number().int().min(0).max(32).optional()
14710
+ toIndex: z10.number().int().min(0).max(32).optional()
14338
14711
  }),
14339
- z9.object({
14340
- op: z9.literal("device.move"),
14712
+ z10.object({
14713
+ op: z10.literal("device.move"),
14341
14714
  deviceId: frameIdSchema,
14342
14715
  toFrameId: frameIdSchema,
14343
- toIndex: z9.number().int().min(0).max(20).optional()
14716
+ toIndex: z10.number().int().min(0).max(20).optional()
14344
14717
  }),
14345
- z9.object({ op: z9.literal("device.reorder"), deviceId: frameIdSchema, toIndex: z9.number().int().min(0).max(20) }),
14346
- z9.object({ op: z9.literal("device.remove"), deviceId: frameIdSchema }),
14718
+ z10.object({ op: z10.literal("device.reorder"), deviceId: frameIdSchema, toIndex: z10.number().int().min(0).max(20) }),
14719
+ z10.object({ op: z10.literal("device.remove"), deviceId: frameIdSchema }),
14347
14720
  // The two asset operations (#573). `assetId` is an OPAQUE per-turn handle, so the wire bounds its
14348
14721
  // shape and nothing more: a path, a URL or an account id is never a legal value here because the
14349
14722
  // handle table is the only thing that resolves one, and it holds only handles this turn minted.
14350
- z9.object({
14351
- op: z9.literal("device.add"),
14723
+ z10.object({
14724
+ op: z10.literal("device.add"),
14352
14725
  toFrameId: frameIdSchema,
14353
14726
  assetId: assetIdSchema,
14354
- toIndex: z9.number().int().min(0).max(20).optional()
14727
+ toIndex: z10.number().int().min(0).max(20).optional()
14355
14728
  }),
14356
- z9.object({ op: z9.literal("device.replace"), deviceId: frameIdSchema, assetId: assetIdSchema })
14729
+ z10.object({ op: z10.literal("device.replace"), deviceId: frameIdSchema, assetId: assetIdSchema })
14357
14730
  ]);
14358
- attachmentSchema = z9.object({
14359
- ref: z9.string().trim().min(1).max(512),
14360
- label: z9.string().trim().min(1).max(180),
14361
- width: z9.number().int().min(1).max(2e4).optional(),
14362
- height: z9.number().int().min(1).max(2e4).optional()
14731
+ attachmentSchema = z10.object({
14732
+ ref: z10.string().trim().min(1).max(512),
14733
+ label: z10.string().trim().min(1).max(180),
14734
+ width: z10.number().int().min(1).max(2e4).optional(),
14735
+ height: z10.number().int().min(1).max(2e4).optional()
14363
14736
  });
14364
- visionScreenshotSchema = z9.object({
14365
- name: z9.string().trim().min(1).max(180),
14366
- mediaType: z9.enum(["image/png", "image/jpeg", "image/webp"]),
14367
- data: z9.string().min(16).max(MAX_VISION_IMAGE_BASE64).regex(/^[A-Za-z0-9+/]+=*$/)
14737
+ refinementAttachmentsSchema = z10.array(attachmentSchema).max(MAX_START_SCREENSHOTS);
14738
+ visionScreenshotSchema = z10.object({
14739
+ name: z10.string().trim().min(1).max(180),
14740
+ mediaType: z10.enum(["image/png", "image/jpeg", "image/webp"]),
14741
+ data: z10.string().min(16).max(MAX_VISION_IMAGE_BASE64).regex(/^[A-Za-z0-9+/]+=*$/)
14368
14742
  });
14369
- requestSchema = z9.object({
14370
- projectId: z9.string().uuid(),
14371
- sessionId: z9.string().uuid().optional(),
14372
- turnId: z9.string().uuid().optional(),
14373
- cycle: z9.number().int().min(1).max(3).optional(),
14374
- instruction: z9.string().trim().min(1).max(MAX_REFINE_PROMPT_LENGTH),
14743
+ requestSchema = z10.object({
14744
+ projectId: z10.string().uuid(),
14745
+ sessionId: z10.string().uuid().optional(),
14746
+ turnId: z10.string().uuid().optional(),
14747
+ cycle: z10.number().int().min(1).max(3).optional(),
14748
+ instruction: z10.string().trim().min(1).max(MAX_REFINE_PROMPT_LENGTH),
14375
14749
  focus: focusSchema.optional(),
14376
14750
  baseLocale: localeCodeSchema,
14377
- current: z9.object({
14378
- captions: z9.array(z9.string().max(240)).min(1).max(MAX_START_SCREENSHOTS),
14379
- outputs: z9.array(z9.enum(START_OUTPUT_IDS)).min(1).max(START_OUTPUT_IDS.length),
14751
+ current: z10.object({
14752
+ captions: z10.array(z10.string().max(240)).min(1).max(MAX_START_SCREENSHOTS),
14753
+ outputs: z10.array(z10.enum(START_OUTPUT_IDS)).min(1).max(START_OUTPUT_IDS.length),
14380
14754
  // Bounded by the CATALOG, not by six. Studio's Add-language dialog offers every catalog entry
14381
14755
  // with no ceiling and the reducer just appends, so a seventh language used to 400 every Agent
14382
14756
  // turn on that project forever. A count ceiling below what the product lets a user build is a
14383
14757
  // lockout; this one is only here to bound the prompt.
14384
- locales: z9.array(localeCodeSchema).min(1).max(localeIds.length),
14385
- designPreset: z9.enum(START_DESIGN_PRESET_IDS).nullable(),
14386
- layout: z9.enum(START_LAYOUT_IDS).nullable(),
14387
- frames: z9.array(currentShotSchema).min(1).max(MAX_START_SCREENSHOTS),
14758
+ locales: z10.array(localeCodeSchema).min(1).max(localeIds.length),
14759
+ designPreset: z10.enum(START_DESIGN_PRESET_IDS).nullable(),
14760
+ layout: z10.enum(START_LAYOUT_IDS).nullable(),
14761
+ frames: z10.array(currentShotSchema).min(1).max(MAX_START_SCREENSHOTS),
14388
14762
  background: currentStripBackgroundSchema.optional(),
14389
- frameStyles: z9.array(currentFramePatchSchema).min(1).max(MAX_START_SCREENSHOTS).optional(),
14763
+ frameStyles: z10.array(currentFramePatchSchema).min(1).max(MAX_START_SCREENSHOTS).optional(),
14390
14764
  // Structural editing can add blank frames without adding source screenshots. Keep the vision
14391
14765
  // attachment ceiling at ten while allowing those real project frames back into correction
14392
14766
  // cycles instead of rejecting the first successful `add` operation on a full strip.
14393
- structure: z9.array(currentFrameSchema).min(1).max(20).optional()
14767
+ structure: z10.array(currentFrameSchema).min(1).max(20).optional()
14394
14768
  }),
14395
- screenshots: z9.array(visionScreenshotSchema).min(1).max(MAX_START_SCREENSHOTS),
14769
+ screenshots: z10.array(visionScreenshotSchema).min(1).max(MAX_START_SCREENSHOTS),
14396
14770
  // Private refs from Studio. Opaque asset handles are derived server-side, so a client cannot
14397
14771
  // plant its own handle table or smuggle a ref into model-visible data.
14398
- attachments: z9.array(attachmentSchema).max(MAX_START_SCREENSHOTS).optional(),
14772
+ attachments: refinementAttachmentsSchema.optional(),
14399
14773
  // Opaque per-turn handles, on the wire because the MCP doors mint the table where the refs are
14400
14774
  // (the adapter) and buy the pass somewhere else — hosted in-process, local over the control
14401
14775
  // plane. Carrying a descriptor is not carrying authority: `{ id, label }` names nothing outside
14402
14776
  // the turn that minted it, binds nothing, and the Studio door overwrites this field with its own
14403
14777
  // table's descriptors regardless of what a client sent. The gate that matters is the
14404
14778
  // authoritative write, which re-checks every ref a record introduces.
14405
- assets: z9.array(z9.object({
14406
- id: z9.string().trim().min(1).max(64),
14407
- label: z9.string().trim().min(1).max(180),
14408
- width: z9.number().int().min(1).max(2e4).optional(),
14409
- height: z9.number().int().min(1).max(2e4).optional()
14779
+ assets: z10.array(z10.object({
14780
+ id: z10.string().trim().min(1).max(64),
14781
+ label: z10.string().trim().min(1).max(180),
14782
+ width: z10.number().int().min(1).max(2e4).optional(),
14783
+ height: z10.number().int().min(1).max(2e4).optional()
14410
14784
  })).max(MAX_START_SCREENSHOTS).optional(),
14411
14785
  composedPreview: visionScreenshotSchema.optional(),
14412
14786
  // MEASURED, never authored (#550): the client derives these from the engine's own collision
@@ -14414,17 +14788,24 @@ var init_refineProjectContracts = __esm({
14414
14788
  // prompt renders into prose, never a value that reaches the reducer, and a strip with an unusual
14415
14789
  // number of overlaps must not 400 a turn that was not about layout. The client's own ceiling is
14416
14790
  // `MAX_REPORTED_FINDINGS` (mockup-engine/layoutFindings.ts), well inside this.
14417
- layoutFindings: z9.array(z9.object({
14418
- kind: z9.enum(["caption-caption", "caption-device", "device-device"]),
14419
- frames: z9.array(z9.number().int().min(0).max(31)).min(1).max(2),
14420
- depthPct: z9.number().int().min(0).max(100),
14421
- anchored: z9.boolean(),
14422
- clearAtScale: z9.number().min(0.05).max(1).optional()
14791
+ layoutFindings: z10.array(z10.object({
14792
+ kind: z10.enum(["caption-caption", "caption-device", "device-device"]),
14793
+ frames: z10.array(z10.number().int().min(0).max(31)).min(1).max(2),
14794
+ depthPct: z10.number().int().min(0).max(100),
14795
+ anchored: z10.boolean(),
14796
+ clearAtScale: z10.number().min(0.05).max(1).optional()
14423
14797
  })).max(24).optional(),
14424
- previous: z9.object({
14425
- actualDiff: z9.array(z9.string().trim().min(1).max(300)).max(24),
14426
- inspection: z9.string().trim().min(1).max(MAX_DURABLE_MODEL_PROSE),
14427
- cycle: z9.number().int().min(1).max(2)
14798
+ previous: z10.object({
14799
+ actualDiff: z10.array(z10.string().trim().min(1).max(300)).max(24),
14800
+ inspection: z10.string().trim().min(1).max(MAX_DURABLE_MODEL_PROSE),
14801
+ cycle: z10.number().int().min(1).max(2),
14802
+ // The creating structural operations earlier cycles of this turn already performed, so the
14803
+ // prompt can list them and the model stops re-adding what it added. Optional: an older client
14804
+ // sends none, and the coordinator drops a repeat regardless.
14805
+ appliedOperations: z10.array(z10.object({
14806
+ op: z10.string().trim().min(1).max(40),
14807
+ target: z10.string().trim().min(1).max(180)
14808
+ })).max(32).optional()
14428
14809
  }).optional()
14429
14810
  }).superRefine((value, context) => {
14430
14811
  if (value.sessionId == null !== (value.turnId == null)) {
@@ -14454,13 +14835,13 @@ var init_refineProjectContracts = __esm({
14454
14835
  });
14455
14836
  }
14456
14837
  });
14457
- legacyFinalizeSchema = z9.object({
14458
- generationId: z9.string().uuid(),
14459
- outcome: z9.enum(["completed", "cancelled", "failed"]),
14460
- projectId: z9.string().uuid(),
14461
- instruction: z9.string().trim().min(1).max(MAX_REFINE_PROMPT_LENGTH).optional(),
14462
- effectSummary: z9.string().trim().min(1).max(2e3).optional(),
14463
- turnStatus: z9.enum(["applied", "no_change", "failed", "cancelled"]).optional()
14838
+ legacyFinalizeSchema = z10.object({
14839
+ generationId: z10.string().uuid(),
14840
+ outcome: z10.enum(["completed", "cancelled", "failed"]),
14841
+ projectId: z10.string().uuid(),
14842
+ instruction: z10.string().trim().min(1).max(MAX_REFINE_PROMPT_LENGTH).optional(),
14843
+ effectSummary: z10.string().trim().min(1).max(2e3).optional(),
14844
+ turnStatus: z10.enum(["applied", "no_change", "failed", "cancelled"]).optional()
14464
14845
  }).superRefine((value, context) => {
14465
14846
  const durableFields = [value.instruction, value.effectSummary, value.turnStatus];
14466
14847
  const present = durableFields.filter((field) => field !== void 0).length;
@@ -14472,52 +14853,57 @@ var init_refineProjectContracts = __esm({
14472
14853
  });
14473
14854
  }
14474
14855
  });
14475
- durableUsageSchema = z9.object({
14476
- inputTokens: z9.number().int().min(0),
14477
- cacheReadTokens: z9.number().int().min(0).default(0),
14478
- imageTokens: z9.number().int().min(0).nullable(),
14479
- outputTokens: z9.number().int().min(0),
14480
- totalTokens: z9.number().int().min(0)
14856
+ durableUsageSchema = z10.object({
14857
+ inputTokens: z10.number().int().min(0),
14858
+ cacheReadTokens: z10.number().int().min(0).default(0),
14859
+ imageTokens: z10.number().int().min(0).nullable(),
14860
+ outputTokens: z10.number().int().min(0),
14861
+ totalTokens: z10.number().int().min(0)
14481
14862
  });
14482
- durablePassSchema = z9.object({
14483
- phase: z9.enum(["applying", "inspecting", "adjusting"]),
14484
- generationId: z9.string().uuid(),
14485
- summary: z9.string().trim().min(1).max(MAX_DURABLE_MODEL_PROSE),
14863
+ durablePassSchema = z10.object({
14864
+ phase: z10.enum(["applying", "inspecting", "adjusting"]),
14865
+ generationId: z10.string().uuid(),
14866
+ summary: z10.string().trim().min(1).max(MAX_DURABLE_MODEL_PROSE),
14486
14867
  usage: durableUsageSchema,
14487
- createdAt: z9.string().datetime()
14868
+ createdAt: z10.string().datetime()
14488
14869
  });
14489
- durableStateDiffSchema = z9.object({
14490
- summary: z9.string().trim().min(1).max(2e3),
14491
- broaderChanges: z9.array(z9.string().trim().min(1).max(500)).max(24),
14492
- remainingMismatch: z9.string().trim().min(1).max(MAX_DURABLE_MODEL_PROSE).nullable()
14870
+ durableStateDiffSchema = z10.object({
14871
+ summary: z10.string().trim().min(1).max(2e3),
14872
+ broaderChanges: z10.array(z10.string().trim().min(1).max(500)).max(24),
14873
+ remainingMismatch: z10.string().trim().min(1).max(MAX_DURABLE_MODEL_PROSE).nullable()
14493
14874
  });
14494
- agentOutcomeSchema = z9.custom((value) => {
14875
+ agentOutcomeSchema = z10.custom((value) => {
14495
14876
  try {
14496
14877
  return !!value && typeof value === "object" && validateAgentOutcome(value).length === 0;
14497
14878
  } catch {
14498
14879
  return false;
14499
14880
  }
14500
14881
  }, { message: "The Agent outcome is invalid." });
14501
- durableFinalizeSchema = z9.object({
14502
- projectId: z9.string().uuid(),
14503
- sessionId: z9.string().uuid(),
14504
- turnId: z9.string().uuid(),
14882
+ durableFinalizeSchema = z10.object({
14883
+ projectId: z10.string().uuid(),
14884
+ sessionId: z10.string().uuid(),
14885
+ turnId: z10.string().uuid(),
14505
14886
  // #580 — the retry / clarification chain, and the ONLY receipt fact this door takes from the
14506
14887
  // browser. Every other figure on the receipt is re-read server-side from the ledger or derived
14507
14888
  // from the validated outcome; a parent id is a correlation the client alone knows.
14508
- parentTurnId: z9.string().uuid().optional(),
14509
- generationIds: z9.array(z9.string().uuid()).min(1).max(3),
14510
- outcome: z9.enum(["completed", "cancelled", "failed"]),
14511
- committed: z9.boolean(),
14512
- instruction: z9.string().trim().min(1).max(MAX_REFINE_PROMPT_LENGTH),
14889
+ parentTurnId: z10.string().uuid().optional(),
14890
+ generationIds: z10.array(z10.string().uuid()).min(1).max(3),
14891
+ outcome: z10.enum(["completed", "cancelled", "failed"]),
14892
+ committed: z10.boolean(),
14893
+ instruction: z10.string().trim().min(1).max(MAX_REFINE_PROMPT_LENGTH),
14513
14894
  focus: focusSchema,
14514
- interpretation: z9.string().trim().min(1).max(MAX_DURABLE_MODEL_PROSE),
14515
- passes: z9.array(durablePassSchema).min(1).max(3),
14895
+ interpretation: z10.string().trim().min(1).max(MAX_DURABLE_MODEL_PROSE),
14896
+ passes: z10.array(durablePassSchema).min(1).max(3),
14516
14897
  finalStateDiff: durableStateDiffSchema,
14517
14898
  agentOutcome: agentOutcomeSchema,
14518
- turnStatus: z9.enum(["applied", "no_change", "capped", "needs_input", "failed", "cancelled"]),
14519
- startedAt: z9.string().datetime(),
14520
- completedAt: z9.string().datetime()
14899
+ // #781 — the coordinator's unmet-demand classification for the whole turn. Optional and
14900
+ // deliberately loose here: it is analytics, so a malformed value must never fail a finalize that
14901
+ // settles real money. `parseAgentTurnDemand` closes it against the vocabulary downstream, and
14902
+ // anything it rejects becomes `not_measured` rather than a 400.
14903
+ agentDemand: z10.unknown().optional(),
14904
+ turnStatus: z10.enum(["applied", "no_change", "capped", "needs_input", "failed", "cancelled"]),
14905
+ startedAt: z10.string().datetime(),
14906
+ completedAt: z10.string().datetime()
14521
14907
  }).superRefine((value, context) => {
14522
14908
  const passGenerationIds = value.passes.map((pass) => pass.generationId);
14523
14909
  if (new Set(value.generationIds).size !== value.generationIds.length) {
@@ -14550,23 +14936,23 @@ var init_refineProjectContracts = __esm({
14550
14936
  });
14551
14937
  }
14552
14938
  });
14553
- revertSchema = z9.object({
14554
- intent: z9.literal("revert"),
14555
- projectId: z9.string().uuid(),
14939
+ revertSchema = z10.object({
14940
+ intent: z10.literal("revert"),
14941
+ projectId: z10.string().uuid(),
14556
14942
  /** The committed turn being undone. Becomes the new receipt's `parent_turn_id`. */
14557
- turnId: z9.string().uuid(),
14943
+ turnId: z10.string().uuid(),
14558
14944
  /** The revert's own turn id, minted by the browser exactly as a turn id is. */
14559
- revertTurnId: z9.string().uuid()
14945
+ revertTurnId: z10.string().uuid()
14560
14946
  });
14561
- finalizeSchema = z9.union([revertSchema, durableFinalizeSchema, legacyFinalizeSchema]);
14562
- createSessionSchema = z9.object({
14563
- projectId: z9.string().uuid(),
14564
- sessionId: z9.string().uuid(),
14565
- firstInstruction: z9.string().trim().min(1).max(MAX_REFINE_PROMPT_LENGTH).optional()
14947
+ finalizeSchema = z10.union([revertSchema, durableFinalizeSchema, legacyFinalizeSchema]);
14948
+ createSessionSchema = z10.object({
14949
+ projectId: z10.string().uuid(),
14950
+ sessionId: z10.string().uuid(),
14951
+ firstInstruction: z10.string().trim().min(1).max(MAX_REFINE_PROMPT_LENGTH).optional()
14566
14952
  });
14567
- historySchema = z9.object({
14568
- projectId: z9.string().uuid(),
14569
- sessionId: z9.string().uuid().optional()
14953
+ historySchema = z10.object({
14954
+ projectId: z10.string().uuid(),
14955
+ sessionId: z10.string().uuid().optional()
14570
14956
  });
14571
14957
  REFINE_FIELD_LABELS = {
14572
14958
  projectId: "the project id",
@@ -14728,47 +15114,55 @@ async function renderedTurn(deps, state, screens) {
14728
15114
  if (!rendered.ok) throw new Error("The project could not be rendered for Agent inspection.");
14729
15115
  const bytesByShotId = new Map(screens.screenManifest.map((entry) => [entry.shotId, entry.bytes]));
14730
15116
  const baseCaptions = resolveCaptionText(state.captionText, state.baseLocale, state.baseLocale);
14731
- const retained = await Promise.all(state.panels.flatMap((panel, frameIndex) => {
15117
+ const hasPixels = (shot) => bytesByShotId.has(shot.id);
15118
+ const visionScreens = await Promise.all(state.panels.flatMap((panel) => {
14732
15119
  const devices = state.shots.filter((shot) => shot.panelId === panel.id);
14733
- const representative = devices.find((shot) => bytesByShotId.has(shot.id));
15120
+ const representative = devices.find(hasPixels);
14734
15121
  if (!representative) return [];
14735
15122
  const bytes = bytesByShotId.get(representative.id);
14736
15123
  const captions = baseCaptions[panel.id] ?? [];
14737
15124
  const background2 = aiBackgroundFromPanel(state.panelBackgrounds[panel.id]);
14738
15125
  return [compactVisionImage(bytes, representative.frameName).then((vision) => ({
14739
- screen: {
14740
- ...vision,
14741
- panelId: panel.id,
14742
- caption: captions[0]?.text ?? "",
14743
- frame: aiShotPatchFromLook(representative.look),
14744
- captionStyle: aiCaptionStyleFromLayer(captions[0]),
14745
- background: background2
14746
- },
14747
- structure: {
14748
- id: panel.id,
14749
- label: `Frame ${frameIndex + 1}`,
14750
- sourceName: devices[0]?.frameName ?? null,
14751
- devices: devices.map((shot, deviceIndex) => ({
14752
- id: shot.id,
14753
- label: devices.length === 1 ? shot.frameName : `${shot.frameName} \xB7 Device ${deviceIndex + 1}`,
14754
- look: aiShotPatchFromLook(shot.look)
14755
- })),
14756
- captions: captions.map((caption, index) => ({
14757
- index,
14758
- label: caption.text.trim() || `Caption ${index + 1}`,
14759
- text: caption.text,
14760
- style: aiCaptionStyleFromLayer(caption)
14761
- })),
14762
- background: background2
14763
- }
15126
+ ...vision,
15127
+ panelId: panel.id,
15128
+ caption: captions[0]?.text ?? "",
15129
+ frame: aiShotPatchFromLook(representative.look),
15130
+ captionStyle: aiCaptionStyleFromLayer(captions[0]),
15131
+ background: background2
14764
15132
  }))];
14765
15133
  }));
14766
- if (retained.length === 0) throw new Error("The project has no readable screenshots for Agent inspection.");
15134
+ let nextScreenshot = 0;
15135
+ const structure = state.panels.map((panel, frameIndex) => {
15136
+ const devices = state.shots.filter((shot) => shot.panelId === panel.id);
15137
+ const captions = baseCaptions[panel.id] ?? [];
15138
+ const background2 = aiBackgroundFromPanel(state.panelBackgrounds[panel.id]);
15139
+ const screenshotIndex = devices.some(hasPixels) ? nextScreenshot++ : null;
15140
+ return {
15141
+ id: panel.id,
15142
+ label: `Frame ${frameIndex + 1}`,
15143
+ sourceName: devices[0]?.frameName ?? null,
15144
+ devices: devices.map((shot, deviceIndex) => ({
15145
+ id: shot.id,
15146
+ label: devices.length === 1 ? shot.frameName : `${shot.frameName} \xB7 Device ${deviceIndex + 1}`,
15147
+ look: aiShotPatchFromLook(shot.look)
15148
+ })),
15149
+ captions: captions.map((caption, index) => ({
15150
+ index,
15151
+ label: caption.text.trim() || `Caption ${index + 1}`,
15152
+ text: caption.text,
15153
+ style: aiCaptionStyleFromLayer(caption)
15154
+ })),
15155
+ background: background2,
15156
+ screenshotIndex
15157
+ };
15158
+ });
15159
+ if (visionScreens.length === 0) throw new Error("The project has no readable screenshots for Agent inspection.");
15160
+ const layoutFindings = rendered.layout?.findings ?? (rendered.layoutMeasured ? [] : void 0);
14767
15161
  return {
14768
- screens: retained.map((item) => item.screen),
14769
- structure: retained.map((item) => item.structure),
15162
+ screens: visionScreens,
15163
+ structure,
14770
15164
  ...rendered.stripBase64 ? { composedPreview: await compactVisionImage(rendered.stripBase64, "shotops-composed-preview.png") } : {},
14771
- ...rendered.layout?.findings ? { layoutFindings: rendered.layout.findings } : {}
15165
+ ...layoutFindings !== void 0 ? { layoutFindings } : {}
14772
15166
  };
14773
15167
  }
14774
15168
  function outsideFocus(patch, focus, rendered) {
@@ -14841,11 +15235,14 @@ function requestForPass(context, input) {
14841
15235
  // planner pass carries about a supplied screenshot has to be meaningless outside this turn.
14842
15236
  ...context.assets.length > 0 ? { assets: context.assets.map((asset) => ({ ...asset })) } : {},
14843
15237
  ...input.rendered.composedPreview ? { composedPreview: input.rendered.composedPreview } : {},
14844
- ...input.rendered.layoutFindings ? { layoutFindings: input.rendered.layoutFindings } : {},
15238
+ // #827 — `!== undefined`, not truthiness: a measured-clean render's `layoutFindings` is `[]`,
15239
+ // which must still reach the planner (it is what says "measured, nothing wrong").
15240
+ ...input.rendered.layoutFindings !== void 0 ? { layoutFindings: input.rendered.layoutFindings } : {},
14845
15241
  ...input.previous ? {
14846
15242
  previous: {
14847
15243
  ...input.previous,
14848
- actualDiff: [...input.previous.actualDiff]
15244
+ actualDiff: [...input.previous.actualDiff],
15245
+ ...input.previous.appliedOperations ? { appliedOperations: [...input.previous.appliedOperations] } : {}
14849
15246
  }
14850
15247
  } : {}
14851
15248
  });
@@ -14866,7 +15263,9 @@ function generatedAuthorization(request, generated, generationId, panelIds, rend
14866
15263
  ...generated.followUp ? { followUp: generated.followUp } : {},
14867
15264
  ...patch ? { patch, broaderChanges: outsideFocus(patch, request.focus ?? contextFocus(), rendered) } : {},
14868
15265
  ...generated.outcome ? { terminalOutcome: generated.outcome } : {},
14869
- ...generated.gaps ? { gaps: generated.gaps } : {}
15266
+ ...generated.gaps ? { gaps: generated.gaps } : {},
15267
+ // #781 — the same intent that decided the outcome, reported as this pass's unmet demand.
15268
+ ...generated.demand ? { demand: generated.demand } : {}
14870
15269
  };
14871
15270
  }
14872
15271
  function contextFocus() {
@@ -14877,7 +15276,7 @@ function applyAllowance(allowance, pass) {
14877
15276
  }
14878
15277
  async function authorizePass(context, request, panelIds, rendered) {
14879
15278
  context.screenshotCount = request.screenshots.length + (request.composedPreview ? 1 : 0);
14880
- const result = await context.runtime.plannerPass(context.userId, request, context.turnKey);
15279
+ const result = await context.runtime.plannerPass(context.userId, request, context.turnKey, context.assetTable);
14881
15280
  applyAllowance(context.allowance, result.allowance);
14882
15281
  if (result.ok) {
14883
15282
  return generatedAuthorization(request, result.plan, result.generationId, panelIds, rendered);
@@ -14934,7 +15333,8 @@ async function runMcpProjectAgentTurn(deps, input, runtime) {
14934
15333
  focus: input.focus,
14935
15334
  allowance,
14936
15335
  runtime,
14937
- assets: assetTable.descriptors
15336
+ assets: assetTable.descriptors,
15337
+ assetTable
14938
15338
  };
14939
15339
  const result = await runAgentTurn({
14940
15340
  turnId,
@@ -14971,6 +15371,7 @@ async function runMcpProjectAgentTurn(deps, input, runtime) {
14971
15371
  }
14972
15372
  return renderedTurn(deps, state, resolved);
14973
15373
  },
15374
+ unresolvedLayout: (rendered) => describeUnresolvedLayout(rendered.layoutFindings),
14974
15375
  authorize: async ({ cycle, state, rendered, previous }) => {
14975
15376
  const request = requestForPass(context, { cycle, state, rendered, previous });
14976
15377
  const authorization = await authorizePass(
@@ -15023,7 +15424,8 @@ async function runMcpProjectAgentTurn(deps, input, runtime) {
15023
15424
  startedAt,
15024
15425
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
15025
15426
  screenshotCount: context.screenshotCount,
15026
- instructionLength: input.instruction.length
15427
+ instructionLength: input.instruction.length,
15428
+ demand: event.result.demand
15027
15429
  }
15028
15430
  });
15029
15431
  allowance.remaining = settlement.balanceCredits;
@@ -15051,7 +15453,8 @@ async function runMcpProjectAgentTurn(deps, input, runtime) {
15051
15453
  startedAt,
15052
15454
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
15053
15455
  screenshotCount: context.screenshotCount,
15054
- instructionLength: input.instruction.length
15456
+ instructionLength: input.instruction.length,
15457
+ demand: event.result.demand
15055
15458
  });
15056
15459
  } catch (error) {
15057
15460
  runtime.log("mcp_agent_turn_receipt_failed", { turnKey, ...safeFailureDiagnostic(error) });
@@ -15070,6 +15473,7 @@ var init_agentTurnAdapter = __esm({
15070
15473
  init_agentOutcome();
15071
15474
  init_projectRecord();
15072
15475
  init_gradient();
15476
+ init_layoutFindings();
15073
15477
  init_capabilities();
15074
15478
  init_invalidRequest();
15075
15479
  init_refineProjectContracts();
@@ -15102,7 +15506,16 @@ function writeErrorFrom(error) {
15102
15506
  }
15103
15507
  function localAgentTurnRuntime(bridge) {
15104
15508
  return {
15105
- plannerPass: (_userId, request, turnKey) => bridge.refinePass(request.projectId, request, turnKey),
15509
+ plannerPass: (_userId, request, turnKey, assetTable) => bridge.refinePass(
15510
+ request.projectId,
15511
+ request,
15512
+ turnKey,
15513
+ void 0,
15514
+ assetTable.descriptors.map(({ id }) => {
15515
+ const { ref, label, width, height } = assetTable.sources[id];
15516
+ return { ref, label, ...width ? { width } : {}, ...height ? { height } : {} };
15517
+ })
15518
+ ),
15106
15519
  // The receipt facts travel WITH settlement (#580): the server writes the one receipt for this
15107
15520
  // turn, keyed by the id the local coordinator ran under, and this machine — which holds no
15108
15521
  // Supabase credential — writes nothing durable at all.
@@ -15122,7 +15535,8 @@ function localAgentTurnRuntime(bridge) {
15122
15535
  startedAt: input.startedAt,
15123
15536
  completedAt: input.completedAt,
15124
15537
  screenshotCount: input.screenshotCount ?? 0,
15125
- instructionLength: input.instructionLength ?? 0
15538
+ instructionLength: input.instructionLength ?? 0,
15539
+ demand: input.demand ?? NOT_MEASURED_DEMAND
15126
15540
  }
15127
15541
  });
15128
15542
  },
@@ -15138,7 +15552,7 @@ function localAgentTurnRuntime(bridge) {
15138
15552
  log: (event, fields2) => logEvent("error", event, fields2)
15139
15553
  };
15140
15554
  }
15141
- async function runLocalRefineTurn(deps, bridge, input) {
15555
+ async function runLocalRefineTurn(deps, bridge, input, runtimeOverrides) {
15142
15556
  try {
15143
15557
  const turn = await runMcpProjectAgentTurn(
15144
15558
  deps,
@@ -15148,7 +15562,7 @@ async function runLocalRefineTurn(deps, bridge, input) {
15148
15562
  ...input.project ? { project: input.project } : {},
15149
15563
  ...input.attachments?.length ? { attachments: input.attachments } : {}
15150
15564
  },
15151
- localAgentTurnRuntime(bridge)
15565
+ runtimeOverrides ? { ...localAgentTurnRuntime(bridge), ...runtimeOverrides } : localAgentTurnRuntime(bridge)
15152
15566
  );
15153
15567
  return refineProjectResult(turn);
15154
15568
  } catch {
@@ -15159,6 +15573,7 @@ var init_localRefineTurn = __esm({
15159
15573
  "src/project/localRefineTurn.ts"() {
15160
15574
  "use strict";
15161
15575
  init_log();
15576
+ init_agentUnmetActions();
15162
15577
  init_agentTurnAdapter();
15163
15578
  init_hostedBridge();
15164
15579
  init_refineProjectReceipt();
@@ -15307,7 +15722,7 @@ var init_projectClaimClient = __esm({
15307
15722
 
15308
15723
  // src/runtime/localDeps.ts
15309
15724
  import { readFile as readFile4 } from "node:fs/promises";
15310
- function localToolDeps(token2) {
15725
+ function localToolDeps(token2, options = {}) {
15311
15726
  const base = {
15312
15727
  renderer: new StripRenderer({ serve: "static" }),
15313
15728
  // Token-backed cloud writes cross the same authority in the control plane. This local handler
@@ -15372,7 +15787,7 @@ function localToolDeps(token2) {
15372
15787
  }
15373
15788
  };
15374
15789
  }
15375
- const bridge = connectHostedBridge(token2);
15790
+ const bridge = options.decorateBridge ? options.decorateBridge(connectHostedBridge(token2)) : connectHostedBridge(token2);
15376
15791
  const bridged = {
15377
15792
  ...base,
15378
15793
  resolveProject: (userId, projectId) => bridge.resolveProject(userId, projectId),
@@ -15413,7 +15828,7 @@ function localToolDeps(token2) {
15413
15828
  await bridge.close();
15414
15829
  }
15415
15830
  };
15416
- bridged.refineProject = (userId, input) => runLocalRefineTurn({ ...bridged, userId }, bridge, input);
15831
+ bridged.refineProject = (userId, input) => runLocalRefineTurn({ ...bridged, userId }, bridge, input, options.emit ? { emit: options.emit } : void 0);
15417
15832
  return bridged;
15418
15833
  }
15419
15834
  async function pushScreens(bridge, projectId, record8, recordVersion, screenshots2) {