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,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated 此模块已废弃,改为 Agent 自主判断/使用 memory 系统。
|
|
3
|
+
* 保留此文件仅为兼容历史数据,新代码请勿使用。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { IntentLevel } from '../constants.js';
|
|
7
|
+
export const communicationEventSchema = z.object({
|
|
8
|
+
id: z.number().optional(),
|
|
9
|
+
source_type: z.enum(['feishu', 'file']),
|
|
10
|
+
source_ref: z.string().nullable().optional(),
|
|
11
|
+
intent_level: z.nativeEnum(IntentLevel),
|
|
12
|
+
sender: z.string().nullable().optional(),
|
|
13
|
+
summary: z.string(),
|
|
14
|
+
raw_content_path: z.string().nullable().optional(),
|
|
15
|
+
opportunity_id: z.string().nullable().optional(),
|
|
16
|
+
created_at: z.string().datetime().optional(),
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=communication.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './task.js';
|
|
2
|
+
export * from './commitment.js';
|
|
3
|
+
export * from './opportunity.js';
|
|
4
|
+
export * from './audit.js';
|
|
5
|
+
export * from './communication.js';
|
|
6
|
+
export * from './org.js';
|
|
7
|
+
export * from './backend.js';
|
|
8
|
+
export * from './workspace.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated 此模块已废弃,改为 Agent 自主判断/使用 memory 系统。
|
|
3
|
+
* 保留此文件仅为兼容历史数据,新代码请勿使用。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { OpportunityStatus } from '../constants.js';
|
|
7
|
+
export const opportunityScoreSchema = z.object({
|
|
8
|
+
impact: z.number().min(0).max(1),
|
|
9
|
+
urgency: z.number().min(0).max(1),
|
|
10
|
+
feasibility: z.number().min(0).max(1),
|
|
11
|
+
permission: z.number().min(0).max(1),
|
|
12
|
+
});
|
|
13
|
+
export const opportunitySchema = z.object({
|
|
14
|
+
id: z.string(),
|
|
15
|
+
status: z.nativeEnum(OpportunityStatus).default('detected'),
|
|
16
|
+
source_ref: z.string().nullable().optional(),
|
|
17
|
+
summary: z.string(),
|
|
18
|
+
score_impact: z.number().nullable().optional(),
|
|
19
|
+
score_urgency: z.number().nullable().optional(),
|
|
20
|
+
score_feasibility: z.number().nullable().optional(),
|
|
21
|
+
score_permission: z.number().nullable().optional(),
|
|
22
|
+
total_score: z.number().nullable().optional(),
|
|
23
|
+
converted_task_id: z.string().nullable().optional(),
|
|
24
|
+
detected_at: z.string().datetime().optional(),
|
|
25
|
+
claimed_at: z.string().datetime().nullable().optional(),
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=opportunity.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const orgMemberSchema = z.object({
|
|
3
|
+
member_id: z.string(),
|
|
4
|
+
name: z.string(),
|
|
5
|
+
role: z.string().nullable().optional(),
|
|
6
|
+
team: z.string().nullable().optional(),
|
|
7
|
+
timezone: z.string().nullable().optional(),
|
|
8
|
+
profile_path: z.string().nullable().optional(),
|
|
9
|
+
response_pattern: z.string().nullable().optional(),
|
|
10
|
+
updated_at: z.string().datetime().optional(),
|
|
11
|
+
});
|
|
12
|
+
export const ownershipSchema = z.object({
|
|
13
|
+
id: z.number().optional(),
|
|
14
|
+
scope: z.enum(['repo', 'module']),
|
|
15
|
+
target: z.string(),
|
|
16
|
+
member_id: z.string(),
|
|
17
|
+
role: z.string().default('owner'),
|
|
18
|
+
});
|
|
19
|
+
export const escalationRuleSchema = z.object({
|
|
20
|
+
id: z.number().optional(),
|
|
21
|
+
scope: z.string().default('global'),
|
|
22
|
+
after_minutes: z.number(),
|
|
23
|
+
action: z.enum(['notify', 'escalate']),
|
|
24
|
+
targets: z.array(z.string()),
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=org.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { TaskStatus } from '../constants.js';
|
|
3
|
+
// ── Zod Schemas ──
|
|
4
|
+
export const taskSourceSchema = z.object({
|
|
5
|
+
type: z.enum(['file', 'feishu', 'meeting_note', 'manual']),
|
|
6
|
+
ref: z.string().optional(),
|
|
7
|
+
received_at: z.string().datetime().optional(),
|
|
8
|
+
});
|
|
9
|
+
export const clarificationSchema = z.object({
|
|
10
|
+
id: z.number().optional(),
|
|
11
|
+
question: z.string(),
|
|
12
|
+
answer: z.string().nullable().optional(),
|
|
13
|
+
asked_by: z.string().default('loid'),
|
|
14
|
+
answered_by: z.string().nullable().optional(),
|
|
15
|
+
asked_at: z.string().datetime().optional(),
|
|
16
|
+
answered_at: z.string().datetime().nullable().optional(),
|
|
17
|
+
});
|
|
18
|
+
export const taskSchema = z.object({
|
|
19
|
+
task_id: z.string(),
|
|
20
|
+
title: z.string(),
|
|
21
|
+
status: z.nativeEnum(TaskStatus),
|
|
22
|
+
// 来源
|
|
23
|
+
source_type: z.enum(['file', 'feishu', 'meeting_note', 'manual']),
|
|
24
|
+
source_ref: z.string().nullable().optional(),
|
|
25
|
+
// 需求定义
|
|
26
|
+
objective: z.string().nullable().optional(),
|
|
27
|
+
acceptance_criteria: z.array(z.string()).optional(),
|
|
28
|
+
context: z.string().nullable().optional(),
|
|
29
|
+
// 执行指引
|
|
30
|
+
execution_guidance: z.string().nullable().optional(),
|
|
31
|
+
// 执行信息
|
|
32
|
+
project_id: z.string().nullable().optional(),
|
|
33
|
+
assignee: z.string().nullable().optional(),
|
|
34
|
+
deliverable_summary: z.string().nullable().optional(),
|
|
35
|
+
// 重试与阻塞
|
|
36
|
+
retry_count: z.number().default(0),
|
|
37
|
+
max_retries: z.number().default(3),
|
|
38
|
+
blocked_reason: z.string().nullable().optional(),
|
|
39
|
+
blocked_since: z.string().datetime().nullable().optional(),
|
|
40
|
+
// 文件路径
|
|
41
|
+
workspace_path: z.string().nullable().optional(),
|
|
42
|
+
// 时间戳
|
|
43
|
+
created_at: z.string().datetime().optional(),
|
|
44
|
+
updated_at: z.string().datetime().optional(),
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=task.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const WorkspaceStatus = {
|
|
3
|
+
ACTIVE: 'active',
|
|
4
|
+
ARCHIVED: 'archived',
|
|
5
|
+
};
|
|
6
|
+
export const WorkspaceRole = {
|
|
7
|
+
YOR: 'yor',
|
|
8
|
+
FRANKY: 'franky',
|
|
9
|
+
};
|
|
10
|
+
export const WorkspaceOwnerType = {
|
|
11
|
+
TASK: 'task',
|
|
12
|
+
TOPIC: 'topic',
|
|
13
|
+
};
|
|
14
|
+
// .workspace.json schema
|
|
15
|
+
export const workspaceMetaSchema = z.object({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
created_at: z.string(),
|
|
18
|
+
project_id: z.string().nullable(),
|
|
19
|
+
repos: z.array(z.object({
|
|
20
|
+
name: z.string(),
|
|
21
|
+
branch: z.string(),
|
|
22
|
+
clone_path: z.string(),
|
|
23
|
+
remote_url: z.string().optional(),
|
|
24
|
+
})),
|
|
25
|
+
current: z.object({
|
|
26
|
+
role: z.enum(['yor', 'franky']),
|
|
27
|
+
owner_type: z.enum(['task', 'topic']),
|
|
28
|
+
owner_id: z.string(),
|
|
29
|
+
since: z.string(),
|
|
30
|
+
}),
|
|
31
|
+
history: z.array(z.object({
|
|
32
|
+
role: z.enum(['yor', 'franky']),
|
|
33
|
+
owner_type: z.enum(['task', 'topic']),
|
|
34
|
+
owner_id: z.string(),
|
|
35
|
+
from: z.string(),
|
|
36
|
+
to: z.string(),
|
|
37
|
+
})),
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { execFile as execFileCb } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { mkdir, copyFile, writeFile, readFile, rm, unlink, stat, cp, readdir } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { workspaceMetaSchema } from './types/workspace.js';
|
|
7
|
+
const execFile = promisify(execFileCb);
|
|
8
|
+
// ── 角色文件映射 ──
|
|
9
|
+
/** 所有角色共享的文档(从 templateDir 根复制) */
|
|
10
|
+
const SHARED_DOCS = ['CHARTER.md', 'PROTOCOL.md', 'TOOLS.md'];
|
|
11
|
+
/** 角色专属文件(从 templateDir/<role>/ 复制) */
|
|
12
|
+
const ROLE_FILES = {
|
|
13
|
+
yor: ['CLAUDE.md', 'PROFILE.md', 'PLAYBOOK.md', 'SELF-HEAL.md'],
|
|
14
|
+
franky: ['CLAUDE.md', 'PROFILE.md', 'PLAYBOOK.md'],
|
|
15
|
+
};
|
|
16
|
+
/** Yor 独有的文件(切换到 franky 时需删除) */
|
|
17
|
+
const YOR_ONLY_FILES = ['SELF-HEAL.md'];
|
|
18
|
+
// ── WorkspaceManager ──
|
|
19
|
+
export class WorkspaceManager {
|
|
20
|
+
store;
|
|
21
|
+
config;
|
|
22
|
+
repoCache;
|
|
23
|
+
constructor(store, config, repoCache) {
|
|
24
|
+
this.store = store;
|
|
25
|
+
this.config = config;
|
|
26
|
+
this.repoCache = repoCache;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 创建工作区:目录 + 共享文档 + 角色文件 + .workspace.json + DB 记录
|
|
30
|
+
*/
|
|
31
|
+
async createWorkspace(opts) {
|
|
32
|
+
const id = `WS-${randomUUID().slice(0, 8)}`;
|
|
33
|
+
const wsPath = join(this.config.workspacesPath, opts.role, opts.ownerId);
|
|
34
|
+
// 创建目录结构
|
|
35
|
+
await mkdir(wsPath, { recursive: true });
|
|
36
|
+
await mkdir(join(wsPath, 'feedback'), { recursive: true });
|
|
37
|
+
await mkdir(join(wsPath, 'checkpoints'), { recursive: true });
|
|
38
|
+
// 复制共享文档
|
|
39
|
+
for (const doc of SHARED_DOCS) {
|
|
40
|
+
await this.safeCopy(join(this.config.templateDir, doc), join(wsPath, doc));
|
|
41
|
+
}
|
|
42
|
+
// 复制角色文件
|
|
43
|
+
await this.copyRoleFiles(wsPath, opts.role);
|
|
44
|
+
// 写 .workspace.json
|
|
45
|
+
const now = new Date().toISOString();
|
|
46
|
+
const meta = {
|
|
47
|
+
id,
|
|
48
|
+
created_at: now,
|
|
49
|
+
project_id: opts.projectId ?? null,
|
|
50
|
+
repos: [],
|
|
51
|
+
current: {
|
|
52
|
+
role: opts.role,
|
|
53
|
+
owner_type: opts.ownerType,
|
|
54
|
+
owner_id: opts.ownerId,
|
|
55
|
+
since: now,
|
|
56
|
+
},
|
|
57
|
+
history: [],
|
|
58
|
+
};
|
|
59
|
+
await writeFile(join(wsPath, '.workspace.json'), JSON.stringify(meta, null, 2));
|
|
60
|
+
// 写 DB
|
|
61
|
+
const row = this.store.create({
|
|
62
|
+
id,
|
|
63
|
+
path: wsPath,
|
|
64
|
+
project_id: opts.projectId ?? null,
|
|
65
|
+
current_role: opts.role,
|
|
66
|
+
current_owner_type: opts.ownerType,
|
|
67
|
+
current_owner_id: opts.ownerId,
|
|
68
|
+
});
|
|
69
|
+
this.config.logger?.info(`[WorkspaceManager] 工作区已创建: ${id} (${opts.role}/${opts.ownerType}/${opts.ownerId})`);
|
|
70
|
+
return row;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 在工作区内设置代码仓库:clone --local + 修正 remote + 创建任务分支
|
|
74
|
+
*/
|
|
75
|
+
async setupRepo(workspaceId, opts) {
|
|
76
|
+
const ws = this.store.get(workspaceId);
|
|
77
|
+
if (!ws)
|
|
78
|
+
throw new Error(`工作区不存在: ${workspaceId}`);
|
|
79
|
+
const wsPath = ws.path;
|
|
80
|
+
const cloneDest = join(wsPath, opts.repoName);
|
|
81
|
+
const baseBranch = opts.baseBranch ?? 'main';
|
|
82
|
+
// 确保 repos/ 缓存新鲜
|
|
83
|
+
const cachedRepoPath = await this.repoCache.ensureRepo(opts.repoName, opts.gitUrl);
|
|
84
|
+
// clone --local(利用 hardlink 加速)
|
|
85
|
+
await execFile('git', [
|
|
86
|
+
'clone', '--local',
|
|
87
|
+
'--branch', baseBranch,
|
|
88
|
+
cachedRepoPath,
|
|
89
|
+
cloneDest,
|
|
90
|
+
]);
|
|
91
|
+
// 修正 remote 指向真实远程(而非 repos/ 本地路径)
|
|
92
|
+
await execFile('git', ['-C', cloneDest, 'remote', 'set-url', 'origin', opts.remoteUrl]);
|
|
93
|
+
// 创建任务分支
|
|
94
|
+
await execFile('git', ['-C', cloneDest, 'checkout', '-b', opts.taskBranch]);
|
|
95
|
+
// 更新 .workspace.json
|
|
96
|
+
const meta = await this.readMeta(wsPath);
|
|
97
|
+
meta.repos.push({
|
|
98
|
+
name: opts.repoName,
|
|
99
|
+
branch: opts.taskBranch,
|
|
100
|
+
clone_path: cloneDest,
|
|
101
|
+
remote_url: opts.remoteUrl,
|
|
102
|
+
});
|
|
103
|
+
await this.writeMeta(wsPath, meta);
|
|
104
|
+
this.config.logger?.info(`[WorkspaceManager] 仓库已设置: ${opts.repoName} @ ${opts.taskBranch}`);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 切换工作区角色:替换角色文件 + 更新元数据 + 更新 DB
|
|
108
|
+
*/
|
|
109
|
+
async switchRole(workspaceId, newRole) {
|
|
110
|
+
const ws = this.store.get(workspaceId);
|
|
111
|
+
if (!ws)
|
|
112
|
+
throw new Error(`工作区不存在: ${workspaceId}`);
|
|
113
|
+
const wsPath = ws.path;
|
|
114
|
+
const oldRole = ws.current_role;
|
|
115
|
+
if (oldRole === newRole)
|
|
116
|
+
return;
|
|
117
|
+
// 删除旧角色独有文件
|
|
118
|
+
if (oldRole === 'yor') {
|
|
119
|
+
for (const file of YOR_ONLY_FILES) {
|
|
120
|
+
await this.safeUnlink(join(wsPath, file));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// 复制新角色文件(覆盖同名文件)
|
|
124
|
+
await this.copyRoleFiles(wsPath, newRole);
|
|
125
|
+
// franky 需要 context.md
|
|
126
|
+
if (newRole === 'franky') {
|
|
127
|
+
const contextPath = join(wsPath, 'context.md');
|
|
128
|
+
if (!(await this.fileExists(contextPath))) {
|
|
129
|
+
await writeFile(contextPath, '# Topic Context\n\n> 从任务升级而来,请继续迭代。\n');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// 更新 .workspace.json
|
|
133
|
+
const now = new Date().toISOString();
|
|
134
|
+
const meta = await this.readMeta(wsPath);
|
|
135
|
+
meta.history.push({
|
|
136
|
+
role: meta.current.role,
|
|
137
|
+
owner_type: meta.current.owner_type,
|
|
138
|
+
owner_id: meta.current.owner_id,
|
|
139
|
+
from: meta.current.since,
|
|
140
|
+
to: now,
|
|
141
|
+
});
|
|
142
|
+
meta.current = {
|
|
143
|
+
...meta.current,
|
|
144
|
+
role: newRole,
|
|
145
|
+
since: now,
|
|
146
|
+
};
|
|
147
|
+
await this.writeMeta(wsPath, meta);
|
|
148
|
+
// 更新 DB
|
|
149
|
+
this.store.update(workspaceId, { current_role: newRole });
|
|
150
|
+
this.config.logger?.info(`[WorkspaceManager] 角色切换: ${oldRole} → ${newRole} (${workspaceId})`);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Fork 工作区:从源工作区复制出独立的新工作区,应用新角色文件。
|
|
154
|
+
* 源工作区保持不变(可供审计)。
|
|
155
|
+
*
|
|
156
|
+
* repo 目录使用 git clone --local(hardlink 优化),其余文件直接复制。
|
|
157
|
+
*/
|
|
158
|
+
async forkWorkspace(sourceId, opts) {
|
|
159
|
+
const src = this.store.get(sourceId);
|
|
160
|
+
if (!src)
|
|
161
|
+
throw new Error(`源工作区不存在: ${sourceId}`);
|
|
162
|
+
const newId = `WS-${randomUUID().slice(0, 8)}`;
|
|
163
|
+
const newPath = join(this.config.workspacesPath, opts.newRole, opts.ownerId);
|
|
164
|
+
// 读取源工作区元数据
|
|
165
|
+
const srcMeta = await this.readMeta(src.path);
|
|
166
|
+
// 创建新工作区目录
|
|
167
|
+
await mkdir(newPath, { recursive: true });
|
|
168
|
+
await mkdir(join(newPath, 'feedback'), { recursive: true });
|
|
169
|
+
await mkdir(join(newPath, 'checkpoints'), { recursive: true });
|
|
170
|
+
// 复制非 repo 文件(共享文档、brief.md 等)
|
|
171
|
+
const entries = await readdir(src.path, { withFileTypes: true });
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
const srcItem = join(src.path, entry.name);
|
|
174
|
+
const destItem = join(newPath, entry.name);
|
|
175
|
+
if (entry.isDirectory()) {
|
|
176
|
+
// repo 目录用 git clone --local(hardlink 优化)
|
|
177
|
+
const isRepo = srcMeta.repos.some(r => r.name === entry.name);
|
|
178
|
+
if (isRepo) {
|
|
179
|
+
await execFile('git', ['clone', '--local', srcItem, destItem]);
|
|
180
|
+
// remote 已经是正确的(源工作区已修正过)
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
// feedback/checkpoints 等普通目录直接复制
|
|
184
|
+
if (entry.name !== '.git') {
|
|
185
|
+
await cp(srcItem, destItem, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// 普通文件直接复制
|
|
190
|
+
await copyFile(srcItem, destItem);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// 覆盖角色文件为新角色
|
|
194
|
+
await this.copyRoleFiles(newPath, opts.newRole);
|
|
195
|
+
// 删除旧角色独有文件(如 Yor 的 SELF-HEAL.md)
|
|
196
|
+
if (src.current_role === 'yor' && opts.newRole !== 'yor') {
|
|
197
|
+
for (const file of YOR_ONLY_FILES) {
|
|
198
|
+
await this.safeUnlink(join(newPath, file));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Franky 需要 context.md(如果源工作区没有的话)
|
|
202
|
+
if (opts.newRole === 'franky') {
|
|
203
|
+
const contextPath = join(newPath, 'context.md');
|
|
204
|
+
if (!(await this.fileExists(contextPath))) {
|
|
205
|
+
await writeFile(contextPath, '# Topic Context\n\n> 从任务升级而来,请继续迭代。\n');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// 写新的 .workspace.json
|
|
209
|
+
const now = new Date().toISOString();
|
|
210
|
+
const newMeta = {
|
|
211
|
+
id: newId,
|
|
212
|
+
created_at: now,
|
|
213
|
+
project_id: srcMeta.project_id,
|
|
214
|
+
repos: srcMeta.repos.map(r => ({
|
|
215
|
+
...r,
|
|
216
|
+
clone_path: join(newPath, r.name),
|
|
217
|
+
})),
|
|
218
|
+
current: {
|
|
219
|
+
role: opts.newRole,
|
|
220
|
+
owner_type: opts.ownerType,
|
|
221
|
+
owner_id: opts.ownerId,
|
|
222
|
+
since: now,
|
|
223
|
+
},
|
|
224
|
+
history: [],
|
|
225
|
+
};
|
|
226
|
+
await this.writeMeta(newPath, newMeta);
|
|
227
|
+
// 写 DB
|
|
228
|
+
const row = this.store.create({
|
|
229
|
+
id: newId,
|
|
230
|
+
path: newPath,
|
|
231
|
+
project_id: src.project_id,
|
|
232
|
+
current_role: opts.newRole,
|
|
233
|
+
current_owner_type: opts.ownerType,
|
|
234
|
+
current_owner_id: opts.ownerId,
|
|
235
|
+
parent_workspace_id: sourceId,
|
|
236
|
+
});
|
|
237
|
+
// 源工作区标记为 handed_off
|
|
238
|
+
this.store.update(sourceId, { status: 'handed_off' });
|
|
239
|
+
this.config.logger?.info(`[WorkspaceManager] 工作区 fork: ${sourceId} → ${newId} (${src.current_role} → ${opts.newRole})`);
|
|
240
|
+
return row;
|
|
241
|
+
}
|
|
242
|
+
/** 获取工作区记录 */
|
|
243
|
+
getWorkspace(id) {
|
|
244
|
+
return this.store.get(id);
|
|
245
|
+
}
|
|
246
|
+
/** 按 owner 查找工作区 */
|
|
247
|
+
findByOwner(ownerId) {
|
|
248
|
+
return this.store.findByOwner(ownerId);
|
|
249
|
+
}
|
|
250
|
+
/** 变更工作区 owner(task → topic 等场景) */
|
|
251
|
+
updateWorkspaceOwner(id, ownerType, ownerId) {
|
|
252
|
+
this.store.update(id, { current_owner_type: ownerType, current_owner_id: ownerId });
|
|
253
|
+
this.config.logger?.info(`[WorkspaceManager] owner 变更: ${ownerType}/${ownerId} (${id})`);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* 归档工作区:标记 archived + 删除目录
|
|
257
|
+
*/
|
|
258
|
+
async archiveWorkspace(id) {
|
|
259
|
+
const ws = this.store.get(id);
|
|
260
|
+
if (!ws)
|
|
261
|
+
throw new Error(`工作区不存在: ${id}`);
|
|
262
|
+
// 更新 DB 状态
|
|
263
|
+
this.store.update(id, { status: 'archived' });
|
|
264
|
+
// 删除目录
|
|
265
|
+
await rm(ws.path, { recursive: true, force: true });
|
|
266
|
+
this.config.logger?.info(`[WorkspaceManager] 工作区已归档: ${id}`);
|
|
267
|
+
}
|
|
268
|
+
// ── Private helpers ──
|
|
269
|
+
async copyRoleFiles(wsPath, role) {
|
|
270
|
+
const files = ROLE_FILES[role] ?? [];
|
|
271
|
+
for (const file of files) {
|
|
272
|
+
await this.safeCopy(join(this.config.templateDir, role, file), join(wsPath, file));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async safeCopy(src, dest) {
|
|
276
|
+
try {
|
|
277
|
+
await copyFile(src, dest);
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
if (err.code === 'ENOENT') {
|
|
281
|
+
this.config.logger?.info(`[WorkspaceManager] 模板文件不存在,跳过: ${src}`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
throw err;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async safeUnlink(path) {
|
|
288
|
+
try {
|
|
289
|
+
await unlink(path);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
if (err.code === 'ENOENT')
|
|
293
|
+
return;
|
|
294
|
+
throw err;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async readMeta(wsPath) {
|
|
298
|
+
const raw = await readFile(join(wsPath, '.workspace.json'), 'utf-8');
|
|
299
|
+
return workspaceMetaSchema.parse(JSON.parse(raw));
|
|
300
|
+
}
|
|
301
|
+
async writeMeta(wsPath, meta) {
|
|
302
|
+
await writeFile(join(wsPath, '.workspace.json'), JSON.stringify(meta, null, 2));
|
|
303
|
+
}
|
|
304
|
+
async fileExists(path) {
|
|
305
|
+
try {
|
|
306
|
+
await stat(path);
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=workspace-manager.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { resolve, dirname } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { readFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import Database from 'better-sqlite3';
|
|
5
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
6
|
+
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
|
|
7
|
+
import * as schema from './schema/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* 获取 migrations 目录的绝对路径
|
|
10
|
+
*
|
|
11
|
+
* 兼容两种运行方式:
|
|
12
|
+
* - 源码运行(vitest/tsx):import.meta.url → src/client.ts → src/migrations/
|
|
13
|
+
* - 编译后运行(node dist/):import.meta.url → dist/client.js → 回溯到 src/migrations/
|
|
14
|
+
*/
|
|
15
|
+
function getMigrationsPath() {
|
|
16
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
// 优先尝试同级 migrations 目录(源码模式)
|
|
18
|
+
const sameLevelPath = resolve(currentDir, 'migrations');
|
|
19
|
+
if (existsSync(resolve(sameLevelPath, 'meta', '_journal.json'))) {
|
|
20
|
+
return sameLevelPath;
|
|
21
|
+
}
|
|
22
|
+
// 回溯到 src/migrations(编译后 dist/ 模式)
|
|
23
|
+
const srcPath = resolve(currentDir, '..', 'src', 'migrations');
|
|
24
|
+
if (existsSync(resolve(srcPath, 'meta', '_journal.json'))) {
|
|
25
|
+
return srcPath;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`找不到 migrations 目录,已尝试:\n - ${sameLevelPath}\n - ${srcPath}`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 创建数据库连接并自动运行 migration
|
|
31
|
+
*/
|
|
32
|
+
export function createDB(dbPath) {
|
|
33
|
+
// 确保数据库文件所在目录存在
|
|
34
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
35
|
+
const sqlite = new Database(dbPath);
|
|
36
|
+
// 启用 WAL 模式(支持并发读)
|
|
37
|
+
sqlite.pragma('journal_mode = WAL');
|
|
38
|
+
sqlite.pragma('foreign_keys = ON');
|
|
39
|
+
const db = drizzle(sqlite, { schema });
|
|
40
|
+
// 自动运行 migration(幂等,已执行过的不会重复执行)
|
|
41
|
+
migrate(db, { migrationsFolder: getMigrationsPath() });
|
|
42
|
+
return db;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 创建内存数据库(测试用,直接从 migration SQL 建表)
|
|
46
|
+
*/
|
|
47
|
+
export function createTestDB() {
|
|
48
|
+
const sqlite = new Database(':memory:');
|
|
49
|
+
sqlite.pragma('foreign_keys = ON');
|
|
50
|
+
const db = drizzle(sqlite, { schema });
|
|
51
|
+
// 读取 migration SQL 直接执行(内存库不走 migration 元数据表)
|
|
52
|
+
const migrationsPath = getMigrationsPath();
|
|
53
|
+
const journalPath = resolve(migrationsPath, 'meta', '_journal.json');
|
|
54
|
+
const journal = JSON.parse(readFileSync(journalPath, 'utf-8'));
|
|
55
|
+
for (const entry of journal.entries) {
|
|
56
|
+
const sqlPath = resolve(migrationsPath, `${entry.tag}.sql`);
|
|
57
|
+
const sqlContent = readFileSync(sqlPath, 'utf-8');
|
|
58
|
+
// drizzle migration SQL 用 --> statement-breakpoint 分隔语句
|
|
59
|
+
const statements = sqlContent
|
|
60
|
+
.split('--> statement-breakpoint')
|
|
61
|
+
.map(s => s.trim())
|
|
62
|
+
.filter(Boolean);
|
|
63
|
+
for (const stmt of statements) {
|
|
64
|
+
sqlite.exec(stmt);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return db;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=client.js.map
|