@keyou007/agent-bot 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +12 -2
- package/README.zh.md +12 -2
- package/config.example.yaml +5 -0
- package/dist/config/loadConfig.js +4 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +1 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/feishu/CardRenderer.js +31 -2
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +2 -2
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -1
- package/dist/local-files/TurnPreviewPage.js +106 -87
- package/dist/local-files/TurnPreviewPage.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +12 -2
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +25 -0
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/migrations.js +12 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/updates/AutomaticUpdatePreparer.js +66 -0
- package/dist/updates/AutomaticUpdatePreparer.js.map +1 -0
- package/dist/updates/DailyUpdateMonitor.js +261 -0
- package/dist/updates/DailyUpdateMonitor.js.map +1 -0
- package/dist/updates/PublishedRelease.js +43 -0
- package/dist/updates/PublishedRelease.js.map +1 -0
- package/dist/updates/types.js +2 -0
- package/dist/updates/types.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +2 -0
- package/src/config/loadConfig.ts +4 -0
- package/src/config/schema.ts +4 -1
- package/src/feishu/CardRenderer.ts +34 -2
- package/src/feishu/FeishuMessageClient.ts +2 -1
- package/src/feishu/types.ts +1 -1
- package/src/index.ts +40 -0
- package/src/local-files/TurnPreviewPage.ts +105 -88
- package/src/proxy/ProxySessionController.ts +14 -2
- package/src/state/StateStore.ts +31 -0
- package/src/state/migrations.ts +12 -0
- package/src/updates/AutomaticUpdatePreparer.ts +76 -0
- package/src/updates/DailyUpdateMonitor.ts +252 -0
- package/src/updates/PublishedRelease.ts +44 -0
- package/src/updates/types.ts +19 -0
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import MarkdownIt from "markdown-it";
|
|
3
3
|
import type { ToolState } from "../runtime/types.js";
|
|
4
4
|
import type { FileSummary, TurnActivity, TurnViewState, TurnViewStatus } from "../presentation/turnViewTypes.js";
|
|
5
|
-
import { displayToolCommand, formatShellCommandForDisplay } from "../feishu/CardRenderer.js";
|
|
5
|
+
import { displayFilePath, displayToolCommand, formatShellCommandForDisplay, toolStatusIcon } from "../feishu/CardRenderer.js";
|
|
6
6
|
import { isFileUrl, rewriteMarkdownFileLinks } from "./MarkdownFileLinks.js";
|
|
7
7
|
|
|
8
8
|
const MARKDOWN = new MarkdownIt({
|
|
@@ -164,7 +164,12 @@ export const TURN_PREVIEW_CLIENT_SCRIPT = `(() => {
|
|
|
164
164
|
const duration = hours > 0
|
|
165
165
|
? String(hours).padStart(2, "0") + ":" + String(minutes).padStart(2, "0") + ":" + String(remainder).padStart(2, "0")
|
|
166
166
|
: String(minutes).padStart(2, "0") + ":" + String(remainder).padStart(2, "0");
|
|
167
|
-
|
|
167
|
+
const shortDuration = hours > 0
|
|
168
|
+
? String(hours * 60 + minutes) + ":" + String(remainder).padStart(2, "0")
|
|
169
|
+
: minutes > 0
|
|
170
|
+
? String(minutes) + ":" + String(remainder).padStart(2, "0")
|
|
171
|
+
: remainder > 0 ? String(remainder) + "s" : "";
|
|
172
|
+
element.textContent = element.dataset.shortDuration === "1" ? shortDuration : labels.elapsed + duration;
|
|
168
173
|
});
|
|
169
174
|
};
|
|
170
175
|
updateElapsed();
|
|
@@ -288,7 +293,7 @@ export function renderTurnPreviewSnapshot(
|
|
|
288
293
|
return localFileUrl(path.resolve(state.projectCwd, filePath));
|
|
289
294
|
} : undefined;
|
|
290
295
|
const renderText = (value: string): string => renderMarkdown(value, state.projectCwd, localFileUrl);
|
|
291
|
-
const timeline = renderTimeline(state.activities ?? [], renderText, resolveFileUrl, state.fullToolOutputs, state.fullToolErrors, language);
|
|
296
|
+
const timeline = renderTimeline(state.activities ?? [], renderText, resolveFileUrl, state.fullToolOutputs, state.fullToolErrors, language, state.projectCwd);
|
|
292
297
|
const sections = [
|
|
293
298
|
state.prompt ? renderMessageActivity("prompt", state.prompt, "user prompt", renderText) : "",
|
|
294
299
|
state.plan.length > 0 ? renderPlan(state, language) : "",
|
|
@@ -331,10 +336,11 @@ function renderTimeline(
|
|
|
331
336
|
fullToolOutputs?: Record<string, string>,
|
|
332
337
|
fullToolErrors?: Record<string, string>,
|
|
333
338
|
language: TurnPreviewLanguage = "zh",
|
|
339
|
+
projectCwd?: string,
|
|
334
340
|
): string {
|
|
335
341
|
return activities
|
|
336
342
|
.filter((activity): activity is Exclude<TurnActivity, { kind: "reasoning" }> => activity.kind !== "reasoning")
|
|
337
|
-
.map((activity) => renderActivity(activity, renderText, localFileUrl, fullToolOutputs, fullToolErrors, language))
|
|
343
|
+
.map((activity) => renderActivity(activity, renderText, localFileUrl, fullToolOutputs, fullToolErrors, language, projectCwd))
|
|
338
344
|
.join("");
|
|
339
345
|
}
|
|
340
346
|
|
|
@@ -345,6 +351,7 @@ function renderActivity(
|
|
|
345
351
|
fullToolOutputs?: Record<string, string>,
|
|
346
352
|
fullToolErrors?: Record<string, string>,
|
|
347
353
|
language: TurnPreviewLanguage = "zh",
|
|
354
|
+
projectCwd?: string,
|
|
348
355
|
): string {
|
|
349
356
|
switch (activity.kind) {
|
|
350
357
|
case "assistant":
|
|
@@ -352,7 +359,7 @@ function renderActivity(
|
|
|
352
359
|
case "user":
|
|
353
360
|
return renderMessageActivity(activity.id, activity.text, "user", renderText);
|
|
354
361
|
case "tool":
|
|
355
|
-
return renderToolActivity(activity.id, activity.tool, localFileUrl, fullToolOutputs, fullToolErrors, language);
|
|
362
|
+
return renderToolActivity(activity.id, activity.tool, localFileUrl, fullToolOutputs, fullToolErrors, language, projectCwd);
|
|
356
363
|
}
|
|
357
364
|
}
|
|
358
365
|
|
|
@@ -367,6 +374,7 @@ function renderToolActivity(
|
|
|
367
374
|
fullToolOutputs?: Record<string, string>,
|
|
368
375
|
fullToolErrors?: Record<string, string>,
|
|
369
376
|
language: TurnPreviewLanguage = "zh",
|
|
377
|
+
projectCwd?: string,
|
|
370
378
|
): string {
|
|
371
379
|
const labels = previewLabels(language);
|
|
372
380
|
const command = tool.command ? (displayToolCommand(tool.command) || tool.command.trim()) : undefined;
|
|
@@ -376,29 +384,40 @@ function renderToolActivity(
|
|
|
376
384
|
: command
|
|
377
385
|
? formatToolCommand(command)
|
|
378
386
|
: undefined;
|
|
379
|
-
const
|
|
380
|
-
const title = commandInHeader ? displayCommand! : toolTitle(tool, command, repl, language);
|
|
387
|
+
const title = displayCommand ? summarizeToolCommand(displayCommand) : toolTitle(tool, command, repl, language);
|
|
381
388
|
const output = fullToolOutputs?.[tool.id] ?? tool.output;
|
|
382
389
|
const error = fullToolErrors?.[tool.id] ?? tool.error;
|
|
383
390
|
const sameError = Boolean(output && error && normalizeOutput(output) === normalizeOutput(error));
|
|
391
|
+
const displayOutput = output && repl && !sameError ? formatReplOutput(output) : output;
|
|
392
|
+
const displayError = sameError ? undefined : error;
|
|
384
393
|
const detailParts = [
|
|
385
|
-
displayCommand
|
|
386
|
-
|
|
387
|
-
? renderToolOutput(
|
|
394
|
+
displayCommand ? `<div class="tool-command"><span class="tool-command-prefix" aria-hidden="true">$</span><pre class="command-block${repl ? " repl-code" : ""}">${escapeHtml(displayCommand)}</pre></div>` : "",
|
|
395
|
+
displayOutput
|
|
396
|
+
? renderToolOutput(sameError ? labels.resultError : repl ? labels.result : labels.output, displayOutput, sameError, repl)
|
|
388
397
|
: "",
|
|
389
|
-
|
|
390
|
-
? renderToolOutput(
|
|
398
|
+
displayError
|
|
399
|
+
? renderToolOutput(labels.error, displayError, true, repl)
|
|
391
400
|
: "",
|
|
392
|
-
!output && !error && !tool.files?.length ? renderToolStatusRow(tool, language) : "",
|
|
393
401
|
renderToolImage(tool, localFileUrl, language),
|
|
394
402
|
tool.files?.length
|
|
395
|
-
? `<
|
|
403
|
+
? `<ul class="files" aria-label="${escapeAttribute(labels.files)}">${tool.files.map((file) => renderFileEntry(file, localFileUrl, projectCwd)).join("")}</ul>`
|
|
396
404
|
: "",
|
|
397
405
|
].filter(Boolean).join("");
|
|
398
|
-
const titleMarkup =
|
|
406
|
+
const titleMarkup = displayCommand
|
|
399
407
|
? `<code class="tool-command-title">${escapeHtml(title)}</code>`
|
|
400
408
|
: `<span class="tool-title">${escapeHtml(title)}</span>`;
|
|
401
|
-
|
|
409
|
+
const timing = renderToolTiming(tool, "header");
|
|
410
|
+
const statusLabel = toolStatusLabel(tool.status, language);
|
|
411
|
+
const icon = `<span class="tool-status-icon" role="img" aria-label="${escapeAttribute(statusLabel)}" title="${escapeAttribute(statusLabel)}">${toolStatusIcon(tool.status)}</span>`;
|
|
412
|
+
const status = `<span class="tool-state ${tool.status}">${escapeHtml(statusLabel)}</span>`;
|
|
413
|
+
const header = `<summary class="tool-header">${icon}<span class="tool-summary-content">${titleMarkup}</span><span class="tool-meta">${timing}</span></summary>`;
|
|
414
|
+
const startedAt = tool.startedAt === undefined ? undefined : new Date(tool.startedAt);
|
|
415
|
+
const startTime = startedAt
|
|
416
|
+
? `<time datetime="${startedAt.toISOString()}" title="${escapeAttribute(labels.startingTime)}">${new Intl.DateTimeFormat(language === "zh" ? "zh-CN" : "en-GB", { hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23" }).format(startedAt)}</time>`
|
|
417
|
+
: `<span title="${escapeAttribute(labels.startingTime)}">${escapeHtml(labels.unknown)}</span>`;
|
|
418
|
+
const characterCount = (displayOutput?.length ?? 0) + (displayError?.length ?? 0);
|
|
419
|
+
const footer = `<div class="tool-footer">${status}${startTime}${renderToolTiming(tool, "footer")}<span>${formatNumber(characterCount)} ${escapeHtml(labels.characters)}</span></div>`;
|
|
420
|
+
return `<article class="activity tool"><details class="tool-step" data-tool-status="${tool.status}" data-activity-id="${escapeAttribute(activityId)}">${header}<div class="tool-body"><div class="tool-content" data-scroll-id="${escapeAttribute(`${activityId}:content`)}" tabindex="0">${detailParts || `<div class="muted">${escapeHtml(labels.noToolDetails)}</div>`}</div>${footer}</div></details></article>`;
|
|
402
421
|
}
|
|
403
422
|
|
|
404
423
|
function toolTitle(tool: ToolState, command?: string, repl = false, language: TurnPreviewLanguage = "zh"): string {
|
|
@@ -408,6 +427,12 @@ function toolTitle(tool: ToolState, command?: string, repl = false, language: Tu
|
|
|
408
427
|
return title || tool.kind;
|
|
409
428
|
}
|
|
410
429
|
|
|
430
|
+
function summarizeToolCommand(command: string): string {
|
|
431
|
+
const summary = command.replace(/[\\`^][ \t]*\r?\n/gu, " ").replace(/\s+/gu, " ").trim();
|
|
432
|
+
if (summary.length <= 160) return summary;
|
|
433
|
+
return `${summary.slice(0, 157).trimEnd()}…`;
|
|
434
|
+
}
|
|
435
|
+
|
|
411
436
|
function isReplTool(tool: ToolState): boolean {
|
|
412
437
|
const title = tool.title.trim();
|
|
413
438
|
const titleLooksLikeToolName = title.length <= 120 && !/[\s"'`]/u.test(title);
|
|
@@ -518,36 +543,29 @@ function normalizeReplText(value: string): string {
|
|
|
518
543
|
}
|
|
519
544
|
|
|
520
545
|
function renderToolOutput(
|
|
521
|
-
activityId: string,
|
|
522
|
-
kind: string,
|
|
523
546
|
label: string,
|
|
524
547
|
value: string,
|
|
525
548
|
error = false,
|
|
526
549
|
repl = false,
|
|
527
|
-
language: TurnPreviewLanguage = "zh",
|
|
528
|
-
tool?: ToolState,
|
|
529
550
|
): string {
|
|
530
|
-
|
|
531
|
-
const displayValue = repl && !error ? formatReplOutput(value) : value;
|
|
532
|
-
const labels = previewLabels(language);
|
|
533
|
-
const status = tool?.status ?? "completed";
|
|
534
|
-
const timing = tool ? renderToolTiming(tool, language) : "";
|
|
535
|
-
return `<details class="tool-output${error ? " error-output" : ""}${repl ? " repl-result" : ""}" data-activity-id="${id}"><summary><span class="tool-output-label">${escapeHtml(label)}</span><span class="tool-output-meta">${timing}<span class="tool-state ${status}" aria-label="${escapeAttribute(status)}">${toolStatusIcon(status, language)}</span><span>${formatNumber(displayValue.length)} ${escapeHtml(labels.characters)}</span></span></summary><pre data-scroll-id="${id}">${escapeHtml(displayValue)}</pre></details>`;
|
|
551
|
+
return `<pre class="tool-output${error ? " error-output" : ""}${repl ? " repl-result" : ""}" aria-label="${escapeAttribute(label)}">${escapeHtml(value)}</pre>`;
|
|
536
552
|
}
|
|
537
553
|
|
|
538
|
-
function
|
|
539
|
-
const
|
|
540
|
-
|
|
554
|
+
function renderToolTiming(tool: ToolState, placement: "header" | "footer"): string {
|
|
555
|
+
const className = `tool-${placement}-timing`;
|
|
556
|
+
const duration = toolDuration(tool);
|
|
557
|
+
if (duration === undefined) {
|
|
558
|
+
return `<span class="${className}">?</span>`;
|
|
559
|
+
}
|
|
560
|
+
const live = tool.status === "running" && tool.completedAt === undefined;
|
|
561
|
+
return `<span class="${className}"${live ? ` data-live-tool-duration data-started-at="${tool.startedAt}" data-short-duration="1"` : ""}>${formatShortDuration(duration)}</span>`;
|
|
541
562
|
}
|
|
542
563
|
|
|
543
|
-
function
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
? `<span>${escapeHtml(labels.elapsed.trim())}${language === "zh" ? "" : " "}${escapeHtml(labels.unknown)}</span>`
|
|
549
|
-
: `<span${tool.status === "running" && tool.completedAt === undefined ? ` data-live-tool-duration data-started-at="${tool.startedAt}"` : ""}>${escapeHtml(labels.elapsed)}${escapeHtml(duration)}</span>`;
|
|
550
|
-
return `<span title="${escapeAttribute(labels.startingTime)}">${escapeHtml(start)}</span>${liveDuration}`;
|
|
564
|
+
function formatShortDuration(duration: string): string {
|
|
565
|
+
const parts = duration.split(":").map(Number);
|
|
566
|
+
const seconds = parts.at(-1) ?? 0;
|
|
567
|
+
const minutes = parts.length > 2 ? (parts.at(-2) ?? 0) + (parts.at(-3) ?? 0) * 60 : parts.at(-2) ?? 0;
|
|
568
|
+
return minutes > 0 ? `${minutes}:${String(seconds).padStart(2, "0")}` : seconds > 0 ? `${seconds}s` : "";
|
|
551
569
|
}
|
|
552
570
|
|
|
553
571
|
function renderToolImage(
|
|
@@ -573,12 +591,12 @@ function renderFileSummary(
|
|
|
573
591
|
localFileUrl?: (filePath: string) => string | undefined,
|
|
574
592
|
): string {
|
|
575
593
|
const labels = previewLabels(language);
|
|
576
|
-
const files = state.fileSummary.map((file) => renderFileEntry(file, localFileUrl)).join("");
|
|
577
|
-
return `<
|
|
594
|
+
const files = state.fileSummary.map((file) => renderFileEntry(file, localFileUrl, state.projectCwd)).join("");
|
|
595
|
+
return `<details class="files-summary" data-activity-id="turn:files-summary"><summary><h2>${escapeHtml(labels.fileChanges)} <span>${state.fileSummary.length}</span></h2></summary><ul class="files">${files}</ul></details>`;
|
|
578
596
|
}
|
|
579
597
|
|
|
580
|
-
function renderFileEntry(file: FileSummary, localFileUrl?: (filePath: string) => string | undefined): string {
|
|
581
|
-
const label = `<code>${escapeHtml(file.path)}</code>`;
|
|
598
|
+
function renderFileEntry(file: FileSummary, localFileUrl?: (filePath: string) => string | undefined, projectCwd?: string): string {
|
|
599
|
+
const label = `<code>${escapeHtml(displayFilePath(file.path, projectCwd))}</code>`;
|
|
582
600
|
const url = localFileUrl?.(file.path);
|
|
583
601
|
const link = url
|
|
584
602
|
? `<a class="file-link" href="${escapeAttribute(url)}" target="_blank" rel="noreferrer noopener">${label}</a>`
|
|
@@ -630,7 +648,7 @@ function statusLabel(status: TurnViewStatus, language: TurnPreviewLanguage): str
|
|
|
630
648
|
return previewLabels(language).statuses[status];
|
|
631
649
|
}
|
|
632
650
|
|
|
633
|
-
function
|
|
651
|
+
function toolStatusLabel(status: ToolState["status"], language: TurnPreviewLanguage): string {
|
|
634
652
|
return status === "completed"
|
|
635
653
|
? previewLabels(language).toolStatuses.completed
|
|
636
654
|
: status === "failed"
|
|
@@ -654,15 +672,6 @@ function formatDuration(durationMs: number): string {
|
|
|
654
672
|
: `${String(minutes).padStart(2, "0")}:${String(remainder).padStart(2, "0")}`;
|
|
655
673
|
}
|
|
656
674
|
|
|
657
|
-
function formatTimestamp(timestampMs: number): string {
|
|
658
|
-
return new Intl.DateTimeFormat(undefined, {
|
|
659
|
-
hour: "2-digit",
|
|
660
|
-
minute: "2-digit",
|
|
661
|
-
second: "2-digit",
|
|
662
|
-
hour12: false,
|
|
663
|
-
}).format(new Date(timestampMs));
|
|
664
|
-
}
|
|
665
|
-
|
|
666
675
|
function formatNumber(value: number): string {
|
|
667
676
|
return Math.max(0, Math.floor(value)).toLocaleString("en-US");
|
|
668
677
|
}
|
|
@@ -716,14 +725,19 @@ h2 span { margin-left: 6px; color: var(--muted); font-size: 12px; font-weight: 4
|
|
|
716
725
|
.metadata > span + span::before { content: "·"; position: absolute; left: 0; top: 0; }
|
|
717
726
|
.metadata strong { color: var(--text); font-weight: 500; font-variant-numeric: tabular-nums; }
|
|
718
727
|
main { max-width: 1320px; margin: 0 auto; padding: 0 20px 40px; }
|
|
719
|
-
.plan, .files-summary, .result { padding: 14px 0;
|
|
728
|
+
.plan, .files-summary, .result { padding: 14px 0; }
|
|
729
|
+
.files-summary, .result { border-top: 1px solid var(--line); }
|
|
730
|
+
.files-summary > summary h2 { margin: 0; }
|
|
731
|
+
.files-summary > .files { padding-top: 8px; }
|
|
720
732
|
.plan ol { display: grid; gap: 4px; margin: 0; padding: 0; list-style: none; }
|
|
721
733
|
.plan li { display: grid; grid-template-columns: 16px minmax(0, 1fr); gap: 6px; overflow-wrap: anywhere; }
|
|
722
734
|
.plan li.completed { color: var(--muted); }
|
|
723
735
|
.plan li.in_progress { color: var(--accent); }
|
|
724
736
|
.timeline { padding-top: 2px; }
|
|
725
737
|
.activity { min-width: 0; }
|
|
726
|
-
.message { padding: 12px 0;
|
|
738
|
+
.message { padding: 12px 0; }
|
|
739
|
+
.prompt { border-bottom: 1px solid var(--line); }
|
|
740
|
+
.timeline .markdown hr { display: none; }
|
|
727
741
|
.markdown { min-width: 0; overflow-wrap: anywhere; }
|
|
728
742
|
.message > .markdown, .result > .markdown { font-size: 14px; font-weight: 400; }
|
|
729
743
|
.markdown > :first-child { margin-top: 0; }
|
|
@@ -748,47 +762,52 @@ summary:hover { background: transparent; }
|
|
|
748
762
|
summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
749
763
|
.tool { margin: 8px 0; }
|
|
750
764
|
.tool-step { border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: transparent; }
|
|
751
|
-
.tool-
|
|
752
|
-
.tool-header
|
|
765
|
+
.tool-step[data-tool-status="running"] { border-color: var(--accent); }
|
|
766
|
+
.tool-header { display: flex; align-items: center; gap: 8px; min-height: 32px; padding: 7px 10px; overflow: hidden; background: transparent; font-weight: 400; }
|
|
767
|
+
.tool-content { max-height: calc(30 * 12px * 1.55 + 16px); padding: 8px 12px; overflow: auto; --tool-scrollbar-thumb: var(--scrollbar-thumb); scrollbar-width: thin; scrollbar-color: var(--tool-scrollbar-thumb) transparent; }
|
|
768
|
+
.tool-content > * + * { margin-top: 8px; }
|
|
769
|
+
.tool-content pre { max-width: none; max-height: none; padding: 0; overflow: visible; background: transparent; }
|
|
770
|
+
.tool-command { display: flex; align-items: flex-start; gap: 8px; width: max-content; min-width: 100%; }
|
|
771
|
+
.tool-command-prefix { flex: none; color: var(--muted); font: 12px/1.55 "Cascadia Mono", "SFMono-Regular", Consolas, monospace; }
|
|
772
|
+
.tool-footer { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; padding: 6px 12px; color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
|
|
773
|
+
.tool-footer > * { white-space: nowrap; }
|
|
774
|
+
.tool-footer > * + *::before { content: "·"; margin-right: 8px; }
|
|
753
775
|
@media (hover: hover) and (pointer: fine) {
|
|
754
|
-
.tool-
|
|
755
|
-
.tool-
|
|
756
|
-
.tool-body pre:is(:hover, :focus-visible, :focus-within) { --tool-scrollbar-thumb: var(--scrollbar-thumb); }
|
|
776
|
+
.tool-content { --tool-scrollbar-thumb: transparent; }
|
|
777
|
+
.tool-content:is(:hover, :focus-visible, :focus-within) { --tool-scrollbar-thumb: var(--scrollbar-thumb); }
|
|
757
778
|
}
|
|
758
779
|
@supports selector(::-webkit-scrollbar) {
|
|
759
|
-
.tool-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
}
|
|
767
|
-
|
|
780
|
+
.tool-content { scrollbar-width: auto; scrollbar-color: auto; }
|
|
781
|
+
.tool-content::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
782
|
+
.tool-content::-webkit-scrollbar-track,
|
|
783
|
+
.tool-content::-webkit-scrollbar-corner { background: transparent; }
|
|
784
|
+
.tool-content::-webkit-scrollbar-thumb { background: var(--tool-scrollbar-thumb); border-radius: 3px; }
|
|
785
|
+
.tool-content::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
|
|
786
|
+
.tool-content::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
|
|
787
|
+
}
|
|
788
|
+
@media (hover: none), (pointer: coarse) {
|
|
789
|
+
.tool-content { scrollbar-width: none; }
|
|
790
|
+
.tool-content::-webkit-scrollbar { display: none; width: 0; height: 0; }
|
|
791
|
+
}
|
|
792
|
+
.tool-status-icon { display: inline-flex; flex: 0 0 16px; align-items: center; justify-content: center; font-size: 13px; line-height: 18px; }
|
|
793
|
+
.tool-state { display: inline-flex; flex: none; align-items: center; justify-content: center; color: var(--muted); font-size: 11px; font-weight: 400; white-space: nowrap; }
|
|
768
794
|
.tool-state.completed { color: var(--success); }
|
|
769
795
|
.tool-state.running { color: var(--accent); }
|
|
770
796
|
.tool-state.failed { color: var(--danger); }
|
|
771
|
-
.tool-
|
|
772
|
-
.tool-command-title { display: block; min-width: 0;
|
|
773
|
-
.tool-
|
|
797
|
+
.tool-summary-content { min-width: 0; flex: 1 1 auto; overflow: hidden; }
|
|
798
|
+
.tool-title, .tool-command-title { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
799
|
+
.tool-title { font-size: 12px; font-weight: 400; line-height: 1.45; }
|
|
800
|
+
.tool-command-title { margin: 0; padding: 0; background: transparent; color: var(--text); font: 12px/1.55 "Cascadia Mono", "SFMono-Regular", Consolas, monospace; font-weight: 400; }
|
|
801
|
+
.tool-meta { display: flex; flex: 0 0 auto; flex-wrap: nowrap; align-items: center; gap: 12px; max-width: none; margin-left: auto; color: var(--muted); font-size: 11px; font-weight: 400; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
774
802
|
.tool-meta span { white-space: nowrap; }
|
|
775
|
-
.tool-
|
|
776
|
-
.tool-
|
|
777
|
-
.
|
|
778
|
-
.tool-output:first-child { border-top: 0; }
|
|
779
|
-
.tool-output > summary { padding: 4px 12px; background: transparent; color: var(--muted); font-size: 12px; }
|
|
780
|
-
.tool-output-label { min-width: 0; }
|
|
781
|
-
.tool-output-meta { display: inline-flex; align-items: center; gap: 12px; margin-left: auto; font-size: 11px; }
|
|
782
|
-
.tool-status-row { display: flex; justify-content: flex-end; padding: 5px 12px; border-top: 1px solid var(--line); }
|
|
783
|
-
.tool-output > pre { max-height: none; border-top: 1px solid var(--line); background: transparent; }
|
|
784
|
-
.repl-code { background: transparent; }
|
|
785
|
-
.repl-result > pre { white-space: pre; overflow-wrap: normal; background: transparent; }
|
|
786
|
-
.error-output > summary, .error-result h2 { color: var(--danger); }
|
|
803
|
+
.tool-header-timing { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
804
|
+
.tool-header-timing:empty, .tool-footer-timing:empty { display: none; }
|
|
805
|
+
.error-output, .error-result h2 { color: var(--danger); }
|
|
787
806
|
.muted, .empty { color: var(--muted); }
|
|
788
|
-
.notice, .empty { padding: 10px 0;
|
|
807
|
+
.notice, .empty { padding: 10px 0; }
|
|
789
808
|
.notice { color: #926a08; }
|
|
790
809
|
.files { display: grid; gap: 4px; margin: 0; padding: 0; list-style: none; }
|
|
791
|
-
.tool-
|
|
810
|
+
.tool-content .files code { background: transparent; }
|
|
792
811
|
.files li { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
|
|
793
812
|
.files code { min-width: 0; }
|
|
794
813
|
.files .file-link { min-width: 0; color: var(--accent); text-underline-offset: 2px; }
|
|
@@ -796,7 +815,6 @@ summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px;
|
|
|
796
815
|
.additions { color: var(--success); }
|
|
797
816
|
.deletions { color: var(--danger); }
|
|
798
817
|
.file-delta { display: inline-flex; flex: none; gap: 6px; font: 12px "Cascadia Mono", Consolas, monospace; }
|
|
799
|
-
.tool-image { padding: 8px 12px; border-top: 1px solid var(--line); }
|
|
800
818
|
.tool-image img { display: block; max-width: 100%; max-height: 720px; }
|
|
801
819
|
.final-result h2 { color: var(--success); }
|
|
802
820
|
@media (max-width: 700px) {
|
|
@@ -809,9 +827,8 @@ summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px;
|
|
|
809
827
|
.message { padding: 10px 0; }
|
|
810
828
|
.tool-header { gap: 6px; padding: 7px 9px; }
|
|
811
829
|
.tool-state { font-size: 11px; }
|
|
812
|
-
.tool-meta {
|
|
813
|
-
.tool-
|
|
814
|
-
.tool-output-meta { gap: 10px; }
|
|
830
|
+
.tool-meta { gap: 10px; font-size: 11px; }
|
|
831
|
+
.tool-content, .tool-footer { padding-left: 10px; padding-right: 10px; }
|
|
815
832
|
pre { padding: 8px 10px; }
|
|
816
833
|
}
|
|
817
834
|
@media (prefers-color-scheme: dark) {
|
|
@@ -487,6 +487,7 @@ export interface ProxyLifecycle {
|
|
|
487
487
|
supervised?: boolean;
|
|
488
488
|
restart(contextKey: string, force: boolean, replyTarget?: MessageReplyTarget): Promise<void>;
|
|
489
489
|
cancelSafeRestart?(scheduleId: number): Promise<boolean>;
|
|
490
|
+
cancelAutomaticUpdate?(action: CardAction): Promise<void>;
|
|
490
491
|
rememberFeishuUserOpenId?(userOpenId: string): Promise<void> | void;
|
|
491
492
|
persistAgentExecutionDefaults?(
|
|
492
493
|
agentName: string,
|
|
@@ -975,7 +976,10 @@ export class ProxySessionController {
|
|
|
975
976
|
await this.outbound.withReplyTarget(contextKey, replyTarget, async () => {
|
|
976
977
|
try {
|
|
977
978
|
const kind = String(scopedAction.value.action ?? "");
|
|
978
|
-
if (kind === "
|
|
979
|
+
if (kind === "agentbot_update_cancel") {
|
|
980
|
+
if (!this.lifecycle?.cancelAutomaticUpdate) throw new Error("自动更新功能未启用。");
|
|
981
|
+
await this.lifecycle.cancelAutomaticUpdate(scopedAction);
|
|
982
|
+
} else if (kind === "help_command") {
|
|
979
983
|
await this.executeHelpCommandAction(scopedAction, contextKey, replyTarget);
|
|
980
984
|
} else if (kind === "turn_details") {
|
|
981
985
|
await this.outbound.showDetails(contextKey, String(scopedAction.value.turnId ?? ""));
|
|
@@ -1708,6 +1712,7 @@ export class ProxySessionController {
|
|
|
1708
1712
|
await loaded.runtime.setReasoningEffort(record.localSessionId, nextEffort);
|
|
1709
1713
|
}
|
|
1710
1714
|
this.store.updateRuntimeSession(record.localSessionId, { model: nextValue, reasoningEffort: nextEffort });
|
|
1715
|
+
this.outbound.updateSessionModel(record.localSessionId, nextValue);
|
|
1711
1716
|
} else if (setting === "thinking" && nextValue) {
|
|
1712
1717
|
const models = await loaded.runtime.listModels(loaded.session.modelProvider, loaded.session.model);
|
|
1713
1718
|
const currentModel = models.find((candidate) => candidate.id === loaded.session.model)
|
|
@@ -3734,6 +3739,10 @@ export class ProxySessionController {
|
|
|
3734
3739
|
while (true) {
|
|
3735
3740
|
let pendingTurnId: string | undefined;
|
|
3736
3741
|
try {
|
|
3742
|
+
this.outbound.updateSessionModel(
|
|
3743
|
+
loaded.record.localSessionId,
|
|
3744
|
+
loaded.runtime.getSession(loaded.record.localSessionId)?.model ?? loaded.session.model,
|
|
3745
|
+
);
|
|
3737
3746
|
pendingTurnId = await this.outbound.startPendingTurn(
|
|
3738
3747
|
loaded.record.localSessionId,
|
|
3739
3748
|
loaded.record.contextKey,
|
|
@@ -5187,7 +5196,9 @@ export class ProxySessionController {
|
|
|
5187
5196
|
throw error;
|
|
5188
5197
|
}
|
|
5189
5198
|
};
|
|
5190
|
-
|
|
5199
|
+
const session = await loaded.runtime.setExecutionSettings(loaded.record.localSessionId, settings, persist);
|
|
5200
|
+
this.outbound.updateSessionModel(loaded.record.localSessionId, session.model);
|
|
5201
|
+
return session;
|
|
5191
5202
|
}
|
|
5192
5203
|
|
|
5193
5204
|
private async assertProviderModelSettings(
|
|
@@ -5249,6 +5260,7 @@ export class ProxySessionController {
|
|
|
5249
5260
|
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, nextEffort);
|
|
5250
5261
|
}
|
|
5251
5262
|
this.store.updateRuntimeSession(loaded.record.localSessionId, { model, reasoningEffort: nextEffort });
|
|
5263
|
+
this.outbound.updateSessionModel(loaded.record.localSessionId, model);
|
|
5252
5264
|
await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
|
|
5253
5265
|
const effortMessage = nextEffort && nextEffort !== currentEffort
|
|
5254
5266
|
? `,思考强度已自动调整为 ${nextEffort}`
|
package/src/state/StateStore.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
4
|
import { migrations } from "./migrations.js";
|
|
5
|
+
import type { UpdateCheckSchedule, UpdateNotice } from "../updates/types.js";
|
|
5
6
|
|
|
6
7
|
export type SessionStatus = "starting" | "ready" | "running" | "closed" | "failed";
|
|
7
8
|
|
|
@@ -287,6 +288,36 @@ export class StateStore {
|
|
|
287
288
|
`);
|
|
288
289
|
}
|
|
289
290
|
|
|
291
|
+
getUpdateCheckSchedule(): UpdateCheckSchedule | undefined {
|
|
292
|
+
const row = this.db.prepare("SELECT value_json FROM update_check_schedule WHERE id = 1")
|
|
293
|
+
.get() as { value_json: string } | undefined;
|
|
294
|
+
return row ? JSON.parse(row.value_json) as UpdateCheckSchedule : undefined;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
saveUpdateCheckSchedule(schedule: UpdateCheckSchedule): void {
|
|
298
|
+
this.db.prepare(`INSERT INTO update_check_schedule (id, value_json) VALUES (1, ?)
|
|
299
|
+
ON CONFLICT(id) DO UPDATE SET value_json = excluded.value_json`).run(JSON.stringify(schedule));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
getUpdateNotice(version: string): UpdateNotice | undefined {
|
|
303
|
+
const row = this.db.prepare("SELECT value_json FROM update_notices WHERE version = ?")
|
|
304
|
+
.get(version) as { value_json: string } | undefined;
|
|
305
|
+
return row ? JSON.parse(row.value_json) as UpdateNotice : undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
saveUpdateNotice(notice: UpdateNotice): void {
|
|
309
|
+
this.db.prepare(`INSERT INTO update_notices (version, status, value_json) VALUES (?, ?, ?)
|
|
310
|
+
ON CONFLICT(version) DO UPDATE SET status = excluded.status, value_json = excluded.value_json`)
|
|
311
|
+
.run(notice.version, notice.status, JSON.stringify(notice));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
listPendingUpdateNotices(): UpdateNotice[] {
|
|
315
|
+
const rows = this.db.prepare(`SELECT value_json FROM update_notices
|
|
316
|
+
WHERE status IN ('announcing', 'countdown', 'preparing', 'scheduled')`)
|
|
317
|
+
.all() as Array<{ value_json: string }>;
|
|
318
|
+
return rows.map((row) => JSON.parse(row.value_json) as UpdateNotice);
|
|
319
|
+
}
|
|
320
|
+
|
|
290
321
|
saveGoalCardDelivery(localSessionId: string, contextKey: string, messageId: string): void {
|
|
291
322
|
const updatedAt = new Date().toISOString();
|
|
292
323
|
this.db.prepare(`
|
package/src/state/migrations.ts
CHANGED
|
@@ -195,4 +195,16 @@ export const migrations = [
|
|
|
195
195
|
CREATE INDEX IF NOT EXISTS idx_card_action_bindings_created
|
|
196
196
|
ON card_action_bindings(created_at);
|
|
197
197
|
`,
|
|
198
|
+
`
|
|
199
|
+
CREATE TABLE IF NOT EXISTS update_check_schedule (
|
|
200
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
201
|
+
value_json TEXT NOT NULL
|
|
202
|
+
);
|
|
203
|
+
CREATE TABLE IF NOT EXISTS update_notices (
|
|
204
|
+
version TEXT PRIMARY KEY,
|
|
205
|
+
status TEXT NOT NULL,
|
|
206
|
+
value_json TEXT NOT NULL
|
|
207
|
+
);
|
|
208
|
+
CREATE INDEX IF NOT EXISTS idx_update_notices_status ON update_notices(status);
|
|
209
|
+
`,
|
|
198
210
|
] as const;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { execFile, type ExecFileOptionsWithStringEncoding } from "node:child_process";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { loadConfig } from "../config/loadConfig.js";
|
|
6
|
+
import { controlEndpoint } from "../cli/controlProtocol.js";
|
|
7
|
+
import { finalizeSelfUpdatePlan, prepareSelfUpdate, releaseSelfUpdatePlan, requireNpmSelfUpdateInstallation } from "../cli/SelfUpdater.js";
|
|
8
|
+
import { stableVersionSchema } from "./PublishedRelease.js";
|
|
9
|
+
|
|
10
|
+
const resultSchema = z.discriminatedUnion("status", [
|
|
11
|
+
z.object({ status: z.literal("current") }),
|
|
12
|
+
z.object({ status: z.literal("prepared"), planPath: z.string().min(1), targetVersion: stableVersionSchema }),
|
|
13
|
+
]);
|
|
14
|
+
const runFile = promisify(execFile);
|
|
15
|
+
type RunPreparation = (file: string, args: string[], options: ExecFileOptionsWithStringEncoding) => Promise<{ stdout: string }>;
|
|
16
|
+
interface UpdateProfile { home: string; configPath: string }
|
|
17
|
+
|
|
18
|
+
export async function checkAutomaticUpdateSupport(profile: UpdateProfile, run: RunPreparation = runFile): Promise<void> {
|
|
19
|
+
z.object({ status: z.literal("supported") }).parse(await runPreparer("--check", profile, run));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function prepareAutomaticUpdate(
|
|
23
|
+
version: string,
|
|
24
|
+
profile: UpdateProfile,
|
|
25
|
+
run: RunPreparation = runFile,
|
|
26
|
+
): Promise<z.infer<typeof resultSchema>> {
|
|
27
|
+
stableVersionSchema.parse(version);
|
|
28
|
+
const result = resultSchema.parse(await runPreparer(version, profile, run));
|
|
29
|
+
if (result.status === "prepared" && result.targetVersion !== version) {
|
|
30
|
+
throw new Error("The prepared update does not match the announced version.");
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runPreparer(argument: string, profile: UpdateProfile, run: RunPreparation): Promise<unknown> {
|
|
36
|
+
const environment: NodeJS.ProcessEnv = { ...process.env, AGENT_BOT_HOME: profile.home, AGENT_BOT_CONFIG: profile.configPath };
|
|
37
|
+
delete environment.AGENT_BOT;
|
|
38
|
+
// npm preparation is synchronous and can take minutes; keep it off the live worker's event loop.
|
|
39
|
+
const { stdout } = await run(process.execPath, [fileURLToPath(import.meta.url), argument], {
|
|
40
|
+
env: environment, windowsHide: true, encoding: "utf8", maxBuffer: 1_000_000,
|
|
41
|
+
});
|
|
42
|
+
return JSON.parse(String(stdout)) as unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main(): Promise<void> {
|
|
46
|
+
if (process.argv[2] === "--check") {
|
|
47
|
+
requireNpmSelfUpdateInstallation();
|
|
48
|
+
process.stdout.write(JSON.stringify({ status: "supported" }));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const version = stableVersionSchema.parse(process.argv[2]);
|
|
52
|
+
const config = loadConfig();
|
|
53
|
+
const prepared = await prepareSelfUpdate({ version, channel: "latest" });
|
|
54
|
+
if (prepared.status === "prepared") {
|
|
55
|
+
try {
|
|
56
|
+
finalizeSelfUpdatePlan(prepared.planPath, {
|
|
57
|
+
controlEndpoint: controlEndpoint(config.storage.sqlitePath),
|
|
58
|
+
databasePath: config.storage.sqlitePath,
|
|
59
|
+
restartService: true,
|
|
60
|
+
workingDirectory: process.cwd(),
|
|
61
|
+
reason: `Agent Bot 自动更新到 ${version}`,
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
releaseSelfUpdatePlan(prepared.planPath);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
process.stdout.write(JSON.stringify(prepared));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
|
72
|
+
void main().catch((error: unknown) => {
|
|
73
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
});
|
|
76
|
+
}
|