zapmyco 0.13.0 → 0.14.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as configureLogger, B as buildSkillSnapshot, D as eventBus, G as SESSION_DIR_NAME, K as VERSION, L as WebError, O as DEFAULT_COMPACTION_CONFIG, V as loadSkills, W as APP_NAME, d as SubAgentManager, f as SecurityBlockedError, g as runWithToolGuardContext, h as getToolGuardContext, i as AgentLlmFacade, j as logger, m as createToolInfoResolver, n as loadConfig, p as ToolGuard, q as __require, t as HOME_CONFIG_PATH, v as createLlmBasedAgent, z as ZapmycoErrorCode } from "../loader-BVhTlDtc.mjs";
2
+ import { A as configureLogger, B as buildSkillSnapshot, D as eventBus, G as SESSION_DIR_NAME, K as VERSION, L as WebError, O as DEFAULT_COMPACTION_CONFIG, V as loadSkills, W as APP_NAME, d as SubAgentManager, f as SecurityBlockedError, g as runWithToolGuardContext, h as getToolGuardContext, i as AgentLlmFacade, j as logger, m as createToolInfoResolver, n as loadConfig, p as ToolGuard, q as __require, t as HOME_CONFIG_PATH, v as createLlmBasedAgent, z as ZapmycoErrorCode } from "../loader-BD5Odmr5.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
@@ -18,6 +18,7 @@ import i18next from "i18next";
18
18
  import TurndownService from "turndown";
19
19
  import { lookup } from "node:dns/promises";
20
20
  import { promisify } from "node:util";
21
+ import { marked } from "marked";
21
22
  import { Client } from "@modelcontextprotocol/sdk/client";
22
23
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
23
24
 
@@ -447,6 +448,14 @@ const noColor = {
447
448
 
448
449
  //#endregion
449
450
  //#region src/core/agent-team/agent-instance-manager.ts
451
+ /**
452
+ * Agent 实例管理器
453
+ *
454
+ * 管理所有 AgentInstance 的生命周期:创建、注册、状态转换、取消、清理。
455
+ * 全局实例注册表,支持按 team/type/depth/status 查询。
456
+ *
457
+ * @module core/agent-team
458
+ */
450
459
  const log$19 = logger.child("agent-instance-manager");
451
460
  /**
452
461
  * Agent 实例状态转换表
@@ -479,8 +488,9 @@ const TERMINAL_STATES = [
479
488
  *
480
489
  * 单例模式,全局唯一。
481
490
  * 维护所有活跃和已完成的 Agent 实例的运行状态。
491
+ * 继承 EventEmitter 以支持 UI 组件实时监听状态变更。
482
492
  */
483
- var AgentInstanceManager = class {
493
+ var AgentInstanceManager = class extends EventEmitter {
484
494
  /** 实例注册表(按 instanceId 索引) */
485
495
  instances = /* @__PURE__ */ new Map();
486
496
  /**
@@ -517,6 +527,11 @@ var AgentInstanceManager = class {
517
527
  depth,
518
528
  parentInstanceId
519
529
  });
530
+ this.emit("instance:registered", {
531
+ instanceId: instance.instanceId,
532
+ typeId: instance.typeId,
533
+ depth
534
+ });
520
535
  return instance;
521
536
  }
522
537
  /**
@@ -542,16 +557,47 @@ var AgentInstanceManager = class {
542
557
  });
543
558
  return false;
544
559
  }
560
+ const oldStatus = instance.status;
545
561
  instance.status = newStatus;
546
562
  log$19.debug("Agent 实例状态转换", {
547
563
  instanceId,
548
564
  typeId: instance.typeId,
549
- from: instance.status,
565
+ from: oldStatus,
566
+ to: newStatus
567
+ });
568
+ this.emit("instance:transitioned", {
569
+ instanceId,
570
+ typeId: instance.typeId,
571
+ from: oldStatus,
550
572
  to: newStatus
551
573
  });
552
574
  return true;
553
575
  }
554
576
  /**
577
+ * 更新实例当前活动信息(供 UI 状态栏实时展示)
578
+ *
579
+ * @param instanceId - 实例 ID
580
+ * @param activity - 活动信息(工具名称、调用次数、参数等)
581
+ */
582
+ setActivity(instanceId, activity) {
583
+ const instance = this.instances.get(instanceId);
584
+ if (!instance) return;
585
+ instance.currentActivity = activity;
586
+ this.emit("instance:activity", {
587
+ instanceId,
588
+ typeId: instance.typeId,
589
+ activity
590
+ });
591
+ }
592
+ /**
593
+ * 获取实例当前活动信息
594
+ *
595
+ * @param instanceId - 实例 ID
596
+ */
597
+ getActivity(instanceId) {
598
+ return this.instances.get(instanceId)?.currentActivity;
599
+ }
600
+ /**
555
601
  * 获取指定实例
556
602
  *
557
603
  * @param instanceId - 实例 ID
@@ -3021,6 +3067,146 @@ function createStatusCommand() {
3021
3067
  };
3022
3068
  }
3023
3069
 
3070
+ //#endregion
3071
+ //#region src/cli/repl/components/agent-status-bar.ts
3072
+ /**
3073
+ * Agent 状态栏组件
3074
+ *
3075
+ * 实时显示正在运行的子 Agent 状态,类似 Claude Code 的 "Running N agents…" 效果。
3076
+ * 固定在 OutputArea 和 Editor 之间,无活跃 Agent 时自动隐藏。
3077
+ *
3078
+ * @module cli/repl/components
3079
+ */
3080
+ /** 状态图标映射 */
3081
+ const STATUS_ICONS$1 = {
3082
+ idle: "○",
3083
+ running: "◉",
3084
+ paused: "◐",
3085
+ completed: "●",
3086
+ failed: "✕",
3087
+ cancelled: "◌"
3088
+ };
3089
+ /** Loading 动画帧 */
3090
+ const LOADING_FRAMES$2 = [
3091
+ "⠋",
3092
+ "⠙",
3093
+ "⠹",
3094
+ "⠸",
3095
+ "⠼",
3096
+ "⠴",
3097
+ "⠦",
3098
+ "⠧",
3099
+ "⠇",
3100
+ "⠏"
3101
+ ];
3102
+ const LOADING_INTERVAL_MS$1 = 200;
3103
+ /** 树形连接线 */
3104
+ const TREE_BRANCH = "├── ";
3105
+ const TREE_LAST = "└── ";
3106
+ const TREE_PIPE = "│ ";
3107
+ const TREE_SPACE = " ";
3108
+ const TREE_CONT = "⎿ ";
3109
+ /**
3110
+ * Agent 状态栏组件
3111
+ *
3112
+ * 从 AgentInstanceManager 读取活跃实例状态,渲染为紧凑状态栏。
3113
+ */
3114
+ var AgentStatusBar = class extends Container {
3115
+ /** 是否展开显示详情 */
3116
+ #expanded = false;
3117
+ /** loading 动画帧索引 */
3118
+ #loadingFrame = 0;
3119
+ /** loading 动画定时器 */
3120
+ #loadingTimer;
3121
+ /** 上次活跃实例快照(用于检测变化) */
3122
+ #lastActiveCount = 0;
3123
+ /**
3124
+ * 切换展开/折叠状态
3125
+ */
3126
+ toggle() {
3127
+ this.#expanded = !this.#expanded;
3128
+ this.invalidate();
3129
+ }
3130
+ /** 获取当前展开状态 */
3131
+ get isExpanded() {
3132
+ return this.#expanded;
3133
+ }
3134
+ invalidate() {
3135
+ super.invalidate();
3136
+ }
3137
+ render(width) {
3138
+ const activeInstances = getAgentInstanceManager().listActive();
3139
+ if (activeInstances.length === 0) {
3140
+ this.#stopLoading();
3141
+ this.#lastActiveCount = 0;
3142
+ return [];
3143
+ }
3144
+ if (this.#lastActiveCount === 0) this.#startLoading();
3145
+ this.#lastActiveCount = activeInstances.length;
3146
+ const totalToolUses = activeInstances.reduce((sum, inst) => {
3147
+ return sum + (inst.currentActivity?.toolUses ?? 0);
3148
+ }, 0);
3149
+ const totalDuration = this.#formatDuration(Math.max(...activeInstances.map((i) => Date.now() - i.createdAt)));
3150
+ const frame = LOADING_FRAMES$2[this.#loadingFrame % LOADING_FRAMES$2.length] ?? "";
3151
+ if (!this.#expanded) return [this.#renderCollapsed(frame, activeInstances.length, totalToolUses, totalDuration).slice(0, width)];
3152
+ return this.#renderExpanded(frame, activeInstances, width);
3153
+ }
3154
+ /** 渲染折叠模式单行 */
3155
+ #renderCollapsed(frame, count, toolUses, duration) {
3156
+ return ` ${chalk.cyan(`${frame} Running ${count} agent${count > 1 ? "s" : ""}...`)} ${chalk.gray(`\u00B7 ${toolUses} tool use${toolUses !== 1 ? "s" : ""} \u00B7 ${duration}`)} ${chalk.dim("(ctrl+o to expand)")}`;
3157
+ }
3158
+ /** 渲染展开模式多行 */
3159
+ #renderExpanded(frame, instances, width) {
3160
+ const lines = [];
3161
+ const count = instances.length;
3162
+ const header = chalk.cyan(` ${frame} Running ${count} agent${count > 1 ? "s" : ""}...`);
3163
+ const hint = chalk.dim("(ctrl+o to collapse)");
3164
+ lines.push(`${header} ${hint}`.slice(0, width));
3165
+ for (let i = 0; i < instances.length; i++) {
3166
+ const inst = instances[i];
3167
+ const isLast = i === instances.length - 1;
3168
+ const connector = isLast ? TREE_LAST : TREE_BRANCH;
3169
+ const childPrefix = isLast ? TREE_SPACE : TREE_PIPE;
3170
+ const icon = STATUS_ICONS$1[inst.status] ?? "?";
3171
+ const statusColor = inst.status === "running" ? chalk.yellow : chalk.gray;
3172
+ const typeLabel = chalk.bold(inst.typeId);
3173
+ const taskPreview = inst.task.description.slice(0, 40);
3174
+ const act = inst.currentActivity;
3175
+ const toolUsesStr = act ? chalk.gray(`\u00B7 ${act.toolUses} tool uses`) : "";
3176
+ const durationStr = chalk.gray(`\u00B7 ${this.#formatDuration(Date.now() - inst.createdAt)}`);
3177
+ const line1 = ` ${chalk.dim(connector)}${statusColor(icon)}${chalk.reset} ${typeLabel} ${chalk.dim(taskPreview)} ${toolUsesStr} ${durationStr}`;
3178
+ lines.push(line1.slice(0, width));
3179
+ if (act) {
3180
+ const toolDisplay = act.args ? `${act.toolName}: ${act.args.slice(0, 60)}` : act.toolName;
3181
+ const line2 = ` ${chalk.dim(childPrefix + TREE_CONT)}${chalk.cyan(toolDisplay)}`;
3182
+ lines.push(line2.slice(0, width));
3183
+ }
3184
+ }
3185
+ return lines;
3186
+ }
3187
+ /** 启动 loading 动画 */
3188
+ #startLoading() {
3189
+ if (this.#loadingTimer) return;
3190
+ this.#loadingTimer = setInterval(() => {
3191
+ this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES$2.length;
3192
+ this.invalidate();
3193
+ }, LOADING_INTERVAL_MS$1);
3194
+ }
3195
+ /** 停止 loading 动画 */
3196
+ #stopLoading() {
3197
+ if (this.#loadingTimer) {
3198
+ clearInterval(this.#loadingTimer);
3199
+ this.#loadingTimer = void 0;
3200
+ }
3201
+ }
3202
+ /** 格式化持续时间为可读字符串 */
3203
+ #formatDuration(ms) {
3204
+ if (ms < 1e3) return `${ms}ms`;
3205
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
3206
+ return `${Math.floor(ms / 6e4)}m${Math.floor(ms % 6e4 / 1e3)}s`;
3207
+ }
3208
+ };
3209
+
3024
3210
  //#endregion
3025
3211
  //#region src/cli/repl/components/custom-editor.ts
3026
3212
  /**
@@ -3040,7 +3226,7 @@ function createStatusCommand() {
3040
3226
  /** 输入提示符 */
3041
3227
  const PROMPT_PREFIX = "❯ ";
3042
3228
  /** loading 动画帧 */
3043
- const LOADING_FRAMES = [
3229
+ const LOADING_FRAMES$1 = [
3044
3230
  "⠋",
3045
3231
  "⠙",
3046
3232
  "⠹",
@@ -3068,8 +3254,12 @@ var ZapmycoEditor = class extends Editor {
3068
3254
  onCtrlD;
3069
3255
  /** Ctrl+O 回调(打开外部编辑器) */
3070
3256
  onOpenEditor;
3071
- /** Ctrl+T / Ctrl+Y 回调(展开/折叠 thinking 内容) */
3257
+ /** Ctrl+T 回调(展开/折叠 TaskStatusBar) */
3258
+ onToggleTasks;
3259
+ /** Ctrl+Y 回调(展开/折叠 thinking 内容) */
3072
3260
  onToggleThinking;
3261
+ /** Ctrl+Shift+O 回调(展开/折叠 Agent 状态栏) */
3262
+ onToggleAgentBar;
3073
3263
  /** 是否正在执行(用于显示 loading) */
3074
3264
  #executing = false;
3075
3265
  /** 是否显示 spinner(执行期间禁用输入但不一定显示 spinner) */
@@ -3120,14 +3310,24 @@ var ZapmycoEditor = class extends Editor {
3120
3310
  if (this.getText().length === 0 && this.onCtrlD) this.onCtrlD();
3121
3311
  return;
3122
3312
  }
3123
- if (matchesKey(data, Key.ctrl("o")) && this.onOpenEditor) {
3124
- this.onOpenEditor();
3313
+ if (matchesKey(data, Key.ctrl("o"))) {
3314
+ if (this.onOpenEditor) this.onOpenEditor();
3125
3315
  return;
3126
3316
  }
3127
- if (matchesKey(data, Key.ctrl("t")) && this.onToggleThinking) {
3128
- this.onToggleThinking();
3317
+ if (matchesKey(data, Key.ctrlShift("o")) && this.onToggleAgentBar) {
3318
+ this.onToggleAgentBar();
3129
3319
  return;
3130
3320
  }
3321
+ if (matchesKey(data, Key.ctrl("t"))) {
3322
+ if (this.onToggleTasks) {
3323
+ this.onToggleTasks();
3324
+ return;
3325
+ }
3326
+ if (this.onToggleThinking) {
3327
+ this.onToggleThinking();
3328
+ return;
3329
+ }
3330
+ }
3131
3331
  if (matchesKey(data, Key.ctrl("y")) && this.onToggleThinking) {
3132
3332
  this.onToggleThinking();
3133
3333
  return;
@@ -3178,7 +3378,7 @@ var ZapmycoEditor = class extends Editor {
3178
3378
  if (executing && showSpinner) {
3179
3379
  this.#loadingFrame = 0;
3180
3380
  this.#loadingTimer = setInterval(() => {
3181
- this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES.length;
3381
+ this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES$1.length;
3182
3382
  this.tui?.requestRender();
3183
3383
  }, 100);
3184
3384
  } else if (this.#loadingTimer) {
@@ -3211,7 +3411,7 @@ var ZapmycoEditor = class extends Editor {
3211
3411
  for (let i = 0; i < contentLines.length; i++) {
3212
3412
  const prefix = i === 0 ? PROMPT_PREFIX : " ".repeat(promptWidth);
3213
3413
  let line;
3214
- if (i === 0 && this.#executing && this.#showSpinner) line = `${prefix}${LOADING_FRAMES[this.#loadingFrame]} ${contentLines[i]}`;
3414
+ if (i === 0 && this.#executing && this.#showSpinner) line = `${prefix}${LOADING_FRAMES$1[this.#loadingFrame]} ${contentLines[i]}`;
3215
3415
  else line = prefix + contentLines[i];
3216
3416
  contentLines[i] = truncateToWidth(line, width);
3217
3417
  }
@@ -3245,6 +3445,243 @@ var ZapmycoEditor = class extends Editor {
3245
3445
  }
3246
3446
  };
3247
3447
 
3448
+ //#endregion
3449
+ //#region src/cli/repl/components/task-status-bar.ts
3450
+ /**
3451
+ * TaskStatusBar — 任务状态栏组件
3452
+ *
3453
+ * 显示 TaskManage 创建的任务列表,支持折叠/展开两种模式。
3454
+ * 类似 Claude Code 的 TaskListV2,使用 pi-tui Container 实现。
3455
+ *
3456
+ * 折叠模式(默认,单行):
3457
+ * 📋 3 tasks · ◼ 1 in_progress · ◻ 1 pending · ✔ 1 completed Ctrl+T expand
3458
+ *
3459
+ * 展开模式(Ctrl+T 切换):
3460
+ * ⠋ #1 Search files
3461
+ * ◻ #2 Core logic
3462
+ * ◻ #3 Tests ▸ blocked by #1
3463
+ * ✔ #4 Analysis
3464
+ *
3465
+ * 1 in_progress · 1 pending · 1 completed
3466
+ * (Ctrl+T collapse)
3467
+ *
3468
+ * 自动隐藏:无任务时 render() 返回 []。
3469
+ * 进行中任务:显示 loading spinner 动画 + cyan 高亮。
3470
+ * 已完成任务:绿色 + 删除线。
3471
+ *
3472
+ * @module cli/repl/components
3473
+ */
3474
+ /** Loading 动画帧(Braille 模式 spinner) */
3475
+ const LOADING_FRAMES = [
3476
+ "⠋",
3477
+ "⠙",
3478
+ "⠹",
3479
+ "⠸",
3480
+ "⠼",
3481
+ "⠴",
3482
+ "⠦",
3483
+ "⠧",
3484
+ "⠇",
3485
+ "⠏"
3486
+ ];
3487
+ const LOADING_INTERVAL_MS = 200;
3488
+ /** 静态状态图标映射 */
3489
+ const STATUS_ICONS = {
3490
+ pending: "◻",
3491
+ in_progress: "◼",
3492
+ completed: "✔",
3493
+ cancelled: "✕"
3494
+ };
3495
+ /**
3496
+ * 任务状态栏组件
3497
+ *
3498
+ * 从 TaskStore 读取任务列表,渲染为紧凑状态栏。
3499
+ * 固定在 OutputArea 和 AgentStatusBar 之间。
3500
+ */
3501
+ var TaskStatusBar = class extends Container {
3502
+ /**
3503
+ * 用户手动折叠标记
3504
+ *
3505
+ * - false(默认):根据任务状态自动决定展开/折叠
3506
+ * - true:用户通过 Ctrl+T 手动折叠,覆盖自动行为
3507
+ */
3508
+ #forceCollapsed = false;
3509
+ /** TaskStore 引用(只读,不写) */
3510
+ #store;
3511
+ /** loading 动画帧索引 */
3512
+ #loadingFrame = 0;
3513
+ /** loading 动画定时器 */
3514
+ #loadingTimer;
3515
+ /** 上次是否有 in_progress 任务(用于启停动画) */
3516
+ #hadInProgress = false;
3517
+ constructor(store) {
3518
+ super();
3519
+ this.#store = store;
3520
+ }
3521
+ /**
3522
+ * 切换展开/折叠状态
3523
+ *
3524
+ * 用户手动 Ctrl+T 时调用,设置 forceCollapsed 标记。
3525
+ * 当新任务出现时,forceCollapsed 会被自动重置。
3526
+ */
3527
+ toggle() {
3528
+ this.#forceCollapsed = !this.#forceCollapsed;
3529
+ this.invalidate();
3530
+ }
3531
+ /** 当前是否处于展开状态 */
3532
+ get isExpanded() {
3533
+ const summary = this.#store.summary();
3534
+ return this.#shouldExpand(summary);
3535
+ }
3536
+ /**
3537
+ * 当 TaskStore 变化时由外部调用
3538
+ *
3539
+ * 如果出现新的活跃任务,自动展开;全部完成后自动折叠。
3540
+ */
3541
+ onTasksChanged() {
3542
+ const summary = this.#store.summary();
3543
+ if (summary.pending > 0 || summary.in_progress > 0) this.#forceCollapsed = false;
3544
+ this.invalidate();
3545
+ }
3546
+ invalidate() {
3547
+ super.invalidate();
3548
+ }
3549
+ /** 判断当前是否应展开 */
3550
+ #shouldExpand(summary) {
3551
+ if (summary.total === 0) return false;
3552
+ if (this.#forceCollapsed) return false;
3553
+ return summary.pending > 0 || summary.in_progress > 0;
3554
+ }
3555
+ /**
3556
+ * 管理 loading 动画的启停
3557
+ *
3558
+ * 当有 in_progress 任务时启动 spinner,全部完成后停止。
3559
+ */
3560
+ #updateLoading(summary) {
3561
+ const hasInProgress = summary.in_progress > 0;
3562
+ if (hasInProgress && !this.#hadInProgress) this.#startLoading();
3563
+ else if (!hasInProgress && this.#hadInProgress) this.#stopLoading();
3564
+ }
3565
+ /** 启动 loading 动画 */
3566
+ #startLoading() {
3567
+ if (this.#loadingTimer) return;
3568
+ this.#loadingTimer = setInterval(() => {
3569
+ this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES.length;
3570
+ this.invalidate();
3571
+ }, LOADING_INTERVAL_MS);
3572
+ }
3573
+ /** 停止 loading 动画 */
3574
+ #stopLoading() {
3575
+ if (this.#loadingTimer) {
3576
+ clearInterval(this.#loadingTimer);
3577
+ this.#loadingTimer = void 0;
3578
+ this.#loadingFrame = 0;
3579
+ }
3580
+ }
3581
+ /** 获取当前 in_progress 的图标(可能为动画帧) */
3582
+ #getInProgressIcon() {
3583
+ return LOADING_FRAMES[this.#loadingFrame % LOADING_FRAMES.length];
3584
+ }
3585
+ render(width) {
3586
+ const tasks = this.#store.read();
3587
+ const summary = this.#store.summary();
3588
+ this.#updateLoading(summary);
3589
+ this.#hadInProgress = summary.in_progress > 0;
3590
+ if (tasks.length === 0) {
3591
+ this.#stopLoading();
3592
+ return [];
3593
+ }
3594
+ if (this.#shouldExpand(summary)) return this.#renderExpanded(tasks, summary, width);
3595
+ return [this.#renderCollapsed(summary).slice(0, width)];
3596
+ }
3597
+ /** 渲染折叠模式单行 */
3598
+ #renderCollapsed(summary) {
3599
+ const parts = [];
3600
+ const taskCount = summary.total === 1 ? "1 task" : `${summary.total} tasks`;
3601
+ parts.push(chalk.cyan(`\uD83D\uDCCB ${taskCount}`));
3602
+ if (summary.in_progress > 0) {
3603
+ const icon = this.#getInProgressIcon();
3604
+ parts.push(chalk.cyan(`${icon} ${summary.in_progress} in_progress`));
3605
+ }
3606
+ if (summary.pending > 0) parts.push(`${STATUS_ICONS.pending} ${summary.pending} pending`);
3607
+ if (summary.completed > 0) parts.push(chalk.green(`${STATUS_ICONS.completed} ${summary.completed} completed`));
3608
+ if (summary.cancelled > 0) parts.push(chalk.red.dim(`${STATUS_ICONS.cancelled} ${summary.cancelled} cancelled`));
3609
+ const hint = chalk.dim("Ctrl+T expand");
3610
+ return ` ${parts.join(" · ")} ${hint}`;
3611
+ }
3612
+ /** 渲染展开模式多行 */
3613
+ #renderExpanded(tasks, summary, width) {
3614
+ const lines = [];
3615
+ const sorted = this.#sortTasks(tasks);
3616
+ for (const task of sorted) lines.push(this.#renderTaskLine(task, width));
3617
+ lines.push("");
3618
+ const summaryParts = [];
3619
+ if (summary.in_progress > 0) summaryParts.push(chalk.cyan(`${summary.in_progress} in_progress`));
3620
+ if (summary.pending > 0) summaryParts.push(`${summary.pending} pending`);
3621
+ if (summary.completed > 0) summaryParts.push(chalk.green(`${summary.completed} completed`));
3622
+ if (summary.cancelled > 0) summaryParts.push(chalk.red.dim(`${summary.cancelled} cancelled`));
3623
+ lines.push(` ${summaryParts.join(" · ")}`);
3624
+ lines.push(chalk.dim(" (Ctrl+T collapse)"));
3625
+ return lines;
3626
+ }
3627
+ /** 渲染单个任务行 */
3628
+ #renderTaskLine(task, width) {
3629
+ const prefix = ` ${task.status === "in_progress" ? this.#getInProgressIcon() : STATUS_ICONS[task.status] ?? "?"} #${task.id} `;
3630
+ const blockedBy = this.#getBlockedHint(task);
3631
+ const blockedText = blockedBy ? ` ${blockedBy}` : "";
3632
+ const availableWidth = Math.max(10, width - [...prefix].length - [...blockedText].length);
3633
+ const subject = task.subject.length > availableWidth ? `${task.subject.slice(0, availableWidth - 1)}\u2026` : task.subject;
3634
+ let subjectStyled;
3635
+ if (task.status === "completed") subjectStyled = chalk.green.dim.strikethrough(subject);
3636
+ else if (task.status === "in_progress") subjectStyled = chalk.cyan.bold(subject);
3637
+ else if (task.status === "cancelled") subjectStyled = chalk.red.dim(subject);
3638
+ else subjectStyled = subject;
3639
+ const rightStyled = blockedBy ? chalk.dim(blockedBy) : "";
3640
+ return `${prefix}${subjectStyled}${blockedBy ? ` ${rightStyled}` : ""}`.slice(0, width);
3641
+ }
3642
+ /** 获取阻塞提示文本 */
3643
+ #getBlockedHint(task) {
3644
+ if (!task.dependencies || task.dependencies.length === 0) return "";
3645
+ if (task.status !== "pending") return "";
3646
+ const allTasks = this.#store.read();
3647
+ const openBlockers = [];
3648
+ for (const depId of task.dependencies) {
3649
+ const depTask = allTasks.find((t) => t.id === depId);
3650
+ if (depTask && depTask.status !== "completed") openBlockers.push(`#${depId}`);
3651
+ else if (!depTask) openBlockers.push(`#${depId}?`);
3652
+ }
3653
+ if (openBlockers.length === 0) return "";
3654
+ return `\u25B8 blocked by ${openBlockers.join(", ")}`;
3655
+ }
3656
+ /** 排序任务 */
3657
+ #sortTasks(tasks) {
3658
+ const allTasks = this.#store.read();
3659
+ return [...tasks].sort((a, b) => {
3660
+ return this.#taskPriority(a, allTasks) - this.#taskPriority(b, allTasks);
3661
+ });
3662
+ }
3663
+ /** 计算任务优先级(值越小越靠前) */
3664
+ #taskPriority(task, allTasks) {
3665
+ switch (task.status) {
3666
+ case "in_progress": return 0;
3667
+ case "pending":
3668
+ if (this.#isBlocked(task, allTasks)) return 2;
3669
+ return 1;
3670
+ case "completed": return 10 - Date.now() + task.updatedAt;
3671
+ case "cancelled": return 20;
3672
+ default: return 100;
3673
+ }
3674
+ }
3675
+ /** 判断任务是否被阻塞 */
3676
+ #isBlocked(task, allTasks) {
3677
+ if (!task.dependencies || task.dependencies.length === 0) return false;
3678
+ return task.dependencies.some((depId) => {
3679
+ const depTask = allTasks.find((t) => t.id === depId);
3680
+ return !depTask || depTask.status !== "completed";
3681
+ });
3682
+ }
3683
+ };
3684
+
3248
3685
  //#endregion
3249
3686
  //#region src/cli/repl/cron/cron-parser.ts
3250
3687
  /** 通配符 * — 匹配所有值 */
@@ -9218,6 +9655,12 @@ function setSkillEntries(entries) {
9218
9655
  skillEntries = entries;
9219
9656
  }
9220
9657
  /**
9658
+ * 获取当前已加载的 Skill 列表
9659
+ */
9660
+ function getSkillEntries() {
9661
+ return skillEntries;
9662
+ }
9663
+ /**
9221
9664
  * 替换 Skill 内容中的模板变量
9222
9665
  *
9223
9666
  * 支持的变量:
@@ -9382,6 +9825,33 @@ function getSkillCommandSpecs(entries) {
9382
9825
  }));
9383
9826
  }
9384
9827
  /**
9828
+ * 格式化 Skill 指令内容(供 Skill tool 和斜杠命令 handler 共用)
9829
+ *
9830
+ * 使用内存中已解析的 skill.body,无需重新读取 SKILL.md 文件。
9831
+ *
9832
+ * @param skill - Skill 定义(含 frontmatter、body、baseDir)
9833
+ * @param args - 用户传递的参数(可选)
9834
+ * @returns 格式化后的完整指令文本
9835
+ */
9836
+ function formatSkillContent(skill, args) {
9837
+ const substituted = substituteVariables(skill.body, args, skill.baseDir, skill.name);
9838
+ const hint = skill.frontmatter["argument-hint"] ? ` [${skill.frontmatter["argument-hint"]}]` : "";
9839
+ const instructionParts = [
9840
+ `# Skill: ${skill.name}${hint}`,
9841
+ "",
9842
+ skill.description ? `> ${skill.description}` : "",
9843
+ "",
9844
+ "---",
9845
+ "",
9846
+ substituted,
9847
+ "",
9848
+ "---",
9849
+ `Base directory: ${skill.baseDir}`
9850
+ ];
9851
+ if (!substituted.includes(args ?? "") && args) instructionParts.push("", `ARGUMENTS: ${args}`);
9852
+ return instructionParts.filter(Boolean).join("\n");
9853
+ }
9854
+ /**
9385
9855
  * 规范化技能命令名称
9386
9856
  *
9387
9857
  * 转换为小写,非字母数字字符替换为连字符。
@@ -11514,6 +11984,7 @@ var AgentOrchestrator = class {
11514
11984
  };
11515
11985
  const depth = 1;
11516
11986
  const instanceId = `flat-${spec.id}-${Date.now()}`;
11987
+ let stopRelay;
11517
11988
  try {
11518
11989
  const agent = createAgentFromType(definition, {
11519
11990
  instanceId,
@@ -11533,6 +12004,7 @@ var AgentOrchestrator = class {
11533
12004
  timeoutMs: this.flatConfig.taskTimeoutMs,
11534
12005
  inheritContext: false
11535
12006
  }, null, depth);
12007
+ stopRelay = this.#relayAgentProgress(agent, instanceId);
11536
12008
  agent.systemPromptOverride = this.buildFlatSystemPrompt(spec, context);
11537
12009
  const result = await Promise.race([agent.execute({
11538
12010
  taskId: `flat-${spec.id}`,
@@ -11547,6 +12019,7 @@ var AgentOrchestrator = class {
11547
12019
  const outputText = this.extractOutputText(result);
11548
12020
  const isSuccess = typeof result === "object" && result !== null && "status" in result && result.status === "success";
11549
12021
  instanceManager.transition(instanceId, isSuccess ? "completed" : "failed");
12022
+ stopRelay();
11550
12023
  return {
11551
12024
  specId: spec.id,
11552
12025
  status: isSuccess ? "success" : "failure",
@@ -11561,6 +12034,9 @@ var AgentOrchestrator = class {
11561
12034
  error: message,
11562
12035
  duration
11563
12036
  });
12037
+ try {
12038
+ stopRelay?.();
12039
+ } catch {}
11564
12040
  try {
11565
12041
  instanceManager.transition(instanceId, "failed");
11566
12042
  } catch {}
@@ -11642,6 +12118,7 @@ var AgentOrchestrator = class {
11642
12118
  const instanceId = `agent-${typeId}-${Date.now()}`;
11643
12119
  const timeoutMs = options?.timeoutMs ?? this.flatConfig.taskTimeoutMs;
11644
12120
  let worktreeInfo;
12121
+ let stopRelay;
11645
12122
  try {
11646
12123
  const agent = createAgentFromType(definition, {
11647
12124
  instanceId,
@@ -11661,6 +12138,7 @@ var AgentOrchestrator = class {
11661
12138
  timeoutMs,
11662
12139
  inheritContext: options?.inheritContext ?? false
11663
12140
  }, parentInstanceId || null, depth);
12141
+ stopRelay = this.#relayAgentProgress(agent, instanceId);
11664
12142
  const promptCtx = {
11665
12143
  taskDescription,
11666
12144
  workdir: process.cwd()
@@ -11762,6 +12240,9 @@ var AgentOrchestrator = class {
11762
12240
  }
11763
12241
  };
11764
12242
  } finally {
12243
+ try {
12244
+ stopRelay?.();
12245
+ } catch {}
11765
12246
  try {
11766
12247
  const instance = instanceManager.get(instanceId);
11767
12248
  if (instance) instance.agent.systemPromptOverride = null;
@@ -11814,6 +12295,41 @@ var AgentOrchestrator = class {
11814
12295
  return aggregateResults(teamId, workerResults);
11815
12296
  }
11816
12297
  /**
12298
+ * 监听子 Agent 进度事件并中继到 InstanceManager
12299
+ *
12300
+ * 在子 Agent 执行工具调用时,更新其 AgentInstance 的 currentActivity,
12301
+ * 驱动 UI 状态栏实时展示子 Agent 当前操作。
12302
+ *
12303
+ * @param agent - 子 Agent 实例
12304
+ * @param instanceId - 对应的 InstanceManager 实例 ID
12305
+ * @returns 清理函数,调用后移除事件监听
12306
+ */
12307
+ #relayAgentProgress(agent, instanceId) {
12308
+ const progressHandler = (event) => {
12309
+ const instanceManager = getAgentInstanceManager();
12310
+ const instance = instanceManager.get(instanceId);
12311
+ if (!instance) return;
12312
+ if (event.percent === 0) {
12313
+ const toolName = event.message;
12314
+ const prevUses = instance.currentActivity?.toolUses ?? 0;
12315
+ const parenIdx = toolName.indexOf("(");
12316
+ const namePart = parenIdx > 0 ? toolName.slice(0, parenIdx) : toolName;
12317
+ const argsPart = parenIdx > 0 ? toolName.slice(parenIdx + 1, -1) : void 0;
12318
+ const activity = {
12319
+ toolName: namePart,
12320
+ toolUses: prevUses + 1,
12321
+ ...argsPart !== void 0 ? { args: argsPart } : {},
12322
+ startedAt: Date.now()
12323
+ };
12324
+ instanceManager.setActivity(instanceId, activity);
12325
+ } else if (event.percent === 100 || event.percent === -1) {}
12326
+ };
12327
+ agent.on(agent.EVENT_PROGRESS, progressHandler);
12328
+ return () => {
12329
+ agent.off(agent.EVENT_PROGRESS, progressHandler);
12330
+ };
12331
+ }
12332
+ /**
11817
12333
  * 为扁平子 Agent 构建系统提示词
11818
12334
  */
11819
12335
  buildFlatSystemPrompt(spec, context) {
@@ -11923,10 +12439,10 @@ const MODE_STRATEGIES = {
11923
12439
  defaultAction: "deny",
11924
12440
  maxAutoAllow: "low"
11925
12441
  },
11926
- /** normal: low 直接 allow,medium+ 需要 ask,critical deny */
12442
+ /** normal: 工具默认放行,仅内容级安全检查(shell 危险命令、敏感文件路径)触发审批 */
11927
12443
  normal: {
11928
- defaultAction: "ask",
11929
- maxAutoAllow: "low"
12444
+ defaultAction: "allow",
12445
+ maxAutoAllow: "high"
11930
12446
  },
11931
12447
  /** permissive: low/medium 直接 allow,high ask,critical deny */
11932
12448
  permissive: {
@@ -12597,6 +13113,207 @@ function createLspTool(lspManager) {
12597
13113
  };
12598
13114
  }
12599
13115
 
13116
+ //#endregion
13117
+ //#region src/cli/repl/utils/markdown-formatter.ts
13118
+ /**
13119
+ * Markdown → ANSI 格式化器
13120
+ *
13121
+ * 将 LLM 返回的 Markdown 文本解析并应用 ANSI 样式(颜色、加粗、斜体等),
13122
+ * 使终端输出更易读。参考 Claude Code 的 formatToken 实现。
13123
+ *
13124
+ * 使用方式:
13125
+ * import { formatMarkdown } from '@/cli/repl/utils/markdown-formatter';
13126
+ * console.log(formatMarkdown('**hello** world'));
13127
+ */
13128
+ let markedConfigured = false;
13129
+ function configureMarked() {
13130
+ if (markedConfigured) return;
13131
+ markedConfigured = true;
13132
+ marked.use({ tokenizer: { del() {} } });
13133
+ }
13134
+ const EOL = "\n";
13135
+ const LIST_ITEM_MARKER = "-";
13136
+ const BLOCKQUOTE_BAR = "│";
13137
+ /**
13138
+ * 格式化 Markdown 文本为 ANSI 样式字符串
13139
+ *
13140
+ * @param text - 原始 Markdown 文本
13141
+ * @param colorEnabled - 是否启用颜色(false 时仅保留结构格式化)
13142
+ * @returns 格式化后的字符串
13143
+ */
13144
+ function formatMarkdown(text, colorEnabled = true) {
13145
+ if (!text) return "";
13146
+ configureMarked();
13147
+ const tokens = marked.lexer(text);
13148
+ const parts = [];
13149
+ for (const token of tokens) parts.push(formatToken(token, colorEnabled, 0, null, null));
13150
+ return parts.join("").trimEnd();
13151
+ }
13152
+ function c(colorEnabled) {
13153
+ if (colorEnabled) return chalk;
13154
+ const noColor = Object.create(chalk);
13155
+ noColor.level = 0;
13156
+ return noColor;
13157
+ }
13158
+ function formatToken(token, colorEnabled, listDepth, orderedListNumber, parent) {
13159
+ const ch = c(colorEnabled);
13160
+ switch (token.type) {
13161
+ case "blockquote": {
13162
+ const inner = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "";
13163
+ const bar = ch.dim(BLOCKQUOTE_BAR);
13164
+ return inner.split(EOL).map((line) => line.trim() ? `${bar} ${ch.italic(line)}` : line).join(EOL);
13165
+ }
13166
+ case "code": {
13167
+ const lang = token.lang ? `${ch.dim(` ${token.lang}`)}` : "";
13168
+ const header = token.lang ? `${ch.dim("```")}${lang}${EOL}` : "";
13169
+ const footer = token.lang ? `${EOL}${ch.dim("```")}` : "";
13170
+ return `${header}${ch.dim(token.text)}${footer}${EOL}`;
13171
+ }
13172
+ case "codespan": return ch.cyan(token.text);
13173
+ case "em": {
13174
+ const inner = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, token)).join("") : "";
13175
+ return ch.italic(inner);
13176
+ }
13177
+ case "strong": {
13178
+ const inner = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, token)).join("") : "";
13179
+ return ch.bold(inner);
13180
+ }
13181
+ case "heading": {
13182
+ const inner = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "";
13183
+ if (token.depth === 1) return `${ch.bold.italic.underline(inner)}${EOL}${EOL}`;
13184
+ return `${ch.bold(inner)}${EOL}${EOL}`;
13185
+ }
13186
+ case "hr": return ch.dim("─".repeat(50)) + EOL;
13187
+ case "image": return token.href;
13188
+ case "link": {
13189
+ const linkText = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, token)).join("") : "";
13190
+ if (linkText && linkText !== token.href) return `${linkText} ${ch.dim(`(${token.href})`)}`;
13191
+ return ch.dim(token.href);
13192
+ }
13193
+ case "list": return token.items.map((item, index) => formatToken(item, colorEnabled, listDepth, token.ordered ? (token.start ?? 1) + index : null, token)).join("");
13194
+ case "list_item": {
13195
+ const marker = orderedListNumber !== null ? `${orderedListNumber}.` : LIST_ITEM_MARKER;
13196
+ return `${" ".repeat(listDepth)}${marker} ${(token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, listDepth + 1, orderedListNumber, token)).join("") : "").trimStart()}`;
13197
+ }
13198
+ case "paragraph": return (token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "") + EOL;
13199
+ case "space": return EOL;
13200
+ case "br": return EOL;
13201
+ case "text":
13202
+ if (parent?.type === "list_item") {
13203
+ const marker = orderedListNumber !== null ? `${orderedListNumber}.` : LIST_ITEM_MARKER;
13204
+ return `${" ".repeat(listDepth)}${marker} ${token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, listDepth, orderedListNumber, token)).join("") : token.text}${EOL}`;
13205
+ }
13206
+ return token.text;
13207
+ case "table": return formatTable(token, colorEnabled);
13208
+ case "escape": return token.text;
13209
+ case "def":
13210
+ case "html":
13211
+ case "del": return "";
13212
+ default: return "";
13213
+ }
13214
+ }
13215
+ /**
13216
+ * 格式化表格为对齐的 ASCII 表格
13217
+ */
13218
+ function formatTable(token, colorEnabled) {
13219
+ const ch = c(colorEnabled);
13220
+ function getCellText(cellTokens) {
13221
+ if (!cellTokens) return "";
13222
+ return cellTokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("");
13223
+ }
13224
+ const columnCount = token.header.length;
13225
+ const colWidths = [];
13226
+ for (let i = 0; i < columnCount; i++) {
13227
+ const headerCell = token.header[i];
13228
+ let maxW = ansiCellWidth(headerCell ? getCellText(headerCell.tokens) : "");
13229
+ for (const row of token.rows) {
13230
+ const cell = row[i];
13231
+ if (cell?.tokens) {
13232
+ const cellText = getCellText(cell.tokens);
13233
+ maxW = Math.max(maxW, ansiCellWidth(cellText));
13234
+ }
13235
+ }
13236
+ colWidths.push(Math.max(maxW, 3));
13237
+ }
13238
+ let output = "";
13239
+ output += "┌";
13240
+ for (let i = 0; i < columnCount; i++) {
13241
+ const cw = colWidths[i] ?? 3;
13242
+ output += "─".repeat(cw + 2) + "┬";
13243
+ }
13244
+ output = output.slice(0, -1) + "┐\n";
13245
+ output += "│ ";
13246
+ for (let i = 0; i < columnCount; i++) {
13247
+ const cell = token.header[i];
13248
+ const content = cell?.tokens ? cell.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "";
13249
+ const displayLen = ansiCellWidth(content);
13250
+ const pad = (colWidths[i] ?? 3) - displayLen;
13251
+ const align = token.align?.[i];
13252
+ output += align === "right" ? " ".repeat(pad) + ch.bold(content) : align === "center" ? " ".repeat(Math.floor(pad / 2)) + ch.bold(content) + " ".repeat(Math.ceil(pad / 2)) : ch.bold(content) + " ".repeat(pad);
13253
+ output += " │ ";
13254
+ }
13255
+ output = output.trimEnd() + EOL;
13256
+ output += "├";
13257
+ for (let i = 0; i < columnCount; i++) {
13258
+ const align = token.align?.[i];
13259
+ const left = align === "center" || align === "right" ? ":" : "─";
13260
+ const right = align === "center" || align === "left" ? ":" : "─";
13261
+ const cw = colWidths[i] ?? 3;
13262
+ output += `${left}${"─".repeat(cw)}${right}┼`;
13263
+ }
13264
+ output = `${output.slice(0, -1)}┤${EOL}`;
13265
+ for (const row of token.rows) {
13266
+ output += "│ ";
13267
+ for (let i = 0; i < columnCount; i++) {
13268
+ const cell = row[i];
13269
+ const content = cell?.tokens ? cell.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "";
13270
+ const displayLen = ansiCellWidth(content);
13271
+ const pad = (colWidths[i] ?? 3) - displayLen;
13272
+ const align = token.align?.[i];
13273
+ output += align === "right" ? " ".repeat(pad) + content : align === "center" ? " ".repeat(Math.floor(pad / 2)) + content + " ".repeat(Math.ceil(pad / 2)) : content + " ".repeat(pad);
13274
+ output += " │ ";
13275
+ }
13276
+ output = output.trimEnd() + EOL;
13277
+ }
13278
+ output += "└";
13279
+ for (let i = 0; i < columnCount; i++) {
13280
+ const cw = colWidths[i] ?? 3;
13281
+ output += "─".repeat(cw + 2) + "┴";
13282
+ }
13283
+ output = output.slice(0, -1) + "┘\n";
13284
+ return output + EOL;
13285
+ }
13286
+ /**
13287
+ * 计算字符串在终端中的可见宽度(考虑 CJK 和 emoji 占 2 列宽)
13288
+ *
13289
+ * ANSI 转义序列不计入宽度,中文字符/emoji 占 2 列,ASCII 占 1 列。
13290
+ */
13291
+ function ansiCellWidth(str) {
13292
+ const ESC = String.fromCharCode(27);
13293
+ const plain = str.replace(new RegExp(`${ESC}\\[[0-9;]*m`, "g"), "");
13294
+ let width = 0;
13295
+ for (const ch of plain) {
13296
+ const code = ch.codePointAt(0) ?? 0;
13297
+ if (isZeroWidthChar(code)) continue;
13298
+ if (isWideChar(code)) width += 2;
13299
+ else width += 1;
13300
+ }
13301
+ return width;
13302
+ }
13303
+ /**
13304
+ * 判断 Unicode 码点是否为零宽字符(不可见 / 组合用字符)
13305
+ *
13306
+ * 这些字符在终端中不占据任何列宽,无论是 CJK 字体还是等宽字体下。
13307
+ * 如果在宽度计算中按 1 或 2 计算,会导致表格列对齐错误。
13308
+ */
13309
+ function isZeroWidthChar(code) {
13310
+ return code === 8203 || code === 8204 || code === 8205 || code === 8206 || code === 8207 || code === 8288 || code >= 8289 && code <= 8292 || code === 65038 || code === 65039 || code >= 768 && code <= 879 || code >= 7616 && code <= 7679 || code >= 8400 && code <= 8447 || code >= 65056 && code <= 65071;
13311
+ }
13312
+ /** 判断 Unicode 码点是否为终端宽字符(CJK / 全角 / emoji) */
13313
+ function isWideChar(code) {
13314
+ return code >= 4352 && code <= 4447 || code >= 11904 && code <= 12287 || code >= 12288 && code <= 13311 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 40960 && code <= 42191 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65049 || code >= 65072 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 126976 && code <= 131071 || code >= 131072 && code <= 196607 || code >= 196608 && code <= 262143 || code === 169 || code === 174 || code >= 8192 && code <= 8303 || code >= 8448 && code <= 10175 || code >= 11008 && code <= 11263 || code >= 10496 && code <= 10623 || code === 12336 || code === 12349 || code === 12951 || code === 12952 || code === 8986 || code === 8987 || code === 9193 || code === 9196 || code === 9200 || code === 9203 || code === 9725 || code === 9726 || code === 9748 || code === 9749 || code === 9800 || code === 9811 || code === 9855 || code === 9875 || code === 9889 || code === 9898 || code === 9899 || code === 9917 || code === 9918 || code === 9924 || code === 9925 || code === 9934 || code === 9940 || code === 9962 || code === 9970 || code === 9971 || code === 9973 || code === 9978 || code === 9981 || code === 9986 || code === 9989 || code === 9992 || code === 9993 || code === 9994 || code === 9995 || code === 9996 || code === 9997 || code === 9999 || code === 10002 || code === 10004 || code === 10006 || code === 10013 || code === 10017 || code === 10024 || code === 10035 || code === 10036 || code === 10052 || code === 10055 || code === 10060 || code === 10062 || code === 10067 || code === 10068 || code === 10069 || code === 10071 || code === 10083 || code === 10084 || code === 10133 || code === 10134 || code === 10135 || code === 10145 || code === 10160 || code === 10175 || code === 10548 || code === 10549 || code === 11013 || code === 11014 || code === 11015 || code === 11035 || code === 11036 || code === 11088 || code === 11093 || code === 11203 || code === 11204;
13315
+ }
13316
+
12600
13317
  //#endregion
12601
13318
  //#region src/config/types.ts
12602
13319
  /**
@@ -13603,6 +14320,7 @@ function ensureTaskDir() {
13603
14320
  }
13604
14321
  var TaskStore = class {
13605
14322
  tasks = /* @__PURE__ */ new Map();
14323
+ changeCallbacks = [];
13606
14324
  /** 读取完整任务列表 */
13607
14325
  read() {
13608
14326
  return Array.from(this.tasks.values());
@@ -13646,6 +14364,24 @@ var TaskStore = class {
13646
14364
  return this.tasks.size > 0;
13647
14365
  }
13648
14366
  /**
14367
+ * 注册任务变更监听
14368
+ *
14369
+ * @param callback - 任务列表变更时回调
14370
+ * @returns unsubscribe 函数,组件销毁时调用以清理
14371
+ */
14372
+ onChange(callback) {
14373
+ this.changeCallbacks.push(callback);
14374
+ return () => {
14375
+ this.changeCallbacks = this.changeCallbacks.filter((cb) => cb !== callback);
14376
+ };
14377
+ }
14378
+ /** 通知所有监听者(write/update/clear 操作后调用) */
14379
+ notifyChange() {
14380
+ for (const cb of this.changeCallbacks) try {
14381
+ cb();
14382
+ } catch {}
14383
+ }
14384
+ /**
13649
14385
  * 全量替换任务列表
13650
14386
  *
13651
14387
  * @param items - 新任务列表
@@ -13675,11 +14411,13 @@ var TaskStore = class {
13675
14411
  updatedAt: now
13676
14412
  };
13677
14413
  if (item.description !== void 0) taskItem.description = item.description;
14414
+ if (item.dependencies !== void 0) taskItem.dependencies = item.dependencies;
13678
14415
  newTasks.set(item.id, taskItem);
13679
14416
  }
13680
14417
  if (inProgressCount > 1) return `不允许同时有 ${inProgressCount} 个进行中的任务,请先将当前任务完成或取消后再开始新任务`;
13681
14418
  this.tasks = newTasks;
13682
14419
  this.persist();
14420
+ this.notifyChange();
13683
14421
  return null;
13684
14422
  }
13685
14423
  /**
@@ -13705,12 +14443,14 @@ var TaskStore = class {
13705
14443
  };
13706
14444
  this.tasks.set(id, updated);
13707
14445
  this.persist();
14446
+ this.notifyChange();
13708
14447
  return null;
13709
14448
  }
13710
14449
  /** 清空所有任务 */
13711
14450
  clear() {
13712
14451
  this.tasks.clear();
13713
14452
  this.persist();
14453
+ this.notifyChange();
13714
14454
  }
13715
14455
  /** 持久化到文件系统 */
13716
14456
  persist() {
@@ -13761,7 +14501,7 @@ var TaskStore = class {
13761
14501
  const lines = ["## 当前任务列表"];
13762
14502
  for (const task of active) {
13763
14503
  const marker = task.status === "in_progress" ? "▶" : "○";
13764
- lines.push(` ${marker} [${task.id}] ${task.subject}`);
14504
+ lines.push(` ${marker} #${task.id} ${task.subject}`);
13765
14505
  }
13766
14506
  return lines.join("\n");
13767
14507
  }
@@ -14863,6 +15603,8 @@ var ReplSession = class {
14863
15603
  tui;
14864
15604
  editor;
14865
15605
  outputArea;
15606
+ taskStatusBar;
15607
+ agentStatusBar;
14866
15608
  options;
14867
15609
  _state = "idle";
14868
15610
  parser;
@@ -14927,10 +15669,19 @@ var ReplSession = class {
14927
15669
  "tui.select.cancel": ["escape"],
14928
15670
  "tui.select.confirm": ["enter"]
14929
15671
  });
15672
+ this.taskStore = new TaskStore();
14930
15673
  this.outputArea = new OutputArea();
15674
+ this.agentStatusBar = new AgentStatusBar();
15675
+ this.taskStatusBar = new TaskStatusBar(this.taskStore);
15676
+ this.taskStore.onChange(() => {
15677
+ this.taskStatusBar.onTasksChanged();
15678
+ this.tui.requestRender();
15679
+ });
14931
15680
  this.editor = new ZapmycoEditor(this.tui, theme.editorTheme);
14932
15681
  const root = new Container();
14933
15682
  root.addChild(this.outputArea);
15683
+ root.addChild(this.taskStatusBar);
15684
+ root.addChild(this.agentStatusBar);
14934
15685
  root.addChild(this.editor);
14935
15686
  this.tui.addChild(root);
14936
15687
  this.tui.setFocus(this.editor);
@@ -14940,8 +15691,6 @@ var ReplSession = class {
14940
15691
  this.history = new HistoryStore(this.options.maxHistorySize);
14941
15692
  for (const entry of this.history.getAll()) this.editor.addToHistory(entry.input);
14942
15693
  this.agent = this.createReplAgent();
14943
- this.taskStore = new TaskStore();
14944
- this.taskStore.load();
14945
15694
  this.cronScheduler = new CronScheduler(getCronStore(), { isIdle: () => this._state === "idle" });
14946
15695
  this.cronScheduler.start();
14947
15696
  this.initWorktreeManager();
@@ -15128,12 +15877,13 @@ var ReplSession = class {
15128
15877
  /**
15129
15878
  * 执行用户目标 — 通过 Agent 执行并流式输出回复
15130
15879
  */
15131
- async executeGoal(rawInput) {
15880
+ async executeGoal(rawInput, displayLabel) {
15132
15881
  const startTime = Date.now();
15133
15882
  let historyEntry;
15134
15883
  const taskId = `task-${Date.now()}`;
15135
15884
  const colorEnabled = this.options.color;
15136
15885
  const userStyle = (s) => colorEnabled ? chalk.bold.cyan(s) : s;
15886
+ const markdownFormattingEnabled = this.config.cli.markdownFormatting !== false;
15137
15887
  const responseStyle = (s) => s;
15138
15888
  const toolStyle = (s) => colorEnabled ? chalk.yellow(s) : s;
15139
15889
  const execStyle = (s) => colorEnabled ? chalk.cyan(s) : s;
@@ -15156,19 +15906,20 @@ var ReplSession = class {
15156
15906
  goalId: `goal-${startTime}`,
15157
15907
  rawInput
15158
15908
  });
15159
- this.outputArea.append([userStyle(rawInput), LOADING_FRAMES[0] ?? ""]);
15909
+ this.outputArea.append([userStyle(displayLabel ?? rawInput), LOADING_FRAMES$1[0] ?? ""]);
15160
15910
  let spinnerFrame = 0;
15161
15911
  spinnerActive = true;
15162
15912
  spinnerInterval = setInterval(() => {
15163
15913
  if (!spinnerActive) return;
15164
- spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES.length;
15165
- this.outputArea.replaceLastLine(LOADING_FRAMES[spinnerFrame] ?? "");
15914
+ spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES$1.length;
15915
+ this.outputArea.replaceLastLine(LOADING_FRAMES$1[spinnerFrame] ?? "");
15166
15916
  this.tui.requestRender();
15167
15917
  }, 100);
15168
15918
  let spinnerStopped = false;
15169
15919
  let outputAccumulator = "";
15170
15920
  let thinkingAccumulator = "";
15171
15921
  let streamMode = "response";
15922
+ let responseLineIndex = null;
15172
15923
  const thinkingDisplayMode = this.config.cli.thinkingDisplay ?? "collapse";
15173
15924
  let thinkingHeaderLineIndex = null;
15174
15925
  let thinkingContentLineCount = 0;
@@ -15215,20 +15966,20 @@ var ReplSession = class {
15215
15966
  streamMode = "response";
15216
15967
  thinkingAccumulator = "";
15217
15968
  outputAccumulator = event.text;
15218
- this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
15969
+ responseLineIndex = this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
15219
15970
  } else if (streamMode !== "response") {
15220
15971
  streamMode = "response";
15221
15972
  if (thinkingElapsedInterval) {
15222
15973
  clearInterval(thinkingElapsedInterval);
15223
15974
  thinkingElapsedInterval = void 0;
15224
15975
  }
15225
- if (thinkingHeaderLineIndex !== null && thinkingDisplayMode === "collapse") {
15226
- const elapsed = ((Date.now() - thinkingStartTimeMs) / 1e3).toFixed(1);
15227
- this.outputArea.updateLine(thinkingHeaderLineIndex, dimStyle(` \u2234 Thinking (${elapsed}s)`));
15228
- }
15229
15976
  thinkingAccumulator = "";
15230
15977
  outputAccumulator = event.text;
15231
- this.outputArea.append([responseStyle(outputAccumulator)]);
15978
+ if (thinkingHeaderLineIndex !== null) {
15979
+ this.outputArea.spliceLines(thinkingHeaderLineIndex, 1, [responseStyle(outputAccumulator)]);
15980
+ responseLineIndex = thinkingHeaderLineIndex;
15981
+ thinkingHeaderLineIndex = null;
15982
+ } else responseLineIndex = this.outputArea.append([responseStyle(outputAccumulator)]);
15232
15983
  } else {
15233
15984
  outputAccumulator += event.text;
15234
15985
  this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
@@ -15245,8 +15996,8 @@ var ReplSession = class {
15245
15996
  spinnerStopped = true;
15246
15997
  spinnerActive = false;
15247
15998
  clearInterval(spinnerInterval);
15248
- thinkingHeaderLineIndex = this.outputArea.replaceLastLine(dimStyle(` ${LOADING_FRAMES[0] ?? ""} Thinking...`));
15249
- } else thinkingHeaderLineIndex = this.outputArea.append([dimStyle(` ${LOADING_FRAMES[0] ?? ""} Thinking...`)]);
15999
+ thinkingHeaderLineIndex = this.outputArea.replaceLastLine(dimStyle(` ${LOADING_FRAMES$1[0] ?? ""} Thinking...`));
16000
+ } else thinkingHeaderLineIndex = this.outputArea.append([dimStyle(` ${LOADING_FRAMES$1[0] ?? ""} Thinking...`)]);
15250
16001
  thinkingAccumulator = event.text;
15251
16002
  thinkingContentLineCount = 0;
15252
16003
  if (thinkingDisplayMode === "expand") {
@@ -15258,7 +16009,7 @@ var ReplSession = class {
15258
16009
  if (thinkingHeaderLineIndex === null) return;
15259
16010
  if (thinkingContentLineCount > 0) return;
15260
16011
  const elapsed = ((Date.now() - thinkingStartTimeMs) / 1e3).toFixed(1);
15261
- const frame = LOADING_FRAMES[thinkingFrame % LOADING_FRAMES.length];
16012
+ const frame = LOADING_FRAMES$1[thinkingFrame % LOADING_FRAMES$1.length];
15262
16013
  thinkingFrame++;
15263
16014
  this.outputArea.updateLine(thinkingHeaderLineIndex, dimStyle(` ${frame} Thinking... (${elapsed}s)`));
15264
16015
  this.tui.requestRender();
@@ -15286,16 +16037,17 @@ var ReplSession = class {
15286
16037
  if (thinkingElapsedInterval && streamMode === "thinking") {
15287
16038
  clearInterval(thinkingElapsedInterval);
15288
16039
  thinkingElapsedInterval = void 0;
15289
- if (thinkingHeaderLineIndex !== null && thinkingDisplayMode === "collapse") {
15290
- const elapsed = ((Date.now() - thinkingStartTimeMs) / 1e3).toFixed(1);
15291
- this.outputArea.updateLine(thinkingHeaderLineIndex, dimStyle(` \u2234 Thinking (${elapsed}s)`));
16040
+ if (thinkingHeaderLineIndex !== null) {
16041
+ this.outputArea.spliceLines(thinkingHeaderLineIndex, 1, []);
16042
+ thinkingHeaderLineIndex = null;
15292
16043
  this.tui.requestRender();
15293
16044
  }
15294
16045
  }
15295
16046
  if (event.message.startsWith("Exec(")) {
15296
16047
  execMessage = event.message;
15297
16048
  execLineIndex = this.outputArea.append([execStyle(` ⏺ ${event.message}`)]);
15298
- } else this.outputArea.append([toolStyle(` → ${event.message}`)]);
16049
+ } else if (event.message.startsWith("TaskManage(")) log.debug("TaskManage 工具调用已记录,跳过 TUI 展示");
16050
+ else this.outputArea.append([toolStyle(` → ${event.message}`)]);
15299
16051
  this.tui.requestRender();
15300
16052
  } else if (event.percent === 100 && execLineIndex !== void 0 && execMessage) {
15301
16053
  if (event.message.startsWith("工具 Exec")) {
@@ -15360,8 +16112,8 @@ var ReplSession = class {
15360
16112
  spinnerActive = true;
15361
16113
  spinnerInterval = setInterval(() => {
15362
16114
  if (!spinnerActive) return;
15363
- spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES.length;
15364
- this.outputArea.replaceLastLine(LOADING_FRAMES[spinnerFrame] ?? "");
16115
+ spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES$1.length;
16116
+ this.outputArea.replaceLastLine(LOADING_FRAMES$1[spinnerFrame] ?? "");
15365
16117
  this.tui.requestRender();
15366
16118
  }, 100);
15367
16119
  }
@@ -15419,6 +16171,24 @@ var ReplSession = class {
15419
16171
  status: taskResult.status
15420
16172
  });
15421
16173
  }
16174
+ if (markdownFormattingEnabled && taskResult.status === "success") {
16175
+ if (outputAccumulator && responseLineIndex !== null) {
16176
+ const formatted = formatMarkdown(outputAccumulator, colorEnabled);
16177
+ if (formatted) {
16178
+ const formattedLines = formatted.split("\n");
16179
+ this.outputArea.spliceLines(responseLineIndex, 1, formattedLines);
16180
+ this.tui.requestRender();
16181
+ }
16182
+ } else if (spinnerStopped === false && outputText) {
16183
+ const formatted = formatMarkdown(outputText, colorEnabled);
16184
+ if (formatted) {
16185
+ const lines = formatted.split("\n");
16186
+ this.outputArea.replaceLastLine(lines[0] ?? "");
16187
+ if (lines.length > 1) this.outputArea.append(lines.slice(1));
16188
+ this.tui.requestRender();
16189
+ }
16190
+ }
16191
+ }
15422
16192
  this.outputArea.append([""]);
15423
16193
  const duration = Date.now() - startTime;
15424
16194
  const result = {
@@ -15843,8 +16613,14 @@ var ReplSession = class {
15843
16613
  usage: spec.name,
15844
16614
  handler: async (args) => {
15845
16615
  const argsStr = args.join(" ");
15846
- const goalInput = argsStr ? `请使用 /${spec.name} 技能,参数: ${argsStr}` : `请使用 /${spec.name} 技能`;
15847
- await this.executeGoal(goalInput);
16616
+ const entry = getSkillEntries().find((e) => sanitizeSkillCommandName(e.skill.name) === spec.name);
16617
+ if (entry) {
16618
+ const skillContent = formatSkillContent(entry.skill, argsStr);
16619
+ await this.executeGoal(skillContent, `/${spec.name}`);
16620
+ } else {
16621
+ const goalInput = argsStr ? `请使用 /${spec.name} 技能,参数: ${argsStr}` : `请使用 /${spec.name} 技能`;
16622
+ await this.executeGoal(goalInput);
16623
+ }
15848
16624
  }
15849
16625
  });
15850
16626
  }
@@ -15884,7 +16660,15 @@ var ReplSession = class {
15884
16660
  this.editor.onCtrlD = () => {
15885
16661
  this.shutdown("收到 EOF (Ctrl+D)");
15886
16662
  };
16663
+ this.editor.onToggleAgentBar = () => {
16664
+ this.agentStatusBar.toggle();
16665
+ this.tui.requestRender();
16666
+ };
15887
16667
  this.editor.onOpenEditor = () => this.openInEditor();
16668
+ this.editor.onToggleTasks = () => {
16669
+ this.taskStatusBar.toggle();
16670
+ this.tui.requestRender();
16671
+ };
15888
16672
  }
15889
16673
  /** 设置信号处理 */
15890
16674
  setupSignalHandlers() {
@@ -15895,6 +16679,19 @@ var ReplSession = class {
15895
16679
  eventBus.on("system:shutdown", ({ reason }) => {
15896
16680
  log.debug(`收到系统关闭信号: ${reason ?? "未知"}`);
15897
16681
  });
16682
+ const instanceManager = getAgentInstanceManager();
16683
+ const refreshStatusBar = () => {
16684
+ this.agentStatusBar.invalidate();
16685
+ this.tui.requestRender();
16686
+ };
16687
+ instanceManager.on("instance:registered", refreshStatusBar);
16688
+ instanceManager.on("instance:transitioned", refreshStatusBar);
16689
+ instanceManager.on("instance:activity", refreshStatusBar);
16690
+ eventBus.on("system:shutdown", () => {
16691
+ instanceManager.off("instance:registered", refreshStatusBar);
16692
+ instanceManager.off("instance:transitioned", refreshStatusBar);
16693
+ instanceManager.off("instance:activity", refreshStatusBar);
16694
+ });
15898
16695
  }
15899
16696
  /** 取消当前正在执行的任务 */
15900
16697
  cancelCurrentTask() {