@pixerate/editor 0.141.0 → 0.143.0

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.
Files changed (75) hide show
  1. package/dist/agent/index.cjs +107 -48
  2. package/dist/agent/index.js +76 -17
  3. package/dist/agent/planner.d.cts +7 -2
  4. package/dist/agent/planner.d.ts +7 -2
  5. package/dist/agent/providers.d.cts +4 -1
  6. package/dist/agent/providers.d.ts +4 -1
  7. package/dist/animation-editor/index.cjs +69 -19
  8. package/dist/animation-editor/index.d.cts +1 -0
  9. package/dist/animation-editor/index.d.ts +1 -0
  10. package/dist/animation-editor/index.js +54 -4
  11. package/dist/animation-editor/playback.d.cts +10 -0
  12. package/dist/animation-editor/playback.d.ts +10 -0
  13. package/dist/canvas/index.cjs +103 -101
  14. package/dist/canvas/index.js +3 -1
  15. package/dist/canvas/layout.d.cts +15 -2
  16. package/dist/canvas/layout.d.ts +15 -2
  17. package/dist/chart/index.cjs +254 -170
  18. package/dist/chart/index.d.cts +1 -0
  19. package/dist/chart/index.d.ts +1 -0
  20. package/dist/chart/index.js +243 -159
  21. package/dist/chart/playback.d.cts +22 -0
  22. package/dist/chart/playback.d.ts +22 -0
  23. package/dist/{chunk-MFFE55CS.js → chunk-54GVEVXS.js} +6 -4
  24. package/dist/{chunk-E747F3D5.js → chunk-CC6WKVIH.js} +86 -5
  25. package/dist/{chunk-UBRYHNRU.cjs → chunk-EL4PSKFT.cjs} +46 -4
  26. package/dist/{chunk-VTY64I6H.cjs → chunk-QSM3MDDJ.cjs} +85 -85
  27. package/dist/{chunk-2RUNJYOX.cjs → chunk-QZFEX2N4.cjs} +86 -5
  28. package/dist/{chunk-DW4CV5H4.js → chunk-U4VE4UI2.js} +45 -3
  29. package/dist/{chunk-LMSFXRAJ.js → chunk-VGJTUCWM.js} +1 -1
  30. package/dist/{chunk-6SFREX6H.cjs → chunk-X2HQP7DF.cjs} +6 -4
  31. package/dist/chunk-XH32BATE.cjs +68 -0
  32. package/dist/chunk-Y6SGC6KO.js +68 -0
  33. package/dist/commands/index.cjs +2 -2
  34. package/dist/commands/index.js +1 -1
  35. package/dist/index.cjs +7 -3
  36. package/dist/index.js +6 -2
  37. package/dist/paint-editor/index.cjs +67 -19
  38. package/dist/paint-editor/index.d.cts +1 -0
  39. package/dist/paint-editor/index.d.ts +1 -0
  40. package/dist/paint-editor/index.js +67 -19
  41. package/dist/paint-editor/playback.d.cts +16 -0
  42. package/dist/paint-editor/playback.d.ts +16 -0
  43. package/dist/parameters/index.cjs +32 -8
  44. package/dist/parameters/index.d.cts +1 -0
  45. package/dist/parameters/index.d.ts +1 -0
  46. package/dist/parameters/index.js +28 -4
  47. package/dist/parameters/playback.d.cts +10 -0
  48. package/dist/parameters/playback.d.ts +10 -0
  49. package/dist/spreadsheet/index.cjs +7 -3
  50. package/dist/spreadsheet/index.d.cts +1 -0
  51. package/dist/spreadsheet/index.d.ts +1 -0
  52. package/dist/spreadsheet/index.js +6 -2
  53. package/dist/spreadsheet/playback.d.cts +15 -0
  54. package/dist/spreadsheet/playback.d.ts +15 -0
  55. package/dist/tools/index.cjs +10 -3
  56. package/dist/tools/index.d.cts +1 -0
  57. package/dist/tools/index.d.ts +1 -0
  58. package/dist/tools/index.js +9 -2
  59. package/dist/tools/script.d.cts +77 -0
  60. package/dist/tools/script.d.ts +77 -0
  61. package/dist/video-editor/index.cjs +81 -28
  62. package/dist/video-editor/index.d.cts +1 -0
  63. package/dist/video-editor/index.d.ts +1 -0
  64. package/dist/video-editor/index.js +58 -5
  65. package/dist/video-editor/playback.d.cts +20 -0
  66. package/dist/video-editor/playback.d.ts +20 -0
  67. package/package.json +1 -1
  68. package/skills/pixerate-animation-editor/SKILL.md +1 -0
  69. package/skills/pixerate-chart/SKILL.md +1 -0
  70. package/skills/pixerate-paint-editor/SKILL.md +1 -0
  71. package/skills/pixerate-parameters/SKILL.md +1 -0
  72. package/skills/pixerate-spreadsheet/SKILL.md +1 -0
  73. package/skills/pixerate-video-editor/SKILL.md +1 -0
  74. package/dist/chunk-PLKIYNJZ.js +0 -0
  75. package/dist/chunk-TNCWKC5M.cjs +0 -1
@@ -25,3 +25,4 @@ export * from './stream.js';
25
25
  export * from './presence.js';
26
26
  export * from './office-files.js';
27
27
  export * from './file-tools.js';
28
+ export * from './playback.js';
@@ -925,6 +925,242 @@ function chartHitTest(scene, point, measure = (t, s, w = 400) => t.length * s *
925
925
  return point.x >= 0 && point.y >= 0 && point.x <= scene.width && point.y <= scene.height ? { kind: "chart" } : null;
926
926
  }
927
927
 
928
+ // src/chart/presence.ts
929
+ var ChartPresence = class extends PresenceStore {
930
+ };
931
+ var CHART_PRESENCE_COLORS = PRESENCE_COLORS;
932
+ var realChartClock = realPresenceClock;
933
+ var createInstantChartClock = createInstantPresenceClock;
934
+ function chartPointAnchor(scene, seriesId, index) {
935
+ const mark = scene.marks.find((m) => (m.role === "bar" || m.role === "point") && m.target?.kind === "point" && m.target.seriesId === seriesId && m.target.index === index);
936
+ if (!mark) return null;
937
+ if (mark.kind === "rect") return { x: mark.x + mark.width / 2, y: mark.y };
938
+ if (mark.kind === "circle") return { x: mark.cx, y: mark.cy };
939
+ return null;
940
+ }
941
+ var anchorFor = (scene, role) => {
942
+ const m = scene.marks.find((x) => x.role === role);
943
+ if (!m) return null;
944
+ if (m.kind === "text") return { x: m.x, y: m.y };
945
+ if (m.kind === "rect") return { x: m.x + m.width / 2, y: m.y + m.height / 2 };
946
+ if (m.kind === "line") return { x: (m.x1 + m.x2) / 2, y: (m.y1 + m.y2) / 2 };
947
+ return null;
948
+ };
949
+ function planChartCommand(spec, scene, command, speed = 1) {
950
+ const ms = (n) => Math.round(n / Math.max(0.1, speed));
951
+ const steps = [];
952
+ const note = "note" in command && command.note ? command.note : void 0;
953
+ if (note) steps.push({ type: "say", text: note, ms: ms(500) });
954
+ switch (command.kind) {
955
+ case "say":
956
+ steps.push({ type: "say", text: command.text, ms: ms(900) });
957
+ return steps;
958
+ case "setValue": {
959
+ const at = chartPointAnchor(scene, command.seriesId, command.index);
960
+ const series = spec.series.find((s) => s.id === command.seriesId);
961
+ const from = series?.values[command.index] ?? 0;
962
+ const scale = series?.axis === "secondary" ? scene.valueScale2 ?? scene.valueScale : scene.valueScale;
963
+ if (at && scale) {
964
+ const px = linearMap(command.value, scale.domain, scale.range);
965
+ const to = scale.axis === "y" ? { x: at.x, y: px } : { x: px, y: at.y };
966
+ steps.push({ type: "move", to: at, ms: ms(380) }, { type: "press" }, { type: "drag", to, ms: ms(520), from, value: command.value }, { type: "release" });
967
+ } else steps.push({ type: "drag", to: { x: scene.plot.x + scene.plot.width / 2, y: scene.plot.y + scene.plot.height / 2 }, ms: ms(200), from, value: command.value });
968
+ steps.push({ type: "commit" });
969
+ return steps;
970
+ }
971
+ case "select": {
972
+ const sel = command.selection;
973
+ let to = null;
974
+ if (sel?.kind === "point") to = chartPointAnchor(scene, sel.seriesId, sel.index);
975
+ else if (sel) {
976
+ const m = scene.marks.find((x) => x.target && JSON.stringify(x.target) === JSON.stringify(sel));
977
+ if (m?.kind === "text") to = { x: m.x, y: m.y };
978
+ else if (m?.kind === "rect") to = { x: m.x + m.width / 2, y: m.y + m.height / 2 };
979
+ else if (m?.kind === "circle") to = { x: m.cx, y: m.cy };
980
+ }
981
+ if (to) steps.push({ type: "move", to, ms: ms(380) }, { type: "press" }, { type: "release" });
982
+ steps.push({ type: "commit" });
983
+ return steps;
984
+ }
985
+ case "update": {
986
+ const keys = Object.keys(command.patch);
987
+ const role = keys.includes("title") ? "title" : keys.includes("legend") ? "legend-label" : keys.some((k) => k === "xAxis") ? "axis" : keys.some((k) => k === "yAxis" || k === "yAxis2") ? "tick-label" : null;
988
+ const to = role && anchorFor(scene, role) || { x: scene.plot.x + scene.plot.width / 2, y: scene.plot.y + 12 };
989
+ steps.push({ type: "move", to, ms: ms(380) }, { type: "press" }, { type: "release" }, { type: "commit" });
990
+ return steps;
991
+ }
992
+ }
993
+ }
994
+ var ChartDirector = class {
995
+ constructor(controller, presence, options) {
996
+ this.controller = controller;
997
+ this.presence = presence;
998
+ this.options = options;
999
+ this.clock = options.clock ?? realChartClock;
1000
+ this.queue = new PerformanceQueue(presence, {
1001
+ clock: this.clock,
1002
+ linger: options.linger,
1003
+ beginGesture: (key) => controller.beginGesture(key),
1004
+ endGesture: () => controller.endGesture()
1005
+ });
1006
+ }
1007
+ controller;
1008
+ presence;
1009
+ options;
1010
+ clock;
1011
+ queue;
1012
+ /** Acts out `commands` in order for `actor`. Resolves with one result per command. */
1013
+ perform(actor, commands) {
1014
+ return this.queue.run(actor, async () => {
1015
+ const results = [];
1016
+ for (const command of commands) results.push(await this.performOne(actor.id, command));
1017
+ return results;
1018
+ });
1019
+ }
1020
+ stale(command) {
1021
+ const spec = this.controller.getSpec();
1022
+ if (command.kind === "setValue") {
1023
+ const s = spec.series.find((x) => x.id === command.seriesId);
1024
+ if (!s) return `The series "${command.seriesId}" no longer exists.`;
1025
+ if (command.index < 0 || command.index >= spec.categories.length) return `Category ${command.index} no longer exists.`;
1026
+ if (!Number.isFinite(command.value)) return "The value must be a finite number.";
1027
+ }
1028
+ return null;
1029
+ }
1030
+ move(id, from, to, ms, each) {
1031
+ return tweenPresenceCursor(this.presence, this.clock, id, from, to, ms, each);
1032
+ }
1033
+ async performOne(id, command) {
1034
+ const stale = this.stale(command);
1035
+ if (stale) return { ok: false, command, message: stale };
1036
+ const steps = planChartCommand(this.controller.getSpec(), this.options.scene(), command, this.options.speed);
1037
+ const where = () => {
1038
+ const a = this.presence.getState().actors.find((x) => x.id === id);
1039
+ return { x: a.x, y: a.y };
1040
+ };
1041
+ {
1042
+ for (const step of steps) {
1043
+ switch (step.type) {
1044
+ case "say":
1045
+ this.presence.set({ id, message: step.text });
1046
+ await this.clock.wait(step.ms);
1047
+ break;
1048
+ case "move":
1049
+ await this.move(id, where(), step.to, step.ms);
1050
+ break;
1051
+ case "press":
1052
+ this.presence.set({ id, pressed: true });
1053
+ break;
1054
+ case "release":
1055
+ this.presence.set({ id, pressed: false });
1056
+ break;
1057
+ case "drag": {
1058
+ if (command.kind !== "setValue") break;
1059
+ const stale2 = this.stale(command);
1060
+ if (stale2) return { ok: false, command, message: stale2 };
1061
+ const label = `${this.presence.getState().actors.find((a) => a.id === id)?.name ?? "Agent"}: edit value`;
1062
+ await this.move(id, where(), step.to, step.ms, (e) => {
1063
+ const v = step.from + (step.value - step.from) * e;
1064
+ this.controller.setValue(command.seriesId, command.index, Number(v.toPrecision(12)), { label });
1065
+ });
1066
+ this.controller.setValue(command.seriesId, command.index, step.value, { label });
1067
+ break;
1068
+ }
1069
+ case "commit": {
1070
+ if (command.kind === "select") this.controller.select(command.selection);
1071
+ else if (command.kind === "update") {
1072
+ const result = this.controller.update(command.patch, command.label ? { label: command.label } : {});
1073
+ if (!result.ok) return { ok: false, command, message: result.message };
1074
+ }
1075
+ break;
1076
+ }
1077
+ }
1078
+ }
1079
+ return { ok: true, command };
1080
+ }
1081
+ }
1082
+ };
1083
+
1084
+ // src/chart/playback.ts
1085
+ var click = (ref) => [{ type: "target", ref }, { type: "press" }, { type: "release" }];
1086
+ var control = (id) => ({ kind: "control", id });
1087
+ function chartPointRef(series, category) {
1088
+ return { kind: "point", id: JSON.stringify([series, category]) };
1089
+ }
1090
+ function locateChartPoint(spec, scene, ref) {
1091
+ if (ref.kind !== "point") return null;
1092
+ let parsed;
1093
+ try {
1094
+ parsed = JSON.parse(ref.id);
1095
+ } catch {
1096
+ return null;
1097
+ }
1098
+ if (!Array.isArray(parsed)) return null;
1099
+ const [seriesRef, categoryRef] = parsed;
1100
+ const series = spec.series.find((s) => s.id === seriesRef) ?? spec.series.find((s) => s.name === seriesRef);
1101
+ const index = typeof categoryRef === "number" ? categoryRef : spec.categories.findIndex((c) => String(c) === String(categoryRef));
1102
+ if (!series || index < 0) return null;
1103
+ const at = chartPointAnchor(scene, series.id, index);
1104
+ return at ? { x: at.x - 6, y: at.y - 6, width: 12, height: 12 } : null;
1105
+ }
1106
+ function planChartToolPlayback(tool, args) {
1107
+ switch (tool) {
1108
+ case "update_chart": {
1109
+ const steps = [];
1110
+ for (const key of Object.keys(args).slice(0, 4)) {
1111
+ steps.push(...click(key === "type" && typeof args.type === "string" ? { kind: "chart-type", id: args.type } : control(key)));
1112
+ }
1113
+ return steps.length ? steps : null;
1114
+ }
1115
+ case "apply_chart_preset":
1116
+ return [...args.layout ? click(control("preset.layout")) : [], ...args.colors ? click(control("preset.colors")) : []];
1117
+ case "set_chart_values": {
1118
+ const cells = Array.isArray(args.cells) ? args.cells : [];
1119
+ const steps = cells.slice(0, 6).filter((c) => typeof c.series === "string" && (typeof c.category === "string" || typeof c.category === "number")).flatMap((c) => click(chartPointRef(c.series, c.category)));
1120
+ return steps.length ? steps : null;
1121
+ }
1122
+ case "select_chart_element":
1123
+ if (args.kind === "point" && typeof args.series === "string" && args.category !== void 0) return click(chartPointRef(args.series, args.category));
1124
+ return typeof args.kind === "string" && args.kind !== "none" ? click(control(args.kind)) : null;
1125
+ case "set_chart_data":
1126
+ case "import_chart_csv":
1127
+ case "import_chart_mermaid":
1128
+ case "import_chart_ooxml":
1129
+ case "add_chart_series":
1130
+ case "update_chart_series":
1131
+ case "remove_chart_series":
1132
+ case "add_chart_category":
1133
+ case "remove_chart_category":
1134
+ case "transpose_chart":
1135
+ return [{ type: "target", ref: { kind: "grid", id: "data" } }];
1136
+ case "add_chart_trendline":
1137
+ case "remove_chart_trendline":
1138
+ return click(control("trendline.add"));
1139
+ case "render_chart":
1140
+ return [{ type: "target", ref: { kind: "action", id: "download-png" } }];
1141
+ case "undo_chart":
1142
+ return click({ kind: "action", id: "undo" });
1143
+ case "redo_chart":
1144
+ return click({ kind: "action", id: "redo" });
1145
+ default:
1146
+ return null;
1147
+ }
1148
+ }
1149
+ var DIRECTED = /* @__PURE__ */ new Set(["set_chart_values", "select_chart_element"]);
1150
+ function withChartPlayback(provider, options) {
1151
+ const plan = (tool) => {
1152
+ if (tool.plan || tool.playback !== void 0) return tool;
1153
+ if (options.director && DIRECTED.has(tool.name)) return { ...tool, playback: "self" };
1154
+ return { ...tool, plan: (args) => planChartToolPlayback(tool.name, args) };
1155
+ };
1156
+ const tools = provider.tools;
1157
+ return {
1158
+ ...provider,
1159
+ playbackSurface: provider.playbackSurface ?? options.surface ?? "chart",
1160
+ tools: typeof tools === "function" ? () => tools().map(plan) : tools.map(plan)
1161
+ };
1162
+ }
1163
+
928
1164
  // src/chart/tools.ts
929
1165
  var RENDER_CHART_INSTRUCTIONS = 'Draws the chart without changing it so you can check how it looks: SVG text by default, or a PNG image with format "png" when the host can rasterize. Returns the layout warnings (hidden or thinned labels, dropped markers) alongside. An image costs tokens, so render after a batch of edits rather than after each one, and use it to check what text cannot tell you: overlapping or cut-off labels, a crowded legend, unreadable colours, whether the chart reads at the given size. For values, ids and warnings alone, get_chart is cheaper.';
930
1166
  var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
@@ -1488,7 +1724,7 @@ function createChartToolProvider(controller, options = {}) {
1488
1724
  };
1489
1725
  const reads = [getChart, renderChart, checkColors, recommend, getTrendline, listPresets, exportMermaid, exportOoxml];
1490
1726
  const writes = [updateChart, setData, setValues, addSeries, updateSeries, removeSeries, addCategory, removeCategory, importCsv, importMermaid, addTrendline, removeTrendline, applyPreset, transpose, select, drag, key, undo, redo];
1491
- return {
1727
+ return withChartPlayback({
1492
1728
  id: options.id ?? "chart",
1493
1729
  namespace: options.namespace,
1494
1730
  title: "Chart editor",
@@ -1508,7 +1744,7 @@ function createChartToolProvider(controller, options = {}) {
1508
1744
  }
1509
1745
  });
1510
1746
  }
1511
- };
1747
+ }, { director: !!options.director });
1512
1748
  }
1513
1749
 
1514
1750
  // src/chart/stream.ts
@@ -1643,162 +1879,6 @@ function blendChartScenes(from, to, t) {
1643
1879
  }
1644
1880
  var easeChartTween = (t) => 1 - Math.pow(1 - Math.max(0, Math.min(1, t)), 3);
1645
1881
 
1646
- // src/chart/presence.ts
1647
- var ChartPresence = class extends PresenceStore {
1648
- };
1649
- var CHART_PRESENCE_COLORS = PRESENCE_COLORS;
1650
- var realChartClock = realPresenceClock;
1651
- var createInstantChartClock = createInstantPresenceClock;
1652
- function chartPointAnchor(scene, seriesId, index) {
1653
- const mark = scene.marks.find((m) => (m.role === "bar" || m.role === "point") && m.target?.kind === "point" && m.target.seriesId === seriesId && m.target.index === index);
1654
- if (!mark) return null;
1655
- if (mark.kind === "rect") return { x: mark.x + mark.width / 2, y: mark.y };
1656
- if (mark.kind === "circle") return { x: mark.cx, y: mark.cy };
1657
- return null;
1658
- }
1659
- var anchorFor = (scene, role) => {
1660
- const m = scene.marks.find((x) => x.role === role);
1661
- if (!m) return null;
1662
- if (m.kind === "text") return { x: m.x, y: m.y };
1663
- if (m.kind === "rect") return { x: m.x + m.width / 2, y: m.y + m.height / 2 };
1664
- if (m.kind === "line") return { x: (m.x1 + m.x2) / 2, y: (m.y1 + m.y2) / 2 };
1665
- return null;
1666
- };
1667
- function planChartCommand(spec, scene, command, speed = 1) {
1668
- const ms = (n) => Math.round(n / Math.max(0.1, speed));
1669
- const steps = [];
1670
- const note = "note" in command && command.note ? command.note : void 0;
1671
- if (note) steps.push({ type: "say", text: note, ms: ms(500) });
1672
- switch (command.kind) {
1673
- case "say":
1674
- steps.push({ type: "say", text: command.text, ms: ms(900) });
1675
- return steps;
1676
- case "setValue": {
1677
- const at = chartPointAnchor(scene, command.seriesId, command.index);
1678
- const series = spec.series.find((s) => s.id === command.seriesId);
1679
- const from = series?.values[command.index] ?? 0;
1680
- const scale = series?.axis === "secondary" ? scene.valueScale2 ?? scene.valueScale : scene.valueScale;
1681
- if (at && scale) {
1682
- const px = linearMap(command.value, scale.domain, scale.range);
1683
- const to = scale.axis === "y" ? { x: at.x, y: px } : { x: px, y: at.y };
1684
- steps.push({ type: "move", to: at, ms: ms(380) }, { type: "press" }, { type: "drag", to, ms: ms(520), from, value: command.value }, { type: "release" });
1685
- } else steps.push({ type: "drag", to: { x: scene.plot.x + scene.plot.width / 2, y: scene.plot.y + scene.plot.height / 2 }, ms: ms(200), from, value: command.value });
1686
- steps.push({ type: "commit" });
1687
- return steps;
1688
- }
1689
- case "select": {
1690
- const sel = command.selection;
1691
- let to = null;
1692
- if (sel?.kind === "point") to = chartPointAnchor(scene, sel.seriesId, sel.index);
1693
- else if (sel) {
1694
- const m = scene.marks.find((x) => x.target && JSON.stringify(x.target) === JSON.stringify(sel));
1695
- if (m?.kind === "text") to = { x: m.x, y: m.y };
1696
- else if (m?.kind === "rect") to = { x: m.x + m.width / 2, y: m.y + m.height / 2 };
1697
- else if (m?.kind === "circle") to = { x: m.cx, y: m.cy };
1698
- }
1699
- if (to) steps.push({ type: "move", to, ms: ms(380) }, { type: "press" }, { type: "release" });
1700
- steps.push({ type: "commit" });
1701
- return steps;
1702
- }
1703
- case "update": {
1704
- const keys = Object.keys(command.patch);
1705
- const role = keys.includes("title") ? "title" : keys.includes("legend") ? "legend-label" : keys.some((k) => k === "xAxis") ? "axis" : keys.some((k) => k === "yAxis" || k === "yAxis2") ? "tick-label" : null;
1706
- const to = role && anchorFor(scene, role) || { x: scene.plot.x + scene.plot.width / 2, y: scene.plot.y + 12 };
1707
- steps.push({ type: "move", to, ms: ms(380) }, { type: "press" }, { type: "release" }, { type: "commit" });
1708
- return steps;
1709
- }
1710
- }
1711
- }
1712
- var ChartDirector = class {
1713
- constructor(controller, presence, options) {
1714
- this.controller = controller;
1715
- this.presence = presence;
1716
- this.options = options;
1717
- this.clock = options.clock ?? realChartClock;
1718
- this.queue = new PerformanceQueue(presence, {
1719
- clock: this.clock,
1720
- linger: options.linger,
1721
- beginGesture: (key) => controller.beginGesture(key),
1722
- endGesture: () => controller.endGesture()
1723
- });
1724
- }
1725
- controller;
1726
- presence;
1727
- options;
1728
- clock;
1729
- queue;
1730
- /** Acts out `commands` in order for `actor`. Resolves with one result per command. */
1731
- perform(actor, commands) {
1732
- return this.queue.run(actor, async () => {
1733
- const results = [];
1734
- for (const command of commands) results.push(await this.performOne(actor.id, command));
1735
- return results;
1736
- });
1737
- }
1738
- stale(command) {
1739
- const spec = this.controller.getSpec();
1740
- if (command.kind === "setValue") {
1741
- const s = spec.series.find((x) => x.id === command.seriesId);
1742
- if (!s) return `The series "${command.seriesId}" no longer exists.`;
1743
- if (command.index < 0 || command.index >= spec.categories.length) return `Category ${command.index} no longer exists.`;
1744
- if (!Number.isFinite(command.value)) return "The value must be a finite number.";
1745
- }
1746
- return null;
1747
- }
1748
- move(id, from, to, ms, each) {
1749
- return tweenPresenceCursor(this.presence, this.clock, id, from, to, ms, each);
1750
- }
1751
- async performOne(id, command) {
1752
- const stale = this.stale(command);
1753
- if (stale) return { ok: false, command, message: stale };
1754
- const steps = planChartCommand(this.controller.getSpec(), this.options.scene(), command, this.options.speed);
1755
- const where = () => {
1756
- const a = this.presence.getState().actors.find((x) => x.id === id);
1757
- return { x: a.x, y: a.y };
1758
- };
1759
- {
1760
- for (const step of steps) {
1761
- switch (step.type) {
1762
- case "say":
1763
- this.presence.set({ id, message: step.text });
1764
- await this.clock.wait(step.ms);
1765
- break;
1766
- case "move":
1767
- await this.move(id, where(), step.to, step.ms);
1768
- break;
1769
- case "press":
1770
- this.presence.set({ id, pressed: true });
1771
- break;
1772
- case "release":
1773
- this.presence.set({ id, pressed: false });
1774
- break;
1775
- case "drag": {
1776
- if (command.kind !== "setValue") break;
1777
- const stale2 = this.stale(command);
1778
- if (stale2) return { ok: false, command, message: stale2 };
1779
- const label = `${this.presence.getState().actors.find((a) => a.id === id)?.name ?? "Agent"}: edit value`;
1780
- await this.move(id, where(), step.to, step.ms, (e) => {
1781
- const v = step.from + (step.value - step.from) * e;
1782
- this.controller.setValue(command.seriesId, command.index, Number(v.toPrecision(12)), { label });
1783
- });
1784
- this.controller.setValue(command.seriesId, command.index, step.value, { label });
1785
- break;
1786
- }
1787
- case "commit": {
1788
- if (command.kind === "select") this.controller.select(command.selection);
1789
- else if (command.kind === "update") {
1790
- const result = this.controller.update(command.patch, command.label ? { label: command.label } : {});
1791
- if (!result.ok) return { ok: false, command, message: result.message };
1792
- }
1793
- break;
1794
- }
1795
- }
1796
- }
1797
- return { ok: true, command };
1798
- }
1799
- }
1800
- };
1801
-
1802
1882
  // src/chart/office-files.ts
1803
1883
  var OfficeChartsError = class extends Error {
1804
1884
  constructor(message, options) {
@@ -2168,6 +2248,7 @@ export {
2168
2248
  chartMovingAverage,
2169
2249
  chartPointAnchor,
2170
2250
  chartPointColor,
2251
+ chartPointRef,
2171
2252
  chartPresetPatch,
2172
2253
  chartQuantile,
2173
2254
  chartRasterSize,
@@ -2231,6 +2312,7 @@ export {
2231
2312
  layoutChart,
2232
2313
  layoutSparkline,
2233
2314
  linearMap,
2315
+ locateChartPoint,
2234
2316
  monochromeChartPalette,
2235
2317
  niceLogScale,
2236
2318
  niceScale,
@@ -2246,6 +2328,7 @@ export {
2246
2328
  parseChartValue,
2247
2329
  parsePartialChartJson,
2248
2330
  planChartCommand,
2331
+ planChartToolPlayback,
2249
2332
  rasterizeChartSvg,
2250
2333
  realChartClock,
2251
2334
  recommendChartTypes,
@@ -2262,5 +2345,6 @@ export {
2262
2345
  summarizeChart,
2263
2346
  summarizeChartSeries,
2264
2347
  trendNumberText,
2265
- uniqueChartSeriesId
2348
+ uniqueChartSeriesId,
2349
+ withChartPlayback
2266
2350
  };
@@ -0,0 +1,22 @@
1
+ import type { AgentToolProvider } from '../tools/registry.cjs';
2
+ import type { PlaybackRef, PlaybackStep } from '../tools/playback.cjs';
3
+ import type { ChartScene, ChartSpec } from './types.cjs';
4
+ /** A point ref: a series (id or name) and a category (index or label), as the tools take them. */
5
+ export declare function chartPointRef(series: string, category: string | number): PlaybackRef;
6
+ /** Where a point ref is drawn in `scene` (a small box around its anchor), or null. */
7
+ export declare function locateChartPoint(spec: ChartSpec, scene: ChartScene, ref: PlaybackRef): {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ } | null;
13
+ /** The cursor steps for one chart tool call (by bare name), or null to fall back to the ids it names. */
14
+ export declare function planChartToolPlayback(tool: string, args: Record<string, any>): PlaybackStep[] | null;
15
+ /**
16
+ * Gives a chart tool provider agent playback on `surface` (default `chart`, where `ChartEditor`
17
+ * attaches). With a director, value changes and selections play through it (`playback: 'self'`).
18
+ */
19
+ export declare function withChartPlayback(provider: AgentToolProvider, options: {
20
+ director: boolean;
21
+ surface?: string;
22
+ }): AgentToolProvider;
@@ -0,0 +1,22 @@
1
+ import type { AgentToolProvider } from '../tools/registry.js';
2
+ import type { PlaybackRef, PlaybackStep } from '../tools/playback.js';
3
+ import type { ChartScene, ChartSpec } from './types.js';
4
+ /** A point ref: a series (id or name) and a category (index or label), as the tools take them. */
5
+ export declare function chartPointRef(series: string, category: string | number): PlaybackRef;
6
+ /** Where a point ref is drawn in `scene` (a small box around its anchor), or null. */
7
+ export declare function locateChartPoint(spec: ChartSpec, scene: ChartScene, ref: PlaybackRef): {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ } | null;
13
+ /** The cursor steps for one chart tool call (by bare name), or null to fall back to the ids it names. */
14
+ export declare function planChartToolPlayback(tool: string, args: Record<string, any>): PlaybackStep[] | null;
15
+ /**
16
+ * Gives a chart tool provider agent playback on `surface` (default `chart`, where `ChartEditor`
17
+ * attaches). With a director, value changes and selections play through it (`playback: 'self'`).
18
+ */
19
+ export declare function withChartPlayback(provider: AgentToolProvider, options: {
20
+ director: boolean;
21
+ surface?: string;
22
+ }): AgentToolProvider;
@@ -87,6 +87,7 @@ function normalizePlan(plan) {
87
87
  }
88
88
  var isErrorResult = (value) => !!value && typeof value === "object" && value.isError === true;
89
89
  var center = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
90
+ var finiteRect = (r) => r && [r.x, r.y, r.width, r.height].every(Number.isFinite) ? r : null;
90
91
  var GLANCE_STEPS = /* @__PURE__ */ new Set(["move", "target", "focus", "say", "wait", "commit"]);
91
92
  var AgentPlayback = class {
92
93
  /** 1 = natural pace; `Infinity` plays nothing (calls run instantly). Takes effect on the next step. */
@@ -222,7 +223,7 @@ var AgentPlayback = class {
222
223
  const pace = () => Math.max(0.1, this.speed) * (run.glance ? 1.6 : 1);
223
224
  const wait = (ms) => instant() || ms <= 0 ? Promise.resolve() : this.clock.wait(ms / pace());
224
225
  const presence = surface.presence;
225
- const locate = (ref) => surface.locate?.(ref.kind === "item" ? { kind: surface.defaultKind ?? "item", id: ref.id } : ref) ?? null;
226
+ const locate = (ref) => finiteRect(surface.locate?.(ref.kind === "item" ? { kind: surface.defaultKind ?? "item", id: ref.id } : ref) ?? null);
226
227
  const record = (step) => {
227
228
  for (const list of this.recordings) list.push({ actor: id, surface: surfaceId, tool: call.name, step });
228
229
  };
@@ -346,7 +347,7 @@ var AgentPlayback = class {
346
347
  return wait(60);
347
348
  case "type": {
348
349
  const total = step.ms ?? Math.min(900, 40 * step.text.length + 150);
349
- if (instant() || total <= 0) {
350
+ if (instant() || !(total > 0) || !Number.isFinite(total)) {
350
351
  presence.set({ id, message: step.text });
351
352
  return;
352
353
  }
@@ -366,11 +367,12 @@ var AgentPlayback = class {
366
367
  }
367
368
  /** Glides along a slight arc with ease-out, by elapsed time (fewer frames in a throttled tab, never longer). */
368
369
  async glide(presence, id, to, ms, instant) {
370
+ if (!Number.isFinite(to.x) || !Number.isFinite(to.y)) return;
369
371
  const a = presence.get(id);
370
- const from = a ? { x: a.x, y: a.y } : to;
372
+ const from = a && Number.isFinite(a.x) && Number.isFinite(a.y) ? { x: a.x, y: a.y } : to;
371
373
  const dist = Math.hypot(to.x - from.x, to.y - from.y);
372
374
  const duration = (ms ?? Math.min(900, 260 + dist * 0.5)) / Math.max(0.1, this.speed);
373
- if (instant() || dist < 1 || duration <= 0) {
375
+ if (instant() || dist < 1 || !(duration > 0) || !Number.isFinite(duration)) {
374
376
  presence.set({ id, x: to.x, y: to.y });
375
377
  return;
376
378
  }
@@ -8,6 +8,7 @@ import {
8
8
  formatIsoDate,
9
9
  formatIsoDateTime,
10
10
  formatTextFunction,
11
+ indexToColName,
11
12
  isBlank,
12
13
  isFormulaError,
13
14
  isoDateHasTime,
@@ -2947,16 +2948,16 @@ var FormulaEvaluator = class {
2947
2948
  const value = this.evaluateNode(argument);
2948
2949
  if (!Array.isArray(value)) return value;
2949
2950
  if (value.length <= 1) return firstValue(value);
2950
- const cell = this.context.getFormulaCell?.();
2951
- if (argument.type === "Range" && cell) {
2951
+ const cell2 = this.context.getFormulaCell?.();
2952
+ if (argument.type === "Range" && cell2) {
2952
2953
  const range = toFormulaRange(value);
2953
2954
  const top = Math.min(argument.startRow, argument.endRow);
2954
2955
  const left = Math.min(colNameToIndex(argument.startCol), colNameToIndex(argument.endCol));
2955
2956
  if (range.cols === 1) {
2956
- const r = cell.row - top;
2957
+ const r = cell2.row - top;
2957
2958
  if (r >= 0 && r < range.rows) return range.values[r];
2958
2959
  } else if (range.rows === 1) {
2959
- const c = colNameToIndex(cell.col) - left;
2960
+ const c = colNameToIndex(cell2.col) - left;
2960
2961
  if (c >= 0 && c < range.cols) return range.values[c];
2961
2962
  }
2962
2963
  return this.fail("#VALUE!", `The @ operator found no cell of ${argument.raw} in the formula's row or column.`);
@@ -3757,6 +3758,84 @@ function getFillDirection(source, target) {
3757
3758
  return null;
3758
3759
  }
3759
3760
 
3761
+ // src/spreadsheet/playback.ts
3762
+ var CELL = /^\s*\$?([A-Za-z]{1,3})\$?(\d+)\s*$/;
3763
+ var COL = /^\s*([A-Za-z]{1,3})\s*$/;
3764
+ var ROW = /^\s*(\d+)\s*$/;
3765
+ function parseSpreadsheetPlaybackRef(ref) {
3766
+ const [a, b = a] = ref.id.split(":");
3767
+ if (!a) return null;
3768
+ if (ref.kind === "cell") {
3769
+ const s = CELL.exec(a);
3770
+ const e = CELL.exec(b);
3771
+ if (!s || !e) return null;
3772
+ const r = [Number(s[2]) - 1, Number(e[2]) - 1];
3773
+ const c = [colNameToIndex(s[1].toUpperCase()), colNameToIndex(e[1].toUpperCase())];
3774
+ return { startRow: Math.min(r[0], r[1]), endRow: Math.max(r[0], r[1]), startCol: Math.min(c[0], c[1]), endCol: Math.max(c[0], c[1]) };
3775
+ }
3776
+ if (ref.kind === "row") {
3777
+ const s = ROW.exec(a);
3778
+ const e = ROW.exec(b);
3779
+ if (!s || !e) return null;
3780
+ return { startRow: Math.min(+s[1], +e[1]) - 1, endRow: Math.max(+s[1], +e[1]) - 1, startCol: null, endCol: null };
3781
+ }
3782
+ if (ref.kind === "column") {
3783
+ const s = COL.exec(a);
3784
+ const e = COL.exec(b);
3785
+ if (!s || !e) return null;
3786
+ const c = [colNameToIndex(s[1].toUpperCase()), colNameToIndex(e[1].toUpperCase())];
3787
+ return { startRow: null, endRow: null, startCol: Math.min(c[0], c[1]), endCol: Math.max(c[0], c[1]) };
3788
+ }
3789
+ return null;
3790
+ }
3791
+ var cell = (id) => ({ kind: "cell", id });
3792
+ var shown = (v) => {
3793
+ if (v === null || v === void 0 || v === "") return null;
3794
+ const text = String(v);
3795
+ return text.length > 40 ? `${text.slice(0, 39)}\u2026` : text;
3796
+ };
3797
+ function planSpreadsheetToolPlayback(tool, args) {
3798
+ const count = Number.isInteger(args.count) && args.count > 0 ? args.count : 1;
3799
+ switch (tool) {
3800
+ case "set_cell": {
3801
+ if (typeof args.cell !== "string") return null;
3802
+ const value = shown(args.value);
3803
+ return [{ type: "target", ref: cell(args.cell) }, { type: "press" }, { type: "release" }, ...value ? [{ type: "type", text: value }] : []];
3804
+ }
3805
+ case "set_range": {
3806
+ const start = typeof args.start === "string" ? CELL.exec(args.start) : null;
3807
+ const rows = Array.isArray(args.values) ? args.values : [];
3808
+ if (!start || !rows.length) return null;
3809
+ const width = Math.max(1, ...rows.map((r) => Array.isArray(r) ? r.length : 1));
3810
+ const col = colNameToIndex(start[1].toUpperCase());
3811
+ const end = `${indexToColName(col + width - 1)}${Number(start[2]) + rows.length - 1}`;
3812
+ const first = shown(rows.flat().find((v) => v !== null && v !== void 0 && v !== ""));
3813
+ return [
3814
+ { type: "target", ref: cell(args.start) },
3815
+ { type: "press" },
3816
+ { type: "target", ref: cell(`${args.start}:${end}`) },
3817
+ { type: "release" },
3818
+ ...first ? [{ type: "type", text: first }] : []
3819
+ ];
3820
+ }
3821
+ case "insert_rows":
3822
+ case "delete_rows": {
3823
+ const from = Number(tool === "insert_rows" ? args.before : args.start);
3824
+ if (!Number.isInteger(from) || from < 1) return null;
3825
+ return [{ type: "target", ref: { kind: "row", id: `${from}:${from + count - 1}` } }, { type: "press" }, { type: "release" }];
3826
+ }
3827
+ case "insert_columns":
3828
+ case "delete_columns": {
3829
+ const from = String(tool === "insert_columns" ? args.before : args.start ?? "");
3830
+ if (!COL.test(from)) return null;
3831
+ const index = colNameToIndex(from.trim().toUpperCase());
3832
+ return [{ type: "target", ref: { kind: "column", id: `${indexToColName(index)}:${indexToColName(index + count - 1)}` } }, { type: "press" }, { type: "release" }];
3833
+ }
3834
+ default:
3835
+ return null;
3836
+ }
3837
+ }
3838
+
3760
3839
  export {
3761
3840
  tokenizeFormula,
3762
3841
  WHOLE_COLUMN_END_ROW,
@@ -3794,5 +3873,7 @@ export {
3794
3873
  roundFloat,
3795
3874
  fillSeries,
3796
3875
  computeFillTarget,
3797
- getFillDirection
3876
+ getFillDirection,
3877
+ parseSpreadsheetPlaybackRef,
3878
+ planSpreadsheetToolPlayback
3798
3879
  };