feishu-codex-console 1.0.0-beta.6

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 (113) hide show
  1. package/.env.example +101 -0
  2. package/.feishu-codex-policy.example.json +11 -0
  3. package/.feishu-codex-runbooks.example.json +36 -0
  4. package/CHANGELOG.md +129 -0
  5. package/CODE_OF_CONDUCT.md +7 -0
  6. package/CONTRIBUTING.md +52 -0
  7. package/LICENSE +21 -0
  8. package/README.en.md +81 -0
  9. package/README.md +398 -0
  10. package/ROADMAP.md +40 -0
  11. package/SECURITY.md +53 -0
  12. package/dist/account-quota-card.js +233 -0
  13. package/dist/account-quota.js +125 -0
  14. package/dist/app-server-client.js +281 -0
  15. package/dist/card-session.js +166 -0
  16. package/dist/codex-events.js +1 -0
  17. package/dist/codex-runner.js +875 -0
  18. package/dist/config.js +198 -0
  19. package/dist/confirmation-card.js +135 -0
  20. package/dist/control-card.js +345 -0
  21. package/dist/conversation-turn-session.js +209 -0
  22. package/dist/data-maintenance.js +71 -0
  23. package/dist/device-card.js +460 -0
  24. package/dist/device-health.js +94 -0
  25. package/dist/diagnostics.js +253 -0
  26. package/dist/doctor.js +250 -0
  27. package/dist/fallback-card-session.js +37 -0
  28. package/dist/health-file.js +75 -0
  29. package/dist/index.js +4330 -0
  30. package/dist/lark-cli.js +558 -0
  31. package/dist/lark-retry.js +34 -0
  32. package/dist/maintenance.js +140 -0
  33. package/dist/model-capabilities.js +31 -0
  34. package/dist/onboarding-card.js +312 -0
  35. package/dist/permission-lease.js +22 -0
  36. package/dist/policy.js +506 -0
  37. package/dist/progress.js +267 -0
  38. package/dist/project-card.js +303 -0
  39. package/dist/project-overview-card.js +182 -0
  40. package/dist/project-overview.js +278 -0
  41. package/dist/project-policy.js +160 -0
  42. package/dist/project-registry.js +259 -0
  43. package/dist/project-status.js +45 -0
  44. package/dist/project-workspace.js +55 -0
  45. package/dist/quota-card.js +94 -0
  46. package/dist/recovery-policy.js +26 -0
  47. package/dist/redaction.js +67 -0
  48. package/dist/remote-ready.js +112 -0
  49. package/dist/response-card.js +139 -0
  50. package/dist/result-card.js +166 -0
  51. package/dist/review-card.js +452 -0
  52. package/dist/runbook-card.js +272 -0
  53. package/dist/runbooks.js +191 -0
  54. package/dist/runtime-card.js +337 -0
  55. package/dist/session-card.js +128 -0
  56. package/dist/session-naming.js +14 -0
  57. package/dist/smoke.js +28 -0
  58. package/dist/state-backup.js +302 -0
  59. package/dist/state-store.js +874 -0
  60. package/dist/task-card.js +640 -0
  61. package/dist/task-center-card.js +176 -0
  62. package/dist/task-failure.js +43 -0
  63. package/dist/task-intent.js +76 -0
  64. package/dist/task-queue.js +187 -0
  65. package/dist/task-reconciliation.js +80 -0
  66. package/dist/task-review.js +497 -0
  67. package/dist/team-card.js +275 -0
  68. package/dist/team-directory.js +54 -0
  69. package/dist/team-policy.js +93 -0
  70. package/dist/types.js +1 -0
  71. package/dist/version.js +9 -0
  72. package/dist/workspace-session.js +64 -0
  73. package/docs/ARCHITECTURE.md +54 -0
  74. package/docs/COMPATIBILITY.md +55 -0
  75. package/docs/CONFIGURATION.md +88 -0
  76. package/docs/DEMO.md +45 -0
  77. package/docs/GOOD_FIRST_ISSUES.md +23 -0
  78. package/docs/INSTALLATION.md +207 -0
  79. package/docs/OPEN_SOURCE_PRODUCT_PLAN.md +113 -0
  80. package/docs/PRODUCT_REQUIREMENTS_MAP.md +591 -0
  81. package/docs/RELEASE_CHECKLIST.md +65 -0
  82. package/docs/TEAM_DEPLOYMENT.md +35 -0
  83. package/docs/TROUBLESHOOTING.md +130 -0
  84. package/docs/V4_WORKSPACE_SESSION_FLOW.md +232 -0
  85. package/docs/requirements/D10_MAINTENANCE_AND_ECOSYSTEM.md +103 -0
  86. package/docs/requirements/D1_INSTALLATION_AND_FIRST_CONNECTION.md +479 -0
  87. package/docs/requirements/D2_DEVICE_AND_CONNECTIVITY.md +54 -0
  88. package/docs/requirements/D3_PROJECTS_AND_SESSIONS.md +107 -0
  89. package/docs/requirements/D4_REMOTE_TASK_EXECUTION.md +102 -0
  90. package/docs/requirements/D5_CODEX_NATIVE_INTERACTIONS.md +99 -0
  91. package/docs/requirements/D6_RESULTS_AND_CODE_REVIEW.md +100 -0
  92. package/docs/requirements/D7_SECURITY_GOVERNANCE.md +106 -0
  93. package/docs/requirements/D8_RELIABILITY_AND_RECOVERY.md +182 -0
  94. package/docs/requirements/D9_TEAM_COLLABORATION.md +129 -0
  95. package/package.json +76 -0
  96. package/scripts/capability-probe.mjs +113 -0
  97. package/scripts/cli.mjs +919 -0
  98. package/scripts/config-file.mjs +137 -0
  99. package/scripts/discovery-lib.mjs +78 -0
  100. package/scripts/install-card.mjs +37 -0
  101. package/scripts/install-detection.mjs +126 -0
  102. package/scripts/install-state.mjs +107 -0
  103. package/scripts/launchd.mjs +161 -0
  104. package/scripts/migrate-legacy.mjs +97 -0
  105. package/scripts/package-smoke.mjs +163 -0
  106. package/scripts/release-dist-tag.mjs +7 -0
  107. package/scripts/runbook-template.mjs +36 -0
  108. package/scripts/service-health.mjs +110 -0
  109. package/scripts/service.mjs +24 -0
  110. package/scripts/setup-lib.mjs +118 -0
  111. package/scripts/systemd.mjs +96 -0
  112. package/scripts/upgrade-lib.mjs +99 -0
  113. package/scripts/verify-release.mjs +37 -0
@@ -0,0 +1,209 @@
1
+ import { applyCodexEvent, cancelTask, failTask, interruptTask, noteSteer, recoverTask, startTask, succeedTask, updateTaskCollaboration, updateQueuePosition, } from "./progress.js";
2
+ import { prepareRemoteMarkdown, redactSensitiveText } from "./redaction.js";
3
+ import { runningActivity, taskModeOf } from "./task-intent.js";
4
+ // Feishu allows at most 20 edits for one text/post message. Keep ordinary
5
+ // answers progressive without exhausting the edit budget before the final
6
+ // response can replace the working state.
7
+ const STREAM_THROTTLE_MS = 1_800;
8
+ const MAX_PROGRESS_EDITS = 18;
9
+ const MAX_MESSAGE_EDITS = 20;
10
+ export class ConversationTurnSession {
11
+ lark;
12
+ messageId;
13
+ current;
14
+ sequence;
15
+ operationChain = Promise.resolve();
16
+ pendingContent = "";
17
+ lastContent = "";
18
+ streamTimer = null;
19
+ snapshotListener;
20
+ constructor(lark, messageId, current, sequence = 0) {
21
+ this.lark = lark;
22
+ this.messageId = messageId;
23
+ this.current = current;
24
+ this.sequence = sequence;
25
+ this.lastContent = renderConversationTurn(current);
26
+ }
27
+ static async create(lark, progress, replyToMessageId, idempotencyKey, replyInThread = false) {
28
+ const content = renderConversationTurn(progress);
29
+ const messageId = await lark.replyMarkdown(replyToMessageId, content, idempotencyKey, replyInThread);
30
+ if (!messageId)
31
+ throw new Error("飞书没有返回自然回复消息 ID");
32
+ return new ConversationTurnSession(lark, messageId, progress);
33
+ }
34
+ static restore(lark, messageId, progress, sequence = 0) {
35
+ return new ConversationTurnSession(lark, messageId, progress, sequence);
36
+ }
37
+ get progress() {
38
+ return this.current;
39
+ }
40
+ get sequenceNumber() {
41
+ return this.sequence;
42
+ }
43
+ onSnapshot(listener) {
44
+ this.snapshotListener = listener;
45
+ }
46
+ async markRunning(reviewBaseline, permissionLabel) {
47
+ this.current = startTask(this.current, Date.now(), reviewBaseline, permissionLabel);
48
+ await this.update("start");
49
+ }
50
+ async markRecovered(note, queuePosition = 1) {
51
+ this.current = recoverTask(this.current, queuePosition, note);
52
+ await this.update("recover");
53
+ }
54
+ async updateQueuePosition(queuePosition) {
55
+ const next = updateQueuePosition(this.current, queuePosition);
56
+ if (next === this.current)
57
+ return;
58
+ this.current = next;
59
+ await this.update("queue-position");
60
+ }
61
+ async markSteered() {
62
+ this.current = noteSteer(this.current);
63
+ await this.update("steer");
64
+ }
65
+ async addActionNote(note) {
66
+ this.current = { ...this.current, actionNote: note };
67
+ await this.update("note");
68
+ }
69
+ async updateCollaboration(context, note) {
70
+ this.current = updateTaskCollaboration(this.current, context, note);
71
+ await this.update("collaboration");
72
+ }
73
+ handleCodexEvent(event) {
74
+ this.current = { ...applyCodexEvent(this.current, event), actionNote: "" };
75
+ this.scheduleUpdate(renderConversationTurn(this.current));
76
+ }
77
+ async finishSucceeded(finalResponse, usage, threadId) {
78
+ this.current = succeedTask(this.current, finalResponse, usage, threadId);
79
+ return this.update("complete");
80
+ }
81
+ async finishFailed(error) {
82
+ this.current = failTask(this.current, error);
83
+ return this.update("fail");
84
+ }
85
+ async finishCancelled(reason) {
86
+ if (this.current.phase === "cancelled")
87
+ return true;
88
+ this.current = cancelTask(this.current, reason);
89
+ return this.update("cancel");
90
+ }
91
+ async finishInterrupted(reason) {
92
+ this.current = interruptTask(this.current, reason);
93
+ return this.update("interrupt");
94
+ }
95
+ async flush() {
96
+ await this.flushPendingUpdate();
97
+ await this.operationChain;
98
+ }
99
+ scheduleUpdate(content) {
100
+ this.pendingContent = content;
101
+ if (this.streamTimer)
102
+ return;
103
+ this.streamTimer = setTimeout(() => {
104
+ this.streamTimer = null;
105
+ void this.flushPendingUpdate();
106
+ }, STREAM_THROTTLE_MS);
107
+ this.streamTimer.unref();
108
+ }
109
+ async flushPendingUpdate() {
110
+ if (this.streamTimer) {
111
+ clearTimeout(this.streamTimer);
112
+ this.streamTimer = null;
113
+ }
114
+ const content = this.pendingContent;
115
+ this.pendingContent = "";
116
+ if (!content || content === this.lastContent)
117
+ return;
118
+ try {
119
+ await this.enqueue("stream", content);
120
+ }
121
+ catch {
122
+ // A later terminal update or the durable fallback will retry delivery.
123
+ }
124
+ }
125
+ async update(label) {
126
+ await this.flushPendingUpdate();
127
+ const content = renderConversationTurn(this.current);
128
+ if (content === this.lastContent) {
129
+ await this.notifySnapshot();
130
+ return true;
131
+ }
132
+ try {
133
+ await this.enqueue(label, content, isTerminalPhase(this.current.phase));
134
+ return true;
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ }
140
+ async enqueue(label, content, terminal = false) {
141
+ const next = this.operationChain.then(async () => {
142
+ if (!terminal && this.sequence >= MAX_PROGRESS_EDITS)
143
+ return;
144
+ if (terminal && this.sequence >= MAX_MESSAGE_EDITS) {
145
+ throw new Error("飞书消息已达到最大可编辑次数");
146
+ }
147
+ await this.lark.updateMarkdown(this.messageId, content);
148
+ this.sequence += 1;
149
+ this.lastContent = content;
150
+ await this.notifySnapshot();
151
+ });
152
+ this.operationChain = next.catch((error) => {
153
+ console.error(`[conversation] ${label} update failed message=${this.messageId}`, error);
154
+ });
155
+ await next;
156
+ }
157
+ async notifySnapshot() {
158
+ await this.snapshotListener?.({
159
+ messageId: this.messageId,
160
+ sequence: this.sequence,
161
+ progress: this.current,
162
+ });
163
+ }
164
+ }
165
+ function isTerminalPhase(phase) {
166
+ return phase === "succeeded" ||
167
+ phase === "failed" ||
168
+ phase === "cancelled" ||
169
+ phase === "interrupted";
170
+ }
171
+ export function renderConversationTurn(progress) {
172
+ if (progress.phase === "succeeded") {
173
+ return safeMarkdown(progress.finalResponse || progress.partialResponse || "Codex 已完成。");
174
+ }
175
+ if (progress.phase === "failed") {
176
+ return `**这次没有完成**\n\n${safeMarkdown(progress.error || "Codex 执行失败,请稍后重试。")}`;
177
+ }
178
+ if (progress.phase === "cancelled") {
179
+ return `**已停止**\n\n${safeMarkdown(progress.error || "当前处理已由用户停止。")}`;
180
+ }
181
+ if (progress.phase === "interrupted") {
182
+ return `**会话被中断**\n\n${safeMarkdown(progress.error || "本地服务中断了这次处理。")}`;
183
+ }
184
+ if (progress.phase === "queued") {
185
+ const queue = progress.queuePosition > 1
186
+ ? ` · 队列第 ${progress.queuePosition} 位`
187
+ : "";
188
+ return `已收到 · 正在准备${queue}`;
189
+ }
190
+ if (progress.actionNote.trim()) {
191
+ return safeInline(progress.actionNote);
192
+ }
193
+ if (progress.partialResponse.trim()) {
194
+ return `${safeMarkdown(progress.partialResponse)}\n\n正在继续生成…`;
195
+ }
196
+ const activity = progress.activity || runningActivity(taskModeOf(progress));
197
+ return `${safeInline(activity)}…`;
198
+ }
199
+ function safeMarkdown(value) {
200
+ return prepareRemoteMarkdown(redactSensitiveText(value, 12_000))
201
+ .replaceAll("<at", "<at")
202
+ .replaceAll("</at>", "</at>")
203
+ .replaceAll("<person", "<person")
204
+ .replaceAll("</person>", "</person>")
205
+ .trim();
206
+ }
207
+ function safeInline(value) {
208
+ return safeMarkdown(value).replace(/[*_`>#\[\]()]/g, "").replace(/\s+/g, " ").trim();
209
+ }
@@ -0,0 +1,71 @@
1
+ import "dotenv/config";
2
+ import { loadConfig } from "./config.js";
3
+ import { safeErrorText } from "./redaction.js";
4
+ import { createRuntimeBackup, listRuntimeBackups, restoreRuntimeBackup, } from "./state-backup.js";
5
+ async function main() {
6
+ const action = process.argv[2] ?? "list";
7
+ const options = parseOptions(process.argv.slice(3));
8
+ const config = loadConfig();
9
+ if (action === "backup") {
10
+ const snapshot = await createRuntimeBackup(config.databaseFile, {
11
+ reason: options.reason ?? "manual",
12
+ });
13
+ console.log("运行数据备份完成");
14
+ console.log(`备份 ID:${snapshot.id}`);
15
+ console.log(`位置:${snapshot.directory}`);
16
+ console.log(`校验:SHA-256 ${snapshot.manifest.database.sha256}`);
17
+ return;
18
+ }
19
+ if (action === "list") {
20
+ const backups = await listRuntimeBackups(config.databaseFile);
21
+ console.log(`可用运行数据备份(${backups.length})`);
22
+ for (const snapshot of backups) {
23
+ console.log(`${snapshot.id} · ${snapshot.manifest.reason} · ${snapshot.manifest.database.bytes} bytes · schema ${snapshot.manifest.database.sqliteUserVersion}`);
24
+ }
25
+ if (backups.length === 0)
26
+ console.log("暂无备份。先运行 feishu-codex-bridge backup。");
27
+ return;
28
+ }
29
+ if (action === "rollback") {
30
+ if (!options.backup)
31
+ throw new Error("rollback 需要 --backup <备份 ID>");
32
+ if (!options.yes) {
33
+ throw new Error("回滚会替换当前运行数据库;确认服务已停止后,添加 --yes 执行");
34
+ }
35
+ const result = await restoreRuntimeBackup(config.databaseFile, config.dataDir, options.backup);
36
+ console.log(`已恢复备份:${result.restored.id}`);
37
+ if (result.safetyBackup) {
38
+ console.log(`回滚前状态已另存为:${result.safetyBackup.id}`);
39
+ }
40
+ console.log("下一步:启动服务并运行 doctor,确认状态和连接正常。");
41
+ return;
42
+ }
43
+ throw new Error(`未知数据维护命令:${action}`);
44
+ }
45
+ function parseOptions(args) {
46
+ const options = { yes: false };
47
+ for (let index = 0; index < args.length; index += 1) {
48
+ const value = args[index];
49
+ if (value === "--yes") {
50
+ options.yes = true;
51
+ continue;
52
+ }
53
+ if (value === "--backup" || value === "--reason") {
54
+ const next = args[index + 1];
55
+ if (!next || next.startsWith("--"))
56
+ throw new Error(`${value} 缺少值`);
57
+ if (value === "--backup")
58
+ options.backup = next;
59
+ else
60
+ options.reason = next;
61
+ index += 1;
62
+ continue;
63
+ }
64
+ throw new Error(`无法识别数据维护参数:${value}`);
65
+ }
66
+ return options;
67
+ }
68
+ void main().catch((error) => {
69
+ console.error(`数据维护失败:${safeErrorText(error)}`);
70
+ process.exitCode = 1;
71
+ });