@fitlab-ai/agent-infra 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.zh-CN.md +10 -0
  2. package/bin/cli.ts +1 -1
  3. package/dist/bin/cli.js +2 -2
  4. package/dist/lib/cp.js +57 -16
  5. package/dist/lib/decide.js +2 -2
  6. package/dist/lib/defaults.json +2 -0
  7. package/dist/lib/init.js +7 -7
  8. package/dist/lib/merge.js +1 -1
  9. package/dist/lib/prompt.js +1 -1
  10. package/dist/lib/run/index.js +7 -7
  11. package/dist/lib/run/prompt.js +1 -1
  12. package/dist/lib/sandbox/capture.js +10 -8
  13. package/dist/lib/sandbox/clipboard/bridge.js +59 -21
  14. package/dist/lib/sandbox/clipboard/inbox.js +82 -0
  15. package/dist/lib/sandbox/clipboard/index.js +5 -6
  16. package/dist/lib/sandbox/clipboard/linux.js +91 -0
  17. package/dist/lib/sandbox/clipboard/paths.js +1 -1
  18. package/dist/lib/sandbox/clipboard/win32.js +144 -0
  19. package/dist/lib/sandbox/commands/create.js +106 -88
  20. package/dist/lib/sandbox/commands/enter.js +9 -9
  21. package/dist/lib/sandbox/commands/list-running.js +2 -2
  22. package/dist/lib/sandbox/commands/ls.js +7 -7
  23. package/dist/lib/sandbox/commands/prune.js +8 -8
  24. package/dist/lib/sandbox/commands/rebuild.js +38 -50
  25. package/dist/lib/sandbox/commands/refresh.js +2 -2
  26. package/dist/lib/sandbox/commands/rm.js +11 -11
  27. package/dist/lib/sandbox/commands/show.js +4 -4
  28. package/dist/lib/sandbox/commands/start.js +4 -4
  29. package/dist/lib/sandbox/commands/vm.js +4 -4
  30. package/dist/lib/sandbox/config.js +10 -4
  31. package/dist/lib/sandbox/constants.js +4 -1
  32. package/dist/lib/sandbox/credentials.js +1 -1
  33. package/dist/lib/sandbox/dotfiles.js +1 -1
  34. package/dist/lib/sandbox/engine.js +3 -3
  35. package/dist/lib/sandbox/engines/index.js +5 -5
  36. package/dist/lib/sandbox/engines/wsl2-paths.js +1 -1
  37. package/dist/lib/sandbox/image-build.js +80 -0
  38. package/dist/lib/sandbox/image-prune.js +2 -2
  39. package/dist/lib/sandbox/index.js +10 -10
  40. package/dist/lib/sandbox/managed-fs.js +1 -1
  41. package/dist/lib/sandbox/readme-scaffold.js +1 -1
  42. package/dist/lib/sandbox/runtimes/base.dockerfile +3 -0
  43. package/dist/lib/sandbox/shell.js +9 -1
  44. package/dist/lib/sandbox/tools.js +4 -4
  45. package/dist/lib/server/adapters/feishu/index.js +10 -3
  46. package/dist/lib/server/adapters/feishu/renderer.js +88 -0
  47. package/dist/lib/server/adapters/feishu/transport.js +2 -13
  48. package/dist/lib/server/daemon.js +64 -43
  49. package/dist/lib/server/display.js +83 -0
  50. package/dist/lib/server/index.js +2 -2
  51. package/dist/lib/server/process-control.js +2 -2
  52. package/dist/lib/server/protocol.js +2 -2
  53. package/dist/lib/server/streamer.js +5 -4
  54. package/dist/lib/task/commands/cat.js +2 -2
  55. package/dist/lib/task/commands/decisions.js +4 -4
  56. package/dist/lib/task/commands/files.js +3 -3
  57. package/dist/lib/task/commands/grep.js +3 -3
  58. package/dist/lib/task/commands/issue-body.js +4 -4
  59. package/dist/lib/task/commands/log.js +5 -5
  60. package/dist/lib/task/commands/ls.js +3 -3
  61. package/dist/lib/task/commands/show.js +1 -1
  62. package/dist/lib/task/commands/status.js +71 -25
  63. package/dist/lib/task/index.js +9 -9
  64. package/dist/lib/task/resolve-ref.js +1 -1
  65. package/dist/lib/task/workflow-warnings.js +94 -0
  66. package/dist/lib/update.js +4 -4
  67. package/lib/cp.ts +56 -15
  68. package/lib/defaults.json +2 -0
  69. package/lib/sandbox/capture.ts +5 -3
  70. package/lib/sandbox/clipboard/bridge.ts +62 -18
  71. package/lib/sandbox/clipboard/inbox.ts +83 -0
  72. package/lib/sandbox/clipboard/index.ts +5 -6
  73. package/lib/sandbox/clipboard/linux.ts +124 -0
  74. package/lib/sandbox/clipboard/win32.ts +173 -0
  75. package/lib/sandbox/commands/create.ts +132 -88
  76. package/lib/sandbox/commands/rebuild.ts +42 -54
  77. package/lib/sandbox/config.ts +13 -1
  78. package/lib/sandbox/constants.ts +4 -0
  79. package/lib/sandbox/image-build.ts +134 -0
  80. package/lib/sandbox/runtimes/base.dockerfile +3 -0
  81. package/lib/sandbox/shell.ts +11 -2
  82. package/lib/sandbox/tools.ts +5 -5
  83. package/lib/server/adapters/_contract.ts +3 -0
  84. package/lib/server/adapters/feishu/index.ts +11 -3
  85. package/lib/server/adapters/feishu/renderer.ts +99 -0
  86. package/lib/server/adapters/feishu/transport.ts +5 -18
  87. package/lib/server/daemon.ts +74 -38
  88. package/lib/server/display.ts +136 -0
  89. package/lib/server/streamer.ts +5 -4
  90. package/lib/task/commands/log.ts +3 -3
  91. package/lib/task/commands/status.ts +102 -21
  92. package/lib/task/workflow-warnings.ts +121 -0
  93. package/package.json +2 -2
  94. package/templates/.agents/README.en.md +3 -0
  95. package/templates/.agents/README.zh-CN.md +3 -0
  96. package/templates/.agents/rules/create-issue.github.en.md +4 -4
  97. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -4
  98. package/templates/.agents/rules/issue-pr-commands.github.en.md +7 -2
  99. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +7 -2
  100. package/templates/.agents/rules/issue-sync.github.en.md +13 -0
  101. package/templates/.agents/rules/issue-sync.github.zh-CN.md +13 -0
  102. package/templates/.agents/rules/next-step-output.en.md +15 -1
  103. package/templates/.agents/rules/next-step-output.zh-CN.md +15 -1
  104. package/templates/.agents/rules/pr-sync.github.en.md +17 -2
  105. package/templates/.agents/rules/pr-sync.github.zh-CN.md +17 -2
  106. package/templates/.agents/rules/review-handshake.en.md +2 -2
  107. package/templates/.agents/rules/review-handshake.zh-CN.md +2 -2
  108. package/templates/.agents/rules/task-management.en.md +1 -1
  109. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  110. package/templates/.agents/rules/testing-discipline.en.md +3 -41
  111. package/templates/.agents/rules/testing-discipline.zh-CN.md +3 -41
  112. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
  113. package/templates/.agents/scripts/validate-artifact.js +213 -0
  114. package/templates/.agents/scripts/workflow-warnings.js +290 -0
  115. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  116. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
  117. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
  118. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  119. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  120. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  121. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  122. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +9 -9
  123. package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
  124. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  125. package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
  126. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  127. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +2 -2
  130. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +2 -2
  131. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +2 -2
  132. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
  133. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  134. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  135. package/templates/.agents/skills/commit/reference/task-status-update.en.md +6 -6
  136. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +6 -6
  137. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +142 -0
  138. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +142 -0
  139. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +49 -0
  140. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +49 -0
  141. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +48 -0
  142. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +48 -0
  143. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +60 -0
  144. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +87 -0
  145. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
  148. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
  149. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  150. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  151. package/templates/.agents/skills/create-task/SKILL.en.md +6 -3
  152. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +9 -6
  153. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
  154. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -3
  155. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
  156. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
  157. package/templates/.agents/skills/import-issue/SKILL.en.md +2 -2
  158. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
  159. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
  160. package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -2
  161. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -3
  162. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +165 -41
  163. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  164. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  165. package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
  166. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
  167. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  168. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  169. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
  170. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
  171. package/templates/.agents/skills/review-code/SKILL.en.md +8 -4
  172. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +6 -3
  173. package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
  174. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
  175. package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -5
  176. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -5
  177. package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -2
  178. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -2
  179. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  180. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  181. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
  182. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
  183. package/templates/.agents/skills/test/SKILL.zh-CN.md +26 -8
  184. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
  185. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +36 -6
  186. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  187. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  188. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +8 -1
  189. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +8 -1
  190. package/templates/.agents/templates/task.en.md +7 -0
  191. package/templates/.agents/templates/task.zh-CN.md +7 -0
  192. package/templates/.claude/commands/complete-manual-validation.en.md +9 -0
  193. package/templates/.claude/commands/complete-manual-validation.zh-CN.md +9 -0
  194. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +8 -0
  195. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +8 -0
  196. package/templates/.github/workflows/metadata-sync.yml +5 -0
  197. package/templates/.opencode/commands/complete-manual-validation.en.md +11 -0
  198. package/templates/.opencode/commands/complete-manual-validation.zh-CN.md +11 -0
@@ -0,0 +1,88 @@
1
+ import { stripVTControlCharacters } from 'node:util';
2
+ import { normalizeOutbound, outboundToText } from '../../display.js';
3
+ const FEISHU_LARK_MD_MAX_LENGTH = 20_000;
4
+ function templateForTone(tone) {
5
+ if (tone === 'success')
6
+ return 'green';
7
+ if (tone === 'warning')
8
+ return 'yellow';
9
+ if (tone === 'danger')
10
+ return 'red';
11
+ return 'blue';
12
+ }
13
+ function templateForResult(exitCode) {
14
+ return exitCode === 0 ? 'green' : 'red';
15
+ }
16
+ export function cleanFeishuText(text) {
17
+ return stripVTControlCharacters(text).replace(/\r\n/g, '\n');
18
+ }
19
+ function clampLarkMarkdown(text) {
20
+ const clean = cleanFeishuText(text);
21
+ if (clean.length <= FEISHU_LARK_MD_MAX_LENGTH)
22
+ return clean;
23
+ return `${clean.slice(0, FEISHU_LARK_MD_MAX_LENGTH - 18)}\n\n...(truncated)`;
24
+ }
25
+ function div(content) {
26
+ return { tag: 'div', text: { tag: 'lark_md', content: clampLarkMarkdown(content) } };
27
+ }
28
+ function fields(rows) {
29
+ return {
30
+ tag: 'div',
31
+ fields: rows.map(([label, value]) => ({
32
+ is_short: true,
33
+ text: { tag: 'lark_md', content: clampLarkMarkdown(`**${label}**\n${value}`) }
34
+ }))
35
+ };
36
+ }
37
+ function card(title, template, elements) {
38
+ return {
39
+ msg_type: 'interactive',
40
+ content: JSON.stringify({
41
+ config: { wide_screen_mode: true },
42
+ header: { title: { tag: 'plain_text', content: cleanFeishuText(title) }, template },
43
+ elements
44
+ })
45
+ };
46
+ }
47
+ function tableMarkdown(message) {
48
+ return outboundToText(message);
49
+ }
50
+ function renderStatus(message) {
51
+ const elements = [];
52
+ if (message.fields && message.fields.length > 0)
53
+ elements.push(fields(message.fields));
54
+ if (message.body)
55
+ elements.push(div(message.body));
56
+ if (elements.length === 0)
57
+ elements.push(div(message.title));
58
+ return card(message.title, templateForTone(message.tone), elements);
59
+ }
60
+ function renderStream(message) {
61
+ const template = message.phase === 'finished' ? templateForResult(message.exitCode ?? null) : 'blue';
62
+ return card(message.title, template, [div(outboundToText(message))]);
63
+ }
64
+ function renderCommandResult(message) {
65
+ return card(message.title, templateForResult(message.exitCode), [div(outboundToText(message))]);
66
+ }
67
+ function renderUnknownDisplayKind(message) {
68
+ return card('agent-infra', 'blue', [div(`[unknown display kind: ${String(message.kind)}]`)]);
69
+ }
70
+ export function renderFeishuMessage(message) {
71
+ const normalized = normalizeOutbound(message);
72
+ switch (normalized.kind) {
73
+ case 'text':
74
+ return card('agent-infra', 'blue', [div(normalized.text)]);
75
+ case 'markdown':
76
+ return card('agent-infra', 'blue', [div(normalized.markdown)]);
77
+ case 'table':
78
+ return card(normalized.title ?? 'agent-infra', 'blue', [div(tableMarkdown(normalized))]);
79
+ case 'status-card':
80
+ return renderStatus(normalized);
81
+ case 'stream-event':
82
+ return renderStream(normalized);
83
+ case 'command-result':
84
+ return renderCommandResult(normalized);
85
+ }
86
+ return renderUnknownDisplayKind(normalized);
87
+ }
88
+ //# sourceMappingURL=renderer.js.map
@@ -1,5 +1,4 @@
1
1
  import * as lark from '@larksuiteoapi/node-sdk';
2
- import { stripVTControlCharacters } from 'node:util';
3
2
  function asRecord(value) {
4
3
  return typeof value === 'object' && value !== null ? value : {};
5
4
  }
@@ -41,21 +40,11 @@ export function normalizeMessage(event) {
41
40
  function resolveDomain(value) {
42
41
  return value === 'lark' || value === 'Lark' ? lark.Domain.Lark : lark.Domain.Feishu;
43
42
  }
44
- export function cleanFeishuText(text) {
45
- return stripVTControlCharacters(text).replace(/\r\n/g, '\n');
46
- }
47
- export function cardMessage(text) {
48
- return { kind: 'interactive', title: 'agent-infra', text: cleanFeishuText(text) };
49
- }
50
43
  export function toFeishuCreateData(chatId, message) {
51
44
  return {
52
45
  receive_id: chatId,
53
- msg_type: 'interactive',
54
- content: JSON.stringify({
55
- config: { wide_screen_mode: true },
56
- header: { title: { tag: 'plain_text', content: message.title }, template: 'blue' },
57
- elements: [{ tag: 'div', text: { tag: 'lark_md', content: message.text } }]
58
- })
46
+ msg_type: message.msg_type,
47
+ content: message.content
59
48
  };
60
49
  }
61
50
  // Build the real SDK-backed transport from adapter config. appId/appSecret come
@@ -1,14 +1,69 @@
1
- import { VERSION } from "../version.js";
2
- import { loadServerConfig } from "./config.js";
3
- import { createLogger } from "./logger.js";
4
- import { loadAdapters, unloadAdapters } from "./plugin-loader.js";
5
- import { authorize } from "./auth.js";
6
- import { commandHelp, parseCommand } from "./protocol.js";
7
- import { runAi } from "./runner.js";
8
- import { streamCommand } from "./streamer.js";
1
+ import { VERSION } from '../version.js';
2
+ import { loadServerConfig } from './config.js';
3
+ import { createLogger } from './logger.js';
4
+ import { loadAdapters, unloadAdapters } from './plugin-loader.js';
5
+ import { authorize } from './auth.js';
6
+ import { commandHelp, parseCommand } from './protocol.js';
7
+ import { runAi } from './runner.js';
8
+ import { streamCommand } from './streamer.js';
9
+ import { markdownMessage, replyOutbound, textMessage } from './display.js';
10
+ import { buildStatusModel, statusModelToDisplay } from '../task/commands/status.js';
9
11
  function errorMessage(error) {
10
12
  return error instanceof Error ? error.message : String(error);
11
13
  }
14
+ function isTaskStatus(argv) {
15
+ return argv[0] === 'task' && argv[1] === 'status' && typeof argv[2] === 'string';
16
+ }
17
+ export function createMessageDispatcher(options) {
18
+ const runAiImpl = options.runAi ?? runAi;
19
+ const buildStatusModelImpl = options.buildStatusModel ?? buildStatusModel;
20
+ const statusModelToDisplayImpl = options.statusModelToDisplay ?? statusModelToDisplay;
21
+ return async (message) => {
22
+ const plan = parseCommand(message.text);
23
+ if (plan.kind === 'ignore')
24
+ return;
25
+ if (plan.kind === 'error') {
26
+ await replyOutbound(message, textMessage(plan.message));
27
+ options.logger.info(`command rejected from ${message.adapter}:${message.userId}: ${plan.message}`);
28
+ return;
29
+ }
30
+ if (plan.kind === 'builtin' && plan.name === 'ping') {
31
+ await replyOutbound(message, textMessage(`pong ${VERSION}`));
32
+ return;
33
+ }
34
+ if (plan.kind === 'builtin' && plan.name === 'help') {
35
+ await replyOutbound(message, markdownMessage(commandHelp()));
36
+ return;
37
+ }
38
+ if (plan.kind === 'builtin' && plan.name === 'version') {
39
+ await replyOutbound(message, textMessage(`agent-infra ${VERSION}`));
40
+ return;
41
+ }
42
+ if (plan.kind === 'ai') {
43
+ const allowed = authorize({ adapter: message.adapter, userId: message.userId }, plan.role, options.config.auth);
44
+ if (!allowed.ok) {
45
+ await replyOutbound(message, textMessage(allowed.message));
46
+ options.logger.info(`unauthorized command from ${message.adapter}:${message.userId}: ${allowed.message}`);
47
+ return;
48
+ }
49
+ if (isTaskStatus(plan.argv)) {
50
+ try {
51
+ await replyOutbound(message, statusModelToDisplayImpl(buildStatusModelImpl(plan.argv[2])));
52
+ return;
53
+ }
54
+ catch {
55
+ // Fall back to the existing CLI streaming path. This preserves the
56
+ // old behavior for invalid refs and any status-model collection error.
57
+ }
58
+ }
59
+ await streamCommand({
60
+ title: `ai ${plan.argv.join(' ')}`,
61
+ chunkChars: typeof options.config.stream?.chunkChars === 'number' ? options.config.stream.chunkChars : 4000,
62
+ throttleMs: typeof options.config.stream?.throttleMs === 'number' ? options.config.stream.throttleMs : 1500
63
+ }, (emit) => runAiImpl(plan.argv, { onChunk: emit }), (outbound) => replyOutbound(message, outbound));
64
+ }
65
+ };
66
+ }
12
67
  // The daemon main loop. Runs in the detached child spawned by
13
68
  // process-control.start(), or in the foreground for debugging.
14
69
  //
@@ -35,41 +90,7 @@ export async function runDaemon() {
35
90
  const shutdown = new Promise((resolve) => {
36
91
  resolveShutdown = resolve;
37
92
  });
38
- const dispatch = async (message) => {
39
- const plan = parseCommand(message.text);
40
- if (plan.kind === 'ignore')
41
- return;
42
- if (plan.kind === 'error') {
43
- await message.reply(plan.message);
44
- logger.info(`command rejected from ${message.adapter}:${message.userId}: ${plan.message}`);
45
- return;
46
- }
47
- if (plan.kind === 'builtin' && plan.name === 'ping') {
48
- await message.reply(`pong ${VERSION}`);
49
- return;
50
- }
51
- if (plan.kind === 'builtin' && plan.name === 'help') {
52
- await message.reply(commandHelp());
53
- return;
54
- }
55
- if (plan.kind === 'builtin' && plan.name === 'version') {
56
- await message.reply(`agent-infra ${VERSION}`);
57
- return;
58
- }
59
- if (plan.kind === 'ai') {
60
- const allowed = authorize({ adapter: message.adapter, userId: message.userId }, plan.role, config.auth);
61
- if (!allowed.ok) {
62
- await message.reply(allowed.message);
63
- logger.info(`unauthorized command from ${message.adapter}:${message.userId}: ${allowed.message}`);
64
- return;
65
- }
66
- await streamCommand({
67
- title: `ai ${plan.argv.join(' ')}`,
68
- chunkChars: typeof config.stream?.chunkChars === 'number' ? config.stream.chunkChars : 4000,
69
- throttleMs: typeof config.stream?.throttleMs === 'number' ? config.stream.throttleMs : 1500
70
- }, (emit) => runAi(plan.argv, { onChunk: emit }), (text) => message.reply(text));
71
- }
72
- };
93
+ const dispatch = createMessageDispatcher({ config, logger });
73
94
  const ctx = { config, logger, dispatch, signal: abortController.signal };
74
95
  const adapters = await loadAdapters(config, ctx);
75
96
  logger.ok(`loaded ${adapters.length} adapter(s)`);
@@ -0,0 +1,83 @@
1
+ export function textMessage(text) {
2
+ return { kind: 'text', text };
3
+ }
4
+ export function markdownMessage(markdown) {
5
+ return { kind: 'markdown', markdown };
6
+ }
7
+ export function tableMessage(columns, rows, title) {
8
+ return { kind: 'table', title, columns, rows };
9
+ }
10
+ export function statusCard(title, tone, fields, body) {
11
+ return { kind: 'status-card', title, tone, fields, body };
12
+ }
13
+ export function streamEvent(title, phase, text, exitCode, signal) {
14
+ const message = { kind: 'stream-event', title, phase };
15
+ if (text !== undefined)
16
+ message.text = text;
17
+ if (exitCode !== undefined)
18
+ message.exitCode = exitCode;
19
+ if (signal !== undefined)
20
+ message.signal = signal;
21
+ return message;
22
+ }
23
+ export function commandResult(title, exitCode, signal, stdout, stderr) {
24
+ return { kind: 'command-result', title, exitCode, signal, stdout, stderr };
25
+ }
26
+ export function normalizeOutbound(message) {
27
+ return typeof message === 'string' ? textMessage(message) : message;
28
+ }
29
+ function tableToText(message) {
30
+ const lines = [];
31
+ if (message.title)
32
+ lines.push(message.title);
33
+ lines.push(message.columns.join(' | '));
34
+ lines.push(message.columns.map(() => '---').join(' | '));
35
+ for (const row of message.rows) {
36
+ lines.push(row.join(' | '));
37
+ }
38
+ return lines.join('\n');
39
+ }
40
+ function compact(lines) {
41
+ return lines.filter((line) => typeof line === 'string' && line !== '').join('\n');
42
+ }
43
+ function unknownDisplayKind(message) {
44
+ return `[unknown: ${String(message.kind)}]`;
45
+ }
46
+ export function outboundToText(message) {
47
+ const normalized = normalizeOutbound(message);
48
+ switch (normalized.kind) {
49
+ case 'text':
50
+ return normalized.text;
51
+ case 'markdown':
52
+ return normalized.markdown;
53
+ case 'table':
54
+ return tableToText(normalized);
55
+ case 'status-card':
56
+ return compact([
57
+ normalized.title,
58
+ ...(normalized.fields ?? []).map(([label, value]) => `${label}: ${value}`),
59
+ normalized.body
60
+ ]);
61
+ case 'stream-event':
62
+ if (normalized.phase === 'started')
63
+ return `started ${normalized.title}`;
64
+ if (normalized.phase === 'chunk')
65
+ return normalized.text ?? '';
66
+ return `finished ${normalized.title} exitCode=${normalized.exitCode ?? 'null'} signal=${normalized.signal ?? 'null'}`;
67
+ case 'command-result':
68
+ return compact([
69
+ `finished ${normalized.title} exitCode=${normalized.exitCode ?? 'null'} signal=${normalized.signal ?? 'null'}`,
70
+ normalized.stdout,
71
+ normalized.stderr
72
+ ]);
73
+ }
74
+ return unknownDisplayKind(normalized);
75
+ }
76
+ export async function replyOutbound(inbound, message) {
77
+ if (inbound.replyDisplay) {
78
+ await inbound.replyDisplay(message);
79
+ return;
80
+ }
81
+ await inbound.reply(outboundToText(message));
82
+ }
83
+ //# sourceMappingURL=display.js.map
@@ -1,5 +1,5 @@
1
- import { runDaemon } from "./daemon.js";
2
- import { start, stop, status, logs } from "./process-control.js";
1
+ import { runDaemon } from './daemon.js';
2
+ import { start, stop, status, logs } from './process-control.js';
3
3
  const USAGE = `Usage: ai server <command> [options]
4
4
 
5
5
  Commands:
@@ -1,8 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { spawn, execFileSync } from 'node:child_process';
4
- import { loadServerConfig } from "./config.js";
5
- import { runDaemon } from "./daemon.js";
4
+ import { loadServerConfig } from './config.js';
5
+ import { runDaemon } from './daemon.js';
6
6
  export function buildStopCommand(pid, platform) {
7
7
  if (platform === 'win32') {
8
8
  return { kind: 'exec', command: 'taskkill', args: ['/PID', String(pid), '/T', '/F'] };
@@ -1,5 +1,5 @@
1
- import { VERSION } from "../version.js";
2
- import { getSkillRunSpec } from "../run/skills.js";
1
+ import { VERSION } from '../version.js';
2
+ import { getSkillRunSpec } from '../run/skills.js';
3
3
  const TASK_READ = new Set(['ls', 'list', 'status', 'show', 'log', 'decisions']);
4
4
  const SANDBOX_READ = new Set(['ls', 'list', 'show']);
5
5
  const SANDBOX_WRITE = new Set(['create', 'start']);
@@ -1,4 +1,5 @@
1
- import { redactSecrets } from "./redact.js";
1
+ import { streamEvent } from './display.js';
2
+ import { redactSecrets } from './redact.js';
2
3
  function chunks(text, size) {
3
4
  if (text.length === 0)
4
5
  return [];
@@ -9,7 +10,7 @@ function chunks(text, size) {
9
10
  return out;
10
11
  }
11
12
  export async function streamCommand(options, run, send) {
12
- await send(`started ${options.title}`);
13
+ await send(streamEvent(options.title, 'started'));
13
14
  const size = options.chunkChars ?? 4000;
14
15
  const throttleMs = options.throttleMs ?? 0;
15
16
  let streamed = false;
@@ -22,7 +23,7 @@ export async function streamCommand(options, run, send) {
22
23
  buffer = '';
23
24
  lastFlush = Date.now();
24
25
  for (const chunk of chunks(text, size)) {
25
- await send(chunk);
26
+ await send(streamEvent(options.title, 'chunk', chunk));
26
27
  }
27
28
  };
28
29
  const emit = async (chunk) => {
@@ -38,7 +39,7 @@ export async function streamCommand(options, run, send) {
38
39
  buffer += [result.stdout, result.stderr].filter(Boolean).join('\n');
39
40
  }
40
41
  await flush();
41
- await send(`finished ${options.title} exitCode=${result.exitCode} signal=${result.signal ?? 'null'}`);
42
+ await send(streamEvent(options.title, 'finished', undefined, result.exitCode, result.signal));
42
43
  return result;
43
44
  }
44
45
  //# sourceMappingURL=streamer.js.map
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
- import { resolveTaskRef } from "../resolve-ref.js";
3
- import { resolveArtifact } from "../artifacts.js";
2
+ import { resolveTaskRef } from '../resolve-ref.js';
3
+ import { resolveArtifact } from '../artifacts.js';
4
4
  const USAGE = `Usage: ai task cat <N | #N | TASK-id> <artifact | N>
5
5
 
6
6
  Prints a task artifact's raw content to stdout.
@@ -1,9 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { formatTable } from "../../table.js";
4
- import { resolveTaskRef } from "../resolve-ref.js";
5
- import { parseLedger, HUMAN_DECISION_STATUSES } from "../ledger.js";
6
- import { extractSubSection } from "../sections.js";
3
+ import { formatTable } from '../../table.js';
4
+ import { resolveTaskRef } from '../resolve-ref.js';
5
+ import { parseLedger, HUMAN_DECISION_STATUSES } from '../ledger.js';
6
+ import { extractSubSection } from '../sections.js';
7
7
  const USAGE = `Usage: ai task decisions <N | #N | TASK-id> [selector] [options]
8
8
 
9
9
  Lists the human-decision (HD-) items recorded in a task's review disagreement
@@ -1,6 +1,6 @@
1
- import { formatTable } from "../../table.js";
2
- import { resolveTaskRef } from "../resolve-ref.js";
3
- import { enumerateArtifacts } from "../artifacts.js";
1
+ import { formatTable } from '../../table.js';
2
+ import { resolveTaskRef } from '../resolve-ref.js';
3
+ import { enumerateArtifacts } from '../artifacts.js';
4
4
  const USAGE = `Usage: ai task files <N | #N | TASK-id>
5
5
 
6
6
  Lists the artifacts in a task directory with stable numbers.
@@ -1,8 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { resolveTaskRef, detectRepoRoot, enumerateTaskDirs } from "../resolve-ref.js";
4
- import { enumerateArtifacts, resolveArtifact } from "../artifacts.js";
5
- import { loadShortIdByTaskId } from "../short-id.js";
3
+ import { resolveTaskRef, detectRepoRoot, enumerateTaskDirs } from '../resolve-ref.js';
4
+ import { enumerateArtifacts, resolveArtifact } from '../artifacts.js';
5
+ import { loadShortIdByTaskId } from '../short-id.js';
6
6
  const USAGE = `Usage: ai task grep <pattern> [ref] [artifact | N]
7
7
 
8
8
  Literal (non-regex) line search across task artifacts.
@@ -1,8 +1,8 @@
1
1
  import fs from 'node:fs';
2
- import { resolveTaskRef } from "../resolve-ref.js";
3
- import { extractTitle } from "../frontmatter.js";
4
- import { extractSection, findSectionHeading } from "../sections.js";
5
- import { renderTemplateBody, PLACEHOLDER } from "../issue-form.js";
2
+ import { resolveTaskRef } from '../resolve-ref.js';
3
+ import { extractTitle } from '../frontmatter.js';
4
+ import { extractSection, findSectionHeading } from '../sections.js';
5
+ import { renderTemplateBody, PLACEHOLDER } from '../issue-form.js';
6
6
  const USAGE = `Usage: ai task issue-body <N | #N | TASK-id> [--template <path>]
7
7
 
8
8
  Print a deterministic Issue body extracted from a task's task.md.
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
- import { formatTable } from "../../table.js";
3
- import { resolveTaskRef } from "../resolve-ref.js";
4
- import { parseLedger, HUMAN_DECISION_STATUSES } from "../ledger.js";
2
+ import { formatTable } from '../../table.js';
3
+ import { resolveTaskRef } from '../resolve-ref.js';
4
+ import { parseLedger } from '../ledger.js';
5
5
  const USAGE = `Usage: ai task log <N | #N | TASK-id>
6
6
 
7
7
  Renders a task's activity log as a per-step status table. A step's start and
@@ -13,7 +13,7 @@ Columns: # (row) / STEP / AGENT / STARTED / DONE / NOTE
13
13
  A human-executed step shows AGENT as 'human' and, when it has no start marker,
14
14
  a '-' STARTED placeholder. Review-step NOTE also carries two human counts in
15
15
  the verdict list, right after blockers/major/minor: manual-validation
16
- and human-decision (current ledger stage total).
16
+ and human-decision (current pending ledger stage total).
17
17
  `;
18
18
  const TABLE_HEADERS = ['#', 'STEP', 'AGENT', 'STARTED', 'DONE', 'NOTE'];
19
19
  // The activity-log H2 heading is language-dependent (zh template / en template).
@@ -100,7 +100,7 @@ function pairEntries(entries) {
100
100
  function countHumanDecisionsByStage(rows) {
101
101
  const counts = new Map();
102
102
  for (const row of rows) {
103
- if (!isReviewStage(row.stage) || !HUMAN_DECISION_STATUSES.has(row.status))
103
+ if (!isReviewStage(row.stage) || row.status !== 'needs-human-decision')
104
104
  continue;
105
105
  counts.set(row.stage, (counts.get(row.stage) ?? 0) + 1);
106
106
  }
@@ -1,9 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
- import { formatTable } from "../../table.js";
5
- import { parseTaskFrontmatter, extractTitle } from "../frontmatter.js";
6
- import { loadShortIdByTaskId } from "../short-id.js";
4
+ import { formatTable } from '../../table.js';
5
+ import { parseTaskFrontmatter, extractTitle } from '../frontmatter.js';
6
+ import { loadShortIdByTaskId } from '../short-id.js';
7
7
  const USAGE = `Usage: ai task ls [--all | --blocked | --completed]
8
8
 
9
9
  Lists tasks under .agents/workspace/. Defaults to active tasks only.
@@ -1,5 +1,5 @@
1
1
  import fs from 'node:fs';
2
- import { resolveTaskRef } from "../resolve-ref.js";
2
+ import { resolveTaskRef } from '../resolve-ref.js';
3
3
  const USAGE = `Usage: ai task show <N | #N | TASK-id>
4
4
 
5
5
  Prints the task.md content for the matching task.
@@ -1,12 +1,14 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
- import { commandForEngine } from "../../sandbox/shell.js";
5
- import { resolveTaskRef } from "../resolve-ref.js";
6
- import { enumerateArtifacts } from "../artifacts.js";
7
- import { parseTaskFrontmatter, extractTitle } from "../frontmatter.js";
8
- import { loadShortIdByTaskId } from "../short-id.js";
9
- import { parseActivityLog, pairEntries } from "./log.js";
4
+ import { commandForEngine } from '../../sandbox/shell.js';
5
+ import { resolveTaskRef } from '../resolve-ref.js';
6
+ import { enumerateArtifacts } from '../artifacts.js';
7
+ import { parseTaskFrontmatter, extractTitle } from '../frontmatter.js';
8
+ import { loadShortIdByTaskId } from '../short-id.js';
9
+ import { getOpenWorkflowWarnings, formatWorkflowWarningSummary } from '../workflow-warnings.js';
10
+ import { parseActivityLog, pairEntries } from './log.js';
11
+ import { statusCard } from '../../server/display.js';
10
12
  const USAGE = `Usage: ai task status <N | #N | TASK-id>
11
13
 
12
14
  Prints an aggregated "health check" view for a task: header, metadata,
@@ -267,6 +269,9 @@ function renderStatus(model) {
267
269
  if (model.title)
268
270
  lines.push(model.title);
269
271
  lines.push('', 'Metadata', ...renderPairs(model.metadata));
272
+ if (model.workflowWarnings.length > 0) {
273
+ lines.push('', `Workflow Warnings (${model.workflowWarnings.length} open)`, ...formatWorkflowWarningSummary(model.workflowWarnings).map((line) => ` ${line}`));
274
+ }
270
275
  lines.push('', `Artifacts (${model.artifacts.count})`);
271
276
  if (model.artifacts.groups.length === 0) {
272
277
  lines.push(' (none)');
@@ -302,6 +307,59 @@ function renderStatus(model) {
302
307
  ]));
303
308
  return lines;
304
309
  }
310
+ function modelTone(model) {
311
+ if (model.workflow.state === 'in-progress')
312
+ return model.workflow.stale === 'yes' ? 'warning' : 'running';
313
+ if (model.metadata.some(([key, value]) => key === 'status' && value === 'completed'))
314
+ return 'success';
315
+ if (model.metadata.some(([key, value]) => key === 'status' && (value === 'blocked' || value === 'cancelled'))) {
316
+ return 'danger';
317
+ }
318
+ return 'info';
319
+ }
320
+ function buildFromResolved(input) {
321
+ const content = fs.readFileSync(input.taskMdPath, 'utf8');
322
+ const fm = parseTaskFrontmatter(content);
323
+ const run = input.run ?? makeRunner(input.repoRoot);
324
+ const artifacts = enumerateArtifacts(input.taskDir);
325
+ const workflow = collectWorkflow(content, input.now);
326
+ return {
327
+ taskId: input.taskId,
328
+ shortId: input.shortId ?? loadShortIdByTaskId(input.repoRoot).get(input.taskId) ?? DASH,
329
+ title: extractTitle(content),
330
+ metadata: collectMetadata(fm),
331
+ workflowWarnings: getOpenWorkflowWarnings(content),
332
+ artifacts: { count: artifacts.length, groups: groupArtifacts(artifacts) },
333
+ workflow,
334
+ runtime: collectRuntime(input.taskDir, workflow, run),
335
+ git: collectGit(fm.branch ?? '', run)
336
+ };
337
+ }
338
+ function buildStatusModel(refOrInput, options = {}) {
339
+ if (typeof refOrInput !== 'string') {
340
+ return buildFromResolved(refOrInput);
341
+ }
342
+ const resolved = resolveTaskRef(refOrInput);
343
+ if (!resolved.ok) {
344
+ throw new Error(resolved.message);
345
+ }
346
+ return buildFromResolved({
347
+ taskId: resolved.taskId,
348
+ taskDir: resolved.taskDir,
349
+ taskMdPath: resolved.taskMdPath,
350
+ repoRoot: resolved.repoRoot,
351
+ run: options.run,
352
+ now: options.now
353
+ });
354
+ }
355
+ function statusModelToDisplay(model) {
356
+ return statusCard(`Task ${model.taskId} (${model.shortId})`, modelTone(model), [
357
+ ['workflow', model.workflow.state],
358
+ ['step', model.workflow.step],
359
+ ['runtime', model.runtime.status],
360
+ ['git', model.git.uncommitted]
361
+ ], renderStatus(model).join('\n'));
362
+ }
305
363
  function status(args = []) {
306
364
  if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
307
365
  process.stdout.write(USAGE);
@@ -309,30 +367,18 @@ function status(args = []) {
309
367
  process.exitCode = 1;
310
368
  return;
311
369
  }
312
- const resolved = resolveTaskRef(args[0]);
313
- if (!resolved.ok) {
314
- process.stderr.write(`ai task status: ${resolved.message}\n`);
370
+ let model;
371
+ try {
372
+ model = buildStatusModel(args[0]);
373
+ }
374
+ catch (error) {
375
+ process.stderr.write(`ai task status: ${error instanceof Error ? error.message : String(error)}\n`);
315
376
  process.exitCode = 1;
316
377
  return;
317
378
  }
318
- const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
319
- const fm = parseTaskFrontmatter(content);
320
- const run = makeRunner(resolved.repoRoot);
321
- const artifacts = enumerateArtifacts(resolved.taskDir);
322
- const workflow = collectWorkflow(content);
323
- const model = {
324
- taskId: resolved.taskId,
325
- shortId: loadShortIdByTaskId(resolved.repoRoot).get(resolved.taskId) ?? DASH,
326
- title: extractTitle(content),
327
- metadata: collectMetadata(fm),
328
- artifacts: { count: artifacts.length, groups: groupArtifacts(artifacts) },
329
- workflow,
330
- runtime: collectRuntime(resolved.taskDir, workflow, run),
331
- git: collectGit(fm.branch ?? '', run)
332
- };
333
379
  for (const line of renderStatus(model)) {
334
380
  process.stdout.write(`${line}\n`);
335
381
  }
336
382
  }
337
- export { status, makeRunner, collectMetadata, groupArtifacts, collectGit, collectWorkflow, collectRuntime, renderStatus, METADATA_KEYS };
383
+ export { status, makeRunner, buildStatusModel, collectMetadata, groupArtifacts, collectGit, collectWorkflow, collectRuntime, renderStatus, statusModelToDisplay, METADATA_KEYS };
338
384
  //# sourceMappingURL=status.js.map