@s_s/harmonia 1.3.0 → 1.4.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.
Files changed (172) hide show
  1. package/README.md +140 -392
  2. package/build/cli/setup.d.ts +4 -2
  3. package/build/cli/setup.js +44 -18
  4. package/build/cli/setup.js.map +1 -1
  5. package/build/core/action-registry.d.ts +36 -0
  6. package/build/core/action-registry.js +53 -0
  7. package/build/core/action-registry.js.map +1 -0
  8. package/build/core/artifacts.d.ts +66 -0
  9. package/build/core/artifacts.js +178 -0
  10. package/build/core/artifacts.js.map +1 -0
  11. package/build/core/dispatch.d.ts +6 -2
  12. package/build/core/dispatch.js +12 -7
  13. package/build/core/dispatch.js.map +1 -1
  14. package/build/core/overrides.d.ts +19 -26
  15. package/build/core/overrides.js +32 -98
  16. package/build/core/overrides.js.map +1 -1
  17. package/build/core/plugin.d.ts +86 -0
  18. package/build/core/plugin.js +332 -0
  19. package/build/core/plugin.js.map +1 -0
  20. package/build/core/registry.d.ts +4 -5
  21. package/build/core/registry.js +8 -9
  22. package/build/core/registry.js.map +1 -1
  23. package/build/core/reviews.d.ts +11 -12
  24. package/build/core/reviews.js +18 -21
  25. package/build/core/reviews.js.map +1 -1
  26. package/build/core/schema.d.ts +43 -15
  27. package/build/core/schema.js +124 -20
  28. package/build/core/schema.js.map +1 -1
  29. package/build/core/state.d.ts +26 -27
  30. package/build/core/state.js +36 -90
  31. package/build/core/state.js.map +1 -1
  32. package/build/core/steps.d.ts +13 -14
  33. package/build/core/steps.js +26 -29
  34. package/build/core/steps.js.map +1 -1
  35. package/build/core/tree-utils.d.ts +52 -0
  36. package/build/core/tree-utils.js +226 -0
  37. package/build/core/tree-utils.js.map +1 -0
  38. package/build/core/types.d.ts +389 -118
  39. package/build/core/types.js +15 -1
  40. package/build/core/types.js.map +1 -1
  41. package/build/core/workflow-engine.d.ts +68 -0
  42. package/build/core/workflow-engine.js +821 -0
  43. package/build/core/workflow-engine.js.map +1 -0
  44. package/build/core/workflow-validator.d.ts +22 -0
  45. package/build/core/workflow-validator.js +489 -0
  46. package/build/core/workflow-validator.js.map +1 -0
  47. package/build/index.js +25 -26
  48. package/build/index.js.map +1 -1
  49. package/build/setup/inject.d.ts +4 -4
  50. package/build/setup/inject.js +6 -6
  51. package/build/setup/inject.js.map +1 -1
  52. package/build/setup/templates.d.ts +9 -7
  53. package/build/setup/templates.js +68 -172
  54. package/build/setup/templates.js.map +1 -1
  55. package/build/tools/artifact-approve.d.ts +8 -0
  56. package/build/tools/{approve-doc.js → artifact-approve.js} +24 -16
  57. package/build/tools/artifact-approve.js.map +1 -0
  58. package/build/tools/artifact-schema.d.ts +12 -0
  59. package/build/tools/artifact-schema.js +148 -0
  60. package/build/tools/artifact-schema.js.map +1 -0
  61. package/build/tools/artifact-tools.d.ts +18 -0
  62. package/build/tools/artifact-tools.js +465 -0
  63. package/build/tools/artifact-tools.js.map +1 -0
  64. package/build/tools/{report-dispatch.d.ts → dispatch-report.d.ts} +7 -3
  65. package/build/tools/{report-dispatch.js → dispatch-report.js} +106 -28
  66. package/build/tools/dispatch-report.js.map +1 -0
  67. package/build/tools/engine-helpers.d.ts +41 -0
  68. package/build/tools/engine-helpers.js +182 -0
  69. package/build/tools/engine-helpers.js.map +1 -0
  70. package/build/tools/get-project-status.d.ts +6 -4
  71. package/build/tools/get-project-status.js +265 -248
  72. package/build/tools/get-project-status.js.map +1 -1
  73. package/build/tools/get-role-prompt.d.ts +1 -1
  74. package/build/tools/get-role-prompt.js +7 -41
  75. package/build/tools/get-role-prompt.js.map +1 -1
  76. package/build/tools/iteration-start.d.ts +7 -4
  77. package/build/tools/iteration-start.js +45 -19
  78. package/build/tools/iteration-start.js.map +1 -1
  79. package/build/tools/loop-done.d.ts +11 -0
  80. package/build/tools/loop-done.js +109 -0
  81. package/build/tools/loop-done.js.map +1 -0
  82. package/build/tools/patch-start.d.ts +4 -2
  83. package/build/tools/patch-start.js +36 -11
  84. package/build/tools/patch-start.js.map +1 -1
  85. package/build/tools/project-init.d.ts +5 -5
  86. package/build/tools/project-init.js +41 -10
  87. package/build/tools/project-init.js.map +1 -1
  88. package/build/tools/role-dispatch.d.ts +55 -0
  89. package/build/tools/role-dispatch.js +508 -0
  90. package/build/tools/role-dispatch.js.map +1 -0
  91. package/build/tools/utils.d.ts +6 -0
  92. package/build/tools/utils.js +36 -0
  93. package/build/tools/utils.js.map +1 -1
  94. package/package.json +1 -1
  95. package/{build/hooks/claude-code.js → workflows/dev/hooks/claude.js} +34 -23
  96. package/{build → workflows/dev}/hooks/content.js +27 -18
  97. package/workflows/dev/hooks/index.js +52 -0
  98. package/{build → workflows/dev}/hooks/openclaw.js +31 -20
  99. package/{build → workflows/dev}/hooks/opencode.js +31 -20
  100. package/workflows/dev/roles/architect.md +68 -28
  101. package/workflows/dev/roles/coordinator.md +103 -0
  102. package/workflows/dev/roles/developer.md +5 -5
  103. package/workflows/dev/roles/tester.md +19 -19
  104. package/workflows/dev/schemas/api-contract.json +42 -0
  105. package/workflows/dev/schemas/api-design.json +30 -13
  106. package/workflows/dev/schemas/data-model.json +20 -7
  107. package/workflows/dev/schemas/prd.completeness-check.json +6 -5
  108. package/workflows/dev/schemas/prd.draft.json +13 -5
  109. package/workflows/dev/schemas/prd.final.json +34 -11
  110. package/workflows/dev/schemas/prd.json +29 -11
  111. package/workflows/dev/schemas/prd.requirements.json +6 -5
  112. package/workflows/dev/schemas/prototype.json +6 -2
  113. package/workflows/dev/schemas/task-breakdown.coarse.json +4 -3
  114. package/workflows/dev/schemas/task-breakdown.dependencies.json +5 -4
  115. package/workflows/dev/schemas/task-breakdown.detailed.json +8 -3
  116. package/workflows/dev/schemas/task-breakdown.final.json +8 -3
  117. package/workflows/dev/schemas/task-breakdown.json +8 -3
  118. package/workflows/dev/schemas/tech-design.analysis.json +6 -5
  119. package/workflows/dev/schemas/tech-design.draft.json +14 -5
  120. package/workflows/dev/schemas/tech-design.final.json +39 -13
  121. package/workflows/dev/schemas/tech-design.json +34 -13
  122. package/workflows/dev/schemas/tech-design.research.json +21 -0
  123. package/workflows/dev/schemas/test-plan.json +17 -7
  124. package/workflows/dev/schemas/test-report.json +26 -9
  125. package/workflows/dev/schemas/user-stories.json +7 -3
  126. package/workflows/dev/tools/index.js +23 -0
  127. package/workflows/dev/workflow.json +234 -101
  128. package/build/core/docs.d.ts +0 -36
  129. package/build/core/docs.js +0 -96
  130. package/build/core/docs.js.map +0 -1
  131. package/build/core/workflow.d.ts +0 -33
  132. package/build/core/workflow.js +0 -140
  133. package/build/core/workflow.js.map +0 -1
  134. package/build/hooks/claude-code.d.ts +0 -20
  135. package/build/hooks/claude-code.js.map +0 -1
  136. package/build/hooks/content.d.ts +0 -43
  137. package/build/hooks/content.js.map +0 -1
  138. package/build/hooks/install.d.ts +0 -40
  139. package/build/hooks/install.js +0 -63
  140. package/build/hooks/install.js.map +0 -1
  141. package/build/hooks/openclaw.d.ts +0 -24
  142. package/build/hooks/openclaw.js.map +0 -1
  143. package/build/hooks/opencode.d.ts +0 -29
  144. package/build/hooks/opencode.js.map +0 -1
  145. package/build/tools/approve-doc.d.ts +0 -6
  146. package/build/tools/approve-doc.js.map +0 -1
  147. package/build/tools/dispatch-role.d.ts +0 -16
  148. package/build/tools/dispatch-role.js +0 -266
  149. package/build/tools/dispatch-role.js.map +0 -1
  150. package/build/tools/doc-tools.d.ts +0 -16
  151. package/build/tools/doc-tools.js +0 -425
  152. package/build/tools/doc-tools.js.map +0 -1
  153. package/build/tools/override-tools.d.ts +0 -6
  154. package/build/tools/override-tools.js +0 -129
  155. package/build/tools/override-tools.js.map +0 -1
  156. package/build/tools/report-dispatch.js.map +0 -1
  157. package/build/tools/set-scale.d.ts +0 -6
  158. package/build/tools/set-scale.js +0 -95
  159. package/build/tools/set-scale.js.map +0 -1
  160. package/build/tools/setup-project.d.ts +0 -8
  161. package/build/tools/setup-project.js +0 -116
  162. package/build/tools/setup-project.js.map +0 -1
  163. package/build/tools/update-phase.d.ts +0 -12
  164. package/build/tools/update-phase.js +0 -148
  165. package/build/tools/update-phase.js.map +0 -1
  166. package/workflows/dev/roles/pm.md +0 -99
  167. package/workflows/dev/schemas/deploy.json +0 -20
  168. package/workflows/dev/schemas/fsd.json +0 -25
  169. package/workflows/dev/schemas/project-plan.json +0 -20
  170. package/workflows/dev/schemas/retrospective.json +0 -20
  171. package/workflows/dev/schemas/risk-assessment.json +0 -15
  172. package/workflows/dev/schemas/tech-design.api-contract.json +0 -20
@@ -1,19 +1,23 @@
1
1
  /**
2
2
  * MCP Tool: project_init
3
- * Register a new Harmonia project in the global registry.
3
+ * Register a new Harmonia project in the global registry and install workflow hooks.
4
4
  *
5
- * This tool ONLY registers the project it does NOT create iteration directories
6
- * or initialize state. After registration, call `iteration_start` to begin the
7
- * first iteration.
5
+ * This tool registers the project AND installs workflow-defined hooks for the
6
+ * detected agent. It does NOT create iteration directories or initialize state.
7
+ * After registration, call `iteration_start` to begin the first iteration.
8
8
  *
9
9
  * Supports an optional `workflow` parameter. When multiple workflows are
10
10
  * available and none is specified, returns an error with the available list
11
11
  * so the agent can re-call with a specific choice.
12
12
  */
13
13
  import { z } from 'zod';
14
- import { loadWorkflow, listWorkflows } from '../core/workflow.js';
15
- import { registerProject, getProject } from '../core/registry.js';
16
- export function registerProjectInit(server, builtinDir, customDir) {
14
+ import { createKit, defineHooks } from '@s_s/agent-kit';
15
+ import { loadWorkflow, listWorkflows } from '../core/plugin.js';
16
+ import { registerProject, getProject, getGlobalDir } from '../core/registry.js';
17
+ import { detectHostAgent } from '../setup/inject.js';
18
+ /** Shared kit instance for hook installation */
19
+ const kit = createKit('harmonia');
20
+ export function registerProjectInit(server, workflowsDir) {
17
21
  server.tool('project_init', 'Register a new Harmonia project. Creates the project entry in the global registry and the project source directory. Does NOT start an iteration — call iteration_start after registration to begin the first iteration.', {
18
22
  project_name: z
19
23
  .string()
@@ -59,7 +63,7 @@ export function registerProjectInit(server, builtinDir, customDir) {
59
63
  };
60
64
  }
61
65
  // Resolve workflow name
62
- const available = await listWorkflows(builtinDir, customDir);
66
+ const available = await listWorkflows(workflowsDir);
63
67
  if (available.length === 0) {
64
68
  return {
65
69
  content: [
@@ -97,7 +101,7 @@ export function registerProjectInit(server, builtinDir, customDir) {
97
101
  const descriptions = [];
98
102
  for (const name of available) {
99
103
  try {
100
- const wf = await loadWorkflow(builtinDir, customDir, name);
104
+ const wf = await loadWorkflow(workflowsDir, name);
101
105
  descriptions.push(`- ${name}: ${wf.definition.description}`);
102
106
  }
103
107
  catch {
@@ -121,9 +125,35 @@ export function registerProjectInit(server, builtinDir, customDir) {
121
125
  };
122
126
  }
123
127
  // Load workflow definition (validate it loads correctly)
124
- const wf = await loadWorkflow(builtinDir, customDir, workflowName);
128
+ const wf = await loadWorkflow(workflowsDir, workflowName);
125
129
  // Register project (creates global data dir + project source dir)
126
130
  await registerProject(project_name, project_dir, workflowName);
131
+ // Install workflow hooks if the plugin provides a hook creator
132
+ let hookMessage = '';
133
+ if (wf.hooks) {
134
+ try {
135
+ const agentType = await detectHostAgent(project_dir);
136
+ const context = {
137
+ defineHooks,
138
+ dataDir: getGlobalDir(),
139
+ projectName: project_name,
140
+ };
141
+ const hookSet = wf.hooks(agentType, context);
142
+ const hookResult = await kit.installHooks(agentType, hookSet);
143
+ if (hookResult.success) {
144
+ hookMessage = `\nHooks 已安装 (${hookResult.filesWritten.length} 个文件)`;
145
+ if (hookResult.warnings.length > 0) {
146
+ hookMessage += `\n警告: ${hookResult.warnings.join('; ')}`;
147
+ }
148
+ }
149
+ else {
150
+ hookMessage = `\n[warn] Hook 安装失败: ${hookResult.error ?? '未知错误'}`;
151
+ }
152
+ }
153
+ catch (err) {
154
+ hookMessage = `\n[warn] Hook 安装出错: ${err instanceof Error ? err.message : String(err)}`;
155
+ }
156
+ }
127
157
  return {
128
158
  content: [
129
159
  {
@@ -134,6 +164,7 @@ export function registerProjectInit(server, builtinDir, customDir) {
134
164
  `源代码目录: ${project_dir}`,
135
165
  `工作流: ${wf.definition.name} (${wf.definition.description})`,
136
166
  `可用角色: ${Object.keys(wf.roles).join(', ')}`,
167
+ hookMessage,
137
168
  ``,
138
169
  `下一步: 调用 iteration_start(project_name="${project_name}") 开始第一轮迭代。`,
139
170
  ].join('\n'),
@@ -1 +1 @@
1
- {"version":3,"file":"project-init.js","sourceRoot":"","sources":["../../src/tools/project-init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"project-init.js","sourceRoot":"","sources":["../../src/tools/project-init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * MCP Tool: role_dispatch
3
+ *
4
+ * Prepare all data needed to hand off a task to a team member role.
5
+ * Returns: role prompt (with overrides injected), session guidance,
6
+ * input artifacts, task brief, and dispatch tracking info.
7
+ *
8
+ * Node-based architecture: validates against workflow node states.
9
+ * Accepts an optional node_id parameter to target a specific task node.
10
+ *
11
+ * Session/parallel behavior is enforced by Core:
12
+ * - session: none → never searches for idle sessions
13
+ * - session: persistent → searches for idle sessions, directs reuse
14
+ * - session: optional → searches for idle sessions, suggests reuse
15
+ * - parallel: true + running dispatch → forces new session
16
+ *
17
+ * Automatically:
18
+ * - Creates a dispatch record for tracking
19
+ * - Enforces session/parallel strategy from role frontmatter
20
+ * - Triggers a dispatch_requested engine event
21
+ * - Returns nextAction from the workflow engine
22
+ *
23
+ * This tool does NOT launch agents — it only prepares the data.
24
+ * The coordinator decides how to pass this to the team member.
25
+ */
26
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
27
+ import type { ArtifactIOContext } from '../core/artifacts.js';
28
+ import type { WorkflowPlugin } from '../core/types.js';
29
+ /**
30
+ * Get file extension from artifact definition format.
31
+ * - 'html' → '.html'
32
+ * - 'json' → '.json'
33
+ * - 'md' or undefined → '.md'
34
+ */
35
+ export declare function getFormatExtension(format?: 'md' | 'html' | 'json'): string;
36
+ /** A resolved input artifact reference */
37
+ export interface InputReference {
38
+ /** Artifact ID */
39
+ id: string;
40
+ /** Human-readable name from artifact definition */
41
+ name: string;
42
+ /** Resolved file path (managed) or directory path (unmanaged) */
43
+ path: string;
44
+ /** Whether this artifact was found on disk */
45
+ found: boolean;
46
+ }
47
+ /**
48
+ * Resolve an artifact ID to a name + path reference.
49
+ *
50
+ * - Managed artifacts: resolves to full file path via format → extension mapping
51
+ * - Unmanaged artifacts: resolves to the output directory
52
+ * - Unknown artifacts: returns not-found
53
+ */
54
+ export declare function resolveInputReference(artifactId: string, wf: WorkflowPlugin, ioCtx: ArtifactIOContext, existingArtifacts: Set<string>): InputReference;
55
+ export declare function registerDispatchRole(server: McpServer, workflowsDir: string): void;
@@ -0,0 +1,508 @@
1
+ /**
2
+ * MCP Tool: role_dispatch
3
+ *
4
+ * Prepare all data needed to hand off a task to a team member role.
5
+ * Returns: role prompt (with overrides injected), session guidance,
6
+ * input artifacts, task brief, and dispatch tracking info.
7
+ *
8
+ * Node-based architecture: validates against workflow node states.
9
+ * Accepts an optional node_id parameter to target a specific task node.
10
+ *
11
+ * Session/parallel behavior is enforced by Core:
12
+ * - session: none → never searches for idle sessions
13
+ * - session: persistent → searches for idle sessions, directs reuse
14
+ * - session: optional → searches for idle sessions, suggests reuse
15
+ * - parallel: true + running dispatch → forces new session
16
+ *
17
+ * Automatically:
18
+ * - Creates a dispatch record for tracking
19
+ * - Enforces session/parallel strategy from role frontmatter
20
+ * - Triggers a dispatch_requested engine event
21
+ * - Returns nextAction from the workflow engine
22
+ *
23
+ * This tool does NOT launch agents — it only prepares the data.
24
+ * The coordinator decides how to pass this to the team member.
25
+ */
26
+ import { z } from 'zod';
27
+ import { readArtifact, listArtifacts, resolveArtifactDir } from '../core/artifacts.js';
28
+ import { getMergedOverrides, resolveRoleConfig } from '../core/overrides.js';
29
+ import { createDispatch, findIdleSession, hasRunningDispatch } from '../core/dispatch.js';
30
+ import { loadArtifactSchema, formatSchemaGuidance } from '../core/schema.js';
31
+ import { resolveActive, isError, buildOverrideSection } from './utils.js';
32
+ import { loadWorkflowForContext, processWorkflowEvent, formatNextAction, findTaskNode } from './engine-helpers.js';
33
+ import { collectTaskNodes, findAncestorLoopId } from '../core/tree-utils.js';
34
+ /**
35
+ * Find task nodes for a given role that are active or pending.
36
+ */
37
+ function findDispatchableNodes(wf, state, role) {
38
+ const allTasks = collectTaskNodes(wf.definition.root);
39
+ // Include floating nodes
40
+ if (wf.definition.floatingNodes) {
41
+ allTasks.push(...wf.definition.floatingNodes);
42
+ }
43
+ return allTasks.filter((t) => {
44
+ if (t.role !== role)
45
+ return false;
46
+ const nodeState = state.nodes[t.id];
47
+ return nodeState && (nodeState.status === 'active' || nodeState.status === 'pending');
48
+ });
49
+ }
50
+ /**
51
+ * Build session guidance text.
52
+ *
53
+ * Incorporates session type behavior, parallel status, model, and agent info.
54
+ * This replaces the former separate "## Configuration" section — all dispatch
55
+ * configuration info is now part of Session Guidance.
56
+ */
57
+ function buildSessionGuidance(params) {
58
+ const { idleSession, sessionType, model, agent, parallelForced } = params;
59
+ const lines = [];
60
+ // Consistent agent descriptor used across all Action lines
61
+ const agentDesc = agent ? `拉起 ${agent} 作为子 agent 执行任务` : '拉起子 agent 执行任务';
62
+ // Model/agent header
63
+ if (model) {
64
+ const agentSuffix = agent ? `,使用 ${agent}` : '';
65
+ lines.push(`**Model**: \`${model}\`${agentSuffix}`);
66
+ }
67
+ else if (agent) {
68
+ lines.push(`**Agent**: ${agent}`);
69
+ }
70
+ if (lines.length > 0)
71
+ lines.push('');
72
+ // Session guidance based on type and findings
73
+ if (parallelForced) {
74
+ // parallel=true and same role already has a running dispatch → force new session
75
+ lines.push(`**Session**: 该角色已有运行中的 dispatch,强制启动新会话(parallel 模式)`);
76
+ lines.push('');
77
+ lines.push(`**Action**: ${agentDesc}。`);
78
+ }
79
+ else if (idleSession) {
80
+ const agentId = idleSession.agentSessionId
81
+ ? `Agent session ID: \`${idleSession.agentSessionId}\``
82
+ : 'Agent session ID: not recorded';
83
+ const label = idleSession.label ? ` (${idleSession.label})` : '';
84
+ lines.push(`**Reusable session found**: ${idleSession.id}${label}`);
85
+ lines.push(`- ${agentId}`);
86
+ lines.push(`- Agent type: ${idleSession.agentType ?? 'unknown'}`);
87
+ lines.push(`- Last active: ${idleSession.lastActiveAt}`);
88
+ lines.push('');
89
+ if (sessionType === 'persistent') {
90
+ lines.push(`**Action**: 复用已有会话,而非启动新 agent。`);
91
+ lines.push(idleSession.agentSessionId
92
+ ? `使用 \`--resume ${idleSession.agentSessionId}\` 或 \`--session ${idleSession.agentSessionId}\` 恢复会话。`
93
+ : `注意: 该会话未记录 agent session ID,可能需要${agentDesc}。`);
94
+ }
95
+ else {
96
+ // optional — suggestion, not directive
97
+ lines.push(`**Suggestion**: 存在空闲会话,可复用也可启动新会话,由你决定。`);
98
+ if (idleSession.agentSessionId) {
99
+ lines.push(`如需复用: \`--resume ${idleSession.agentSessionId}\` 或 \`--session ${idleSession.agentSessionId}\``);
100
+ }
101
+ }
102
+ }
103
+ else {
104
+ // No idle session found (or session type is 'none')
105
+ if (sessionType === 'none') {
106
+ lines.push(`**Session**: 每次 dispatch 启动全新会话(session: none)`);
107
+ }
108
+ else {
109
+ lines.push(`**未找到可复用会话**`);
110
+ lines.push(`Session type: ${sessionType}`);
111
+ }
112
+ lines.push('');
113
+ lines.push(`**Action**: ${agentDesc}。`);
114
+ }
115
+ return lines.join('\n');
116
+ }
117
+ /**
118
+ * Get file extension from artifact definition format.
119
+ * - 'html' → '.html'
120
+ * - 'json' → '.json'
121
+ * - 'md' or undefined → '.md'
122
+ */
123
+ export function getFormatExtension(format) {
124
+ switch (format) {
125
+ case 'html':
126
+ return '.html';
127
+ case 'json':
128
+ return '.json';
129
+ default:
130
+ return '.md';
131
+ }
132
+ }
133
+ /**
134
+ * Resolve an artifact ID to a name + path reference.
135
+ *
136
+ * - Managed artifacts: resolves to full file path via format → extension mapping
137
+ * - Unmanaged artifacts: resolves to the output directory
138
+ * - Unknown artifacts: returns not-found
139
+ */
140
+ export function resolveInputReference(artifactId, wf, ioCtx, existingArtifacts) {
141
+ const artifactDef = wf.artifactDefinitions[artifactId];
142
+ if (!artifactDef) {
143
+ return {
144
+ id: artifactId,
145
+ name: artifactId,
146
+ path: '',
147
+ found: false,
148
+ };
149
+ }
150
+ if (artifactDef.unmanaged) {
151
+ // Unmanaged: resolve to directory
152
+ const dir = resolveArtifactDir(artifactDef.output, ioCtx);
153
+ return {
154
+ id: artifactId,
155
+ name: artifactDef.name,
156
+ path: dir,
157
+ found: true, // unmanaged dirs are always "available"
158
+ };
159
+ }
160
+ // Managed: resolve to full file path via format → extension
161
+ const dir = resolveArtifactDir(artifactDef.output, ioCtx);
162
+ const ext = getFormatExtension(artifactDef.format);
163
+ const filePath = `${dir}/${artifactId}${ext}`;
164
+ return {
165
+ id: artifactId,
166
+ name: artifactDef.name,
167
+ path: filePath,
168
+ found: existingArtifacts.has(artifactId),
169
+ };
170
+ }
171
+ /**
172
+ * Build Artifact Requirements section for the dispatch data package.
173
+ * Only includes schemas for artifacts associated with the dispatched role
174
+ * (via the role's capabilities).
175
+ */
176
+ async function buildArtifactRequirements(wf, workflowsDir, workflowName, role) {
177
+ const artifactDefs = wf.artifactDefinitions;
178
+ // Extract artifact IDs from role capabilities
179
+ const roleDef = wf.roles[role];
180
+ const roleArtifactIds = new Set();
181
+ if (roleDef?.frontmatter.capabilities) {
182
+ for (const cap of roleDef.frontmatter.capabilities) {
183
+ if (cap.artifact) {
184
+ roleArtifactIds.add(cap.artifact);
185
+ }
186
+ }
187
+ }
188
+ // If role has no artifact capabilities, skip
189
+ if (roleArtifactIds.size === 0)
190
+ return '';
191
+ const sections = [];
192
+ for (const artifactId of roleArtifactIds) {
193
+ const artifactDef = artifactDefs[artifactId];
194
+ if (!artifactDef || artifactDef.unmanaged)
195
+ continue;
196
+ // Load main schema
197
+ const schema = await loadArtifactSchema(workflowsDir, workflowName, artifactId);
198
+ // Load step schemas if artifact has steps
199
+ let stepSchemas;
200
+ if (artifactDef.steps && artifactDef.steps.length > 0) {
201
+ stepSchemas = [];
202
+ for (const step of artifactDef.steps) {
203
+ const stepSchema = await loadArtifactSchema(workflowsDir, workflowName, `${artifactId}.${step.id}`);
204
+ stepSchemas.push({ step, schema: stepSchema });
205
+ }
206
+ }
207
+ // Skip if no schema at all
208
+ if (!schema && (!stepSchemas || stepSchemas.every((s) => !s.schema)))
209
+ continue;
210
+ sections.push(formatSchemaGuidance(artifactId, artifactDef, schema, stepSchemas));
211
+ }
212
+ if (sections.length === 0)
213
+ return '';
214
+ return ['## Artifact Requirements', '', ...sections].join('\n');
215
+ }
216
+ export function registerDispatchRole(server, workflowsDir) {
217
+ server.tool('role_dispatch', "Prepare all data needed to dispatch a task to a team member. Returns the role's prompt (with capability overrides), configuration, input artifacts, task brief, and a dispatch tracking ID. Automatically searches for reusable sessions and provides guidance. Does NOT launch agents — you (coordinator) decide how to pass this to the team member. After launching, call dispatch_report to register the session.", {
218
+ project_name: z.string().describe('Project name'),
219
+ role: z.string().describe('Role ID to dispatch (e.g. architect, developer, tester)'),
220
+ task_brief: z
221
+ .string()
222
+ .describe('Task description for the team member — what they need to do, which tasks from the breakdown, specific instructions, etc.'),
223
+ node_id: z
224
+ .string()
225
+ .optional()
226
+ .describe('Target task node ID from the workflow tree. If omitted, automatically finds an active/pending task node for the specified role.'),
227
+ input_artifact_ids: z
228
+ .array(z.string())
229
+ .optional()
230
+ .describe('Supplementary artifact IDs to include as input references (name + path) for the team member. These are merged with the node-level inputArtifacts declaration. Note: only path references are provided, not content.'),
231
+ }, async ({ project_name, role, task_brief, node_id, input_artifact_ids }) => {
232
+ try {
233
+ const ctx = await resolveActive(project_name);
234
+ if (isError(ctx))
235
+ return ctx;
236
+ // Load project state and workflow plugin
237
+ const { wf, state } = await loadWorkflowForContext(workflowsDir, project_name, ctx);
238
+ // Validate role exists
239
+ const roleDef = wf.roles[role];
240
+ if (!roleDef) {
241
+ const available = Object.keys(wf.roles).join(', ');
242
+ return {
243
+ content: [
244
+ {
245
+ type: 'text',
246
+ text: `Role "${role}" not found. Available: ${available}`,
247
+ },
248
+ ],
249
+ isError: true,
250
+ };
251
+ }
252
+ // Resolve target task node
253
+ let targetNode;
254
+ if (node_id) {
255
+ // Explicit node_id — validate it
256
+ targetNode = findTaskNode(wf, node_id);
257
+ if (!targetNode) {
258
+ return {
259
+ content: [
260
+ {
261
+ type: 'text',
262
+ text: `Node "${node_id}" not found or is not a task node in the workflow.`,
263
+ },
264
+ ],
265
+ isError: true,
266
+ };
267
+ }
268
+ if (targetNode.role !== role) {
269
+ return {
270
+ content: [
271
+ {
272
+ type: 'text',
273
+ text: `Node "${node_id}" is assigned to role "${targetNode.role}", not "${role}".`,
274
+ },
275
+ ],
276
+ isError: true,
277
+ };
278
+ }
279
+ // Validate node state allows dispatch
280
+ const nodeState = state.nodes[node_id];
281
+ if (nodeState && nodeState.status !== 'active' && nodeState.status !== 'pending') {
282
+ return {
283
+ content: [
284
+ {
285
+ type: 'text',
286
+ text: `Node "${node_id}" is in status "${nodeState.status}" — cannot dispatch. Only active or pending nodes can be dispatched.`,
287
+ },
288
+ ],
289
+ isError: true,
290
+ };
291
+ }
292
+ }
293
+ else {
294
+ // Auto-find: look for active/pending task nodes for this role
295
+ const candidates = findDispatchableNodes(wf, state, role);
296
+ if (candidates.length === 0) {
297
+ return {
298
+ content: [
299
+ {
300
+ type: 'text',
301
+ text: `No active or pending task nodes found for role "${role}". Check project_status to see the current workflow state.`,
302
+ },
303
+ ],
304
+ isError: true,
305
+ };
306
+ }
307
+ // Prefer active over pending
308
+ targetNode = candidates.find((t) => state.nodes[t.id]?.status === 'active') ?? candidates[0];
309
+ }
310
+ const targetNodeId = targetNode.id;
311
+ // Build I/O context for artifact path resolution
312
+ const ioCtx = {
313
+ contextDir: ctx.dir,
314
+ projectDir: ctx.entry.dir,
315
+ contextLabel: ctx.activeContext,
316
+ };
317
+ // Get merged overrides
318
+ const overrides = await getMergedOverrides(project_name);
319
+ // Build the full prompt with overrides injected
320
+ const overrideSection = buildOverrideSection(role, overrides);
321
+ let fullPrompt = overrideSection ? `${roleDef.prompt}\n${overrideSection}` : roleDef.prompt;
322
+ // Execute beforeDispatch hooks (if defined on the target node)
323
+ const hookInjections = [];
324
+ if (targetNode.beforeDispatch) {
325
+ // Collect static inject text
326
+ if (targetNode.beforeDispatch.inject) {
327
+ hookInjections.push(...targetNode.beforeDispatch.inject);
328
+ }
329
+ // Execute registered actions
330
+ if (targetNode.beforeDispatch.actions && wf.actions) {
331
+ const nodeState = state.nodes[targetNodeId];
332
+ // Resolve loopIteration: find ancestor loop node and read its current iteration
333
+ let loopIteration;
334
+ const ancestorLoopId = findAncestorLoopId(wf.definition.root, targetNodeId);
335
+ if (ancestorLoopId) {
336
+ const loopState = state.nodes[ancestorLoopId];
337
+ if (loopState) {
338
+ loopIteration = loopState.currentIteration;
339
+ }
340
+ }
341
+ const actionCtx = {
342
+ nodeId: targetNodeId,
343
+ role,
344
+ retryCount: nodeState?.retryCount ?? 0,
345
+ projectName: project_name,
346
+ pluginConfig: wf.config,
347
+ workflowState: state,
348
+ artifacts: {
349
+ read: (artifactId) => readArtifact(artifactId, ioCtx, wf.artifactDefinitions[artifactId]),
350
+ list: () => listArtifacts(ioCtx, wf.artifactDefinitions),
351
+ },
352
+ loopIteration,
353
+ };
354
+ for (const actionName of targetNode.beforeDispatch.actions) {
355
+ const handler = wf.actions[actionName];
356
+ if (handler) {
357
+ try {
358
+ const result = await handler(actionCtx);
359
+ if (result.inject) {
360
+ hookInjections.push(...result.inject);
361
+ }
362
+ }
363
+ catch (err) {
364
+ console.warn(`[harmonia] beforeDispatch action "${actionName}" failed:`, err);
365
+ }
366
+ }
367
+ else {
368
+ console.warn(`[harmonia] beforeDispatch action "${actionName}" not registered`);
369
+ }
370
+ }
371
+ }
372
+ }
373
+ // Append hook injections to the role prompt
374
+ if (hookInjections.length > 0) {
375
+ fullPrompt += '\n\n' + hookInjections.join('\n\n');
376
+ }
377
+ // Resolve input artifact references (merge node declaration + coordinator parameter)
378
+ const nodeInputIds = targetNode.inputArtifacts ?? [];
379
+ const paramInputIds = input_artifact_ids ?? [];
380
+ const mergedInputIds = [...new Set([...nodeInputIds, ...paramInputIds])];
381
+ // List existing artifacts for reference resolution
382
+ const existingArtifacts = new Set(await listArtifacts(ioCtx, wf.artifactDefinitions));
383
+ // Resolve all input references
384
+ const inputRefs = mergedInputIds.map((id) => resolveInputReference(id, wf, ioCtx, existingArtifacts));
385
+ const foundRefs = inputRefs.filter((r) => r.found);
386
+ const missingRefs = inputRefs.filter((r) => !r.found);
387
+ // Resolve agent/model: override takes precedence over frontmatter
388
+ const roleConfig = resolveRoleConfig(role, overrides);
389
+ const modelDisplay = roleConfig.model ?? roleDef.frontmatter.model;
390
+ const agentDisplay = roleConfig.agent ?? roleDef.frontmatter.agent;
391
+ const sessionType = roleDef.frontmatter.session;
392
+ // Determine session strategy based on session type + parallel field
393
+ let idleSession = null;
394
+ let parallelForced = false;
395
+ if (sessionType === 'none') {
396
+ // session: none → never look for idle sessions
397
+ idleSession = null;
398
+ }
399
+ else if (roleDef.frontmatter.parallel) {
400
+ // parallel=true → check if same role has running dispatches
401
+ const hasRunning = await hasRunningDispatch(project_name, ctx.number, role, ctx.dir);
402
+ if (hasRunning) {
403
+ // Force new session — don't look for idle ones
404
+ parallelForced = true;
405
+ idleSession = null;
406
+ }
407
+ else {
408
+ // No running dispatch → follow normal session behavior
409
+ idleSession = await findIdleSession(project_name, ctx.number, role, ctx.dir);
410
+ }
411
+ }
412
+ else {
413
+ // session: persistent or optional → look for idle sessions
414
+ idleSession = await findIdleSession(project_name, ctx.number, role, ctx.dir);
415
+ }
416
+ // Create dispatch record
417
+ const dispatch = await createDispatch(project_name, ctx.number, role, task_brief, [], // expectedOutputs — determined dynamically by workflow engine
418
+ idleSession?.id, ctx.dir, targetNodeId);
419
+ // Trigger engine event: dispatch_requested
420
+ const engineResult = await processWorkflowEvent(workflowsDir, project_name, ctx, {
421
+ type: 'dispatch_requested',
422
+ nodeId: targetNodeId,
423
+ });
424
+ const nextActionText = formatNextAction(engineResult.nextAction);
425
+ // Build session guidance (now includes model/agent info — replaces ## Configuration)
426
+ const sessionGuidance = buildSessionGuidance({
427
+ idleSession,
428
+ sessionType,
429
+ model: modelDisplay,
430
+ agent: agentDisplay,
431
+ parallelForced,
432
+ });
433
+ // Build artifact requirements for expected outputs
434
+ const artifactRequirements = await buildArtifactRequirements(wf, workflowsDir, state.workflow, role);
435
+ const summary = [
436
+ `# Dispatch: ${role}`,
437
+ ``,
438
+ `## Dispatch Tracking`,
439
+ `- Dispatch ID: \`${dispatch.id}\``,
440
+ `- Status: ${dispatch.status}`,
441
+ `- Target Node: ${targetNodeId}`,
442
+ ``,
443
+ `## Session Guidance`,
444
+ sessionGuidance,
445
+ ``,
446
+ `## Task Brief`,
447
+ task_brief,
448
+ ``,
449
+ `## Project Context`,
450
+ `- Project: ${project_name}`,
451
+ `- Directory: ${state.projectDir}`,
452
+ `- Workflow: ${state.workflow}`,
453
+ ];
454
+ // Inject unmanaged artifact output path hints
455
+ const unmanagedHints = [];
456
+ for (const cap of roleDef.frontmatter.capabilities ?? []) {
457
+ if (!cap.artifact)
458
+ continue;
459
+ const def = wf.artifactDefinitions[cap.artifact];
460
+ if (!def?.unmanaged)
461
+ continue;
462
+ const outputDir = resolveArtifactDir(def.output, ioCtx);
463
+ unmanagedHints.push(`- **${cap.artifact}** (${def.name}): \`${outputDir}\``);
464
+ }
465
+ if (unmanagedHints.length > 0) {
466
+ summary.push(``, `## Unmanaged Artifact Output Paths`, `以下 artifact 由 agent 直接输出(非 artifact_write),请将产出写入对应路径:`, ...unmanagedHints);
467
+ }
468
+ summary.push(``, `## Input References (${foundRefs.length}${missingRefs.length > 0 ? `, ${missingRefs.length} missing` : ''})`);
469
+ if (foundRefs.length > 0) {
470
+ for (const ref of foundRefs) {
471
+ summary.push(`- **${ref.id}** (${ref.name}): \`${ref.path}\``);
472
+ }
473
+ }
474
+ if (missingRefs.length > 0) {
475
+ summary.push(``, `### Missing`);
476
+ for (const ref of missingRefs) {
477
+ summary.push(`- **${ref.id}** (${ref.name}): 未找到`);
478
+ }
479
+ }
480
+ summary.push(``, `## Next Step`, `After launching the agent, call \`dispatch_report\` with dispatch_id="${dispatch.id}" and the agent's session ID.`, `When the agent finishes, call \`dispatch_report\` again with status="completed" (or "failed").`);
481
+ if (artifactRequirements) {
482
+ summary.push(``, artifactRequirements);
483
+ }
484
+ summary.push(``, `## Role Prompt`, ``, fullPrompt);
485
+ summary.push(nextActionText);
486
+ return {
487
+ content: [
488
+ {
489
+ type: 'text',
490
+ text: summary.join('\n'),
491
+ },
492
+ ],
493
+ };
494
+ }
495
+ catch (err) {
496
+ return {
497
+ content: [
498
+ {
499
+ type: 'text',
500
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`,
501
+ },
502
+ ],
503
+ isError: true,
504
+ };
505
+ }
506
+ });
507
+ }
508
+ //# sourceMappingURL=role-dispatch.js.map