@keyou007/agent-bot 0.1.24-alpha.1 → 0.1.24-alpha.2

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@keyou007/agent-bot",
3
- "version": "0.1.24-alpha.1",
3
+ "version": "0.1.24-alpha.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@keyou007/agent-bot",
9
- "version": "0.1.24-alpha.1",
9
+ "version": "0.1.24-alpha.2",
10
10
  "license": "GPL-3.0-only",
11
11
  "dependencies": {
12
12
  "@agentclientprotocol/sdk": "1.2.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyou007/agent-bot",
3
- "version": "0.1.24-alpha.1",
3
+ "version": "0.1.24-alpha.2",
4
4
  "type": "module",
5
5
  "description": "Use Codex, TraeX, and compatible coding agents from Lark/Feishu.",
6
6
  "license": "GPL-3.0-only",
@@ -125,7 +125,7 @@ agentbot task permissions [auto|confirm]
125
125
 
126
126
  Omit the value to inspect the current setting and available choices. `agent` changes the default Agent for future tasks in that conversation. The other settings affect the specified task from its next request and become the saved defaults for that Agent.
127
127
 
128
- A successful Provider switch confirms settings, not inference connectivity. Model-list fallback warnings appear on settings cards; inspect the Provider service when availability is unconfirmed. App Server errors and native retry notices appear on the current progress card without terminating the turn. Do not stop or restart a retrying task unless the user requests it.
128
+ A successful Provider switch confirms settings, not inference connectivity. Model-list fallback warnings appear on settings cards; inspect the Provider service when availability is unconfirmed. App Server errors and native retry notices show the message and supplied `additionalDetails` as literal text on the current progress card and Preview without terminating the turn. Use Preview for long reasons; `Reconnecting` alone does not identify the cause when the Agent supplies no details, and old snapshots are not backfilled. Do not stop or restart a retrying task unless the user requests it.
129
129
 
130
130
  Providers such as a Responses bridge may omit assistant message phases. Unphased text stays in the progress timeline; successful completion promotes only the last message not followed by a tool start. Do not diagnose progress text in the answer area as a CSS issue without checking message phases. Old snapshots are not automatically backfilled.
131
131
 
@@ -571,6 +571,7 @@ export function runNpmCommand(args: string[], cwd: string): CommandResult {
571
571
  {
572
572
  cwd,
573
573
  env: process.env,
574
+ windowsHide: true,
574
575
  encoding: "utf8",
575
576
  maxBuffer: 32 * 1024 * 1024,
576
577
  shell: process.platform === "win32",
@@ -610,6 +611,7 @@ function runNode(args: string[], cwd: string): CommandResult {
610
611
  spawnSync(process.execPath, args, {
611
612
  cwd,
612
613
  env: process.env,
614
+ windowsHide: true,
613
615
  encoding: "utf8",
614
616
  maxBuffer: 32 * 1024 * 1024,
615
617
  }),
@@ -73,7 +73,7 @@ export function mapCodexNotification(method: string, params: unknown): MappedCod
73
73
  if (!threadId || !turnId) return undefined;
74
74
 
75
75
  if (method === "error" && isRecord(params.error)) {
76
- const message = stringValue(params.error.message)?.trim();
76
+ const message = formatCodexError(params.error);
77
77
  if (!message || typeof params.willRetry !== "boolean") return undefined;
78
78
  return { kind: "runtime_error", threadId, turnId, message, willRetry: params.willRetry };
79
79
  }
@@ -200,7 +200,7 @@ export function mapCodexNotification(method: string, params: unknown): MappedCod
200
200
  if (method === "turn/completed" && isRecord(params.turn)) {
201
201
  const status = params.turn.status;
202
202
  const mappedStatus = status === "interrupted" ? "cancelled" : status === "failed" ? "failed" : "completed";
203
- const error = isRecord(params.turn.error) ? stringValue(params.turn.error.message) : undefined;
203
+ const error = formatCodexError(params.turn.error);
204
204
  return {
205
205
  kind: "terminal",
206
206
  threadId,
@@ -213,6 +213,15 @@ export function mapCodexNotification(method: string, params: unknown): MappedCod
213
213
  return undefined;
214
214
  }
215
215
 
216
+ export function formatCodexError(error: unknown): string | undefined {
217
+ if (!isRecord(error)) return undefined;
218
+ const message = stringValue(error.message)?.trim();
219
+ const details = stringValue(error.additionalDetails)?.trim();
220
+ if (!message) return details || undefined;
221
+ if (!details || message.includes(details)) return message;
222
+ return details.includes(message) ? details : `${message}\n\n${details}`;
223
+ }
224
+
216
225
  function mapTool(
217
226
  item: Record<string, unknown>,
218
227
  phase: "started" | "updated",
@@ -29,7 +29,7 @@ import type {
29
29
  import { appendGeneratedImageMarkdown } from "../utils/generatedImageMarkdown.js";
30
30
  import { normalizeTaskTitle } from "../utils/taskTitle.js";
31
31
  import { AppServerRequestError } from "./AppServerConnection.js";
32
- import { mapCodexNotification } from "./CodexEventMapper.js";
32
+ import { formatCodexError, mapCodexNotification } from "./CodexEventMapper.js";
33
33
  import { CodexLocalActivityDetector } from "./CodexLocalActivityDetector.js";
34
34
  import { detectProjectlessWorkspace } from "./ProjectlessWorkspace.js";
35
35
  import { threadWriterLockPath } from "./ThreadWriterProcess.js";
@@ -1460,7 +1460,7 @@ export class CodexRuntime implements AgentRuntime {
1460
1460
  type: "turn_failed",
1461
1461
  sessionId: session.localSessionId,
1462
1462
  turnId: turn.id,
1463
- message: turn.error?.message ?? "App Server turn failed.",
1463
+ message: formatCodexError(turn.error) ?? "App Server turn failed.",
1464
1464
  });
1465
1465
  return;
1466
1466
  }
@@ -1639,7 +1639,7 @@ interface CodexTurnSnapshot {
1639
1639
  status?: string;
1640
1640
  savedPath?: string;
1641
1641
  }>;
1642
- error?: { message?: string } | null;
1642
+ error?: { message?: string; additionalDetails?: string | null } | null;
1643
1643
  startedAt?: number | null;
1644
1644
  durationMs?: number | null;
1645
1645
  }
@@ -1798,7 +1798,7 @@ function remoteSessionSummary(thread: CodexThreadSnapshot): RemoteSessionSummary
1798
1798
  finalResponse: lastTurn && lastTurn.status !== "inProgress"
1799
1799
  ? appendGeneratedImageMarkdown(extractFinalResponse(lastTurn), extractGeneratedImagePaths(lastTurn)) || undefined
1800
1800
  : undefined,
1801
- lastError: lastTurn?.error?.message,
1801
+ lastError: formatCodexError(lastTurn?.error),
1802
1802
  lastTurnToolCount: toolCounts?.total,
1803
1803
  lastTurnCompletedToolCount: toolCounts?.completed,
1804
1804
  lastTurnFailedToolCount: toolCounts?.failed,
@@ -1847,6 +1847,10 @@ function renderActivity(
1847
1847
  if (activity.kind === "assistant" || (activity.kind === "reasoning" && activity.id.startsWith("commentary:"))) {
1848
1848
  const text = activity.text.trim();
1849
1849
  if (!text) return [];
1850
+ if (activity.id.startsWith("commentary:runtime-error:")) {
1851
+ const chunks = fullAssistantText ? splitText(text, ACTIVITY_TEXT_CHUNK) : [text];
1852
+ return chunks.map((chunk) => markdown(codeBlock(chunk, fullAssistantText ? ACTIVITY_TEXT_CHUNK : MAX_LIVE_ASSISTANT_TEXT)));
1853
+ }
1850
1854
  return fullAssistantText
1851
1855
  ? splitText(text, ACTIVITY_TEXT_CHUNK).map(markdown)
1852
1856
  : [markdown(truncateText(text, MAX_LIVE_ASSISTANT_TEXT))];
@@ -54,11 +54,25 @@ function includeLocalFileLineReferences(
54
54
  projectCwd?: string,
55
55
  localFileUrl?: LocalFileUrlResolver,
56
56
  ): string {
57
- return markdown.replace(MARKDOWN_LINK, (link, imageMarker: string, label: string, rawTarget: string) => {
58
- if (imageMarker) return link;
57
+ const codeSpans = inlineCodeSpans(markdown);
58
+ return markdown.replace(MARKDOWN_LINK, (link, imageMarker: string, label: string, rawTarget: string, offset: number) => {
59
+ if (imageMarker || codeSpans.some(([start, end]) => offset >= start && offset < end)) return link;
59
60
 
60
61
  const target = unwrapMarkdownTarget(rawTarget.trim());
61
- if (!isLocalFileTarget(target)) return link;
62
+ if (!isLocalFileTarget(target)) {
63
+ const relative = resolveRelativeFileTarget(target, projectCwd);
64
+ if (!relative || isLocalImageTarget(relative.filePath)) return link;
65
+ const viewerUrl = localFileUrl?.(relative.filePath, relative.reference);
66
+ if (!viewerUrl) {
67
+ const displayPath = displayAbsolutePath(relative.filePath) + (relative.reference ?? "");
68
+ return label ? `${label}(${inlineCode(displayPath)})` : inlineCode(displayPath);
69
+ }
70
+ const url = new URL(viewerUrl);
71
+ if (relative.hash) url.hash = relative.hash;
72
+ const normalizedLabel = relative.reference && !label.endsWith(relative.reference)
73
+ ? `${label}${relative.reference}` : label;
74
+ return `[${normalizedLabel}](${url.toString()})`;
75
+ }
62
76
  if (isLocalImageTarget(target)) return link;
63
77
 
64
78
  const reference = LOCAL_FILE_LINE_REFERENCE.exec(target)?.[1];
@@ -84,6 +98,58 @@ function includeLocalFileLineReferences(
84
98
  });
85
99
  }
86
100
 
101
+ interface RelativeFileTarget {
102
+ filePath: string;
103
+ reference?: string;
104
+ hash?: string;
105
+ }
106
+
107
+ function resolveRelativeFileTarget(target: string, projectCwd?: string): RelativeFileTarget | undefined {
108
+ if (!projectCwd || !target || /^(?:#|\?|\/\/)/u.test(target)) return undefined;
109
+ const pathApi = usesWindowsPaths(projectCwd) ? path.win32 : path.posix;
110
+ if (!pathApi.isAbsolute(projectCwd)) return undefined;
111
+ const suffixStart = target.search(/[?#]/u);
112
+ const pathTarget = suffixStart < 0 ? target : target.slice(0, suffixStart);
113
+ const lineSuffix = LOCAL_FILE_LINE_REFERENCE.exec(pathTarget)?.[1];
114
+ // A basename with a line suffix (report.md:12) is a file, not a URI scheme.
115
+ const fileWithLine = /^[^:/\\]+\.[^:/\\]+:\d+(?::\d+)?$/u.test(pathTarget);
116
+ if (!fileWithLine && /^[a-z][a-z\d+.-]*:/iu.test(target)) return undefined;
117
+ const encodedPath = lineSuffix ? pathTarget.slice(0, -lineSuffix.length) : pathTarget;
118
+ try {
119
+ const decodedPath = decodeURIComponent(encodedPath);
120
+ if (!decodedPath || /[\x00-\x1f\x7f]/u.test(decodedPath)
121
+ || pathApi.isAbsolute(decodedPath) || /^[a-z]:/iu.test(decodedPath)) return undefined;
122
+ const filePath = pathApi.resolve(projectCwd, decodedPath);
123
+ const hashStart = target.indexOf("#");
124
+ const hash = hashStart < 0 ? undefined : new URL(target.slice(hashStart), "http://localhost/").hash;
125
+ const hashLine = /^#L(\d+)(?:C\d+)?(?:-L?\d+(?:C\d+)?)?$/u.exec(hash ?? "")?.[1];
126
+ const validHashLine = hashLine && Number.isSafeInteger(Number(hashLine)) && Number(hashLine) > 0;
127
+ return {
128
+ filePath,
129
+ reference: validHashLine ? `:${Number(hashLine)}` : hash ? undefined : lineSuffix,
130
+ hash: validHashLine ? `#L${Number(hashLine)}` : hash,
131
+ };
132
+ } catch {
133
+ return undefined;
134
+ }
135
+ }
136
+
137
+ function inlineCodeSpans(markdown: string): Array<[number, number]> {
138
+ const runs = [...markdown.matchAll(/`+/gu)];
139
+ const spans: Array<[number, number]> = [];
140
+ for (let index = 0; index < runs.length; index += 1) {
141
+ const opener = runs[index]!;
142
+ const backslashes = /\\*$/u.exec(markdown.slice(0, opener.index))?.[0].length ?? 0;
143
+ if (backslashes % 2 !== 0) continue;
144
+ const closing = runs.findIndex((run, candidate) => candidate > index && run[0].length === opener[0].length);
145
+ if (closing < 0) continue;
146
+ const closer = runs[closing]!;
147
+ spans.push([opener.index, closer.index + closer[0].length]);
148
+ index = closing;
149
+ }
150
+ return spans;
151
+ }
152
+
87
153
  function projectFilePathLabel(
88
154
  target: string,
89
155
  projectCwd: string,
@@ -362,6 +362,9 @@ function renderActivity(
362
362
  ): string {
363
363
  switch (activity.kind) {
364
364
  case "assistant":
365
+ if (activity.id.startsWith("commentary:runtime-error:")) {
366
+ return `<article class="activity message runtime-error" data-activity="${escapeAttribute(activity.id)}"><pre>${escapeHtml(activity.text)}</pre></article>`;
367
+ }
365
368
  return renderMessageActivity(activity.id, activity.text, "commentary", renderText);
366
369
  case "user":
367
370
  return renderMessageActivity(activity.id, activity.text, "user", renderText);
@@ -813,6 +816,7 @@ summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px;
813
816
  .tool-meta span { white-space: nowrap; }
814
817
  .tool-header-timing { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
815
818
  .tool-header-timing:empty, .tool-footer-timing:empty { display: none; }
819
+ .runtime-error pre { white-space: pre-wrap; overflow-wrap: anywhere; border-left: 2px solid var(--danger); }
816
820
  .error-output, .error-result h2 { color: var(--danger); }
817
821
  .muted, .empty { color: var(--muted); }
818
822
  .notice, .empty { padding: 10px 0; }
@@ -179,7 +179,7 @@ export function reduceTurnEvent(state: TurnViewState, event: AgentEvent): TurnVi
179
179
  const activityUpdate = upsertReasoningActivity(
180
180
  warning ? activities.filter((activity) => activity.id !== (event.activityId ?? "progress")) : activities,
181
181
  event.activityId ?? "progress",
182
- warning ? bound(event.text) : event.text,
182
+ warning && !event.activityId?.startsWith("commentary:runtime-error:") ? bound(event.text) : event.text,
183
183
  !warning && event.append === true,
184
184
  event.activityId?.startsWith("commentary:") ? "assistant" : "reasoning",
185
185
  );
@@ -252,7 +252,7 @@ export function reduceTurnEvent(state: TurnViewState, event: AgentEvent): TurnVi
252
252
  approval: undefined,
253
253
  completedAt,
254
254
  durationMs: Math.max(0, completedAt - state.startedAt),
255
- error: bound(event.message),
255
+ error: event.message,
256
256
  };
257
257
  }
258
258
  }
@@ -2158,7 +2158,7 @@ export class ProxySessionController {
2158
2158
  await this.openExecutionSettings(contextKey, "model");
2159
2159
  return;
2160
2160
  case "provider":
2161
- await this.openProviderSettings(contextKey);
2161
+ await this.openExecutionSettings(contextKey, "provider");
2162
2162
  return;
2163
2163
  case "thinking":
2164
2164
  await this.openExecutionSettings(contextKey, "thinking");
@@ -5179,21 +5179,6 @@ export class ProxySessionController {
5179
5179
  await this.openExecutionSettings(contextKey, "agent");
5180
5180
  }
5181
5181
 
5182
- private async openProviderSettings(contextKey: string): Promise<void> {
5183
- const loaded = await this.loadSession(this.requireCurrentSession(contextKey));
5184
- const providers = loaded.runtime.kind === "codex" && loaded.runtime.listModelProviders
5185
- ? await this.modelProviderOptions(loaded)
5186
- : [];
5187
- const current = loaded.session.modelProvider?.trim()
5188
- || providers.find((provider) => provider.isDefault)?.id
5189
- || "运行时默认";
5190
- if (providers.length <= 1) {
5191
- await this.outbound.sendText(contextKey, `当前 Provider:${current}\n当前没有其他 Provider 可以切换。`);
5192
- return;
5193
- }
5194
- await this.openExecutionSettings(contextKey, "provider", { sessionId: loaded.record.localSessionId });
5195
- }
5196
-
5197
5182
  private async selectProvider(
5198
5183
  contextKey: string,
5199
5184
  modelProvider: string,