@keyou007/agent-bot 0.1.13-alpha.2 → 0.1.13-alpha.3

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/src/cli.ts CHANGED
@@ -56,6 +56,13 @@ import {
56
56
  import { formatServerStatus, withConfiguredFeishuAppId } from "./cli/serverStatus.js";
57
57
  import { resolveSystemSkillsRoot, SkillRegistry, type SkillRegistrationStatus } from "./cli/SkillRegistry.js";
58
58
  import { readPackageVersion } from "./cli/packageVersion.js";
59
+ import {
60
+ readInitializationReceipt,
61
+ resolveInitializationWelcomeKind,
62
+ sendInitializationWelcome,
63
+ writeInitializationReceipt,
64
+ type InitializationWelcomeResult,
65
+ } from "./cli/InitializationWelcome.js";
59
66
  import { applyExplicitProfile, parseGlobalOptions } from "./cli/profile.js";
60
67
  import { printVerificationQrAndLink } from "./cli/VerificationOutput.js";
61
68
  import {
@@ -147,6 +154,7 @@ interface InitCommandResult extends InitializationResult {
147
154
  configuration?: EnsureFeishuAppConfigurationResult;
148
155
  };
149
156
  server: InitializationServerResult;
157
+ welcome: InitializationWelcomeResult;
150
158
  }
151
159
 
152
160
  interface InitializedAgent extends SupportedAgentInspection {
@@ -162,11 +170,13 @@ async function initCommand(
162
170
  explicitProfile: boolean,
163
171
  ): Promise<void> {
164
172
  const options = parseInitCommandOptions(input, explicitProfile);
173
+ const version = readPackageVersion();
174
+ const previousInitialization = readInitializationReceipt(agentBotHome());
165
175
  let initializationLock = options.reset
166
176
  ? acquireInitializationLock(agentBotHome())
167
177
  : undefined;
168
178
  let paths: InitializationResult;
169
- let initialized: Omit<InitCommandResult, "server">;
179
+ let initialized: Omit<InitCommandResult, "server" | "welcome">;
170
180
  try {
171
181
  if (options.reset) await assertResetProfileServerStopped(configPath);
172
182
  paths = initializeAgentBot({ configPath, reset: options.reset });
@@ -189,9 +199,46 @@ async function initCommand(
189
199
  process.stdout.write(cliText("\nStarting Agent Bot server...\n", "\n正在启动 Agent Bot 服务...\n"));
190
200
  }
191
201
  const server = await startInitializedServer({ skipFeishu: options.skipFeishu, configPath });
192
- const result: InitCommandResult = { ...initialized, server };
202
+ const welcomeKind = resolveInitializationWelcomeKind({
203
+ firstInitialization: paths.config.status === "created",
204
+ previousVersion: previousInitialization?.version,
205
+ currentVersion: version,
206
+ });
207
+ let welcome: InitializationWelcomeResult;
208
+ if (options.skipFeishu) {
209
+ welcome = { status: "skipped", kind: welcomeKind, reason: "feishu-skipped" };
210
+ } else {
211
+ try {
212
+ welcome = await sendInitializationWelcome({
213
+ configPath,
214
+ language: cliLanguage,
215
+ version,
216
+ previousVersion: previousInitialization?.version,
217
+ kind: welcomeKind,
218
+ activationPending: server.status === "restart-scheduled",
219
+ });
220
+ } catch (error) {
221
+ welcome = {
222
+ status: "failed",
223
+ kind: welcomeKind,
224
+ message: error instanceof Error ? error.message : String(error),
225
+ };
226
+ }
227
+ }
228
+ try {
229
+ writeInitializationReceipt(paths.home.path, version);
230
+ } catch (error) {
231
+ process.stderr.write(`${cliText(
232
+ "Warning: Could not save initialization version metadata: ",
233
+ "警告:无法保存初始化版本信息:",
234
+ )}${error instanceof Error ? error.message : String(error)}\n`);
235
+ }
236
+ const result: InitCommandResult = { ...initialized, server, welcome };
193
237
  if (options.json) printJson(result);
194
- else printInitializationServerResult(server);
238
+ else {
239
+ printInitializationServerResult(server);
240
+ printInitializationWelcomeResult(welcome);
241
+ }
195
242
  }
196
243
 
197
244
  async function initializeSupportedAgents(json: boolean): Promise<InitializedAgent[]> {
@@ -1002,7 +1049,7 @@ function printInitializationPaths(result: InitializationResult): void {
1002
1049
  process.stdout.write(`${cliText("Log directory: ", "日志目录:")}${result.logs.path} (${initializationStatusLabel(result.logs.status)})\n`);
1003
1050
  }
1004
1051
 
1005
- function printInitializationResult(result: Omit<InitCommandResult, "server">): void {
1052
+ function printInitializationResult(result: Omit<InitCommandResult, "server" | "welcome">): void {
1006
1053
  process.stdout.write(cliText(
1007
1054
  "\nAgent Bot initialization completed.\n",
1008
1055
  "\nAgent Bot 初始化完成。\n",
@@ -1079,6 +1126,29 @@ function printInitializationServerResult(result: InitCommandResult["server"]): v
1079
1126
  printServerStartResult(result);
1080
1127
  }
1081
1128
 
1129
+ function printInitializationWelcomeResult(result: InitializationWelcomeResult): void {
1130
+ if (result.status === "sent") {
1131
+ process.stdout.write(cliText(
1132
+ "Welcome card: sent to your Lark private chat.\n",
1133
+ "欢迎卡片:已发送到你的飞书私聊。\n",
1134
+ ));
1135
+ return;
1136
+ }
1137
+ if (result.status === "failed") {
1138
+ process.stderr.write(cliText(
1139
+ `Warning: The private welcome card could not be sent: ${result.message}\n`,
1140
+ `警告:无法发送私聊欢迎卡片:${result.message}\n`,
1141
+ ));
1142
+ return;
1143
+ }
1144
+ if (result.reason === "missing-user-open-id") {
1145
+ process.stderr.write(cliText(
1146
+ "Warning: The private welcome card was skipped because FEISHU_USER_OPEN_ID is not available. Send the bot a private message, then run agentbot init again.\n",
1147
+ "警告:尚未获得 FEISHU_USER_OPEN_ID,无法发送私聊欢迎卡片。请先私聊机器人,再次运行 agentbot init。\n",
1148
+ ));
1149
+ }
1150
+ }
1151
+
1082
1152
  function printServerStartResult(result: ServerStartResult): void {
1083
1153
  process.stdout.write(
1084
1154
  result.status === "already-running"
@@ -35,6 +35,27 @@ export interface StartupStatusView {
35
35
  };
36
36
  }
37
37
 
38
+ export type InitializationWelcomeKind = "first" | "upgrade" | "refresh";
39
+
40
+ export interface InitializationWelcomeFeature {
41
+ icon: string;
42
+ title: string;
43
+ description: string;
44
+ }
45
+
46
+ export interface InitializationWelcomeView {
47
+ kind: InitializationWelcomeKind;
48
+ language: "en" | "zh";
49
+ version: string;
50
+ previousVersion?: string;
51
+ activationPending?: boolean;
52
+ defaultAgentName: string;
53
+ defaultAgentTitle: string;
54
+ availableAgents: string[];
55
+ logoPath: string;
56
+ features: InitializationWelcomeFeature[];
57
+ }
58
+
38
59
  export interface SafeRestartStatusView {
39
60
  scheduleId: number;
40
61
  reason: string;
@@ -679,6 +700,128 @@ export class CardRenderer {
679
700
  return sectionCard("Agent Bot 已启动", [markdown(lines.join("\n"))], "green");
680
701
  }
681
702
 
703
+ renderInitializationWelcome(view: InitializationWelcomeView): Record<string, unknown> {
704
+ const chinese = view.language === "zh";
705
+ const title = view.kind === "first"
706
+ ? (chinese ? "欢迎使用 Agent Bot" : "Welcome to Agent Bot")
707
+ : view.kind === "upgrade"
708
+ ? (chinese ? "Agent Bot 已更新" : "Agent Bot is updated")
709
+ : (chinese ? "Agent Bot 已准备就绪" : "Agent Bot is ready");
710
+ const subtitle = view.activationPending
711
+ ? (chinese ? "配置已完成,安全重启后生效" : "Configured and pending a safe restart")
712
+ : view.kind === "first"
713
+ ? (chinese ? "本地 Agent 已接入飞书" : "Your local Agents are now available in Lark")
714
+ : view.kind === "upgrade"
715
+ ? (chinese ? `新版本 ${view.version} 已生效` : `Version ${view.version} is now active`)
716
+ : (chinese ? "初始化配置已刷新" : "Initialization settings were refreshed");
717
+ const activationNote = view.activationPending
718
+ ? (chinese ? "当前任务完成并安全重启后生效。" : "It will take effect after active work finishes and the safe restart completes.")
719
+ : undefined;
720
+ const intro = view.kind === "first"
721
+ ? (chinese
722
+ ? "**初始化完成**\n从现在起,你可以直接在飞书里把任务交给本机 Agent,并随时查看进度、切换任务或创建分支。"
723
+ : "**Setup complete**\nYou can now work with local Agents from Lark, follow progress, switch tasks, and branch conversations.")
724
+ : view.kind === "upgrade"
725
+ ? (chinese
726
+ ? `**升级完成**\n${view.previousVersion ? `${inlineCode(view.previousVersion)} → ` : ""}${inlineCode(view.version)} 已准备好。${activationNote ?? "下面是本版值得关注的能力。"}`
727
+ : `**Upgrade complete**\n${view.previousVersion ? `${inlineCode(view.previousVersion)} → ` : ""}${inlineCode(view.version)} is ready. ${activationNote ?? "Here is what is new."}`)
728
+ : (chinese
729
+ ? `**配置刷新完成**\n${inlineCode(view.version)} 已重新检查配置、Agent 和飞书连接。${activationNote ?? ""}`
730
+ : `**Configuration refreshed**\n${inlineCode(view.version)} rechecked your configuration, Agents, and Lark connection. ${activationNote ?? ""}`);
731
+ const logo = {
732
+ ...localCardImage(view.logoPath, "Agent Bot logo"),
733
+ preview: false,
734
+ };
735
+ const featureColumns = view.features.slice(0, 4).map((feature) => ({
736
+ tag: "column",
737
+ width: "weighted",
738
+ weight: 1,
739
+ vertical_align: "top",
740
+ elements: [markdown([
741
+ `${feature.icon} **${feature.title}**`,
742
+ `<font color='grey'>${feature.description}</font>`,
743
+ ].join("\n"))],
744
+ }));
745
+ const featureRows = [0, 2].flatMap((start) => {
746
+ const columns = featureColumns.slice(start, start + 2);
747
+ return columns.length > 0
748
+ ? [{
749
+ tag: "column_set",
750
+ flex_mode: "none",
751
+ horizontal_spacing: "16px",
752
+ vertical_align: "top",
753
+ columns,
754
+ }]
755
+ : [];
756
+ });
757
+ const availableAgents = view.availableAgents.length > 0
758
+ ? view.availableAgents.map((agent) => inlineCode(agent)).join(" · ")
759
+ : inlineCode(view.defaultAgentName);
760
+ return {
761
+ schema: "2.0",
762
+ config: {
763
+ update_multi: true,
764
+ width_mode: "fill",
765
+ },
766
+ header: {
767
+ template: view.kind === "first" ? "turquoise" : view.kind === "upgrade" ? "blue" : "green",
768
+ title: { tag: "plain_text", content: title },
769
+ subtitle: { tag: "plain_text", content: subtitle },
770
+ padding: "12px 12px 12px 12px",
771
+ },
772
+ body: {
773
+ direction: "vertical",
774
+ vertical_spacing: "12px",
775
+ padding: "12px 12px 12px 12px",
776
+ elements: [
777
+ {
778
+ tag: "column_set",
779
+ flex_mode: "none",
780
+ horizontal_spacing: "16px",
781
+ vertical_align: "center",
782
+ columns: [
783
+ {
784
+ tag: "column",
785
+ width: "weighted",
786
+ weight: 1,
787
+ vertical_align: "center",
788
+ elements: [logo],
789
+ },
790
+ {
791
+ tag: "column",
792
+ width: "weighted",
793
+ weight: 3,
794
+ vertical_align: "center",
795
+ elements: [markdown(intro)],
796
+ },
797
+ ],
798
+ },
799
+ { tag: "hr" },
800
+ markdown(chinese
801
+ ? `**${view.kind === "upgrade" ? "本版亮点" : "你可以这样使用"}**`
802
+ : `**${view.kind === "upgrade" ? "What's new" : "What you can do"}**`),
803
+ ...featureRows,
804
+ { tag: "hr" },
805
+ markdown(chinese
806
+ ? "> 直接发送消息即可开始;发送 `/new` 创建新任务;发送 `/help` 查看全部命令。"
807
+ : "> Send a message to begin, `/new` for a new task, or `/help` for all commands."),
808
+ {
809
+ ...markdown(chinese
810
+ ? `**版本** ${inlineCode(view.version)} · **默认 Agent** ${inlineCode(view.defaultAgentTitle)} · **可用 Agent** ${availableAgents}`
811
+ : `**Version** ${inlineCode(view.version)} · **Default Agent** ${inlineCode(view.defaultAgentTitle)} · **Available** ${availableAgents}`),
812
+ text_size: "notation",
813
+ },
814
+ {
815
+ ...markdown(chinese
816
+ ? "📋 [查看更新日志](https://github.com/keyou/agent-bot/blob/master/CHANGELOG.md)"
817
+ : "📋 [View Changelog](https://github.com/keyou/agent-bot/blob/master/CHANGELOG.md)"),
818
+ text_size: "notation",
819
+ },
820
+ ],
821
+ },
822
+ };
823
+ }
824
+
682
825
  renderTurn(state: TurnViewState): Record<string, unknown> {
683
826
  const elements = renderTurnElements(state, "hidden");
684
827
  if (isTurnStoppable(state.status)) {
@@ -161,17 +161,7 @@ export class SafeRestartNotifier {
161
161
  }
162
162
 
163
163
  private collectNotificationTargets(status: SafeRestartStatus): void {
164
- const requestedTargets: RestartNotificationTarget[] = status.notificationTargets
165
- ?? this.store.listChatContexts("p2p").map(({ contextKey }) => ({ contextKey }));
166
- const recentTargets: RestartNotificationTarget[] = this.store.listRecentlyActiveChatContexts(
167
- new Date(Date.now() - 60_000),
168
- )
169
- .filter((chat) => !isThreadContextKey(chat.contextKey))
170
- .map(({ contextKey }) => ({ contextKey }));
171
- for (const target of [
172
- ...requestedTargets,
173
- ...recentTargets,
174
- ]) {
164
+ for (const target of status.notificationTargets ?? []) {
175
165
  const contextKey = target.contextKey.trim();
176
166
  if (!contextKey) continue;
177
167
  const existing = this.notificationTargets.get(contextKey);