@keyou007/agent-bot 0.1.15-alpha.0 → 0.1.15-alpha.1

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 (56) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +23 -20
  3. package/README.zh.md +23 -20
  4. package/config.example.yaml +1 -0
  5. package/dist/cli/FeishuAppConfiguration.js +42 -3
  6. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  7. package/dist/cli.js +4 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/feishu/CardRenderer.js +51 -8
  10. package/dist/feishu/CardRenderer.js.map +1 -1
  11. package/dist/feishu/FeishuConnector.js +17 -5
  12. package/dist/feishu/FeishuConnector.js.map +1 -1
  13. package/dist/feishu/FeishuMarkdown.js +58 -6
  14. package/dist/feishu/FeishuMarkdown.js.map +1 -1
  15. package/dist/feishu/FeishuMessageClient.js +33 -4
  16. package/dist/feishu/FeishuMessageClient.js.map +1 -1
  17. package/dist/feishu/FeishuTurnPresenter.js +7 -6
  18. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  19. package/dist/feishu/FinalResponseBranding.js +15 -0
  20. package/dist/feishu/FinalResponseBranding.js.map +1 -0
  21. package/dist/logging/DailyLogStream.js +73 -0
  22. package/dist/logging/DailyLogStream.js.map +1 -0
  23. package/dist/logging/logger.js +2 -1
  24. package/dist/logging/logger.js.map +1 -1
  25. package/dist/proxy/ProxySessionController.js +192 -36
  26. package/dist/proxy/ProxySessionController.js.map +1 -1
  27. package/dist/state/StateStore.js +126 -0
  28. package/dist/state/StateStore.js.map +1 -1
  29. package/dist/state/migrations.js +11 -0
  30. package/dist/state/migrations.js.map +1 -1
  31. package/dist/supervision/SupervisorDiagnostics.js +37 -21
  32. package/dist/supervision/SupervisorDiagnostics.js.map +1 -1
  33. package/dist/supervisor.js +11 -4
  34. package/dist/supervisor.js.map +1 -1
  35. package/dist/utils/executeShellCommand.js +83 -16
  36. package/dist/utils/executeShellCommand.js.map +1 -1
  37. package/docs/technical-reference.md +9 -8
  38. package/docs/technical-reference.zh.md +9 -8
  39. package/npm-shrinkwrap.json +2 -2
  40. package/package.json +1 -1
  41. package/src/cli/FeishuAppConfiguration.ts +76 -3
  42. package/src/cli.ts +6 -1
  43. package/src/feishu/CardRenderer.ts +75 -9
  44. package/src/feishu/FeishuConnector.ts +28 -6
  45. package/src/feishu/FeishuMarkdown.ts +64 -5
  46. package/src/feishu/FeishuMessageClient.ts +41 -12
  47. package/src/feishu/FeishuTurnPresenter.ts +9 -6
  48. package/src/feishu/FinalResponseBranding.ts +18 -0
  49. package/src/logging/DailyLogStream.ts +88 -0
  50. package/src/logging/logger.ts +2 -1
  51. package/src/proxy/ProxySessionController.ts +208 -35
  52. package/src/state/StateStore.ts +180 -0
  53. package/src/state/migrations.ts +11 -0
  54. package/src/supervision/SupervisorDiagnostics.ts +41 -25
  55. package/src/supervisor.ts +10 -4
  56. package/src/utils/executeShellCommand.ts +103 -15
@@ -18,8 +18,17 @@ export function executeShellCommand(command, cwd, options = {}) {
18
18
  timedOut = true;
19
19
  child.kill();
20
20
  }, timeoutMs);
21
- child.stdout.on("data", (chunk) => output.append("stdout", chunk));
22
- child.stderr.on("data", (chunk) => output.append("stderr", chunk));
21
+ const appendOutput = (stream, chunk) => {
22
+ output.append(stream, chunk);
23
+ try {
24
+ options.onOutput?.(output.result());
25
+ }
26
+ catch {
27
+ // Output presentation must never interrupt the child process.
28
+ }
29
+ };
30
+ child.stdout.on("data", (chunk) => appendOutput("stdout", chunk));
31
+ child.stderr.on("data", (chunk) => appendOutput("stderr", chunk));
23
32
  child.once("error", (error) => {
24
33
  clearTimeout(timeout);
25
34
  reject(error);
@@ -45,32 +54,90 @@ function prepareShellCommand(command, platform) {
45
54
  };
46
55
  }
47
56
  class BoundedOutput {
48
- maxBytes;
49
- stdout = [];
50
- stderr = [];
51
- retainedBytes = 0;
57
+ retained = [];
58
+ seenBytes = { stdout: 0, stderr: 0 };
59
+ headBytes;
60
+ tailBytes;
52
61
  truncated = false;
53
62
  constructor(maxBytes) {
54
- this.maxBytes = maxBytes;
63
+ const normalizedMaxBytes = Math.max(1, Math.floor(maxBytes));
64
+ this.headBytes = Math.ceil(normalizedMaxBytes / 3);
65
+ this.tailBytes = normalizedMaxBytes - this.headBytes;
55
66
  }
56
67
  append(stream, chunk) {
57
- const remaining = Math.max(0, this.maxBytes - this.retainedBytes);
58
- if (remaining === 0) {
68
+ if (chunk.length === 0)
69
+ return;
70
+ this.seenBytes[stream] += chunk.length;
71
+ if (!this.truncated) {
72
+ const next = [...this.retained, { stream, data: chunk }];
73
+ if (outputChunksLength(next) <= this.headBytes + this.tailBytes) {
74
+ this.retained = next;
75
+ return;
76
+ }
59
77
  this.truncated = true;
78
+ this.retained = [
79
+ ...takeOutputChunkHead(next, this.headBytes),
80
+ ...takeOutputChunkTail(next, this.tailBytes),
81
+ ];
60
82
  return;
61
83
  }
62
- const retained = chunk.length <= remaining ? chunk : chunk.subarray(0, remaining);
63
- this[stream].push(retained);
64
- this.retainedBytes += retained.length;
65
- if (retained.length < chunk.length)
66
- this.truncated = true;
84
+ const head = takeOutputChunkHead(this.retained, this.headBytes);
85
+ const previousTail = takeOutputChunkTail(this.retained, this.tailBytes);
86
+ const tail = takeOutputChunkTail([...previousTail, { stream, data: chunk }], this.tailBytes);
87
+ this.retained = [...head, ...tail];
67
88
  }
68
89
  result() {
69
90
  return {
70
- stdout: Buffer.concat(this.stdout).toString("utf8"),
71
- stderr: Buffer.concat(this.stderr).toString("utf8"),
91
+ stdout: this.streamResult("stdout"),
92
+ stderr: this.streamResult("stderr"),
72
93
  outputTruncated: this.truncated,
73
94
  };
74
95
  }
96
+ streamResult(stream) {
97
+ if (!this.truncated) {
98
+ return Buffer.concat(this.retained.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString("utf8");
99
+ }
100
+ const head = takeOutputChunkHead(this.retained, this.headBytes)
101
+ .filter((chunk) => chunk.stream === stream);
102
+ const tail = takeOutputChunkTail(this.retained, this.tailBytes)
103
+ .filter((chunk) => chunk.stream === stream);
104
+ const retainedBytes = outputChunksLength([...head, ...tail]);
105
+ const marker = this.seenBytes[stream] > retainedBytes
106
+ ? Buffer.from("\n... output truncated ...\n", "utf8")
107
+ : Buffer.alloc(0);
108
+ return Buffer.concat([
109
+ ...head.map((chunk) => chunk.data),
110
+ marker,
111
+ ...tail.map((chunk) => chunk.data),
112
+ ]).toString("utf8");
113
+ }
114
+ }
115
+ function outputChunksLength(chunks) {
116
+ return chunks.reduce((total, chunk) => total + chunk.data.length, 0);
117
+ }
118
+ function takeOutputChunkHead(chunks, maxBytes) {
119
+ const retained = [];
120
+ let remaining = maxBytes;
121
+ for (const chunk of chunks) {
122
+ if (remaining <= 0)
123
+ break;
124
+ const data = chunk.data.length <= remaining ? chunk.data : chunk.data.subarray(0, remaining);
125
+ retained.push({ stream: chunk.stream, data });
126
+ remaining -= data.length;
127
+ }
128
+ return retained;
129
+ }
130
+ function takeOutputChunkTail(chunks, maxBytes) {
131
+ const retained = [];
132
+ let remaining = maxBytes;
133
+ for (let index = chunks.length - 1; index >= 0 && remaining > 0; index -= 1) {
134
+ const chunk = chunks[index];
135
+ const data = chunk.data.length <= remaining
136
+ ? chunk.data
137
+ : chunk.data.subarray(chunk.data.length - remaining);
138
+ retained.unshift({ stream: chunk.stream, data });
139
+ remaining -= data.length;
140
+ }
141
+ return retained;
75
142
  }
76
143
  //# sourceMappingURL=executeShellCommand.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"executeShellCommand.js","sourceRoot":"","sources":["../../src/utils/executeShellCommand.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"executeShellCommand.js","sourceRoot":"","sources":["../../src/utils/executeShellCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,kBAAkB,GAAG,OAAO,CAAC;AACnC,MAAM,wBAAwB,GAAG,EAAE,GAAG,IAAI,CAAC;AAsB3C,MAAM,UAAU,mBAAmB,CACjC,OAAe,EACf,GAAW,EACX,OAAO,GAAwB,EAAE;IAEjC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAC1D,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;IAC1E,MAAM,QAAQ,GAAG,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEpF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE;YACnD,GAAG;YACH,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;YACjC,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,cAAc,CAAC,CAAC;QACjD,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,CAAC,IAAI,EAAE,CAAC;QACf,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,MAAM,YAAY,GAAG,CAAC,MAA2B,EAAE,KAAa,EAAQ,EAAE;YACxE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC7B,IAAI,CAAC;gBACH,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;YACtC,CAAC;YAAC,MAAM,CAAC;gBACP,8DAA8D;YAChE,CAAC;QACH,CAAC,CAAC;QACF,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QAC1E,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QAC1E,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC5B,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE;YAC/B,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,OAAO,CAAC;gBACN,GAAG,MAAM,CAAC,MAAM,EAAE;gBAClB,QAAQ;gBACR,QAAQ;aACT,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,mBAAmB,CAAC,OAAe,EAAE,QAAyB;IACrE,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;IAChF,MAAM,MAAM,GAAG,4GAA4G,OAAO,EAAE,CAAC;IACrI,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAClE,OAAO;QACL,OAAO,EAAE,gBAAgB;QACzB,IAAI,EAAE,CAAC,SAAS,EAAE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,CAAC;KACxG,CAAC;AACJ,CAAC;AAED,MAAM,aAAa;IACT,QAAQ,GAAkB,EAAE,CAAC;IACpB,SAAS,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACrC,SAAS,CAAS;IAClB,SAAS,CAAS;IAC3B,SAAS,GAAG,KAAK,CAAC;IAE1B,YAAY,QAAgB;QAC1B,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,SAAS,GAAG,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC;IACvD,CAAC;IAED,MAAM,CAAC,MAA2B,EAAE,KAAa;QAC/C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC;QAEvC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACzD,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;gBAChE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,IAAI,CAAC,QAAQ,GAAG;gBACd,GAAG,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBAC5C,GAAG,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;aAC7C,CAAC;YACF,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAChE,MAAM,YAAY,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACxE,MAAM,IAAI,GAAG,mBAAmB,CAAC,CAAC,GAAG,YAAY,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7F,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,MAAM;QACJ,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;YACnC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;YACnC,eAAe,EAAE,IAAI,CAAC,SAAS;SAChC,CAAC;IACJ,CAAC;IAEO,YAAY,CAAC,MAA2B;QAC9C,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO,MAAM,CAAC,MAAM,CAClB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CACpF,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC;aAC5D,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC;aAC5D,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QAE9C,MAAM,aAAa,GAAG,kBAAkB,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,aAAa;YACnD,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE,MAAM,CAAC;YACrD,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACpB,OAAO,MAAM,CAAC,MAAM,CAAC;YACnB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;YAClC,MAAM;YACN,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SACnC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;CACF;AAOD,SAAS,kBAAkB,CAAC,MAAqB;IAC/C,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAqB,EAAE,QAAgB;IAClE,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,IAAI,SAAS,GAAG,QAAQ,CAAC;IACzB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,SAAS,IAAI,CAAC;YAAE,MAAM;QAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAC7F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC;IAC3B,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAqB,EAAE,QAAgB;IAClE,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,IAAI,SAAS,GAAG,QAAQ,CAAC;IACzB,KAAK,IAAI,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,SAAS,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC5E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,SAAS;YACzC,CAAC,CAAC,KAAK,CAAC,IAAI;YACZ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;QACvD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC;IAC3B,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -43,7 +43,7 @@ The CLI reads the system locale through Node.js internationalization support. Lo
43
43
  | `~/.agent-bot/.env` | Feishu credentials |
44
44
  | `~/.agent-bot/data/agent-bot.sqlite` | Persistent task and delivery state |
45
45
  | `~/.agent-bot/data/inbound-images/` | Cached incoming images |
46
- | `~/.agent-bot/logs/agent-bot.log` | Structured runtime logs |
46
+ | `~/.agent-bot/logs/agent-bot.YYYY-MM-DD.log` | Daily structured runtime logs |
47
47
 
48
48
  Configuration path precedence:
49
49
 
@@ -84,18 +84,19 @@ Initialization holds `~/.agent-bot/init.lock` so concurrent commands cannot crea
84
84
 
85
85
  When complete credentials are absent, initialization uses Feishu one-click registration and reports its verification URL as text and a QR code. In-progress registration codes are not resumed: if the process exits before the complete credential pair is persisted, the next run starts a new app registration. After credentials are persisted, initialization audits the currently published app version and can safely resume that audit after interruption.
86
86
 
87
- Missing app configuration is handled in two stages:
87
+ Missing app configuration is handled in three stages:
88
88
 
89
- 1. Core configuration is requested and polled until it becomes available. When all-group-message mode was selected, the manually configured `im:message.group_msg` scope is included and may be explicitly skipped with `Y`; mention-only mode excludes it entirely.
89
+ 1. Core configuration is requested and polled until basic messaging becomes available.
90
90
  2. Remaining optional configuration is requested. The CLI prints its QR code followed by the authorization URL, then immediately polls for up to five minutes. An interactive terminal offers only `Y` to skip optional authorization and continue; otherwise the user completes authorization in the browser while polling continues.
91
+ 3. Only in all-group-message mode, the manually configured and published `im:message.group_msg` scope is requested last. Entering `Y` skips this step, and a timeout returns a partial result instead of failing initialization. Mention-only mode excludes the permission entirely.
91
92
 
92
- Configuration supported by the one-click launcher is encoded in its `addons` manifest. When all-group-message mode is selected, the core `im:message.group_msg` scope is excluded from that manifest because Feishu does not add it through one-click configuration. Instead, the CLI prints a QR code and this app-specific, pre-filtered Developer Console URL:
93
+ Configuration supported by the one-click launcher is encoded in its `addons` manifest. When all-group-message mode is selected, the manual `im:message.group_msg` scope is excluded from that manifest because Feishu does not add it through one-click configuration. Instead, the CLI prints a QR code and this app-specific, pre-filtered Developer Console URL during the final stage:
93
94
 
94
95
  ```text
95
96
  https://open.feishu.cn/app/<appId>/auth?q=im%3Amessage.group_msg&op_from=openapi&token_type=tenant
96
97
  ```
97
98
 
98
- The user must add the permission, publish the app version, and complete tenant approval when required. Core polling then detects the permission in the published version before initialization continues. In an interactive terminal, entering `Y` skips only this manual permission wait; other missing core scopes and the message event remain blocking. Initialization returns a partial configuration result and warns that ordinary group messages which do not mention the bot are unavailable.
99
+ The user must add the permission, publish the app version, and complete tenant approval when required. The final stage waits for up to five minutes for the permission to appear in the published version. Entering `Y`, reaching the timeout, or leaving the version unpublished returns a partial configuration result and warns that ordinary group messages which do not mention the bot are unavailable.
99
100
 
100
101
  Optional polling failures and timeouts return a partial configuration result instead of failing initialization. When stdin is not an interactive terminal, no skip input is available and polling continues until configuration becomes active or times out. Verification URLs and prompts use stderr, so `--json` keeps its final stdout machine-readable.
101
102
 
@@ -223,7 +224,7 @@ The message route determines the current task:
223
224
 
224
225
  Commands, card callbacks, progress cards, and final responses stay on the originating route.
225
226
 
226
- Starting a Feishu thread from a mapped user message, progress card, or final response forks from the associated completed App Server turn. If no reliable completed source turn exists, the operation fails instead of selecting an arbitrary point.
227
+ Starting a Feishu thread from a mapped user message, progress card, or final response does not immediately create a task. Slash commands execute while the thread remains unbound; commands that require a current task return binding guidance instead of targeting the parent conversation. The first ordinary Prompt lazily forks from the associated completed App Server turn. If no reliable source turn exists, it starts a fresh task. `/new` also starts a fresh task directly, and `/sessions` or `/switch` can bind an existing task without creating an intermediate fork.
227
228
 
228
229
  `/forkgroup` has thread-aware source selection. An unbound thread, or a bound thread task with no completed turn of its own, forks directly from the thread's original anchor turn without creating an intermediate thread task. Once the thread task has completed a turn, `/forkgroup` uses its latest locally persisted completed turn. A newer active turn does not block the command and is excluded from the fork point.
229
230
 
@@ -303,8 +304,8 @@ On Windows, the CLI refreshes Machine and User environment variables before the
303
304
 
304
305
  Supervisor crash diagnostics are isolated to the selected profile:
305
306
 
306
- - `logs/supervisor.log` permanently records worker PIDs, exit codes, uptime, restart delays, and diagnostic artifact paths.
307
- - `logs/worker.stderr.log` captures Node/V8 fatal output that would otherwise be lost by the detached process. Both diagnostic logs rotate at 10 MiB with three backups.
307
+ - `logs/supervisor.YYYY-MM-DD.log` permanently records worker PIDs, exit codes, uptime, restart delays, and diagnostic artifact paths.
308
+ - `logs/worker.stderr.YYYY-MM-DD.log` captures Node/V8 fatal output that would otherwise be lost by the detached process. Both diagnostic logs are split by local calendar day and retain the existing 10 MiB backup rotation within a day.
308
309
  - `data/last-crash.json` points to the latest unexpected worker exit, while timestamped `data/crash-reports/crash-*.json` files preserve the history.
309
310
  - `data/crash-reports/report.*.json` contains Node diagnostic reports when Node can generate one.
310
311
 
@@ -43,7 +43,7 @@ CLI 通过 Node.js 国际化能力读取系统 Locale。以 `zh` 开头的 Local
43
43
  | `~/.agent-bot/.env` | 飞书凭据 |
44
44
  | `~/.agent-bot/data/agent-bot.sqlite` | 任务和投递持久化状态 |
45
45
  | `~/.agent-bot/data/inbound-images/` | 接收图片缓存 |
46
- | `~/.agent-bot/logs/agent-bot.log` | 结构化运行日志 |
46
+ | `~/.agent-bot/logs/agent-bot.YYYY-MM-DD.log` | 按天切分的结构化运行日志 |
47
47
 
48
48
  配置文件优先级:
49
49
 
@@ -84,18 +84,19 @@ CLI 通过 Node.js 国际化能力读取系统 Locale。以 `zh` 开头的 Local
84
84
 
85
85
  没有完整凭据时,初始化会启动飞书一键创建流程,并以文本和二维码显示验证链接。注册中的设备码不会恢复:如果进程在完整凭据持久化前退出,下次会重新创建应用。凭据持久化后会检查应用当前已发布版本;这一权限审计阶段中断后可以安全继续。
86
86
 
87
- 缺失配置分两阶段处理:
87
+ 缺失配置分三阶段处理:
88
88
 
89
- 1. 请求核心配置并轮询,直到基础能力生效。选择接收所有群消息时会包含需要手动配置的 `im:message.group_msg`,并允许输入 `Y` 显式跳过;仅 @ 响应模式会完全排除该权限
89
+ 1. 请求核心配置并轮询,直到基础消息能力生效
90
90
  2. 请求其余可选配置。CLI 会先显示二维码,随后显示授权链接,然后立即开始最多 5 分钟的轮询。交互式终端只提供 `Y` 选项,用于跳过可选授权并继续;否则用户直接在浏览器中完成授权,轮询会持续进行
91
+ 3. 仅在选择接收所有群消息时,最后请求需要手动配置并发布的 `im:message.group_msg`。这一步可输入 `Y` 跳过,等待超时也只返回部分配置结果,不会让初始化失败;仅 @ 响应模式会完全排除该权限
91
92
 
92
- 一键配置支持的内容会编码到 launcher 的 `addons` 清单中。选择接收所有群消息时,核心权限 `im:message.group_msg` 不会放入该清单,因为飞书无法通过一键配置新增它。CLI 会改为显示二维码,以及当前应用已筛选该权限的开发者后台直达链接:
93
+ 一键配置支持的内容会编码到 launcher 的 `addons` 清单中。选择接收所有群消息时,手动权限 `im:message.group_msg` 不会放入该清单,因为飞书无法通过一键配置新增它。CLI 会在最后阶段显示二维码,以及当前应用已筛选该权限的开发者后台直达链接:
93
94
 
94
95
  ```text
95
96
  https://open.feishu.cn/app/<appId>/auth?q=im%3Amessage.group_msg&op_from=openapi&token_type=tenant
96
97
  ```
97
98
 
98
- 用户需要手动新增权限、发布应用版本,并在需要时完成租户管理员审批。核心轮询会在已发布版本中检测到该权限后继续初始化。交互式终端中输入 `Y` 只会跳过这个手动权限的等待,其他缺失的核心权限和消息事件仍会阻塞。初始化会返回部分配置结果,并提示机器人无法响应未 @ 它的普通群消息。
99
+ 用户需要手动新增权限、发布应用版本,并在需要时完成租户管理员审批。最后阶段会等待最多 5 分钟,在已发布版本中检测到该权限后完成;输入 `Y`、等待超时或没有完成发布都会返回部分配置结果,并提示机器人无法响应未 @ 它的普通群消息。
99
100
 
100
101
  可选配置轮询失败或超时会返回部分配置结果,不会导致初始化失败。stdin 不是交互式终端时无法输入跳过指令,轮询会持续到配置生效或超时。验证链接和提示写入 stderr,因此 `--json` 的最终 stdout 仍可直接供程序读取。
101
102
 
@@ -223,7 +224,7 @@ AGENT_BOT_LARK_USER_OPEN_ID=<已保存的授权用户 open_id,可用时提供>
223
224
 
224
225
  命令、卡片回调、进度卡和最终回答都保留在来源路由。
225
226
 
226
- 从已映射的用户消息、进度卡或最终回答创建飞书话题时,会从其关联的已完成 App Server turn 创建分支。无法可靠确定已完成来源 turn 时会直接失败,不会选择任意分支点。
227
+ 从已映射的用户消息、进度卡或最终回答创建飞书话题时,不会立即创建任务。斜杠命令可以在话题保持未绑定状态时执行;依赖当前任务的命令会返回绑定指引,不会操作父会话任务。收到第一条普通 Prompt 后,才会从关联的已完成 App Server turn 延迟创建分支;无法可靠确定来源 turn 时则创建全新任务。`/new` 也会直接创建全新任务,`/sessions` 或 `/switch` 可以绑定现有任务,均不会产生中间分支。
227
228
 
228
229
  `/forkgroup` 会根据话题状态选择来源。话题尚未绑定任务,或者已绑定任务但尚未完成过自己的 turn 时,直接从话题原始锚点 turn fork,不创建中间话题任务。话题任务完成过 turn 后,使用本地持久化的最近完成 turn;更晚的执行中 turn 不会阻塞命令,也不会成为 fork 点。
229
230
 
@@ -303,8 +304,8 @@ Windows 下,CLI 在首次启动 Supervisor 前、Worker 在启动替换 Superv
303
304
 
304
305
  Supervisor 的崩溃诊断文件按当前 Profile 隔离:
305
306
 
306
- - `logs/supervisor.log` 持久记录 Worker PID、退出码、运行时长、重启延迟和诊断文件路径
307
- - `logs/worker.stderr.log` 保存后台进程原本会丢失的 Node/V8 fatal 输出;两类诊断日志达到 10 MiB 时轮转,保留三个备份
307
+ - `logs/supervisor.YYYY-MM-DD.log` 持久记录 Worker PID、退出码、运行时长、重启延迟和诊断文件路径
308
+ - `logs/worker.stderr.YYYY-MM-DD.log` 保存后台进程原本会丢失的 Node/V8 fatal 输出;两类诊断日志会按本地自然日切分,并在单日文件达到 10 MiB 时继续使用原有备份轮转
308
309
  - `data/last-crash.json` 指向最近一次 Worker 异常退出,带时间戳的 `data/crash-reports/crash-*.json` 保留历史记录
309
310
  - Node 能生成报告时,写入 `data/crash-reports/report.*.json`
310
311
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@keyou007/agent-bot",
3
- "version": "0.1.15-alpha.0",
3
+ "version": "0.1.15-alpha.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@keyou007/agent-bot",
9
- "version": "0.1.15-alpha.0",
9
+ "version": "0.1.15-alpha.1",
10
10
  "license": "GPL-3.0-only",
11
11
  "dependencies": {
12
12
  "@agentclientprotocol/sdk": "1.2.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyou007/agent-bot",
3
- "version": "0.1.15-alpha.0",
3
+ "version": "0.1.15-alpha.1",
4
4
  "type": "module",
5
5
  "description": "Use Codex, TraeX, and compatible coding agents from Lark/Feishu.",
6
6
  "license": "GPL-3.0-only",
@@ -71,7 +71,6 @@ export const REQUIRED_FEISHU_CALLBACKS = ["card.action.trigger"] as const;
71
71
 
72
72
  const CORE_FEISHU_SCOPES = new Set([
73
73
  "application:application:self_manage",
74
- "im:message.group_msg",
75
74
  "im:message.p2p_msg:readonly",
76
75
  "im:message:send_as_bot",
77
76
  ]);
@@ -287,7 +286,8 @@ async function completeOptionalFeishuConfiguration(
287
286
  ): Promise<EnsureFeishuAppConfigurationResult> {
288
287
  let configuration = initialConfiguration;
289
288
  let remaining = withoutIgnoredScopes(missingFeishuAppConfiguration(configuration), excludedScopes);
290
- const requested = withoutIgnoredScopes(remaining, ignoredScopes);
289
+ const launcherIgnoredScopes = new Set([...ignoredScopes, ...MANUAL_FEISHU_SCOPES]);
290
+ const requested = withoutIgnoredScopes(remaining, launcherIgnoredScopes);
291
291
  if (hasMissingConfiguration(requested)) {
292
292
  await requestMissingFeishuConfiguration(
293
293
  credentials.appId,
@@ -305,7 +305,32 @@ async function completeOptionalFeishuConfiguration(
305
305
  timeoutMs,
306
306
  options.signal,
307
307
  options.optionalSkipSignal,
308
- ignoredScopes,
308
+ launcherIgnoredScopes,
309
+ excludedScopes,
310
+ );
311
+ configuration = completed.configuration;
312
+ remaining = completed.remaining;
313
+ }
314
+ }
315
+
316
+ const manualMissing = manualMissingFeishuAppConfiguration(remaining);
317
+ if (hasMissingConfiguration(manualMissing)) {
318
+ await requestMissingFeishuConfiguration(
319
+ credentials.appId,
320
+ manualMissing,
321
+ true,
322
+ options.onVerification,
323
+ );
324
+ if (!options.manualPermissionSkipSignal?.aborted) {
325
+ const completed = await waitForManualFeishuConfiguration(
326
+ credentials,
327
+ configuration,
328
+ fetchImpl,
329
+ sleep,
330
+ pollIntervalMs,
331
+ timeoutMs,
332
+ options.signal,
333
+ options.manualPermissionSkipSignal,
309
334
  excludedScopes,
310
335
  );
311
336
  configuration = completed.configuration;
@@ -320,6 +345,44 @@ async function completeOptionalFeishuConfiguration(
320
345
  };
321
346
  }
322
347
 
348
+ async function waitForManualFeishuConfiguration(
349
+ credentials: FeishuAppCredentials,
350
+ initialConfiguration: FeishuAppConfiguration,
351
+ fetchImpl: typeof globalThis.fetch,
352
+ sleep: (milliseconds: number) => Promise<void>,
353
+ pollIntervalMs: number,
354
+ timeoutMs: number,
355
+ signal?: AbortSignal,
356
+ manualPermissionSkipSignal?: AbortSignal,
357
+ excludedScopes: ReadonlySet<string> = new Set(),
358
+ ): Promise<{
359
+ configuration: FeishuAppConfiguration;
360
+ remaining: MissingFeishuAppConfiguration;
361
+ }> {
362
+ let configuration = initialConfiguration;
363
+ let remaining = withoutIgnoredScopes(missingFeishuAppConfiguration(configuration), excludedScopes);
364
+ let remainingMs = timeoutMs;
365
+ while (remainingMs > 0 && hasMissingConfiguration(manualMissingFeishuAppConfiguration(remaining))) {
366
+ if (manualPermissionSkipSignal?.aborted) break;
367
+ const waitMs = Math.min(pollIntervalMs, remainingMs);
368
+ const waitResult = await sleepWithOptionalSkip(
369
+ sleep,
370
+ waitMs,
371
+ signal,
372
+ manualPermissionSkipSignal,
373
+ );
374
+ if (waitResult === "skipped") break;
375
+ remainingMs -= waitMs;
376
+ try {
377
+ configuration = await readFeishuAppConfiguration(credentials, fetchImpl, signal);
378
+ remaining = withoutIgnoredScopes(missingFeishuAppConfiguration(configuration), excludedScopes);
379
+ } catch (error) {
380
+ if (signal?.aborted) throw abortError(signal);
381
+ }
382
+ }
383
+ return { configuration, remaining };
384
+ }
385
+
323
386
  async function waitForOptionalFeishuConfiguration(
324
387
  credentials: FeishuAppCredentials,
325
388
  initialConfiguration: FeishuAppConfiguration,
@@ -621,6 +684,16 @@ function coreMissingFeishuAppConfiguration(missing: MissingFeishuAppConfiguratio
621
684
  };
622
685
  }
623
686
 
687
+ function manualMissingFeishuAppConfiguration(
688
+ missing: MissingFeishuAppConfiguration,
689
+ ): MissingFeishuAppConfiguration {
690
+ return {
691
+ scopes: missing.scopes.filter((scope) => MANUAL_FEISHU_SCOPES.has(scope)),
692
+ events: [],
693
+ callbacks: [],
694
+ };
695
+ }
696
+
624
697
  function resolvedMissingConfiguration(
625
698
  initial: MissingFeishuAppConfiguration,
626
699
  remaining: MissingFeishuAppConfiguration,
package/src/cli.ts CHANGED
@@ -2044,7 +2044,12 @@ function printFeishuConfigurationVerification(
2044
2044
  "请添加已筛选的权限、发布应用版本,并在需要时完成租户管理员审批。\n",
2045
2045
  ));
2046
2046
  }
2047
- if (challenge.blocking) {
2047
+ if (challenge.kind === "manual_scope") {
2048
+ process.stderr.write(cliText(
2049
+ "Waiting up to 5 minutes for this permission to appear in the published app version...\n",
2050
+ "将等待最多 5 分钟,直到该权限出现在已发布的应用版本中...\n",
2051
+ ));
2052
+ } else if (challenge.blocking) {
2048
2053
  process.stderr.write(cliText(
2049
2054
  "Waiting for the core scopes and message event to become active...\n",
2050
2055
  "正在等待核心权限和消息事件生效...\n",
@@ -11,7 +11,7 @@ import type {
11
11
  ToolState,
12
12
  } from "../runtime/types.js";
13
13
  import type { TurnActivity, TurnViewState, TurnViewStatus } from "../presentation/turnViewTypes.js";
14
- import { truncateText } from "../utils/markdown.js";
14
+ import { truncateMiddle, truncateText } from "../utils/markdown.js";
15
15
  import { localCardImage } from "./LocalCardImage.js";
16
16
 
17
17
  export interface StartupStatusView {
@@ -195,7 +195,6 @@ export interface DirectoryBrowserCardEntry {
195
195
  name: string;
196
196
  kind: "directory" | "drive" | "file" | "image" | "binary";
197
197
  openAction?: TaskListCardAction;
198
- actions?: TaskListCardAction[];
199
198
  }
200
199
 
201
200
  export interface DirectoryBrowserCardView {
@@ -233,6 +232,17 @@ export interface DismissGroupCardView {
233
232
  requestedBy: string;
234
233
  }
235
234
 
235
+ export interface ShellCommandCardView {
236
+ command: string;
237
+ cwd: string;
238
+ stdout: string;
239
+ stderr: string;
240
+ status: "running" | "completed" | "failed" | "timed_out";
241
+ exitCode?: number | null;
242
+ elapsedMs: number;
243
+ outputTruncated: boolean;
244
+ }
245
+
236
246
  export class CardRenderer {
237
247
  private readonly thinkingCardLayout: ThinkingCardLayout;
238
248
 
@@ -240,6 +250,35 @@ export class CardRenderer {
240
250
  this.thinkingCardLayout = options.thinkingCardLayout ?? "grouped";
241
251
  }
242
252
 
253
+ renderShellCommandCard(view: ShellCommandCardView): Record<string, unknown> {
254
+ const stdout = normalizeTerminalOutput(view.stdout).trimEnd();
255
+ const stderr = normalizeTerminalOutput(view.stderr).trimEnd();
256
+ const rawOutput = [
257
+ stdout,
258
+ stderr ? `[stderr]\n${stderr}` : "",
259
+ ].filter(Boolean).join("\n");
260
+ const displayOutput = truncateMiddle(
261
+ rawOutput,
262
+ 6_000,
263
+ "\n\n... 中间输出已截断 ...\n\n",
264
+ );
265
+ const command = truncateMiddle(view.command.trim(), 600, " ... ");
266
+ const output = displayOutput || (view.status === "running" ? "等待输出..." : "(无输出)");
267
+ const status = shellCommandStatus(view);
268
+ const outputWasTruncated = view.outputTruncated || rawOutput.length > 6_000;
269
+ return sectionCard(
270
+ status.title,
271
+ [
272
+ markdown(codeBlock(`$ ${command}\n${output}`, 6_700)),
273
+ markdown([
274
+ `${inlineCode(view.cwd)} · ${status.label} · ${formatTurnDuration(view.elapsedMs)}`,
275
+ ...(outputWasTruncated ? ["输出过长,已保留开头和结尾并截断中间内容。"] : []),
276
+ ].join("\n")),
277
+ ],
278
+ status.template,
279
+ );
280
+ }
281
+
243
282
  renderExecutionSettings(view: ExecutionSettingsCardView): Record<string, unknown> {
244
283
  const baseAction: Record<string, string> = {
245
284
  contextKey: view.contextKey,
@@ -1958,10 +1997,26 @@ function renderToolDetails(tool: ToolState, projectCwd?: string): string {
1958
1997
  ? unwrapShellCommand(normalizedCommand) ?? normalizedCommand
1959
1998
  : normalizedCommand;
1960
1999
  const commandText = truncateText(displayCommand, 600);
1961
- const resultText = result ? truncateToolResult(stripAnsi(result).trim()) : undefined;
2000
+ const resultText = result ? truncateToolResult(normalizeTerminalOutput(result).trim()) : undefined;
1962
2001
  return codeBlock([`$ ${commandText}`, resultText].filter((part): part is string => part !== undefined).join("\n"), 2_003);
1963
2002
  }
1964
2003
 
2004
+ function normalizeTerminalOutput(value: string): string {
2005
+ const clean = stripAnsi(value).replace(/\r\n/g, "\n");
2006
+ return clean
2007
+ .split("\n")
2008
+ .map((line) => {
2009
+ if (!line.includes("\r")) return line;
2010
+ const updates = line.split("\r");
2011
+ for (let index = updates.length - 1; index >= 0; index -= 1) {
2012
+ const update = updates[index];
2013
+ if (update) return update;
2014
+ }
2015
+ return "";
2016
+ })
2017
+ .join("\n");
2018
+ }
2019
+
1965
2020
  function truncateToolResult(result: string): string {
1966
2021
  const maxLength = 900;
1967
2022
  const marker = "\n...\n";
@@ -2048,6 +2103,23 @@ function formatTurnDuration(durationMs: number): string {
2048
2103
  return `${seconds}s`;
2049
2104
  }
2050
2105
 
2106
+ function shellCommandStatus(view: ShellCommandCardView): {
2107
+ title: string;
2108
+ label: string;
2109
+ template: string;
2110
+ } {
2111
+ if (view.status === "running") {
2112
+ return { title: "正在执行命令", label: "运行中", template: "blue" };
2113
+ }
2114
+ if (view.status === "timed_out") {
2115
+ return { title: "命令执行超时", label: "已超时(120s)", template: "orange" };
2116
+ }
2117
+ if (view.status === "completed") {
2118
+ return { title: "命令执行完成", label: `退出码 ${view.exitCode ?? 0}`, template: "green" };
2119
+ }
2120
+ return { title: "命令执行失败", label: `退出码 ${view.exitCode ?? "未知"}`, template: "red" };
2121
+ }
2122
+
2051
2123
  function unwrapShellCommand(command: string): string | undefined {
2052
2124
  return unwrapPowerShellCommand(command) ?? unwrapPosixShellCommand(command);
2053
2125
  }
@@ -2453,12 +2525,6 @@ function directoryBrowserEntryRow(entry: DirectoryBrowserCardEntry): Record<stri
2453
2525
  ? [taskActionElement({ ...entry.openAction, text: label })]
2454
2526
  : [markdown(escapeCardHtml(label))],
2455
2527
  },
2456
- ...(entry.actions ?? []).map((action) => ({
2457
- tag: "column",
2458
- width: "auto",
2459
- vertical_align: "center",
2460
- elements: [taskActionElement(action)],
2461
- })),
2462
2528
  ],
2463
2529
  };
2464
2530
  }
@@ -4,7 +4,7 @@ import { threadContextKey } from "./contextKey.js";
4
4
  import { resolveFeishuBotOpenId } from "./FeishuBotIdentity.js";
5
5
  import { normalizeFeishuPostText } from "./InboundText.js";
6
6
  import { allowsFeishuUser } from "./ownerAccess.js";
7
- import type { ChatUpdatedEvent, FeishuEventHandler, IncomingMessage } from "./types.js";
7
+ import type { CardAction, ChatUpdatedEvent, FeishuEventHandler, IncomingMessage } from "./types.js";
8
8
 
9
9
  type BotOpenIdResolver = (appId: string, appSecret: string) => Promise<string>;
10
10
  type BotOpenIdListener = (botOpenId: string) => void;
@@ -89,6 +89,7 @@ export class FeishuConnector {
89
89
  });
90
90
  },
91
91
  "card.action.trigger": async (data: unknown) => {
92
+ const receivedAt = Date.now();
92
93
  const operatorOpenId = getFeishuEvent(data)?.operator?.open_id;
93
94
  if (!allowsFeishuUser(this.config, operatorOpenId)) {
94
95
  this.logger.debug(
@@ -103,11 +104,11 @@ export class FeishuConnector {
103
104
  return {};
104
105
  }
105
106
 
106
- void Promise.resolve()
107
- .then(() => this.handler.onCardAction(action))
108
- .catch((error: unknown) => {
109
- this.logger.error({ error, actionId: action.actionId }, "Failed to handle Feishu card action.");
110
- });
107
+ this.deferCardAction(action, receivedAt);
108
+ this.logger.debug(
109
+ { actionId: action.actionId, messageId: action.messageId, responsePreparedInMs: Date.now() - receivedAt },
110
+ "Prepared the Feishu card action acknowledgement.",
111
+ );
111
112
  return {
112
113
  toast: {
113
114
  type: "success",
@@ -123,6 +124,27 @@ export class FeishuConnector {
123
124
  await wsClient.start({ eventDispatcher });
124
125
  this.logger.info("Feishu WebSocket connector started.");
125
126
  }
127
+
128
+ private deferCardAction(action: CardAction, receivedAt: number): void {
129
+ setImmediate(() => {
130
+ const startedAt = Date.now();
131
+ this.logger.debug(
132
+ { actionId: action.actionId, messageId: action.messageId, dispatchDelayMs: startedAt - receivedAt },
133
+ "Started deferred Feishu card action handling.",
134
+ );
135
+ void Promise.resolve()
136
+ .then(() => this.handler.onCardAction(action))
137
+ .then(() => {
138
+ this.logger.debug(
139
+ { actionId: action.actionId, messageId: action.messageId, durationMs: Date.now() - startedAt },
140
+ "Completed Feishu card action handling.",
141
+ );
142
+ })
143
+ .catch((error: unknown) => {
144
+ this.logger.error({ error, actionId: action.actionId }, "Failed to handle Feishu card action.");
145
+ });
146
+ });
147
+ }
126
148
  }
127
149
 
128
150
  function toChatUpdatedEvent(data: unknown): ChatUpdatedEvent | undefined {