@workerdeck/react 3.0.1 → 3.1.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/build/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
2
- import { DEFAULT_PRICING, ENGINE_CAPABILITIES, PEER_MENTION_SIGIL, PROMPT_SENTENCE_TAIL, PROTOCOL_VERSION, isPeerMentionBody, mergePricing, mergeUsage, orderUsageWindows, peerMentionKey, peerMentionSlug, sessionLabel } from "@workerdeck/protocol";
1
+ import { useCallback, useEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
3
2
  import { WorkerDeckError } from "@workerdeck/client";
3
+ import { DEFAULT_PRICING, ENGINE_CAPABILITIES, PEER_MENTION_SIGIL, PROMPT_SENTENCE_TAIL, PROTOCOL_VERSION, errorMessage, isPeerMentionBody, mergePricing, mergeUsage, orderUsageWindows, peerMentionKey, peerMentionSlug, sessionLabel } from "@workerdeck/protocol";
4
4
  //#region src/lib/transcript.ts
5
5
  const initialTranscriptState = {
6
6
  status: "starting",
@@ -94,13 +94,22 @@ function slashCommandText(text) {
94
94
  const args = COMMAND_ARGS.exec(text)?.[1]?.trim();
95
95
  return args ? `${name} ${args}` : name;
96
96
  }
97
- function upsert(items, item) {
98
- const index = items.findIndex((existing) => existing.id === item.id && existing.kind === item.kind);
97
+ function findLastIndexOf(items, kind, id) {
98
+ for (let index = items.length - 1; index >= 0; index--) {
99
+ const item = items[index];
100
+ if (item?.id === id && item.kind === kind) return index;
101
+ }
102
+ return -1;
103
+ }
104
+ function replaceAt(items, index, item) {
99
105
  if (index === -1) return [...items, item];
100
106
  const next = [...items];
101
107
  next[index] = item;
102
108
  return next;
103
109
  }
110
+ function upsert(items, item) {
111
+ return replaceAt(items, items.findIndex((existing) => existing.id === item.id && existing.kind === item.kind), item);
112
+ }
104
113
  function seedFromSessionInfo(state, info) {
105
114
  const engine = info.engine ?? state.engine;
106
115
  return {
@@ -372,21 +381,23 @@ function applyEvent(state, event) {
372
381
  if (delta.type !== "content_block_delta") return base;
373
382
  if (delta.delta?.type === "text_delta") {
374
383
  const id = streamingTextId(event.parentToolUseId);
384
+ const index = findLastIndexOf(base.items, "assistant_text", id);
375
385
  const item = {
376
386
  kind: "assistant_text",
377
387
  id,
378
- text: (base.items.find((item) => item.kind === "assistant_text" && item.id === id)?.text ?? "") + (delta.delta.text ?? ""),
388
+ text: ((index === -1 ? void 0 : base.items[index])?.text ?? "") + (delta.delta.text ?? ""),
379
389
  streaming: true,
380
390
  parentToolUseId: event.parentToolUseId
381
391
  };
382
392
  return {
383
393
  ...base,
384
- items: upsert(base.items, item)
394
+ items: replaceAt(base.items, index, item)
385
395
  };
386
396
  }
387
397
  if (delta.delta?.type === "thinking_delta") {
388
398
  const id = streamingThinkingId(event.parentToolUseId);
389
- const text = (base.items.find((item) => item.kind === "thinking" && item.id === id)?.text ?? "") + (delta.delta.thinking ?? "");
399
+ const index = findLastIndexOf(base.items, "thinking", id);
400
+ const text = ((index === -1 ? void 0 : base.items[index])?.text ?? "") + (delta.delta.thinking ?? "");
390
401
  if (text.trim() === "") return base;
391
402
  const item = {
392
403
  kind: "thinking",
@@ -396,7 +407,7 @@ function applyEvent(state, event) {
396
407
  };
397
408
  return {
398
409
  ...base,
399
- items: upsert(base.items, item)
410
+ items: replaceAt(base.items, index, item)
400
411
  };
401
412
  }
402
413
  return base;
@@ -697,8 +708,8 @@ function useClaudeSession(client, sessionId, options) {
697
708
  text
698
709
  });
699
710
  return true;
700
- } catch {
701
- dispatch({
711
+ } catch (error) {
712
+ if (isNotFound(error)) dispatch({
702
713
  type: "transcript_hydrate_shell",
703
714
  shellId,
704
715
  shell: void 0
@@ -707,8 +718,9 @@ function useClaudeSession(client, sessionId, options) {
707
718
  }
708
719
  }, [client, sessionId]);
709
720
  const verifyShell = useCallback(async (shellId) => {
710
- if (!sessionId || verifiedRef.current.has(shellId)) return false;
711
- verifiedRef.current.add(shellId);
721
+ const verified = verifiedRef.current;
722
+ if (!sessionId || verified.has(shellId)) return false;
723
+ verified.add(shellId);
712
724
  try {
713
725
  const record = await client.getShell(sessionId, shellId);
714
726
  dispatch({
@@ -717,12 +729,13 @@ function useClaudeSession(client, sessionId, options) {
717
729
  shell: record
718
730
  });
719
731
  return true;
720
- } catch {
721
- dispatch({
732
+ } catch (error) {
733
+ if (isNotFound(error)) dispatch({
722
734
  type: "transcript_hydrate_shell",
723
735
  shellId,
724
736
  shell: void 0
725
737
  });
738
+ else verified.delete(shellId);
726
739
  return false;
727
740
  }
728
741
  }, [client, sessionId]);
@@ -754,6 +767,17 @@ function useClaudeSession(client, sessionId, options) {
754
767
  return false;
755
768
  }
756
769
  }, [client, sessionId]);
770
+ const actions = useMemo(() => ({
771
+ send: (text, attachmentIds) => handleRef.current?.send(text, attachmentIds),
772
+ approve: (requestId, updatedInput) => handleRef.current?.approve(requestId, updatedInput),
773
+ deny: (requestId, message, interrupt) => handleRef.current?.deny(requestId, message, interrupt),
774
+ interrupt: () => handleRef.current?.interrupt(),
775
+ clearContext: () => handleRef.current?.clearContext(),
776
+ runShell: (command) => handleRef.current?.runShell(command),
777
+ setPermissionMode: (mode) => handleRef.current?.setPermissionMode(mode),
778
+ setModel: (model) => handleRef.current?.setModel(model),
779
+ closeSession: () => handleRef.current?.closeSession()
780
+ }), []);
757
781
  return useMemo(() => ({
758
782
  state,
759
783
  connected,
@@ -765,15 +789,7 @@ function useClaudeSession(client, sessionId, options) {
765
789
  models,
766
790
  effectiveModel: state.model ?? state.defaultModel,
767
791
  handle: handleState,
768
- send: (text, attachmentIds) => handleRef.current?.send(text, attachmentIds),
769
- approve: (requestId, updatedInput) => handleRef.current?.approve(requestId, updatedInput),
770
- deny: (requestId, message, interrupt) => handleRef.current?.deny(requestId, message, interrupt),
771
- interrupt: () => handleRef.current?.interrupt(),
772
- clearContext: () => handleRef.current?.clearContext(),
773
- runShell: (command) => handleRef.current?.runShell(command),
774
- setPermissionMode: (mode) => handleRef.current?.setPermissionMode(mode),
775
- setModel: (model) => handleRef.current?.setModel(model),
776
- closeSession: () => handleRef.current?.closeSession(),
792
+ ...actions,
777
793
  reconnectNow,
778
794
  loadFullResult,
779
795
  loadShellOutput,
@@ -790,6 +806,7 @@ function useClaudeSession(client, sessionId, options) {
790
806
  pricing,
791
807
  models,
792
808
  handleState,
809
+ actions,
793
810
  reconnectNow,
794
811
  loadFullResult,
795
812
  loadShellOutput,
@@ -798,6 +815,9 @@ function useClaudeSession(client, sessionId, options) {
798
815
  setShellAgentWrite
799
816
  ]);
800
817
  }
818
+ function isNotFound(error) {
819
+ return error instanceof WorkerDeckError && error.status === 404;
820
+ }
801
821
  function useProfileModelFallback(client, sessionId, state) {
802
822
  const [catalog, setCatalog] = useState([]);
803
823
  const profile = state.session?.profile;
@@ -841,7 +861,7 @@ function clearProfileUsageCache() {
841
861
  const KEY = "workerdeck.drafts.v1";
842
862
  const MAX_DRAFTS = 20;
843
863
  let memory;
844
- function storage() {
864
+ function storage$1() {
845
865
  try {
846
866
  return globalThis.localStorage;
847
867
  } catch {
@@ -851,7 +871,7 @@ function storage() {
851
871
  function load() {
852
872
  if (memory) return memory;
853
873
  memory = {};
854
- const raw = storage()?.getItem(KEY);
874
+ const raw = storage$1()?.getItem(KEY);
855
875
  if (raw) try {
856
876
  const parsed = JSON.parse(raw);
857
877
  for (const [key, draft] of Object.entries(parsed)) if (typeof draft?.text === "string" && typeof draft.savedAt === "number") memory[key] = draft;
@@ -865,7 +885,7 @@ function persist(drafts) {
865
885
  for (const [key] of entries.slice(MAX_DRAFTS)) delete drafts[key];
866
886
  }
867
887
  try {
868
- storage()?.setItem(KEY, JSON.stringify(drafts));
888
+ storage$1()?.setItem(KEY, JSON.stringify(drafts));
869
889
  } catch {}
870
890
  }
871
891
  function draftKey(client, sessionId) {
@@ -888,7 +908,7 @@ function writeDraft(key, text, now = Date.now()) {
888
908
  function clearDrafts() {
889
909
  memory = {};
890
910
  try {
891
- storage()?.removeItem(KEY);
911
+ storage$1()?.removeItem(KEY);
892
912
  } catch {}
893
913
  }
894
914
  //#endregion
@@ -951,7 +971,7 @@ function useAttachments(client, sessionId, { capabilities, engine }) {
951
971
  } catch (e) {
952
972
  patch(key, {
953
973
  status: "failed",
954
- error: e instanceof Error ? e.message : "Upload failed"
974
+ error: errorMessage(e, "Upload failed")
955
975
  });
956
976
  }
957
977
  })();
@@ -1140,6 +1160,58 @@ function useAliveRef() {
1140
1160
  function isRouteUnsupported(e) {
1141
1161
  return e instanceof WorkerDeckError && e.status === 404;
1142
1162
  }
1163
+ function useAsync(load, deps, { enabled = true, pollMs } = {}) {
1164
+ const [data, setData] = useState();
1165
+ const [error, setError] = useState();
1166
+ const [loading, setLoading] = useState(enabled);
1167
+ const generation = useRef(0);
1168
+ const loadRef = useRef(load);
1169
+ loadRef.current = load;
1170
+ const run = useCallback(async () => {
1171
+ const id = ++generation.current;
1172
+ setLoading(true);
1173
+ try {
1174
+ const next = await loadRef.current();
1175
+ if (id === generation.current) {
1176
+ setData(() => next);
1177
+ setError(void 0);
1178
+ }
1179
+ } catch (e) {
1180
+ if (id === generation.current) setError(e);
1181
+ } finally {
1182
+ if (id === generation.current) setLoading(false);
1183
+ }
1184
+ }, []);
1185
+ useEffect(() => {
1186
+ setData(void 0);
1187
+ setError(void 0);
1188
+ }, deps);
1189
+ useEffect(() => {
1190
+ if (!enabled) {
1191
+ generation.current++;
1192
+ setLoading(false);
1193
+ return;
1194
+ }
1195
+ run();
1196
+ const timer = pollMs ? setInterval(() => void run(), pollMs) : void 0;
1197
+ return () => {
1198
+ generation.current++;
1199
+ clearInterval(timer);
1200
+ };
1201
+ }, [
1202
+ enabled,
1203
+ pollMs,
1204
+ run,
1205
+ ...deps
1206
+ ]);
1207
+ return {
1208
+ data,
1209
+ error,
1210
+ loading,
1211
+ reload: run,
1212
+ setData
1213
+ };
1214
+ }
1143
1215
  //#endregion
1144
1216
  //#region src/lib/host-tree.ts
1145
1217
  function flattenHostTree(root, dirs, expanded) {
@@ -1287,7 +1359,7 @@ function useHostFileTree(client, cwd) {
1287
1359
  setUnsupported(true);
1288
1360
  return;
1289
1361
  }
1290
- setError(e instanceof Error ? e.message : "Could not read that directory");
1362
+ setError(errorMessage(e, "Could not read that directory"));
1291
1363
  });
1292
1364
  }, [client, unsupported]);
1293
1365
  useEffect(() => {
@@ -1337,7 +1409,7 @@ function useHostFileTree(client, cwd) {
1337
1409
  }
1338
1410
  //#endregion
1339
1411
  //#region src/hooks/use-peer-sessions.ts
1340
- const EMPTY = {
1412
+ const EMPTY$1 = {
1341
1413
  available: false,
1342
1414
  peers: [],
1343
1415
  names: []
@@ -1370,7 +1442,7 @@ function usePeerSessions(client, sessionId, enabled = true) {
1370
1442
  unsupported
1371
1443
  ]);
1372
1444
  return useMemo(() => {
1373
- if (!enabled || unsupported || !sessions) return EMPTY;
1445
+ if (!enabled || unsupported || !sessions) return EMPTY$1;
1374
1446
  const peers = sessions.filter((info) => info.id !== sessionId).sort((a, b) => (b.lastActivityAt ?? b.createdAt) - (a.lastActivityAt ?? a.createdAt)).map((info) => ({
1375
1447
  id: info.id,
1376
1448
  slug: peerMentionSlug(info.title, info.id),
@@ -1424,6 +1496,111 @@ function useProjectIcons(rows, clientFor) {
1424
1496
  return resolved;
1425
1497
  }
1426
1498
  //#endregion
1499
+ //#region src/lib/bookmark-store.ts
1500
+ const BOOKMARKS_STORAGE_KEY = "workerdeck.bookmarks.v1";
1501
+ const EMPTY = Object.freeze([]);
1502
+ const listeners = /* @__PURE__ */ new Set();
1503
+ let loaded;
1504
+ let detachStorageEvent;
1505
+ function storage() {
1506
+ try {
1507
+ return globalThis.localStorage ?? void 0;
1508
+ } catch {
1509
+ return;
1510
+ }
1511
+ }
1512
+ function storageEvents() {
1513
+ const target = globalThis.window;
1514
+ return typeof target?.addEventListener === "function" ? target : void 0;
1515
+ }
1516
+ function readStored() {
1517
+ try {
1518
+ const raw = storage()?.getItem(BOOKMARKS_STORAGE_KEY);
1519
+ if (raw == null) return {};
1520
+ const parsed = JSON.parse(raw);
1521
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
1522
+ const map = {};
1523
+ for (const [key, value] of Object.entries(parsed)) if (Array.isArray(value)) map[key] = value.filter((id) => typeof id === "string");
1524
+ return map;
1525
+ } catch {
1526
+ return;
1527
+ }
1528
+ }
1529
+ function sameIds(a, b) {
1530
+ return a.length === b.length && a.every((id, index) => id === b[index]);
1531
+ }
1532
+ function adopt(next) {
1533
+ const previous = loaded ?? {};
1534
+ const merged = {};
1535
+ for (const [key, ids] of Object.entries(next)) {
1536
+ const held = previous[key];
1537
+ merged[key] = held && sameIds(held, ids) ? held : ids;
1538
+ }
1539
+ loaded = merged;
1540
+ }
1541
+ function current() {
1542
+ if (loaded === void 0) adopt(readStored() ?? {});
1543
+ return loaded ?? {};
1544
+ }
1545
+ function notify() {
1546
+ for (const listener of listeners) listener();
1547
+ }
1548
+ function bookmarksFor(sessionKey) {
1549
+ return current()[sessionKey] ?? EMPTY;
1550
+ }
1551
+ function toggleBookmark(sessionKey, itemId) {
1552
+ const base = readStored() ?? current();
1553
+ const held = base[sessionKey] ?? EMPTY;
1554
+ const ids = held.includes(itemId) ? held.filter((id) => id !== itemId) : [...held, itemId];
1555
+ const next = {
1556
+ ...base,
1557
+ [sessionKey]: ids
1558
+ };
1559
+ if (ids.length === 0) delete next[sessionKey];
1560
+ try {
1561
+ storage()?.setItem(BOOKMARKS_STORAGE_KEY, JSON.stringify(next));
1562
+ } catch {}
1563
+ adopt(next);
1564
+ notify();
1565
+ }
1566
+ function onStorage(event) {
1567
+ if (event.key !== null && event.key !== "workerdeck.bookmarks.v1") return;
1568
+ const stored = readStored();
1569
+ if (stored) {
1570
+ adopt(stored);
1571
+ notify();
1572
+ }
1573
+ }
1574
+ function subscribeBookmarks(listener) {
1575
+ listeners.add(listener);
1576
+ const target = storageEvents();
1577
+ if (listeners.size === 1 && target) {
1578
+ target.addEventListener("storage", onStorage);
1579
+ detachStorageEvent = () => target.removeEventListener("storage", onStorage);
1580
+ }
1581
+ return () => {
1582
+ listeners.delete(listener);
1583
+ if (listeners.size === 0) {
1584
+ detachStorageEvent?.();
1585
+ detachStorageEvent = void 0;
1586
+ }
1587
+ };
1588
+ }
1589
+ //#endregion
1590
+ //#region src/hooks/use-bookmarks.ts
1591
+ const NONE = Object.freeze([]);
1592
+ function useBookmarks(sessionKey) {
1593
+ const read = useCallback(() => sessionKey ? bookmarksFor(sessionKey) : NONE, [sessionKey]);
1594
+ const bookmarks = useSyncExternalStore(subscribeBookmarks, read, () => NONE);
1595
+ const toggle = useCallback((itemId) => {
1596
+ if (sessionKey) toggleBookmark(sessionKey, itemId);
1597
+ }, [sessionKey]);
1598
+ return useMemo(() => ({
1599
+ bookmarks,
1600
+ toggle
1601
+ }), [bookmarks, toggle]);
1602
+ }
1603
+ //#endregion
1427
1604
  //#region src/hooks/use-draft.ts
1428
1605
  function useDraft(client, sessionId) {
1429
1606
  const key = sessionId ? draftKey(client, sessionId) : void 0;
@@ -1493,37 +1670,11 @@ function useProfileUsage(client, profile, options = {}) {
1493
1670
  //#endregion
1494
1671
  //#region src/hooks/use-session-info.ts
1495
1672
  function useSessionInfo(client, sessionId) {
1496
- const [info, setInfo] = useState();
1497
- const [loading, setLoading] = useState(!!sessionId);
1498
- const [error, setError] = useState();
1499
- useEffect(() => {
1500
- if (!sessionId) {
1501
- setInfo(void 0);
1502
- setLoading(false);
1503
- setError(void 0);
1504
- return;
1505
- }
1506
- let cancelled = false;
1507
- setLoading(true);
1508
- setError(void 0);
1509
- setInfo(void 0);
1510
- client.getSession(sessionId).then((next) => {
1511
- if (cancelled) return;
1512
- setInfo(next);
1513
- setLoading(false);
1514
- }).catch((e) => {
1515
- if (cancelled) return;
1516
- setError(e instanceof Error ? e.message : "Session not found");
1517
- setLoading(false);
1518
- });
1519
- return () => {
1520
- cancelled = true;
1521
- };
1522
- }, [client, sessionId]);
1673
+ const { data, error, loading } = useAsync(() => client.getSession(sessionId), [client, sessionId], { enabled: !!sessionId });
1523
1674
  return {
1524
- info,
1675
+ info: data,
1525
1676
  loading,
1526
- error
1677
+ error: error === void 0 ? void 0 : errorMessage(error, "Session not found")
1527
1678
  };
1528
1679
  }
1529
1680
  //#endregion
@@ -1682,7 +1833,7 @@ function useOpenFiles(client) {
1682
1833
  dispatch({
1683
1834
  type: "failed",
1684
1835
  path,
1685
- error: e instanceof Error ? e.message : "Could not read that file"
1836
+ error: errorMessage(e, "Could not read that file")
1686
1837
  });
1687
1838
  });
1688
1839
  }
@@ -1726,7 +1877,7 @@ function useOpenFiles(client) {
1726
1877
  dispatch({
1727
1878
  type: "failed",
1728
1879
  path,
1729
- error: e instanceof Error ? e.message : "Could not re-read that file"
1880
+ error: errorMessage(e, "Could not re-read that file")
1730
1881
  });
1731
1882
  });
1732
1883
  }, [read]);
@@ -1753,7 +1904,7 @@ function useOpenFiles(client) {
1753
1904
  type: "saveFailed",
1754
1905
  path,
1755
1906
  conflict,
1756
- error: conflict ? "This file changed on disk since you opened it." : e instanceof Error ? e.message : "Could not save that file"
1907
+ error: conflict ? "This file changed on disk since you opened it." : errorMessage(e, "Could not save that file")
1757
1908
  });
1758
1909
  }
1759
1910
  }, [client]);
@@ -1783,7 +1934,7 @@ function useOpenFiles(client) {
1783
1934
  dispatch({
1784
1935
  type: "saveFailed",
1785
1936
  path,
1786
- error: e instanceof Error ? e.message : "Could not save that file"
1937
+ error: errorMessage(e, "Could not save that file")
1787
1938
  });
1788
1939
  }
1789
1940
  }, [client, write]);
@@ -1812,19 +1963,33 @@ function createToolCallHost(handle, options = {}) {
1812
1963
  let enginePromise;
1813
1964
  let disposed = false;
1814
1965
  const track = (execution) => options.onExecution?.(execution);
1815
- const refuse = (frame, reason, error, startedAt) => {
1816
- handle.sendToolCallError(frame.executionId, reason, error);
1817
- track({
1818
- executionId: frame.executionId,
1819
- toolName: frame.toolName,
1820
- status: "failed",
1821
- reason,
1822
- startedAt,
1823
- endedAt: Date.now()
1824
- });
1966
+ const settle = (frame, startedAt, outcome) => {
1967
+ const { executionId, toolName } = frame;
1968
+ if (outcome.ok) {
1969
+ handle.sendToolCallResult(executionId, {
1970
+ type: "json",
1971
+ value: outcome.value
1972
+ }, outcome.logs);
1973
+ track({
1974
+ executionId,
1975
+ toolName,
1976
+ status: "settled",
1977
+ startedAt,
1978
+ endedAt: Date.now()
1979
+ });
1980
+ } else {
1981
+ handle.sendToolCallError(executionId, outcome.reason, outcome.error, outcome.logs);
1982
+ track({
1983
+ executionId,
1984
+ toolName,
1985
+ status: "failed",
1986
+ reason: outcome.reason,
1987
+ startedAt,
1988
+ endedAt: Date.now()
1989
+ });
1990
+ }
1825
1991
  };
1826
- const runClientTool = async (frame, handler) => {
1827
- const startedAt = Date.now();
1992
+ const execute = async (frame, startedAt, work) => {
1828
1993
  const controller = new AbortController();
1829
1994
  inFlight.set(frame.executionId, controller);
1830
1995
  track({
@@ -1833,116 +1998,94 @@ function createToolCallHost(handle, options = {}) {
1833
1998
  status: "running",
1834
1999
  startedAt
1835
2000
  });
2001
+ let outcome;
1836
2002
  try {
1837
- const result = await handler(frame.input, {
1838
- executionId: frame.executionId,
1839
- signal: controller.signal
1840
- });
1841
- if (disposed || !inFlight.has(frame.executionId)) return;
1842
- if ("error" in result) {
1843
- handle.sendToolCallError(frame.executionId, result.reason ?? "client_error", result.error);
1844
- track({
1845
- executionId: frame.executionId,
1846
- toolName: frame.toolName,
1847
- status: "failed",
1848
- reason: result.reason ?? "client_error",
1849
- startedAt,
1850
- endedAt: Date.now()
1851
- });
1852
- } else {
1853
- handle.sendToolCallResult(frame.executionId, {
1854
- type: "json",
1855
- value: result.value
1856
- });
1857
- track({
1858
- executionId: frame.executionId,
1859
- toolName: frame.toolName,
1860
- status: "settled",
1861
- startedAt,
1862
- endedAt: Date.now()
1863
- });
1864
- }
2003
+ outcome = await work(controller.signal);
1865
2004
  } catch (error) {
1866
- if (disposed || !inFlight.has(frame.executionId)) return;
1867
- refuse(frame, "host_error", error instanceof Error ? error.message : String(error), startedAt);
2005
+ outcome = {
2006
+ ok: false,
2007
+ reason: "host_error",
2008
+ error: errorMessage(error)
2009
+ };
2010
+ }
2011
+ try {
2012
+ if (!disposed && inFlight.has(frame.executionId)) settle(frame, startedAt, outcome);
1868
2013
  } finally {
1869
2014
  inFlight.delete(frame.executionId);
1870
2015
  }
1871
2016
  };
2017
+ const runClientTool = (frame, handler, startedAt) => execute(frame, startedAt, async (signal) => {
2018
+ const result = await handler(frame.input, {
2019
+ executionId: frame.executionId,
2020
+ signal
2021
+ });
2022
+ return "error" in result ? {
2023
+ ok: false,
2024
+ reason: result.reason ?? "client_error",
2025
+ error: result.error
2026
+ } : {
2027
+ ok: true,
2028
+ value: result.value
2029
+ };
2030
+ });
2031
+ const runScript = (frame, script, startedAt) => execute(frame, startedAt, async (signal) => {
2032
+ const sandbox = await import("@workerdeck/sandbox");
2033
+ const vfs = sandbox.createVfs(frame.vfsSeed);
2034
+ const timeoutMs = Math.min(frame.limits?.timeoutMs ?? Number.POSITIVE_INFINITY, options.timeoutMs ?? 5e3);
2035
+ const memoryLimitBytes = Math.min(frame.limits?.memoryLimitBytes ?? Number.POSITIVE_INFINITY, options.memoryLimitBytes ?? 67108864);
2036
+ let result;
2037
+ if (options.execute) result = await options.execute({
2038
+ script,
2039
+ vfs,
2040
+ timeoutMs,
2041
+ memoryLimitBytes,
2042
+ signal
2043
+ });
2044
+ else {
2045
+ enginePromise ??= (options.loadEngine ?? defaultLoadEngine)();
2046
+ result = await sandbox.runScript(await enginePromise, {
2047
+ script,
2048
+ vfs,
2049
+ timeoutMs,
2050
+ memoryLimitBytes,
2051
+ signal,
2052
+ fetchText: options.fetchText
2053
+ });
2054
+ }
2055
+ const logs = result.logs.map((l) => `[${l.level}] ${l.text}`);
2056
+ return result.ok ? {
2057
+ ok: true,
2058
+ value: result.value,
2059
+ logs
2060
+ } : {
2061
+ ok: false,
2062
+ reason: result.reason,
2063
+ error: result.error,
2064
+ logs
2065
+ };
2066
+ });
1872
2067
  const run = async (frame) => {
1873
2068
  const startedAt = Date.now();
1874
2069
  const clientHandler = options.clientTools?.[frame.toolName];
1875
- if (clientHandler) return runClientTool(frame, clientHandler);
2070
+ if (clientHandler) return runClientTool(frame, clientHandler, startedAt);
1876
2071
  if (!(options.tools ?? ["eval_script"]).includes(frame.toolName)) {
1877
- refuse(frame, "unsupported_tool", `this client does not execute '${frame.toolName}'`, startedAt);
2072
+ settle(frame, startedAt, {
2073
+ ok: false,
2074
+ reason: "unsupported_tool",
2075
+ error: `this client does not execute '${frame.toolName}'`
2076
+ });
1878
2077
  return;
1879
2078
  }
1880
2079
  const script = frame.input?.script;
1881
2080
  if (typeof script !== "string") {
1882
- refuse(frame, "invalid_input", "expected a string `script` input", startedAt);
2081
+ settle(frame, startedAt, {
2082
+ ok: false,
2083
+ reason: "invalid_input",
2084
+ error: "expected a string `script` input"
2085
+ });
1883
2086
  return;
1884
2087
  }
1885
- const controller = new AbortController();
1886
- inFlight.set(frame.executionId, controller);
1887
- track({
1888
- executionId: frame.executionId,
1889
- toolName: frame.toolName,
1890
- status: "running",
1891
- startedAt
1892
- });
1893
- try {
1894
- const sandbox = await import("@workerdeck/sandbox");
1895
- const vfs = sandbox.createVfs(frame.vfsSeed);
1896
- const timeoutMs = Math.min(frame.limits?.timeoutMs ?? Number.POSITIVE_INFINITY, options.timeoutMs ?? 5e3);
1897
- const memoryLimitBytes = Math.min(frame.limits?.memoryLimitBytes ?? Number.POSITIVE_INFINITY, options.memoryLimitBytes ?? 67108864);
1898
- const result = options.execute ? await options.execute({
1899
- script,
1900
- vfs,
1901
- timeoutMs,
1902
- memoryLimitBytes,
1903
- signal: controller.signal
1904
- }) : await (async () => {
1905
- enginePromise ??= (options.loadEngine ?? defaultLoadEngine)();
1906
- return sandbox.runScript(await enginePromise, {
1907
- script,
1908
- vfs,
1909
- timeoutMs,
1910
- memoryLimitBytes,
1911
- signal: controller.signal,
1912
- fetchText: options.fetchText
1913
- });
1914
- })();
1915
- if (disposed || !inFlight.has(frame.executionId)) return;
1916
- const logs = result.logs.map((l) => `[${l.level}] ${l.text}`);
1917
- if (result.ok) {
1918
- handle.sendToolCallResult(frame.executionId, {
1919
- type: "json",
1920
- value: result.value
1921
- }, logs);
1922
- track({
1923
- executionId: frame.executionId,
1924
- toolName: frame.toolName,
1925
- status: "settled",
1926
- startedAt,
1927
- endedAt: Date.now()
1928
- });
1929
- } else {
1930
- handle.sendToolCallError(frame.executionId, result.reason, result.error, logs);
1931
- track({
1932
- executionId: frame.executionId,
1933
- toolName: frame.toolName,
1934
- status: "failed",
1935
- reason: result.reason,
1936
- startedAt,
1937
- endedAt: Date.now()
1938
- });
1939
- }
1940
- } catch (error) {
1941
- if (disposed || !inFlight.has(frame.executionId)) return;
1942
- refuse(frame, "host_error", error instanceof Error ? error.message : String(error), startedAt);
1943
- } finally {
1944
- inFlight.delete(frame.executionId);
1945
- }
2088
+ return runScript(frame, script, startedAt);
1946
2089
  };
1947
2090
  const offRequest = handle.on("toolCallRequest", (frame) => void run(frame));
1948
2091
  const offCancel = handle.on("toolCallCanceled", ({ executionId, reason }) => {
@@ -2182,6 +2325,6 @@ function useShellTerminal(handle, shellId, options) {
2182
2325
  ]);
2183
2326
  }
2184
2327
  //#endregion
2185
- export { REPLAY_HOLD_MAX_MS, ancestorsWithin, applyEvent, attachmentKind, clearDrafts, clearProfileUsageCache, clearTranscriptCache, createToolCallHost, currentText, flattenHostTree, hydrateShellOutput, hydrateShellRow, hydrateToolResult, initialOpenFilesState, initialReplayTarget, initialTranscriptState, isDirty, openFilesReducer, rateLimitWindows, recapLine, scanPromptTokens, seedFromSessionInfo, shellRowText, staleAttach, summarizeSince, useAttachments, useClaudeSession, useDraft, useHostFileRoots, useHostFileSearch, useHostFileTree, useOpenFiles, usePeerSessions, useProfileUsage, useProjectIcons, useSessionInfo, useShellTerminal, useToolCallHost };
2328
+ export { BOOKMARKS_STORAGE_KEY, REPLAY_HOLD_MAX_MS, ancestorsWithin, applyEvent, attachmentKind, clearDrafts, clearProfileUsageCache, clearTranscriptCache, createToolCallHost, currentText, flattenHostTree, hydrateShellOutput, hydrateShellRow, hydrateToolResult, initialOpenFilesState, initialReplayTarget, initialTranscriptState, isDirty, openFilesReducer, rateLimitWindows, recapLine, scanPromptTokens, seedFromSessionInfo, shellRowText, staleAttach, summarizeSince, useAsync, useAttachments, useBookmarks, useClaudeSession, useDraft, useHostFileRoots, useHostFileSearch, useHostFileTree, useOpenFiles, usePeerSessions, useProfileUsage, useProjectIcons, useSessionInfo, useShellTerminal, useToolCallHost };
2186
2329
 
2187
2330
  //# sourceMappingURL=index.mjs.map