@keyou007/agent-bot 0.1.24-alpha.8 → 0.1.25-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +12 -6
  3. package/README.zh.md +12 -6
  4. package/dist/codex/CodexRuntime.js +141 -21
  5. package/dist/codex/CodexRuntime.js.map +1 -1
  6. package/dist/codex/ProviderSwitch.js +1 -1
  7. package/dist/codex/ProviderSwitch.js.map +1 -1
  8. package/dist/feishu/CardRenderer.js +10 -6
  9. package/dist/feishu/CardRenderer.js.map +1 -1
  10. package/dist/feishu/FeishuConnector.js +10 -3
  11. package/dist/feishu/FeishuConnector.js.map +1 -1
  12. package/dist/feishu/MergedForwardMessage.js +14 -11
  13. package/dist/feishu/MergedForwardMessage.js.map +1 -1
  14. package/dist/local-files/TurnPreviewPage.js +26 -10
  15. package/dist/local-files/TurnPreviewPage.js.map +1 -1
  16. package/dist/presentation/visibleTurnActivities.js +13 -0
  17. package/dist/presentation/visibleTurnActivities.js.map +1 -0
  18. package/dist/proxy/ProxySessionController.js +169 -92
  19. package/dist/proxy/ProxySessionController.js.map +1 -1
  20. package/dist/runtime/responseErrors.js +60 -0
  21. package/dist/runtime/responseErrors.js.map +1 -0
  22. package/docs/technical-reference.md +12 -2
  23. package/docs/technical-reference.zh.md +12 -2
  24. package/npm-shrinkwrap.json +2 -2
  25. package/package.json +1 -1
  26. package/skills/agent-bot/SKILL.md +5 -1
  27. package/src/codex/CodexRuntime.ts +139 -20
  28. package/src/codex/ProviderSwitch.ts +1 -1
  29. package/src/feishu/CardRenderer.ts +10 -5
  30. package/src/feishu/FeishuConnector.ts +10 -3
  31. package/src/feishu/MergedForwardMessage.ts +14 -11
  32. package/src/feishu/types.ts +3 -0
  33. package/src/local-files/TurnPreviewPage.ts +25 -10
  34. package/src/presentation/visibleTurnActivities.ts +12 -0
  35. package/src/proxy/ProxySessionController.ts +175 -105
  36. package/src/runtime/responseErrors.ts +62 -0
  37. package/src/runtime/types.ts +7 -1
@@ -105,6 +105,7 @@ export function renderMergedForwardPrompt(
105
105
 
106
106
  const senderLabels = new Map<string, string>();
107
107
  const imageNumbers = new Map<string, number>();
108
+ const fileNumbers = new Map<string, number>();
108
109
  const images: MergedForwardImageReference[] = [];
109
110
  const files: MergedForwardFileReference[] = [];
110
111
  let userIndex = 0;
@@ -126,22 +127,24 @@ export function renderMergedForwardPrompt(
126
127
  const time = formatMessageTime(item.create_time);
127
128
  const heading = [`消息 ${index + 1}`, senderLabel, time].filter(Boolean).join(" · ");
128
129
  return `[${heading}]\n${renderMessageContent(item, (imageKey) => {
129
- const messageId = item.message_id?.trim();
130
- if (!messageId || !imageKey) return "[图片]";
131
- const referenceKey = `${messageId}:${imageKey}`;
132
- let imageNumber = imageNumbers.get(referenceKey);
130
+ if (!imageKey) return "[图片]";
131
+ let imageNumber = imageNumbers.get(imageKey);
133
132
  if (!imageNumber) {
134
133
  imageNumber = images.length + 1;
135
- imageNumbers.set(referenceKey, imageNumber);
136
- images.push({ messageId, imageKey });
134
+ imageNumbers.set(imageKey, imageNumber);
135
+ // Forwarded resources belong to the container, not the listed child IDs.
136
+ images.push({ messageId: parentMessageId, imageKey });
137
137
  }
138
138
  return `[图片 ${imageNumber}]`;
139
139
  }, (fileKey, fileName) => {
140
- const messageId = item.message_id?.trim();
141
- if (!messageId || !fileKey) return namedPlaceholder("文件", fileName);
142
- const fileNumber = files.length + 1;
143
- files.push({ messageId, fileKey, fileName: fileName || `file-${fileNumber}` });
144
- return `[文件 ${fileNumber}:${fileName || `file-${fileNumber}`}]`;
140
+ if (!fileKey) return namedPlaceholder("文件", fileName);
141
+ let fileNumber = fileNumbers.get(fileKey);
142
+ if (!fileNumber) {
143
+ fileNumber = files.length + 1;
144
+ fileNumbers.set(fileKey, fileNumber);
145
+ files.push({ messageId: parentMessageId, fileKey, fileName: fileName || `file-${fileNumber}` });
146
+ }
147
+ return `[文件 ${fileNumber}:${files[fileNumber - 1]!.fileName}]`;
145
148
  })}`;
146
149
  });
147
150
  const prompt = [
@@ -9,6 +9,9 @@ export interface IncomingMessage {
9
9
  messageId: string;
10
10
  messageType?: string;
11
11
  createdAt?: number;
12
+ /** SDK callback time, not WebSocket frame arrival time. */
13
+ receivedAt?: number;
14
+ eventId?: string;
12
15
  contextKey: string;
13
16
  chatId?: string;
14
17
  chatType?: "p2p" | "group";
@@ -8,6 +8,8 @@ import type { ApprovalRequest, ToolState } from "../runtime/types.js";
8
8
  import type { FileSummary, TurnActivity, TurnReasoningItem, TurnViewState, TurnViewStatus } from "../presentation/turnViewTypes.js";
9
9
  import { displayFilePath, displayToolCommand, formatShellCommandForDisplay, toolStatusIcon } from "../feishu/CardRenderer.js";
10
10
  import { turnReasoningItems } from "../presentation/turnReasoning.js";
11
+ import { visibleTurnActivities } from "../presentation/visibleTurnActivities.js";
12
+ import { displayRuntimeError } from "../runtime/responseErrors.js";
11
13
  import { isFileUrl, rewriteMarkdownFileLinks } from "./MarkdownFileLinks.js";
12
14
 
13
15
  const MARKDOWN = new MarkdownIt({
@@ -285,6 +287,10 @@ export const TURN_PREVIEW_CLIENT_SCRIPT = DIAGRAM_PREVIEW_CLIENT_SCRIPT + `(() =
285
287
  };
286
288
  const compatible = (current, next) => current.nodeType === next.nodeType && current.nodeName === next.nodeName;
287
289
  const patchNode = (current, next) => {
290
+ if (!compatible(current, next)) {
291
+ current.replaceWith(next.cloneNode(true));
292
+ return;
293
+ }
288
294
  // Summary snapshots never own a detail body that has already been fetched.
289
295
  if (current.nodeType === 1 && current.hasAttribute("data-detail-body") && next.hasAttribute("data-detail-body")) return;
290
296
  if (current.isEqualNode(next)) return;
@@ -620,16 +626,19 @@ export function renderTurnPreviewPatch(
620
626
  state: TurnViewState, changed: Set<string>, removed: Set<string>,
621
627
  localFileUrl: (filePath: string) => string | undefined, language: TurnPreviewLanguage,
622
628
  ): TurnPreviewSnapshot & { removed: string[]; before: Record<string, string> } {
629
+ const visible = visibleTurnActivities(state);
630
+ const visibleIds = new Set(visible.map((activity) => activity.id));
631
+ const suppressed = state.activities.filter((activity) => !visibleIds.has(activity.id)).map((activity) => activity.id);
623
632
  const partial = { ...state,
624
- activities: state.activities.filter((a) => changed.has(a.id)),
633
+ activities: visible.filter((a) => changed.has(a.id)),
625
634
  reasoningItems: state.reasoningItems?.filter((r) => changed.has(`reasoning:${r.itemId}`)),
626
635
  };
627
636
  const order = [
628
637
  ...(state.reasoningItems ?? []).filter((r) => !r.afterActivityId).map((r) => `reasoning:${r.itemId}`),
629
- ...state.activities.flatMap((a) => [a.id, ...(state.reasoningItems ?? []).filter((r) => r.afterActivityId === a.id).map((r) => `reasoning:${r.itemId}`)]),
638
+ ...visible.flatMap((a) => [a.id, ...(state.reasoningItems ?? []).filter((r) => r.afterActivityId === a.id).map((r) => `reasoning:${r.itemId}`)]),
630
639
  ];
631
640
  const before = Object.fromEntries(order.flatMap((id, i) => changed.has(id) && order[i + 1] ? [[id, order[i + 1]]] : []));
632
- return { ...renderTurnPreviewSnapshot(partial, localFileUrl, language, { deferDetails: true }), removed: [...removed], before };
641
+ return { ...renderTurnPreviewSnapshot(partial, localFileUrl, language, { deferDetails: true }), removed: [...new Set([...removed, ...suppressed])], before };
633
642
  }
634
643
 
635
644
  export function renderTurnPreviewSnapshot(
@@ -645,7 +654,7 @@ export function renderTurnPreviewSnapshot(
645
654
  return localFileUrl(path.resolve(state.projectCwd, filePath));
646
655
  } : undefined;
647
656
  const renderText = (value: string): string => renderMarkdown(value, state.projectCwd, localFileUrl, language);
648
- const timeline = renderTimeline(state, renderText, resolveFileUrl, state.fullToolOutputs, state.fullToolErrors, language, state.projectCwd, options.deferDetails);
657
+ const timeline = renderTimeline({ ...state, activities: visibleTurnActivities(state) }, renderText, resolveFileUrl, state.fullToolOutputs, state.fullToolErrors, language, state.projectCwd, options.deferDetails);
649
658
  const terminal = isTerminal(state.status);
650
659
  const waitingForProgress = !terminal && !state.finalResponse && !state.assistantText
651
660
  && !state.error && !state.approval && state.status !== "waiting_for_approval"
@@ -666,7 +675,7 @@ export function renderTurnPreviewSnapshot(
666
675
  `<section class="timeline" data-preview-key="timeline"${timelineContent ? "" : " hidden"}>${timelineContent}</section>`,
667
676
  state.fileSummary.length > 0 ? renderFileSummary(state, language, resolveFileUrl, options.deferDetails) : "",
668
677
  state.approval ? renderApprovalNotice(state.approval, renderText, language) : "",
669
- state.error ? `<section class="result error-result" data-preview-key="error"><h2>${escapeHtml(labels.error)}</h2><pre>${escapeHtml(state.error)}</pre></section>` : "",
678
+ state.error ? `<section class="result error-result" data-preview-key="error"><h2>${escapeHtml(labels.error)}</h2><pre>${escapeHtml(displayRuntimeError(state.error))}</pre></section>` : "",
670
679
  state.finalResponse
671
680
  ? renderFinalAnswer(state.finalResponse, state.turnId, renderText, language)
672
681
  : state.assistantText
@@ -792,8 +801,12 @@ function renderReasoningItem(
792
801
  deferDetails: boolean,
793
802
  ): string {
794
803
  const presentation = reasoningPresentation(item);
795
- if (!presentation.title && !presentation.sections.some((section) => section.parts.length > 0)) return "";
804
+ const hasBody = presentation.sections.some((section) => section.parts.length > 0);
805
+ if (!presentation.title && !hasBody) return "";
796
806
  const title = presentation.title ?? previewLabels(language).reasoning;
807
+ if (!hasBody) {
808
+ return `<article class="reasoning-step" data-activity-id="reasoning:${escapeAttribute(item.itemId)}"><div class="reasoning-header" title="${escapeAttribute(title)}"><span class="reasoning-title">💭 ${escapeHtml(title)}</span></div></article>`;
809
+ }
797
810
  const lazy = deferDetails ? lazyDetailAttributes(`reasoning:${item.itemId}`, reasoningRevision(item, cwd)) : "";
798
811
  const body = deferDetails ? lazyDetailBody() : renderReasoningBody(item, cwd, localFileUrl, language, presentation);
799
812
  return `<details class="reasoning-step" data-activity-id="reasoning:${escapeAttribute(item.itemId)}"${lazy}><summary class="reasoning-header" title="${escapeAttribute(title)}"><span class="reasoning-title">💭 ${escapeHtml(title)}</span></summary>${body}</details>`;
@@ -831,7 +844,7 @@ function renderActivity(
831
844
  switch (activity.kind) {
832
845
  case "assistant":
833
846
  if (activity.id.startsWith("commentary:runtime-error:")) {
834
- return `<article class="activity message runtime-error" data-activity="${escapeAttribute(activity.id)}"><pre>${escapeHtml(activity.text)}</pre></article>`;
847
+ return `<article class="activity message runtime-error" data-activity="${escapeAttribute(activity.id)}"><pre>${escapeHtml(displayRuntimeError(activity.text))}</pre></article>`;
835
848
  }
836
849
  return renderMessageActivity(activity.id, activity.text, "commentary", renderText);
837
850
  case "user":
@@ -1163,9 +1176,11 @@ export function renderTurnPreviewDetail(
1163
1176
  }
1164
1177
  if (key.startsWith("reasoning:")) {
1165
1178
  const item = turnReasoningItems(state).find((item) => `reasoning:${item.itemId}` === key);
1166
- if (!item || ![...item.summary, ...item.content].some((text) => text?.trim())) return undefined;
1179
+ if (!item) return undefined;
1180
+ const presentation = reasoningPresentation(item);
1181
+ if (!presentation.sections.some((section) => section.parts.length > 0)) return undefined;
1167
1182
  return { key, revision: reasoningRevision(item, state.projectCwd),
1168
- content: renderReasoningBody(item, state.projectCwd, localFileUrl, language) };
1183
+ content: renderReasoningBody(item, state.projectCwd, localFileUrl, language, presentation) };
1169
1184
  }
1170
1185
  const activity = state.activities?.find((item) => item.kind === "tool" && `tool:${item.id}` === key);
1171
1186
  if (activity?.kind !== "tool") return undefined;
@@ -1334,7 +1349,7 @@ main { max-width: 1320px; margin: 0 auto; padding: 0 20px 40px; }
1334
1349
  .markdown > :first-child { margin-top: 0; }
1335
1350
  .markdown > :last-child { margin-bottom: 0; }
1336
1351
  .reasoning-step { margin:8px 0; border:1px solid var(--line); border-radius:7px; min-width:0; }
1337
- .reasoning-header { cursor:pointer; padding:8px 10px; color:var(--muted); }
1352
+ .reasoning-header { display:flex; align-items:center; gap:8px; min-height:32px; padding:8px 10px; color:var(--muted); }
1338
1353
  .reasoning-title { min-width:0; flex:1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
1339
1354
  .reasoning-section { margin:0 10px 10px; max-height:30em; overflow:auto; overflow-wrap:anywhere; }
1340
1355
  .reasoning-part + .reasoning-part { border-top:1px solid var(--line); padding-top:8px; }
@@ -0,0 +1,12 @@
1
+ import { RUNTIME_WARNING_PREFIXES, sameRuntimeError } from "../runtime/responseErrors.js";
2
+ import type { TurnActivity, TurnViewState } from "./turnViewTypes.js";
3
+
4
+ export function visibleTurnActivities(state: TurnViewState): TurnActivity[] {
5
+ if (state.status !== "failed" || !state.error) return state.activities;
6
+ return state.activities.filter((activity) => {
7
+ if (activity.id !== `commentary:runtime-error:${state.turnId}`
8
+ || (activity.kind !== "assistant" && activity.kind !== "reasoning")) return true;
9
+ const prefix = RUNTIME_WARNING_PREFIXES.find((value) => activity.text.startsWith(value));
10
+ return !sameRuntimeError(prefix ? activity.text.slice(prefix.length) : activity.text, state.error!);
11
+ });
12
+ }