@volter-ai-dev/supercode-ui 0.1.36 → 0.1.37

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/composer.mjs CHANGED
@@ -32,6 +32,7 @@ var EMPTY_UI_STATE = Object.freeze({
32
32
  mode: "none",
33
33
  strategy: null,
34
34
  canSend: false,
35
+ canSteer: false,
35
36
  canResume: false,
36
37
  continuationModes: Object.freeze([]),
37
38
  canBranch: false,
@@ -67,6 +68,16 @@ var EMPTY_UI_STATE = Object.freeze({
67
68
  function harnessDisplayName(id) {
68
69
  return HARNESS_NAMES[id] ?? id;
69
70
  }
71
+ var ACTIVITY_PRIORITY = Object.freeze({
72
+ "needs-input": 70,
73
+ failed: 60,
74
+ working: 50,
75
+ unseen: 40,
76
+ finished: 30,
77
+ running: 20,
78
+ recent: 10,
79
+ idle: 0
80
+ });
70
81
  function canContinueHere(state) {
71
82
  if (state.mode !== "mirror" || state.canSend) return false;
72
83
  const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
@@ -130,7 +141,7 @@ var ICONS = {
130
141
  function UiIcon({ name, size = 16, class: className = "" }) {
131
142
  const Glyph = ICONS[name];
132
143
  if (!Glyph) return null;
133
- return /* @__PURE__ */ jsx("svg", { class: `scui-icon ${className}`, style: { "--scui-icon-size": `${size}px` }, viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Glyph, {}) });
144
+ return /* @__PURE__ */ jsx("svg", { className: `scui-icon ${className}`, style: { "--scui-icon-size": `${size}px` }, viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Glyph, {}) });
134
145
  }
135
146
 
136
147
  // src/context.jsx
@@ -204,6 +215,26 @@ function partitionAttachments(value) {
204
215
  }
205
216
  return { context, images };
206
217
  }
218
+ function attachmentKey(item) {
219
+ if (item.id) return `id:${item.id}`;
220
+ return "detail" in item ? `context:${item.kind ?? ""}\0${item.label}\0${item.detail}` : `image:${item.url}`;
221
+ }
222
+ function ContextCandidate({ attachment, attached, onAttach }) {
223
+ const image = "url" in attachment;
224
+ return /* @__PURE__ */ jsxs2("button", { type: "button", disabled: attached, "aria-label": `${attached ? "Attached" : "Attach"} ${attachment.label}`, onClick: () => onAttach(attachment), children: [
225
+ image ? /* @__PURE__ */ jsx2("img", { src: attachment.url, alt: "" }) : /* @__PURE__ */ jsx2(UiIcon, { name: "attach", size: 13 }),
226
+ /* @__PURE__ */ jsxs2("span", { children: [
227
+ /* @__PURE__ */ jsx2("strong", { children: attachment.label }),
228
+ /* @__PURE__ */ jsx2("small", { children: image ? "Image" : attachment.kind || "Context" })
229
+ ] }),
230
+ attached ? /* @__PURE__ */ jsx2(UiIcon, { name: "check", size: 13 }) : /* @__PURE__ */ jsx2(UiIcon, { name: "plus", size: 13 })
231
+ ] });
232
+ }
233
+ function ContextCandidates({ items, context, images, state, adapter, onAttach, component: Candidate = ContextCandidate }) {
234
+ if (!items.length) return null;
235
+ const attached = new Set([...context, ...images].map(attachmentKey));
236
+ return /* @__PURE__ */ jsx2("div", { className: "scui-context-candidates", "aria-label": "Available context", children: items.map((attachment, index) => /* @__PURE__ */ jsx2(Candidate, { value: attachment, attachment, attached: attached.has(attachmentKey(attachment)), state, adapter, onAttach, index }, attachmentKey(attachment))) });
237
+ }
207
238
  async function imageAttachmentsFromFiles(value) {
208
239
  const files = Array.from(value ?? []).filter((file) => file?.type?.startsWith("image/"));
209
240
  if (files.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images at a time.`);
@@ -221,7 +252,7 @@ async function imageAttachmentsFromFiles(value) {
221
252
  }
222
253
  function ContextTray({ items, onRemove }) {
223
254
  if (!items.length) return null;
224
- return /* @__PURE__ */ jsx2("div", { class: "scui-compose-context", "aria-label": "Attached context", children: items.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
255
+ return /* @__PURE__ */ jsx2("div", { className: "scui-compose-context", "aria-label": "Attached context", children: items.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
225
256
  /* @__PURE__ */ jsx2(UiIcon, { name: "attach", size: 12 }),
226
257
  /* @__PURE__ */ jsx2("strong", { children: item.label }),
227
258
  /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove context ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) })
@@ -229,13 +260,23 @@ function ContextTray({ items, onRemove }) {
229
260
  }
230
261
  function ImageTray({ items, onRemove }) {
231
262
  if (!items.length) return null;
232
- return /* @__PURE__ */ jsx2("div", { class: "scui-compose-images", "aria-label": "Attached images", children: items.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
263
+ return /* @__PURE__ */ jsx2("div", { className: "scui-compose-images", "aria-label": "Attached images", children: items.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
233
264
  /* @__PURE__ */ jsx2("img", { src: item.url, alt: "" }),
234
265
  /* @__PURE__ */ jsx2("strong", { children: item.label }),
235
266
  onRemove ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove image ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) }) : null
236
267
  ] }, item.id ?? `${item.label}:${index}`)) });
237
268
  }
238
269
 
270
+ // src/intent.js
271
+ var CONFIRMABLE_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "branch", "reduce", "export"]);
272
+ async function dispatchConfirmedIntent(adapter, intent) {
273
+ if (CONFIRMABLE_ACTIONS.has(intent.action) && adapter.confirmIntent) {
274
+ const confirmed = await adapter.confirmIntent(intent);
275
+ if (!confirmed) return;
276
+ }
277
+ return adapter.onIntent(intent);
278
+ }
279
+
239
280
  // src/textarea.js
240
281
  import { useLayoutEffect } from "preact/hooks";
241
282
  function useAutosizeTextarea(ref, value) {
@@ -262,28 +303,29 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
262
303
  const terminal = resume && state.continuationModes?.includes("terminal");
263
304
  const join = state.canAttach;
264
305
  const branch = state.canBranch;
265
- if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
306
+ if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
266
307
  /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
267
308
  /* @__PURE__ */ jsx3("small", { children: "This session cannot be continued by an available harness." })
268
309
  ] }) });
269
- return /* @__PURE__ */ jsxs3("div", { class: "scui-continuation", children: [
310
+ return /* @__PURE__ */ jsxs3("div", { className: "scui-continuation", children: [
270
311
  /* @__PURE__ */ jsxs3("span", { children: [
271
312
  /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
272
313
  /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
273
314
  ] }),
274
- /* @__PURE__ */ jsxs3("span", { class: "scui-continuation-actions", children: [
275
- /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
276
- terminal ? /* @__PURE__ */ jsx3("button", { type: "button", class: "scui-secondary", disabled: Boolean(state.operation), onClick: () => adapter.onIntent({ action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
315
+ /* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
316
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
317
+ terminal ? /* @__PURE__ */ jsx3("button", { type: "button", className: "scui-secondary", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, { action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
277
318
  ] })
278
319
  ] });
279
320
  }
280
- function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
321
+ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
281
322
  const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
282
323
  const [draft, setDraft] = useState2(remembered.draft);
283
324
  const [context, setContext] = useState2(remembered.context ?? []);
284
325
  const [images, setImages] = useState2(remembered.images ?? []);
285
326
  const [queue, setQueue] = useState2((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
286
327
  const [dispatching, setDispatching] = useState2(false);
328
+ const [steering, setSteering] = useState2(false);
287
329
  const [picking, setPicking] = useState2(false);
288
330
  const [dragging, setDragging] = useState2(false);
289
331
  const [pickerError, setPickerError] = useState2(null);
@@ -298,8 +340,10 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
298
340
  remember(draft, context, images, next);
299
341
  return next;
300
342
  });
301
- const queueBlocked = state.busy || pendingStatus !== null || dispatching;
302
- const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
343
+ const queueBlocked = state.busy || pendingStatus !== null || dispatching || steering;
344
+ const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching || steering;
345
+ const steerAvailable = state.busy && state.canSteer && !steering && pendingStatus === null;
346
+ const canSteerDraft = steerAvailable && Boolean(draft.trim()) && context.length === 0 && images.length === 0;
303
347
  useEffect2(() => {
304
348
  if (!queueBlocked && state.canSend && queue.length) {
305
349
  const [next, ...rest] = queue;
@@ -351,6 +395,14 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
351
395
  });
352
396
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
353
397
  };
398
+ const attachCandidate = (picked) => {
399
+ const attachments = partitionAttachments(picked);
400
+ const nextContext = mergeContext(context, attachments.context);
401
+ const nextImages = mergeImages(images, attachments.images);
402
+ setContext(nextContext);
403
+ setImages(nextImages);
404
+ remember(draft, nextContext, nextImages, queue);
405
+ };
354
406
  const addImageFiles = (value, source) => {
355
407
  const allFiles = Array.from(value ?? []);
356
408
  if (!allFiles.length) return false;
@@ -379,9 +431,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
379
431
  setDragging(false);
380
432
  if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
381
433
  };
382
- const send = () => {
434
+ const send = (forceQueue = false) => {
383
435
  const text = draft.trim();
384
436
  if (!text && !images.length) return;
437
+ if (canSteerDraft && !forceQueue) {
438
+ setSteering(true);
439
+ Promise.resolve(adapter.onIntent({ action: "steer", text })).then(() => {
440
+ setDraft("");
441
+ remember("", context, images, queue);
442
+ }, (error) => setPickerError(error instanceof Error ? error.message : "Could not steer the active turn.")).finally(() => setSteering(false));
443
+ return;
444
+ }
385
445
  const message = { text, context, images };
386
446
  if (queuesNewMessage) updateQueue((items) => [...items, message]);
387
447
  else if (state.canSend) {
@@ -394,8 +454,8 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
394
454
  setImages([]);
395
455
  remember("", [], [], queuesNewMessage ? [...queue, message] : queue);
396
456
  };
397
- return /* @__PURE__ */ jsxs3("div", { class: "scui-compose", children: [
398
- queue.length ? /* @__PURE__ */ jsxs3("div", { class: "scui-queue", children: [
457
+ return /* @__PURE__ */ jsxs3("div", { className: "scui-compose", children: [
458
+ queue.length ? /* @__PURE__ */ jsxs3("div", { className: "scui-queue", children: [
399
459
  /* @__PURE__ */ jsxs3("strong", { children: [
400
460
  queue.length,
401
461
  " queued"
@@ -411,6 +471,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
411
471
  /* @__PURE__ */ jsx3("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: /* @__PURE__ */ jsx3(UiIcon, { name: "close", size: 13 }) })
412
472
  ] }, `${index}:${item.text}`))
413
473
  ] }) : null,
474
+ /* @__PURE__ */ jsx3(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }),
414
475
  /* @__PURE__ */ jsx3(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
415
476
  const next = items.filter((_, itemIndex) => itemIndex !== index);
416
477
  remember(draft, context, next, queue);
@@ -421,8 +482,8 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
421
482
  remember(draft, next, images, queue);
422
483
  return next;
423
484
  }) }),
424
- pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
425
- /* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
485
+ pickerError ? /* @__PURE__ */ jsx3("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
486
+ /* @__PURE__ */ jsxs3("div", { className: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
426
487
  if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
427
488
  event.preventDefault();
428
489
  setDragging(true);
@@ -432,8 +493,8 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
432
493
  }, onDragLeave: (event) => {
433
494
  if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
434
495
  }, onDrop: dropImages, children: [
435
- adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
436
- /* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onPaste: pasteImages, onInput: (event) => {
496
+ adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { className: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
497
+ /* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : steerAvailable && context.length === 0 && images.length === 0 ? "Redirect the current turn\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onPaste: pasteImages, onInput: (event) => {
437
498
  const value = event.currentTarget.value;
438
499
  setDraft(value);
439
500
  remember(value, context, images, queue);
@@ -444,8 +505,9 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
444
505
  }
445
506
  } }),
446
507
  /* @__PURE__ */ jsxs3("span", { children: [
447
- state.busy ? /* @__PURE__ */ jsx3("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx3(UiIcon, { name: "stop", size: 15 }) }) : null,
448
- /* @__PURE__ */ jsx3("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() && !images.length || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx3(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
508
+ state.busy ? /* @__PURE__ */ jsx3("button", { className: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx3(UiIcon, { name: "stop", size: 15 }) }) : null,
509
+ canSteerDraft ? /* @__PURE__ */ jsx3("button", { className: "scui-queue-send", type: "button", "aria-label": "Queue follow-up instead", onClick: () => send(true), children: /* @__PURE__ */ jsx3(UiIcon, { name: "plus", size: 15 }) }) : null,
510
+ /* @__PURE__ */ jsx3("button", { className: "scui-send", type: "button", "aria-label": canSteerDraft ? "Steer current turn" : queuesNewMessage ? "Queue message" : "Send message", disabled: steering || !draft.trim() && !images.length || !queuesNewMessage && !state.canSend, onClick: () => send(), children: steering ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: canSteerDraft ? "send" : queuesNewMessage ? "plus" : "send", size: 17 }) })
449
511
  ] })
450
512
  ] })
451
513
  ] });
package/controller.d.ts CHANGED
@@ -115,6 +115,10 @@ export interface ControllerBindingOptions {
115
115
  onResumeTerminal?: (intent: Extract<SupercodeUiIntent, { action: 'resume' }>) => void | Promise<void>;
116
116
  copyText?: UiAdapter['copyText'];
117
117
  resolveImage?: UiAdapter['resolveImage'];
118
+ confirmIntent?: UiAdapter['confirmIntent'];
119
+ pickContext?: UiAdapter['pickContext'];
120
+ onClose?: UiAdapter['onClose'];
121
+ onOpen?: UiAdapter['onOpen'];
118
122
  }
119
123
 
120
124
  export interface ControllerIntentTarget {
package/controller.mjs CHANGED
@@ -470,6 +470,7 @@ function projectClientSnapshotInternal(snapshot, options, imageRegistry) {
470
470
  mode: snapshot.connection?.mode ?? 'none',
471
471
  strategy: snapshot.connection?.strategy ?? null,
472
472
  canSend: actions.send === true,
473
+ canSteer: actions.steer === true,
473
474
  canResume: actions.resume === true,
474
475
  continuationModes: options.continuationModes ?? (actions.resume === true ? ['headless'] : []),
475
476
  canBranch: actions.branch === true,
@@ -508,6 +509,19 @@ function projectClientSnapshotInternal(snapshot, options, imageRegistry) {
508
509
  installed: harness.installed === true,
509
510
  startable: harness.availableActions?.start === true,
510
511
  reason: harness.reason ?? null,
512
+ auth: harness.auth,
513
+ runtime: harness.runtime,
514
+ protocol: harness.protocol,
515
+ repair: harness.repair ?? null,
516
+ capabilities: {
517
+ start: harness.availableActions?.start === true,
518
+ resume: harness.availableActions?.resume === true,
519
+ attach: harness.availableActions?.attach === true,
520
+ send: harness.availableActions?.send === true,
521
+ interrupt: harness.availableActions?.interrupt === true,
522
+ steer: harness.availableActions?.steer === true,
523
+ respond: harness.availableActions?.respond === true,
524
+ },
511
525
  })),
512
526
  history: {
513
527
  sessionLimit: options.history?.sessionLimit ?? sessions.length,
@@ -552,6 +566,7 @@ export async function dispatchControllerIntent(controller, intent, options = {})
552
566
  if (intent.action === 'mounted') return;
553
567
  if (intent.action === 'attach') return controller.dispatch({ type: 'observe', sessionKey: intent.key });
554
568
  if (intent.action === 'send') return controller.dispatch({ type: 'send', text: intent.text, ...(intent.context?.length ? { context: intent.context } : {}), ...(intent.images?.length ? { images: intent.images } : {}) });
569
+ if (intent.action === 'steer') return controller.dispatch({ type: 'steer', text: intent.text });
555
570
  if (intent.action === 'new') {
556
571
  if (intent.mode === 'terminal') {
557
572
  return options.onStartTerminal
@@ -608,6 +623,10 @@ export function createControllerBinding(controller, options = {}) {
608
623
  },
609
624
  ...(options.copyText ? { copyText: options.copyText } : {}),
610
625
  ...(options.resolveImage ? { resolveImage: options.resolveImage } : {}),
626
+ ...(options.confirmIntent ? { confirmIntent: options.confirmIntent } : {}),
627
+ ...(options.pickContext ? { pickContext: options.pickContext } : {}),
628
+ ...(options.onClose ? { onClose: options.onClose } : {}),
629
+ ...(options.onOpen ? { onOpen: options.onOpen } : {}),
611
630
  };
612
631
  return {
613
632
  adapter,