@triflux/core 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.
- package/hooks/agent-route-guard.mjs +109 -0
- package/hooks/cross-review-tracker.mjs +122 -0
- package/hooks/error-context.mjs +148 -0
- package/hooks/hook-manager.mjs +352 -0
- package/hooks/hook-orchestrator.mjs +312 -0
- package/hooks/hook-registry.json +213 -0
- package/hooks/hooks.json +89 -0
- package/hooks/keyword-rules.json +581 -0
- package/hooks/lib/resolve-root.mjs +59 -0
- package/hooks/mcp-config-watcher.mjs +85 -0
- package/hooks/pipeline-stop.mjs +76 -0
- package/hooks/safety-guard.mjs +106 -0
- package/hooks/subagent-verifier.mjs +80 -0
- package/hub/assign-callbacks.mjs +133 -0
- package/hub/bridge.mjs +799 -0
- package/hub/cli-adapter-base.mjs +192 -0
- package/hub/codex-adapter.mjs +190 -0
- package/hub/codex-compat.mjs +78 -0
- package/hub/codex-preflight.mjs +147 -0
- package/hub/delegator/contracts.mjs +37 -0
- package/hub/delegator/index.mjs +14 -0
- package/hub/delegator/schema/delegator-tools.schema.json +250 -0
- package/hub/delegator/service.mjs +307 -0
- package/hub/delegator/tool-definitions.mjs +35 -0
- package/hub/fullcycle.mjs +96 -0
- package/hub/gemini-adapter.mjs +179 -0
- package/hub/hitl.mjs +143 -0
- package/hub/intent.mjs +193 -0
- package/hub/lib/process-utils.mjs +361 -0
- package/hub/middleware/request-logger.mjs +81 -0
- package/hub/paths.mjs +30 -0
- package/hub/pipeline/gates/confidence.mjs +56 -0
- package/hub/pipeline/gates/consensus.mjs +94 -0
- package/hub/pipeline/gates/index.mjs +5 -0
- package/hub/pipeline/gates/selfcheck.mjs +82 -0
- package/hub/pipeline/index.mjs +318 -0
- package/hub/pipeline/state.mjs +191 -0
- package/hub/pipeline/transitions.mjs +124 -0
- package/hub/platform.mjs +225 -0
- package/hub/quality/deslop.mjs +253 -0
- package/hub/reflexion.mjs +372 -0
- package/hub/research.mjs +146 -0
- package/hub/router.mjs +791 -0
- package/hub/routing/complexity.mjs +166 -0
- package/hub/routing/index.mjs +117 -0
- package/hub/routing/q-learning.mjs +336 -0
- package/hub/session-fingerprint.mjs +352 -0
- package/hub/state.mjs +245 -0
- package/hub/team-bridge.mjs +25 -0
- package/hub/token-mode.mjs +224 -0
- package/hub/workers/worker-utils.mjs +104 -0
- package/hud/colors.mjs +88 -0
- package/hud/constants.mjs +81 -0
- package/hud/hud-qos-status.mjs +206 -0
- package/hud/providers/claude.mjs +309 -0
- package/hud/providers/codex.mjs +151 -0
- package/hud/providers/gemini.mjs +320 -0
- package/hud/renderers.mjs +424 -0
- package/hud/terminal.mjs +140 -0
- package/hud/utils.mjs +287 -0
- package/package.json +31 -0
- package/scripts/lib/claudemd-manager.mjs +325 -0
- package/scripts/lib/context.mjs +67 -0
- package/scripts/lib/cross-review-utils.mjs +51 -0
- package/scripts/lib/env-probe.mjs +241 -0
- package/scripts/lib/gemini-profiles.mjs +85 -0
- package/scripts/lib/hook-utils.mjs +14 -0
- package/scripts/lib/keyword-rules.mjs +166 -0
- package/scripts/lib/logger.mjs +105 -0
- package/scripts/lib/mcp-filter.mjs +739 -0
- package/scripts/lib/mcp-guard-engine.mjs +940 -0
- package/scripts/lib/mcp-manifest.mjs +79 -0
- package/scripts/lib/mcp-server-catalog.mjs +118 -0
- package/scripts/lib/psmux-info.mjs +119 -0
- package/scripts/lib/remote-spawn-transfer.mjs +196 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://triflux.dev/schema/hub/delegator-tools.schema.json",
|
|
4
|
+
"title": "Triflux Delegator MCP Tool Schemas",
|
|
5
|
+
"$defs": {
|
|
6
|
+
"Provider": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"enum": ["auto", "codex", "gemini"]
|
|
9
|
+
},
|
|
10
|
+
"Mode": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"enum": ["sync", "async"]
|
|
13
|
+
},
|
|
14
|
+
"JobStatus": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["queued", "running", "waiting_reply", "completed", "failed"]
|
|
17
|
+
},
|
|
18
|
+
"McpProfile": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"enum": [
|
|
21
|
+
"auto",
|
|
22
|
+
"default",
|
|
23
|
+
"executor",
|
|
24
|
+
"designer",
|
|
25
|
+
"explore",
|
|
26
|
+
"reviewer",
|
|
27
|
+
"writer",
|
|
28
|
+
"none",
|
|
29
|
+
"implement",
|
|
30
|
+
"analyze",
|
|
31
|
+
"review",
|
|
32
|
+
"docs",
|
|
33
|
+
"minimal"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"SearchTool": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"enum": ["exa", "brave-search", "tavily"]
|
|
39
|
+
},
|
|
40
|
+
"NullableDateTime": {
|
|
41
|
+
"type": ["string", "null"],
|
|
42
|
+
"format": "date-time"
|
|
43
|
+
},
|
|
44
|
+
"NullableString": {
|
|
45
|
+
"type": ["string", "null"]
|
|
46
|
+
},
|
|
47
|
+
"DelegateInput": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"required": ["prompt"],
|
|
51
|
+
"properties": {
|
|
52
|
+
"prompt": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"minLength": 1
|
|
55
|
+
},
|
|
56
|
+
"provider": {
|
|
57
|
+
"$ref": "#/$defs/Provider",
|
|
58
|
+
"default": "auto"
|
|
59
|
+
},
|
|
60
|
+
"mode": {
|
|
61
|
+
"$ref": "#/$defs/Mode",
|
|
62
|
+
"default": "sync"
|
|
63
|
+
},
|
|
64
|
+
"agent_type": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"default": "executor",
|
|
67
|
+
"minLength": 1
|
|
68
|
+
},
|
|
69
|
+
"cwd": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"minLength": 1
|
|
72
|
+
},
|
|
73
|
+
"timeout_ms": {
|
|
74
|
+
"type": "integer",
|
|
75
|
+
"minimum": 1
|
|
76
|
+
},
|
|
77
|
+
"session_key": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"minLength": 1
|
|
80
|
+
},
|
|
81
|
+
"thread_id": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"minLength": 1
|
|
84
|
+
},
|
|
85
|
+
"reset_session": {
|
|
86
|
+
"type": "boolean",
|
|
87
|
+
"default": false
|
|
88
|
+
},
|
|
89
|
+
"mcp_profile": {
|
|
90
|
+
"$ref": "#/$defs/McpProfile",
|
|
91
|
+
"default": "auto"
|
|
92
|
+
},
|
|
93
|
+
"search_tool": {
|
|
94
|
+
"$ref": "#/$defs/SearchTool"
|
|
95
|
+
},
|
|
96
|
+
"context_file": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"minLength": 1
|
|
99
|
+
},
|
|
100
|
+
"model": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"minLength": 1
|
|
103
|
+
},
|
|
104
|
+
"developer_instructions": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"minLength": 1
|
|
107
|
+
},
|
|
108
|
+
"compact_prompt": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"minLength": 1
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"DelegateReplyInput": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"additionalProperties": false,
|
|
117
|
+
"required": ["job_id", "reply"],
|
|
118
|
+
"properties": {
|
|
119
|
+
"job_id": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"minLength": 1
|
|
122
|
+
},
|
|
123
|
+
"reply": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"minLength": 1
|
|
126
|
+
},
|
|
127
|
+
"done": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
"default": false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"StatusInput": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"additionalProperties": false,
|
|
136
|
+
"required": ["job_id"],
|
|
137
|
+
"properties": {
|
|
138
|
+
"job_id": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"minLength": 1
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"DelegateOutput": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"additionalProperties": false,
|
|
147
|
+
"required": [
|
|
148
|
+
"ok",
|
|
149
|
+
"job_id",
|
|
150
|
+
"status",
|
|
151
|
+
"mode",
|
|
152
|
+
"provider_requested",
|
|
153
|
+
"provider_resolved",
|
|
154
|
+
"agent_type",
|
|
155
|
+
"transport",
|
|
156
|
+
"created_at",
|
|
157
|
+
"started_at",
|
|
158
|
+
"updated_at",
|
|
159
|
+
"completed_at",
|
|
160
|
+
"thread_id",
|
|
161
|
+
"session_key",
|
|
162
|
+
"conversation_open"
|
|
163
|
+
],
|
|
164
|
+
"properties": {
|
|
165
|
+
"ok": {
|
|
166
|
+
"type": "boolean"
|
|
167
|
+
},
|
|
168
|
+
"job_id": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"minLength": 1
|
|
171
|
+
},
|
|
172
|
+
"status": {
|
|
173
|
+
"$ref": "#/$defs/JobStatus"
|
|
174
|
+
},
|
|
175
|
+
"mode": {
|
|
176
|
+
"$ref": "#/$defs/Mode"
|
|
177
|
+
},
|
|
178
|
+
"provider_requested": {
|
|
179
|
+
"$ref": "#/$defs/Provider"
|
|
180
|
+
},
|
|
181
|
+
"provider_resolved": {
|
|
182
|
+
"type": ["string", "null"]
|
|
183
|
+
},
|
|
184
|
+
"agent_type": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"minLength": 1
|
|
187
|
+
},
|
|
188
|
+
"transport": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"minLength": 1
|
|
191
|
+
},
|
|
192
|
+
"created_at": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"format": "date-time"
|
|
195
|
+
},
|
|
196
|
+
"started_at": {
|
|
197
|
+
"$ref": "#/$defs/NullableDateTime"
|
|
198
|
+
},
|
|
199
|
+
"updated_at": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"format": "date-time"
|
|
202
|
+
},
|
|
203
|
+
"completed_at": {
|
|
204
|
+
"$ref": "#/$defs/NullableDateTime"
|
|
205
|
+
},
|
|
206
|
+
"output": {
|
|
207
|
+
"type": "string"
|
|
208
|
+
},
|
|
209
|
+
"stderr": {
|
|
210
|
+
"type": "string"
|
|
211
|
+
},
|
|
212
|
+
"error": {
|
|
213
|
+
"type": "string"
|
|
214
|
+
},
|
|
215
|
+
"thread_id": {
|
|
216
|
+
"$ref": "#/$defs/NullableString"
|
|
217
|
+
},
|
|
218
|
+
"session_key": {
|
|
219
|
+
"$ref": "#/$defs/NullableString"
|
|
220
|
+
},
|
|
221
|
+
"conversation_open": {
|
|
222
|
+
"type": "boolean"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"x-triflux-mcp-tools": [
|
|
228
|
+
{
|
|
229
|
+
"name": "delegate",
|
|
230
|
+
"description": "Create a new delegator job and optionally wait for the first result.",
|
|
231
|
+
"inputSchemaDef": "DelegateInput",
|
|
232
|
+
"outputSchemaDef": "DelegateOutput",
|
|
233
|
+
"pipeAction": "delegator_delegate"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"name": "delegate-reply",
|
|
237
|
+
"description": "Send a follow-up reply into an existing delegator conversation job.",
|
|
238
|
+
"inputSchemaDef": "DelegateReplyInput",
|
|
239
|
+
"outputSchemaDef": "DelegateOutput",
|
|
240
|
+
"pipeAction": "delegator_reply"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"name": "status",
|
|
244
|
+
"description": "Read the latest snapshot for an existing delegator job.",
|
|
245
|
+
"inputSchemaDef": "StatusInput",
|
|
246
|
+
"outputSchemaDef": "DelegateOutput",
|
|
247
|
+
"pipeAction": "delegator_status"
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DELEGATOR_JOB_STATUSES,
|
|
5
|
+
DELEGATOR_MODES,
|
|
6
|
+
DELEGATOR_PROVIDERS,
|
|
7
|
+
} from './contracts.mjs';
|
|
8
|
+
import { getDelegatorMcpToolDefinitions } from './tool-definitions.mjs';
|
|
9
|
+
|
|
10
|
+
function deepClone(value) {
|
|
11
|
+
if (value == null) return value;
|
|
12
|
+
return JSON.parse(JSON.stringify(value));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function assertKnown(enumValues, value, fieldName) {
|
|
16
|
+
if (value == null) return;
|
|
17
|
+
if (!enumValues.includes(value)) {
|
|
18
|
+
throw new Error(`Unsupported ${fieldName}: ${value}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class DelegatorService {
|
|
23
|
+
constructor({
|
|
24
|
+
idFactory = randomUUID,
|
|
25
|
+
now = () => new Date(),
|
|
26
|
+
worker = null,
|
|
27
|
+
} = {}) {
|
|
28
|
+
this.idFactory = idFactory;
|
|
29
|
+
this.now = now;
|
|
30
|
+
this.jobs = new Map();
|
|
31
|
+
this.worker = worker;
|
|
32
|
+
this._workerJobMap = new Map();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
listToolDefinitions() {
|
|
36
|
+
return getDelegatorMcpToolDefinitions();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
createJobSnapshot(input = {}) {
|
|
40
|
+
const timestamp = this.now().toISOString();
|
|
41
|
+
const jobId = input.job_id || this.idFactory();
|
|
42
|
+
const mode = input.mode || 'sync';
|
|
43
|
+
const providerRequested = input.provider || 'auto';
|
|
44
|
+
|
|
45
|
+
assertKnown(DELEGATOR_MODES, mode, 'mode');
|
|
46
|
+
assertKnown(DELEGATOR_PROVIDERS, providerRequested, 'provider');
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
job_id: jobId,
|
|
51
|
+
status: 'queued',
|
|
52
|
+
mode,
|
|
53
|
+
provider_requested: providerRequested,
|
|
54
|
+
provider_resolved: null,
|
|
55
|
+
agent_type: input.agent_type || 'executor',
|
|
56
|
+
transport: 'resident-pending',
|
|
57
|
+
created_at: timestamp,
|
|
58
|
+
started_at: null,
|
|
59
|
+
updated_at: timestamp,
|
|
60
|
+
completed_at: null,
|
|
61
|
+
output: '',
|
|
62
|
+
stderr: '',
|
|
63
|
+
error: '',
|
|
64
|
+
thread_id: input.thread_id || null,
|
|
65
|
+
session_key: input.session_key || null,
|
|
66
|
+
conversation_open: false,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
recordJob(snapshot) {
|
|
71
|
+
if (!snapshot?.job_id) {
|
|
72
|
+
throw new Error('job_id is required');
|
|
73
|
+
}
|
|
74
|
+
assertKnown(DELEGATOR_JOB_STATUSES, snapshot.status, 'status');
|
|
75
|
+
this.jobs.set(snapshot.job_id, deepClone(snapshot));
|
|
76
|
+
return this.getStatusSnapshot(snapshot.job_id);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getStatusSnapshot(jobId) {
|
|
80
|
+
const snapshot = this.jobs.get(jobId);
|
|
81
|
+
return snapshot ? deepClone(snapshot) : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// -- 필드 정규화 헬퍼 --
|
|
85
|
+
|
|
86
|
+
_normalizeInput(input = {}) {
|
|
87
|
+
return {
|
|
88
|
+
prompt: input.prompt,
|
|
89
|
+
provider: input.provider || 'auto',
|
|
90
|
+
mode: input.mode || 'sync',
|
|
91
|
+
agent_type: input.agent_type || input.agentType || 'executor',
|
|
92
|
+
cwd: input.cwd || null,
|
|
93
|
+
timeout_ms: input.timeout_ms || input.timeoutMs || null,
|
|
94
|
+
session_key: input.session_key || input.sessionKey || null,
|
|
95
|
+
thread_id: input.thread_id || input.threadId || null,
|
|
96
|
+
reset_session: input.reset_session ?? input.resetSession ?? false,
|
|
97
|
+
mcp_profile: input.mcp_profile || input.mcpProfile || 'auto',
|
|
98
|
+
search_tool: input.search_tool || input.searchTool || null,
|
|
99
|
+
context_file: input.context_file || input.contextFile || null,
|
|
100
|
+
model: input.model || null,
|
|
101
|
+
developer_instructions: input.developer_instructions || input.developerInstructions || null,
|
|
102
|
+
compact_prompt: input.compact_prompt || input.compactPrompt || null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_toWorkerArgs(normalized) {
|
|
107
|
+
return {
|
|
108
|
+
prompt: normalized.prompt,
|
|
109
|
+
provider: normalized.provider,
|
|
110
|
+
mode: normalized.mode,
|
|
111
|
+
agentType: normalized.agent_type,
|
|
112
|
+
cwd: normalized.cwd,
|
|
113
|
+
timeoutMs: normalized.timeout_ms,
|
|
114
|
+
sessionKey: normalized.session_key,
|
|
115
|
+
threadId: normalized.thread_id,
|
|
116
|
+
resetSession: normalized.reset_session,
|
|
117
|
+
mcpProfile: normalized.mcp_profile,
|
|
118
|
+
searchTool: normalized.search_tool,
|
|
119
|
+
contextFile: normalized.context_file,
|
|
120
|
+
model: normalized.model,
|
|
121
|
+
developerInstructions: normalized.developer_instructions,
|
|
122
|
+
compactPrompt: normalized.compact_prompt,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_applyWorkerResult(jobId, workerResult) {
|
|
127
|
+
const snapshot = this.jobs.get(jobId);
|
|
128
|
+
if (!snapshot) return null;
|
|
129
|
+
|
|
130
|
+
const timestamp = this.now().toISOString();
|
|
131
|
+
const ok = workerResult.ok !== false;
|
|
132
|
+
|
|
133
|
+
snapshot.ok = ok;
|
|
134
|
+
snapshot.status = workerResult.status || (ok ? 'completed' : 'failed');
|
|
135
|
+
snapshot.provider_resolved = workerResult.providerResolved || workerResult.provider_resolved || null;
|
|
136
|
+
snapshot.transport = workerResult.transport || snapshot.transport;
|
|
137
|
+
snapshot.output = workerResult.output || '';
|
|
138
|
+
snapshot.stderr = workerResult.stderr || '';
|
|
139
|
+
snapshot.error = workerResult.error || '';
|
|
140
|
+
snapshot.thread_id = workerResult.threadId || workerResult.thread_id || null;
|
|
141
|
+
snapshot.session_key = workerResult.sessionKey || workerResult.session_key || null;
|
|
142
|
+
snapshot.conversation_open = workerResult.conversationOpen ?? workerResult.conversation_open ?? false;
|
|
143
|
+
snapshot.started_at = snapshot.started_at || timestamp;
|
|
144
|
+
snapshot.updated_at = timestamp;
|
|
145
|
+
if (snapshot.status === 'completed' || snapshot.status === 'failed') {
|
|
146
|
+
snapshot.completed_at = timestamp;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.jobs.set(jobId, deepClone(snapshot));
|
|
150
|
+
return deepClone(snapshot);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
_failJob(jobId, error) {
|
|
154
|
+
const snapshot = this.jobs.get(jobId);
|
|
155
|
+
if (snapshot) {
|
|
156
|
+
const timestamp = this.now().toISOString();
|
|
157
|
+
snapshot.ok = false;
|
|
158
|
+
snapshot.status = 'failed';
|
|
159
|
+
snapshot.error = error;
|
|
160
|
+
snapshot.updated_at = timestamp;
|
|
161
|
+
snapshot.completed_at = timestamp;
|
|
162
|
+
this.jobs.set(jobId, deepClone(snapshot));
|
|
163
|
+
return deepClone(snapshot);
|
|
164
|
+
}
|
|
165
|
+
return this._errorSnapshot(jobId, error);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_errorSnapshot(jobId, error) {
|
|
169
|
+
const timestamp = this.now().toISOString();
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
job_id: jobId || 'unknown',
|
|
173
|
+
status: 'failed',
|
|
174
|
+
mode: 'sync',
|
|
175
|
+
provider_requested: 'auto',
|
|
176
|
+
provider_resolved: null,
|
|
177
|
+
agent_type: 'executor',
|
|
178
|
+
transport: 'resident-pending',
|
|
179
|
+
created_at: timestamp,
|
|
180
|
+
started_at: null,
|
|
181
|
+
updated_at: timestamp,
|
|
182
|
+
completed_at: timestamp,
|
|
183
|
+
output: '',
|
|
184
|
+
stderr: '',
|
|
185
|
+
error,
|
|
186
|
+
thread_id: null,
|
|
187
|
+
session_key: null,
|
|
188
|
+
conversation_open: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// -- 위임/응답/상태 메서드 --
|
|
193
|
+
|
|
194
|
+
async delegate(input = {}) {
|
|
195
|
+
const normalized = this._normalizeInput(input);
|
|
196
|
+
|
|
197
|
+
if (!normalized.prompt || typeof normalized.prompt !== 'string' || !normalized.prompt.trim()) {
|
|
198
|
+
return this._errorSnapshot(null, 'prompt is required');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const snapshot = this.createJobSnapshot(normalized);
|
|
202
|
+
this.recordJob(snapshot);
|
|
203
|
+
|
|
204
|
+
if (!this.worker) {
|
|
205
|
+
return this._failJob(snapshot.job_id, 'worker가 설정되지 않았습니다');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const workerArgs = this._toWorkerArgs(normalized);
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const workerResult = await this.worker.delegate(workerArgs, null);
|
|
212
|
+
|
|
213
|
+
// worker job ID 매핑 (reply/status에서 사용)
|
|
214
|
+
const workerJobId = workerResult.jobId || workerResult.job_id;
|
|
215
|
+
if (workerJobId) {
|
|
216
|
+
this._workerJobMap.set(snapshot.job_id, workerJobId);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return this._applyWorkerResult(snapshot.job_id, workerResult);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return this._failJob(snapshot.job_id, err instanceof Error ? err.message : String(err));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async reply(input = {}) {
|
|
226
|
+
const jobId = input.job_id || input.jobId;
|
|
227
|
+
if (!jobId) {
|
|
228
|
+
return this._errorSnapshot('unknown', 'job_id is required');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const snapshot = this.jobs.get(jobId);
|
|
232
|
+
if (!snapshot) {
|
|
233
|
+
return this._errorSnapshot(jobId, 'job not found');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!snapshot.conversation_open) {
|
|
237
|
+
return this._failJob(jobId, 'conversation is not open');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!this.worker) {
|
|
241
|
+
return this._failJob(jobId, 'worker가 설정되지 않았습니다');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const workerJobId = this._workerJobMap.get(jobId);
|
|
245
|
+
if (!workerJobId) {
|
|
246
|
+
return this._failJob(jobId, 'worker job 매핑을 찾을 수 없습니다');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const workerResult = await this.worker.reply({
|
|
251
|
+
job_id: workerJobId,
|
|
252
|
+
reply: input.reply,
|
|
253
|
+
done: input.done ?? false,
|
|
254
|
+
}, null);
|
|
255
|
+
|
|
256
|
+
return this._applyWorkerResult(jobId, workerResult);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
return this._failJob(jobId, err instanceof Error ? err.message : String(err));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async status({ job_id: jobId, jobId: jobIdAlias } = {}) {
|
|
263
|
+
const resolvedId = jobId || jobIdAlias;
|
|
264
|
+
const snapshot = this.getStatusSnapshot(resolvedId);
|
|
265
|
+
|
|
266
|
+
if (!snapshot) {
|
|
267
|
+
const timestamp = this.now().toISOString();
|
|
268
|
+
return {
|
|
269
|
+
ok: false,
|
|
270
|
+
job_id: resolvedId || 'unknown-job',
|
|
271
|
+
status: 'failed',
|
|
272
|
+
mode: 'async',
|
|
273
|
+
provider_requested: 'auto',
|
|
274
|
+
provider_resolved: null,
|
|
275
|
+
agent_type: 'executor',
|
|
276
|
+
transport: 'resident-pending',
|
|
277
|
+
created_at: timestamp,
|
|
278
|
+
started_at: null,
|
|
279
|
+
updated_at: timestamp,
|
|
280
|
+
completed_at: null,
|
|
281
|
+
output: '',
|
|
282
|
+
stderr: '',
|
|
283
|
+
error: 'job not found',
|
|
284
|
+
thread_id: null,
|
|
285
|
+
session_key: null,
|
|
286
|
+
conversation_open: false,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// running/queued 상태이면 worker에서 최신 상태 갱신
|
|
291
|
+
if (this.worker && (snapshot.status === 'running' || snapshot.status === 'queued')) {
|
|
292
|
+
const workerJobId = this._workerJobMap.get(resolvedId);
|
|
293
|
+
if (workerJobId) {
|
|
294
|
+
try {
|
|
295
|
+
const workerResult = await this.worker.getJobStatus(workerJobId, null);
|
|
296
|
+
if (workerResult && workerResult.ok !== undefined) {
|
|
297
|
+
return this._applyWorkerResult(resolvedId, workerResult);
|
|
298
|
+
}
|
|
299
|
+
} catch {
|
|
300
|
+
// worker 상태 확인 실패 시 캐시된 snapshot 반환
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return snapshot;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +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
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// hub/fullcycle.mjs — tfx-fullcycle runtime artifact/state helpers
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { ensureTfxDirs, TFX_FULLCYCLE_DIR, TFX_PLANS_DIR } from './paths.mjs';
|
|
6
|
+
|
|
7
|
+
function safeResolve(baseDir, relativePath) {
|
|
8
|
+
const base = resolve(baseDir);
|
|
9
|
+
const target = resolve(join(baseDir, relativePath));
|
|
10
|
+
if (!target.startsWith(base)) {
|
|
11
|
+
throw new Error('Invalid fullcycle path: path traversal detected');
|
|
12
|
+
}
|
|
13
|
+
return target;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createFullcycleRunId(now = new Date()) {
|
|
17
|
+
return now
|
|
18
|
+
.toISOString()
|
|
19
|
+
.replace(/[:.]/g, '-')
|
|
20
|
+
.replace('T', '_')
|
|
21
|
+
.replace('Z', 'Z');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getFullcycleRunDir(runId, baseDir = process.cwd()) {
|
|
25
|
+
return safeResolve(baseDir, join(TFX_FULLCYCLE_DIR, runId));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ensureFullcycleRunDir(runId, baseDir = process.cwd()) {
|
|
29
|
+
ensureTfxDirs(baseDir);
|
|
30
|
+
const dir = getFullcycleRunDir(runId, baseDir);
|
|
31
|
+
mkdirSync(dir, { recursive: true });
|
|
32
|
+
return dir;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function saveFullcycleArtifact(runId, filename, content, baseDir = process.cwd()) {
|
|
36
|
+
if (!filename || typeof filename !== 'string') {
|
|
37
|
+
throw new Error('Artifact filename is required');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const dir = ensureFullcycleRunDir(runId, baseDir);
|
|
41
|
+
const path = safeResolve(dir, filename);
|
|
42
|
+
writeFileSync(path, content, 'utf8');
|
|
43
|
+
return path;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function readFullcycleArtifact(runId, filename, baseDir = process.cwd()) {
|
|
47
|
+
const dir = getFullcycleRunDir(runId, baseDir);
|
|
48
|
+
const path = safeResolve(dir, filename);
|
|
49
|
+
if (!existsSync(path)) return null;
|
|
50
|
+
return readFileSync(path, 'utf8');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function writeFullcycleState(runId, state, baseDir = process.cwd()) {
|
|
54
|
+
const payload = typeof state === 'object' && state !== null ? state : {};
|
|
55
|
+
const serialized = JSON.stringify(payload, null, 2);
|
|
56
|
+
return saveFullcycleArtifact(runId, 'state.json', serialized, baseDir);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function readFullcycleState(runId, baseDir = process.cwd()) {
|
|
60
|
+
const content = readFullcycleArtifact(runId, 'state.json', baseDir);
|
|
61
|
+
if (!content) return null;
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(content);
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function findLatestInterviewPlan(baseDir = process.cwd()) {
|
|
70
|
+
const plansDir = safeResolve(baseDir, TFX_PLANS_DIR);
|
|
71
|
+
if (!existsSync(plansDir)) return null;
|
|
72
|
+
|
|
73
|
+
const candidates = readdirSync(plansDir)
|
|
74
|
+
.filter((name) => /^interview-.*\.md$/i.test(name))
|
|
75
|
+
.map((name) => {
|
|
76
|
+
const path = join(plansDir, name);
|
|
77
|
+
const stats = statSync(path);
|
|
78
|
+
return { name, path, mtimeMs: stats.mtimeMs };
|
|
79
|
+
})
|
|
80
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
81
|
+
|
|
82
|
+
return candidates[0]?.path || null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function shouldStopQaLoop(failureHistory = [], maxRepeats = 3) {
|
|
86
|
+
if (!Array.isArray(failureHistory) || maxRepeats <= 1) return false;
|
|
87
|
+
|
|
88
|
+
const normalized = failureHistory
|
|
89
|
+
.map((entry) => String(entry ?? '').trim())
|
|
90
|
+
.filter(Boolean);
|
|
91
|
+
|
|
92
|
+
if (normalized.length < maxRepeats) return false;
|
|
93
|
+
const target = normalized.at(-1);
|
|
94
|
+
const tail = normalized.slice(-maxRepeats);
|
|
95
|
+
return tail.every((entry) => entry === target);
|
|
96
|
+
}
|