team-anya 0.2.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.
- package/README.md +38 -0
- package/apps/server/dist/broker/cc-broker.js +267 -0
- package/apps/server/dist/cli.js +296 -0
- package/apps/server/dist/config.js +78 -0
- package/apps/server/dist/daemon.js +51 -0
- package/apps/server/dist/franky/context-builder.js +161 -0
- package/apps/server/dist/franky/franky-mcp-server.js +110 -0
- package/apps/server/dist/franky/franky-orchestrator.js +629 -0
- package/apps/server/dist/franky/index.js +5 -0
- package/apps/server/dist/franky/topic-router.js +16 -0
- package/apps/server/dist/gateway/chat-sync.js +135 -0
- package/apps/server/dist/gateway/command-router.js +116 -0
- package/apps/server/dist/gateway/commands/cancel.js +32 -0
- package/apps/server/dist/gateway/commands/help.js +16 -0
- package/apps/server/dist/gateway/commands/index.js +26 -0
- package/apps/server/dist/gateway/commands/restart.js +43 -0
- package/apps/server/dist/gateway/commands/status.js +34 -0
- package/apps/server/dist/gateway/commands/tasks.js +33 -0
- package/apps/server/dist/gateway/feishu-sender.js +508 -0
- package/apps/server/dist/gateway/feishu-ws.js +353 -0
- package/apps/server/dist/gateway/health-monitor.js +154 -0
- package/apps/server/dist/gateway/http.js +1064 -0
- package/apps/server/dist/gateway/media-downloader.js +182 -0
- package/apps/server/dist/gateway/message-events.js +10 -0
- package/apps/server/dist/gateway/message-intake.js +72 -0
- package/apps/server/dist/gateway/message-queue.js +118 -0
- package/apps/server/dist/gateway/session-reader.js +142 -0
- package/apps/server/dist/gateway/ws-push.js +115 -0
- package/apps/server/dist/loid/brain.js +121 -0
- package/apps/server/dist/loid/clarifier.js +162 -0
- package/apps/server/dist/loid/context-builder.js +462 -0
- package/apps/server/dist/loid/mcp-server.js +119 -0
- package/apps/server/dist/loid/memory-settler.js +189 -0
- package/apps/server/dist/loid/opportunity-manager.js +148 -0
- package/apps/server/dist/loid/profile-updater.js +179 -0
- package/apps/server/dist/loid/project-registry.js +192 -0
- package/apps/server/dist/loid/reporter.js +148 -0
- package/apps/server/dist/loid/schemas.js +117 -0
- package/apps/server/dist/loid/self-calibrator.js +314 -0
- package/apps/server/dist/loid/session-manager.js +472 -0
- package/apps/server/dist/loid/session.js +276 -0
- package/apps/server/dist/main.js +528 -0
- package/apps/server/dist/tracing/index.js +2 -0
- package/apps/server/dist/tracing/trace-context.js +92 -0
- package/apps/server/dist/types/message.js +2 -0
- package/apps/server/dist/yor/yor-mcp-server.js +107 -0
- package/apps/server/dist/yor/yor-orchestrator.js +248 -0
- package/apps/web/dist/assets/index-BiiEB0qZ.css +1 -0
- package/apps/web/dist/assets/index-Dnb9LGZd.js +798 -0
- package/apps/web/dist/index.html +13 -0
- package/package.json +42 -0
- package/packages/cc-client/dist/claude-code-backend.js +792 -0
- package/packages/cc-client/dist/index.js +2 -0
- package/packages/cc-client/package.json +11 -0
- package/packages/core/dist/constants.js +60 -0
- package/packages/core/dist/errors.js +35 -0
- package/packages/core/dist/index.js +9 -0
- package/packages/core/dist/office-init.js +190 -0
- package/packages/core/dist/repo-cache.js +70 -0
- package/packages/core/dist/scope/checker.js +114 -0
- package/packages/core/dist/scope/defaults.js +55 -0
- package/packages/core/dist/scope/index.js +3 -0
- package/packages/core/dist/state-machine.js +86 -0
- package/packages/core/dist/types/audit.js +12 -0
- package/packages/core/dist/types/backend.js +2 -0
- package/packages/core/dist/types/commitment.js +17 -0
- package/packages/core/dist/types/communication.js +18 -0
- package/packages/core/dist/types/index.js +9 -0
- package/packages/core/dist/types/opportunity.js +27 -0
- package/packages/core/dist/types/org.js +26 -0
- package/packages/core/dist/types/task.js +46 -0
- package/packages/core/dist/types/workspace.js +39 -0
- package/packages/core/dist/workspace-manager.js +314 -0
- package/packages/core/package.json +10 -0
- package/packages/db/dist/client.js +69 -0
- package/packages/db/dist/index.js +756 -0
- package/packages/db/dist/schema/audit-events.js +13 -0
- package/packages/db/dist/schema/cc-sessions.js +14 -0
- package/packages/db/dist/schema/chats.js +35 -0
- package/packages/db/dist/schema/commitments.js +18 -0
- package/packages/db/dist/schema/communication-events.js +14 -0
- package/packages/db/dist/schema/index.js +14 -0
- package/packages/db/dist/schema/message-log.js +20 -0
- package/packages/db/dist/schema/opportunities.js +23 -0
- package/packages/db/dist/schema/org.js +36 -0
- package/packages/db/dist/schema/projects.js +23 -0
- package/packages/db/dist/schema/tasks.js +51 -0
- package/packages/db/dist/schema/topics.js +22 -0
- package/packages/db/dist/schema/trace-spans.js +19 -0
- package/packages/db/dist/schema/workspaces.js +15 -0
- package/packages/db/package.json +12 -0
- package/packages/db/src/migrations/0000_baseline.sql +251 -0
- package/packages/db/src/migrations/0001_workspaces.sql +19 -0
- package/packages/db/src/migrations/0002_workspace_parent.sql +1 -0
- package/packages/db/src/migrations/0003_chat_context.sql +3 -0
- package/packages/db/src/migrations/meta/_journal.json +34 -0
- package/packages/mcp-tools/dist/index.js +41 -0
- package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
- package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
- package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
- package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
- package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
- package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
- package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
- package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
- package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
- package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
- package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
- package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
- package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
- package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
- package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
- package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
- package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
- package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
- package/packages/mcp-tools/dist/layer2/franky/topic-checkpoint.js +43 -0
- package/packages/mcp-tools/dist/layer2/franky/topic-escalate.js +19 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
- package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
- package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
- package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +206 -0
- package/packages/mcp-tools/dist/layer2/loid/task-escalate-to-topic.js +170 -0
- package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +45 -0
- package/packages/mcp-tools/dist/layer2/loid/topic-close.js +22 -0
- package/packages/mcp-tools/dist/layer2/loid/topic-create.js +60 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +28 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
- package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
- package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
- package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
- package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +203 -0
- package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
- package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
- package/packages/mcp-tools/dist/layer3/channel-send.js +75 -0
- package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
- package/packages/mcp-tools/dist/registry.js +911 -0
- package/packages/mcp-tools/package.json +13 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { execFile as execFileCb } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { updateTask, insertAuditEvent, getTask } from '@team-anya/db';
|
|
4
|
+
const execFile = promisify(execFileCb);
|
|
5
|
+
export async function deliverySubmit(deps, input) {
|
|
6
|
+
const { db, logger } = deps;
|
|
7
|
+
// 确定平台:通过任务的 project_id 查项目配置
|
|
8
|
+
const task = getTask(db, input.task_id);
|
|
9
|
+
let platform = 'local';
|
|
10
|
+
if (task?.project_id && deps.getProjectConfig) {
|
|
11
|
+
const config = await deps.getProjectConfig(task.project_id);
|
|
12
|
+
platform = config.platform;
|
|
13
|
+
}
|
|
14
|
+
logger?.info(`[anya:pipeline] [Loid] delivery.submit ${input.task_id} | platform=${platform} title="${input.title}"`);
|
|
15
|
+
switch (platform) {
|
|
16
|
+
case 'github': {
|
|
17
|
+
const args = ['pr', 'create', '--title', input.title, '--body', input.description];
|
|
18
|
+
if (input.base_branch) {
|
|
19
|
+
args.push('--base', input.base_branch);
|
|
20
|
+
}
|
|
21
|
+
const { stdout } = await execFile('gh', args, { cwd: input.working_dir });
|
|
22
|
+
const prUrl = stdout.trim();
|
|
23
|
+
updateTask(db, input.task_id, { pr_url: prUrl });
|
|
24
|
+
insertAuditEvent(db, {
|
|
25
|
+
event_type: 'pr_created',
|
|
26
|
+
actor: 'loid',
|
|
27
|
+
task_id: input.task_id,
|
|
28
|
+
summary: `GitHub PR 已创建: ${input.title}`,
|
|
29
|
+
detail: JSON.stringify({ pr_url: prUrl, base_branch: input.base_branch, platform }),
|
|
30
|
+
});
|
|
31
|
+
return { task_id: input.task_id, pr_url: prUrl, platform };
|
|
32
|
+
}
|
|
33
|
+
case 'gitlab': {
|
|
34
|
+
const args = [
|
|
35
|
+
'mr', 'create',
|
|
36
|
+
'--title', input.title,
|
|
37
|
+
'--description', input.description,
|
|
38
|
+
'--yes',
|
|
39
|
+
];
|
|
40
|
+
if (input.base_branch) {
|
|
41
|
+
args.push('--target-branch', input.base_branch);
|
|
42
|
+
}
|
|
43
|
+
const { stdout } = await execFile('glab', args, { cwd: input.working_dir });
|
|
44
|
+
// glab 输出格式:通常包含 MR URL
|
|
45
|
+
const mrUrl = stdout.trim().split('\n').pop() ?? stdout.trim();
|
|
46
|
+
updateTask(db, input.task_id, { pr_url: mrUrl });
|
|
47
|
+
insertAuditEvent(db, {
|
|
48
|
+
event_type: 'mr_created',
|
|
49
|
+
actor: 'loid',
|
|
50
|
+
task_id: input.task_id,
|
|
51
|
+
summary: `GitLab MR 已创建: ${input.title}`,
|
|
52
|
+
detail: JSON.stringify({ mr_url: mrUrl, base_branch: input.base_branch, platform }),
|
|
53
|
+
});
|
|
54
|
+
return { task_id: input.task_id, pr_url: mrUrl, platform };
|
|
55
|
+
}
|
|
56
|
+
case 'local': {
|
|
57
|
+
// 纯本地任务,记录最终 commit hash
|
|
58
|
+
let commitHash = 'unknown';
|
|
59
|
+
try {
|
|
60
|
+
const { stdout } = await execFile('git', ['-C', input.working_dir, 'log', '-1', '--format=%H']);
|
|
61
|
+
commitHash = stdout.trim();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// git 命令失败,忽略
|
|
65
|
+
}
|
|
66
|
+
insertAuditEvent(db, {
|
|
67
|
+
event_type: 'delivery_local',
|
|
68
|
+
actor: 'loid',
|
|
69
|
+
task_id: input.task_id,
|
|
70
|
+
summary: `本地交付完成: ${input.title}`,
|
|
71
|
+
detail: JSON.stringify({ commit_hash: commitHash, platform }),
|
|
72
|
+
});
|
|
73
|
+
return { task_id: input.task_id, commit_hash: commitHash, platform };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=delivery-submit.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { fileUpload } from '../../layer3/file-upload.js';
|
|
2
|
+
/**
|
|
3
|
+
* delivery.upload - 上传文件产物
|
|
4
|
+
*
|
|
5
|
+
* 委托给 file.upload 实现。target 参数暂不使用(预留给后续上传到指定位置的场景)。
|
|
6
|
+
*/
|
|
7
|
+
export async function deliveryUpload(deps, input) {
|
|
8
|
+
const result = await fileUpload(deps, {
|
|
9
|
+
file_path: input.file_path,
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
status: 'uploaded',
|
|
13
|
+
url: result.url,
|
|
14
|
+
file_name: result.file_name,
|
|
15
|
+
file_size: result.file_size,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=delivery-upload.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getProject, softDeleteProject, insertAuditEvent } from '@team-anya/db';
|
|
2
|
+
export async function projectRemove(db, _workspacePath, input) {
|
|
3
|
+
const existing = getProject(db, input.project_id);
|
|
4
|
+
if (!existing) {
|
|
5
|
+
return { removed: false, message: `Project ${input.project_id} not found` };
|
|
6
|
+
}
|
|
7
|
+
softDeleteProject(db, input.project_id);
|
|
8
|
+
insertAuditEvent(db, {
|
|
9
|
+
event_type: 'project_removed',
|
|
10
|
+
actor: 'loid',
|
|
11
|
+
summary: `Project ${input.project_id} soft-deleted: ${input.reason}`,
|
|
12
|
+
detail: JSON.stringify({ project_id: input.project_id, reason: input.reason }),
|
|
13
|
+
});
|
|
14
|
+
return { removed: true, message: `Project ${input.project_id} archived` };
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=project-remove.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getProject, upsertProject, syncProjectRepos, insertAuditEvent } from '@team-anya/db';
|
|
2
|
+
export async function projectUpsert(db, _workspacePath, input) {
|
|
3
|
+
const existing = getProject(db, input.project_id);
|
|
4
|
+
const action = existing ? 'updated' : 'created';
|
|
5
|
+
upsertProject(db, {
|
|
6
|
+
project_id: input.project_id,
|
|
7
|
+
name: input.name,
|
|
8
|
+
description: input.description ?? null,
|
|
9
|
+
platform: input.platform ?? 'github',
|
|
10
|
+
claude_md: input.claude_md ?? null,
|
|
11
|
+
});
|
|
12
|
+
const repos = input.repos ?? [];
|
|
13
|
+
if (repos.length > 0) {
|
|
14
|
+
syncProjectRepos(db, input.project_id, repos.map(r => ({
|
|
15
|
+
name: r.name,
|
|
16
|
+
git_url: r.git_url,
|
|
17
|
+
repo_path: r.repo_path,
|
|
18
|
+
default_branch: r.default_branch ?? 'main',
|
|
19
|
+
})));
|
|
20
|
+
}
|
|
21
|
+
insertAuditEvent(db, {
|
|
22
|
+
event_type: `project_${action}`,
|
|
23
|
+
actor: 'loid',
|
|
24
|
+
summary: `Project ${input.project_id} ${action}`,
|
|
25
|
+
detail: JSON.stringify({
|
|
26
|
+
project_id: input.project_id,
|
|
27
|
+
platform: input.platform ?? 'github',
|
|
28
|
+
repo_count: repos.length,
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
return { project_id: input.project_id, action };
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=project-upsert.js.map
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { execFile as execFileCb } from 'node:child_process';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { createTask, updateTask, insertAuditEvent, getTodayMaxSequence, } from '@team-anya/db';
|
|
6
|
+
import { TaskStatus, generateTaskId, assertTransition } from '@team-anya/core';
|
|
7
|
+
const execFile = promisify(execFileCb);
|
|
8
|
+
/**
|
|
9
|
+
* 创建任务、写 brief、自动准备工作区
|
|
10
|
+
*
|
|
11
|
+
* 派工两步走:task.dispatch(MCP) → yor.spawn(MCP)
|
|
12
|
+
* dispatch 内部通过 WorkspaceManager 创建 clone --local 隔离工作区。
|
|
13
|
+
* 工作区准备失败时标记 BLOCKED 并返回 error,不抛异常。
|
|
14
|
+
*/
|
|
15
|
+
export async function taskDispatch(deps, input) {
|
|
16
|
+
const { db, workspacePath, logger } = deps;
|
|
17
|
+
const seq = getTodayMaxSequence(db) + 1;
|
|
18
|
+
const taskId = generateTaskId(seq);
|
|
19
|
+
// 1. 创建任务
|
|
20
|
+
createTask(db, {
|
|
21
|
+
task_id: taskId,
|
|
22
|
+
title: input.title,
|
|
23
|
+
status: TaskStatus.NEW,
|
|
24
|
+
source_type: 'feishu',
|
|
25
|
+
source_ref: input.source_message_id ?? null,
|
|
26
|
+
objective: input.objective,
|
|
27
|
+
acceptance_criteria: input.acceptance_criteria ? JSON.stringify(input.acceptance_criteria) : undefined,
|
|
28
|
+
context: input.context,
|
|
29
|
+
project_id: input.project_id,
|
|
30
|
+
created_by: input.created_by ?? null,
|
|
31
|
+
source_chat_id: input.source_chat_id ?? null,
|
|
32
|
+
});
|
|
33
|
+
// 2. 审计日志
|
|
34
|
+
insertAuditEvent(db, {
|
|
35
|
+
event_type: 'task_dispatched',
|
|
36
|
+
actor: 'loid',
|
|
37
|
+
task_id: taskId,
|
|
38
|
+
summary: `Loid 派工: ${input.title}`,
|
|
39
|
+
detail: JSON.stringify({ brief_length: input.brief.length, project: input.project_id }),
|
|
40
|
+
});
|
|
41
|
+
// 3. 获取项目配置
|
|
42
|
+
const project = deps.getProjectConfig
|
|
43
|
+
? await deps.getProjectConfig(input.project_id)
|
|
44
|
+
: undefined;
|
|
45
|
+
// 传了 project_id 但未在注册表中找到 → 直接 BLOCKED,不静默降级为 adhoc
|
|
46
|
+
if (input.project_id && project?.mode === 'adhoc') {
|
|
47
|
+
// 先写 brief 到旧路径(兼容)
|
|
48
|
+
const taskDir = join(workspacePath, 'yor', 'tasks', taskId);
|
|
49
|
+
await mkdir(taskDir, { recursive: true });
|
|
50
|
+
const briefPath = join(taskDir, 'brief.md');
|
|
51
|
+
await writeFile(briefPath, input.brief, 'utf-8');
|
|
52
|
+
logger?.info(`[anya:pipeline] [Loid] 创建任务 ${taskId} "${input.title}" | project=${input.project_id} 未注册,BLOCKED`);
|
|
53
|
+
assertTransition(TaskStatus.NEW, TaskStatus.BLOCKED);
|
|
54
|
+
updateTask(db, taskId, { status: TaskStatus.BLOCKED });
|
|
55
|
+
insertAuditEvent(db, {
|
|
56
|
+
event_type: 'task_status_changed',
|
|
57
|
+
actor: 'loid',
|
|
58
|
+
task_id: taskId,
|
|
59
|
+
summary: `NEW → BLOCKED: 项目 "${input.project_id}" 未在注册表中找到,请先通过 project.upsert 注册`,
|
|
60
|
+
detail: JSON.stringify({ from: 'NEW', to: 'BLOCKED', reason: 'project_not_found', project_id: input.project_id }),
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
task_id: taskId,
|
|
64
|
+
status: 'blocked',
|
|
65
|
+
brief_path: briefPath,
|
|
66
|
+
error: `项目 "${input.project_id}" 未在注册表中找到。请先调用 project.upsert 注册项目及其仓库配置,然后重新派工。`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
logger?.info(`[anya:pipeline] [Loid] 创建任务 ${taskId} "${input.title}"${input.project_id ? ` | project=${input.project_id} mode=${project?.mode}` : ''}`);
|
|
70
|
+
// 4. 通过 WorkspaceManager 准备 clone --local 工作区
|
|
71
|
+
let workspaceResult;
|
|
72
|
+
let briefPath;
|
|
73
|
+
workspaceResult = await prepareWorkspaceViaManager(taskId, input.project_id, project, deps.workspaceManager, logger);
|
|
74
|
+
if (workspaceResult.ok) {
|
|
75
|
+
briefPath = join(workspaceResult.workingDir, 'brief.md');
|
|
76
|
+
await writeFile(briefPath, input.brief, 'utf-8');
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// 工作区创建失败,brief 写到临时目录
|
|
80
|
+
const taskDir = join(workspacePath, 'yor', 'tasks', taskId);
|
|
81
|
+
await mkdir(taskDir, { recursive: true });
|
|
82
|
+
briefPath = join(taskDir, 'brief.md');
|
|
83
|
+
await writeFile(briefPath, input.brief, 'utf-8');
|
|
84
|
+
}
|
|
85
|
+
if (!workspaceResult.ok) {
|
|
86
|
+
// 工作区准备失败 → NEW → BLOCKED(经状态机校验)
|
|
87
|
+
assertTransition(TaskStatus.NEW, TaskStatus.BLOCKED);
|
|
88
|
+
updateTask(db, taskId, { status: TaskStatus.BLOCKED });
|
|
89
|
+
insertAuditEvent(db, {
|
|
90
|
+
event_type: 'task_status_changed',
|
|
91
|
+
actor: 'loid',
|
|
92
|
+
task_id: taskId,
|
|
93
|
+
summary: `NEW → BLOCKED: 工作区准备失败: ${workspaceResult.error}`,
|
|
94
|
+
detail: JSON.stringify({ from: 'NEW', to: 'BLOCKED', reason: workspaceResult.error }),
|
|
95
|
+
});
|
|
96
|
+
return { task_id: taskId, status: 'blocked', brief_path: briefPath, error: workspaceResult.error };
|
|
97
|
+
}
|
|
98
|
+
// 5. 工作区就绪 → NEW → READY,同时回写工作区信息
|
|
99
|
+
assertTransition(TaskStatus.NEW, TaskStatus.READY);
|
|
100
|
+
const branchName = project?.mode === 'project' ? `feat/anya-${taskId}` : undefined;
|
|
101
|
+
updateTask(db, taskId, {
|
|
102
|
+
status: TaskStatus.READY,
|
|
103
|
+
workspace_path: workspaceResult.workingDir,
|
|
104
|
+
assignee: 'yor',
|
|
105
|
+
...(branchName ? { branch: branchName } : {}),
|
|
106
|
+
...(workspaceResult.workspaceId ? { workspace_id: workspaceResult.workspaceId } : {}),
|
|
107
|
+
});
|
|
108
|
+
insertAuditEvent(db, {
|
|
109
|
+
event_type: 'task_status_changed',
|
|
110
|
+
actor: 'loid',
|
|
111
|
+
task_id: taskId,
|
|
112
|
+
summary: 'NEW → READY: 工作区准备完成',
|
|
113
|
+
detail: JSON.stringify({ from: 'NEW', to: 'READY', workspace_path: workspaceResult.workingDir, branch: branchName, workspace_id: workspaceResult.workspaceId }),
|
|
114
|
+
});
|
|
115
|
+
return { task_id: taskId, status: 'dispatched', brief_path: briefPath, working_dir: workspaceResult.workingDir };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 通过 WorkspaceManager 创建 clone --local 工作区
|
|
119
|
+
*/
|
|
120
|
+
async function prepareWorkspaceViaManager(taskId, projectId, project, wm, logger) {
|
|
121
|
+
const mode = project?.mode ?? 'adhoc';
|
|
122
|
+
try {
|
|
123
|
+
// 1. 创建工作区
|
|
124
|
+
const ws = await wm.createWorkspace({
|
|
125
|
+
role: 'yor',
|
|
126
|
+
ownerType: 'task',
|
|
127
|
+
ownerId: taskId,
|
|
128
|
+
projectId: projectId ?? null,
|
|
129
|
+
});
|
|
130
|
+
const workingDir = ws.path;
|
|
131
|
+
// 2. project mode: 为每个 repo 执行 setupRepo
|
|
132
|
+
if (mode === 'project' && project?.repos) {
|
|
133
|
+
const branchName = `feat/anya-${taskId}`;
|
|
134
|
+
for (const repo of project.repos) {
|
|
135
|
+
const gitUrl = repo.git_url;
|
|
136
|
+
if (!gitUrl) {
|
|
137
|
+
return { ok: false, error: `仓库 "${repo.name}" 未配置 git_url,无法 clone` };
|
|
138
|
+
}
|
|
139
|
+
await wm.setupRepo(ws.id, {
|
|
140
|
+
repoName: repo.name,
|
|
141
|
+
gitUrl,
|
|
142
|
+
baseBranch: repo.default_branch ?? 'main',
|
|
143
|
+
taskBranch: branchName,
|
|
144
|
+
remoteUrl: gitUrl,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// git init 根目录(CC 需要 .git 作为项目边界)
|
|
148
|
+
await execFile('git', ['init', workingDir]);
|
|
149
|
+
// .gitignore 忽略所有 clone 子目录(它们各自有独立的 .git)
|
|
150
|
+
const ignoreContent = project.repos.map(r => `/${r.name}/`).join('\n') + '\n';
|
|
151
|
+
await writeFile(join(workingDir, '.gitignore'), ignoreContent, 'utf-8');
|
|
152
|
+
// 写入 WORKSPACE-REPOS.md(仓库说明,不覆盖角色 CLAUDE.md)
|
|
153
|
+
const reposMd = buildWorkspaceReposMd(taskId, project.repos, project.claudeMd);
|
|
154
|
+
await writeFile(join(workingDir, 'WORKSPACE-REPOS.md'), reposMd, 'utf-8');
|
|
155
|
+
// 提交到 git
|
|
156
|
+
await execFile('git', ['-C', workingDir, 'add', 'WORKSPACE-REPOS.md', '.gitignore']);
|
|
157
|
+
await execFile('git', ['-C', workingDir, 'commit', '-m', 'init: workspace config']);
|
|
158
|
+
logger?.info(`[anya:pipeline] [Loid] 工作区已创建 (${project.repos.length} repos): ${workingDir}`);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// adhoc mode: 只创建工作区,不 setupRepo
|
|
162
|
+
await mkdir(join(workingDir, 'adhoc'), { recursive: true });
|
|
163
|
+
await execFile('git', ['init', workingDir]);
|
|
164
|
+
await writeFile(join(workingDir, '.gitignore'), '*.tmp\nnode_modules/\n.DS_Store\n', 'utf-8');
|
|
165
|
+
await execFile('git', ['-C', workingDir, 'add', '.']);
|
|
166
|
+
await execFile('git', ['-C', workingDir, 'commit', '-m', `chore: init task ${taskId}`]);
|
|
167
|
+
logger?.info(`[anya:pipeline] [Loid] adhoc 工作区已创建: ${workingDir}`);
|
|
168
|
+
}
|
|
169
|
+
return { ok: true, workingDir, workspaceId: ws.id };
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
173
|
+
return { ok: false, error: `工作区准备失败 (${mode}): ${msg}` };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ── 工作区 CLAUDE.md 生成 ──
|
|
177
|
+
function buildWorkspaceReposMd(taskId, repos, projectClaudeMd) {
|
|
178
|
+
const lines = [
|
|
179
|
+
`# 任务工作区 ${taskId}`,
|
|
180
|
+
'',
|
|
181
|
+
'本目录是任务工作区根目录。每个子目录是一个独立的 git clone。',
|
|
182
|
+
'',
|
|
183
|
+
'## 仓库列表',
|
|
184
|
+
'',
|
|
185
|
+
'| 目录 | 分支 |',
|
|
186
|
+
'|------|------|',
|
|
187
|
+
];
|
|
188
|
+
for (const repo of repos) {
|
|
189
|
+
lines.push(`| \`${repo.name}/\` | \`feat/anya-${taskId}\` (基于 ${repo.default_branch}) |`);
|
|
190
|
+
}
|
|
191
|
+
lines.push('');
|
|
192
|
+
lines.push('## 重要');
|
|
193
|
+
lines.push('');
|
|
194
|
+
lines.push('- 每个子目录是独立的 git 仓库(clone),搜索代码时请进入对应子目录');
|
|
195
|
+
lines.push('- git 操作(add/commit/push)必须在对应子目录内执行');
|
|
196
|
+
lines.push('- 不要在根目录执行 git commit');
|
|
197
|
+
lines.push('- 任务产物请读取 `brief.md` 了解详情');
|
|
198
|
+
if (projectClaudeMd) {
|
|
199
|
+
lines.push('');
|
|
200
|
+
lines.push('## 项目技术栈');
|
|
201
|
+
lines.push('');
|
|
202
|
+
lines.push(projectClaudeMd);
|
|
203
|
+
}
|
|
204
|
+
return lines.join('\n') + '\n';
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=task-dispatch.js.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getTask, updateTask, generateTopicId, upsertTopic, getProject, insertAuditEvent, } from '@team-anya/db';
|
|
4
|
+
import { TaskStatus, assertTransition } from '@team-anya/core';
|
|
5
|
+
/**
|
|
6
|
+
* 将已完成的任务升级为话题(Topic),fork 出独立工作区。
|
|
7
|
+
*
|
|
8
|
+
* 核心流程:
|
|
9
|
+
* 1. 校验任务存在且状态为 DONE
|
|
10
|
+
* 2. 校验任务有 workspace_id(无则建议用 topic.create)
|
|
11
|
+
* 3. 生成 topic,创建飞书话题(可选)
|
|
12
|
+
* 4. forkWorkspace:从源工作区 fork 出独立新工作区(franky 角色)
|
|
13
|
+
* 5. topic 指向新工作区,源工作区保持不变(可审计)
|
|
14
|
+
* 6. 任务状态 DONE → ESCALATED_TO_TOPIC
|
|
15
|
+
* 7. 写入 context.md 到新工作区
|
|
16
|
+
*/
|
|
17
|
+
export async function taskEscalateToTopic(deps, input) {
|
|
18
|
+
const { db, workspaceManager, logger } = deps;
|
|
19
|
+
// 1. 校验任务
|
|
20
|
+
const task = getTask(db, input.task_id);
|
|
21
|
+
if (!task) {
|
|
22
|
+
throw new Error(`任务 ${input.task_id} 不存在`);
|
|
23
|
+
}
|
|
24
|
+
if (task.status !== TaskStatus.DONE) {
|
|
25
|
+
throw new Error(`任务 ${input.task_id} 状态为 ${task.status},仅 DONE 状态可升级为话题`);
|
|
26
|
+
}
|
|
27
|
+
// 2. 校验 workspace
|
|
28
|
+
if (!task.workspace_id) {
|
|
29
|
+
throw new Error(`任务 ${input.task_id} 没有关联工作区(workspace_id),无法升级。请使用 topic.create 创建独立话题。`);
|
|
30
|
+
}
|
|
31
|
+
const ws = workspaceManager.getWorkspace(task.workspace_id);
|
|
32
|
+
if (!ws) {
|
|
33
|
+
throw new Error(`工作区 ${task.workspace_id} 不存在`);
|
|
34
|
+
}
|
|
35
|
+
// 3. 生成 topic ID
|
|
36
|
+
const topicId = generateTopicId(db);
|
|
37
|
+
const topicTitle = input.title ?? task.title;
|
|
38
|
+
const topicDescription = input.description ?? `从任务 ${input.task_id}「${task.title}」升级而来`;
|
|
39
|
+
// 4. 自动创建飞书话题(复用 topic-create 的逻辑)
|
|
40
|
+
let threadId = input.thread_id ?? null;
|
|
41
|
+
let rootMessageId = null;
|
|
42
|
+
const chatId = input.chat_id ?? task.source_chat_id ?? null;
|
|
43
|
+
if (!threadId && chatId && deps.threadCreator) {
|
|
44
|
+
let projectLabel = '';
|
|
45
|
+
if (task.project_id) {
|
|
46
|
+
const project = getProject(db, task.project_id);
|
|
47
|
+
projectLabel = project ? `・${project.name}` : `・${task.project_id}`;
|
|
48
|
+
}
|
|
49
|
+
const headerText = `「话题${projectLabel}」${topicTitle}`;
|
|
50
|
+
rootMessageId = await deps.threadCreator.sendText({
|
|
51
|
+
receiveIdType: 'chat_id',
|
|
52
|
+
receiveId: chatId,
|
|
53
|
+
text: headerText,
|
|
54
|
+
});
|
|
55
|
+
const result = await deps.threadCreator.sendReplyInThread({
|
|
56
|
+
text: `📌 话题已开启(从任务 ${input.task_id} 升级),后续讨论请在此话题内进行。`,
|
|
57
|
+
replyToMessageId: rootMessageId,
|
|
58
|
+
});
|
|
59
|
+
threadId = result.threadId;
|
|
60
|
+
}
|
|
61
|
+
// 5. Fork 工作区:从源工作区复制出独立的 franky 工作区
|
|
62
|
+
const forkedWs = await workspaceManager.forkWorkspace(task.workspace_id, {
|
|
63
|
+
newRole: 'franky',
|
|
64
|
+
ownerType: 'topic',
|
|
65
|
+
ownerId: topicId,
|
|
66
|
+
});
|
|
67
|
+
// 6. 写入 topic 记录(指向新的 forked 工作区)
|
|
68
|
+
upsertTopic(db, {
|
|
69
|
+
id: topicId,
|
|
70
|
+
title: topicTitle,
|
|
71
|
+
description: topicDescription,
|
|
72
|
+
status: 'active',
|
|
73
|
+
project_id: task.project_id ?? null,
|
|
74
|
+
workspace_id: forkedWs.id,
|
|
75
|
+
workspace_path: forkedWs.path,
|
|
76
|
+
escalated_from_task_id: input.task_id,
|
|
77
|
+
thread_id: threadId,
|
|
78
|
+
root_message_id: rootMessageId,
|
|
79
|
+
chat_id: chatId,
|
|
80
|
+
created_by: task.created_by ?? null,
|
|
81
|
+
});
|
|
82
|
+
// 7. 任务状态 DONE → ESCALATED_TO_TOPIC(源工作区保持不变,可审计)
|
|
83
|
+
assertTransition(TaskStatus.DONE, TaskStatus.ESCALATED_TO_TOPIC);
|
|
84
|
+
updateTask(db, input.task_id, {
|
|
85
|
+
status: TaskStatus.ESCALATED_TO_TOPIC,
|
|
86
|
+
escalated_to_topic_id: topicId,
|
|
87
|
+
});
|
|
88
|
+
// 8. 写入 context.md 到新工作区
|
|
89
|
+
const contextContent = buildEscalateContext(input.task_id, task, topicId, topicTitle, topicDescription, chatId, rootMessageId, threadId);
|
|
90
|
+
await writeFile(join(forkedWs.path, 'context.md'), contextContent, 'utf-8');
|
|
91
|
+
// 9. 审计日志
|
|
92
|
+
insertAuditEvent(db, {
|
|
93
|
+
event_type: 'task_escalated_to_topic',
|
|
94
|
+
actor: 'loid',
|
|
95
|
+
task_id: input.task_id,
|
|
96
|
+
summary: `任务 ${input.task_id} 升级为话题 ${topicId}「${topicTitle}」`,
|
|
97
|
+
detail: JSON.stringify({
|
|
98
|
+
task_id: input.task_id,
|
|
99
|
+
topic_id: topicId,
|
|
100
|
+
source_workspace_id: task.workspace_id,
|
|
101
|
+
forked_workspace_id: forkedWs.id,
|
|
102
|
+
forked_workspace_path: forkedWs.path,
|
|
103
|
+
thread_id: threadId,
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
logger?.info(`[anya:pipeline] [Loid] 任务 ${input.task_id} 升级为话题 ${topicId}(fork: ${task.workspace_id} → ${forkedWs.id})`);
|
|
107
|
+
return {
|
|
108
|
+
task_id: input.task_id,
|
|
109
|
+
topic_id: topicId,
|
|
110
|
+
workspace_path: forkedWs.path,
|
|
111
|
+
thread_id: threadId,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function buildEscalateContext(taskId, task, topicId, topicTitle, topicDescription, chatId, rootMessageId, threadId) {
|
|
115
|
+
const lines = [
|
|
116
|
+
`# 话题上下文:${topicTitle} (${topicId})`,
|
|
117
|
+
'',
|
|
118
|
+
'## 来源',
|
|
119
|
+
`从任务 ${taskId}「${task.title}」升级而来。`,
|
|
120
|
+
'',
|
|
121
|
+
'## 目标',
|
|
122
|
+
topicDescription,
|
|
123
|
+
'',
|
|
124
|
+
];
|
|
125
|
+
if (task.objective) {
|
|
126
|
+
lines.push('## 原任务目标', task.objective, '');
|
|
127
|
+
}
|
|
128
|
+
if (task.acceptance_criteria) {
|
|
129
|
+
lines.push('## 原验收标准');
|
|
130
|
+
try {
|
|
131
|
+
const criteria = JSON.parse(task.acceptance_criteria);
|
|
132
|
+
if (Array.isArray(criteria)) {
|
|
133
|
+
for (const c of criteria)
|
|
134
|
+
lines.push(`- ${c}`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
lines.push(task.acceptance_criteria);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
lines.push(task.acceptance_criteria);
|
|
142
|
+
}
|
|
143
|
+
lines.push('');
|
|
144
|
+
}
|
|
145
|
+
if (task.context) {
|
|
146
|
+
lines.push('## 背景信息', task.context, '');
|
|
147
|
+
}
|
|
148
|
+
lines.push('## 通讯信息');
|
|
149
|
+
if (chatId)
|
|
150
|
+
lines.push(`- 群聊 ID:\`${chatId}\``);
|
|
151
|
+
if (rootMessageId)
|
|
152
|
+
lines.push(`- 话题根消息 ID:\`${rootMessageId}\``);
|
|
153
|
+
if (threadId)
|
|
154
|
+
lines.push(`- 话题 ID:\`${threadId}\`(仅供参考,channel.send 的 reply_to 请用根消息 ID)`);
|
|
155
|
+
lines.push('');
|
|
156
|
+
lines.push('## 项目信息');
|
|
157
|
+
lines.push(`- 话题 ID:${topicId}`);
|
|
158
|
+
if (task.project_id)
|
|
159
|
+
lines.push(`- 关联项目:${task.project_id}`);
|
|
160
|
+
if (task.branch)
|
|
161
|
+
lines.push(`- 分支:${task.branch}`);
|
|
162
|
+
lines.push('');
|
|
163
|
+
lines.push('## 注意');
|
|
164
|
+
lines.push('- 本工作区从 Yor 任务工作区 fork 而来,代码 clone 已存在');
|
|
165
|
+
lines.push('- 请在已有代码基础上继续迭代,不需要重新 clone');
|
|
166
|
+
lines.push('- 源工作区(Yor)保持不变,仅供审计参考');
|
|
167
|
+
lines.push('');
|
|
168
|
+
return lines.join('\n');
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=task-escalate-to-topic.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getTask, getTasksByStatus, getRecentDoneTasksSince } from '@team-anya/db';
|
|
2
|
+
import { TaskStatus } from '@team-anya/core';
|
|
3
|
+
/**
|
|
4
|
+
* 查询任务状态和历史
|
|
5
|
+
*
|
|
6
|
+
* 可按 task_id 或 status 过滤。不传参数时返回所有活跃任务。
|
|
7
|
+
* mode='recent_done' 时返回最近 30 分钟内完成的任务。
|
|
8
|
+
*/
|
|
9
|
+
export async function taskLookup(db, input) {
|
|
10
|
+
let tasks;
|
|
11
|
+
if (input.mode === 'recent_done') {
|
|
12
|
+
const sinceISO = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
13
|
+
tasks = getRecentDoneTasksSince(db, sinceISO);
|
|
14
|
+
}
|
|
15
|
+
else if (input.task_id) {
|
|
16
|
+
const task = getTask(db, input.task_id);
|
|
17
|
+
tasks = task ? [task] : [];
|
|
18
|
+
}
|
|
19
|
+
else if (input.status) {
|
|
20
|
+
tasks = getTasksByStatus(db, input.status);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
// 默认返回活跃任务
|
|
24
|
+
tasks = [
|
|
25
|
+
...getTasksByStatus(db, TaskStatus.IN_PROGRESS),
|
|
26
|
+
...getTasksByStatus(db, TaskStatus.READY),
|
|
27
|
+
...getTasksByStatus(db, TaskStatus.NEED_CLARIFICATION),
|
|
28
|
+
...getTasksByStatus(db, TaskStatus.DELIVERING),
|
|
29
|
+
...getTasksByStatus(db, TaskStatus.BLOCKED),
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
tasks: tasks.map(t => ({
|
|
34
|
+
task_id: t.task_id,
|
|
35
|
+
title: t.title,
|
|
36
|
+
status: t.status,
|
|
37
|
+
assignee: t.assignee,
|
|
38
|
+
created_at: t.created_at,
|
|
39
|
+
pr_url: t.pr_url ?? null,
|
|
40
|
+
workspace_id: t.workspace_id ?? null,
|
|
41
|
+
escalated_to_topic_id: t.escalated_to_topic_id ?? null,
|
|
42
|
+
})),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=task-lookup.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getTopic, updateTopicStatus, insertAuditEvent, } from '@team-anya/db';
|
|
2
|
+
export async function topicClose(db, input, actor = 'loid') {
|
|
3
|
+
const topic = getTopic(db, input.topic_id);
|
|
4
|
+
if (!topic) {
|
|
5
|
+
return { closed: false, topic_id: input.topic_id, error: `话题 ${input.topic_id} 不存在` };
|
|
6
|
+
}
|
|
7
|
+
if (topic.status === 'closed') {
|
|
8
|
+
return { closed: false, topic_id: input.topic_id, error: `话题已处于 closed 状态` };
|
|
9
|
+
}
|
|
10
|
+
updateTopicStatus(db, input.topic_id, 'closed');
|
|
11
|
+
insertAuditEvent(db, {
|
|
12
|
+
event_type: 'topic_closed',
|
|
13
|
+
actor,
|
|
14
|
+
summary: `关闭话题「${topic.title}」(${input.topic_id})`,
|
|
15
|
+
detail: JSON.stringify({
|
|
16
|
+
topic_id: input.topic_id,
|
|
17
|
+
close_summary: input.summary,
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
return { closed: true, topic_id: input.topic_id };
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=topic-close.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { generateTopicId, upsertTopic, getTopic, getProject, insertAuditEvent, patchMessageLogMetadata, } from '@team-anya/db';
|
|
2
|
+
export async function topicCreate(db, input, threadCreator) {
|
|
3
|
+
const topicId = generateTopicId(db);
|
|
4
|
+
let threadId = input.thread_id ?? null;
|
|
5
|
+
let rootMessageId = null;
|
|
6
|
+
// 自动创建话题:先发标题消息,再回复开启话题线程
|
|
7
|
+
if (!threadId && input.chat_id && threadCreator) {
|
|
8
|
+
// 1. 发送话题标题消息(作为飞书话题根消息)
|
|
9
|
+
let projectLabel = '';
|
|
10
|
+
if (input.project_id) {
|
|
11
|
+
const project = getProject(db, input.project_id);
|
|
12
|
+
projectLabel = project ? `・${project.name}` : `・${input.project_id}`;
|
|
13
|
+
}
|
|
14
|
+
const headerText = `「话题${projectLabel}」${input.title}`;
|
|
15
|
+
rootMessageId = await threadCreator.sendText({
|
|
16
|
+
receiveIdType: 'chat_id',
|
|
17
|
+
receiveId: input.chat_id,
|
|
18
|
+
text: headerText,
|
|
19
|
+
});
|
|
20
|
+
// 2. 回复标题消息,开启话题线程
|
|
21
|
+
const result = await threadCreator.sendReplyInThread({
|
|
22
|
+
text: `📌 话题已开启,后续讨论请在此话题内进行。`,
|
|
23
|
+
replyToMessageId: rootMessageId,
|
|
24
|
+
});
|
|
25
|
+
threadId = result.threadId;
|
|
26
|
+
// 回写 thread_id 到标题消息的 metadata,确保后续以它为 replyTo 的消息能继承 thread_id
|
|
27
|
+
if (threadId && rootMessageId) {
|
|
28
|
+
patchMessageLogMetadata(db, rootMessageId, { thread_id: threadId });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
upsertTopic(db, {
|
|
32
|
+
id: topicId,
|
|
33
|
+
title: input.title,
|
|
34
|
+
description: input.description ?? null,
|
|
35
|
+
status: 'active',
|
|
36
|
+
project_id: input.project_id ?? null,
|
|
37
|
+
thread_id: threadId,
|
|
38
|
+
root_message_id: rootMessageId,
|
|
39
|
+
chat_id: input.chat_id ?? null,
|
|
40
|
+
created_by: input.created_by ?? null,
|
|
41
|
+
});
|
|
42
|
+
insertAuditEvent(db, {
|
|
43
|
+
event_type: 'topic_created',
|
|
44
|
+
actor: 'loid',
|
|
45
|
+
summary: `创建话题「${input.title}」(${topicId})`,
|
|
46
|
+
detail: JSON.stringify({
|
|
47
|
+
topic_id: topicId,
|
|
48
|
+
project_id: input.project_id,
|
|
49
|
+
thread_id: threadId,
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
const topic = getTopic(db, topicId);
|
|
53
|
+
return {
|
|
54
|
+
topic_id: topic.id,
|
|
55
|
+
title: topic.title,
|
|
56
|
+
status: topic.status,
|
|
57
|
+
thread_id: topic.thread_id,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=topic-create.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function yorApprove(deps, input) {
|
|
2
|
+
const { yorOrchestrator, logger } = deps;
|
|
3
|
+
logger?.info(`[anya:pipeline] [Loid] yor.approve task=${input.task_id}`);
|
|
4
|
+
const message = input.message ?? '审核通过,任务完成。你可以做最后的清理工作。';
|
|
5
|
+
yorOrchestrator.approveAndShutdown(input.task_id, message);
|
|
6
|
+
return { task_id: input.task_id, approved: true };
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=yor-approve.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export async function yorKill(deps, input) {
|
|
2
|
+
const { yorOrchestrator, logger } = deps;
|
|
3
|
+
logger?.info(`[anya:pipeline] [Loid] yor.kill task=${input.task_id}`);
|
|
4
|
+
const result = await yorOrchestrator.kill(input.task_id);
|
|
5
|
+
return { task_id: input.task_id, killed: result.success };
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=yor-kill.js.map
|