@triflux/remote 10.0.0-alpha.1

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 (68) hide show
  1. package/hub/pipe.mjs +579 -0
  2. package/hub/public/dashboard.html +355 -0
  3. package/hub/public/tray-icon.ico +0 -0
  4. package/hub/public/tray-icon.png +0 -0
  5. package/hub/server.mjs +1124 -0
  6. package/hub/store-adapter.mjs +851 -0
  7. package/hub/store.mjs +897 -0
  8. package/hub/team/agent-map.json +11 -0
  9. package/hub/team/ansi.mjs +379 -0
  10. package/hub/team/backend.mjs +90 -0
  11. package/hub/team/cli/commands/attach.mjs +37 -0
  12. package/hub/team/cli/commands/control.mjs +43 -0
  13. package/hub/team/cli/commands/debug.mjs +74 -0
  14. package/hub/team/cli/commands/focus.mjs +53 -0
  15. package/hub/team/cli/commands/interrupt.mjs +36 -0
  16. package/hub/team/cli/commands/kill.mjs +37 -0
  17. package/hub/team/cli/commands/list.mjs +24 -0
  18. package/hub/team/cli/commands/send.mjs +37 -0
  19. package/hub/team/cli/commands/start/index.mjs +106 -0
  20. package/hub/team/cli/commands/start/parse-args.mjs +130 -0
  21. package/hub/team/cli/commands/start/start-headless.mjs +109 -0
  22. package/hub/team/cli/commands/start/start-in-process.mjs +40 -0
  23. package/hub/team/cli/commands/start/start-mux.mjs +73 -0
  24. package/hub/team/cli/commands/start/start-wt.mjs +69 -0
  25. package/hub/team/cli/commands/status.mjs +87 -0
  26. package/hub/team/cli/commands/stop.mjs +31 -0
  27. package/hub/team/cli/commands/task.mjs +30 -0
  28. package/hub/team/cli/commands/tasks.mjs +13 -0
  29. package/hub/team/cli/help.mjs +42 -0
  30. package/hub/team/cli/index.mjs +41 -0
  31. package/hub/team/cli/manifest.mjs +29 -0
  32. package/hub/team/cli/render.mjs +30 -0
  33. package/hub/team/cli/services/attach-fallback.mjs +54 -0
  34. package/hub/team/cli/services/hub-client.mjs +208 -0
  35. package/hub/team/cli/services/member-selector.mjs +30 -0
  36. package/hub/team/cli/services/native-control.mjs +117 -0
  37. package/hub/team/cli/services/runtime-mode.mjs +62 -0
  38. package/hub/team/cli/services/state-store.mjs +48 -0
  39. package/hub/team/cli/services/task-model.mjs +30 -0
  40. package/hub/team/dashboard-anchor.mjs +14 -0
  41. package/hub/team/dashboard-layout.mjs +33 -0
  42. package/hub/team/dashboard-open.mjs +153 -0
  43. package/hub/team/dashboard.mjs +274 -0
  44. package/hub/team/handoff.mjs +303 -0
  45. package/hub/team/headless.mjs +1149 -0
  46. package/hub/team/native-supervisor.mjs +392 -0
  47. package/hub/team/native.mjs +649 -0
  48. package/hub/team/nativeProxy.mjs +681 -0
  49. package/hub/team/orchestrator.mjs +161 -0
  50. package/hub/team/pane.mjs +153 -0
  51. package/hub/team/psmux.mjs +1354 -0
  52. package/hub/team/routing.mjs +223 -0
  53. package/hub/team/session.mjs +611 -0
  54. package/hub/team/shared.mjs +13 -0
  55. package/hub/team/staleState.mjs +361 -0
  56. package/hub/team/tui-lite.mjs +380 -0
  57. package/hub/team/tui-viewer.mjs +463 -0
  58. package/hub/team/tui.mjs +1245 -0
  59. package/hub/tools.mjs +554 -0
  60. package/hub/tray.mjs +376 -0
  61. package/hub/workers/claude-worker.mjs +475 -0
  62. package/hub/workers/codex-mcp.mjs +504 -0
  63. package/hub/workers/delegator-mcp.mjs +1076 -0
  64. package/hub/workers/factory.mjs +21 -0
  65. package/hub/workers/gemini-worker.mjs +373 -0
  66. package/hub/workers/interface.mjs +52 -0
  67. package/hub/workers/worker-utils.mjs +104 -0
  68. package/package.json +31 -0
package/hub/tools.mjs ADDED
@@ -0,0 +1,554 @@
1
+ // hub/tools.mjs — MCP 도구 정의
2
+ // register/status/publish/ask/poll/handoff/HITL + team proxy
3
+ // 모든 도구 응답: { ok: boolean, error?: { code, message }, data?: ... }
4
+
5
+ import {
6
+ teamInfo,
7
+ teamTaskList,
8
+ teamTaskUpdate,
9
+ teamSendMessage,
10
+ } from './team-bridge.mjs';
11
+ import {
12
+ ensurePipelineTable,
13
+ createPipeline,
14
+ } from './pipeline/index.mjs';
15
+ import {
16
+ readPipelineState,
17
+ initPipelineState,
18
+ listPipelineStates,
19
+ } from './pipeline/state.mjs';
20
+
21
+ /**
22
+ * MCP 도구 목록 생성
23
+ * @param {object} store — createStore() 반환
24
+ * @param {object} router — createRouter() 반환
25
+ * @param {object} hitl — createHitlManager() 반환
26
+ * @param {object} pipe — createPipeServer() 반환
27
+ * @returns {Array<{name, description, inputSchema, handler}>}
28
+ */
29
+ export function createTools(store, router, hitl, pipe = null) {
30
+ /** 도구 핸들러 래퍼 — 에러 처리 + MCP content 형식 변환 */
31
+ function wrap(code, fn) {
32
+ return async (args) => {
33
+ try {
34
+ const result = await fn(args);
35
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
36
+ } catch (e) {
37
+ const err = { ok: false, error: { code, message: e.message } };
38
+ return { content: [{ type: 'text', text: JSON.stringify(err) }], isError: true };
39
+ }
40
+ };
41
+ }
42
+
43
+ return [
44
+ // ── 1. register ──
45
+ {
46
+ name: 'register',
47
+ description: '에이전트를 허브에 등록하고 lease를 발급받습니다',
48
+ inputSchema: {
49
+ type: 'object',
50
+ required: ['agent_id', 'cli', 'capabilities', 'topics', 'heartbeat_ttl_ms'],
51
+ properties: {
52
+ agent_id: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
53
+ cli: { type: 'string', enum: ['codex', 'gemini', 'claude', 'other'] },
54
+ pid: { type: 'integer', minimum: 1 },
55
+ capabilities: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 64 },
56
+ topics: { type: 'array', items: { type: 'string' }, maxItems: 64 },
57
+ metadata: { type: 'object' },
58
+ heartbeat_ttl_ms: { type: 'integer', minimum: 10000, maximum: 7200000 },
59
+ },
60
+ },
61
+ handler: wrap('REGISTER_FAILED', (args) => {
62
+ const data = router.registerAgent(args);
63
+ return { ok: true, data };
64
+ }),
65
+ },
66
+
67
+ // ── 2. status ──
68
+ {
69
+ name: 'status',
70
+ description: '허브, 에이전트, 큐, 트레이스 상태를 조회합니다',
71
+ inputSchema: {
72
+ type: 'object',
73
+ properties: {
74
+ scope: { type: 'string', enum: ['hub', 'agent', 'queue', 'trace'], default: 'hub' },
75
+ agent_id: { type: 'string' },
76
+ trace_id: { type: 'string' },
77
+ include_metrics: { type: 'boolean', default: true },
78
+ },
79
+ },
80
+ handler: wrap('STATUS_FAILED', (args) => {
81
+ return router.getStatus(args.scope || 'hub', args);
82
+ }),
83
+ },
84
+
85
+ // ── 3. publish ──
86
+ {
87
+ name: 'publish',
88
+ description: '이벤트 또는 응답 메시지를 발행합니다. to에 "topic:XXX" 지정 시 구독자 전체 fanout',
89
+ inputSchema: {
90
+ type: 'object',
91
+ required: ['from', 'to', 'topic', 'payload'],
92
+ properties: {
93
+ from: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
94
+ to: { type: 'string' },
95
+ topic: { type: 'string', pattern: '^[a-zA-Z0-9._:-]+$' },
96
+ priority: { type: 'integer', minimum: 1, maximum: 9, default: 5 },
97
+ ttl_ms: { type: 'integer', minimum: 1000, maximum: 86400000, default: 300000 },
98
+ payload: { type: 'object' },
99
+ trace_id: { type: 'string' },
100
+ correlation_id: { type: 'string' },
101
+ },
102
+ },
103
+ handler: wrap('PUBLISH_FAILED', (args) => {
104
+ return router.handlePublish(args);
105
+ }),
106
+ },
107
+
108
+ // ── 4. ask ──
109
+ {
110
+ name: 'ask',
111
+ description: '다른 에이전트에게 질문합니다. await_response_ms > 0이면 짧은 폴링으로 응답 대기',
112
+ inputSchema: {
113
+ type: 'object',
114
+ required: ['from', 'to', 'topic', 'question'],
115
+ properties: {
116
+ from: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
117
+ to: { type: 'string' },
118
+ topic: { type: 'string', pattern: '^[a-zA-Z0-9._:-]+$' },
119
+ question: { type: 'string', minLength: 1, maxLength: 20000 },
120
+ context_refs: { type: 'array', items: { type: 'string' }, maxItems: 32 },
121
+ payload: { type: 'object' },
122
+ priority: { type: 'integer', minimum: 1, maximum: 9, default: 5 },
123
+ ttl_ms: { type: 'integer', minimum: 1000, maximum: 86400000, default: 300000 },
124
+ await_response_ms: { type: 'integer', minimum: 0, maximum: 30000, default: 0 },
125
+ trace_id: { type: 'string' },
126
+ correlation_id: { type: 'string' },
127
+ },
128
+ },
129
+ handler: wrap('ASK_FAILED', async (args) => {
130
+ return await router.handleAsk(args);
131
+ }),
132
+ },
133
+
134
+ // ── 5. poll_messages ──
135
+ {
136
+ name: 'poll_messages',
137
+ description: 'Deprecated. poll_messages 대신 Named Pipe subscribe/publish 채널을 사용합니다',
138
+ inputSchema: {
139
+ type: 'object',
140
+ required: ['agent_id'],
141
+ properties: {
142
+ agent_id: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
143
+ wait_ms: { type: 'integer', minimum: 0, maximum: 30000, default: 1000 },
144
+ max_messages: { type: 'integer', minimum: 1, maximum: 100, default: 20 },
145
+ include_topics: { type: 'array', items: { type: 'string' }, maxItems: 64 },
146
+ ack_ids: { type: 'array', items: { type: 'string' }, maxItems: 100 },
147
+ auto_ack: { type: 'boolean', default: false },
148
+ },
149
+ },
150
+ handler: wrap('POLL_DEPRECATED', async (args) => {
151
+ const replay = router.drainAgent(args.agent_id, {
152
+ max_messages: args.max_messages,
153
+ include_topics: args.include_topics,
154
+ auto_ack: args.auto_ack,
155
+ });
156
+ if (args.ack_ids?.length) {
157
+ router.ackMessages(args.ack_ids, args.agent_id);
158
+ }
159
+ return {
160
+ ok: false,
161
+ error: {
162
+ code: 'POLL_DEPRECATED',
163
+ message: 'poll_messages는 deprecated 되었습니다. pipe subscribe/publish 채널을 사용하세요.',
164
+ },
165
+ data: {
166
+ pipe_path: pipe?.path || null,
167
+ delivery_mode: 'pipe_push',
168
+ protocol: 'ndjson',
169
+ replay: {
170
+ messages: replay,
171
+ count: replay.length,
172
+ },
173
+ server_time_ms: Date.now(),
174
+ },
175
+ };
176
+ }),
177
+ },
178
+
179
+ // ── 6. handoff ──
180
+ {
181
+ name: 'handoff',
182
+ description: '다른 에이전트에게 작업을 인계합니다. acceptance_criteria로 완료 기준 지정 가능',
183
+ inputSchema: {
184
+ type: 'object',
185
+ required: ['from', 'to', 'topic', 'task'],
186
+ properties: {
187
+ from: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
188
+ to: { type: 'string' },
189
+ topic: { type: 'string', pattern: '^[a-zA-Z0-9._:-]+$' },
190
+ task: { type: 'string', minLength: 1, maxLength: 20000 },
191
+ acceptance_criteria: { type: 'array', items: { type: 'string' }, maxItems: 32 },
192
+ context_refs: { type: 'array', items: { type: 'string' }, maxItems: 32 },
193
+ priority: { type: 'integer', minimum: 1, maximum: 9, default: 5 },
194
+ ttl_ms: { type: 'integer', minimum: 1000, maximum: 86400000, default: 600000 },
195
+ trace_id: { type: 'string' },
196
+ correlation_id: { type: 'string' },
197
+ },
198
+ },
199
+ handler: wrap('HANDOFF_FAILED', (args) => {
200
+ return router.handleHandoff(args);
201
+ }),
202
+ },
203
+
204
+ // ── 7. assign_async ──
205
+ {
206
+ name: 'assign_async',
207
+ description: 'AWS CAO 스타일 비차단 assign job을 생성하고 워커에게 실시간 전달합니다',
208
+ inputSchema: {
209
+ type: 'object',
210
+ required: ['supervisor_agent', 'worker_agent', 'task'],
211
+ properties: {
212
+ supervisor_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
213
+ worker_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
214
+ topic: { type: 'string', pattern: '^[a-zA-Z0-9._:-]+$', default: 'assign.job' },
215
+ task: { type: 'string', minLength: 1, maxLength: 20000 },
216
+ payload: { type: 'object' },
217
+ priority: { type: 'integer', minimum: 1, maximum: 9, default: 5 },
218
+ ttl_ms: { type: 'integer', minimum: 1000, maximum: 86400000, default: 600000 },
219
+ timeout_ms: { type: 'integer', minimum: 1000, maximum: 86400000, default: 600000 },
220
+ max_retries: { type: 'integer', minimum: 0, maximum: 20, default: 0 },
221
+ trace_id: { type: 'string' },
222
+ correlation_id: { type: 'string' },
223
+ },
224
+ },
225
+ handler: wrap('ASSIGN_ASYNC_FAILED', (args) => {
226
+ return router.assignAsync(args);
227
+ }),
228
+ },
229
+
230
+ // ── 8. assign_result ──
231
+ {
232
+ name: 'assign_result',
233
+ description: 'assign job의 진행/완료 결과를 보고합니다. completed + metadata.result 관례를 지원합니다',
234
+ inputSchema: {
235
+ type: 'object',
236
+ required: ['job_id', 'status'],
237
+ properties: {
238
+ job_id: { type: 'string', minLength: 1, maxLength: 128 },
239
+ worker_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
240
+ status: { type: 'string', enum: ['queued', 'running', 'in_progress', 'completed', 'succeeded', 'success', 'failed', 'error', 'timed_out', 'timeout'] },
241
+ attempt: { type: 'integer', minimum: 1 },
242
+ result: {},
243
+ error: {},
244
+ metadata: { type: 'object' },
245
+ payload: { type: 'object' },
246
+ },
247
+ },
248
+ handler: wrap('ASSIGN_RESULT_FAILED', (args) => {
249
+ return router.reportAssignResult(args);
250
+ }),
251
+ },
252
+
253
+ // ── 9. assign_status ──
254
+ {
255
+ name: 'assign_status',
256
+ description: 'assign job 단건 상태 또는 supervisor/worker/status 기준 목록을 조회합니다',
257
+ inputSchema: {
258
+ type: 'object',
259
+ properties: {
260
+ job_id: { type: 'string', minLength: 1, maxLength: 128 },
261
+ supervisor_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
262
+ worker_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
263
+ status: { type: 'string', enum: ['queued', 'running', 'succeeded', 'failed', 'timed_out'] },
264
+ statuses: {
265
+ type: 'array',
266
+ items: { type: 'string', enum: ['queued', 'running', 'succeeded', 'failed', 'timed_out'] },
267
+ maxItems: 8,
268
+ },
269
+ trace_id: { type: 'string' },
270
+ correlation_id: { type: 'string' },
271
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 50 },
272
+ },
273
+ },
274
+ handler: wrap('ASSIGN_STATUS_FAILED', (args) => {
275
+ return router.getAssignStatus(args);
276
+ }),
277
+ },
278
+
279
+ // ── 10. request_human_input ──
280
+ {
281
+ name: 'request_human_input',
282
+ description: '사용자에게 입력을 요청합니다 (CAPTCHA, 승인, 자격증명, 선택, 텍스트)',
283
+ inputSchema: {
284
+ type: 'object',
285
+ required: ['requester_agent', 'kind', 'prompt', 'requested_schema', 'deadline_ms', 'default_action'],
286
+ properties: {
287
+ requester_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
288
+ kind: { type: 'string', enum: ['captcha', 'approval', 'credential', 'choice', 'text'] },
289
+ prompt: { type: 'string', minLength: 1, maxLength: 20000 },
290
+ requested_schema: { type: 'object' },
291
+ deadline_ms: { type: 'integer', minimum: 1000 },
292
+ default_action: { type: 'string', enum: ['decline', 'cancel', 'timeout_continue'] },
293
+ channel_preference: { type: 'string', enum: ['terminal', 'pipe', 'file_polling'], default: 'terminal' },
294
+ trace_id: { type: 'string' },
295
+ correlation_id: { type: 'string' },
296
+ },
297
+ },
298
+ handler: wrap('HITL_REQUEST_FAILED', (args) => {
299
+ return hitl.requestHumanInput(args);
300
+ }),
301
+ },
302
+
303
+ // ── 11. submit_human_input ──
304
+ {
305
+ name: 'submit_human_input',
306
+ description: '사용자 입력 요청에 응답합니다 (accept, decline, cancel)',
307
+ inputSchema: {
308
+ type: 'object',
309
+ required: ['request_id', 'action'],
310
+ properties: {
311
+ request_id: { type: 'string' },
312
+ action: { type: 'string', enum: ['accept', 'decline', 'cancel'] },
313
+ content: { type: 'object' },
314
+ submitted_by: { type: 'string', default: 'human' },
315
+ },
316
+ },
317
+ handler: wrap('HITL_SUBMIT_FAILED', (args) => {
318
+ return hitl.submitHumanInput(args);
319
+ }),
320
+ },
321
+
322
+ // ── 12. team_info ──
323
+ {
324
+ name: 'team_info',
325
+ description: 'Claude Native Teams 메타/멤버/경로 정보를 조회합니다',
326
+ inputSchema: {
327
+ type: 'object',
328
+ required: ['team_name'],
329
+ properties: {
330
+ team_name: { type: 'string', minLength: 1, maxLength: 128, pattern: '^[a-z0-9][a-z0-9-]*$' },
331
+ include_members: { type: 'boolean', default: true },
332
+ include_paths: { type: 'boolean', default: true },
333
+ },
334
+ },
335
+ handler: wrap('TEAM_INFO_FAILED', (args) => {
336
+ return teamInfo(args);
337
+ }),
338
+ },
339
+
340
+ // ── 13. team_task_list ──
341
+ {
342
+ name: 'team_task_list',
343
+ description: 'Claude Native Teams task 목록을 owner/status 조건으로 조회합니다. 실패 판정은 completed + metadata.result도 함께 확인해야 합니다',
344
+ inputSchema: {
345
+ type: 'object',
346
+ required: ['team_name'],
347
+ properties: {
348
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
349
+ owner: { type: 'string' },
350
+ statuses: {
351
+ type: 'array',
352
+ items: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'failed', 'deleted'] },
353
+ maxItems: 8,
354
+ },
355
+ include_internal: { type: 'boolean', default: false },
356
+ limit: { type: 'integer', minimum: 1, maximum: 1000, default: 200 },
357
+ },
358
+ },
359
+ handler: wrap('TEAM_TASK_LIST_FAILED', (args) => {
360
+ return teamTaskList(args);
361
+ }),
362
+ },
363
+
364
+ // ── 14. team_task_update ──
365
+ {
366
+ name: 'team_task_update',
367
+ description: 'Claude Native Teams task를 claim/update 합니다. status: "failed" 입력은 completed + metadata.result="failed"로 정규화됩니다',
368
+ inputSchema: {
369
+ type: 'object',
370
+ required: ['team_name', 'task_id'],
371
+ properties: {
372
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
373
+ task_id: { type: 'string', minLength: 1, maxLength: 64 },
374
+ claim: { type: 'boolean', default: false },
375
+ owner: { type: 'string' },
376
+ status: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'failed', 'deleted'] },
377
+ subject: { type: 'string' },
378
+ description: { type: 'string' },
379
+ activeForm: { type: 'string' },
380
+ add_blocks: { type: 'array', items: { type: 'string' } },
381
+ add_blocked_by: { type: 'array', items: { type: 'string' } },
382
+ metadata_patch: { type: 'object' },
383
+ if_match_mtime_ms: { type: 'number' },
384
+ actor: { type: 'string' },
385
+ },
386
+ },
387
+ handler: wrap('TEAM_TASK_UPDATE_FAILED', (args) => {
388
+ return teamTaskUpdate(args);
389
+ }),
390
+ },
391
+
392
+ // ── 15. team_send_message ──
393
+ {
394
+ name: 'team_send_message',
395
+ description: 'Claude Native Teams inbox에 메시지를 append 합니다',
396
+ inputSchema: {
397
+ type: 'object',
398
+ required: ['team_name', 'from', 'text'],
399
+ properties: {
400
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
401
+ from: { type: 'string', minLength: 1, maxLength: 128 },
402
+ to: { type: 'string', default: 'team-lead' },
403
+ text: { type: 'string', minLength: 1, maxLength: 200000 },
404
+ summary: { type: 'string', maxLength: 1000 },
405
+ color: { type: 'string', default: 'blue' },
406
+ },
407
+ },
408
+ handler: wrap('TEAM_SEND_MESSAGE_FAILED', (args) => {
409
+ return teamSendMessage(args);
410
+ }),
411
+ },
412
+
413
+ // ── 16. pipeline_state ──
414
+ {
415
+ name: 'pipeline_state',
416
+ description: '파이프라인 상태를 조회합니다 (--thorough 모드)',
417
+ inputSchema: {
418
+ type: 'object',
419
+ required: ['team_name'],
420
+ properties: {
421
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
422
+ },
423
+ },
424
+ handler: wrap('PIPELINE_STATE_FAILED', (args) => {
425
+ ensurePipelineTable(store.db);
426
+ const state = readPipelineState(store.db, args.team_name);
427
+ return state
428
+ ? { ok: true, data: state }
429
+ : { ok: false, error: { code: 'PIPELINE_NOT_FOUND', message: `파이프라인 없음: ${args.team_name}` } };
430
+ }),
431
+ },
432
+
433
+ // ── 17. pipeline_advance ──
434
+ {
435
+ name: 'pipeline_advance',
436
+ description: '파이프라인을 다음 단계로 전이합니다 (전이 규칙 + fix loop 바운딩 적용)',
437
+ inputSchema: {
438
+ type: 'object',
439
+ required: ['team_name', 'phase'],
440
+ properties: {
441
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
442
+ phase: { type: 'string', enum: ['plan', 'prd', 'exec', 'verify', 'fix', 'complete', 'failed'] },
443
+ },
444
+ },
445
+ handler: wrap('PIPELINE_ADVANCE_FAILED', (args) => {
446
+ ensurePipelineTable(store.db);
447
+ const pipeline = createPipeline(store.db, args.team_name);
448
+ return pipeline.advance(args.phase);
449
+ }),
450
+ },
451
+
452
+ // ── 18. pipeline_init ──
453
+ {
454
+ name: 'pipeline_init',
455
+ description: '새 파이프라인을 초기화합니다 (기존 상태 덮어쓰기)',
456
+ inputSchema: {
457
+ type: 'object',
458
+ required: ['team_name'],
459
+ properties: {
460
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
461
+ fix_max: { type: 'integer', minimum: 1, maximum: 20, default: 3 },
462
+ ralph_max: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
463
+ },
464
+ },
465
+ handler: wrap('PIPELINE_INIT_FAILED', (args) => {
466
+ ensurePipelineTable(store.db);
467
+ const state = initPipelineState(store.db, args.team_name, {
468
+ fix_max: args.fix_max,
469
+ ralph_max: args.ralph_max,
470
+ });
471
+ return { ok: true, data: state };
472
+ }),
473
+ },
474
+
475
+ // ── 19. pipeline_advance_gated (HITL 승인 게이트) ──
476
+ {
477
+ name: 'pipeline_advance_gated',
478
+ description: 'HITL 승인 게이트가 포함된 파이프라인 전이. 지정 단계로의 전이 전 사용자 승인을 요청하고, 승인 후 전이를 실행합니다. deadline 초과 시 default_action에 따라 자동 처리됩니다.',
479
+ inputSchema: {
480
+ type: 'object',
481
+ required: ['team_name', 'phase'],
482
+ properties: {
483
+ team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
484
+ phase: { type: 'string', enum: ['plan', 'prd', 'exec', 'verify', 'fix', 'complete', 'failed'] },
485
+ prompt: { type: 'string', description: '사용자에게 표시할 승인 요청 메시지' },
486
+ deadline_ms: { type: 'integer', minimum: 5000, maximum: 600000, default: 120000 },
487
+ default_action: { type: 'string', enum: ['timeout_continue', 'timeout_abort'], default: 'timeout_continue' },
488
+ requester_agent: { type: 'string', description: '요청자 에이전트 이름' },
489
+ },
490
+ },
491
+ handler: wrap('PIPELINE_ADVANCE_GATED_FAILED', (args) => {
492
+ ensurePipelineTable(store.db);
493
+ const pipeline = createPipeline(store.db, args.team_name);
494
+
495
+ // 전이 가능 여부 사전 확인
496
+ if (!pipeline.canAdvance(args.phase)) {
497
+ const current = pipeline.getState();
498
+ return {
499
+ ok: false,
500
+ error: {
501
+ code: 'TRANSITION_BLOCKED',
502
+ message: `전이 불가: ${current.phase} → ${args.phase}`,
503
+ },
504
+ };
505
+ }
506
+
507
+ // HITL 승인 요청 생성
508
+ const approvalPrompt = args.prompt || `파이프라인 ${args.team_name}: ${pipeline.getState().phase} → ${args.phase} 전이를 승인하시겠습니까?`;
509
+ const deadlineMs = args.deadline_ms || 120000;
510
+ const now = Date.now();
511
+
512
+ const hitlResult = hitl.requestHumanInput({
513
+ requester_agent: args.requester_agent || `pipeline:${args.team_name}`,
514
+ kind: 'approval',
515
+ prompt: approvalPrompt,
516
+ deadline_ms: now + deadlineMs,
517
+ default_action: args.default_action || 'timeout_continue',
518
+ });
519
+
520
+ if (!hitlResult.ok) {
521
+ return hitlResult;
522
+ }
523
+
524
+ return {
525
+ ok: true,
526
+ data: {
527
+ pending: true,
528
+ request_id: hitlResult.data.request_id,
529
+ team_name: args.team_name,
530
+ target_phase: args.phase,
531
+ current_phase: pipeline.getState().phase,
532
+ deadline_ms: now + deadlineMs,
533
+ default_action: args.default_action || 'timeout_continue',
534
+ message: `승인 대기 중. ID: ${hitlResult.data.request_id}. ${Math.round(deadlineMs / 1000)}초 후 ${args.default_action || 'timeout_continue'} 자동 실행.`,
535
+ },
536
+ };
537
+ }),
538
+ },
539
+
540
+ // ── 20. pipeline_list ──
541
+ {
542
+ name: 'pipeline_list',
543
+ description: '활성 파이프라인 목록을 조회합니다',
544
+ inputSchema: {
545
+ type: 'object',
546
+ properties: {},
547
+ },
548
+ handler: wrap('PIPELINE_LIST_FAILED', () => {
549
+ ensurePipelineTable(store.db);
550
+ return { ok: true, data: listPipelineStates(store.db) };
551
+ }),
552
+ },
553
+ ];
554
+ }