@senguoyun/dsh-arkme 0.1.73 → 0.1.74

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.
Files changed (29) hide show
  1. package/lib/client.js +659 -1828
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +6 -43
  4. package/lib/types/client/ArkmeArkoSurface.d.ts +31 -4
  5. package/lib/types/client/ArkmeArkoSurface.d.ts.map +1 -1
  6. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  7. package/lib/types/harness-embed-route.d.ts +0 -1
  8. package/lib/types/harness-embed-route.d.ts.map +1 -1
  9. package/lib/types/index.d.ts.map +1 -1
  10. package/package.json +5 -5
  11. package/lib/arko-native-client.js +0 -6079
  12. package/lib/types/client/ArkmeArkoPanel.d.ts +0 -5
  13. package/lib/types/client/ArkmeArkoPanel.d.ts.map +0 -1
  14. package/lib/types/client/ArkoNativeSurface.d.ts +0 -27
  15. package/lib/types/client/ArkoNativeSurface.d.ts.map +0 -1
  16. package/lib/types/client/arko-conversation-contract.d.ts +0 -47
  17. package/lib/types/client/arko-conversation-contract.d.ts.map +0 -1
  18. package/lib/types/client/arko-native-client.d.ts +0 -19
  19. package/lib/types/client/arko-native-client.d.ts.map +0 -1
  20. package/lib/types/client/arko-native-composer.d.ts +0 -31
  21. package/lib/types/client/arko-native-composer.d.ts.map +0 -1
  22. package/lib/types/client/arko-native-emoji-picker.d.ts +0 -17
  23. package/lib/types/client/arko-native-emoji-picker.d.ts.map +0 -1
  24. package/lib/types/client/arko-native-frame.d.ts +0 -3
  25. package/lib/types/client/arko-native-frame.d.ts.map +0 -1
  26. package/lib/types/client/arko-native-transport.d.ts +0 -20
  27. package/lib/types/client/arko-native-transport.d.ts.map +0 -1
  28. package/lib/types/client/useArkoConversationController.d.ts +0 -57
  29. package/lib/types/client/useArkoConversationController.d.ts.map +0 -1
package/lib/client.js CHANGED
@@ -1979,7 +1979,7 @@ window.__ModuleLoader__.load({
1979
1979
  }
1980
1980
  //#endregion
1981
1981
  //#region src/avatar-diagnostics.ts
1982
- function record$1(value) {
1982
+ function record(value) {
1983
1983
  return value !== null && typeof value === "object" ? value : {};
1984
1984
  }
1985
1985
  function positiveId(value) {
@@ -1995,7 +1995,7 @@ window.__ModuleLoader__.load({
1995
1995
  const encoded = parts[1] ?? "";
1996
1996
  if (parts.length !== 3 || encoded.length > 1024 || !/^[A-Za-z0-9_-]+$/.test(encoded)) return context;
1997
1997
  const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
1998
- const payload = record$1(JSON.parse(globalThis.atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))));
1998
+ const payload = record(JSON.parse(globalThis.atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))));
1999
1999
  if (payload.version !== 1) return context;
2000
2000
  const viewer = positiveId(payload.viewerUserId);
2001
2001
  const target = positiveId(payload.targetUserId);
@@ -2025,8 +2025,8 @@ window.__ModuleLoader__.load({
2025
2025
  }
2026
2026
  function logArkmeAvatarDiagnostic(event, context, error) {
2027
2027
  try {
2028
- const source = record$1(error);
2029
- const body = record$1(source.body);
2028
+ const source = record(error);
2029
+ const body = record(source.body);
2030
2030
  const detail = source.name === "ArkmeClientError" ? body : source;
2031
2031
  const errorName = [
2032
2032
  "Error",
@@ -2046,7 +2046,7 @@ window.__ModuleLoader__.load({
2046
2046
  retryable: typeof detail.retryable === "boolean" ? detail.retryable : void 0,
2047
2047
  httpStatus: httpStatus(source.httpStatus),
2048
2048
  upstreamStatus: httpStatus(source.upstreamStatus) ?? imageDownloadStatus(source),
2049
- causeCode: safeErrorCode(record$1(source.cause).code)
2049
+ causeCode: safeErrorCode(record(source.cause).code)
2050
2050
  } }
2051
2051
  })}`);
2052
2052
  } catch {}
@@ -15537,7 +15537,7 @@ function(property) {
15537
15537
  * @returns {Check}
15538
15538
  */
15539
15539
  function(test) {
15540
- if (test === null || test === void 0) return ok$1;
15540
+ if (test === null || test === void 0) return ok;
15541
15541
  if (typeof test === "function") return castFactory(test);
15542
15542
  if (typeof test === "object") return Array.isArray(test) ? anyFactory(test) : propertiesFactory(test);
15543
15543
  if (typeof test === "string") return typeFactory(test);
@@ -15615,7 +15615,7 @@ function(test) {
15615
15615
  return Boolean(looksLikeANode(value) && testFunction.call(this, value, typeof index === "number" ? index : void 0, parent || void 0));
15616
15616
  }
15617
15617
  }
15618
- function ok$1() {
15618
+ function ok() {
15619
15619
  return true;
15620
15620
  }
15621
15621
  /**
@@ -146353,7 +146353,7 @@ body:has([data-arkme-self-topic-menu]) > [role="menu"] { z-index: 10030; }
146353
146353
  * Wrap `React.useLayoutEffect` which will not throw warning message in test env
146354
146354
  */
146355
146355
  const useInternalLayoutEffect = canUseDom() ? react.useLayoutEffect : react.useEffect;
146356
- const useLayoutEffect$22 = (callback, deps) => {
146356
+ const useLayoutEffect$21 = (callback, deps) => {
146357
146357
  const firstMountRef = react.useRef(true);
146358
146358
  useInternalLayoutEffect(() => {
146359
146359
  return callback(firstMountRef.current);
@@ -146397,7 +146397,7 @@ body:has([data-arkme-self-topic-menu]) > [role="menu"] { z-index: 10030; }
146397
146397
  function useControlledState(defaultStateValue, value) {
146398
146398
  const [innerValue, setInnerValue] = (0, react.useState)(defaultStateValue);
146399
146399
  const mergedValue = value !== void 0 ? value : innerValue;
146400
- useLayoutEffect$22((mount) => {
146400
+ useLayoutEffect$21((mount) => {
146401
146401
  if (!mount) setInnerValue(value);
146402
146402
  }, [value]);
146403
146403
  return [mergedValue, setInnerValue];
@@ -146460,7 +146460,7 @@ body:has([data-arkme-self-topic-menu]) > [role="menu"] { z-index: 10030; }
146460
146460
  };
146461
146461
  //#endregion
146462
146462
  //#region node_modules/@rc-component/util/es/hooks/useMemo.js
146463
- function useMemo$32(getValue, condition, shouldUpdate) {
146463
+ function useMemo$31(getValue, condition, shouldUpdate) {
146464
146464
  const cacheRef = react.useRef({});
146465
146465
  if (!("value" in cacheRef.current) || shouldUpdate(cacheRef.current.condition, condition)) {
146466
146466
  cacheRef.current.value = getValue();
@@ -146573,7 +146573,7 @@ body:has([data-arkme-self-topic-menu]) > [role="menu"] { z-index: 10030; }
146573
146573
  };
146574
146574
  };
146575
146575
  const useComposeRef = (...refs) => {
146576
- return useMemo$32(() => composeRef(...refs), refs, (prev, next) => prev.length !== next.length || prev.every((ref, i) => ref !== next[i]));
146576
+ return useMemo$31(() => composeRef(...refs), refs, (prev, next) => prev.length !== next.length || prev.every((ref, i) => ref !== next[i]));
146577
146577
  };
146578
146578
  const supportRef = (nodeOrComponent) => {
146579
146579
  if (!nodeOrComponent) return false;
@@ -147463,13 +147463,13 @@ ${height ? `height: ${webkitScrollbarStyle.height};` : ""}
147463
147463
  ele.parentElement?.removeChild(ele);
147464
147464
  appendedRef.current = false;
147465
147465
  }
147466
- useLayoutEffect$22(() => {
147466
+ useLayoutEffect$21(() => {
147467
147467
  if (render) if (queueCreate) queueCreate(append);
147468
147468
  else append();
147469
147469
  else cleanup();
147470
147470
  return cleanup;
147471
147471
  }, [render]);
147472
- useLayoutEffect$22(() => {
147472
+ useLayoutEffect$21(() => {
147473
147473
  if (queue.length) {
147474
147474
  queue.forEach((appendFn) => appendFn());
147475
147475
  setQueue(EMPTY_LIST);
@@ -147495,7 +147495,7 @@ ${height ? `height: ${webkitScrollbarStyle.height};` : ""}
147495
147495
  uuid$1 += 1;
147496
147496
  return `${UNIQUE_ID}_${uuid$1}`;
147497
147497
  });
147498
- useLayoutEffect$22(() => {
147498
+ useLayoutEffect$21(() => {
147499
147499
  if (mergedLock) {
147500
147500
  const scrollbarSize = getTargetScrollBarSize(document.body).width;
147501
147501
  updateCSS(`
@@ -148438,7 +148438,7 @@ html body {
148438
148438
  ]);
148439
148439
  const getPopupContainerNeedParams = getPopupContainer?.length > 0;
148440
148440
  const [show, setShow] = react.useState(!getPopupContainer || !getPopupContainerNeedParams);
148441
- useLayoutEffect$22(() => {
148441
+ useLayoutEffect$21(() => {
148442
148442
  if (!show && getPopupContainerNeedParams && target) setShow(true);
148443
148443
  }, [
148444
148444
  show,
@@ -148990,8 +148990,8 @@ html body {
148990
148990
  ready: false
148991
148991
  }));
148992
148992
  };
148993
- useLayoutEffect$22(resetReady, [placement]);
148994
- useLayoutEffect$22(() => {
148993
+ useLayoutEffect$21(resetReady, [placement]);
148994
+ useLayoutEffect$21(() => {
148995
148995
  if (!open) resetReady();
148996
148996
  }, [open]);
148997
148997
  return [
@@ -149035,7 +149035,7 @@ html body {
149035
149035
  //#endregion
149036
149036
  //#region node_modules/@rc-component/trigger/es/hooks/useWatch.js
149037
149037
  function useWatch(open, target, popup, onAlign, onScroll) {
149038
- useLayoutEffect$22(() => {
149038
+ useLayoutEffect$21(() => {
149039
149039
  if (open && target && popup) {
149040
149040
  const targetElement = target;
149041
149041
  const popupElement = popup;
@@ -149190,7 +149190,7 @@ html body {
149190
149190
  id,
149191
149191
  onEsc
149192
149192
  }));
149193
- useLayoutEffect$22(() => {
149193
+ useLayoutEffect$21(() => {
149194
149194
  if (uniqueContext && unique && targetEle && !openUncontrolled && !parentContext) if (mergedOpen) uniqueContext.show(getUniqueOptions(mouseEnterDelay), isOpen);
149195
149195
  else uniqueContext.hide(mouseLeaveDelay);
149196
149196
  }, [mergedOpen, targetEle]);
@@ -149226,7 +149226,7 @@ html body {
149226
149226
  if (top) triggerOpen(false);
149227
149227
  }
149228
149228
  const [inMotion, setInMotion] = react.useState(false);
149229
- useLayoutEffect$22((firstMount) => {
149229
+ useLayoutEffect$21((firstMount) => {
149230
149230
  if (!firstMount || mergedOpen) setInMotion(true);
149231
149231
  }, [mergedOpen]);
149232
149232
  const [motionPrepareResolve, setMotionPrepareResolve] = react.useState(null);
@@ -149245,10 +149245,10 @@ html body {
149245
149245
  if (openRef.current && alignPoint && clickToHide) triggerOpen(false);
149246
149246
  };
149247
149247
  useWatch(mergedOpen, targetEle, popupEle, triggerAlign, onScroll);
149248
- useLayoutEffect$22(() => {
149248
+ useLayoutEffect$21(() => {
149249
149249
  triggerAlign();
149250
149250
  }, [mousePos, popupPlacement]);
149251
- useLayoutEffect$22(() => {
149251
+ useLayoutEffect$21(() => {
149252
149252
  if (mergedOpen && !builtinPlacements?.[popupPlacement]) triggerAlign();
149253
149253
  }, [JSON.stringify(popupAlign)]);
149254
149254
  const alignedClassName = react.useMemo(() => {
@@ -149288,7 +149288,7 @@ html body {
149288
149288
  syncTargetSize();
149289
149289
  setMotionPrepareResolve(() => resolve);
149290
149290
  });
149291
- useLayoutEffect$22(() => {
149291
+ useLayoutEffect$21(() => {
149292
149292
  if (motionPrepareResolve) {
149293
149293
  onAlign();
149294
149294
  motionPrepareResolve();
@@ -149495,7 +149495,7 @@ html body {
149495
149495
  }
149496
149496
  function useTarget(target, open, gap, scrollIntoViewOptions, inlineMode, placeholderRef) {
149497
149497
  const [targetElement, setTargetElement] = (0, react.useState)(void 0);
149498
- useLayoutEffect$22(() => {
149498
+ useLayoutEffect$21(() => {
149499
149499
  const nextElement = typeof target === "function" ? target() : target;
149500
149500
  setTargetElement(nextElement || null);
149501
149501
  });
@@ -149525,7 +149525,7 @@ html body {
149525
149525
  } else setPosInfo(null);
149526
149526
  });
149527
149527
  const getGapOffset = (index) => (Array.isArray(gap?.offset) ? gap?.offset[index] : gap?.offset) ?? 6;
149528
- useLayoutEffect$22(() => {
149528
+ useLayoutEffect$21(() => {
149529
149529
  updatePos();
149530
149530
  window.addEventListener("resize", updatePos);
149531
149531
  window.addEventListener("scroll", updatePos);
@@ -149815,7 +149815,7 @@ html body {
149815
149815
  const mergedOpen = mergedCurrent < 0 || mergedCurrent >= steps.length ? false : internalOpen ?? true;
149816
149816
  const [hasOpened, setHasOpened] = react.useState(mergedOpen);
149817
149817
  const openRef = react.useRef(mergedOpen);
149818
- useLayoutEffect$22(() => {
149818
+ useLayoutEffect$21(() => {
149819
149819
  if (mergedOpen) {
149820
149820
  if (!openRef.current) setMergedCurrent(0);
149821
149821
  setHasOpened(true);
@@ -149832,7 +149832,7 @@ html body {
149832
149832
  const mergedPlacement = getPlacement(targetElement, placement, stepPlacement);
149833
149833
  const mergedArrow = targetElement ? typeof stepArrow === "undefined" ? arrow : stepArrow : false;
149834
149834
  const arrowPointAtCenter = typeof mergedArrow === "object" ? mergedArrow.pointAtCenter : false;
149835
- useLayoutEffect$22(() => {
149835
+ useLayoutEffect$21(() => {
149836
149836
  triggerRef.current?.forceAlign();
149837
149837
  }, [arrowPointAtCenter, mergedCurrent]);
149838
149838
  const onInternalChange = (nextCurrent) => {
@@ -149870,7 +149870,7 @@ html body {
149870
149870
  return;
149871
149871
  }
149872
149872
  });
149873
- useLayoutEffect$22(() => {
149873
+ useLayoutEffect$21(() => {
149874
149874
  if (!mergedOpen) return;
149875
149875
  window.addEventListener("keydown", keyboardHandler);
149876
149876
  return () => {
@@ -163490,1642 +163490,6 @@ html body {
163490
163490
  });
163491
163491
  }
163492
163492
  //#endregion
163493
- //#region src/client/arko-native-transport.ts
163494
- function record(value) {
163495
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
163496
- }
163497
- function requestOf(payload) {
163498
- return record(record(record(payload).args).request);
163499
- }
163500
- function messageKey(message) {
163501
- return message.presentationKey ?? (message.messageId === void 0 ? message.id : `arko:${String(message.sessionId ?? "")}:${String(message.messageId)}`);
163502
- }
163503
- function renderContent(message) {
163504
- return [...message.reasoning ? [{
163505
- type: "reasoning",
163506
- text: message.reasoning
163507
- }] : [], {
163508
- type: "text",
163509
- text: message.text
163510
- }];
163511
- }
163512
- function endReason(message) {
163513
- if (message.status === "error" || message.runStatus === "failed") return {
163514
- kind: "error",
163515
- error: {
163516
- code: "ARKO_RUN_FAILED",
163517
- message: message.text || "Arko 运行失败"
163518
- }
163519
- };
163520
- if (message.runStatus === "cancelled") return {
163521
- kind: "aborted",
163522
- reason: { kind: "user" }
163523
- };
163524
- if (message.runStatus === "waiting_user") return { kind: "blocked" };
163525
- if (message.runStatus === "expired" || message.runStatus === "partial") return { kind: "interrupted" };
163526
- return { kind: "completed" };
163527
- }
163528
- const ok = (value) => ({
163529
- ok: true,
163530
- value
163531
- });
163532
- const denied = (message, code = "gateway/bad-request") => ({
163533
- ok: false,
163534
- error: {
163535
- code,
163536
- message,
163537
- details: {}
163538
- }
163539
- });
163540
- /**
163541
- * Render-only protocol adapter. Snapshots and admitted actions remain owned by
163542
- * ArkmeArkoSurface; this object owns only DSH cursor/identity presentation data.
163543
- * Unsupported calls never reach the host transport or any Arkme API.
163544
- */
163545
- function createArkoNativeTransport(initial, initialActions) {
163546
- let snapshot = initial;
163547
- let actions = initialActions;
163548
- const accountKey = initial.accountKey;
163549
- const sessionId = `arko-native-${crypto.randomUUID()}`;
163550
- const createdAt = Date.now();
163551
- let disposed = false;
163552
- let historyRevision = 0;
163553
- let submitting = false;
163554
- let changingModel = false;
163555
- let pendingPrompt;
163556
- const listeners = /* @__PURE__ */ new Set();
163557
- const streams = /* @__PURE__ */ new Set();
163558
- const sequenceOrigin = 2 ** 40;
163559
- let nextSeq = sequenceOrigin;
163560
- let pageRequest;
163561
- let journal = [];
163562
- let projected = [];
163563
- let turn = 0;
163564
- let assistantRevision = 0;
163565
- let activeAssistant;
163566
- function emit(endpoint, value) {
163567
- for (const stream of streams) if (stream.endpoint === endpoint && (endpoint !== "session/follow" || stream.historyRevision === historyRevision)) stream.push(value);
163568
- }
163569
- function append(type, data, message, surfaceOp) {
163570
- const event = {
163571
- type,
163572
- seq: nextSeq++,
163573
- time: Math.max(0, Math.trunc(message.createdAtMillis ?? createdAt)),
163574
- data,
163575
- ...surfaceOp === void 0 ? {} : { surfaceOp },
163576
- ...type.startsWith("arko/") ? { ignorable: true } : {}
163577
- };
163578
- const entry = {
163579
- type: "event",
163580
- event
163581
- };
163582
- journal.push(entry);
163583
- emit("session/follow", entry);
163584
- return event.seq;
163585
- }
163586
- function appendMessage(row) {
163587
- const { message, key, rpcId } = row;
163588
- const source = message.role === "user" ? {
163589
- kind: "user",
163590
- ...rpcId === void 0 ? {} : { rpcId }
163591
- } : message.role === "divider" ? {
163592
- kind: "plugin",
163593
- plugin: "arko",
163594
- form: "notice",
163595
- summary: message.text
163596
- } : {
163597
- kind: "model",
163598
- provider: "arko",
163599
- model: "unspecified"
163600
- };
163601
- const wire = {
163602
- id: key,
163603
- role: message.role === "assistant" ? "assistant" : "user",
163604
- source,
163605
- content: renderContent(message)
163606
- };
163607
- row.seq = append(message.role === "assistant" ? "assistant/message" : "user/message", message.role === "assistant" ? {
163608
- turn: row.turn,
163609
- step: 1,
163610
- message: wire,
163611
- stream: activeAssistant?.attemptId === key ? activeAssistant.stream.slice() : [],
163612
- ...[
163613
- "cancelled",
163614
- "partial",
163615
- "expired"
163616
- ].includes(message.runStatus ?? "") ? { interrupted: true } : {}
163617
- } : wire, message, "append");
163618
- }
163619
- function assistantFrame(frame) {
163620
- emit("session/follow", {
163621
- type: "assistant-stream",
163622
- frame: {
163623
- ...frame,
163624
- revision: ++assistantRevision
163625
- }
163626
- });
163627
- }
163628
- function assistantChunk(chunk, message) {
163629
- if (activeAssistant === void 0) return;
163630
- const time = Math.max(0, Math.trunc(message.createdAtMillis ?? createdAt));
163631
- const index = activeAssistant.nextIndex++;
163632
- activeAssistant.stream.push({
163633
- type: "chunk",
163634
- time,
163635
- chunk
163636
- });
163637
- assistantFrame({
163638
- type: "chunk",
163639
- attemptId: activeAssistant.attemptId,
163640
- index,
163641
- time,
163642
- chunk
163643
- });
163644
- }
163645
- function streamMessage(row, previous) {
163646
- if (activeAssistant?.attemptId !== row.key) {
163647
- activeAssistant = {
163648
- attemptId: row.key,
163649
- startedAfterSeq: nextSeq - 1,
163650
- turn: row.turn,
163651
- step: 1,
163652
- nextIndex: 0,
163653
- stream: []
163654
- };
163655
- assistantFrame({
163656
- type: "start",
163657
- attemptId: row.key,
163658
- startedAfterSeq: activeAssistant.startedAfterSeq,
163659
- turn: row.turn,
163660
- step: 1
163661
- });
163662
- }
163663
- for (const [index, kind] of ["reasoning", "text"].entries()) {
163664
- const text = kind === "reasoning" ? row.message.reasoning ?? "" : row.message.text;
163665
- const before = previous === void 0 ? void 0 : kind === "reasoning" ? previous.reasoning ?? "" : previous.text;
163666
- if (before === void 0 || !text.startsWith(before)) assistantChunk({
163667
- type: "block-start",
163668
- index,
163669
- blockType: kind
163670
- }, row.message);
163671
- const delta = before !== void 0 && text.startsWith(before) ? text.slice(before.length) : text;
163672
- if (delta !== "") assistantChunk({
163673
- type: `${kind}-delta`,
163674
- index,
163675
- text: delta
163676
- }, row.message);
163677
- }
163678
- }
163679
- function settleAssistant(row) {
163680
- appendMessage(row);
163681
- if (activeAssistant?.attemptId === row.key) {
163682
- assistantFrame({
163683
- type: "end",
163684
- attemptId: row.key,
163685
- index: activeAssistant.nextIndex,
163686
- outcome: {
163687
- kind: "committed",
163688
- eventType: "assistant/message",
163689
- seq: row.seq
163690
- }
163691
- });
163692
- activeAssistant = void 0;
163693
- }
163694
- }
163695
- function endMessage(row) {
163696
- if (row.message.role !== "assistant" || row.message.status === "sending" || row.ended) return;
163697
- append("step/end", {
163698
- turn: row.turn,
163699
- step: 1
163700
- }, row.message);
163701
- append("turn/end", {
163702
- turn: row.turn,
163703
- reason: endReason(row.message)
163704
- }, row.message);
163705
- row.ended = true;
163706
- }
163707
- function addMessage(message) {
163708
- if (message.role === "user" || turn === 0 || message.role === "assistant" && projected.at(-1)?.ended === true) {
163709
- turn++;
163710
- append("turn/start", { turn }, message);
163711
- append("step/start", {
163712
- turn,
163713
- step: 1
163714
- }, message);
163715
- }
163716
- const row = {
163717
- message,
163718
- key: messageKey(message),
163719
- seq: -1,
163720
- turn,
163721
- ended: false
163722
- };
163723
- if (message.role === "user" && pendingPrompt !== void 0 && message.text.trim() === pendingPrompt.text) {
163724
- row.rpcId = pendingPrompt.requestId;
163725
- pendingPrompt = void 0;
163726
- }
163727
- if (message.role === "assistant" && message.status === "sending") streamMessage(row);
163728
- else appendMessage(row);
163729
- endMessage(row);
163730
- projected.push(row);
163731
- }
163732
- function historyRecord(message, seq) {
163733
- const key = messageKey(message);
163734
- const wire = {
163735
- id: key,
163736
- role: message.role === "assistant" ? "assistant" : "user",
163737
- source: message.role === "assistant" ? {
163738
- kind: "model",
163739
- provider: "arko",
163740
- model: "unspecified"
163741
- } : message.role === "divider" ? {
163742
- kind: "plugin",
163743
- plugin: "arko",
163744
- form: "notice",
163745
- summary: message.text
163746
- } : { kind: "user" },
163747
- content: renderContent(message)
163748
- };
163749
- const presentationTurn = seq + 1;
163750
- return {
163751
- entry: {
163752
- type: "event",
163753
- event: {
163754
- type: message.role === "assistant" ? "assistant/message" : "user/message",
163755
- seq,
163756
- time: Math.max(0, Math.trunc(message.createdAtMillis ?? createdAt)),
163757
- surfaceOp: "append",
163758
- data: message.role === "assistant" ? {
163759
- turn: presentationTurn,
163760
- step: 1,
163761
- message: wire,
163762
- stream: [],
163763
- ...[
163764
- "cancelled",
163765
- "partial",
163766
- "expired"
163767
- ].includes(message.runStatus ?? "") ? { interrupted: true } : {}
163768
- } : wire
163769
- }
163770
- },
163771
- row: {
163772
- message,
163773
- key,
163774
- seq,
163775
- turn: presentationTurn,
163776
- ended: true
163777
- }
163778
- };
163779
- }
163780
- function prependHistory(messages) {
163781
- const known = new Set(projected.map((row) => row.key));
163782
- const persistedKey = (message) => message.messageId === void 0 ? void 0 : `${message.sessionId}:${message.messageId}`;
163783
- const persisted = new Set(projected.map((row) => persistedKey(row.message)).filter((key) => key !== void 0));
163784
- const older = messages.filter((message) => {
163785
- const key = messageKey(message), identity = persistedKey(message);
163786
- if (known.has(key) || identity !== void 0 && persisted.has(identity)) return false;
163787
- known.add(key);
163788
- if (identity !== void 0) persisted.add(identity);
163789
- return true;
163790
- });
163791
- const base = journal[0]?.event.seq ?? nextSeq;
163792
- if (older.length > base) throw new Error("Arko 历史窗口边界已变化,请重新打开会话");
163793
- const entries = older.map((message, index) => historyRecord(message, base - older.length + index));
163794
- journal = [...entries.map((value) => value.entry), ...journal];
163795
- projected = [...entries.map((value) => value.row), ...projected];
163796
- }
163797
- function seedHistory(messages) {
163798
- for (const message of messages) {
163799
- if (message.status === "sending" || message.messageId === void 0) {
163800
- addMessage(message);
163801
- continue;
163802
- }
163803
- const value = historyRecord(message, nextSeq++);
163804
- journal.push(value.entry);
163805
- projected.push(value.row);
163806
- turn = Math.max(turn, value.row.turn);
163807
- }
163808
- }
163809
- function selectedModel() {
163810
- const route = snapshot.modelCatalog?.effectiveRouteKey;
163811
- return route ? {
163812
- provider: "arko",
163813
- model: route
163814
- } : null;
163815
- }
163816
- function projectionValues() {
163817
- return {
163818
- title: snapshot.displayName,
163819
- modelSelection: {
163820
- lastUsed: null,
163821
- next: selectedModel()
163822
- },
163823
- sessionListMetadata: {
163824
- blank: false,
163825
- lastPromptAt: snapshot.messages.filter((message) => message.role === "user").at(-1)?.createdAtMillis ?? null
163826
- }
163827
- };
163828
- }
163829
- function projections() {
163830
- return {
163831
- asOfSeq: nextSeq - 1,
163832
- values: projectionValues()
163833
- };
163834
- }
163835
- function summary() {
163836
- return {
163837
- sessionId,
163838
- updatedAt: snapshot.messages.at(-1)?.createdAtMillis ?? createdAt,
163839
- running: snapshot.busy,
163840
- blank: false,
163841
- projections: projections()
163842
- };
163843
- }
163844
- function checkSession(request) {
163845
- return request.sessionId === sessionId;
163846
- }
163847
- function blocked() {
163848
- return snapshot.loading ? "Arko 正在加载" : snapshot.inputBlockedReason || (snapshot.busy || submitting || changingModel ? "Arko 正在处理上一项操作" : void 0);
163849
- }
163850
- async function dispatch(method, payload) {
163851
- const request = requestOf(payload);
163852
- switch (method) {
163853
- case "settings/describe": return ok({
163854
- writable: false,
163855
- hasDocument: false,
163856
- namespaces: []
163857
- });
163858
- case "credentials/describe": return ok({});
163859
- case "dynamicCordisRunner/syncInspectManifest": return ok(null);
163860
- case "dynamicCordisRunner/inventory":
163861
- case "commands/list":
163862
- case "llm/listConfigurableProviders": return ok([]);
163863
- case "agentPresets/list": return ok({
163864
- presets: [],
163865
- authorable: false
163866
- });
163867
- case "subagents/list": return ok({
163868
- entries: [],
163869
- parentAvailable: false
163870
- });
163871
- case "skills/list": return ok({ skills: [] });
163872
- case "session/list": return ok({ items: [summary()] });
163873
- case "llm/listProviders": return ok([{
163874
- id: "arko",
163875
- name: snapshot.displayName
163876
- }]);
163877
- case "session/modelCatalog": {
163878
- const catalog = snapshot.modelCatalog;
163879
- return ok({
163880
- default: {
163881
- provider: "arko",
163882
- model: catalog?.defaultRouteKey || catalog?.effectiveRouteKey || ""
163883
- },
163884
- routableProviders: catalog?.options.length ? ["arko"] : [],
163885
- groups: catalog === null ? [] : [{
163886
- id: "arko",
163887
- name: snapshot.displayName,
163888
- models: catalog.options.map((option) => ({
163889
- id: option.routeKey,
163890
- name: option.displayName,
163891
- description: option.description
163892
- }))
163893
- }],
163894
- failures: []
163895
- });
163896
- }
163897
- case "session/selectModel": {
163898
- if (!checkSession(request)) return denied("Arko 会话已失效");
163899
- const reason = blocked();
163900
- if (reason !== void 0) return denied(reason);
163901
- if (request.provider !== "arko" || typeof request.model !== "string" || request.reasoningEffort !== void 0 || !snapshot.modelCatalog?.options.some((option) => option.routeKey === request.model)) return denied("此 Arko 模型不可用");
163902
- if (request.model === snapshot.modelCatalog.effectiveRouteKey) return ok({ selected: selectedModel() });
163903
- changingModel = true;
163904
- try {
163905
- if (await actions.selectModel(request.model) !== true && snapshot.modelCatalog?.effectiveRouteKey !== request.model) return denied(snapshot.error || "Arko 模型切换未确认");
163906
- return ok({ selected: {
163907
- provider: "arko",
163908
- model: request.model
163909
- } });
163910
- } finally {
163911
- changingModel = false;
163912
- }
163913
- }
163914
- case "session/prompt": {
163915
- if (!checkSession(request)) return denied("Arko 会话已失效");
163916
- const reason = blocked();
163917
- if (reason !== void 0) return denied(reason);
163918
- if (request.mode !== "queue" || typeof request.requestId !== "string" || request.requestId.length === 0) return denied("Arko 不支持此发送方式");
163919
- if (!Array.isArray(request.content) || request.content.some((part) => record(part).type !== "text" || typeof record(part).text !== "string")) return denied("Arko 当前仅支持文字输入");
163920
- const text = request.content.map((part) => record(part).text).join("").trim();
163921
- if (!text || text.length > 60 * 1024) return denied("Arko 输入为空或超过长度上限");
163922
- submitting = true;
163923
- pendingPrompt = {
163924
- requestId: request.requestId,
163925
- text
163926
- };
163927
- try {
163928
- if (await actions.submit(text) !== true) {
163929
- pendingPrompt = void 0;
163930
- return denied(snapshot.error || "Arko 未接受此次发送");
163931
- }
163932
- return ok({ accepted: true });
163933
- } catch (error) {
163934
- pendingPrompt = void 0;
163935
- throw error;
163936
- } finally {
163937
- submitting = false;
163938
- }
163939
- }
163940
- case "session/cancel":
163941
- if (!checkSession(request)) return denied("Arko 会话已失效");
163942
- if (!snapshot.busy) return denied("Arko 当前没有可停止的运行");
163943
- return await actions.cancel() === true ? ok({ accepted: true }) : denied(snapshot.error || "Arko 停止请求未确认");
163944
- case "session/page": {
163945
- if (record(request.address).kind !== "session" || record(request.address).sessionId !== sessionId) return denied("Arko 会话已失效");
163946
- if (request.beforeSeq !== void 0 && (!Number.isSafeInteger(request.beforeSeq) || Number(request.beforeSeq) < 0)) return denied("Arko 历史边界无效");
163947
- const before = typeof request.beforeSeq === "number" ? request.beforeSeq : Infinity;
163948
- const through = typeof request.throughSeq === "number" ? request.throughSeq : nextSeq - 1;
163949
- if (snapshot.hasMore && before <= (journal[0]?.event.seq ?? nextSeq)) {
163950
- pageRequest ??= (async () => {
163951
- const page = await actions.loadEarlier();
163952
- if (disposed) throw new Error("Arko 原生呈现已关闭");
163953
- prependHistory(page.messages);
163954
- snapshot = {
163955
- ...snapshot,
163956
- hasMore: page.hasMore
163957
- };
163958
- })().finally(() => {
163959
- pageRequest = void 0;
163960
- });
163961
- await pageRequest;
163962
- }
163963
- const available = journal.filter((entry) => entry.event.seq <= through && entry.event.seq < before);
163964
- const limit = typeof request.maxMessages === "number" && Number.isSafeInteger(request.maxMessages) ? Math.min(50, Math.max(1, request.maxMessages)) : 50;
163965
- return ok({
163966
- records: available.slice(-limit),
163967
- hasMore: available.length > limit || snapshot.hasMore === true
163968
- });
163969
- }
163970
- default: return denied(`Arko 不支持原生操作:${method}`);
163971
- }
163972
- }
163973
- seedHistory(snapshot.messages);
163974
- return {
163975
- sessionId,
163976
- get historyRevision() {
163977
- return historyRevision;
163978
- },
163979
- getSnapshot: () => snapshot,
163980
- subscribe(listener) {
163981
- if (disposed) return () => {};
163982
- listeners.add(listener);
163983
- return () => {
163984
- listeners.delete(listener);
163985
- };
163986
- },
163987
- update(next, nextActions) {
163988
- if (disposed) throw new Error("Arko 原生呈现已销毁");
163989
- if (next.accountKey !== accountKey) throw new Error("Arko 账号切换必须建立新的原生呈现");
163990
- const previous = snapshot;
163991
- const oldValues = projectionValues();
163992
- snapshot = next;
163993
- if (nextActions !== void 0) actions = nextActions;
163994
- if (projected.length > 0) {
163995
- const head = next.messages.findIndex((message) => messageKey(message) === projected[0].key);
163996
- if (head > 0) prependHistory(next.messages.slice(0, head));
163997
- }
163998
- if (projected.some((row, index) => {
163999
- const message = next.messages[index];
164000
- if (message === void 0 || messageKey(message) !== row.key) return true;
164001
- return row.seq >= 0 && (row.message.text !== message.text || row.message.reasoning !== message.reasoning || row.message.role !== message.role || row.message.status !== message.status || JSON.stringify(endReason(row.message)) !== JSON.stringify(endReason(message)));
164002
- })) {
164003
- historyRevision++;
164004
- journal = [];
164005
- projected = [];
164006
- turn = 0;
164007
- activeAssistant = void 0;
164008
- assistantRevision = 0;
164009
- nextSeq = sequenceOrigin;
164010
- seedHistory(next.messages);
164011
- } else for (let index = 0; index < next.messages.length; index++) {
164012
- const message = next.messages[index];
164013
- const row = projected[index];
164014
- if (row === void 0) {
164015
- addMessage(message);
164016
- continue;
164017
- }
164018
- const previousMessage = row.message;
164019
- row.message = message;
164020
- if (message.role === "assistant" && row.seq < 0) if (message.status === "sending") streamMessage(row, previousMessage);
164021
- else settleAssistant(row);
164022
- endMessage(row);
164023
- }
164024
- const values = projectionValues();
164025
- const changedKeys = Object.keys(values).filter((key) => JSON.stringify(values[key]) !== JSON.stringify(oldValues[key]));
164026
- const context = {
164027
- id: "",
164028
- role: "divider",
164029
- text: "",
164030
- status: "done",
164031
- createdAtMillis: Date.now()
164032
- };
164033
- if (changedKeys.length > 0) append("arko/presentation", Object.fromEntries(changedKeys.map((key) => [key, values[key]])), context);
164034
- for (const key of changedKeys) emit("session/control", {
164035
- type: "projection",
164036
- sessionId,
164037
- key,
164038
- value: values[key],
164039
- seq: nextSeq - 1
164040
- });
164041
- if (previous.busy !== next.busy) emit("$events", {
164042
- type: "emit",
164043
- event: "api-session/status",
164044
- args: [sessionId, next.busy]
164045
- });
164046
- if (next.error && previous.error !== next.error) emit("$events", {
164047
- type: "emit",
164048
- event: "api-session/error",
164049
- args: [sessionId, next.error]
164050
- });
164051
- if (JSON.stringify(previous.modelCatalog) !== JSON.stringify(next.modelCatalog)) emit("$events", {
164052
- type: "emit",
164053
- event: "llm/adapters-updated",
164054
- args: []
164055
- });
164056
- for (const listener of listeners) listener();
164057
- },
164058
- async fetch(_input, init) {
164059
- let rpcId = "";
164060
- let result;
164061
- try {
164062
- if (typeof init.body !== "string") throw new Error("Arko 原生请求格式无效");
164063
- const envelope = record(JSON.parse(init.body));
164064
- rpcId = typeof envelope.rpcId === "string" ? envelope.rpcId : "";
164065
- if (envelope.type !== "client-request" || typeof envelope.method !== "string" || rpcId === "") throw new Error("Arko 原生请求格式无效");
164066
- if (disposed || init.signal?.aborted) result = denied("Arko 原生呈现已关闭");
164067
- else {
164068
- result = await dispatch(envelope.method, envelope.payload);
164069
- if (disposed || init.signal?.aborted) result = denied("Arko 原生呈现已关闭");
164070
- }
164071
- } catch (error) {
164072
- result = denied(error instanceof Error ? error.message : "Arko 原生请求失败");
164073
- }
164074
- return new Response(JSON.stringify({
164075
- type: "server-response",
164076
- rpcId,
164077
- result
164078
- }), { headers: { "content-type": "application/json" } });
164079
- },
164080
- async *openStream(endpoint, payload, signal) {
164081
- if (disposed || signal.aborted) return;
164082
- if (![
164083
- "$events",
164084
- "session/control",
164085
- "session/follow",
164086
- "workspace/follow"
164087
- ].includes(endpoint)) throw new Error(`Arko 不支持原生流:${endpoint}`);
164088
- if (endpoint === "session/follow") {
164089
- const address = record(requestOf(payload).address);
164090
- if (address.kind !== "session" || address.sessionId !== sessionId) throw new Error("Arko 会话已失效");
164091
- }
164092
- const queue = [];
164093
- let wake;
164094
- let ended = false;
164095
- const sink = {
164096
- endpoint,
164097
- historyRevision,
164098
- push(value) {
164099
- if (!ended) {
164100
- queue.push(value);
164101
- wake?.();
164102
- }
164103
- },
164104
- close() {
164105
- ended = true;
164106
- wake?.();
164107
- }
164108
- };
164109
- streams.add(sink);
164110
- const abort = () => sink.close();
164111
- signal.addEventListener("abort", abort, { once: true });
164112
- try {
164113
- if (endpoint === "$events") yield {
164114
- type: "ready",
164115
- clientId: crypto.randomUUID(),
164116
- host: { home: "" }
164117
- };
164118
- if (endpoint === "workspace/follow") yield {
164119
- type: "baseline",
164120
- value: {
164121
- items: [],
164122
- archivedSessionIds: []
164123
- }
164124
- };
164125
- if (endpoint === "session/control") yield {
164126
- type: "baseline",
164127
- value: {
164128
- queues: { [sessionId]: [] },
164129
- jobs: { [sessionId]: [] },
164130
- projections: { [sessionId]: projections() }
164131
- }
164132
- };
164133
- if (endpoint === "session/follow") yield {
164134
- type: "snapshot",
164135
- header: {
164136
- version: 3,
164137
- id: sessionId,
164138
- createdAt,
164139
- isSeeded: false
164140
- },
164141
- cursor: nextSeq - 1,
164142
- records: journal.slice(),
164143
- hasMore: snapshot.hasMore === true,
164144
- projections: projections(),
164145
- assistantStream: {
164146
- revision: assistantRevision,
164147
- ...activeAssistant === void 0 ? {} : { activeAttempt: {
164148
- ...activeAssistant,
164149
- stream: activeAssistant.stream.slice()
164150
- } }
164151
- }
164152
- };
164153
- while (!ended) {
164154
- if (queue.length > 0) {
164155
- yield queue.shift();
164156
- continue;
164157
- }
164158
- await new Promise((resolve) => {
164159
- wake = resolve;
164160
- });
164161
- wake = void 0;
164162
- }
164163
- } finally {
164164
- streams.delete(sink);
164165
- signal.removeEventListener("abort", abort);
164166
- queue.length = 0;
164167
- }
164168
- },
164169
- dispose() {
164170
- if (disposed) return;
164171
- disposed = true;
164172
- for (const stream of streams) stream.close();
164173
- streams.clear();
164174
- listeners.clear();
164175
- journal = [];
164176
- projected = [];
164177
- pendingPrompt = void 0;
164178
- activeAssistant = void 0;
164179
- }
164180
- };
164181
- }
164182
- //#endregion
164183
- //#region src/client/arko-native-frame.ts
164184
- const conversationOnlyLayout = `
164185
- button[aria-haspopup="listbox"][aria-label="指令"],
164186
- button[aria-haspopup="listbox"][aria-label="Commands"] {
164187
- display: none !important;
164188
- }
164189
- body div:has(> :first-child > [data-slot="sidebar"]):has(> [data-rightbar-col]) > :first-child,
164190
- body div:has(> :first-child > [data-slot="sidebar"]):has(> [data-rightbar-col]) > [data-side="sidebar"] {
164191
- display: none !important;
164192
- }
164193
- body div:has(> :first-child > [data-slot="sidebar"]):has(> [data-rightbar-col]) > :nth-child(2) {
164194
- grid-column: 1 / 3; grid-row: 1;
164195
- }
164196
- body div:has(> :first-child > [data-slot="sidebar"]):has(> [data-rightbar-col]) > [data-rightbar-col] {
164197
- grid-column: 3; grid-row: 1;
164198
- }
164199
- `;
164200
- /** The owning parent installs a per-frame carrier before any native DSH code executes. */
164201
- function arkoNativeFrameDocument(html, key, origin) {
164202
- if (!/^[a-zA-Z0-9-]+$/.test(key) || !/<head(?:\s[^>]*)?>/i.test(html)) throw new Error("Arko 原生页面启动配置无效");
164203
- const base = new URL("/", origin).href.replaceAll("&", "&amp;").replaceAll("\"", "&quot;");
164204
- return html.replace(/<head(?:\s[^>]*)?>/i, (head) => `${head}<base href="${base}"><style data-arkme-arko-layout>${conversationOnlyLayout}</style><script>
164205
- const carrier = parent.__arkmeArkoNativeFrames?.get(${JSON.stringify(key)});
164206
- if (!carrier) throw new Error('Arko native carrier unavailable');
164207
- // Session selection belongs to this document, not the ordinary DSH tab.
164208
- const storage = globalThis.localStorage;
164209
- const isolated = new Map();
164210
- const conversationKey = 'dsh.conversation.' + carrier.sessionId;
164211
- for (const method of ['getItem', 'setItem', 'removeItem']) {
164212
- const original = Storage.prototype[method];
164213
- Storage.prototype[method] = function(key, value) {
164214
- if (this !== storage || (key !== 'dsh.sessions.current' && key !== conversationKey)) return original.apply(this, arguments);
164215
- if (method === 'getItem') return isolated.get(key) ?? null;
164216
- if (method === 'removeItem') isolated.delete(key);
164217
- else isolated.set(key, String(value));
164218
- };
164219
- }
164220
- globalThis.__DSH_TRANSPORT__ = {
164221
- async fetch(...args) {
164222
- const response = await carrier.fetch(...args);
164223
- return new Response(await response.text(), { status: response.status, headers: { 'content-type': 'application/json' } });
164224
- },
164225
- async *openStream(...args) {
164226
- for await (const value of carrier.openStream(...args)) yield JSON.parse(JSON.stringify(value));
164227
- },
164228
- };
164229
- globalThis.__ARKME_NATIVE_ARKO__ = carrier;
164230
- <\/script>`);
164231
- }
164232
- //#endregion
164233
- //#region src/client/ArkoNativeSurface.tsx
164234
- /** This component owns only the native document and its disposable presentation carrier. */
164235
- function ArkoNativeSurface({ snapshot, actions, ui }) {
164236
- const latest = (0, react$1.useRef)({
164237
- snapshot,
164238
- actions,
164239
- ui
164240
- });
164241
- latest.current = {
164242
- snapshot,
164243
- actions,
164244
- ui
164245
- };
164246
- const carrier = (0, react$1.useRef)();
164247
- const [html, setHtml] = (0, react$1.useState)("");
164248
- const [error, setError] = (0, react$1.useState)("");
164249
- const [ready, setReady] = (0, react$1.useState)(false);
164250
- const [attempt, setAttempt] = (0, react$1.useState)(0);
164251
- (0, react$1.useLayoutEffect)(() => {
164252
- const transport = carrier.current;
164253
- if (!transport || transport.getSnapshot().accountKey !== snapshot.accountKey) return;
164254
- const revision = transport.historyRevision;
164255
- transport.ui = ui;
164256
- transport.update(snapshot, actions);
164257
- if (transport.historyRevision !== revision) setAttempt((value) => value + 1);
164258
- }, [
164259
- snapshot,
164260
- actions,
164261
- ui
164262
- ]);
164263
- (0, react$1.useEffect)(() => {
164264
- if (snapshot.loading) return;
164265
- const controller = new AbortController();
164266
- const key = crypto.randomUUID();
164267
- const current = latest.current;
164268
- const transport = Object.assign(createArkoNativeTransport(current.snapshot, current.actions), {
164269
- ui: current.ui,
164270
- ready: () => {
164271
- if (!controller.signal.aborted) {
164272
- setError("");
164273
- setReady(true);
164274
- }
164275
- }
164276
- });
164277
- carrier.current = transport;
164278
- const frames = window.__arkmeArkoNativeFrames ??= /* @__PURE__ */ new Map();
164279
- frames.set(key, transport);
164280
- setError("");
164281
- setHtml("");
164282
- setReady(false);
164283
- fetch("/arkme-self/harness-frame?arkme-arko=1", {
164284
- signal: controller.signal,
164285
- credentials: "same-origin"
164286
- }).then(async (response) => {
164287
- if (!response.ok) throw new Error(`原生页面加载失败(${response.status})`);
164288
- const source = await response.text();
164289
- if (!controller.signal.aborted) setHtml(arkoNativeFrameDocument(source, key, location.origin));
164290
- }).catch((caught) => {
164291
- if (!controller.signal.aborted) setError(caught instanceof Error ? caught.message : String(caught));
164292
- });
164293
- const deadline = window.setTimeout(() => {
164294
- if (!controller.signal.aborted) setReady((value) => {
164295
- if (!value) setError("原生页面启动超时,请重试");
164296
- return value;
164297
- });
164298
- }, 3e4);
164299
- return () => {
164300
- controller.abort();
164301
- clearTimeout(deadline);
164302
- transport.dispose();
164303
- frames.delete(key);
164304
- if (carrier.current === transport) carrier.current = void 0;
164305
- };
164306
- }, [
164307
- snapshot.accountKey,
164308
- attempt,
164309
- snapshot.loading
164310
- ]);
164311
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
164312
- "data-arkme-owned": "arko-native-surface",
164313
- style: {
164314
- height: "100%",
164315
- minHeight: 0,
164316
- position: "relative",
164317
- display: "flex",
164318
- flexDirection: "column"
164319
- },
164320
- children: [
164321
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
164322
- role: "alert",
164323
- children: [error, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
164324
- onClick: () => setAttempt((value) => value + 1),
164325
- children: "重新加载原生页面"
164326
- })]
164327
- }),
164328
- !ready && !error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
164329
- role: "status",
164330
- "aria-label": "正在加载 Arko 对话",
164331
- "data-arko-loading-skeleton": true,
164332
- style: {
164333
- position: "absolute",
164334
- inset: 0,
164335
- display: "flex",
164336
- flexDirection: "column",
164337
- background: arkmeTheme.base
164338
- },
164339
- children: [
164340
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
164341
- style: {
164342
- display: "flex",
164343
- alignItems: "center",
164344
- gap: 10,
164345
- padding: "16px 24px",
164346
- borderBottom: `1px solid ${arkmeTheme.border}`
164347
- },
164348
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkmeArkoAvatar, { size: 30 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
164349
- style: { color: arkmeTheme.text },
164350
- children: snapshot.displayName
164351
- })]
164352
- }),
164353
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
164354
- "aria-hidden": true,
164355
- style: {
164356
- flex: 1,
164357
- width: "min(720px, 86%)",
164358
- margin: "24px auto",
164359
- display: "flex",
164360
- flexDirection: "column",
164361
- gap: 20
164362
- },
164363
- children: [
164364
- 70,
164365
- 45,
164366
- 82
164367
- ].map((width, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
164368
- width: `${width}%`,
164369
- height: index === 1 ? 40 : 64,
164370
- alignSelf: index === 1 ? "flex-end" : "flex-start",
164371
- borderRadius: 12,
164372
- background: arkmeTheme.hover
164373
- } }, index))
164374
- }),
164375
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
164376
- style: {
164377
- width: "min(720px, 86%)",
164378
- margin: "0 auto 24px"
164379
- },
164380
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
164381
- "aria-hidden": true,
164382
- style: {
164383
- height: 92,
164384
- border: `1px solid ${arkmeTheme.border}`,
164385
- borderRadius: 16,
164386
- background: arkmeTheme.hover
164387
- }
164388
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
164389
- style: {
164390
- textAlign: "center",
164391
- color: arkmeTheme.secondary,
164392
- fontSize: 12,
164393
- paddingTop: 10
164394
- },
164395
- children: "正在加载对话…"
164396
- })]
164397
- })
164398
- ]
164399
- }),
164400
- html && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
164401
- name: "arkme-arko-native",
164402
- title: "Arko 原生 DSH 页面",
164403
- srcDoc: html,
164404
- style: {
164405
- border: 0,
164406
- width: "100%",
164407
- flex: 1,
164408
- minHeight: 0,
164409
- visibility: ready && !error ? "visible" : "hidden"
164410
- },
164411
- "aria-hidden": !ready || !!error,
164412
- allow: "clipboard-read; clipboard-write"
164413
- })
164414
- ]
164415
- });
164416
- }
164417
- //#endregion
164418
- //#region src/client/arko-pending-turn-store.ts
164419
- const STORAGE_KEY_PREFIX = "arkme:arko:pending-turn:v1:";
164420
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
164421
- function defaultStorage() {
164422
- try {
164423
- return typeof globalThis.sessionStorage === "undefined" ? void 0 : globalThis.sessionStorage;
164424
- } catch {
164425
- return;
164426
- }
164427
- }
164428
- function storageKey(userId) {
164429
- return `${STORAGE_KEY_PREFIX}${String(userId)}`;
164430
- }
164431
- function optionalTrimmedString(value) {
164432
- if (typeof value !== "string") return void 0;
164433
- const normalized = value.trim();
164434
- return normalized === "" ? void 0 : normalized;
164435
- }
164436
- function isValidArkoPendingTurn(value, expectedUserId) {
164437
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
164438
- const turn = value;
164439
- const hasContinuationRun = optionalTrimmedString(turn.replyToRunUid) !== void 0;
164440
- const hasContinuationMessage = Number.isSafeInteger(turn.replyToAssistantMsgId) && (turn.replyToAssistantMsgId ?? 0) > 0;
164441
- return Number.isSafeInteger(turn.userId) && (turn.userId ?? 0) > 0 && (expectedUserId === void 0 || turn.userId === expectedUserId) && Number.isSafeInteger(turn.sessionId) && (turn.sessionId ?? 0) > 0 && typeof turn.clientTurnUid === "string" && UUID_PATTERN.test(turn.clientTurnUid) && typeof turn.text === "string" && turn.text.trim() !== "" && Number.isSafeInteger(turn.createdAtMillis) && (turn.createdAtMillis ?? 0) > 0 && optionalTrimmedString(turn.localUserMessageId) !== void 0 && optionalTrimmedString(turn.localAssistantMessageId) !== void 0 && (turn.modelRouteKey === void 0 || optionalTrimmedString(turn.modelRouteKey) !== void 0) && hasContinuationRun === hasContinuationMessage && (!hasContinuationRun || turn.modelRouteKey === void 0);
164442
- }
164443
- function readArkoPendingTurn(userId, storage = defaultStorage()) {
164444
- if (!Number.isSafeInteger(userId) || userId <= 0 || storage === void 0) return void 0;
164445
- try {
164446
- const encoded = storage.getItem(storageKey(userId));
164447
- if (encoded === null) return void 0;
164448
- const value = JSON.parse(encoded);
164449
- if (!isValidArkoPendingTurn(value, userId)) {
164450
- storage.removeItem(storageKey(userId));
164451
- return;
164452
- }
164453
- return value;
164454
- } catch {
164455
- return;
164456
- }
164457
- }
164458
- function writeArkoPendingTurn(turn, storage = defaultStorage()) {
164459
- if (!isValidArkoPendingTurn(turn) || storage === void 0) return false;
164460
- try {
164461
- storage.setItem(storageKey(turn.userId), JSON.stringify(turn));
164462
- return true;
164463
- } catch {
164464
- return false;
164465
- }
164466
- }
164467
- function removeArkoPendingTurn(userId, storage = defaultStorage()) {
164468
- if (!Number.isSafeInteger(userId) || userId <= 0 || storage === void 0) return;
164469
- try {
164470
- storage.removeItem(storageKey(userId));
164471
- } catch {}
164472
- }
164473
- //#endregion
164474
- //#region src/client/useArkoConversationController.ts
164475
- const arkoQuestionMaxLength = 60 * 1024;
164476
- const ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
164477
- "accepted",
164478
- "queued",
164479
- "running",
164480
- "stream_timeout",
164481
- "waiting_tool"
164482
- ]);
164483
- function errorMessage$8(error) {
164484
- if (error instanceof ArkmeClientError) return error.body.message;
164485
- return error instanceof Error ? error.message : String(error);
164486
- }
164487
- function isActiveRunStatus(status) {
164488
- return status !== void 0 && ACTIVE_RUN_STATUSES.has(status);
164489
- }
164490
- function isActivityPlaceholderText(value) {
164491
- return /^(正在)?(思考|处理)(中)?[.。…]*$/.test(value.trim());
164492
- }
164493
- function isActiveHistoryRun(item) {
164494
- const placeholder = isActivityPlaceholderText(item.text);
164495
- return item.role === "assistant" && (isActiveRunStatus(item.runStatus) || item.runStatus === void 0 && item.status === 2 && (item.text.trim() === "" || placeholder));
164496
- }
164497
- function arkoHistoryHasTerminalRun(items, sessionId, assistantMsgId, runUid) {
164498
- const matchingItem = items.find((item) => item.role === "assistant" && item.sessionId === sessionId && item.messageId === assistantMsgId && (item.runUid === void 0 || item.runUid === runUid));
164499
- return matchingItem !== void 0 && !isActiveHistoryRun(matchingItem);
164500
- }
164501
- function waitForNextPoll(signal, delayMillis = 1200) {
164502
- return new Promise((resolve) => {
164503
- if (signal.aborted) {
164504
- resolve();
164505
- return;
164506
- }
164507
- const finish = () => {
164508
- clearTimeout(timeout);
164509
- signal.removeEventListener("abort", finish);
164510
- resolve();
164511
- };
164512
- const timeout = setTimeout(finish, delayMillis);
164513
- signal.addEventListener("abort", finish, { once: true });
164514
- });
164515
- }
164516
- function historyMessage(item) {
164517
- const placeholder = isActivityPlaceholderText(item.text);
164518
- const reasoningPlaceholder = isActivityPlaceholderText(item.reasoning);
164519
- const active = isActiveHistoryRun(item);
164520
- return {
164521
- id: `history:${String(item.messageId)}`,
164522
- messageId: item.messageId,
164523
- sessionId: item.sessionId,
164524
- role: item.role,
164525
- text: active && placeholder ? "" : item.text,
164526
- status: active ? "sending" : item.runStatus === "failed" ? "error" : "done",
164527
- createdAtMillis: item.createdAtMillis,
164528
- ...item.reasoning.trim() === "" || !active && reasoningPlaceholder ? {} : { reasoning: item.reasoning },
164529
- ...item.role !== "assistant" ? {} : { assistantMsgId: item.messageId },
164530
- ...item.runUid === void 0 ? {} : { runUid: item.runUid },
164531
- ...item.runStatus === void 0 ? {} : { runStatus: item.runStatus },
164532
- ...item.messageActionRef === void 0 ? {} : {
164533
- messageActionRef: item.messageActionRef,
164534
- ...item.messageActionConversationRef === void 0 ? {} : { messageActionConversationRef: item.messageActionConversationRef },
164535
- copyLinkAvailable: item.messageActionCapabilities?.copyLink === true,
164536
- forwardAvailable: item.messageActionCapabilities?.forward === true
164537
- }
164538
- };
164539
- }
164540
- function latestActiveRun(items, sessionId) {
164541
- const item = items.filter((candidate) => candidate.sessionId === sessionId && candidate.role === "assistant" && candidate.runUid !== void 0 && isActiveRunStatus(candidate.runStatus)).sort((left, right) => right.createdAtMillis - left.createdAtMillis || right.messageId - left.messageId)[0];
164542
- if (item?.runUid === void 0) return void 0;
164543
- return {
164544
- sessionId: item.sessionId,
164545
- assistantMsgId: item.messageId,
164546
- runUid: item.runUid
164547
- };
164548
- }
164549
- function mergeHistory(current, items) {
164550
- const byId = new Map(current.map((item) => [item.id, item]));
164551
- for (const item of items) {
164552
- const key = `history:${String(item.messageId)}`;
164553
- const prior = byId.get(key);
164554
- byId.set(key, {
164555
- ...historyMessage(item),
164556
- ...prior?.presentationKey ? { presentationKey: prior.presentationKey } : {}
164557
- });
164558
- }
164559
- return [...byId.values()].sort((left, right) => {
164560
- const leftAt = left.createdAtMillis ?? Number.MAX_SAFE_INTEGER;
164561
- const rightAt = right.createdAtMillis ?? Number.MAX_SAFE_INTEGER;
164562
- if (leftAt !== rightAt) return leftAt - rightAt;
164563
- return (left.messageId ?? Number.MAX_SAFE_INTEGER) - (right.messageId ?? Number.MAX_SAFE_INTEGER);
164564
- });
164565
- }
164566
- function resultText(result) {
164567
- if (result.text.trim() !== "") return result.text.trim();
164568
- if (result.errorMessage?.trim()) return result.errorMessage.trim();
164569
- if (isActiveRunStatus(result.run?.status ?? result.status)) return "";
164570
- return "任务已处理。";
164571
- }
164572
- function latestContinuation(messages, sessionId) {
164573
- if (sessionId === void 0) return void 0;
164574
- for (let index = messages.length - 1; index >= 0; index -= 1) {
164575
- const message = messages[index];
164576
- if (message === void 0) continue;
164577
- if (message.role === "divider") return void 0;
164578
- if (message.sessionId !== sessionId) continue;
164579
- if (message.role === "user") return void 0;
164580
- if (message.runStatus !== "waiting_user" || message.runUid === void 0 || message.assistantMsgId === void 0) return;
164581
- return {
164582
- runUid: message.runUid,
164583
- assistantMsgId: message.assistantMsgId
164584
- };
164585
- }
164586
- }
164587
- function selectedModelName(catalog) {
164588
- return catalog?.options.find((option) => option.routeKey === catalog.effectiveRouteKey)?.displayName ?? "模型目录暂不可用";
164589
- }
164590
- function useArkoConversationController() {
164591
- const alive = (0, react$1.useRef)(true);
164592
- (0, react$1.useEffect)(() => {
164593
- alive.current = true;
164594
- return () => {
164595
- alive.current = false;
164596
- };
164597
- }, []);
164598
- useArkmeLocale();
164599
- const historyLoadInFlightRef = (0, react$1.useRef)(false);
164600
- const sendInFlightRef = (0, react$1.useRef)(false);
164601
- const authSnapshot = (0, react$1.useSyncExternalStore)(arkmeAuthStore.subscribe, arkmeAuthStore.getSnapshot, arkmeAuthStore.getSnapshot);
164602
- const profileSnapshot = (0, react$1.useSyncExternalStore)(arkmeArkoProfileStore.subscribe, arkmeArkoProfileStore.getSnapshot, arkmeArkoProfileStore.getSnapshot);
164603
- const profileUserId = authSnapshot.auth?.status === "authenticated" ? authSnapshot.auth.userId : void 0;
164604
- const composerDraftKey = arkmeArkoComposerDraftKey(profileUserId);
164605
- (0, react$1.useSyncExternalStore)(arkmeComposerDraftStore.subscribe, arkmeComposerDraftStore.getRevision, arkmeComposerDraftStore.getRevision);
164606
- const draftSnapshot = arkmeComposerDraftStore.get(composerDraftKey);
164607
- const draft = draftSnapshot.text;
164608
- const accountKey = authSnapshot.auth?.status === "authenticated" && profileUserId !== void 0 ? `${authSnapshot.auth.environment}:${String(profileUserId)}` : void 0;
164609
- const profile = profileSnapshot.userId === profileUserId ? profileSnapshot.profile : void 0;
164610
- const [userProfile, setUserProfile] = (0, react$1.useState)(null);
164611
- const [session, setSession] = (0, react$1.useState)();
164612
- const [catalog, setCatalog] = (0, react$1.useState)();
164613
- const [messages, setMessages] = (0, react$1.useState)([]);
164614
- const [historyOffset, setHistoryOffset] = (0, react$1.useState)(null);
164615
- const [historyError, setHistoryError] = (0, react$1.useState)("");
164616
- const [historyPageError, setHistoryPageError] = (0, react$1.useState)("");
164617
- const historyPageAbort = (0, react$1.useRef)();
164618
- (0, react$1.useEffect)(() => () => {
164619
- historyPageAbort.current?.abort();
164620
- }, [accountKey]);
164621
- const [loading, setLoading] = (0, react$1.useState)(true);
164622
- const [historyLoading, setHistoryLoading] = (0, react$1.useState)(false);
164623
- const [sending, setSending] = (0, react$1.useState)(false);
164624
- const [clearing, setClearing] = (0, react$1.useState)(false);
164625
- const clearInFlightRef = (0, react$1.useRef)(false);
164626
- const [selectingModel, setSelectingModel] = (0, react$1.useState)(false);
164627
- const modelSelectionInFlightRef = (0, react$1.useRef)(false);
164628
- const [cancelling, setCancelling] = (0, react$1.useState)(false);
164629
- const [error, setError] = (0, react$1.useState)("");
164630
- const [notice, setNotice] = (0, react$1.useState)("");
164631
- const [activeRun, setActiveRun] = (0, react$1.useState)();
164632
- const [pendingTurn, setPendingTurn] = (0, react$1.useState)();
164633
- (0, react$1.useEffect)(() => {
164634
- arkmeArkoProfileStore.activateUser(profileUserId);
164635
- arkmeArkoConversationPreviewStore.activateUser(profileUserId);
164636
- }, [profileUserId]);
164637
- (0, react$1.useEffect)(() => {
164638
- if (profileUserId === void 0) return;
164639
- arkmeArkoConversationPreviewStore.setLatestFromSurface(profileUserId, messages.filter((message) => message.role !== "divider").map((message) => ({
164640
- key: message.id,
164641
- text: message.text,
164642
- ...message.messageId === void 0 ? {} : { messageId: message.messageId },
164643
- ...message.createdAtMillis === void 0 ? {} : { createdAtMillis: message.createdAtMillis }
164644
- })));
164645
- }, [messages, profileUserId]);
164646
- (0, react$1.useEffect)(() => {
164647
- if (profileUserId === void 0) {
164648
- setPendingTurn(void 0);
164649
- return;
164650
- }
164651
- const restored = readArkoPendingTurn(profileUserId);
164652
- setPendingTurn(restored);
164653
- if (restored === void 0) return;
164654
- setMessages((current) => {
164655
- if (current.some((item) => item.id === restored.localAssistantMessageId)) return current;
164656
- return [
164657
- ...current,
164658
- {
164659
- id: restored.localUserMessageId,
164660
- presentationKey: restored.localUserMessageId,
164661
- sessionId: restored.sessionId,
164662
- role: "user",
164663
- text: restored.text,
164664
- status: "done",
164665
- createdAtMillis: restored.createdAtMillis
164666
- },
164667
- {
164668
- id: restored.localAssistantMessageId,
164669
- presentationKey: restored.localAssistantMessageId,
164670
- sessionId: restored.sessionId,
164671
- role: "assistant",
164672
- text: "",
164673
- status: "error",
164674
- createdAtMillis: restored.createdAtMillis + 1
164675
- }
164676
- ];
164677
- });
164678
- }, [profileUserId]);
164679
- (0, react$1.useEffect)(() => {
164680
- const controller = new AbortController();
164681
- setLoading(true);
164682
- setError("");
164683
- setHistoryError("");
164684
- Promise.allSettled([
164685
- callArkme("arko.session", void 0, controller.signal),
164686
- callArkme("arko.profile", void 0, controller.signal),
164687
- callArkme("arko.models", void 0, controller.signal),
164688
- callArkme("arko.history", {
164689
- limit: 50,
164690
- offset: 0
164691
- }, controller.signal),
164692
- callArkme("user.profile", void 0, controller.signal).then(async (snapshot) => snapshot.profile === null ? await callArkme("user.profile.refresh", void 0, controller.signal) : snapshot)
164693
- ]).then(([sessionResult, profileResult, modelResult, historyResult, userProfileResult]) => {
164694
- if (controller.signal.aborted) return;
164695
- if (sessionResult.status === "rejected") {
164696
- setError(errorMessage$8(sessionResult.reason));
164697
- return;
164698
- }
164699
- setSession(sessionResult.value);
164700
- if (profileResult.status === "fulfilled" && profileUserId !== void 0) arkmeArkoProfileStore.setProfile(profileUserId, profileResult.value);
164701
- if (modelResult.status === "fulfilled") setCatalog(modelResult.value);
164702
- if (userProfileResult.status === "fulfilled") setUserProfile(userProfileResult.value.profile);
164703
- if (historyResult.status === "fulfilled") {
164704
- setMessages((current) => mergeHistory(current, historyResult.value.items));
164705
- setHistoryOffset(historyResult.value.nextOffset);
164706
- const restoredRun = latestActiveRun(historyResult.value.items, sessionResult.value.sessionId);
164707
- if (restoredRun !== void 0) {
164708
- setActiveRun(restoredRun);
164709
- setSending(true);
164710
- }
164711
- } else setHistoryError(tr$1("加载 Arko 对话记录失败:{v0}", { v0: errorMessage$8(historyResult.reason) }));
164712
- }).finally(() => {
164713
- if (!controller.signal.aborted) setLoading(false);
164714
- });
164715
- return () => {
164716
- controller.abort();
164717
- };
164718
- }, [profileUserId]);
164719
- (0, react$1.useEffect)(() => {
164720
- if (activeRun === void 0) return;
164721
- const controller = new AbortController();
164722
- let consecutiveFailures = 0;
164723
- const finishRun = async (knownHistory) => {
164724
- const [historyResult, profileResult] = await Promise.allSettled([knownHistory === void 0 ? callArkme("arko.history", {
164725
- limit: 50,
164726
- offset: 0
164727
- }, controller.signal) : Promise.resolve(knownHistory), callArkme("arko.profile", void 0, controller.signal)]);
164728
- if (controller.signal.aborted) return;
164729
- if (historyResult.status === "fulfilled") {
164730
- setMessages((current) => mergeHistory(current, historyResult.value.items));
164731
- setHistoryOffset((current) => current === null ? historyResult.value.nextOffset : current);
164732
- setHistoryError("");
164733
- } else setHistoryError(tr$1("刷新 Arko 对话记录失败:{v0}", { v0: errorMessage$8(historyResult.reason) }));
164734
- if (profileResult.status === "fulfilled" && profileUserId !== void 0) arkmeArkoProfileStore.setProfile(profileUserId, profileResult.value);
164735
- setActiveRun((current) => current?.runUid === activeRun.runUid ? void 0 : current);
164736
- setSending(false);
164737
- };
164738
- const poll = async () => {
164739
- await waitForNextPoll(controller.signal);
164740
- while (!controller.signal.aborted) {
164741
- try {
164742
- const status = await callArkme("arko.run.status", {
164743
- sessionId: activeRun.sessionId,
164744
- runUid: activeRun.runUid
164745
- }, controller.signal);
164746
- if (controller.signal.aborted) return;
164747
- consecutiveFailures = 0;
164748
- if (status.status === "waiting_tool") setNotice("当前任务需要 DSH 尚未支持的客户端操作,可以停止任务后换一种方式重试");
164749
- setMessages((current) => current.map((item) => item.assistantMsgId === activeRun.assistantMsgId ? {
164750
- ...item,
164751
- runStatus: status.status,
164752
- status: isActiveRunStatus(status.status) ? "sending" : status.status === "failed" ? "error" : "done"
164753
- } : item));
164754
- if (!isActiveRunStatus(status.status)) {
164755
- await finishRun();
164756
- return;
164757
- }
164758
- } catch {
164759
- if (controller.signal.aborted) return;
164760
- consecutiveFailures += 1;
164761
- try {
164762
- const history = await callArkme("arko.history", {
164763
- limit: 50,
164764
- offset: 0
164765
- }, controller.signal);
164766
- if (controller.signal.aborted) return;
164767
- if (arkoHistoryHasTerminalRun(history.items, activeRun.sessionId, activeRun.assistantMsgId, activeRun.runUid)) {
164768
- await finishRun(history);
164769
- return;
164770
- }
164771
- } catch {
164772
- if (controller.signal.aborted) return;
164773
- }
164774
- }
164775
- const retryDelay = consecutiveFailures === 0 ? 1200 : Math.min(1e4, 1200 * 2 ** Math.min(consecutiveFailures - 1, 3));
164776
- await waitForNextPoll(controller.signal, retryDelay);
164777
- }
164778
- };
164779
- poll();
164780
- return () => {
164781
- controller.abort();
164782
- };
164783
- }, [activeRun, profileUserId]);
164784
- const loadEarlier = (0, react$1.useCallback)(async () => {
164785
- const offset = historyOffset;
164786
- if (typeof offset !== "number") return {
164787
- messages: [],
164788
- hasMore: false
164789
- };
164790
- if (historyLoadInFlightRef.current) throw new Error("正在加载更早记录");
164791
- const controller = new AbortController();
164792
- historyPageAbort.current = controller;
164793
- historyLoadInFlightRef.current = true;
164794
- setHistoryPageError("");
164795
- setHistoryLoading(true);
164796
- try {
164797
- const page = await callArkme("arko.history", {
164798
- limit: 50,
164799
- offset
164800
- }, controller.signal);
164801
- if (controller.signal.aborted) throw new Error("Arko 历史读取已取消");
164802
- if (page.hasMore && (page.nextOffset === void 0 || page.nextOffset <= offset)) throw new Error("历史分页未前进,请重试");
164803
- setMessages((current) => {
164804
- const ids = new Set(current.map((item) => item.messageId));
164805
- return [...page.items.filter((item) => {
164806
- if (ids.has(item.messageId)) return false;
164807
- ids.add(item.messageId);
164808
- return true;
164809
- }).reverse().map(historyMessage), ...current];
164810
- });
164811
- setHistoryOffset(page.nextOffset);
164812
- return {
164813
- messages: [...page.items].reverse().map(historyMessage),
164814
- hasMore: page.hasMore
164815
- };
164816
- } catch (caught) {
164817
- if (!controller.signal.aborted) setHistoryPageError(`加载更早记录失败:${errorMessage$8(caught)}。请重试。`);
164818
- throw caught;
164819
- } finally {
164820
- if (historyPageAbort.current === controller) {
164821
- historyLoadInFlightRef.current = false;
164822
- setHistoryLoading(false);
164823
- historyPageAbort.current = void 0;
164824
- }
164825
- }
164826
- }, [historyOffset]);
164827
- const retryHistory = (0, react$1.useCallback)(async () => {
164828
- if (historyLoadInFlightRef.current) return;
164829
- const controller = new AbortController();
164830
- historyPageAbort.current = controller;
164831
- historyLoadInFlightRef.current = true;
164832
- setHistoryLoading(true);
164833
- setHistoryError("");
164834
- try {
164835
- const page = await callArkme("arko.history", {
164836
- limit: 50,
164837
- offset: 0
164838
- }, controller.signal);
164839
- if (controller.signal.aborted) return;
164840
- setMessages((current) => mergeHistory(current, page.items));
164841
- setHistoryOffset((current) => current === null ? page.nextOffset : current);
164842
- if (session !== void 0) {
164843
- const restoredRun = latestActiveRun(page.items, session.sessionId);
164844
- if (restoredRun !== void 0) {
164845
- setActiveRun(restoredRun);
164846
- setSending(true);
164847
- }
164848
- }
164849
- } catch (caught) {
164850
- if (!controller.signal.aborted) setHistoryError(tr$1("加载 Arko 对话记录失败:{v0}", { v0: errorMessage$8(caught) }));
164851
- } finally {
164852
- if (historyPageAbort.current === controller) {
164853
- historyLoadInFlightRef.current = false;
164854
- historyPageAbort.current = void 0;
164855
- setHistoryLoading(false);
164856
- }
164857
- }
164858
- }, [session]);
164859
- const submitTurn = (0, react$1.useCallback)(async (turn) => {
164860
- if (sendInFlightRef.current || sending || activeRun !== void 0) return false;
164861
- sendInFlightRef.current = true;
164862
- let handedOffToPolling = false;
164863
- setSending(true);
164864
- setError("");
164865
- setNotice("");
164866
- setMessages((current) => current.map((item) => item.id === turn.localAssistantMessageId ? {
164867
- ...item,
164868
- text: "",
164869
- status: "sending",
164870
- runStatus: "accepted"
164871
- } : item));
164872
- try {
164873
- const result = await callArkme("arko.ask", {
164874
- text: turn.text,
164875
- sessionId: turn.sessionId,
164876
- clientTurnUid: turn.clientTurnUid,
164877
- waitSeconds: 1,
164878
- ...turn.modelRouteKey === void 0 ? {} : { modelRouteKey: turn.modelRouteKey },
164879
- ...turn.replyToRunUid === void 0 ? {} : { replyToRunUid: turn.replyToRunUid },
164880
- ...turn.replyToAssistantMsgId === void 0 ? {} : { replyToAssistantMsgId: turn.replyToAssistantMsgId }
164881
- });
164882
- if (!alive.current) return false;
164883
- removeArkoPendingTurn(turn.userId);
164884
- setPendingTurn((current) => current?.clientTurnUid === turn.clientTurnUid ? void 0 : current);
164885
- setSession((current) => current === void 0 ? current : {
164886
- ...current,
164887
- sessionId: result.sessionId
164888
- });
164889
- if (result.profile !== void 0 && profileUserId !== void 0) arkmeArkoProfileStore.setProfile(profileUserId, result.profile);
164890
- const runStatus = result.run?.status ?? result.status;
164891
- const runActive = isActiveRunStatus(runStatus);
164892
- const hasVisibleReasoning = result.reasoning.trim() !== "" && (runActive || !isActivityPlaceholderText(result.reasoning));
164893
- setMessages((current) => mergeHistory(current.map((item) => item.id === turn.localUserMessageId ? {
164894
- ...item,
164895
- id: `history:${String(result.userMsgId)}`,
164896
- messageId: result.userMsgId,
164897
- sessionId: result.sessionId
164898
- } : item.id === turn.localAssistantMessageId ? {
164899
- id: `history:${String(result.assistantMsgId)}`,
164900
- presentationKey: item.presentationKey ?? item.id,
164901
- messageId: result.assistantMsgId,
164902
- sessionId: result.sessionId,
164903
- role: "assistant",
164904
- text: resultText(result),
164905
- status: runActive ? "sending" : result.errorMessage === void 0 ? "done" : "error",
164906
- assistantMsgId: result.assistantMsgId,
164907
- ...item.createdAtMillis === void 0 ? {} : { createdAtMillis: item.createdAtMillis },
164908
- ...hasVisibleReasoning ? { reasoning: result.reasoning } : {},
164909
- ...result.runUid === void 0 ? {} : { runUid: result.runUid },
164910
- runStatus
164911
- } : item), []));
164912
- if (runActive && result.runUid !== void 0) {
164913
- handedOffToPolling = true;
164914
- setActiveRun({
164915
- sessionId: result.sessionId,
164916
- assistantMsgId: result.assistantMsgId,
164917
- runUid: result.runUid
164918
- });
164919
- }
164920
- return true;
164921
- } catch (caught) {
164922
- if (!alive.current) return false;
164923
- const message = errorMessage$8(caught);
164924
- if (!(caught instanceof ArkmeClientError) || caught.body.retryable) {
164925
- setPendingTurn(turn);
164926
- writeArkoPendingTurn(turn);
164927
- setError(`Arko 发送结果暂未确认:${message}。请重试确认,系统会复用同一次请求,不会重复执行。`);
164928
- setMessages((current) => current.map((item) => item.id === turn.localAssistantMessageId ? {
164929
- ...item,
164930
- role: "assistant",
164931
- text: "",
164932
- status: "error"
164933
- } : item));
164934
- } else {
164935
- removeArkoPendingTurn(turn.userId);
164936
- setPendingTurn((current) => current?.clientTurnUid === turn.clientTurnUid ? void 0 : current);
164937
- setError(message);
164938
- setMessages((current) => current.map((item) => item.id === turn.localAssistantMessageId ? {
164939
- ...item,
164940
- role: "assistant",
164941
- text: message,
164942
- status: "error"
164943
- } : item));
164944
- }
164945
- return false;
164946
- } finally {
164947
- sendInFlightRef.current = false;
164948
- if (!handedOffToPolling) setSending(false);
164949
- }
164950
- }, [
164951
- activeRun,
164952
- composerDraftKey,
164953
- profileUserId,
164954
- sending
164955
- ]);
164956
- const interactionLocked = sending || pendingTurn !== void 0 || activeRun !== void 0;
164957
- const sendDisabled = loading || interactionLocked || clearing || selectingModel || session === void 0 || profileUserId === void 0;
164958
- const inputDisabled = loading || interactionLocked || session === void 0 || profileUserId === void 0;
164959
- const send = (0, react$1.useCallback)(async (presetText) => {
164960
- const text = (presetText ?? serializeArkmeComposerDraft(arkmeComposerDraftStore.get(composerDraftKey)).text).trim();
164961
- if (text === "" || sendInFlightRef.current || modelSelectionInFlightRef.current || clearInFlightRef.current || sendDisabled || session === void 0 || profileUserId === void 0 || composerDraftKey === void 0) return false;
164962
- if (text.length > 61440) {
164963
- setError("内容长度超过上限,请删减后再发送");
164964
- return false;
164965
- }
164966
- const continuation = latestContinuation(messages, session.sessionId);
164967
- const createdAtMillis = Date.now();
164968
- const turn = {
164969
- userId: profileUserId,
164970
- sessionId: session.sessionId,
164971
- clientTurnUid: crypto.randomUUID(),
164972
- text,
164973
- createdAtMillis,
164974
- localUserMessageId: crypto.randomUUID(),
164975
- localAssistantMessageId: crypto.randomUUID(),
164976
- ...continuation === void 0 ? { ...catalog === void 0 ? {} : { modelRouteKey: catalog.effectiveRouteKey } } : {
164977
- replyToRunUid: continuation.runUid,
164978
- replyToAssistantMsgId: continuation.assistantMsgId
164979
- }
164980
- };
164981
- writeArkoPendingTurn(turn);
164982
- setPendingTurn(turn);
164983
- if (presetText === void 0) arkmeComposerDraftStore.clear(composerDraftKey);
164984
- setMessages((current) => [
164985
- ...current,
164986
- {
164987
- id: turn.localUserMessageId,
164988
- presentationKey: turn.localUserMessageId,
164989
- sessionId: turn.sessionId,
164990
- role: "user",
164991
- text,
164992
- status: "done",
164993
- createdAtMillis
164994
- },
164995
- {
164996
- id: turn.localAssistantMessageId,
164997
- presentationKey: turn.localAssistantMessageId,
164998
- sessionId: turn.sessionId,
164999
- role: "assistant",
165000
- text: "",
165001
- status: "sending",
165002
- runStatus: "accepted",
165003
- createdAtMillis: createdAtMillis + 1
165004
- }
165005
- ]);
165006
- return await submitTurn(turn);
165007
- }, [
165008
- catalog,
165009
- composerDraftKey,
165010
- messages,
165011
- profileUserId,
165012
- sendDisabled,
165013
- session,
165014
- submitTurn
165015
- ]);
165016
- const selectModel = (0, react$1.useCallback)(async (routeKey) => {
165017
- if (modelSelectionInFlightRef.current || sendInFlightRef.current || clearInFlightRef.current || loading || interactionLocked || clearing || catalog === void 0 || routeKey === catalog.effectiveRouteKey || !catalog.options.some((option) => option.routeKey === routeKey)) return false;
165018
- modelSelectionInFlightRef.current = true;
165019
- setSelectingModel(true);
165020
- setError("");
165021
- try {
165022
- const next = await callArkme("arko.model.activate", { routeKey });
165023
- if (!alive.current) return false;
165024
- setCatalog(next);
165025
- return true;
165026
- } catch (caught) {
165027
- setError(errorMessage$8(caught));
165028
- return false;
165029
- } finally {
165030
- modelSelectionInFlightRef.current = false;
165031
- setSelectingModel(false);
165032
- }
165033
- }, [
165034
- catalog,
165035
- clearing,
165036
- interactionLocked,
165037
- loading
165038
- ]);
165039
- const cancelActiveRun = (0, react$1.useCallback)(async () => {
165040
- if (activeRun === void 0 || cancelling) return false;
165041
- setCancelling(true);
165042
- setError("");
165043
- try {
165044
- await callArkme("arko.cancel", {
165045
- sessionId: activeRun.sessionId,
165046
- assistantMsgId: activeRun.assistantMsgId,
165047
- runUid: activeRun.runUid
165048
- });
165049
- setNotice("已请求停止当前任务,正在确认最终状态");
165050
- return true;
165051
- } catch (caught) {
165052
- setError(tr$1("停止 Arko 任务失败:{v0}", { v0: errorMessage$8(caught) }));
165053
- return false;
165054
- } finally {
165055
- setCancelling(false);
165056
- }
165057
- }, [activeRun, cancelling]);
165058
- const clearContext = (0, react$1.useCallback)(async () => {
165059
- if (clearInFlightRef.current || sendInFlightRef.current || modelSelectionInFlightRef.current || loading || interactionLocked || clearing) return;
165060
- clearInFlightRef.current = true;
165061
- setClearing(true);
165062
- setError("");
165063
- setNotice("");
165064
- try {
165065
- const nextSession = await callArkme("arko.new-session");
165066
- if (!alive.current) return;
165067
- setSession(nextSession);
165068
- setMessages((current) => [...current, {
165069
- id: crypto.randomUUID(),
165070
- role: "divider",
165071
- text: "新的对话",
165072
- status: "done",
165073
- createdAtMillis: Date.now()
165074
- }]);
165075
- setNotice("上下文已清除,历史记录仍然保留");
165076
- } catch (caught) {
165077
- setError(errorMessage$8(caught));
165078
- } finally {
165079
- clearInFlightRef.current = false;
165080
- setClearing(false);
165081
- }
165082
- }, [
165083
- clearing,
165084
- interactionLocked,
165085
- loading
165086
- ]);
165087
- return {
165088
- accountKey,
165089
- profileUserId,
165090
- composerDraftKey,
165091
- draftSnapshot,
165092
- draft,
165093
- userProfile,
165094
- session,
165095
- catalog,
165096
- messages,
165097
- historyOffset,
165098
- historyError,
165099
- historyPageError,
165100
- loading,
165101
- historyLoading,
165102
- sending,
165103
- clearing,
165104
- selectingModel,
165105
- cancelling,
165106
- error,
165107
- notice,
165108
- activeRun,
165109
- pendingTurn,
165110
- interactionLocked,
165111
- sendDisabled,
165112
- inputDisabled,
165113
- displayName: arkoPresentationName(profile),
165114
- selectedModel: selectedModelName(catalog),
165115
- canChooseModel: (catalog?.options.length ?? 0) > 1,
165116
- continuation: (0, react$1.useMemo)(() => latestContinuation(messages, session?.sessionId), [messages, session?.sessionId]),
165117
- send,
165118
- submitTurn,
165119
- selectModel,
165120
- cancelActiveRun,
165121
- clearContext,
165122
- loadEarlier,
165123
- retryHistory,
165124
- setDraft: (text) => arkmeComposerDraftStore.setText(composerDraftKey, text),
165125
- feedback: setError
165126
- };
165127
- }
165128
- //#endregion
165129
163493
  //#region src/harness-conversation-layout-contract.ts
165130
163494
  /** Optional, presentation-only exports from the installed Harness runtime. */
165131
163495
  const HARNESS_LAYOUT_MODULES = {
@@ -165595,6 +163959,62 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
165595
163959
  r$3.displayName = "ArrowsClockwiseIcon";
165596
163960
  const m$4 = r$3;
165597
163961
  //#endregion
163962
+ //#region src/client/arko-pending-turn-store.ts
163963
+ const STORAGE_KEY_PREFIX = "arkme:arko:pending-turn:v1:";
163964
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
163965
+ function defaultStorage() {
163966
+ try {
163967
+ return typeof globalThis.sessionStorage === "undefined" ? void 0 : globalThis.sessionStorage;
163968
+ } catch {
163969
+ return;
163970
+ }
163971
+ }
163972
+ function storageKey(userId) {
163973
+ return `${STORAGE_KEY_PREFIX}${String(userId)}`;
163974
+ }
163975
+ function optionalTrimmedString(value) {
163976
+ if (typeof value !== "string") return void 0;
163977
+ const normalized = value.trim();
163978
+ return normalized === "" ? void 0 : normalized;
163979
+ }
163980
+ function isValidArkoPendingTurn(value, expectedUserId) {
163981
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
163982
+ const turn = value;
163983
+ const hasContinuationRun = optionalTrimmedString(turn.replyToRunUid) !== void 0;
163984
+ const hasContinuationMessage = Number.isSafeInteger(turn.replyToAssistantMsgId) && (turn.replyToAssistantMsgId ?? 0) > 0;
163985
+ return Number.isSafeInteger(turn.userId) && (turn.userId ?? 0) > 0 && (expectedUserId === void 0 || turn.userId === expectedUserId) && Number.isSafeInteger(turn.sessionId) && (turn.sessionId ?? 0) > 0 && typeof turn.clientTurnUid === "string" && UUID_PATTERN.test(turn.clientTurnUid) && typeof turn.text === "string" && turn.text.trim() !== "" && Number.isSafeInteger(turn.createdAtMillis) && (turn.createdAtMillis ?? 0) > 0 && optionalTrimmedString(turn.localUserMessageId) !== void 0 && optionalTrimmedString(turn.localAssistantMessageId) !== void 0 && (turn.modelRouteKey === void 0 || optionalTrimmedString(turn.modelRouteKey) !== void 0) && hasContinuationRun === hasContinuationMessage && (!hasContinuationRun || turn.modelRouteKey === void 0);
163986
+ }
163987
+ function readArkoPendingTurn(userId, storage = defaultStorage()) {
163988
+ if (!Number.isSafeInteger(userId) || userId <= 0 || storage === void 0) return void 0;
163989
+ try {
163990
+ const encoded = storage.getItem(storageKey(userId));
163991
+ if (encoded === null) return void 0;
163992
+ const value = JSON.parse(encoded);
163993
+ if (!isValidArkoPendingTurn(value, userId)) {
163994
+ storage.removeItem(storageKey(userId));
163995
+ return;
163996
+ }
163997
+ return value;
163998
+ } catch {
163999
+ return;
164000
+ }
164001
+ }
164002
+ function writeArkoPendingTurn(turn, storage = defaultStorage()) {
164003
+ if (!isValidArkoPendingTurn(turn) || storage === void 0) return false;
164004
+ try {
164005
+ storage.setItem(storageKey(turn.userId), JSON.stringify(turn));
164006
+ return true;
164007
+ } catch {
164008
+ return false;
164009
+ }
164010
+ }
164011
+ function removeArkoPendingTurn(userId, storage = defaultStorage()) {
164012
+ if (!Number.isSafeInteger(userId) || userId <= 0 || storage === void 0) return;
164013
+ try {
164014
+ storage.removeItem(storageKey(userId));
164015
+ } catch {}
164016
+ }
164017
+ //#endregion
165598
164018
  //#region src/client/conversation-composer-presentation.ts
165599
164019
  const arkmeConversationComposerMaxHeight = 336;
165600
164020
  /** Keep each border property present across target and resize transitions. */
@@ -166953,7 +165373,7 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
166953
165373
  }]))
166954
165374
  };
166955
165375
  }
166956
- function errorMessage$7(error) {
165376
+ function errorMessage$8(error) {
166957
165377
  if (error instanceof ArkmeClientError) return error.body.message;
166958
165378
  return error instanceof Error ? error.message : String(error);
166959
165379
  }
@@ -167268,7 +165688,7 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
167268
165688
  await copyText$3(value);
167269
165689
  showStatus("已复制", revision);
167270
165690
  } catch (error) {
167271
- showStatus(errorMessage$7(error) || "复制失败,请稍后重试", revision);
165691
+ showStatus(errorMessage$8(error) || "复制失败,请稍后重试", revision);
167272
165692
  }
167273
165693
  }, [showStatus]);
167274
165694
  const copyLink = (0, react$1.useCallback)(async (items) => {
@@ -167296,7 +165716,7 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
167296
165716
  setSelectedIds(void 0);
167297
165717
  showStatus("已复制链接");
167298
165718
  } catch (error) {
167299
- if (revision === revisionRef.current) showStatus(controller.signal.aborted ? "复制链接超时,请重试" : errorMessage$7(error) || "分享失败,请重试");
165719
+ if (revision === revisionRef.current) showStatus(controller.signal.aborted ? "复制链接超时,请重试" : errorMessage$8(error) || "分享失败,请重试");
167300
165720
  } finally {
167301
165721
  window.clearTimeout(timeout);
167302
165722
  if (revision === revisionRef.current) setBusy(void 0);
@@ -167359,7 +165779,7 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
167359
165779
  setPicker((current) => current === void 0 ? current : {
167360
165780
  ...current,
167361
165781
  loading: false,
167362
- error: controller.signal.aborted ? "转发对象加载超时" : errorMessage$7(error) || "转发对象加载失败"
165782
+ error: controller.signal.aborted ? "转发对象加载超时" : errorMessage$8(error) || "转发对象加载失败"
167363
165783
  });
167364
165784
  } finally {
167365
165785
  window.clearTimeout(timeout);
@@ -167426,7 +165846,7 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
167426
165846
  const warningText = successes.map((success) => success.value.result.warningText?.trim() ?? "").find((value) => value !== "") ?? "转发已完成,附言发送失败";
167427
165847
  for (const success of successes) input.onForwarded?.(success.value.target, success.value.result);
167428
165848
  if (successes.length === 0) {
167429
- const message = controller.signal.aborted ? "转发超时,请重试" : errorMessage$7(results[0]?.reason) || "发送失败,请重试";
165849
+ const message = controller.signal.aborted ? "转发超时,请重试" : errorMessage$8(results[0]?.reason) || "发送失败,请重试";
167430
165850
  setPicker((current) => current === void 0 ? current : {
167431
165851
  ...current,
167432
165852
  error: message
@@ -167798,6 +166218,14 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
167798
166218
  }
167799
166219
  //#endregion
167800
166220
  //#region src/client/ArkmeArkoSurface.tsx
166221
+ const arkoQuestionMaxLength = 60 * 1024;
166222
+ const ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
166223
+ "accepted",
166224
+ "queued",
166225
+ "running",
166226
+ "stream_timeout",
166227
+ "waiting_tool"
166228
+ ]);
167801
166229
  const colors$5 = {
167802
166230
  text: arkmeTheme.text,
167803
166231
  secondary: arkmeTheme.secondary,
@@ -168300,6 +166728,10 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
168300
166728
  },
168301
166729
  stop: { background: colors$5.danger }
168302
166730
  };
166731
+ function errorMessage$7(error) {
166732
+ if (error instanceof ArkmeClientError) return error.body.message;
166733
+ return error instanceof Error ? error.message : String(error);
166734
+ }
168303
166735
  function arkoRunActivityLabel(status) {
168304
166736
  if (status === "accepted" || status === "queued") return "正在思考";
168305
166737
  if (status === "running" || status === "stream_timeout") return tr$1("正在处理");
@@ -168317,6 +166749,29 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
168317
166749
  function arkoPreservedScrollTop(previousScrollTop, previousScrollHeight, nextScrollHeight) {
168318
166750
  return Math.max(0, previousScrollTop + (nextScrollHeight - previousScrollHeight));
168319
166751
  }
166752
+ function isActiveRunStatus(status) {
166753
+ return status !== void 0 && ACTIVE_RUN_STATUSES.has(status);
166754
+ }
166755
+ function isActivityPlaceholderText(value) {
166756
+ return /^(正在)?(思考|处理)(中)?[.。…]*$/.test(value.trim());
166757
+ }
166758
+ function isActiveHistoryRun(item) {
166759
+ const placeholder = isActivityPlaceholderText(item.text);
166760
+ return item.role === "assistant" && (isActiveRunStatus(item.runStatus) || item.runStatus === void 0 && item.status === 2 && (item.text.trim() === "" || placeholder));
166761
+ }
166762
+ function arkoHistoryHasTerminalRun(items, sessionId, assistantMsgId, runUid) {
166763
+ const matchingItem = items.find((item) => item.role === "assistant" && item.sessionId === sessionId && item.messageId === assistantMsgId && (item.runUid === void 0 || item.runUid === runUid));
166764
+ return matchingItem !== void 0 && !isActiveHistoryRun(matchingItem);
166765
+ }
166766
+ function waitForNextPoll(signal, delayMillis = 1200) {
166767
+ return new Promise((resolve) => {
166768
+ const timeout = setTimeout(resolve, delayMillis);
166769
+ signal.addEventListener("abort", () => {
166770
+ clearTimeout(timeout);
166771
+ resolve();
166772
+ }, { once: true });
166773
+ });
166774
+ }
168320
166775
  function ArkoThinkingPanel({ reasoning, activity }) {
168321
166776
  useArkmeLocale();
168322
166777
  const [expanded, setExpanded] = (0, react$1.useState)(true);
@@ -168363,37 +166818,115 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
168363
166818
  })]
168364
166819
  });
168365
166820
  }
168366
- function ArkmeArkoSurface({ native = false } = {}) {
168367
- const controller = useArkoConversationController();
168368
- return native ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkoNativeConversation, { controller }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkoConversationView, { controller });
166821
+ function historyMessage(item) {
166822
+ const placeholder = isActivityPlaceholderText(item.text);
166823
+ const reasoningPlaceholder = isActivityPlaceholderText(item.reasoning);
166824
+ const active = isActiveHistoryRun(item);
166825
+ return {
166826
+ id: `history:${String(item.messageId)}`,
166827
+ messageId: item.messageId,
166828
+ sessionId: item.sessionId,
166829
+ role: item.role,
166830
+ text: active && placeholder ? "" : item.text,
166831
+ status: active ? "sending" : item.runStatus === "failed" ? "error" : "done",
166832
+ createdAtMillis: item.createdAtMillis,
166833
+ ...item.reasoning.trim() === "" || !active && reasoningPlaceholder ? {} : { reasoning: item.reasoning },
166834
+ ...item.role !== "assistant" ? {} : { assistantMsgId: item.messageId },
166835
+ ...item.runUid === void 0 ? {} : { runUid: item.runUid },
166836
+ ...item.runStatus === void 0 ? {} : { runStatus: item.runStatus },
166837
+ ...item.messageActionRef === void 0 ? {} : {
166838
+ messageActionRef: item.messageActionRef,
166839
+ ...item.messageActionConversationRef === void 0 ? {} : { messageActionConversationRef: item.messageActionConversationRef },
166840
+ copyLinkAvailable: item.messageActionCapabilities?.copyLink === true,
166841
+ forwardAvailable: item.messageActionCapabilities?.forward === true
166842
+ }
166843
+ };
168369
166844
  }
168370
- function ArkoConversationView({ controller }) {
166845
+ function latestActiveRun(items, sessionId) {
166846
+ const item = items.filter((candidate) => candidate.sessionId === sessionId && candidate.role === "assistant" && candidate.runUid !== void 0 && isActiveRunStatus(candidate.runStatus)).sort((left, right) => right.createdAtMillis - left.createdAtMillis || right.messageId - left.messageId)[0];
166847
+ if (item?.runUid === void 0) return void 0;
166848
+ return {
166849
+ sessionId: item.sessionId,
166850
+ assistantMsgId: item.messageId,
166851
+ runUid: item.runUid
166852
+ };
166853
+ }
166854
+ function mergeHistory(current, items) {
166855
+ const byId = new Map(current.map((item) => [item.id, item]));
166856
+ for (const item of items) byId.set(`history:${String(item.messageId)}`, historyMessage(item));
166857
+ return [...byId.values()].sort((left, right) => {
166858
+ const leftAt = left.createdAtMillis ?? Number.MAX_SAFE_INTEGER;
166859
+ const rightAt = right.createdAtMillis ?? Number.MAX_SAFE_INTEGER;
166860
+ if (leftAt !== rightAt) return leftAt - rightAt;
166861
+ return (left.messageId ?? Number.MAX_SAFE_INTEGER) - (right.messageId ?? Number.MAX_SAFE_INTEGER);
166862
+ });
166863
+ }
166864
+ function resultText(result) {
166865
+ if (result.text.trim() !== "") return result.text.trim();
166866
+ if (result.errorMessage?.trim()) return result.errorMessage.trim();
166867
+ if (isActiveRunStatus(result.run?.status ?? result.status)) return "";
166868
+ return "任务已处理。";
166869
+ }
166870
+ function latestContinuation(messages, sessionId) {
166871
+ if (sessionId === void 0) return void 0;
166872
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
166873
+ const message = messages[index];
166874
+ if (message === void 0) continue;
166875
+ if (message.role === "divider") return void 0;
166876
+ if (message.sessionId !== sessionId) continue;
166877
+ if (message.role === "user") return void 0;
166878
+ if (message.runStatus !== "waiting_user" || message.runUid === void 0 || message.assistantMsgId === void 0) return;
166879
+ return {
166880
+ runUid: message.runUid,
166881
+ assistantMsgId: message.assistantMsgId
166882
+ };
166883
+ }
166884
+ }
166885
+ function selectedModelName(catalog) {
166886
+ return catalog?.options.find((option) => option.routeKey === catalog.effectiveRouteKey)?.displayName ?? "模型目录暂不可用";
166887
+ }
166888
+ function ArkmeArkoSurface() {
168371
166889
  useArkmeLocale();
168372
- const { accountKey, profileUserId, composerDraftKey, draftSnapshot, draft, userProfile, session, catalog, messages, historyOffset, historyError, historyPageError, loading, historyLoading, sending, clearing, selectingModel, cancelling, error, notice, activeRun, pendingTurn, interactionLocked, sendDisabled, inputDisabled, displayName, selectedModel, canChooseModel, continuation, submitTurn, cancelActiveRun, retryHistory, feedback: setError } = controller;
168373
166890
  const bodyRef = (0, react$1.useRef)(null);
168374
166891
  const historySentinelRef = (0, react$1.useRef)(null);
166892
+ const historyLoadInFlightRef = (0, react$1.useRef)(false);
168375
166893
  const composerInputBoundaryRef = (0, react$1.useRef)(null);
168376
166894
  const composerRef = (0, react$1.useRef)(null);
168377
166895
  const textareaRef = (0, react$1.useRef)(null);
168378
166896
  const pendingComposerFocusRef = (0, react$1.useRef)(false);
166897
+ const sendInFlightRef = (0, react$1.useRef)(false);
166898
+ const authSnapshot = (0, react$1.useSyncExternalStore)(arkmeAuthStore.subscribe, arkmeAuthStore.getSnapshot, arkmeAuthStore.getSnapshot);
166899
+ const profileSnapshot = (0, react$1.useSyncExternalStore)(arkmeArkoProfileStore.subscribe, arkmeArkoProfileStore.getSnapshot, arkmeArkoProfileStore.getSnapshot);
166900
+ const profileUserId = authSnapshot.auth?.status === "authenticated" ? authSnapshot.auth.userId : void 0;
166901
+ const composerDraftKey = arkmeArkoComposerDraftKey(profileUserId);
166902
+ (0, react$1.useSyncExternalStore)(arkmeComposerDraftStore.subscribe, arkmeComposerDraftStore.getRevision, arkmeComposerDraftStore.getRevision);
166903
+ const draftSnapshot = arkmeComposerDraftStore.get(composerDraftKey);
166904
+ const draft = draftSnapshot.text;
166905
+ const accountKey = authSnapshot.auth?.status === "authenticated" && profileUserId !== void 0 ? `${authSnapshot.auth.environment}:${String(profileUserId)}` : void 0;
166906
+ const profile = profileSnapshot.userId === profileUserId ? profileSnapshot.profile : void 0;
166907
+ const [userProfile, setUserProfile] = (0, react$1.useState)(null);
166908
+ const [session, setSession] = (0, react$1.useState)();
166909
+ const [catalog, setCatalog] = (0, react$1.useState)();
166910
+ const [messages, setMessages] = (0, react$1.useState)([]);
166911
+ const [historyOffset, setHistoryOffset] = (0, react$1.useState)();
166912
+ const [historyError, setHistoryError] = (0, react$1.useState)("");
166913
+ const [loading, setLoading] = (0, react$1.useState)(true);
166914
+ const [historyLoading, setHistoryLoading] = (0, react$1.useState)(false);
166915
+ const [sending, setSending] = (0, react$1.useState)(false);
166916
+ const [clearing, setClearing] = (0, react$1.useState)(false);
166917
+ const [selectingModel, setSelectingModel] = (0, react$1.useState)(false);
166918
+ const [cancelling, setCancelling] = (0, react$1.useState)(false);
168379
166919
  const [modelDialogOpen, setModelDialogOpen] = (0, react$1.useState)(false);
168380
166920
  const [clearConfirmOpen, setClearConfirmOpen] = (0, react$1.useState)(false);
168381
- const send = (text) => {
168382
- pendingComposerFocusRef.current = true;
168383
- return controller.send(text);
168384
- };
168385
- const selectModel = async (routeKey) => {
168386
- if (await controller.selectModel(routeKey)) setModelDialogOpen(false);
168387
- };
168388
- const clearContext = async () => {
168389
- setClearConfirmOpen(false);
168390
- await controller.clearContext();
168391
- };
166921
+ const [error, setError] = (0, react$1.useState)("");
166922
+ const [notice, setNotice] = (0, react$1.useState)("");
166923
+ const [activeRun, setActiveRun] = (0, react$1.useState)();
166924
+ const [pendingTurn, setPendingTurn] = (0, react$1.useState)();
168392
166925
  const detailTriggerRef = (0, react$1.useRef)(null);
168393
166926
  const detailBodyRef = (0, react$1.useRef)(null);
168394
- const detailScope = `${accountKey ?? ""}:${String(profileUserId)}:${String(session?.sessionId)}`;
166927
+ const detailScope = `${authSnapshot.auth?.environment ?? ""}:${String(profileUserId)}:${String(session?.sessionId)}`;
168395
166928
  const [detailSelection, setDetailSelection] = (0, react$1.useState)();
168396
- const detailMessage = detailSelection?.scope === detailScope ? messages.find((message) => (message.presentationKey ?? message.id) === detailSelection.id && message.role !== "divider") : void 0;
166929
+ const detailMessage = detailSelection?.scope === detailScope ? messages.find((message) => message.id === detailSelection.id && message.role !== "divider") : void 0;
168397
166930
  const detailActivity = detailMessage?.status === "sending" ? arkoRunActivityLabel(detailMessage.runStatus) ?? "等待回复" : detailMessage?.status === "error" ? pendingTurn?.localAssistantMessageId === detailMessage.id ? "发送结果待确认" : "消息处理失败" : void 0;
168398
166931
  (0, react$1.useEffect)(() => {
168399
166932
  setDetailSelection(void 0);
@@ -168404,7 +166937,7 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
168404
166937
  trigger.focus({ preventScroll: true });
168405
166938
  setDetailSelection({
168406
166939
  scope: detailScope,
168407
- id: messages.find((message) => message.id === id)?.presentationKey ?? id
166940
+ id
168408
166941
  });
168409
166942
  };
168410
166943
  const messageActionItems = (0, react$1.useMemo)(() => messages.flatMap((message) => message.role === "divider" || message.messageActionRef === void 0 || message.messageActionConversationRef === void 0 ? [] : [{
@@ -168433,33 +166966,195 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
168433
166966
  if (bodyRef.current !== null) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
168434
166967
  });
168435
166968
  }, []);
168436
- const lastMessage = messages.at(-1);
168437
166969
  (0, react$1.useEffect)(() => {
166970
+ arkmeArkoProfileStore.activateUser(profileUserId);
166971
+ arkmeArkoConversationPreviewStore.activateUser(profileUserId);
166972
+ }, [profileUserId]);
166973
+ (0, react$1.useEffect)(() => {
166974
+ if (profileUserId === void 0) return;
166975
+ arkmeArkoConversationPreviewStore.setLatestFromSurface(profileUserId, messages.filter((message) => message.role !== "divider").map((message) => ({
166976
+ key: message.id,
166977
+ text: message.text,
166978
+ ...message.messageId === void 0 ? {} : { messageId: message.messageId },
166979
+ ...message.createdAtMillis === void 0 ? {} : { createdAtMillis: message.createdAtMillis }
166980
+ })));
166981
+ }, [messages, profileUserId]);
166982
+ (0, react$1.useEffect)(() => {
166983
+ if (profileUserId === void 0) {
166984
+ setPendingTurn(void 0);
166985
+ return;
166986
+ }
166987
+ const restored = readArkoPendingTurn(profileUserId);
166988
+ setPendingTurn(restored);
166989
+ if (restored === void 0) return;
166990
+ setMessages((current) => {
166991
+ if (current.some((item) => item.id === restored.localAssistantMessageId)) return current;
166992
+ return [
166993
+ ...current,
166994
+ {
166995
+ id: restored.localUserMessageId,
166996
+ sessionId: restored.sessionId,
166997
+ role: "user",
166998
+ text: restored.text,
166999
+ status: "done",
167000
+ createdAtMillis: restored.createdAtMillis
167001
+ },
167002
+ {
167003
+ id: restored.localAssistantMessageId,
167004
+ sessionId: restored.sessionId,
167005
+ role: "assistant",
167006
+ text: "",
167007
+ status: "error",
167008
+ createdAtMillis: restored.createdAtMillis + 1
167009
+ }
167010
+ ];
167011
+ });
168438
167012
  scrollToBottom();
167013
+ }, [profileUserId, scrollToBottom]);
167014
+ (0, react$1.useEffect)(() => {
167015
+ const controller = new AbortController();
167016
+ setLoading(true);
167017
+ setError("");
167018
+ setHistoryError("");
167019
+ Promise.allSettled([
167020
+ callArkme("arko.session", void 0, controller.signal),
167021
+ callArkme("arko.profile", void 0, controller.signal),
167022
+ callArkme("arko.models", void 0, controller.signal),
167023
+ callArkme("arko.history", {
167024
+ limit: 50,
167025
+ offset: 0
167026
+ }, controller.signal),
167027
+ callArkme("user.profile", void 0, controller.signal).then(async (snapshot) => snapshot.profile === null ? await callArkme("user.profile.refresh", void 0, controller.signal) : snapshot)
167028
+ ]).then(([sessionResult, profileResult, modelResult, historyResult, userProfileResult]) => {
167029
+ if (controller.signal.aborted) return;
167030
+ if (sessionResult.status === "rejected") {
167031
+ setError(errorMessage$7(sessionResult.reason));
167032
+ return;
167033
+ }
167034
+ setSession(sessionResult.value);
167035
+ if (profileResult.status === "fulfilled" && profileUserId !== void 0) arkmeArkoProfileStore.setProfile(profileUserId, profileResult.value);
167036
+ if (modelResult.status === "fulfilled") setCatalog(modelResult.value);
167037
+ if (userProfileResult.status === "fulfilled") setUserProfile(userProfileResult.value.profile);
167038
+ if (historyResult.status === "fulfilled") {
167039
+ setMessages((current) => mergeHistory(current, historyResult.value.items));
167040
+ setHistoryOffset(historyResult.value.nextOffset);
167041
+ scrollToBottom();
167042
+ const restoredRun = latestActiveRun(historyResult.value.items, sessionResult.value.sessionId);
167043
+ if (restoredRun !== void 0) {
167044
+ setActiveRun(restoredRun);
167045
+ setSending(true);
167046
+ }
167047
+ } else setHistoryError(tr$1("加载 Arko 对话记录失败:{v0}", { v0: errorMessage$7(historyResult.reason) }));
167048
+ }).finally(() => {
167049
+ if (!controller.signal.aborted) setLoading(false);
167050
+ });
167051
+ return () => {
167052
+ controller.abort();
167053
+ };
167054
+ }, [profileUserId, scrollToBottom]);
167055
+ (0, react$1.useEffect)(() => {
167056
+ if (activeRun === void 0) return;
167057
+ const controller = new AbortController();
167058
+ let consecutiveFailures = 0;
167059
+ const finishRun = async (knownHistory) => {
167060
+ const [historyResult, profileResult] = await Promise.allSettled([knownHistory === void 0 ? callArkme("arko.history", {
167061
+ limit: 50,
167062
+ offset: 0
167063
+ }, controller.signal) : Promise.resolve(knownHistory), callArkme("arko.profile", void 0, controller.signal)]);
167064
+ if (controller.signal.aborted) return;
167065
+ if (historyResult.status === "fulfilled") {
167066
+ setMessages((current) => mergeHistory(current, historyResult.value.items));
167067
+ setHistoryOffset(historyResult.value.nextOffset);
167068
+ setHistoryError("");
167069
+ scrollToBottom();
167070
+ } else setHistoryError(tr$1("刷新 Arko 对话记录失败:{v0}", { v0: errorMessage$7(historyResult.reason) }));
167071
+ if (profileResult.status === "fulfilled" && profileUserId !== void 0) arkmeArkoProfileStore.setProfile(profileUserId, profileResult.value);
167072
+ setActiveRun((current) => current?.runUid === activeRun.runUid ? void 0 : current);
167073
+ setSending(false);
167074
+ };
167075
+ const poll = async () => {
167076
+ await waitForNextPoll(controller.signal);
167077
+ while (!controller.signal.aborted) {
167078
+ try {
167079
+ const status = await callArkme("arko.run.status", {
167080
+ sessionId: activeRun.sessionId,
167081
+ runUid: activeRun.runUid
167082
+ }, controller.signal);
167083
+ if (controller.signal.aborted) return;
167084
+ consecutiveFailures = 0;
167085
+ if (status.status === "waiting_tool") setNotice("当前任务需要 DSH 尚未支持的客户端操作,可以停止任务后换一种方式重试");
167086
+ setMessages((current) => current.map((item) => item.assistantMsgId === activeRun.assistantMsgId ? {
167087
+ ...item,
167088
+ runStatus: status.status,
167089
+ status: isActiveRunStatus(status.status) ? "sending" : status.status === "failed" ? "error" : "done"
167090
+ } : item));
167091
+ if (!isActiveRunStatus(status.status)) {
167092
+ await finishRun();
167093
+ return;
167094
+ }
167095
+ } catch {
167096
+ if (controller.signal.aborted) return;
167097
+ consecutiveFailures += 1;
167098
+ try {
167099
+ const history = await callArkme("arko.history", {
167100
+ limit: 50,
167101
+ offset: 0
167102
+ }, controller.signal);
167103
+ if (controller.signal.aborted) return;
167104
+ if (arkoHistoryHasTerminalRun(history.items, activeRun.sessionId, activeRun.assistantMsgId, activeRun.runUid)) {
167105
+ await finishRun(history);
167106
+ return;
167107
+ }
167108
+ } catch {
167109
+ if (controller.signal.aborted) return;
167110
+ }
167111
+ }
167112
+ const retryDelay = consecutiveFailures === 0 ? 1200 : Math.min(1e4, 1200 * 2 ** Math.min(consecutiveFailures - 1, 3));
167113
+ await waitForNextPoll(controller.signal, retryDelay);
167114
+ }
167115
+ };
167116
+ poll();
167117
+ return () => {
167118
+ controller.abort();
167119
+ };
168439
167120
  }, [
168440
- lastMessage?.id,
168441
- lastMessage?.text,
168442
- lastMessage?.reasoning,
167121
+ activeRun,
167122
+ profileUserId,
168443
167123
  scrollToBottom
168444
167124
  ]);
168445
167125
  const loadEarlier = (0, react$1.useCallback)(async () => {
167126
+ if (historyOffset === void 0 || historyLoadInFlightRef.current) return;
168446
167127
  const body = bodyRef.current;
168447
167128
  const previousScrollHeight = body?.scrollHeight ?? 0;
168448
167129
  const previousScrollTop = body?.scrollTop ?? 0;
167130
+ historyLoadInFlightRef.current = true;
167131
+ setHistoryLoading(true);
168449
167132
  try {
168450
- await controller.loadEarlier();
167133
+ const page = await callArkme("arko.history", {
167134
+ limit: 50,
167135
+ offset: historyOffset
167136
+ });
167137
+ setMessages((current) => mergeHistory(current, page.items));
167138
+ setHistoryOffset(page.nextOffset);
167139
+ setHistoryError("");
168451
167140
  requestAnimationFrame(() => {
168452
167141
  const target = bodyRef.current;
168453
- if (target) target.scrollTop = arkoPreservedScrollTop(previousScrollTop, previousScrollHeight, target.scrollHeight);
167142
+ if (target === null) return;
167143
+ target.scrollTop = arkoPreservedScrollTop(previousScrollTop, previousScrollHeight, target.scrollHeight);
168454
167144
  });
168455
- } catch {}
168456
- }, [controller.loadEarlier]);
167145
+ } catch (caught) {
167146
+ setHistoryError(tr$1("加载更早的 Arko 对话记录失败:{v0}", { v0: errorMessage$7(caught) }));
167147
+ } finally {
167148
+ historyLoadInFlightRef.current = false;
167149
+ setHistoryLoading(false);
167150
+ }
167151
+ }, [historyOffset]);
168457
167152
  (0, react$1.useEffect)(() => {
168458
167153
  const root = bodyRef.current;
168459
167154
  const sentinel = historySentinelRef.current;
168460
- if (loading || historyLoading || historyError !== "" || historyPageError !== "" || root === null || sentinel === null || typeof historyOffset !== "number") return;
167155
+ if (loading || historyLoading || historyError !== "" || root === null || sentinel === null || historyOffset === void 0) return;
168461
167156
  const observer = new IntersectionObserver((entries) => {
168462
- if (entries[0]?.isIntersecting !== true) return;
167157
+ if (entries[0]?.isIntersecting !== true || historyLoadInFlightRef.current) return;
168463
167158
  loadEarlier();
168464
167159
  }, {
168465
167160
  root,
@@ -168470,19 +167165,280 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
168470
167165
  observer.disconnect();
168471
167166
  };
168472
167167
  }, [
168473
- historyPageError,
168474
167168
  historyError,
168475
167169
  historyLoading,
168476
167170
  historyOffset,
168477
167171
  loadEarlier,
168478
167172
  loading
168479
167173
  ]);
167174
+ const retryHistory = (0, react$1.useCallback)(async () => {
167175
+ if (historyLoading) return;
167176
+ setHistoryLoading(true);
167177
+ setHistoryError("");
167178
+ try {
167179
+ const page = await callArkme("arko.history", {
167180
+ limit: 50,
167181
+ offset: 0
167182
+ });
167183
+ setMessages((current) => mergeHistory(current, page.items));
167184
+ setHistoryOffset(page.nextOffset);
167185
+ scrollToBottom();
167186
+ if (session !== void 0) {
167187
+ const restoredRun = latestActiveRun(page.items, session.sessionId);
167188
+ if (restoredRun !== void 0) {
167189
+ setActiveRun(restoredRun);
167190
+ setSending(true);
167191
+ }
167192
+ }
167193
+ } catch (caught) {
167194
+ setHistoryError(tr$1("加载 Arko 对话记录失败:{v0}", { v0: errorMessage$7(caught) }));
167195
+ } finally {
167196
+ setHistoryLoading(false);
167197
+ }
167198
+ }, [
167199
+ historyLoading,
167200
+ scrollToBottom,
167201
+ session
167202
+ ]);
167203
+ const submitTurn = (0, react$1.useCallback)(async (turn) => {
167204
+ if (sending || activeRun !== void 0) return;
167205
+ let handedOffToPolling = false;
167206
+ setSending(true);
167207
+ setError("");
167208
+ setNotice("");
167209
+ setMessages((current) => current.map((item) => item.id === turn.localAssistantMessageId ? {
167210
+ ...item,
167211
+ text: "",
167212
+ status: "sending",
167213
+ runStatus: "accepted"
167214
+ } : item));
167215
+ try {
167216
+ const result = await callArkme("arko.ask", {
167217
+ text: turn.text,
167218
+ sessionId: turn.sessionId,
167219
+ clientTurnUid: turn.clientTurnUid,
167220
+ waitSeconds: 1,
167221
+ ...turn.modelRouteKey === void 0 ? {} : { modelRouteKey: turn.modelRouteKey },
167222
+ ...turn.replyToRunUid === void 0 ? {} : { replyToRunUid: turn.replyToRunUid },
167223
+ ...turn.replyToAssistantMsgId === void 0 ? {} : { replyToAssistantMsgId: turn.replyToAssistantMsgId }
167224
+ });
167225
+ removeArkoPendingTurn(turn.userId);
167226
+ setPendingTurn((current) => current?.clientTurnUid === turn.clientTurnUid ? void 0 : current);
167227
+ setSession((current) => current === void 0 ? current : {
167228
+ ...current,
167229
+ sessionId: result.sessionId
167230
+ });
167231
+ if (result.profile !== void 0 && profileUserId !== void 0) arkmeArkoProfileStore.setProfile(profileUserId, result.profile);
167232
+ const runStatus = result.run?.status ?? result.status;
167233
+ const runActive = isActiveRunStatus(runStatus);
167234
+ const hasVisibleReasoning = result.reasoning.trim() !== "" && (runActive || !isActivityPlaceholderText(result.reasoning));
167235
+ setMessages((current) => mergeHistory(current.map((item) => item.id === turn.localUserMessageId ? {
167236
+ ...item,
167237
+ id: `history:${String(result.userMsgId)}`,
167238
+ messageId: result.userMsgId,
167239
+ sessionId: result.sessionId
167240
+ } : item.id === turn.localAssistantMessageId ? {
167241
+ id: `history:${String(result.assistantMsgId)}`,
167242
+ messageId: result.assistantMsgId,
167243
+ sessionId: result.sessionId,
167244
+ role: "assistant",
167245
+ text: resultText(result),
167246
+ status: runActive ? "sending" : result.errorMessage === void 0 ? "done" : "error",
167247
+ assistantMsgId: result.assistantMsgId,
167248
+ ...item.createdAtMillis === void 0 ? {} : { createdAtMillis: item.createdAtMillis },
167249
+ ...hasVisibleReasoning ? { reasoning: result.reasoning } : {},
167250
+ ...result.runUid === void 0 ? {} : { runUid: result.runUid },
167251
+ runStatus
167252
+ } : item), []));
167253
+ setDetailSelection((current) => {
167254
+ if (current?.scope !== detailScope) return current;
167255
+ if (current.id === turn.localUserMessageId) return {
167256
+ ...current,
167257
+ id: `history:${String(result.userMsgId)}`
167258
+ };
167259
+ if (current.id === turn.localAssistantMessageId) return {
167260
+ ...current,
167261
+ id: `history:${String(result.assistantMsgId)}`
167262
+ };
167263
+ return current;
167264
+ });
167265
+ if (runActive && result.runUid !== void 0) {
167266
+ handedOffToPolling = true;
167267
+ setActiveRun({
167268
+ sessionId: result.sessionId,
167269
+ assistantMsgId: result.assistantMsgId,
167270
+ runUid: result.runUid
167271
+ });
167272
+ }
167273
+ } catch (caught) {
167274
+ const message = errorMessage$7(caught);
167275
+ if (!(caught instanceof ArkmeClientError) || caught.body.retryable) {
167276
+ setPendingTurn(turn);
167277
+ writeArkoPendingTurn(turn);
167278
+ setError(`Arko 发送结果暂未确认:${message}。请重试确认,系统会复用同一次请求,不会重复执行。`);
167279
+ setMessages((current) => current.map((item) => item.id === turn.localAssistantMessageId ? {
167280
+ ...item,
167281
+ role: "assistant",
167282
+ text: "",
167283
+ status: "error"
167284
+ } : item));
167285
+ } else {
167286
+ removeArkoPendingTurn(turn.userId);
167287
+ setPendingTurn((current) => current?.clientTurnUid === turn.clientTurnUid ? void 0 : current);
167288
+ setError(message);
167289
+ setMessages((current) => current.map((item) => item.id === turn.localAssistantMessageId ? {
167290
+ ...item,
167291
+ role: "assistant",
167292
+ text: message,
167293
+ status: "error"
167294
+ } : item));
167295
+ }
167296
+ } finally {
167297
+ if (!handedOffToPolling) setSending(false);
167298
+ }
167299
+ }, [
167300
+ activeRun,
167301
+ detailScope,
167302
+ profileUserId,
167303
+ sending
167304
+ ]);
167305
+ const interactionLocked = sending || pendingTurn !== void 0 || activeRun !== void 0;
167306
+ const sendDisabled = loading || interactionLocked || clearing || selectingModel || session === void 0 || profileUserId === void 0;
167307
+ const inputDisabled = loading || interactionLocked || session === void 0 || profileUserId === void 0;
168480
167308
  const insertEmoji = (0, react$1.useCallback)((emoji) => {
168481
167309
  if (inputDisabled || composerDraftKey === void 0) return false;
168482
167310
  const result = textareaRef.current?.insertEmoji(emoji);
168483
167311
  if (result === "length-limit") setError("内容长度已达上限,请删减后再添加表情");
168484
167312
  return result === "inserted";
168485
167313
  }, [composerDraftKey, inputDisabled]);
167314
+ const send = (0, react$1.useCallback)(async (presetText) => {
167315
+ const text = (presetText ?? serializeArkmeComposerDraft(arkmeComposerDraftStore.get(composerDraftKey)).text).trim();
167316
+ if (text === "" || sendInFlightRef.current || sendDisabled || session === void 0 || profileUserId === void 0 || composerDraftKey === void 0) return;
167317
+ if (text.length > arkoQuestionMaxLength) {
167318
+ setError("内容长度超过上限,请删减后再发送");
167319
+ return;
167320
+ }
167321
+ sendInFlightRef.current = true;
167322
+ try {
167323
+ const continuation = latestContinuation(messages, session.sessionId);
167324
+ const createdAtMillis = Date.now();
167325
+ const turn = {
167326
+ userId: profileUserId,
167327
+ sessionId: session.sessionId,
167328
+ clientTurnUid: crypto.randomUUID(),
167329
+ text,
167330
+ createdAtMillis,
167331
+ localUserMessageId: crypto.randomUUID(),
167332
+ localAssistantMessageId: crypto.randomUUID(),
167333
+ ...continuation === void 0 ? { ...catalog === void 0 ? {} : { modelRouteKey: catalog.effectiveRouteKey } } : {
167334
+ replyToRunUid: continuation.runUid,
167335
+ replyToAssistantMsgId: continuation.assistantMsgId
167336
+ }
167337
+ };
167338
+ writeArkoPendingTurn(turn);
167339
+ pendingComposerFocusRef.current = true;
167340
+ setPendingTurn(turn);
167341
+ if (presetText === void 0) arkmeComposerDraftStore.clear(composerDraftKey);
167342
+ setMessages((current) => [
167343
+ ...current,
167344
+ {
167345
+ id: turn.localUserMessageId,
167346
+ sessionId: turn.sessionId,
167347
+ role: "user",
167348
+ text,
167349
+ status: "done",
167350
+ createdAtMillis
167351
+ },
167352
+ {
167353
+ id: turn.localAssistantMessageId,
167354
+ sessionId: turn.sessionId,
167355
+ role: "assistant",
167356
+ text: "",
167357
+ status: "sending",
167358
+ runStatus: "accepted",
167359
+ createdAtMillis: createdAtMillis + 1
167360
+ }
167361
+ ]);
167362
+ scrollToBottom();
167363
+ await submitTurn(turn);
167364
+ } finally {
167365
+ sendInFlightRef.current = false;
167366
+ }
167367
+ }, [
167368
+ catalog,
167369
+ composerDraftKey,
167370
+ messages,
167371
+ profileUserId,
167372
+ scrollToBottom,
167373
+ sendDisabled,
167374
+ session,
167375
+ submitTurn
167376
+ ]);
167377
+ const selectModel = (0, react$1.useCallback)(async (routeKey) => {
167378
+ if (selectingModel) return;
167379
+ setSelectingModel(true);
167380
+ setError("");
167381
+ try {
167382
+ const next = await callArkme("arko.model.activate", { routeKey });
167383
+ setCatalog(next);
167384
+ setModelDialogOpen(false);
167385
+ setNotice(tr$1("已切换到 {v0}", { v0: selectedModelName(next) }));
167386
+ } catch (caught) {
167387
+ setError(errorMessage$7(caught));
167388
+ } finally {
167389
+ setSelectingModel(false);
167390
+ }
167391
+ }, [selectingModel]);
167392
+ const cancelActiveRun = (0, react$1.useCallback)(async () => {
167393
+ if (activeRun === void 0 || cancelling) return;
167394
+ setCancelling(true);
167395
+ setError("");
167396
+ try {
167397
+ await callArkme("arko.cancel", {
167398
+ sessionId: activeRun.sessionId,
167399
+ assistantMsgId: activeRun.assistantMsgId,
167400
+ runUid: activeRun.runUid
167401
+ });
167402
+ setNotice("已请求停止当前任务,正在确认最终状态");
167403
+ } catch (caught) {
167404
+ setError(tr$1("停止 Arko 任务失败:{v0}", { v0: errorMessage$7(caught) }));
167405
+ } finally {
167406
+ setCancelling(false);
167407
+ }
167408
+ }, [activeRun, cancelling]);
167409
+ const clearContext = (0, react$1.useCallback)(async () => {
167410
+ if (sending || clearing || pendingTurn !== void 0) return;
167411
+ setClearConfirmOpen(false);
167412
+ setClearing(true);
167413
+ setError("");
167414
+ setNotice("");
167415
+ try {
167416
+ const nextSession = await callArkme("arko.new-session");
167417
+ setSession(nextSession);
167418
+ setMessages((current) => [...current, {
167419
+ id: crypto.randomUUID(),
167420
+ role: "divider",
167421
+ text: "新的对话",
167422
+ status: "done",
167423
+ createdAtMillis: Date.now()
167424
+ }]);
167425
+ scrollToBottom();
167426
+ setNotice("上下文已清除,历史记录仍然保留");
167427
+ } catch (caught) {
167428
+ setError(errorMessage$7(caught));
167429
+ } finally {
167430
+ setClearing(false);
167431
+ }
167432
+ }, [
167433
+ clearing,
167434
+ pendingTurn,
167435
+ scrollToBottom,
167436
+ sending
167437
+ ]);
167438
+ const displayName = arkoPresentationName(profile);
167439
+ const selectedModel = selectedModelName(catalog);
167440
+ const canChooseModel = (catalog?.options.length ?? 0) > 1;
167441
+ const continuation = (0, react$1.useMemo)(() => latestContinuation(messages, session?.sessionId), [messages, session?.sessionId]);
168486
167442
  (0, react$1.useEffect)(() => {
168487
167443
  if (!pendingComposerFocusRef.current || loading || interactionLocked || session === void 0 || profileUserId === void 0) return;
168488
167444
  pendingComposerFocusRef.current = false;
@@ -168605,23 +167561,6 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
168605
167561
  "data-arko-message-viewport": true,
168606
167562
  style: styles$24.body,
168607
167563
  children: [
168608
- historyPageError && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
168609
- role: "alert",
168610
- style: {
168611
- ...styles$24.error,
168612
- ...styles$24.feedbackRow
168613
- },
168614
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: historyPageError }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
168615
- "data-arkme-feedback": "neutral",
168616
- type: "button",
168617
- style: styles$24.retryButton,
168618
- disabled: historyLoading,
168619
- onClick: () => {
168620
- loadEarlier();
168621
- },
168622
- children: tr$1("重试加载更早记录")
168623
- })]
168624
- }),
168625
167564
  notice !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
168626
167565
  style: styles$24.notice,
168627
167566
  children: notice
@@ -168641,7 +167580,7 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
168641
167580
  style: styles$24.retryButton,
168642
167581
  disabled: historyLoading,
168643
167582
  onClick: () => {
168644
- if (typeof historyOffset !== "number") retryHistory();
167583
+ if (historyOffset === void 0) retryHistory();
168645
167584
  else loadEarlier();
168646
167585
  },
168647
167586
  children: historyLoading ? tr$1("加载中...") : tr$1("重新加载")
@@ -169124,89 +168063,6 @@ globalThis.__ARKME_NATIVE_ARKO__ = carrier;
169124
168063
  })]
169125
168064
  });
169126
168065
  }
169127
- function ArkoNativeConversation({ controller }) {
169128
- const { accountKey, session, catalog, messages, loading, sending, activeRun, draft, error, historyError, notice, historyOffset, historyPageError, inputDisabled, sendDisabled, selectModel, cancelActiveRun, loadEarlier, pendingTurn, submitTurn, retryHistory, setDraft, feedback: setError, send, displayName, clearing, selectingModel, continuation } = controller;
169129
- const [clearConfirmOpen, setClearConfirmOpen] = (0, react$1.useState)(false);
169130
- const clearContext = async () => {
169131
- setClearConfirmOpen(false);
169132
- await controller.clearContext();
169133
- };
169134
- const hint = loading ? "正在恢复会话" : clearing ? "正在清除上下文" : selectingModel ? "正在切换模型" : pendingTurn !== void 0 && !sending ? "请先确认上一次发送结果" : sending ? "等待回复" : continuation === void 0 ? "" : "继续当前任务";
169135
- if (accountKey === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
169136
- role: "status",
169137
- children: "正在恢复账号…"
169138
- });
169139
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkoNativeSurface, {
169140
- snapshot: {
169141
- accountKey,
169142
- displayName,
169143
- ...session ? { sessionId: session.sessionId } : {},
169144
- messages,
169145
- modelCatalog: catalog ?? null,
169146
- loading,
169147
- busy: sending || activeRun !== void 0,
169148
- draft,
169149
- error: error || historyError,
169150
- notice: notice || hint,
169151
- hasMore: typeof historyOffset === "number",
169152
- historyError: historyPageError,
169153
- ...inputDisabled && activeRun === void 0 ? { inputBlockedReason: hint || "当前输入暂不可用" } : {}
169154
- },
169155
- actions: {
169156
- submit: async (text) => {
169157
- if (sendDisabled || text.trim() === "" || text.length > 61440) return false;
169158
- setDraft(text);
169159
- return await send() === true;
169160
- },
169161
- selectModel,
169162
- cancel: cancelActiveRun,
169163
- loadEarlier
169164
- },
169165
- ui: {
169166
- clearDisabled: sendDisabled,
169167
- setDraft: (text) => setDraft(text),
169168
- clearContext: () => setClearConfirmOpen(true),
169169
- retry: async () => {
169170
- if (pendingTurn) await submitTurn(pendingTurn);
169171
- else await retryHistory();
169172
- },
169173
- ...pendingTurn ? { retryLabel: "重试确认" } : historyError ? { retryLabel: "重试加载记录" } : {},
169174
- feedback: setError
169175
- }
169176
- }), clearConfirmOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
169177
- role: "dialog",
169178
- "aria-label": "清除上下文",
169179
- style: {
169180
- position: "absolute",
169181
- inset: 0,
169182
- zIndex: 30,
169183
- display: "grid",
169184
- placeItems: "center",
169185
- background: "#0004"
169186
- },
169187
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
169188
- style: {
169189
- background: arkmeTheme.base,
169190
- padding: 24,
169191
- borderRadius: 12
169192
- },
169193
- children: [
169194
- "清除当前上下文?历史记录仍然保留。",
169195
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
169196
- onClick: () => setClearConfirmOpen(false),
169197
- children: "取消"
169198
- }),
169199
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
169200
- disabled: sendDisabled,
169201
- onClick: () => {
169202
- clearContext();
169203
- },
169204
- children: "确认清除"
169205
- })
169206
- ]
169207
- })
169208
- })] });
169209
- }
169210
168066
  //#endregion
169211
168067
  //#region src/client/ArkmePrivateCallMenu.tsx
169212
168068
  const maskStyle = {
@@ -201417,7 +200273,7 @@ registerProcessor('arkme-pcm-journal', ArkmePcmJournal);
201417
200273
  const c$3 = e$24;
201418
200274
  //#endregion
201419
200275
  //#region package.json
201420
- var version = "0.1.73";
200276
+ var version = "0.1.74";
201421
200277
  //#endregion
201422
200278
  //#region src/client/profile-change-store.ts
201423
200279
  let revision = 0;
@@ -222338,7 +221194,7 @@ registerProcessor('arkme-pcm-journal', ArkmePcmJournal);
222338
221194
  onPrivateChatOpened: activateSource
222339
221195
  }) : ui.mode === "voiceprint" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkmeVoiceprintSurface, { onBack: () => {
222340
221196
  arkmeUi.showRecordings();
222341
- } }) : ui.mode === "arko" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkmeArkoSurface, { native: true }, arkmeArkoSurfaceKey(auth)) : botConversationVisible && ui.selectedBot !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkmeBotConversationSurface, {
221197
+ } }) : ui.mode === "arko" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkmeArkoSurface, {}, arkmeArkoSurfaceKey(auth)) : botConversationVisible && ui.selectedBot !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkmeBotConversationSurface, {
222342
221198
  bot: ui.selectedBot,
222343
221199
  onConversationActivity: (bot) => {
222344
221200
  arkmeUi.openBotConversation(bot);
@@ -236869,29 +235725,6 @@ body[data-ds-dark-theme] .arkme-member-label[data-tier="2"] {
236869
235725
  };
236870
235726
  }
236871
235727
  //#endregion
236872
- //#region src/client/ArkmeArkoPanel.tsx
236873
- /** The workspace keys this single retained panel by authenticated account scope. */
236874
- function ArkmeArkoPanel({ visible }) {
236875
- const [visited, setVisited] = (0, react$1.useState)(false);
236876
- (0, react$1.useEffect)(() => {
236877
- if (visible) setVisited(true);
236878
- }, [visible]);
236879
- if (!visible && !visited) return null;
236880
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
236881
- "data-arkme-owned": "arko-panel",
236882
- "aria-hidden": !visible || void 0,
236883
- ...!visible ? { inert: "" } : {},
236884
- style: {
236885
- position: "absolute",
236886
- inset: 0,
236887
- visibility: visible ? "visible" : "hidden",
236888
- pointerEvents: visible ? "auto" : "none",
236889
- zIndex: visible ? 1 : 0
236890
- },
236891
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkmeArkoSurface, { native: true })
236892
- });
236893
- }
236894
- //#endregion
236895
235728
  //#region src/client/redesign/contacts/ContactDirectoryAddDialog.tsx
236896
235729
  const styles$4 = {
236897
235730
  backdrop: {
@@ -240897,8 +239730,7 @@ body[data-ds-dark-theme] .arkme-member-label[data-tier="2"] {
240897
239730
  const contactsMode = ui.mode === "source" && ui.productMode === "contacts";
240898
239731
  const webLockedHarness = !startupAuthGateEnabled() && authState.auth?.status !== "authenticated";
240899
239732
  const harnessVisible = ui.mode === "harness" || webLockedHarness;
240900
- const arkoVisible = ui.mode === "arko" && !webLockedHarness;
240901
- const conversationHidden = harnessVisible || contactsMode || arkoVisible;
239733
+ const conversationHidden = harnessVisible || contactsMode;
240902
239734
  const conversationActive = !conversationHidden && ui.calendarOpen !== true;
240903
239735
  const contactsContextRef = (0, react$1.useRef)({
240904
239736
  accountKey: contactsAccountKey,
@@ -240931,7 +239763,6 @@ body[data-ds-dark-theme] .arkme-member-label[data-tier="2"] {
240931
239763
  accountScope: contactsAccountKey,
240932
239764
  followSession: ui.mode === "harness"
240933
239765
  }, contactsAccountKey ?? "guest"),
240934
- contactsAccountKey !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkmeArkoPanel, { visible: arkoVisible }, contactsAccountKey),
240935
239766
  !webLockedHarness && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
240936
239767
  "data-arkme-owned": "arkme-conversation-layer",
240937
239768
  style: {