triflux 10.3.4 → 10.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 (120) hide show
  1. package/CLAUDE.md +193 -0
  2. package/LICENSE +21 -21
  3. package/hooks/hook-registry.json +256 -256
  4. package/hub/adaptive-inject.mjs +1 -1
  5. package/hub/assign-callbacks.mjs +120 -120
  6. package/hub/delegator/index.mjs +14 -14
  7. package/hub/delegator/tool-definitions.mjs +35 -35
  8. package/hub/hitl.mjs +143 -143
  9. package/hub/router.mjs +791 -791
  10. package/hub/session-fingerprint.mjs +1 -1
  11. package/hub/team/cli/commands/attach.mjs +37 -37
  12. package/hub/team/cli/commands/debug.mjs +74 -74
  13. package/hub/team/cli/commands/focus.mjs +53 -53
  14. package/hub/team/cli/commands/list.mjs +24 -24
  15. package/hub/team/cli/commands/start/start-in-process.mjs +40 -40
  16. package/hub/team/cli/commands/start/start-mux.mjs +73 -73
  17. package/hub/team/cli/commands/start/start-wt.mjs +69 -69
  18. package/hub/team/cli/commands/tasks.mjs +13 -13
  19. package/hub/team/cli/render.mjs +30 -30
  20. package/hub/team/cli/services/attach-fallback.mjs +54 -54
  21. package/hub/team/cli/services/member-selector.mjs +30 -30
  22. package/hub/team/cli/services/native-control.mjs +116 -116
  23. package/hub/team/cli/services/task-model.mjs +30 -30
  24. package/hub/team/notify.mjs +1 -1
  25. package/hub/team/orchestrator.mjs +161 -161
  26. package/hub/team/session.mjs +611 -611
  27. package/hub/team/shared.mjs +13 -13
  28. package/hub/tray.mjs +368 -368
  29. package/hub/workers/codex-mcp.mjs +507 -507
  30. package/hub/workers/factory.mjs +21 -21
  31. package/package.json +21 -55
  32. package/references/hosts.json +33 -0
  33. package/scripts/completions/tfx.bash +47 -47
  34. package/scripts/completions/tfx.fish +44 -44
  35. package/scripts/completions/tfx.zsh +83 -83
  36. package/scripts/hub-ensure.mjs +120 -120
  37. package/scripts/keyword-detector.mjs +272 -272
  38. package/scripts/keyword-rules-expander.mjs +521 -521
  39. package/scripts/lib/mcp-server-catalog.mjs +118 -118
  40. package/scripts/notion-read.mjs +553 -553
  41. package/scripts/test-tfx-route-no-claude-native.mjs +57 -57
  42. package/scripts/tfx-batch-stats.mjs +96 -96
  43. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  44. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  45. package/skills/.omc/state/last-tool-error.json +7 -0
  46. package/skills/.omc/state/subagent-tracking.json +7 -0
  47. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  48. package/skills/tfx-workspace/async-tests/run-tests.sh +203 -0
  49. package/skills/tfx-workspace/evals/evals.json +79 -0
  50. package/skills/tfx-workspace/iteration-1/benchmark.json +162 -0
  51. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +11 -0
  52. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +9 -0
  53. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +154 -0
  54. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +5 -0
  55. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +9 -0
  56. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +126 -0
  57. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +5 -0
  58. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +11 -0
  59. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +9 -0
  60. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +119 -0
  61. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +5 -0
  62. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +9 -0
  63. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +115 -0
  64. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +5 -0
  65. package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +10 -0
  66. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +8 -0
  67. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +86 -0
  68. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +5 -0
  69. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +8 -0
  70. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +81 -0
  71. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +5 -0
  72. package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +12 -0
  73. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +10 -0
  74. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +316 -0
  75. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +5 -0
  76. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +10 -0
  77. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +352 -0
  78. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +5 -0
  79. package/skills/tfx-workspace/iteration-1/review.html +1325 -0
  80. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +12 -0
  81. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +10 -0
  82. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +97 -0
  83. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +5 -0
  84. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +10 -0
  85. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +94 -0
  86. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +5 -0
  87. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +12 -0
  88. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +10 -0
  89. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +209 -0
  90. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +5 -0
  91. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +10 -0
  92. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +193 -0
  93. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +5 -0
  94. package/skills/tfx-workspace/iteration-2/benchmark.json +62 -0
  95. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +13 -0
  96. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +11 -0
  97. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +382 -0
  98. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +5 -0
  99. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +11 -0
  100. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +333 -0
  101. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +5 -0
  102. package/skills/tfx-workspace/iteration-2/review.html +1325 -0
  103. package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +217 -0
  104. package/skills/tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md +77 -0
  105. package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +65 -0
  106. package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +94 -0
  107. package/skills/tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md +82 -0
  108. package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +133 -0
  109. package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +426 -0
  110. package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +101 -0
  111. package/.claude-plugin/marketplace.json +0 -34
  112. package/.claude-plugin/plugin.json +0 -22
  113. package/config/mcp-registry.json +0 -29
  114. package/tui/codex-profile.mjs +0 -402
  115. package/tui/core.mjs +0 -236
  116. package/tui/doctor.mjs +0 -328
  117. package/tui/gemini-profile.mjs +0 -254
  118. package/tui/monitor-data.mjs +0 -148
  119. package/tui/monitor.mjs +0 -295
  120. package/tui/setup.mjs +0 -442
@@ -1,5 +1,5 @@
1
- // hub/assign-callbacks.mjs — assign job 상태 변경용 Named Pipe/Unix socket 브로드캐스터
2
-
1
+ // hub/assign-callbacks.mjs — assign job 상태 변경용 Named Pipe/Unix socket 브로드캐스터
2
+
3
3
  import net from 'node:net';
4
4
  import { existsSync, unlinkSync } from 'node:fs';
5
5
  import { IS_WINDOWS, pipePath } from './platform.mjs';
@@ -7,127 +7,127 @@ import { IS_WINDOWS, pipePath } from './platform.mjs';
7
7
  export function getAssignCallbackPipePath(sessionId = process.pid) {
8
8
  return pipePath('triflux-assign-callback', sessionId);
9
9
  }
10
-
11
- function buildAssignCallbackEvent(event = {}, row = null) {
12
- const source = row || event || {};
13
- const updatedAtMs = Number(source.updated_at_ms);
14
- const createdAtMs = Number(source.created_at_ms);
15
- const timestampMs = Number.isFinite(updatedAtMs)
16
- ? updatedAtMs
17
- : (Number.isFinite(createdAtMs) ? createdAtMs : Date.now());
18
-
19
- return {
20
- event: 'assign_job_status',
21
- job_id: source.job_id || event.job_id || null,
22
- supervisor_agent: source.supervisor_agent || null,
23
- worker_agent: source.worker_agent || null,
24
- topic: source.topic || null,
25
- task: source.task || null,
26
- status: source.status || event.status || null,
27
- attempt: Number.isFinite(Number(source.attempt)) ? Number(source.attempt) : null,
28
- retry_count: Number.isFinite(Number(source.retry_count)) ? Number(source.retry_count) : null,
29
- max_retries: Number.isFinite(Number(source.max_retries)) ? Number(source.max_retries) : null,
30
- priority: Number.isFinite(Number(source.priority)) ? Number(source.priority) : null,
31
- ttl_ms: Number.isFinite(Number(source.ttl_ms)) ? Number(source.ttl_ms) : null,
32
- timeout_ms: Number.isFinite(Number(source.timeout_ms)) ? Number(source.timeout_ms) : null,
33
- deadline_ms: Number.isFinite(Number(source.deadline_ms)) ? Number(source.deadline_ms) : null,
34
- trace_id: source.trace_id || null,
35
- correlation_id: source.correlation_id || null,
36
- last_message_id: source.last_message_id || null,
37
- result: Object.hasOwn(source, 'result')
38
- ? source.result
39
- : (Object.hasOwn(event, 'result') ? event.result : null),
40
- error: Object.hasOwn(source, 'error')
41
- ? source.error
42
- : (Object.hasOwn(event, 'error') ? event.error : null),
43
- created_at_ms: Number.isFinite(createdAtMs) ? createdAtMs : null,
44
- updated_at_ms: Number.isFinite(updatedAtMs) ? updatedAtMs : null,
45
- started_at_ms: Number.isFinite(Number(source.started_at_ms)) ? Number(source.started_at_ms) : null,
46
- completed_at_ms: Number.isFinite(Number(source.completed_at_ms)) ? Number(source.completed_at_ms) : null,
47
- last_retry_at_ms: Number.isFinite(Number(source.last_retry_at_ms)) ? Number(source.last_retry_at_ms) : null,
48
- timestamp: new Date(timestampMs).toISOString(),
49
- };
50
- }
51
-
52
- export function createAssignCallbackServer({ store = null, sessionId = process.pid } = {}) {
53
- const pipePath = getAssignCallbackPipePath(sessionId);
54
- const clients = new Set();
55
- let server = null;
56
- let detachStoreListener = null;
57
-
58
- function removeSocket(socket) {
59
- if (!socket) return;
60
- clients.delete(socket);
61
- try { socket.destroy(); } catch {}
62
- }
63
-
64
- function broadcast(event) {
65
- const frame = `${JSON.stringify(event)}\n`;
66
- for (const socket of Array.from(clients)) {
67
- if (!socket.writable || socket.destroyed) {
68
- removeSocket(socket);
69
- continue;
70
- }
71
- try {
72
- socket.write(frame);
73
- } catch {
74
- removeSocket(socket);
75
- }
76
- }
77
- }
78
-
79
- return {
80
- path: pipePath,
81
- getStatus() {
82
- return {
83
- path: pipePath,
84
- clients: clients.size,
85
- };
86
- },
87
- async start() {
88
- if (server) return { path: pipePath };
10
+
11
+ function buildAssignCallbackEvent(event = {}, row = null) {
12
+ const source = row || event || {};
13
+ const updatedAtMs = Number(source.updated_at_ms);
14
+ const createdAtMs = Number(source.created_at_ms);
15
+ const timestampMs = Number.isFinite(updatedAtMs)
16
+ ? updatedAtMs
17
+ : (Number.isFinite(createdAtMs) ? createdAtMs : Date.now());
18
+
19
+ return {
20
+ event: 'assign_job_status',
21
+ job_id: source.job_id || event.job_id || null,
22
+ supervisor_agent: source.supervisor_agent || null,
23
+ worker_agent: source.worker_agent || null,
24
+ topic: source.topic || null,
25
+ task: source.task || null,
26
+ status: source.status || event.status || null,
27
+ attempt: Number.isFinite(Number(source.attempt)) ? Number(source.attempt) : null,
28
+ retry_count: Number.isFinite(Number(source.retry_count)) ? Number(source.retry_count) : null,
29
+ max_retries: Number.isFinite(Number(source.max_retries)) ? Number(source.max_retries) : null,
30
+ priority: Number.isFinite(Number(source.priority)) ? Number(source.priority) : null,
31
+ ttl_ms: Number.isFinite(Number(source.ttl_ms)) ? Number(source.ttl_ms) : null,
32
+ timeout_ms: Number.isFinite(Number(source.timeout_ms)) ? Number(source.timeout_ms) : null,
33
+ deadline_ms: Number.isFinite(Number(source.deadline_ms)) ? Number(source.deadline_ms) : null,
34
+ trace_id: source.trace_id || null,
35
+ correlation_id: source.correlation_id || null,
36
+ last_message_id: source.last_message_id || null,
37
+ result: Object.hasOwn(source, 'result')
38
+ ? source.result
39
+ : (Object.hasOwn(event, 'result') ? event.result : null),
40
+ error: Object.hasOwn(source, 'error')
41
+ ? source.error
42
+ : (Object.hasOwn(event, 'error') ? event.error : null),
43
+ created_at_ms: Number.isFinite(createdAtMs) ? createdAtMs : null,
44
+ updated_at_ms: Number.isFinite(updatedAtMs) ? updatedAtMs : null,
45
+ started_at_ms: Number.isFinite(Number(source.started_at_ms)) ? Number(source.started_at_ms) : null,
46
+ completed_at_ms: Number.isFinite(Number(source.completed_at_ms)) ? Number(source.completed_at_ms) : null,
47
+ last_retry_at_ms: Number.isFinite(Number(source.last_retry_at_ms)) ? Number(source.last_retry_at_ms) : null,
48
+ timestamp: new Date(timestampMs).toISOString(),
49
+ };
50
+ }
51
+
52
+ export function createAssignCallbackServer({ store = null, sessionId = process.pid } = {}) {
53
+ const pipePath = getAssignCallbackPipePath(sessionId);
54
+ const clients = new Set();
55
+ let server = null;
56
+ let detachStoreListener = null;
57
+
58
+ function removeSocket(socket) {
59
+ if (!socket) return;
60
+ clients.delete(socket);
61
+ try { socket.destroy(); } catch {}
62
+ }
63
+
64
+ function broadcast(event) {
65
+ const frame = `${JSON.stringify(event)}\n`;
66
+ for (const socket of Array.from(clients)) {
67
+ if (!socket.writable || socket.destroyed) {
68
+ removeSocket(socket);
69
+ continue;
70
+ }
71
+ try {
72
+ socket.write(frame);
73
+ } catch {
74
+ removeSocket(socket);
75
+ }
76
+ }
77
+ }
78
+
79
+ return {
80
+ path: pipePath,
81
+ getStatus() {
82
+ return {
83
+ path: pipePath,
84
+ clients: clients.size,
85
+ };
86
+ },
87
+ async start() {
88
+ if (server) return { path: pipePath };
89
89
  if (!IS_WINDOWS && existsSync(pipePath)) {
90
90
  try { unlinkSync(pipePath); } catch {}
91
91
  }
92
-
93
- server = net.createServer((socket) => {
94
- clients.add(socket);
95
- socket.setEncoding('utf8');
96
- socket.on('error', () => removeSocket(socket));
97
- socket.on('close', () => removeSocket(socket));
98
- });
99
-
100
- await new Promise((resolve, reject) => {
101
- server.once('error', reject);
102
- server.listen(pipePath, () => {
103
- server?.off('error', reject);
104
- resolve();
105
- });
106
- });
107
-
108
- if (store?.onAssignStatusChange && !detachStoreListener) {
109
- detachStoreListener = store.onAssignStatusChange((event, row) => {
110
- broadcast(buildAssignCallbackEvent(event, row));
111
- });
112
- }
113
-
114
- return { path: pipePath };
115
- },
116
- async stop() {
117
- if (detachStoreListener) {
118
- try { detachStoreListener(); } catch {}
119
- detachStoreListener = null;
120
- }
121
- if (!server) return;
122
- for (const socket of Array.from(clients)) {
123
- removeSocket(socket);
124
- }
125
- await new Promise((resolve) => server.close(resolve));
126
- server = null;
92
+
93
+ server = net.createServer((socket) => {
94
+ clients.add(socket);
95
+ socket.setEncoding('utf8');
96
+ socket.on('error', () => removeSocket(socket));
97
+ socket.on('close', () => removeSocket(socket));
98
+ });
99
+
100
+ await new Promise((resolve, reject) => {
101
+ server.once('error', reject);
102
+ server.listen(pipePath, () => {
103
+ server?.off('error', reject);
104
+ resolve();
105
+ });
106
+ });
107
+
108
+ if (store?.onAssignStatusChange && !detachStoreListener) {
109
+ detachStoreListener = store.onAssignStatusChange((event, row) => {
110
+ broadcast(buildAssignCallbackEvent(event, row));
111
+ });
112
+ }
113
+
114
+ return { path: pipePath };
115
+ },
116
+ async stop() {
117
+ if (detachStoreListener) {
118
+ try { detachStoreListener(); } catch {}
119
+ detachStoreListener = null;
120
+ }
121
+ if (!server) return;
122
+ for (const socket of Array.from(clients)) {
123
+ removeSocket(socket);
124
+ }
125
+ await new Promise((resolve) => server.close(resolve));
126
+ server = null;
127
127
  if (!IS_WINDOWS && existsSync(pipePath)) {
128
128
  try { unlinkSync(pipePath); } catch {}
129
129
  }
130
- },
131
- broadcast,
132
- };
133
- }
130
+ },
131
+ broadcast,
132
+ };
133
+ }
@@ -1,14 +1,14 @@
1
- export {
2
- DELEGATOR_JOB_STATUSES,
3
- DELEGATOR_MCP_SERVER_INFO,
4
- DELEGATOR_MODES,
5
- DELEGATOR_PIPE_ACTIONS,
6
- DELEGATOR_PROVIDERS,
7
- DELEGATOR_SCHEMA_URL,
8
- DELEGATOR_TOOL_NAMES,
9
- } from './contracts.mjs';
10
- export { DelegatorService } from './service.mjs';
11
- export {
12
- getDelegatorMcpToolDefinitions,
13
- loadDelegatorSchemaBundle,
14
- } from './tool-definitions.mjs';
1
+ export {
2
+ DELEGATOR_JOB_STATUSES,
3
+ DELEGATOR_MCP_SERVER_INFO,
4
+ DELEGATOR_MODES,
5
+ DELEGATOR_PIPE_ACTIONS,
6
+ DELEGATOR_PROVIDERS,
7
+ DELEGATOR_SCHEMA_URL,
8
+ DELEGATOR_TOOL_NAMES,
9
+ } from './contracts.mjs';
10
+ export { DelegatorService } from './service.mjs';
11
+ export {
12
+ getDelegatorMcpToolDefinitions,
13
+ loadDelegatorSchemaBundle,
14
+ } from './tool-definitions.mjs';
@@ -1,35 +1,35 @@
1
- import { readFileSync } from 'node:fs';
2
-
3
- import { DELEGATOR_SCHEMA_URL } from './contracts.mjs';
4
-
5
- let schemaBundleCache = null;
6
-
7
- function deepClone(value) {
8
- if (value == null) return value;
9
- return JSON.parse(JSON.stringify(value));
10
- }
11
-
12
- export function loadDelegatorSchemaBundle() {
13
- if (schemaBundleCache) {
14
- return schemaBundleCache;
15
- }
16
-
17
- schemaBundleCache = JSON.parse(readFileSync(DELEGATOR_SCHEMA_URL, 'utf8'));
18
- return schemaBundleCache;
19
- }
20
-
21
- export function getDelegatorMcpToolDefinitions() {
22
- const bundle = loadDelegatorSchemaBundle();
23
- const defs = bundle.$defs || {};
24
- const tools = Array.isArray(bundle['x-triflux-mcp-tools'])
25
- ? bundle['x-triflux-mcp-tools']
26
- : [];
27
-
28
- return tools.map((tool) => ({
29
- name: tool.name,
30
- description: tool.description,
31
- inputSchema: deepClone(defs[tool.inputSchemaDef]),
32
- outputSchema: deepClone(defs[tool.outputSchemaDef]),
33
- pipeAction: tool.pipeAction,
34
- }));
35
- }
1
+ import { readFileSync } from 'node:fs';
2
+
3
+ import { DELEGATOR_SCHEMA_URL } from './contracts.mjs';
4
+
5
+ let schemaBundleCache = null;
6
+
7
+ function deepClone(value) {
8
+ if (value == null) return value;
9
+ return JSON.parse(JSON.stringify(value));
10
+ }
11
+
12
+ export function loadDelegatorSchemaBundle() {
13
+ if (schemaBundleCache) {
14
+ return schemaBundleCache;
15
+ }
16
+
17
+ schemaBundleCache = JSON.parse(readFileSync(DELEGATOR_SCHEMA_URL, 'utf8'));
18
+ return schemaBundleCache;
19
+ }
20
+
21
+ export function getDelegatorMcpToolDefinitions() {
22
+ const bundle = loadDelegatorSchemaBundle();
23
+ const defs = bundle.$defs || {};
24
+ const tools = Array.isArray(bundle['x-triflux-mcp-tools'])
25
+ ? bundle['x-triflux-mcp-tools']
26
+ : [];
27
+
28
+ return tools.map((tool) => ({
29
+ name: tool.name,
30
+ description: tool.description,
31
+ inputSchema: deepClone(defs[tool.inputSchemaDef]),
32
+ outputSchema: deepClone(defs[tool.outputSchemaDef]),
33
+ pipeAction: tool.pipeAction,
34
+ }));
35
+ }
package/hub/hitl.mjs CHANGED
@@ -1,143 +1,143 @@
1
- // hub/hitl.mjs — Human-in-the-Loop 매니저
2
- // 사용자 입력 요청/응답, 타임아웃 자동 처리
3
-
4
- /**
5
- * HITL 매니저 생성
6
- * @param {object} store — createStore() 반환 객체
7
- * @param {object} router — createRouter() 반환 객체
8
- */
9
- export function createHitlManager(store, router = null) {
10
- function forwardHumanResponse({ requesterAgent, requestId, action, content, submittedBy, correlationId, traceId, priority }) {
11
- if (!router?.handlePublish) {
12
- throw new Error('router.handlePublish is required for HITL forwarding');
13
- }
14
- return router.handlePublish({
15
- from: 'hub:hitl',
16
- to: requesterAgent,
17
- topic: 'human.response',
18
- priority,
19
- ttl_ms: 300000,
20
- payload: { request_id: requestId, action, content, submitted_by: submittedBy },
21
- correlation_id: correlationId,
22
- trace_id: traceId,
23
- message_type: 'human_response',
24
- });
25
- }
26
-
27
- return {
28
- /**
29
- * 사용자에게 입력 요청 생성
30
- * 터미널에 알림 출력 후 pending 상태로 저장
31
- */
32
- requestHumanInput({
33
- requester_agent, kind, prompt, requested_schema = {},
34
- deadline_ms, default_action, channel_preference = 'terminal',
35
- correlation_id, trace_id,
36
- }) {
37
- const result = store.insertHumanRequest({
38
- requester_agent, kind, prompt, requested_schema,
39
- deadline_ms, default_action,
40
- correlation_id, trace_id,
41
- });
42
-
43
- // 터미널 알림 (stderr — stdout은 MCP 용)
44
- const kindLabel = { captcha: 'CAPTCHA', approval: '승인', credential: '자격증명', choice: '선택', text: '텍스트' };
45
- process.stderr.write(
46
- `\n[tfx-hub] 사용자 입력 요청 (${kindLabel[kind] || kind})\n` +
47
- ` 요청자: ${requester_agent}\n` +
48
- ` 내용: ${prompt}\n` +
49
- ` ID: ${result.request_id}\n` +
50
- ` 제한: ${Math.round(deadline_ms / 1000)}초\n\n`,
51
- );
52
-
53
- return { ok: true, data: result };
54
- },
55
-
56
- /**
57
- * 사용자 입력 응답 제출
58
- * 유효성 검증 → 상태 업데이트 → 요청자에게 응답 메시지 전달
59
- */
60
- submitHumanInput({ request_id, action, content = null, submitted_by = 'human' }) {
61
- // 요청 조회
62
- const hr = store.getHumanRequest(request_id);
63
- if (!hr) {
64
- return { ok: false, error: { code: 'NOT_FOUND', message: `요청 없음: ${request_id}` } };
65
- }
66
- if (hr.state !== 'pending') {
67
- return { ok: false, error: { code: 'ALREADY_HANDLED', message: `이미 처리됨: ${hr.state}` } };
68
- }
69
-
70
- // 상태 매핑
71
- const stateMap = { accept: 'accepted', decline: 'declined', cancel: 'cancelled' };
72
- const newState = stateMap[action];
73
- if (!newState) {
74
- return { ok: false, error: { code: 'INVALID_ACTION', message: `잘못된 action: ${action}` } };
75
- }
76
-
77
- // DB 업데이트
78
- store.updateHumanRequest(request_id, newState, content);
79
-
80
- // 요청자에게 응답 메시지 전달
81
- let forwardedMessageId = null;
82
- if (action === 'accept' || action === 'decline') {
83
- const published = forwardHumanResponse({
84
- requesterAgent: hr.requester_agent,
85
- requestId: request_id,
86
- action,
87
- content,
88
- submittedBy: submitted_by,
89
- correlationId: hr.correlation_id,
90
- traceId: hr.trace_id,
91
- priority: 7,
92
- });
93
- forwardedMessageId = published.data?.message_id || null;
94
- }
95
-
96
- return {
97
- ok: true,
98
- data: { request_id, new_state: newState, forwarded_message_id: forwardedMessageId },
99
- };
100
- },
101
-
102
- /**
103
- * 만료된 요청 자동 처리
104
- * deadline 초과 시 default_action 적용
105
- */
106
- checkTimeouts() {
107
- const pending = store.getPendingHumanRequests();
108
- const now = Date.now();
109
- const expired = pending.filter(hr => hr.deadline_ms <= now);
110
- if (!expired.length) return 0;
111
-
112
- const expireRequests = () => {
113
- for (const hr of expired) {
114
- store.updateHumanRequest(hr.request_id, 'timed_out', null);
115
- if (hr.default_action === 'timeout_continue') {
116
- forwardHumanResponse({
117
- requesterAgent: hr.requester_agent,
118
- requestId: hr.request_id,
119
- action: 'timeout_continue',
120
- content: null,
121
- submittedBy: 'system',
122
- correlationId: hr.correlation_id,
123
- traceId: hr.trace_id,
124
- priority: 5,
125
- });
126
- }
127
- }
128
- return expired.length;
129
- };
130
-
131
- const processExpired = store.db?.transaction
132
- ? store.db.transaction(expireRequests)
133
- : expireRequests;
134
-
135
- return processExpired();
136
- },
137
-
138
- /** 대기 중인 요청 목록 */
139
- getPendingRequests() {
140
- return store.getPendingHumanRequests();
141
- },
142
- };
143
- }
1
+ // hub/hitl.mjs — Human-in-the-Loop 매니저
2
+ // 사용자 입력 요청/응답, 타임아웃 자동 처리
3
+
4
+ /**
5
+ * HITL 매니저 생성
6
+ * @param {object} store — createStore() 반환 객체
7
+ * @param {object} router — createRouter() 반환 객체
8
+ */
9
+ export function createHitlManager(store, router = null) {
10
+ function forwardHumanResponse({ requesterAgent, requestId, action, content, submittedBy, correlationId, traceId, priority }) {
11
+ if (!router?.handlePublish) {
12
+ throw new Error('router.handlePublish is required for HITL forwarding');
13
+ }
14
+ return router.handlePublish({
15
+ from: 'hub:hitl',
16
+ to: requesterAgent,
17
+ topic: 'human.response',
18
+ priority,
19
+ ttl_ms: 300000,
20
+ payload: { request_id: requestId, action, content, submitted_by: submittedBy },
21
+ correlation_id: correlationId,
22
+ trace_id: traceId,
23
+ message_type: 'human_response',
24
+ });
25
+ }
26
+
27
+ return {
28
+ /**
29
+ * 사용자에게 입력 요청 생성
30
+ * 터미널에 알림 출력 후 pending 상태로 저장
31
+ */
32
+ requestHumanInput({
33
+ requester_agent, kind, prompt, requested_schema = {},
34
+ deadline_ms, default_action, channel_preference = 'terminal',
35
+ correlation_id, trace_id,
36
+ }) {
37
+ const result = store.insertHumanRequest({
38
+ requester_agent, kind, prompt, requested_schema,
39
+ deadline_ms, default_action,
40
+ correlation_id, trace_id,
41
+ });
42
+
43
+ // 터미널 알림 (stderr — stdout은 MCP 용)
44
+ const kindLabel = { captcha: 'CAPTCHA', approval: '승인', credential: '자격증명', choice: '선택', text: '텍스트' };
45
+ process.stderr.write(
46
+ `\n[tfx-hub] 사용자 입력 요청 (${kindLabel[kind] || kind})\n` +
47
+ ` 요청자: ${requester_agent}\n` +
48
+ ` 내용: ${prompt}\n` +
49
+ ` ID: ${result.request_id}\n` +
50
+ ` 제한: ${Math.round(deadline_ms / 1000)}초\n\n`,
51
+ );
52
+
53
+ return { ok: true, data: result };
54
+ },
55
+
56
+ /**
57
+ * 사용자 입력 응답 제출
58
+ * 유효성 검증 → 상태 업데이트 → 요청자에게 응답 메시지 전달
59
+ */
60
+ submitHumanInput({ request_id, action, content = null, submitted_by = 'human' }) {
61
+ // 요청 조회
62
+ const hr = store.getHumanRequest(request_id);
63
+ if (!hr) {
64
+ return { ok: false, error: { code: 'NOT_FOUND', message: `요청 없음: ${request_id}` } };
65
+ }
66
+ if (hr.state !== 'pending') {
67
+ return { ok: false, error: { code: 'ALREADY_HANDLED', message: `이미 처리됨: ${hr.state}` } };
68
+ }
69
+
70
+ // 상태 매핑
71
+ const stateMap = { accept: 'accepted', decline: 'declined', cancel: 'cancelled' };
72
+ const newState = stateMap[action];
73
+ if (!newState) {
74
+ return { ok: false, error: { code: 'INVALID_ACTION', message: `잘못된 action: ${action}` } };
75
+ }
76
+
77
+ // DB 업데이트
78
+ store.updateHumanRequest(request_id, newState, content);
79
+
80
+ // 요청자에게 응답 메시지 전달
81
+ let forwardedMessageId = null;
82
+ if (action === 'accept' || action === 'decline') {
83
+ const published = forwardHumanResponse({
84
+ requesterAgent: hr.requester_agent,
85
+ requestId: request_id,
86
+ action,
87
+ content,
88
+ submittedBy: submitted_by,
89
+ correlationId: hr.correlation_id,
90
+ traceId: hr.trace_id,
91
+ priority: 7,
92
+ });
93
+ forwardedMessageId = published.data?.message_id || null;
94
+ }
95
+
96
+ return {
97
+ ok: true,
98
+ data: { request_id, new_state: newState, forwarded_message_id: forwardedMessageId },
99
+ };
100
+ },
101
+
102
+ /**
103
+ * 만료된 요청 자동 처리
104
+ * deadline 초과 시 default_action 적용
105
+ */
106
+ checkTimeouts() {
107
+ const pending = store.getPendingHumanRequests();
108
+ const now = Date.now();
109
+ const expired = pending.filter(hr => hr.deadline_ms <= now);
110
+ if (!expired.length) return 0;
111
+
112
+ const expireRequests = () => {
113
+ for (const hr of expired) {
114
+ store.updateHumanRequest(hr.request_id, 'timed_out', null);
115
+ if (hr.default_action === 'timeout_continue') {
116
+ forwardHumanResponse({
117
+ requesterAgent: hr.requester_agent,
118
+ requestId: hr.request_id,
119
+ action: 'timeout_continue',
120
+ content: null,
121
+ submittedBy: 'system',
122
+ correlationId: hr.correlation_id,
123
+ traceId: hr.trace_id,
124
+ priority: 5,
125
+ });
126
+ }
127
+ }
128
+ return expired.length;
129
+ };
130
+
131
+ const processExpired = store.db?.transaction
132
+ ? store.db.transaction(expireRequests)
133
+ : expireRequests;
134
+
135
+ return processExpired();
136
+ },
137
+
138
+ /** 대기 중인 요청 목록 */
139
+ getPendingRequests() {
140
+ return store.getPendingHumanRequests();
141
+ },
142
+ };
143
+ }