autosnippet 3.2.4 → 3.2.7
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 +18 -5
- package/bin/cli.js +164 -145
- package/config/constitution.yaml +2 -0
- package/dashboard/dist/assets/{index-DNOHYBhy.css → index-BaGY7kJI.css} +1 -1
- package/dashboard/dist/assets/{index-6itPuGFl.js → index-DfHY_3ln.js} +25 -25
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/CliLogger.js +78 -0
- package/lib/cli/SetupService.js +9 -718
- package/lib/cli/UpgradeService.js +23 -398
- package/lib/cli/deploy/FileDeployer.js +562 -0
- package/lib/cli/deploy/FileManifest.js +272 -0
- package/lib/external/mcp/McpServer.js +22 -26
- package/lib/external/mcp/autoApproveInjector.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +5 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +25 -3
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +6 -6
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +4 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +5 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +89 -44
- package/lib/external/mcp/handlers/consolidated.js +8 -9
- package/lib/external/mcp/handlers/dimension-complete-external.js +4 -4
- package/lib/external/mcp/handlers/guard.js +283 -5
- package/lib/external/mcp/handlers/task.js +361 -15
- package/lib/external/mcp/tools.js +32 -81
- package/lib/http/HttpServer.js +4 -0
- package/lib/http/routes/remote.js +1138 -0
- package/lib/http/routes/task.js +56 -0
- package/lib/infrastructure/database/migrations/003_add_remote_commands.js +27 -0
- package/lib/service/chat/AnalystAgent.js +12 -12
- package/lib/service/chat/ChatAgent.js +227 -545
- package/lib/service/chat/ChatAgentPrompts.js +9 -11
- package/lib/service/chat/ContextWindow.js +2 -296
- package/lib/service/chat/EpisodicConsolidator.js +15 -15
- package/lib/service/chat/ExplorationTracker.js +1262 -0
- package/lib/service/chat/HandoffProtocol.js +8 -9
- package/lib/service/chat/Memory.js +4 -0
- package/lib/service/chat/ProducerAgent.js +9 -6
- package/lib/service/chat/ProjectSemanticMemory.js +4 -0
- package/lib/service/chat/ReasoningTrace.js +182 -0
- package/lib/service/chat/WorkingMemory.js +4 -0
- package/lib/service/chat/memory/ActiveContext.js +910 -0
- package/lib/service/chat/memory/MemoryCoordinator.js +662 -0
- package/lib/service/chat/memory/PersistentMemory.js +450 -0
- package/lib/service/chat/memory/SessionStore.js +896 -0
- package/lib/service/chat/memory/index.js +13 -0
- package/lib/service/chat/tools/ast-graph.js +17 -16
- package/lib/service/cursor/AgentInstructionsGenerator.js +76 -47
- package/lib/service/cursor/FileProtection.js +4 -1
- package/lib/service/guard/GuardCheckEngine.js +10 -3
- package/lib/service/task/TaskGraphService.js +3 -3
- package/lib/shared/LanguageService.js +2 -1
- package/package.json +12 -1
- package/skills/autosnippet-intent/SKILL.md +1 -3
- package/skills/autosnippet-recipes/SKILL.md +1 -3
- package/templates/claude-code/commands/prime.md +19 -0
- package/templates/claude-code/hooks/autosnippet-session.sh +63 -0
- package/templates/claude-code/settings.json +21 -0
- package/templates/copilot-instructions.md +64 -177
- package/templates/cursor-hooks/commands/prime.md +12 -0
- package/templates/cursor-hooks/hooks/session-start.sh +10 -0
- package/templates/cursor-hooks/hooks.json +11 -0
- package/templates/cursor-rules/autosnippet-conventions.mdc +52 -3
- package/templates/cursor-rules/autosnippet-workflow.mdc +51 -27
- package/lib/external/mcp/handlers/decide.js +0 -109
- package/lib/external/mcp/handlers/ready.js +0 -42
- package/lib/service/chat/ReasoningLayer.js +0 -888
- package/templates/claude-hooks.yaml +0 -19
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP Handler — autosnippet_task (Task
|
|
2
|
+
* MCP Handler — autosnippet_task (Unified Task & Decision Management)
|
|
3
3
|
*
|
|
4
|
-
* Operations:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Operations:
|
|
5
|
+
* Session: prime (session entry — loads decisions + ready tasks + stats)
|
|
6
|
+
* Tasks: create / ready / claim / close / fail / defer / progress
|
|
7
|
+
* show / list / stats / blocked / decompose / dep_add / dep_tree
|
|
8
|
+
* Decisions: record_decision / revise_decision / unpin_decision / list_decisions
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { envelope } from '../envelope.js';
|
|
12
|
+
// guard is independent — no guardState dependency in task lifecycle
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* 统一入口
|
|
@@ -18,23 +19,35 @@ import { envelope } from '../envelope.js';
|
|
|
18
19
|
export async function taskHandler(ctx, args) {
|
|
19
20
|
const taskService = ctx.container.get('taskGraphService');
|
|
20
21
|
|
|
22
|
+
let result;
|
|
21
23
|
switch (args.operation) {
|
|
24
|
+
// ── Session ──
|
|
25
|
+
case 'prime':
|
|
26
|
+
return _prime(taskService, args);
|
|
27
|
+
// ── Task CRUD ──
|
|
22
28
|
case 'create':
|
|
23
|
-
|
|
29
|
+
result = await _create(taskService, args);
|
|
30
|
+
break;
|
|
24
31
|
case 'ready':
|
|
25
32
|
return _ready(taskService, args);
|
|
26
33
|
case 'claim':
|
|
27
|
-
|
|
34
|
+
result = await _claim(taskService, args);
|
|
35
|
+
break;
|
|
28
36
|
case 'close':
|
|
29
|
-
|
|
37
|
+
result = await _close(ctx, taskService, args);
|
|
38
|
+
break;
|
|
30
39
|
case 'fail':
|
|
31
|
-
|
|
40
|
+
result = await _fail(taskService, args);
|
|
41
|
+
break;
|
|
32
42
|
case 'defer':
|
|
33
|
-
|
|
43
|
+
result = await _defer(taskService, args);
|
|
44
|
+
break;
|
|
34
45
|
case 'progress':
|
|
35
|
-
|
|
46
|
+
result = await _progress(taskService, args);
|
|
47
|
+
break;
|
|
36
48
|
case 'decompose':
|
|
37
|
-
|
|
49
|
+
result = await _decompose(taskService, args);
|
|
50
|
+
break;
|
|
38
51
|
case 'show':
|
|
39
52
|
return _show(taskService, args);
|
|
40
53
|
case 'list':
|
|
@@ -47,13 +60,32 @@ export async function taskHandler(ctx, args) {
|
|
|
47
60
|
return _depTree(taskService, args);
|
|
48
61
|
case 'stats':
|
|
49
62
|
return _stats(taskService);
|
|
63
|
+
// ── Decisions ──
|
|
64
|
+
case 'record_decision':
|
|
65
|
+
result = await _recordDecision(taskService, args);
|
|
66
|
+
break;
|
|
67
|
+
case 'revise_decision':
|
|
68
|
+
result = await _reviseDecision(taskService, args);
|
|
69
|
+
break;
|
|
70
|
+
case 'unpin_decision':
|
|
71
|
+
result = await _unpinDecision(taskService, args);
|
|
72
|
+
break;
|
|
73
|
+
case 'list_decisions':
|
|
74
|
+
return _listDecisions(taskService);
|
|
50
75
|
default:
|
|
51
76
|
return envelope({
|
|
52
77
|
success: false,
|
|
53
|
-
message: `Unknown operation: ${args.operation}.
|
|
78
|
+
message: `Unknown operation: ${args.operation}. Valid: prime, ready, create, claim, close, fail, defer, progress, decompose, dep_add, dep_tree, stats, list, record_decision, revise_decision, unpin_decision, list_decisions.`,
|
|
54
79
|
meta: { tool: 'autosnippet_task' },
|
|
55
80
|
});
|
|
56
81
|
}
|
|
82
|
+
|
|
83
|
+
// ── 飞书任务进度通知(异步非阻塞)──
|
|
84
|
+
_notifyTaskProgress(args.operation, args, result).catch((err) => {
|
|
85
|
+
process.stderr.write(`[MCP/Task] Notify error: ${err?.message}\n`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return result;
|
|
57
89
|
}
|
|
58
90
|
|
|
59
91
|
// ── create ──
|
|
@@ -113,10 +145,11 @@ async function _claim(svc, args) {
|
|
|
113
145
|
|
|
114
146
|
// ── close ──
|
|
115
147
|
|
|
116
|
-
async function _close(svc, args) {
|
|
148
|
+
async function _close(ctx, svc, args) {
|
|
117
149
|
if (!args.id) {
|
|
118
150
|
return envelope({ success: false, message: 'id is required', meta: { tool: 'autosnippet_task' } });
|
|
119
151
|
}
|
|
152
|
+
|
|
120
153
|
const { task, newlyReady } = await svc.close(args.id, args.reason || 'Completed');
|
|
121
154
|
return envelope({
|
|
122
155
|
success: true,
|
|
@@ -281,3 +314,316 @@ async function _stats(svc) {
|
|
|
281
314
|
meta: { tool: 'autosnippet_task' },
|
|
282
315
|
});
|
|
283
316
|
}
|
|
317
|
+
|
|
318
|
+
// ═══ Session (prime) ═══════════════════════════════════
|
|
319
|
+
|
|
320
|
+
async function _prime(svc, args) {
|
|
321
|
+
const result = await svc.prime({
|
|
322
|
+
limit: args.limit || 10,
|
|
323
|
+
withKnowledge: args.withKnowledge !== false,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const decisionCount = (result.decisions || []).length;
|
|
327
|
+
const staleCount = (result.staleDecisions || []).length;
|
|
328
|
+
const decisionTitles = (result.decisions || []).map((d) => d.title).join('; ');
|
|
329
|
+
const statsLine = `${result.inProgress.length} in-progress, ${result.ready.length} ready, ${result.stats.total} total`;
|
|
330
|
+
|
|
331
|
+
// ── Behavioral Rules Reminder (survives compaction) ──
|
|
332
|
+
result._taskRules = {
|
|
333
|
+
reminder: [
|
|
334
|
+
'📋 TASK RULES (MANDATORY):',
|
|
335
|
+
'🔑 YOU are the task operator — user speaks naturally, you translate to task operations. NEVER tell user to run task commands.',
|
|
336
|
+
'• MUST prime on EVERY message BEFORE anything else',
|
|
337
|
+
'• MUST create task for non-trivial work (≥2 files OR ≥10 lines)',
|
|
338
|
+
'• MUST claim before coding, close when done with meaningful reason',
|
|
339
|
+
'• MUST handle unfinished tasks before starting new work (ask user: Continue/Defer/Abandon)',
|
|
340
|
+
'• NEVER skip prime, NEVER start new work with open in_progress tasks',
|
|
341
|
+
'• NEVER leave tasks in in_progress when session ends — close or defer ALL',
|
|
342
|
+
'• When in doubt → create a task. When idle → ready()',
|
|
343
|
+
'• Session end → close all tasks, defer incomplete, verify zero in_progress',
|
|
344
|
+
].join('\n'),
|
|
345
|
+
translationHint: [
|
|
346
|
+
'User Says → You Run:',
|
|
347
|
+
'"fix bug"/"implement" → create→claim→code→close',
|
|
348
|
+
'"continue" → resume in-progress→close',
|
|
349
|
+
'"pause" → defer | "abandon" → fail | "break down" → decompose',
|
|
350
|
+
'"what\'s next" → ready() | "agreed" → record_decision',
|
|
351
|
+
'Quick question → No task. Just answer.',
|
|
352
|
+
].join('\n'),
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
let message;
|
|
356
|
+
if (decisionCount > 0) {
|
|
357
|
+
const stalePart = staleCount > 0 ? ` ${staleCount} stale.` : '';
|
|
358
|
+
message = `⚠️ ${decisionCount} ACTIVE DECISION(S): [${decisionTitles}].${stalePart} ${statsLine}.`;
|
|
359
|
+
} else {
|
|
360
|
+
message = `${statsLine}.`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ── Resume Prompt: 有 inProgress 任务时,提示 Agent 让用户选择 ──
|
|
364
|
+
if (result.inProgress.length > 0) {
|
|
365
|
+
const taskList = result.inProgress.map((t) => {
|
|
366
|
+
const age = t.updatedAt
|
|
367
|
+
? `${Math.floor((Date.now() / 1000 - t.updatedAt) / 86400)}d ago`
|
|
368
|
+
: '';
|
|
369
|
+
return `• **${t.id}** — ${t.title}${age ? ` (${age})` : ''}`;
|
|
370
|
+
}).join('\n');
|
|
371
|
+
|
|
372
|
+
result._resumePrompt = {
|
|
373
|
+
instruction: [
|
|
374
|
+
'There are unfinished tasks. You MUST present these options to the user BEFORE doing anything else:',
|
|
375
|
+
'',
|
|
376
|
+
'**Unfinished tasks:**',
|
|
377
|
+
taskList,
|
|
378
|
+
'',
|
|
379
|
+
'Ask the user to choose:',
|
|
380
|
+
'1. **Continue** — resume the unfinished task(s)',
|
|
381
|
+
'2. **Defer** — pause it and work on something else',
|
|
382
|
+
'3. **Abandon** — close/fail it and start fresh',
|
|
383
|
+
'',
|
|
384
|
+
'Wait for the user\'s answer. Do NOT auto-resume.',
|
|
385
|
+
].join('\n'),
|
|
386
|
+
taskIds: result.inProgress.map((t) => t.id),
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
message += ` ⏸️ ${result.inProgress.length} unfinished task(s) — ask user before resuming.`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return envelope({
|
|
393
|
+
success: true,
|
|
394
|
+
data: result,
|
|
395
|
+
message,
|
|
396
|
+
meta: { tool: 'autosnippet_task' },
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ═══ Decisions ═══════════════════════════════════════
|
|
401
|
+
|
|
402
|
+
async function _recordDecision(svc, args) {
|
|
403
|
+
if (!args.title) {
|
|
404
|
+
return envelope({ success: false, message: 'title is required', meta: { tool: 'autosnippet_task' } });
|
|
405
|
+
}
|
|
406
|
+
if (!args.description) {
|
|
407
|
+
return envelope({ success: false, message: 'description is required', meta: { tool: 'autosnippet_task' } });
|
|
408
|
+
}
|
|
409
|
+
const { task, isDuplicate } = await svc.recordDecision({
|
|
410
|
+
title: args.title,
|
|
411
|
+
description: args.description,
|
|
412
|
+
rationale: args.rationale || '',
|
|
413
|
+
tags: args.tags || [],
|
|
414
|
+
relatedTaskId: args.relatedTaskId || null,
|
|
415
|
+
});
|
|
416
|
+
return envelope({
|
|
417
|
+
success: true,
|
|
418
|
+
data: task.toJSON(),
|
|
419
|
+
message: isDuplicate
|
|
420
|
+
? `⚠ Decision already recorded: ${task.id}`
|
|
421
|
+
: `✅ Decision pinned: ${task.id} — "${args.title}"`,
|
|
422
|
+
meta: { tool: 'autosnippet_task' },
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function _reviseDecision(svc, args) {
|
|
427
|
+
if (!args.id) {
|
|
428
|
+
return envelope({ success: false, message: 'id of old decision is required', meta: { tool: 'autosnippet_task' } });
|
|
429
|
+
}
|
|
430
|
+
if (!args.title) {
|
|
431
|
+
return envelope({ success: false, message: 'title of new decision is required', meta: { tool: 'autosnippet_task' } });
|
|
432
|
+
}
|
|
433
|
+
if (!args.description) {
|
|
434
|
+
return envelope({ success: false, message: 'description of new decision is required', meta: { tool: 'autosnippet_task' } });
|
|
435
|
+
}
|
|
436
|
+
const result = await svc.reviseDecision({
|
|
437
|
+
oldDecisionId: args.id,
|
|
438
|
+
title: args.title,
|
|
439
|
+
description: args.description,
|
|
440
|
+
rationale: args.rationale || '',
|
|
441
|
+
reason: args.reason || '',
|
|
442
|
+
});
|
|
443
|
+
return envelope({
|
|
444
|
+
success: true,
|
|
445
|
+
data: {
|
|
446
|
+
newDecision: result.newDecision.toJSON(),
|
|
447
|
+
superseded: result.oldDecisionId,
|
|
448
|
+
},
|
|
449
|
+
message: `✅ Decision revised: ${result.oldDecisionId} → ${result.newDecision.id}`,
|
|
450
|
+
meta: { tool: 'autosnippet_task' },
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function _unpinDecision(svc, args) {
|
|
455
|
+
if (!args.id) {
|
|
456
|
+
return envelope({ success: false, message: 'id is required', meta: { tool: 'autosnippet_task' } });
|
|
457
|
+
}
|
|
458
|
+
const task = await svc.unpinDecision(args.id, args.reason || '');
|
|
459
|
+
return envelope({
|
|
460
|
+
success: true,
|
|
461
|
+
data: task.toJSON(),
|
|
462
|
+
message: `Decision ${args.id} unpinned and closed`,
|
|
463
|
+
meta: { tool: 'autosnippet_task' },
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async function _listDecisions(svc) {
|
|
468
|
+
const decisions = await svc.list({ status: 'pinned', taskType: 'decision' }, { limit: 50 });
|
|
469
|
+
return envelope({
|
|
470
|
+
success: true,
|
|
471
|
+
data: decisions.map((d) => d.toJSON()),
|
|
472
|
+
message: `${decisions.length} active decision(s)`,
|
|
473
|
+
meta: { tool: 'autosnippet_task' },
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ═══ 飞书任务进度通知(通过 API Server 中转)═══════════════
|
|
478
|
+
//
|
|
479
|
+
// MCP Server 与 API Server 是独立进程。
|
|
480
|
+
// 飞书 WSClient 连接在 API Server 中,因此通知需 HTTP 中转。
|
|
481
|
+
// ═══════════════════════════════════════════════════════════
|
|
482
|
+
|
|
483
|
+
const PRIORITY_LABELS = ['P0 紧急', 'P1 高', 'P2 中', 'P3 低', 'P4 微'];
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* 通过 API Server 的 /api/v1/remote/notify 发送飞书通知
|
|
487
|
+
* @param {string} text
|
|
488
|
+
* @returns {Promise<boolean>}
|
|
489
|
+
*/
|
|
490
|
+
async function _sendLarkViaApi(text) {
|
|
491
|
+
try {
|
|
492
|
+
const port = process.env.PORT || 3000;
|
|
493
|
+
const resp = await fetch(`http://localhost:${port}/api/v1/remote/notify`, {
|
|
494
|
+
method: 'POST',
|
|
495
|
+
headers: { 'Content-Type': 'application/json' },
|
|
496
|
+
body: JSON.stringify({ text }),
|
|
497
|
+
signal: AbortSignal.timeout(5000),
|
|
498
|
+
});
|
|
499
|
+
if (!resp.ok) {
|
|
500
|
+
process.stderr.write(`[MCP/Task] Lark notify HTTP ${resp.status}\n`);
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
const body = await resp.json();
|
|
504
|
+
return body.success === true;
|
|
505
|
+
} catch (err) {
|
|
506
|
+
process.stderr.write(`[MCP/Task] Lark notify failed: ${err?.message}\n`);
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* 通过 API Server 截取 IDE 窗口截图并发送到飞书
|
|
513
|
+
* @param {string} [caption] — 可选文字说明
|
|
514
|
+
* @returns {Promise<boolean>}
|
|
515
|
+
*/
|
|
516
|
+
async function _sendScreenshotViaApi(caption = '') {
|
|
517
|
+
try {
|
|
518
|
+
const port = process.env.PORT || 3000;
|
|
519
|
+
const resp = await fetch(`http://localhost:${port}/api/v1/remote/screenshot`, {
|
|
520
|
+
method: 'POST',
|
|
521
|
+
headers: { 'Content-Type': 'application/json' },
|
|
522
|
+
body: JSON.stringify({ caption }),
|
|
523
|
+
signal: AbortSignal.timeout(15000),
|
|
524
|
+
});
|
|
525
|
+
if (!resp.ok) {
|
|
526
|
+
process.stderr.write(`[MCP/Task] Screenshot HTTP ${resp.status}\n`);
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
const body = await resp.json();
|
|
530
|
+
return body.success === true;
|
|
531
|
+
} catch (err) {
|
|
532
|
+
process.stderr.write(`[MCP/Task] Screenshot failed: ${err?.message}\n`);
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* 根据任务操作向飞书发送进度通知(异步非阻塞)
|
|
539
|
+
* result 是 envelope() 返回的 { success, data, message, meta }
|
|
540
|
+
*/
|
|
541
|
+
async function _notifyTaskProgress(operation, args, result) {
|
|
542
|
+
if (!result || result.success === false) return;
|
|
543
|
+
|
|
544
|
+
const data = result.data;
|
|
545
|
+
let text = '';
|
|
546
|
+
|
|
547
|
+
switch (operation) {
|
|
548
|
+
case 'create': {
|
|
549
|
+
const title = data?.title || args.title || '';
|
|
550
|
+
const id = data?.id || '';
|
|
551
|
+
const type = data?.taskType || args.taskType || 'task';
|
|
552
|
+
const pri = PRIORITY_LABELS[data?.priority ?? args.priority ?? 2] || 'P2';
|
|
553
|
+
const dup = result.message?.includes('Duplicate') || result.message?.startsWith('⚠') ? ' (重复)' : '';
|
|
554
|
+
text = `📋 新任务${dup}: ${id}\n${title}\n类型: ${type} | ${pri}`;
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
case 'claim': {
|
|
558
|
+
const id = data?.id || args.id;
|
|
559
|
+
const title = data?.title || '';
|
|
560
|
+
text = `🔨 开始执行: ${id}\n${title}`;
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
case 'close': {
|
|
564
|
+
const closed = data?.closed || data;
|
|
565
|
+
const title = closed?.title || '';
|
|
566
|
+
const id = closed?.id || args.id;
|
|
567
|
+
const reason = closed?.closeReason || args.reason || '';
|
|
568
|
+
const readyCount = data?.newlyReady?.length || 0;
|
|
569
|
+
const readyInfo = readyCount > 0 ? `\n→ ${readyCount} 个任务新就绪` : '';
|
|
570
|
+
text = `✅ 完成: ${id}\n${title}\n原因: ${reason}${readyInfo}`;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
case 'fail': {
|
|
574
|
+
const title = data?.title || '';
|
|
575
|
+
const id = data?.id || args.id;
|
|
576
|
+
const reason = data?.lastFailReason || args.reason || '未知';
|
|
577
|
+
const count = data?.failCount || 0;
|
|
578
|
+
text = `❌ 失败: ${id}\n${title}\n原因: ${reason}${count > 1 ? ` (第${count}次)` : ''}`;
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
case 'defer': {
|
|
582
|
+
const id = data?.id || args.id;
|
|
583
|
+
const title = data?.title || '';
|
|
584
|
+
text = `⏸️ 暂缓: ${id} — ${title}`;
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case 'progress': {
|
|
588
|
+
const id = data?.id || args.id;
|
|
589
|
+
const note = args.reason || args.description || '';
|
|
590
|
+
text = note
|
|
591
|
+
? `📝 进度: ${id}\n${note.slice(0, 200)}`
|
|
592
|
+
: `📝 进度: ${id}`;
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
case 'decompose': {
|
|
596
|
+
const epicId = args.id;
|
|
597
|
+
const count = Array.isArray(data) ? data.length : 0;
|
|
598
|
+
const subTitles = Array.isArray(data)
|
|
599
|
+
? data.slice(0, 5).map((t, i) => ` ${i + 1}. ${t.title || t.id}`).join('\n')
|
|
600
|
+
: '';
|
|
601
|
+
text = `📂 拆解: ${epicId} → ${count} 个子任务${subTitles ? '\n' + subTitles : ''}`;
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
case 'record_decision': {
|
|
605
|
+
const title = data?.title || args.title || '';
|
|
606
|
+
text = `📌 决策: ${title}`;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
case 'revise_decision': {
|
|
610
|
+
const oldId = data?.superseded || args.id;
|
|
611
|
+
const newTitle = data?.newDecision?.title || args.title;
|
|
612
|
+
text = `🔄 决策更新: ${oldId} → ${newTitle}`;
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
case 'unpin_decision': {
|
|
616
|
+
const id = data?.id || args.id;
|
|
617
|
+
text = `🔓 决策取消: ${id}`;
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
default:
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (text) {
|
|
625
|
+
await _sendLarkViaApi(text);
|
|
626
|
+
// 发送文字通知后,附带 IDE 窗口截图
|
|
627
|
+
await _sendScreenshotViaApi();
|
|
628
|
+
}
|
|
629
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具定义 — V3 整合版 (18 agent + 4 admin = 22 工具)
|
|
3
3
|
*
|
|
4
|
-
* 从 39 → 22 工具(参数路由合并同类工具 + 外部 Agent 冷启动新架构 + TaskGraph
|
|
5
|
-
* TaskGraph
|
|
4
|
+
* 从 39 → 22 工具(参数路由合并同类工具 + 外部 Agent 冷启动新架构 + TaskGraph 统一入口)。
|
|
5
|
+
* TaskGraph: autosnippet_task (统一入口,含 prime/decision/task CRUD)
|
|
6
6
|
* 每个工具声明增加 tier 字段(agent / admin)。
|
|
7
7
|
* tools.js 只包含 JSON Schema 声明 + Gateway 映射,不含业务逻辑。
|
|
8
8
|
*
|
|
@@ -45,16 +45,7 @@ export const TOOL_GATEWAY_MAP = {
|
|
|
45
45
|
autosnippet_submit_knowledge: { action: 'knowledge:create', resource: 'knowledge' },
|
|
46
46
|
autosnippet_submit_knowledge_batch: { action: 'knowledge:create', resource: 'knowledge' },
|
|
47
47
|
autosnippet_save_document: { action: 'knowledge:create', resource: 'knowledge' },
|
|
48
|
-
//
|
|
49
|
-
autosnippet_decide: {
|
|
50
|
-
resolver: (args) =>
|
|
51
|
-
({
|
|
52
|
-
record: { action: 'task:create', resource: 'tasks' },
|
|
53
|
-
revise: { action: 'task:update', resource: 'tasks' },
|
|
54
|
-
unpin: { action: 'task:update', resource: 'tasks' },
|
|
55
|
-
})[args?.operation] || null, // list 只读
|
|
56
|
-
},
|
|
57
|
-
// task 写操作(create/claim/close/fail/defer/decompose/dep_add)
|
|
48
|
+
// task 写操作(create/claim/close/fail/defer/decompose/dep_add + decision 写操作)
|
|
58
49
|
autosnippet_task: {
|
|
59
50
|
resolver: (args) =>
|
|
60
51
|
({
|
|
@@ -66,7 +57,10 @@ export const TOOL_GATEWAY_MAP = {
|
|
|
66
57
|
progress: { action: 'task:update', resource: 'tasks' },
|
|
67
58
|
decompose: { action: 'task:create', resource: 'tasks' },
|
|
68
59
|
dep_add: { action: 'task:update', resource: 'tasks' },
|
|
69
|
-
|
|
60
|
+
record_decision: { action: 'task:create', resource: 'tasks' },
|
|
61
|
+
revise_decision: { action: 'task:update', resource: 'tasks' },
|
|
62
|
+
unpin_decision: { action: 'task:update', resource: 'tasks' },
|
|
63
|
+
})[args?.operation] || null, // prime/ready/show/list/blocked/dep_tree/stats/list_decisions 只读
|
|
70
64
|
},
|
|
71
65
|
// admin 工具
|
|
72
66
|
autosnippet_enrich_candidates: { action: 'knowledge:update', resource: 'knowledge' },
|
|
@@ -216,32 +210,27 @@ export const TOOLS = [
|
|
|
216
210
|
},
|
|
217
211
|
},
|
|
218
212
|
|
|
219
|
-
// 6. Guard
|
|
213
|
+
// 6. Guard 检查(统一入口)
|
|
220
214
|
{
|
|
221
215
|
name: 'autosnippet_guard',
|
|
222
216
|
tier: 'agent',
|
|
223
|
-
description:
|
|
217
|
+
description:
|
|
218
|
+
'代码规范检查 & 质量门禁。\n' +
|
|
219
|
+
'• 无参数 → 自动从 git diff 检测增量文件并检查(编码后推荐用法)\n' +
|
|
220
|
+
'• files: ["path/to/file.m", ...] → 检查指定文件\n' +
|
|
221
|
+
'• code: "..." → 单文件内联检查\n' +
|
|
222
|
+
'每个 violation 内联 recipe 修复指南(doClause + coreCode),直接按指示修复后再次调用。',
|
|
224
223
|
inputSchema: {
|
|
225
224
|
type: 'object',
|
|
226
225
|
properties: {
|
|
227
|
-
code: { type: 'string', description: '待检查代码(单文件模式,与 files 二选一)' },
|
|
228
|
-
language: { type: 'string', description: '编程语言' },
|
|
229
|
-
filePath: { type: 'string', description: '文件路径(单文件模式)' },
|
|
230
226
|
files: {
|
|
231
227
|
type: 'array',
|
|
232
|
-
items: {
|
|
233
|
-
|
|
234
|
-
properties: { path: { type: 'string' }, content: { type: 'string' } },
|
|
235
|
-
required: ['path'],
|
|
236
|
-
},
|
|
237
|
-
description: '文件列表(批量模式,与 code 二选一)',
|
|
238
|
-
},
|
|
239
|
-
scope: {
|
|
240
|
-
type: 'string',
|
|
241
|
-
enum: ['file', 'target', 'project'],
|
|
242
|
-
default: 'project',
|
|
243
|
-
description: '审计范围(批量模式)',
|
|
228
|
+
items: { type: 'string' },
|
|
229
|
+
description: '待检查文件路径列表(string[])。省略则自动从 git diff 检测增量文件。',
|
|
244
230
|
},
|
|
231
|
+
code: { type: 'string', description: '待检查代码(单文件模式,与 files 二选一)' },
|
|
232
|
+
language: { type: 'string', description: '编程语言(单文件模式)' },
|
|
233
|
+
filePath: { type: 'string', description: '文件路径提示(单文件模式,用于语言检测)' },
|
|
245
234
|
},
|
|
246
235
|
required: [],
|
|
247
236
|
},
|
|
@@ -538,68 +527,26 @@ export const TOOLS = [
|
|
|
538
527
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
539
528
|
},
|
|
540
529
|
|
|
541
|
-
// 13.
|
|
542
|
-
{
|
|
543
|
-
name: 'autosnippet_ready',
|
|
544
|
-
tier: 'agent',
|
|
545
|
-
description:
|
|
546
|
-
'Session entry point. Call FIRST at conversation start to load project context, active decisions, and available tasks.\n' +
|
|
547
|
-
'Returns: in-progress tasks, ready tasks (with knowledge context), pinned decisions, and stats.',
|
|
548
|
-
inputSchema: {
|
|
549
|
-
type: 'object',
|
|
550
|
-
properties: {
|
|
551
|
-
limit: { type: 'number', default: 10, description: 'Max ready tasks to return' },
|
|
552
|
-
withKnowledge: { type: 'boolean', default: true, description: 'Attach knowledge context to ready tasks' },
|
|
553
|
-
},
|
|
554
|
-
required: [],
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
|
|
558
|
-
// 14. autosnippet_decide — 决策管理
|
|
559
|
-
{
|
|
560
|
-
name: 'autosnippet_decide',
|
|
561
|
-
tier: 'agent',
|
|
562
|
-
description:
|
|
563
|
-
'Decision management. Record, revise or unpin project decisions that persist across conversations.\n' +
|
|
564
|
-
'Call record when you and user reach agreement. Decisions are shown in every autosnippet_ready call.',
|
|
565
|
-
inputSchema: {
|
|
566
|
-
type: 'object',
|
|
567
|
-
properties: {
|
|
568
|
-
operation: {
|
|
569
|
-
type: 'string',
|
|
570
|
-
enum: ['record', 'revise', 'unpin', 'list'],
|
|
571
|
-
description: 'record=pin new decision, revise=supersede old, unpin=close, list=show active',
|
|
572
|
-
},
|
|
573
|
-
title: { type: 'string', description: 'Decision title (record/revise)' },
|
|
574
|
-
description: { type: 'string', description: 'Decision description (record/revise)' },
|
|
575
|
-
rationale: { type: 'string', description: 'Why this decision (record/revise)' },
|
|
576
|
-
tags: { type: 'array', items: { type: 'string' }, description: 'Classification tags (record)' },
|
|
577
|
-
relatedTaskId: { type: 'string', description: 'Related task ID (record)' },
|
|
578
|
-
id: { type: 'string', description: 'Decision ID (revise/unpin)' },
|
|
579
|
-
reason: { type: 'string', description: 'Reason for revision/unpin' },
|
|
580
|
-
},
|
|
581
|
-
required: ['operation'],
|
|
582
|
-
},
|
|
583
|
-
},
|
|
584
|
-
|
|
585
|
-
// 15. autosnippet_task — 任务 CRUD
|
|
530
|
+
// 13. autosnippet_task — 统一任务管理(含 prime/decision/CRUD)
|
|
586
531
|
{
|
|
587
532
|
name: 'autosnippet_task',
|
|
588
533
|
tier: 'agent',
|
|
589
534
|
description:
|
|
590
|
-
'
|
|
591
|
-
'
|
|
535
|
+
'Unified task & decision management. Includes session context (prime), task CRUD, and decision persistence.\n' +
|
|
536
|
+
'Call prime FIRST at every conversation start to load decisions + tasks + knowledge context.',
|
|
592
537
|
inputSchema: {
|
|
593
538
|
type: 'object',
|
|
594
539
|
properties: {
|
|
595
540
|
operation: {
|
|
596
541
|
type: 'string',
|
|
597
542
|
enum: [
|
|
598
|
-
'
|
|
543
|
+
'prime', 'ready',
|
|
544
|
+
'create', 'claim', 'close', 'fail', 'defer',
|
|
599
545
|
'progress', 'show', 'list', 'stats',
|
|
600
546
|
'blocked', 'decompose', 'dep_add', 'dep_tree',
|
|
547
|
+
'record_decision', 'revise_decision', 'unpin_decision', 'list_decisions',
|
|
601
548
|
],
|
|
602
|
-
description: '
|
|
549
|
+
description: 'prime=session entry (CALL FIRST) | ready=ready tasks | record_decision/revise_decision/unpin_decision/list_decisions=decision management | create/claim/close/fail/defer/progress/decompose=task CRUD',
|
|
603
550
|
},
|
|
604
551
|
title: { type: 'string', description: 'Task title (create)' },
|
|
605
552
|
description: { type: 'string', description: 'Task description (create/progress)' },
|
|
@@ -612,8 +559,12 @@ export const TOOLS = [
|
|
|
612
559
|
description: 'Task type (create)',
|
|
613
560
|
},
|
|
614
561
|
parentId: { type: 'string', description: 'Parent task ID (create subtask)' },
|
|
615
|
-
id: { type: 'string', description: 'Task ID (claim/close/fail/defer/show/dep_add/dep_tree/progress)' },
|
|
616
|
-
reason: { type: 'string', description: 'Reason (close/fail/defer)' },
|
|
562
|
+
id: { type: 'string', description: 'Task ID (claim/close/fail/defer/show/dep_add/dep_tree/progress/revise_decision/unpin_decision)' },
|
|
563
|
+
reason: { type: 'string', description: 'Reason (close/fail/defer/unpin_decision)' },
|
|
564
|
+
rationale: { type: 'string', description: 'Why this decision (record_decision/revise_decision)' },
|
|
565
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Classification tags (record_decision)' },
|
|
566
|
+
relatedTaskId: { type: 'string', description: 'Related task ID (record_decision)' },
|
|
567
|
+
|
|
617
568
|
dependsOn: { type: 'string', description: 'Dependency target task ID (dep_add)' },
|
|
618
569
|
depType: {
|
|
619
570
|
type: 'string',
|
package/lib/http/HttpServer.js
CHANGED
|
@@ -38,6 +38,7 @@ import skillsRouter from './routes/skills.js';
|
|
|
38
38
|
import snippetRouter from './routes/snippets.js';
|
|
39
39
|
import spmRouter from './routes/spm.js';
|
|
40
40
|
import taskRouter from './routes/task.js';
|
|
41
|
+
import remoteRouter from './routes/remote.js';
|
|
41
42
|
import violationsRouter from './routes/violations.js';
|
|
42
43
|
import wikiRouter from './routes/wiki.js';
|
|
43
44
|
|
|
@@ -302,6 +303,9 @@ export class HttpServer {
|
|
|
302
303
|
// Wiki 路由
|
|
303
304
|
this.app.use(`${apiPrefix}/wiki`, wikiRouter);
|
|
304
305
|
|
|
306
|
+
// Remote 路由(飞书 Bot → IDE 远程指令桥接)
|
|
307
|
+
this.app.use(`${apiPrefix}/remote`, remoteRouter);
|
|
308
|
+
|
|
305
309
|
// 根路径 — 返回 API 元信息(避免外部探测产生无意义 404)
|
|
306
310
|
this.app.all('/', (req, res) => {
|
|
307
311
|
res.json({
|