@yeaft/webchat-agent 0.1.505 → 0.1.506

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.
@@ -25,7 +25,7 @@ import { sendToServer, flushMessageBuffer } from './buffer.js';
25
25
  import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
26
26
  import { loadMcpServers, updateMcpConfig } from '../mcp.js';
27
27
  import { getLlmConfig, updateLlmConfig, getUnifySettings, updateUnifySettings } from '../unify/config-api.js';
28
- import { handleUnifyChat, handleUnifyModeSwitch, handleUnifyModelSwitch, resetUnifySession, handleUnifyLoadHistory, handleUnifyMergeThread, handleUnifyForkThread } from '../unify/web-bridge.js';
28
+ import { handleUnifyChat, handleUnifyModeSwitch, handleUnifyModelSwitch, resetUnifySession, handleUnifyLoadHistory, handleUnifyMergeThread, handleUnifyForkThread, handleUnifyAbortThread, handleUnifyAbortAll } from '../unify/web-bridge.js';
29
29
 
30
30
  export async function handleMessage(msg) {
31
31
  switch (msg.type) {
@@ -373,6 +373,19 @@ export async function handleMessage(msg) {
373
373
  handleUnifyForkThread(msg);
374
374
  break;
375
375
 
376
+ case 'unify_abort_thread':
377
+ // task-325c: user-initiated abort of a single thread's in-flight
378
+ // query. Payload `{ threadId }`. Silent no-op when the thread has
379
+ // no in-flight controller.
380
+ handleUnifyAbortThread(msg);
381
+ break;
382
+
383
+ case 'unify_abort_all':
384
+ // task-325c: user-initiated abort of ALL in-flight queries across
385
+ // every thread. Always emits `unify_aborted` ack.
386
+ handleUnifyAbortAll();
387
+ break;
388
+
376
389
  // Expert roles definition (for ExpertPanel detail view)
377
390
  case 'get_expert_roles': {
378
391
  const { getExpertRolesDefinition } = await import('../expert-roles.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.505",
3
+ "version": "0.1.506",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/unify/session.js CHANGED
@@ -269,5 +269,13 @@ export async function loadSession(options = {}) {
269
269
  threadStore: getThreadStore(),
270
270
  status,
271
271
  shutdown,
272
+ // task-325c: user-initiated abort API. Delegates to web-bridge which
273
+ // owns the per-thread AbortController registry (`abortByThread`).
274
+ // Lazy-imported to avoid a hard cycle with web-bridge.js (which already
275
+ // imports this module to call loadSession).
276
+ async abort(opts = {}) {
277
+ const { abortUnifySession } = await import('./web-bridge.js');
278
+ return abortUnifySession(opts);
279
+ },
272
280
  };
273
281
  }
@@ -848,6 +848,90 @@ export async function handleUnifyChat(msg) {
848
848
  }
849
849
  }
850
850
 
851
+ /**
852
+ * task-325c: user-initiated abort of an in-flight Unify query on ONE thread.
853
+ *
854
+ * Cancels the AbortController registered for `msg.threadId` (if any). Silent
855
+ * no-op when the thread has no in-flight round — users clicking Stop on an
856
+ * already-idle thread should not trigger an error bubble. Emits an
857
+ * `unify_aborted` event for UI acknowledgement and a fresh
858
+ * `thread_list_updated` so inflight pills clear immediately.
859
+ *
860
+ * Red line (PM): the `thread_list_updated` event name is preserved; no
861
+ * new per-thread abort signal leaks into `Engine.abort()`'s signature.
862
+ *
863
+ * @param {{ threadId?: string }} msg
864
+ * @returns {{ aborted: string[], all: boolean }}
865
+ */
866
+ export function handleUnifyAbortThread(msg = {}) {
867
+ const aborted = [];
868
+ const threadId = msg && msg.threadId;
869
+ if (threadId) {
870
+ const ctrl = abortByThread.get(threadId);
871
+ if (ctrl) {
872
+ try { ctrl.abort(); } catch { /* best-effort */ }
873
+ abortByThread.delete(threadId);
874
+ aborted.push(threadId);
875
+ }
876
+ }
877
+ sendUnifyEvent({ type: 'unify_aborted', aborted, all: false });
878
+ sendThreadListUpdate();
879
+ return { aborted, all: false };
880
+ }
881
+
882
+ /**
883
+ * task-325c: user-initiated abort of ALL in-flight Unify queries.
884
+ *
885
+ * Iterates every registered controller, aborts it, then clears the map.
886
+ * Always emits `unify_aborted` with `all:true` (even when nothing was
887
+ * running) so the UI can confirm the click landed.
888
+ *
889
+ * @returns {{ aborted: string[], all: boolean }}
890
+ */
891
+ export function handleUnifyAbortAll() {
892
+ const aborted = [];
893
+ for (const [threadId, ctrl] of abortByThread.entries()) {
894
+ try { ctrl.abort(); } catch { /* best-effort */ }
895
+ aborted.push(threadId);
896
+ }
897
+ abortByThread.clear();
898
+ sendUnifyEvent({ type: 'unify_aborted', aborted, all: true });
899
+ sendThreadListUpdate();
900
+ return { aborted, all: true };
901
+ }
902
+
903
+ /**
904
+ * Unified dispatcher bound onto `session.abort({ threadId?, all? })`.
905
+ * Routes to {@link handleUnifyAbortThread} or {@link handleUnifyAbortAll}
906
+ * per input. Kept exported so message-router and tests can call it too.
907
+ *
908
+ * @param {{ threadId?: string, all?: boolean }} [opts]
909
+ */
910
+ export function abortUnifySession(opts = {}) {
911
+ if (opts && opts.all) return handleUnifyAbortAll();
912
+ if (opts && opts.threadId) return handleUnifyAbortThread({ threadId: opts.threadId });
913
+ // No payload — conservative default: abort nothing, just emit ack so
914
+ // callers see the no-op round-trip. Matches PM "don't accidentally
915
+ // nuke everything on a bare click".
916
+ sendUnifyEvent({ type: 'unify_aborted', aborted: [], all: false });
917
+ return { aborted: [], all: false };
918
+ }
919
+
920
+ /**
921
+ * Test-only: seed / inspect the abort registry without spinning up a
922
+ * full session. Never use from production code — the prod registry is
923
+ * managed by handleUnifyChat's per-query controller lifecycle.
924
+ * @private
925
+ */
926
+ export function __testSeedAbortController(threadId, ctrl) {
927
+ abortByThread.set(threadId, ctrl);
928
+ }
929
+
930
+ /** Test-only: returns the set of thread ids currently registered. */
931
+ export function __testGetRegisteredThreadIds() {
932
+ return [...abortByThread.keys()];
933
+ }
934
+
851
935
  /**
852
936
  * Handle mode switch from the web UI.
853
937
  * DEPRECATED (task-297): Unify no longer has chat/work mode distinction.