@ai-matrx/content-ir-react 0.13.18 → 0.15.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.0 — 2026-10-02
4
+
5
+ - **A card in a chat message applies under its conversation.** New
6
+ `DirectiveScopeProvider` / `useDirectiveScope` and a `conversationId` prop on
7
+ `DirectiveRender`. The Apply control passes it to BOTH `confirm` and
8
+ `applyState` as `shell.conversationId` — the namespace an agent proposal's
9
+ Approve uses — so one action rendered twice (proposal card + in-message card)
10
+ keys once and can never apply twice. A note mounts no scope (person namespace).
11
+ - **Run again.** A clean tally carries "Run again": it calls `ask` with
12
+ `again: true` (the question must say it runs a SECOND time) and only a yes
13
+ sends `shell.force = true`; a no leaves the tally as it was. A tally with a
14
+ failed item carries "Retry" instead — a plain apply, never `force`.
15
+ - `confirm` / `applyState` now take `DirectiveShell` (`__kind`, `items`,
16
+ optional `conversationId`, optional `force`).
17
+
18
+ ### Consumer action
19
+
20
+ - Pass the conversation a message renders in (`DirectiveRender conversationId`
21
+ or `DirectiveScopeProvider`), and forward `shell.conversationId` as
22
+ `conversation_id` and `shell.force` as `force` to `/directives/confirm`
23
+ (and `conversationId` to `/directives/apply_state`).
24
+ - Word your `ask` dialog for `request.again`.
25
+
26
+ ## 0.14.0 — 2026-10-02
27
+
28
+ - **A card survives a reload.** New host seam `applyState(shell)` →
29
+ `DirectiveApplyState` (`not_applied` | `in_flight` | `applied`, with the
30
+ ledger's own `message` and the `records` it wrote). The Apply control reads it
31
+ once on mount: while it answers nothing offers a write (a quiet spinner); an
32
+ `applied` block mounts as its tally — the same sentence and record doors a
33
+ fresh apply leaves — instead of "Apply" again; `in_flight` reads "Finishing…"
34
+ and re-asks every 2 s for up to a minute; a read that fails is reported through
35
+ `reportError` and Apply is offered (safe: `confirm` is idempotent). Absent seam
36
+ → unchanged behaviour.
37
+
38
+ ### Consumer action
39
+
40
+ - Wire `applyState` to the server's ledger read (`POST /directives/apply_state`
41
+ in the SAME namespace your `confirm` applies in — for a block in a person's own
42
+ content, no conversation), batched if a page carries many cards.
43
+
3
44
  ## 0.13.18
4
45
 
5
46
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -16,6 +16,25 @@ interface DirectiveApplyResult {
16
16
  */
17
17
  records?: DirectiveRecordRef[];
18
18
  }
19
+ /**
20
+ * What the server's action ledger says about ONE block, read on mount so a
21
+ * reloaded page never offers "Apply" for work that already ran (reviewer
22
+ * defect 5, 2026-10-02).
23
+ *
24
+ * - `not_applied` — still to do (or partly done: the batch re-applies, and the
25
+ * server replays the members it already holds). The card offers Apply.
26
+ * - `in_flight` — another request is applying it right now. The card says it
27
+ * is finishing and asks again shortly; it offers neither Apply nor a tally.
28
+ * - `applied` — the ledger holds every item. The card shows the tally exactly
29
+ * as a fresh apply would: the ledger's own sentence and the records, as doors.
30
+ */
31
+ interface DirectiveApplyState {
32
+ state: "not_applied" | "in_flight" | "applied";
33
+ /** The ledger row's sentence ("Created task."), never composed client-side. */
34
+ message?: string | null;
35
+ /** The records the original apply wrote — the tally's doors. */
36
+ records?: DirectiveRecordRef[];
37
+ }
19
38
  /** One record a directive names: the noun it is, and its id. */
20
39
  interface DirectiveRecordRef {
21
40
  noun: string;
@@ -42,6 +61,28 @@ interface DirectiveAskRequest {
42
61
  items: Record<string, unknown>[];
43
62
  /** The noun's human label ("Task"), from the host's catalog when it has one. */
44
63
  nounLabel: string;
64
+ /**
65
+ * True for "Run again": the ledger already holds this block, and a yes runs
66
+ * it a SECOND time (`force`) — another copy for a create, the same fields
67
+ * written again for an update, another delete for a restored record. The
68
+ * question must say so.
69
+ */
70
+ again?: boolean;
71
+ }
72
+ /**
73
+ * The shell a card hands its host — the two keys, plus WHERE it sits.
74
+ *
75
+ * `conversationId` is the conversation the card renders in (a chat message),
76
+ * absent in a person's own content (a note). It is the idempotency NAMESPACE:
77
+ * a card in an agent's message must apply under the SAME key as that
78
+ * proposal's Approve button, or the one action could run twice.
79
+ */
80
+ interface DirectiveShell {
81
+ __kind: string;
82
+ items: Record<string, unknown>[];
83
+ conversationId?: string;
84
+ /** "Run again": skip the ledger's dedup and apply a second time. */
85
+ force?: boolean;
45
86
  }
46
87
  interface DirectiveOpenItemOptions {
47
88
  /** The item, `__kind`-stamped when the shape has an item kind. */
@@ -70,10 +111,7 @@ interface DirectiveHost {
70
111
  * item against the registered model and runs the ONE handler; idempotent).
71
112
  * Reject with an Error whose message is safe to show.
72
113
  */
73
- confirm?: (shell: {
74
- __kind: string;
75
- items: Record<string, unknown>[];
76
- }) => Promise<DirectiveApplyResult>;
114
+ confirm?: (shell: DirectiveShell) => Promise<DirectiveApplyResult>;
77
115
  /**
78
116
  * Ask the person BEFORE anything runs, naming the consequence (the records,
79
117
  * what changes). Resolve `true` to go ahead, `false` to leave the card as it
@@ -83,6 +121,17 @@ interface DirectiveHost {
83
121
  * will read "Applying…" while its dialog is up).
84
122
  */
85
123
  ask?: (request: DirectiveAskRequest) => Promise<boolean>;
124
+ /**
125
+ * Has this exact block already been applied? Read ONCE when the card mounts
126
+ * (and again while the answer is `in_flight`), so the card survives a reload:
127
+ * the server ledger, not component state, is what knows. The identity is the
128
+ * server's (it hashes the validated items in the same namespace `confirm`
129
+ * applies in), so a host asks — it never computes. Batch on the host side if
130
+ * a page carries many cards. Reject with an Error when the ledger cannot be
131
+ * read: the card reports it and offers Apply, which is safe because `confirm`
132
+ * is idempotent. Absent → the card starts at Apply, as before.
133
+ */
134
+ applyState?: (shell: DirectiveShell) => Promise<DirectiveApplyState>;
86
135
  /**
87
136
  * Draw ONE existing record by its live name, as a way to open it. Used on
88
137
  * update/delete rows (the record that will change), in the host's own ask
@@ -101,6 +150,20 @@ interface DirectiveHost {
101
150
  /** Where a decode failure or an unhonourable shell is reported. Default: console.error. */
102
151
  reportError?: (message: string) => void;
103
152
  }
153
+ /** Where a directive renders — today, the conversation it sits in (if any). */
154
+ interface DirectiveScope {
155
+ conversationId?: string | null;
156
+ }
157
+ /**
158
+ * Tell every card below WHERE it renders. A host draws a chat message's
159
+ * directives inside one of these (or passes `conversationId` to
160
+ * `DirectiveRender`), so the card applies in that conversation's namespace —
161
+ * the same key the agent's proposal approves under. A note mounts none.
162
+ */
163
+ declare function DirectiveScopeProvider({ conversationId, children, }: DirectiveScope & {
164
+ children: ReactNode;
165
+ }): react.JSX.Element;
166
+ declare function useDirectiveScope(): DirectiveScope;
104
167
  interface DirectiveHostProviderProps {
105
168
  host: DirectiveHost;
106
169
  children: ReactNode;
@@ -113,4 +176,4 @@ declare function DirectiveHostProvider({ host, children }: DirectiveHostProvider
113
176
  */
114
177
  declare function useDirectiveHost(): DirectiveHost;
115
178
 
116
- export { type DirectiveApplyResult as D, type DirectiveAskRequest as a, type DirectiveCopyProps as b, type DirectiveHost as c, DirectiveHostProvider as d, type DirectiveHostProviderProps as e, type DirectiveOpenItemOptions as f, type DirectiveRecordProps as g, type DirectiveRecordRef as h, useDirectiveHost as u };
179
+ export { type DirectiveApplyResult as D, type DirectiveApplyState as a, type DirectiveAskRequest as b, type DirectiveCopyProps as c, type DirectiveHost as d, DirectiveHostProvider as e, type DirectiveHostProviderProps as f, type DirectiveOpenItemOptions as g, type DirectiveRecordProps as h, type DirectiveRecordRef as i, type DirectiveScope as j, DirectiveScopeProvider as k, type DirectiveShell as l, useDirectiveScope as m, useDirectiveHost as u };
@@ -16,6 +16,25 @@ interface DirectiveApplyResult {
16
16
  */
17
17
  records?: DirectiveRecordRef[];
18
18
  }
19
+ /**
20
+ * What the server's action ledger says about ONE block, read on mount so a
21
+ * reloaded page never offers "Apply" for work that already ran (reviewer
22
+ * defect 5, 2026-10-02).
23
+ *
24
+ * - `not_applied` — still to do (or partly done: the batch re-applies, and the
25
+ * server replays the members it already holds). The card offers Apply.
26
+ * - `in_flight` — another request is applying it right now. The card says it
27
+ * is finishing and asks again shortly; it offers neither Apply nor a tally.
28
+ * - `applied` — the ledger holds every item. The card shows the tally exactly
29
+ * as a fresh apply would: the ledger's own sentence and the records, as doors.
30
+ */
31
+ interface DirectiveApplyState {
32
+ state: "not_applied" | "in_flight" | "applied";
33
+ /** The ledger row's sentence ("Created task."), never composed client-side. */
34
+ message?: string | null;
35
+ /** The records the original apply wrote — the tally's doors. */
36
+ records?: DirectiveRecordRef[];
37
+ }
19
38
  /** One record a directive names: the noun it is, and its id. */
20
39
  interface DirectiveRecordRef {
21
40
  noun: string;
@@ -42,6 +61,28 @@ interface DirectiveAskRequest {
42
61
  items: Record<string, unknown>[];
43
62
  /** The noun's human label ("Task"), from the host's catalog when it has one. */
44
63
  nounLabel: string;
64
+ /**
65
+ * True for "Run again": the ledger already holds this block, and a yes runs
66
+ * it a SECOND time (`force`) — another copy for a create, the same fields
67
+ * written again for an update, another delete for a restored record. The
68
+ * question must say so.
69
+ */
70
+ again?: boolean;
71
+ }
72
+ /**
73
+ * The shell a card hands its host — the two keys, plus WHERE it sits.
74
+ *
75
+ * `conversationId` is the conversation the card renders in (a chat message),
76
+ * absent in a person's own content (a note). It is the idempotency NAMESPACE:
77
+ * a card in an agent's message must apply under the SAME key as that
78
+ * proposal's Approve button, or the one action could run twice.
79
+ */
80
+ interface DirectiveShell {
81
+ __kind: string;
82
+ items: Record<string, unknown>[];
83
+ conversationId?: string;
84
+ /** "Run again": skip the ledger's dedup and apply a second time. */
85
+ force?: boolean;
45
86
  }
46
87
  interface DirectiveOpenItemOptions {
47
88
  /** The item, `__kind`-stamped when the shape has an item kind. */
@@ -70,10 +111,7 @@ interface DirectiveHost {
70
111
  * item against the registered model and runs the ONE handler; idempotent).
71
112
  * Reject with an Error whose message is safe to show.
72
113
  */
73
- confirm?: (shell: {
74
- __kind: string;
75
- items: Record<string, unknown>[];
76
- }) => Promise<DirectiveApplyResult>;
114
+ confirm?: (shell: DirectiveShell) => Promise<DirectiveApplyResult>;
77
115
  /**
78
116
  * Ask the person BEFORE anything runs, naming the consequence (the records,
79
117
  * what changes). Resolve `true` to go ahead, `false` to leave the card as it
@@ -83,6 +121,17 @@ interface DirectiveHost {
83
121
  * will read "Applying…" while its dialog is up).
84
122
  */
85
123
  ask?: (request: DirectiveAskRequest) => Promise<boolean>;
124
+ /**
125
+ * Has this exact block already been applied? Read ONCE when the card mounts
126
+ * (and again while the answer is `in_flight`), so the card survives a reload:
127
+ * the server ledger, not component state, is what knows. The identity is the
128
+ * server's (it hashes the validated items in the same namespace `confirm`
129
+ * applies in), so a host asks — it never computes. Batch on the host side if
130
+ * a page carries many cards. Reject with an Error when the ledger cannot be
131
+ * read: the card reports it and offers Apply, which is safe because `confirm`
132
+ * is idempotent. Absent → the card starts at Apply, as before.
133
+ */
134
+ applyState?: (shell: DirectiveShell) => Promise<DirectiveApplyState>;
86
135
  /**
87
136
  * Draw ONE existing record by its live name, as a way to open it. Used on
88
137
  * update/delete rows (the record that will change), in the host's own ask
@@ -101,6 +150,20 @@ interface DirectiveHost {
101
150
  /** Where a decode failure or an unhonourable shell is reported. Default: console.error. */
102
151
  reportError?: (message: string) => void;
103
152
  }
153
+ /** Where a directive renders — today, the conversation it sits in (if any). */
154
+ interface DirectiveScope {
155
+ conversationId?: string | null;
156
+ }
157
+ /**
158
+ * Tell every card below WHERE it renders. A host draws a chat message's
159
+ * directives inside one of these (or passes `conversationId` to
160
+ * `DirectiveRender`), so the card applies in that conversation's namespace —
161
+ * the same key the agent's proposal approves under. A note mounts none.
162
+ */
163
+ declare function DirectiveScopeProvider({ conversationId, children, }: DirectiveScope & {
164
+ children: ReactNode;
165
+ }): react.JSX.Element;
166
+ declare function useDirectiveScope(): DirectiveScope;
104
167
  interface DirectiveHostProviderProps {
105
168
  host: DirectiveHost;
106
169
  children: ReactNode;
@@ -113,4 +176,4 @@ declare function DirectiveHostProvider({ host, children }: DirectiveHostProvider
113
176
  */
114
177
  declare function useDirectiveHost(): DirectiveHost;
115
178
 
116
- export { type DirectiveApplyResult as D, type DirectiveAskRequest as a, type DirectiveCopyProps as b, type DirectiveHost as c, DirectiveHostProvider as d, type DirectiveHostProviderProps as e, type DirectiveOpenItemOptions as f, type DirectiveRecordProps as g, type DirectiveRecordRef as h, useDirectiveHost as u };
179
+ export { type DirectiveApplyResult as D, type DirectiveApplyState as a, type DirectiveAskRequest as b, type DirectiveCopyProps as c, type DirectiveHost as d, DirectiveHostProvider as e, type DirectiveHostProviderProps as f, type DirectiveOpenItemOptions as g, type DirectiveRecordProps as h, type DirectiveRecordRef as i, type DirectiveScope as j, DirectiveScopeProvider as k, type DirectiveShell as l, useDirectiveScope as m, useDirectiveHost as u };
@@ -27,6 +27,7 @@ __export(directives_exports, {
27
27
  DirectiveHostProvider: () => DirectiveHostProvider,
28
28
  DirectiveProvisionalCard: () => DirectiveProvisionalCard,
29
29
  DirectiveRender: () => DirectiveRender,
30
+ DirectiveScopeProvider: () => DirectiveScopeProvider,
30
31
  SideEffectDirectiveCard: () => SideEffectDirectiveCard,
31
32
  TARGETING_CLASSES: () => TARGETING_CLASSES,
32
33
  fieldLabel: () => fieldLabel,
@@ -37,7 +38,8 @@ __export(directives_exports, {
37
38
  recordFallbackName: () => recordFallbackName,
38
39
  registerDirectiveRenderer: () => registerDirectiveRenderer,
39
40
  resetDirectiveRenderers: () => resetDirectiveRenderers,
40
- useDirectiveHost: () => useDirectiveHost
41
+ useDirectiveHost: () => useDirectiveHost,
42
+ useDirectiveScope: () => useDirectiveScope
41
43
  });
42
44
  module.exports = __toCommonJS(directives_exports);
43
45
 
@@ -45,6 +47,16 @@ module.exports = __toCommonJS(directives_exports);
45
47
  var import_react = require("react");
46
48
  var import_jsx_runtime = require("react/jsx-runtime");
47
49
  var DirectiveHostContext = (0, import_react.createContext)(null);
50
+ var DirectiveScopeContext = (0, import_react.createContext)({});
51
+ function DirectiveScopeProvider({
52
+ conversationId,
53
+ children
54
+ }) {
55
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DirectiveScopeContext.Provider, { value: { conversationId: conversationId ?? null }, children });
56
+ }
57
+ function useDirectiveScope() {
58
+ return (0, import_react.useContext)(DirectiveScopeContext);
59
+ }
48
60
  function DirectiveHostProvider({ host, children }) {
49
61
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DirectiveHostContext.Provider, { value: host, children });
50
62
  }
@@ -197,6 +209,10 @@ var MaximizeIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Svg, { .
197
209
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3", x2: "10", y1: "21", y2: "14" })
198
210
  ] });
199
211
  var LoaderIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Svg, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
212
+ var RotateCcwIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Svg, { ...p, children: [
213
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
214
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 3v5h5" })
215
+ ] });
200
216
  var CheckCircleIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Svg, { ...p, children: [
201
217
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
202
218
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m9 12 2 2 4-4" })
@@ -223,6 +239,8 @@ function classIcon(directiveClass, noun) {
223
239
 
224
240
  // directives/ApplyDirectiveButton.tsx
225
241
  var import_jsx_runtime3 = require("react/jsx-runtime");
242
+ var FINISH_POLL_MS = 2e3;
243
+ var FINISH_POLLS_MAX = 30;
226
244
  function ApplyDirectiveButton({
227
245
  directive,
228
246
  label = "Apply",
@@ -230,9 +248,62 @@ function ApplyDirectiveButton({
230
248
  onApplied
231
249
  }) {
232
250
  const host = useDirectiveHost();
233
- const [state, setState] = (0, import_react2.useState)({ status: "idle" });
251
+ const scope = useDirectiveScope();
252
+ const conversationId = scope.conversationId || void 0;
253
+ const items = directive.items;
254
+ const readsLedger = !!host.applyState && !!host.confirm && directive.parsed.executes && items.length > 0;
255
+ const [state, setState] = (0, import_react2.useState)(
256
+ readsLedger ? { status: "checking" } : { status: "idle" }
257
+ );
234
258
  const [elapsed, setElapsed] = (0, import_react2.useState)(0);
235
259
  const startedAt = (0, import_react2.useRef)(null);
260
+ const nounText = (0, import_content_ir2.nounLabel)(directive.noun, host.nouns);
261
+ const shellKey = readsLedger ? JSON.stringify([directive.slug, items, conversationId ?? null]) : "";
262
+ const read = host.applyState;
263
+ const report = host.reportError;
264
+ (0, import_react2.useEffect)(() => {
265
+ if (!shellKey || !read) return;
266
+ const [slug, shellItems, inConversation] = JSON.parse(shellKey);
267
+ const shell = { __kind: slug, items: shellItems };
268
+ if (inConversation) shell.conversationId = inConversation;
269
+ let live = true;
270
+ let polls = 0;
271
+ let timer = null;
272
+ const ask = () => {
273
+ read(shell).then((answer) => {
274
+ if (!live) return;
275
+ if (answer.state === "applied") {
276
+ setState(
277
+ (prev) => prev.status === "checking" || prev.status === "finishing" ? {
278
+ status: "applied",
279
+ applied: shellItems.length,
280
+ failed: 0,
281
+ message: answer.message?.trim() ? answer.message.trim() : null,
282
+ records: answer.records ?? []
283
+ } : prev
284
+ );
285
+ } else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
286
+ polls += 1;
287
+ setState((prev) => prev.status === "checking" || prev.status === "finishing" ? { status: "finishing" } : prev);
288
+ timer = setTimeout(ask, FINISH_POLL_MS);
289
+ } else {
290
+ setState((prev) => prev.status === "checking" || prev.status === "finishing" ? { status: "idle" } : prev);
291
+ }
292
+ }).catch((error) => {
293
+ if (!live) return;
294
+ const why = error instanceof Error && error.message ? error.message : String(error);
295
+ (report ?? console.error)(
296
+ `[kind-directives] Could not read whether ${slug} was already applied: ${why}`
297
+ );
298
+ setState((prev) => prev.status === "checking" || prev.status === "finishing" ? { status: "idle" } : prev);
299
+ });
300
+ };
301
+ ask();
302
+ return () => {
303
+ live = false;
304
+ if (timer) clearTimeout(timer);
305
+ };
306
+ }, [shellKey, read, report]);
236
307
  (0, import_react2.useEffect)(() => {
237
308
  if (state.status !== "applying") return;
238
309
  startedAt.current = Date.now();
@@ -245,9 +316,9 @@ function ApplyDirectiveButton({
245
316
  if (!directive.parsed.executes) return null;
246
317
  const confirm = host.confirm;
247
318
  if (!confirm) return null;
248
- const items = directive.items;
249
- async function handleApply() {
319
+ async function handleApply(again = false) {
250
320
  if (!confirm) return;
321
+ const before = state;
251
322
  if (host.ask) {
252
323
  setState({ status: "asking" });
253
324
  let ok = false;
@@ -255,7 +326,8 @@ function ApplyDirectiveButton({
255
326
  ok = await host.ask({
256
327
  directive,
257
328
  items,
258
- nounLabel: (0, import_content_ir2.nounLabel)(directive.noun, host.nouns)
329
+ nounLabel: nounText,
330
+ ...again ? { again: true } : {}
259
331
  });
260
332
  } catch (error) {
261
333
  setState({
@@ -265,14 +337,17 @@ function ApplyDirectiveButton({
265
337
  return;
266
338
  }
267
339
  if (!ok) {
268
- setState({ status: "idle" });
340
+ setState(again ? before : { status: "idle" });
269
341
  return;
270
342
  }
271
343
  }
272
344
  setElapsed(0);
273
345
  setState({ status: "applying" });
274
346
  try {
275
- const result = await confirm({ __kind: directive.slug, items });
347
+ const shell = { __kind: directive.slug, items };
348
+ if (conversationId) shell.conversationId = conversationId;
349
+ if (again) shell.force = true;
350
+ const result = await confirm(shell);
276
351
  setState({
277
352
  status: "applied",
278
353
  applied: result.applied,
@@ -291,7 +366,7 @@ function ApplyDirectiveButton({
291
366
  if (state.status === "applied") {
292
367
  const bad = state.failed > 0;
293
368
  const trashed = directive.directiveClass === "delete";
294
- const label2 = (0, import_content_ir2.nounLabel)(directive.noun, host.nouns);
369
+ const label2 = nounText;
295
370
  const records = host.renderRecord ? state.records : [];
296
371
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
297
372
  "span",
@@ -309,7 +384,46 @@ function ApplyDirectiveButton({
309
384
  fallback: recordFallbackName(label2, record.id),
310
385
  context: "applied",
311
386
  trashed
312
- }) }, `${record.noun}:${record.id}`))
387
+ }) }, `${record.noun}:${record.id}`)),
388
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
389
+ "button",
390
+ {
391
+ type: "button",
392
+ onClick: () => void handleApply(!bad),
393
+ className: "inline-flex items-center gap-1 rounded px-1 py-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
394
+ "data-apply-action": bad ? "retry" : "run-again",
395
+ children: [
396
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RotateCcwIcon, { className: "h-3 w-3" }),
397
+ bad ? "Retry" : "Run again"
398
+ ]
399
+ }
400
+ )
401
+ ]
402
+ }
403
+ );
404
+ }
405
+ if (state.status === "checking") {
406
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
407
+ "span",
408
+ {
409
+ className: "inline-flex h-6 w-6 items-center justify-center text-muted-foreground",
410
+ role: "status",
411
+ "aria-label": "Checking whether this already ran",
412
+ "data-apply-state": "checking",
413
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" })
414
+ }
415
+ );
416
+ }
417
+ if (state.status === "finishing") {
418
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
419
+ "span",
420
+ {
421
+ className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
422
+ role: "status",
423
+ "data-apply-state": "finishing",
424
+ children: [
425
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }),
426
+ "Finishing\u2026"
313
427
  ]
314
428
  }
315
429
  );
@@ -548,7 +662,11 @@ function isWholeJson(content) {
548
662
  return false;
549
663
  }
550
664
  }
551
- function DirectiveRender({ content, className, streaming }) {
665
+ function DirectiveRender({ conversationId, ...props }) {
666
+ if (conversationId === void 0) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DirectiveRenderInner, { ...props });
667
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DirectiveScopeProvider, { conversationId, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DirectiveRenderInner, { ...props }) });
668
+ }
669
+ function DirectiveRenderInner({ content, className, streaming }) {
552
670
  const host = useDirectiveHost();
553
671
  const directive = (0, import_content_ir6.tryDecodeDirectiveContent)(content, (message) => {
554
672
  const line = `[kind-directives] ${message}`;