team-anya 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of team-anya might be problematic. Click here for more details.

Files changed (177) hide show
  1. package/README.md +38 -0
  2. package/anya/prompts/execution-guides/git-delivery.md +38 -0
  3. package/anya/prompts/execution-guides/testing-and-self-heal.md +28 -0
  4. package/anya/prompts/protocols/brief-assembly.md +55 -0
  5. package/anya/prompts/protocols/report.md +175 -0
  6. package/anya/prompts/protocols/review.md +90 -0
  7. package/anya/prompts/task-claude-md.template.md +32 -0
  8. package/apps/server/dist/broker/cc-broker.js +257 -0
  9. package/apps/server/dist/cli.js +296 -0
  10. package/apps/server/dist/config.js +76 -0
  11. package/apps/server/dist/daemon.js +51 -0
  12. package/apps/server/dist/gateway/chat-sync.js +135 -0
  13. package/apps/server/dist/gateway/command-router.js +114 -0
  14. package/apps/server/dist/gateway/commands/cancel.js +32 -0
  15. package/apps/server/dist/gateway/commands/help.js +16 -0
  16. package/apps/server/dist/gateway/commands/index.js +26 -0
  17. package/apps/server/dist/gateway/commands/restart.js +34 -0
  18. package/apps/server/dist/gateway/commands/status.js +34 -0
  19. package/apps/server/dist/gateway/commands/tasks.js +33 -0
  20. package/apps/server/dist/gateway/feishu-sender.js +346 -0
  21. package/apps/server/dist/gateway/feishu-ws.js +254 -0
  22. package/apps/server/dist/gateway/http.js +994 -0
  23. package/apps/server/dist/gateway/media-downloader.js +149 -0
  24. package/apps/server/dist/gateway/message-events.js +10 -0
  25. package/apps/server/dist/gateway/message-intake.js +50 -0
  26. package/apps/server/dist/gateway/message-queue.js +104 -0
  27. package/apps/server/dist/gateway/session-reader.js +142 -0
  28. package/apps/server/dist/gateway/ws-push.js +115 -0
  29. package/apps/server/dist/loid/brain.js +104 -0
  30. package/apps/server/dist/loid/brief-assembler.js +156 -0
  31. package/apps/server/dist/loid/clarifier.js +162 -0
  32. package/apps/server/dist/loid/commitment-tracker.js +236 -0
  33. package/apps/server/dist/loid/context-builder.js +413 -0
  34. package/apps/server/dist/loid/dispatcher.js +544 -0
  35. package/apps/server/dist/loid/intent-classifier.js +158 -0
  36. package/apps/server/dist/loid/mcp-server.js +104 -0
  37. package/apps/server/dist/loid/memory-settler.js +189 -0
  38. package/apps/server/dist/loid/opportunity-manager.js +148 -0
  39. package/apps/server/dist/loid/process-manager.js +186 -0
  40. package/apps/server/dist/loid/profile-updater.js +179 -0
  41. package/apps/server/dist/loid/reporter.js +148 -0
  42. package/apps/server/dist/loid/schemas.js +117 -0
  43. package/apps/server/dist/loid/self-calibrator.js +314 -0
  44. package/apps/server/dist/loid/session-manager.js +217 -0
  45. package/apps/server/dist/loid/session.js +271 -0
  46. package/apps/server/dist/loid/worktree-manager.js +191 -0
  47. package/apps/server/dist/main.js +337 -0
  48. package/apps/server/dist/tracing/index.js +2 -0
  49. package/apps/server/dist/tracing/trace-context.js +92 -0
  50. package/apps/server/dist/types/message.js +2 -0
  51. package/apps/server/dist/yor/yor-mcp-server.js +104 -0
  52. package/apps/server/dist/yor/yor-orchestrator.js +233 -0
  53. package/apps/web/dist/assets/index-CHIT0Dya.css +1 -0
  54. package/apps/web/dist/assets/index-CJzAjoVH.js +798 -0
  55. package/apps/web/dist/index.html +13 -0
  56. package/package.json +42 -0
  57. package/packages/cc-client/dist/claude-code-backend.js +664 -0
  58. package/packages/cc-client/dist/index.js +2 -0
  59. package/packages/cc-client/package.json +11 -0
  60. package/packages/core/dist/constants.js +59 -0
  61. package/packages/core/dist/errors.js +35 -0
  62. package/packages/core/dist/index.js +7 -0
  63. package/packages/core/dist/office-init.js +97 -0
  64. package/packages/core/dist/scope/checker.js +114 -0
  65. package/packages/core/dist/scope/defaults.js +40 -0
  66. package/packages/core/dist/scope/index.js +3 -0
  67. package/packages/core/dist/state-machine.js +85 -0
  68. package/packages/core/dist/types/audit.js +12 -0
  69. package/packages/core/dist/types/backend.js +2 -0
  70. package/packages/core/dist/types/commitment.js +17 -0
  71. package/packages/core/dist/types/communication.js +18 -0
  72. package/packages/core/dist/types/index.js +8 -0
  73. package/packages/core/dist/types/opportunity.js +27 -0
  74. package/packages/core/dist/types/org.js +26 -0
  75. package/packages/core/dist/types/task.js +46 -0
  76. package/packages/core/package.json +10 -0
  77. package/packages/db/dist/client.js +69 -0
  78. package/packages/db/dist/index.js +603 -0
  79. package/packages/db/dist/schema/audit-events.js +13 -0
  80. package/packages/db/dist/schema/cc-sessions.js +14 -0
  81. package/packages/db/dist/schema/chats.js +33 -0
  82. package/packages/db/dist/schema/commitments.js +18 -0
  83. package/packages/db/dist/schema/communication-events.js +14 -0
  84. package/packages/db/dist/schema/index.js +12 -0
  85. package/packages/db/dist/schema/message-log.js +20 -0
  86. package/packages/db/dist/schema/opportunities.js +23 -0
  87. package/packages/db/dist/schema/org.js +36 -0
  88. package/packages/db/dist/schema/projects.js +23 -0
  89. package/packages/db/dist/schema/tasks.js +46 -0
  90. package/packages/db/dist/schema/trace-spans.js +19 -0
  91. package/packages/db/package.json +12 -0
  92. package/packages/db/src/migrations/0000_simple_magneto.sql +148 -0
  93. package/packages/db/src/migrations/0001_nifty_morph.sql +42 -0
  94. package/packages/db/src/migrations/0002_common_joshua_kane.sql +20 -0
  95. package/packages/db/src/migrations/0003_add_cc_sessions.sql +13 -0
  96. package/packages/db/src/migrations/0004_jittery_triathlon.sql +1 -0
  97. package/packages/db/src/migrations/meta/0000_snapshot.json +987 -0
  98. package/packages/db/src/migrations/meta/0001_snapshot.json +1280 -0
  99. package/packages/db/src/migrations/meta/0002_snapshot.json +1417 -0
  100. package/packages/db/src/migrations/meta/0004_snapshot.json +1505 -0
  101. package/packages/db/src/migrations/meta/_journal.json +41 -0
  102. package/packages/mcp-tools/dist/index.js +41 -0
  103. package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
  104. package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
  105. package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
  106. package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
  107. package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
  108. package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
  109. package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
  110. package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
  111. package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
  112. package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
  113. package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
  114. package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
  115. package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
  116. package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
  117. package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
  118. package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
  119. package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
  120. package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
  121. package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
  122. package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
  123. package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
  124. package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
  125. package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
  126. package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
  127. package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
  128. package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
  129. package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +177 -0
  130. package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +38 -0
  131. package/packages/mcp-tools/dist/layer2/loid/task-review.js +151 -0
  132. package/packages/mcp-tools/dist/layer2/loid/workspace-cleanup.js +7 -0
  133. package/packages/mcp-tools/dist/layer2/loid/workspace-info.js +31 -0
  134. package/packages/mcp-tools/dist/layer2/loid/workspace-prepare.js +12 -0
  135. package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
  136. package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
  137. package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
  138. package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +15 -0
  139. package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
  140. package/packages/mcp-tools/dist/layer2/yor/code-lint.js +47 -0
  141. package/packages/mcp-tools/dist/layer2/yor/code-test.js +52 -0
  142. package/packages/mcp-tools/dist/layer2/yor/git-add.js +24 -0
  143. package/packages/mcp-tools/dist/layer2/yor/git-commit.js +24 -0
  144. package/packages/mcp-tools/dist/layer2/yor/git-push.js +64 -0
  145. package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
  146. package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
  147. package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
  148. package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +191 -0
  149. package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
  150. package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
  151. package/packages/mcp-tools/dist/layer3/channel-send.js +90 -0
  152. package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
  153. package/packages/mcp-tools/dist/registry.js +779 -0
  154. package/packages/mcp-tools/package.json +13 -0
  155. package/workspace/.claude/settings.local.json +9 -0
  156. package/workspace/.mcp.json +12 -0
  157. package/workspace/CHARTER.md +73 -0
  158. package/workspace/CLAUDE.md +49 -0
  159. package/workspace/PROTOCOL.md +126 -0
  160. package/workspace/TOOLS.md +464 -0
  161. package/workspace/audit/.gitkeep +0 -0
  162. package/workspace/loid/CLAUDE.md +12 -0
  163. package/workspace/loid/PLAYBOOK.md +198 -0
  164. package/workspace/loid/PROFILE.md +78 -0
  165. package/workspace/memory/commitments/.gitkeep +0 -0
  166. package/workspace/memory/execution/.gitkeep +0 -0
  167. package/workspace/memory/people/.gitkeep +0 -0
  168. package/workspace/memory/projects/.gitkeep +0 -0
  169. package/workspace/memory/self/.gitkeep +0 -0
  170. package/workspace/reference/identity/.gitkeep +0 -0
  171. package/workspace/reference/org/escalation.yaml +24 -0
  172. package/workspace/reference/org/ownership.yaml +28 -0
  173. package/workspace/reports/.gitkeep +0 -0
  174. package/workspace/yor/CLAUDE.md +22 -0
  175. package/workspace/yor/PLAYBOOK.md +73 -0
  176. package/workspace/yor/PROFILE.md +52 -0
  177. package/workspace/yor/SELF-HEAL.md +39 -0
@@ -0,0 +1,24 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ /**
5
+ * git.add - 执行 git add 命令
6
+ *
7
+ * files 可以是文件列表或 "."(添加全部变更)。
8
+ */
9
+ export async function gitAdd(input) {
10
+ const { files, working_dir } = input;
11
+ const fileList = typeof files === 'string' ? [files] : files;
12
+ if (fileList.length === 0) {
13
+ return { success: false, files_added: [], error: '未指定要添加的文件' };
14
+ }
15
+ try {
16
+ await execFileAsync('git', ['add', ...fileList], { cwd: working_dir });
17
+ return { success: true, files_added: fileList };
18
+ }
19
+ catch (err) {
20
+ const message = err instanceof Error ? err.message : String(err);
21
+ return { success: false, files_added: [], error: message };
22
+ }
23
+ }
24
+ //# sourceMappingURL=git-add.js.map
@@ -0,0 +1,24 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ /**
5
+ * git.commit - 执行 git commit
6
+ */
7
+ export async function gitCommit(input) {
8
+ const { message, working_dir } = input;
9
+ if (!message.trim()) {
10
+ return { success: false, error: 'commit message 不能为空' };
11
+ }
12
+ try {
13
+ const { stdout } = await execFileAsync('git', ['commit', '-m', message], { cwd: working_dir });
14
+ // 从输出中提取 commit hash
15
+ const hashMatch = stdout.match(/\[[\w/.-]+\s+([a-f0-9]+)\]/);
16
+ const commitHash = hashMatch?.[1];
17
+ return { success: true, commit_hash: commitHash };
18
+ }
19
+ catch (err) {
20
+ const message = err instanceof Error ? err.message : String(err);
21
+ return { success: false, error: message };
22
+ }
23
+ }
24
+ //# sourceMappingURL=git-commit.js.map
@@ -0,0 +1,64 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ /** 禁止推送到这些分支 */
5
+ const PROTECTED_BRANCHES = ['main', 'master'];
6
+ /** 禁止提交的敏感文件模式 */
7
+ const SENSITIVE_FILE_PATTERNS = [
8
+ /\.env$/,
9
+ /\.env\.\w+$/,
10
+ /credentials\.json$/,
11
+ /\.pem$/,
12
+ /\.key$/,
13
+ /secret/i,
14
+ ];
15
+ /**
16
+ * git.push - 执行 git push
17
+ *
18
+ * 安全约束:
19
+ * - 禁止 --force 推送到 main/master
20
+ * - 检查暂存区是否包含敏感文件
21
+ */
22
+ export async function gitPush(input) {
23
+ const { working_dir, branch } = input;
24
+ // 确定目标分支
25
+ let targetBranch = branch;
26
+ if (!targetBranch) {
27
+ try {
28
+ const { stdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: working_dir });
29
+ targetBranch = stdout.trim();
30
+ }
31
+ catch {
32
+ return { success: false, error: '无法获取当前分支名' };
33
+ }
34
+ }
35
+ // 安全检查:禁止推送到受保护分支
36
+ if (PROTECTED_BRANCHES.includes(targetBranch)) {
37
+ return { success: false, error: `禁止直接推送到受保护分支: ${targetBranch}` };
38
+ }
39
+ // 安全检查:检查暂存区是否包含敏感文件
40
+ try {
41
+ const { stdout } = await execFileAsync('git', ['diff', '--cached', '--name-only'], { cwd: working_dir });
42
+ const stagedFiles = stdout.trim().split('\n').filter(Boolean);
43
+ for (const file of stagedFiles) {
44
+ for (const pattern of SENSITIVE_FILE_PATTERNS) {
45
+ if (pattern.test(file)) {
46
+ return { success: false, error: `暂存区包含敏感文件: ${file},请先移除` };
47
+ }
48
+ }
49
+ }
50
+ }
51
+ catch {
52
+ // diff 命令失败不阻塞推送
53
+ }
54
+ try {
55
+ const pushArgs = ['push', '-u', 'origin', targetBranch];
56
+ await execFileAsync('git', pushArgs, { cwd: working_dir });
57
+ return { success: true, branch: targetBranch };
58
+ }
59
+ catch (err) {
60
+ const message = err instanceof Error ? err.message : String(err);
61
+ return { success: false, error: message };
62
+ }
63
+ }
64
+ //# sourceMappingURL=git-push.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 报告阻塞
3
+ *
4
+ * Yor 遇到无法解决的问题时调用。
5
+ * 增加 category 字段与 PROTOCOL.md 对齐。
6
+ */
7
+ export async function taskBlock(collector, input) {
8
+ collector.onBlockerReport(input);
9
+ return { received: true, type: 'blocker' };
10
+ }
11
+ //# sourceMappingURL=task-block.js.map
@@ -0,0 +1,35 @@
1
+ import { execFile as execFileCb } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFile = promisify(execFileCb);
4
+ /**
5
+ * 提交交付成果
6
+ *
7
+ * Yor 完成任务后调用,报告变更文件、测试结果、PR 链接。
8
+ * 结果通过 collector 回调传给 dispatcher。
9
+ *
10
+ * 交付前检查 git 状态:如果有未提交变更,拒绝交付。
11
+ */
12
+ export async function taskDeliver(collector, input, workingDir) {
13
+ // 检查 git 状态
14
+ if (workingDir) {
15
+ try {
16
+ // 检查是否是 git 仓库
17
+ await execFile('git', ['-C', workingDir, 'rev-parse', '--git-dir']);
18
+ // 检查是否有未提交变更
19
+ const { stdout } = await execFile('git', ['-C', workingDir, 'status', '--porcelain']);
20
+ if (stdout.trim().length > 0) {
21
+ return {
22
+ received: false,
23
+ type: 'delivery_rejected',
24
+ reason: `❌ 交付被拒绝:工作目录有未提交的变更。\n请先执行 git add + git commit,然后重新调用 task.deliver。\n未提交文件:\n${stdout.trim()}`,
25
+ };
26
+ }
27
+ }
28
+ catch {
29
+ // 不是 git 仓库或 git 命令失败,跳过检查(warn 级别)
30
+ }
31
+ }
32
+ collector.onDeliverySubmit(input);
33
+ return { received: true, type: 'delivery' };
34
+ }
35
+ //# sourceMappingURL=task-deliver.js.map
@@ -0,0 +1,21 @@
1
+ import { insertAuditEvent } from '@team-anya/db';
2
+ /**
3
+ * 中间进度汇报
4
+ *
5
+ * 长任务中 Yor 向 Loid 汇报里程碑,不改变任务状态。
6
+ * 写 audit event(yor_progress),Loid 可通过 audit.query 查看。
7
+ */
8
+ export async function taskProgress(db, taskId, input) {
9
+ insertAuditEvent(db, {
10
+ event_type: 'yor_progress',
11
+ actor: 'yor',
12
+ task_id: taskId,
13
+ summary: input.milestone,
14
+ detail: JSON.stringify({
15
+ detail: input.detail,
16
+ next_step: input.next_step,
17
+ }),
18
+ });
19
+ return { received: true };
20
+ }
21
+ //# sourceMappingURL=task-progress.js.map
@@ -0,0 +1,191 @@
1
+ // ── 媒体类型检测 ──
2
+ const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.ico', '.tiff', '.tif']);
3
+ const AUDIO_EXTS = new Set(['.opus']);
4
+ const FILE_TYPE_MAP = {
5
+ '.opus': 'opus', '.mp4': 'mp4', '.pdf': 'pdf',
6
+ '.doc': 'doc', '.docx': 'doc', '.xls': 'xls', '.xlsx': 'xls',
7
+ '.ppt': 'ppt', '.pptx': 'ppt',
8
+ };
9
+ /**
10
+ * 根据文件扩展名检测媒体类型
11
+ */
12
+ export function detectMediaKind(filePath) {
13
+ const dot = filePath.lastIndexOf('.');
14
+ if (dot === -1)
15
+ return 'file';
16
+ const ext = filePath.slice(dot).toLowerCase();
17
+ if (IMAGE_EXTS.has(ext))
18
+ return 'image';
19
+ if (AUDIO_EXTS.has(ext))
20
+ return 'audio';
21
+ return 'file';
22
+ }
23
+ /**
24
+ * 根据文件扩展名获取飞书 file_type 参数
25
+ */
26
+ export function resolveFileType(filePath) {
27
+ const dot = filePath.lastIndexOf('.');
28
+ if (dot === -1)
29
+ return 'stream';
30
+ const ext = filePath.slice(dot).toLowerCase();
31
+ return FILE_TYPE_MAP[ext] ?? 'stream';
32
+ }
33
+ // ── Markdown 检测 ──
34
+ /**
35
+ * 检测文本是否包含 Markdown 语法
36
+ *
37
+ * 匹配常见 Markdown 元素:标题、粗体、斜体、代码块、链接、列表、引用、分割线、表格等。
38
+ * 短文本(<50 字符)且仅含单个 markdown 元素时倾向纯文本,避免误判。
39
+ */
40
+ const MD_PATTERNS = [
41
+ /^#{1,6}\s/m, // 标题: # / ## / ###
42
+ /\*\*[^*]+\*\*/, // 粗体: **text**
43
+ /\*[^*]+\*/, // 斜体: *text*
44
+ /`[^`]+`/, // 行内代码: `code`
45
+ /```[\s\S]*?```/, // 代码块: ```...```
46
+ /\[.+?\]\(.+?\)/, // 链接: [text](url)
47
+ /^\s*[-*+]\s/m, // 无序列表: - item / * item
48
+ /^\s*\d+\.\s/m, // 有序列表: 1. item
49
+ /^\s*>/m, // 引用: > text
50
+ /^-{3,}$/m, // 分割线: ---
51
+ /^\|.+\|$/m, // 表格: | col | col |
52
+ ];
53
+ export function isMarkdown(text) {
54
+ let matchCount = 0;
55
+ for (const pattern of MD_PATTERNS) {
56
+ if (pattern.test(text))
57
+ matchCount++;
58
+ if (matchCount >= 2)
59
+ return true;
60
+ }
61
+ // 单个匹配:仅当文本较长时才认为是 markdown
62
+ return matchCount === 1 && text.length >= 80;
63
+ }
64
+ /**
65
+ * 将 markdown 内容转换为飞书交互式卡片格式
66
+ */
67
+ function markdownToFeishuCard(content) {
68
+ return {
69
+ schema: '2.0',
70
+ config: { wide_screen_mode: true },
71
+ body: {
72
+ elements: [
73
+ { tag: 'markdown', content },
74
+ ],
75
+ },
76
+ };
77
+ }
78
+ /**
79
+ * 飞书通道适配器
80
+ *
81
+ * 将 FeishuSender 包装为 ChannelAdapter 接口。
82
+ * target 格式:
83
+ * - "chat_id" → sendText to chat
84
+ * - "reply/msg_id" → sendReply(向后兼容)
85
+ */
86
+ export class FeishuAdapter {
87
+ scheme = 'feishu';
88
+ sender;
89
+ constructor(sender) {
90
+ this.sender = sender;
91
+ }
92
+ /**
93
+ * 将 mentions 注入到文本中(飞书 @ 格式)
94
+ */
95
+ injectMentions(text, mentions) {
96
+ if (!mentions || mentions.length === 0)
97
+ return text;
98
+ const atTags = mentions.map(uid => `<at user_id="${uid}"></at>`).join(' ');
99
+ return `${atTags} ${text}`;
100
+ }
101
+ async send(target, message, options) {
102
+ const text = this.injectMentions(message, options?.mentions);
103
+ // 检测是否为 markdown 内容,且 sender 支持卡片发送
104
+ const useCard = isMarkdown(text) && !!this.sender.sendCard;
105
+ // 优先使用 options.replyTo
106
+ const replyTo = options?.replyTo;
107
+ // 向后兼容:target 以 "reply/" 开头
108
+ const resolvedReplyTo = replyTo || (target.startsWith('reply/') ? target.slice('reply/'.length) : undefined);
109
+ const chatTarget = target.startsWith('reply/') ? undefined : target;
110
+ if (useCard) {
111
+ const card = markdownToFeishuCard(text);
112
+ const id = await this.sender.sendCard({
113
+ receiveIdType: 'chat_id',
114
+ receiveId: chatTarget ?? '',
115
+ card,
116
+ replyToMessageId: resolvedReplyTo,
117
+ });
118
+ return { id };
119
+ }
120
+ // 纯文本发送
121
+ if (resolvedReplyTo) {
122
+ const id = await this.sender.sendReply({
123
+ text,
124
+ replyToMessageId: resolvedReplyTo,
125
+ });
126
+ return { id };
127
+ }
128
+ const id = await this.sender.sendText({
129
+ receiveIdType: 'chat_id',
130
+ receiveId: chatTarget ?? target,
131
+ text,
132
+ });
133
+ return { id };
134
+ }
135
+ async sendFile(target, file, message, options) {
136
+ const path = await import('node:path');
137
+ const fileName = file.fileName || path.basename(file.filePath);
138
+ // 确定回复目标
139
+ const replyTo = options?.replyTo || (target.startsWith('reply/') ? target.slice('reply/'.length) : undefined);
140
+ const chatTarget = target.startsWith('reply/') ? undefined : target;
141
+ // 自动检测媒体类型
142
+ const mediaKind = detectMediaKind(file.filePath);
143
+ let mediaMessageId;
144
+ if (mediaKind === 'image') {
145
+ // 图片:走 image.create 上传 → image 消息
146
+ if (!this.sender.uploadImage || !this.sender.sendImage) {
147
+ throw new Error('FeishuSender 未实现图片上传能力');
148
+ }
149
+ const imageKey = await this.sender.uploadImage(file.filePath);
150
+ mediaMessageId = await this.sender.sendImage({
151
+ receiveIdType: 'chat_id',
152
+ receiveId: chatTarget ?? '',
153
+ imageKey,
154
+ replyToMessageId: replyTo,
155
+ });
156
+ }
157
+ else {
158
+ // 文件/音频:走 file.create 上传 → file/audio 消息
159
+ if (!this.sender.uploadFile || !this.sender.sendFile) {
160
+ throw new Error('FeishuSender 未实现文件上传能力');
161
+ }
162
+ const fileType = file.fileType || resolveFileType(file.filePath);
163
+ const fileKey = await this.sender.uploadFile({
164
+ filePath: file.filePath,
165
+ fileName,
166
+ fileType,
167
+ duration: file.duration,
168
+ });
169
+ const msgType = mediaKind === 'audio' ? 'audio' : 'file';
170
+ mediaMessageId = await this.sender.sendFile({
171
+ receiveIdType: 'chat_id',
172
+ receiveId: chatTarget ?? '',
173
+ fileKey,
174
+ msgType,
175
+ replyToMessageId: replyTo,
176
+ });
177
+ }
178
+ // 如果有附带文本,额外发一条文本消息
179
+ if (message) {
180
+ const text = this.injectMentions(message, options?.mentions);
181
+ if (replyTo) {
182
+ await this.sender.sendReply({ text, replyToMessageId: replyTo });
183
+ }
184
+ else if (chatTarget) {
185
+ await this.sender.sendText({ receiveIdType: 'chat_id', receiveId: chatTarget, text });
186
+ }
187
+ }
188
+ return { id: mediaMessageId };
189
+ }
190
+ }
191
+ //# sourceMappingURL=feishu-adapter.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 通道适配器接口
3
+ *
4
+ * 统一飞书/Slack/Web/CLI 等不同通道的消息发送。
5
+ * channel.send 根据 channel 字段路由到对应 adapter。
6
+ */
7
+ /**
8
+ * 通道注册表
9
+ *
10
+ * 管理所有已注册的通道适配器。
11
+ * channel.send 通过 channel 字段查找对应的 adapter。
12
+ */
13
+ export class ChannelRegistry {
14
+ adapters = new Map();
15
+ register(adapter) {
16
+ this.adapters.set(adapter.scheme, adapter);
17
+ }
18
+ get(scheme) {
19
+ return this.adapters.get(scheme);
20
+ }
21
+ has(scheme) {
22
+ return this.adapters.has(scheme);
23
+ }
24
+ getSchemes() {
25
+ return Array.from(this.adapters.keys());
26
+ }
27
+ }
28
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 拉取新消息(暂不实现)
3
+ *
4
+ * 当前消息通过 gateway 推送,Loid 不需要主动拉取。
5
+ * 保留接口定义,后续版本实现。
6
+ */
7
+ export async function channelReceive(_input) {
8
+ // 消息通过 gateway 推送,暂不实现拉取
9
+ return { messages: [] };
10
+ }
11
+ //# sourceMappingURL=channel-receive.js.map
@@ -0,0 +1,90 @@
1
+ import { insertAuditEvent, insertMessageLog } from '@team-anya/db';
2
+ /** 默认通道(当前只支持飞书) */
3
+ const DEFAULT_SCHEME = 'feishu';
4
+ /**
5
+ * 向飞书群发消息
6
+ *
7
+ * target 直接填群聊 ID(如 oc_xxx)。
8
+ * 支持发送文件、@ 用户和回复消息。
9
+ */
10
+ export async function channelSend(deps, input) {
11
+ const { db, channelRegistry, logger } = deps;
12
+ const scheme = DEFAULT_SCHEME;
13
+ const chatTarget = input.target;
14
+ const adapter = channelRegistry.get(scheme);
15
+ if (!adapter) {
16
+ const error = `通道 ${scheme} 未注册(已注册: ${channelRegistry.getSchemes().join(', ')})`;
17
+ logger?.error(`[anya:pipeline] channel.send: ${error}`);
18
+ return { sent: false, error };
19
+ }
20
+ const sendOptions = {
21
+ replyTo: input.reply_to,
22
+ mentions: input.mentions,
23
+ };
24
+ try {
25
+ let result;
26
+ if (input.file) {
27
+ if (!adapter.sendFile) {
28
+ const error = `通道 ${scheme} 不支持发送文件`;
29
+ logger?.error(`[anya:pipeline] channel.send: ${error}`);
30
+ return { sent: false, error };
31
+ }
32
+ result = await adapter.sendFile(chatTarget, {
33
+ filePath: input.file.file_path,
34
+ fileName: input.file.file_name,
35
+ fileType: input.file.file_type,
36
+ duration: input.file.duration,
37
+ }, input.message, sendOptions);
38
+ }
39
+ else {
40
+ result = await adapter.send(chatTarget, input.message, sendOptions);
41
+ }
42
+ // 记录出站消息
43
+ try {
44
+ insertMessageLog(db, {
45
+ direction: 'outbound',
46
+ source_type: scheme,
47
+ source_ref: result.id,
48
+ sender: 'anya',
49
+ receiver: chatTarget,
50
+ chat_id: chatTarget,
51
+ chat_type: 'group',
52
+ content: input.file ? `[文件] ${input.file.file_name ?? input.file.file_path} ${input.message}` : input.message,
53
+ message_type: 'message',
54
+ related_task_id: input.task_id ?? null,
55
+ });
56
+ }
57
+ catch (err) {
58
+ logger?.error('[anya:pipeline] channel.send: 记录 message_log 失败:', err);
59
+ }
60
+ // 审计日志
61
+ insertAuditEvent(db, {
62
+ event_type: 'channel_send',
63
+ actor: 'loid',
64
+ task_id: input.task_id ?? undefined,
65
+ summary: `通过 ${scheme} 发消息: ${input.message.slice(0, 100)}`,
66
+ detail: JSON.stringify({
67
+ target: chatTarget,
68
+ has_file: !!input.file,
69
+ has_mentions: !!input.mentions?.length,
70
+ reply_to: input.reply_to ?? null,
71
+ sent: true,
72
+ }),
73
+ });
74
+ deps.onMessageSent?.(chatTarget);
75
+ logger?.info(`[anya:pipeline] [Loid] channel.send (${scheme}${input.file ? ', file' : ''}): "${input.message.slice(0, 80)}"`);
76
+ return { sent: true, message_id: result.id };
77
+ }
78
+ catch (err) {
79
+ logger?.error(`[anya:pipeline] channel.send 失败:`, err);
80
+ insertAuditEvent(db, {
81
+ event_type: 'channel_send_failed',
82
+ actor: 'loid',
83
+ task_id: input.task_id ?? undefined,
84
+ summary: `通道发送失败: ${String(err)}`,
85
+ detail: JSON.stringify({ target: chatTarget, error: String(err) }),
86
+ });
87
+ return { sent: false, error: String(err) };
88
+ }
89
+ }
90
+ //# sourceMappingURL=channel-send.js.map
@@ -0,0 +1,44 @@
1
+ import { insertAuditEvent } from '@team-anya/db';
2
+ /**
3
+ * file.upload - 通用文件上传
4
+ *
5
+ * 与通道无关。当前使用本地文件复制实现。
6
+ * 上传到 $ANYA_HOME/uploads/ 目录,返回本地路径作为 URL。
7
+ */
8
+ export async function fileUpload(deps, input) {
9
+ const fs = await import('node:fs');
10
+ const path = await import('node:path');
11
+ const srcPath = input.file_path;
12
+ const fileName = input.file_name || path.basename(srcPath);
13
+ // 检查源文件是否存在
14
+ if (!fs.existsSync(srcPath)) {
15
+ throw new Error(`文件不存在: ${srcPath}`);
16
+ }
17
+ const stat = fs.statSync(srcPath);
18
+ if (!stat.isFile()) {
19
+ throw new Error(`路径不是文件: ${srcPath}`);
20
+ }
21
+ // 确保上传目录存在
22
+ fs.mkdirSync(deps.uploadDir, { recursive: true });
23
+ // 生成唯一文件名避免冲突
24
+ const timestamp = Date.now();
25
+ const uniqueName = `${timestamp}-${fileName}`;
26
+ const destPath = path.join(deps.uploadDir, uniqueName);
27
+ // 复制文件
28
+ fs.copyFileSync(srcPath, destPath);
29
+ const url = destPath; // 本地路径即 URL
30
+ deps.logger?.info(`[anya:pipeline] file.upload: ${fileName} (${stat.size} bytes) → ${destPath}`);
31
+ // 审计日志
32
+ insertAuditEvent(deps.db, {
33
+ event_type: 'file_upload',
34
+ actor: 'loid',
35
+ summary: `上传文件: ${fileName} (${stat.size} bytes)`,
36
+ detail: JSON.stringify({ src: srcPath, dest: destPath, size: stat.size }),
37
+ });
38
+ return {
39
+ url,
40
+ file_name: fileName,
41
+ file_size: stat.size,
42
+ };
43
+ }
44
+ //# sourceMappingURL=file-upload.js.map