agent-canvas 0.1.37 → 0.1.39

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.
@@ -14,7 +14,6 @@ jobs:
14
14
  uses: ./.github/workflows/deploy-worker.yml
15
15
  with:
16
16
  env: prod
17
- secrets:
18
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
19
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
20
- SHARE_AUTH_TOKEN: ${{ secrets.SHARE_AUTH_TOKEN_PROD }}
17
+ # inherit so the reusable workflow can resolve secrets; the env-scoped
18
+ # values (set on the `prod` environment) win over repo-level ones.
19
+ secrets: inherit
@@ -16,7 +16,6 @@ jobs:
16
16
  uses: ./.github/workflows/deploy-worker.yml
17
17
  with:
18
18
  env: stage
19
- secrets:
20
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
21
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
22
- SHARE_AUTH_TOKEN: ${{ secrets.SHARE_AUTH_TOKEN_STAGE }}
19
+ # inherit so the reusable workflow can resolve secrets; the env-scoped
20
+ # values (set on the `stage` environment) win over repo-level ones.
21
+ secrets: inherit
@@ -7,14 +7,13 @@ on:
7
7
  description: "Target environment (stage or prod)"
8
8
  required: true
9
9
  type: string
10
- secrets:
11
- CLOUDFLARE_API_TOKEN:
12
- required: true
13
- CLOUDFLARE_ACCOUNT_ID:
14
- required: true
15
- SHARE_AUTH_TOKEN:
16
- description: "Bearer token the daemon must send to POST /shares. Optional but strongly recommended in production."
17
- required: false
10
+ # No `secrets:` inputs on purpose. The deploy job below declares
11
+ # `environment: ${{ inputs.env }}`, so referencing `secrets.X` directly
12
+ # resolves the matching *environment* secret (falling back to a
13
+ # repo-level secret of the same name). Callers pass `secrets: inherit`
14
+ # so org/repo secrets remain reachable too. This is what makes the
15
+ # CLOUDFLARE_* / SHARE_AUTH_TOKEN values environment-scoped — a prod
16
+ # token can never be read by a stage deploy and vice versa.
18
17
 
19
18
  jobs:
20
19
  deploy:
@@ -59,7 +59,11 @@ export async function handlePush(args: string[]) {
59
59
  // Skip restart only for clear user errors (syntax errors with line/col,
60
60
  // runtime validation errors) — those won't be fixed by a restart.
61
61
  const isUserError = !result.ok && result.errors && Object.values(result.errors).every((e: any) =>
62
- typeof e === "string" && (/\(line \d+, col \d+\)/.test(e) || e.startsWith("Runtime error:")));
62
+ typeof e === "string" && (
63
+ /\(line \d+, col \d+\)/.test(e) ||
64
+ /Syntax Error|Parse error|Mermaid syntax error/i.test(e) ||
65
+ e.startsWith("Runtime error:")
66
+ ));
63
67
  const shouldRestart = !result.ok && result.error === "Canvas compilation failed" && !isUserError;
64
68
  if (shouldRestart) {
65
69
  console.error("Compilation failed, restarting daemon and retrying...");
@@ -58,6 +58,7 @@ function getTextNodesInRange(range: Range): Text[] {
58
58
  // Skip line numbers and existing marks
59
59
  const parent = node.parentElement;
60
60
  if (parent?.classList.contains("select-none")) continue;
61
+ if (parent?.closest("[data-annotation-id]")) continue;
61
62
 
62
63
  if (range.intersectsNode(node)) {
63
64
  nodes.push(node);
@@ -108,27 +109,18 @@ export function restoreMarks(
108
109
  container: HTMLElement,
109
110
  annotations: { id: string; snippet: string; filePath?: string; context?: { before: string; after: string; hierarchy: string[] } }[]
110
111
  ) {
111
- // Skip annotations that already have marks in the DOM
112
- const toRestore = annotations.filter(
113
- (a) => !document.querySelector(`[data-annotation-id="${a.id}"]`)
114
- );
115
- if (!toRestore.length) return;
112
+ for (const ann of annotations) {
113
+ // A canvas can be rendered more than once (overview, compare, hidden
114
+ // source panes), so restore decisions must be scoped to this container.
115
+ if (container.querySelector(`[data-annotation-id="${ann.id}"]`)) continue;
116
116
 
117
- // Build full text content + mapping from text offset to (textNode, offsetInNode)
118
- const textNodes: { node: Text; start: number }[] = [];
119
- let fullText = "";
120
- const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
121
- let tn: Text | null;
122
- while ((tn = walker.nextNode() as Text | null)) {
123
- if (tn.parentElement?.classList.contains("select-none")) continue;
124
- textNodes.push({ node: tn, start: fullText.length });
125
- fullText += tn.textContent || "";
126
- }
127
-
128
- for (const ann of toRestore) {
129
117
  const snippet = ann.snippet;
130
118
  if (!snippet) continue;
131
119
 
120
+ // Rebuild after every inserted mark. Wrapping text splits and moves text
121
+ // nodes, so a cached offset map can point at stale DOM after the first hit.
122
+ const { fullText, textNodes } = buildTextIndex(container);
123
+
132
124
  // Find all occurrences of the snippet in the full text
133
125
  const occurrences: number[] = [];
134
126
  let searchFrom = 0;
@@ -181,6 +173,24 @@ export function restoreMarks(
181
173
  }
182
174
  }
183
175
 
176
+ function buildTextIndex(container: HTMLElement): {
177
+ fullText: string;
178
+ textNodes: { node: Text; start: number }[];
179
+ } {
180
+ const textNodes: { node: Text; start: number }[] = [];
181
+ let fullText = "";
182
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
183
+ let tn: Text | null;
184
+ while ((tn = walker.nextNode() as Text | null)) {
185
+ const parent = tn.parentElement;
186
+ if (parent?.classList.contains("select-none")) continue;
187
+ if (parent?.closest("[data-annotation-id]")) continue;
188
+ textNodes.push({ node: tn, start: fullText.length });
189
+ fullText += tn.textContent || "";
190
+ }
191
+ return { fullText, textNodes };
192
+ }
193
+
184
194
  function offsetToNode(
185
195
  textNodes: { node: Text; start: number }[],
186
196
  offset: number