triflux 7.1.3 → 7.2.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 (73) hide show
  1. package/.claude-plugin/marketplace.json +31 -31
  2. package/.claude-plugin/plugin.json +22 -23
  3. package/bin/triflux.mjs +18 -5
  4. package/hooks/keyword-rules.json +393 -361
  5. package/hub/bridge.mjs +799 -786
  6. package/hub/delegator/contracts.mjs +37 -38
  7. package/hub/delegator/schema/delegator-tools.schema.json +250 -250
  8. package/hub/delegator/service.mjs +307 -302
  9. package/hub/intent.mjs +108 -11
  10. package/hub/lib/process-utils.mjs +20 -0
  11. package/hub/pipe.mjs +589 -589
  12. package/hub/pipeline/gates/confidence.mjs +1 -1
  13. package/hub/pipeline/gates/selfcheck.mjs +2 -4
  14. package/hub/pipeline/state.mjs +191 -187
  15. package/hub/pipeline/transitions.mjs +124 -120
  16. package/hub/public/dashboard.html +355 -349
  17. package/hub/quality/deslop.mjs +5 -3
  18. package/hub/reflexion.mjs +5 -1
  19. package/hub/research.mjs +6 -1
  20. package/hub/router.mjs +791 -782
  21. package/hub/server.mjs +893 -822
  22. package/hub/store.mjs +807 -778
  23. package/hub/team/agent-map.json +10 -0
  24. package/hub/team/ansi.mjs +3 -4
  25. package/hub/team/cli/commands/control.mjs +43 -43
  26. package/hub/team/cli/commands/interrupt.mjs +36 -36
  27. package/hub/team/cli/commands/kill.mjs +3 -3
  28. package/hub/team/cli/commands/send.mjs +37 -37
  29. package/hub/team/cli/commands/start/index.mjs +18 -8
  30. package/hub/team/cli/commands/start/parse-args.mjs +3 -1
  31. package/hub/team/cli/commands/start/start-headless.mjs +4 -1
  32. package/hub/team/cli/commands/status.mjs +87 -87
  33. package/hub/team/cli/commands/stop.mjs +1 -1
  34. package/hub/team/cli/commands/task.mjs +1 -1
  35. package/hub/team/cli/index.mjs +41 -39
  36. package/hub/team/cli/manifest.mjs +29 -28
  37. package/hub/team/cli/services/hub-client.mjs +37 -0
  38. package/hub/team/cli/services/state-store.mjs +26 -12
  39. package/hub/team/dashboard.mjs +11 -4
  40. package/hub/team/handoff.mjs +12 -0
  41. package/hub/team/headless.mjs +202 -200
  42. package/hub/team/native-supervisor.mjs +386 -346
  43. package/hub/team/nativeProxy.mjs +680 -692
  44. package/hub/team/staleState.mjs +361 -369
  45. package/hub/team/tui-viewer.mjs +27 -3
  46. package/hub/team/tui.mjs +1 -0
  47. package/hub/token-mode.mjs +114 -24
  48. package/hub/workers/delegator-mcp.mjs +1059 -1057
  49. package/hud/colors.mjs +88 -0
  50. package/hud/constants.mjs +78 -0
  51. package/hud/hud-qos-status.mjs +206 -1872
  52. package/hud/providers/claude.mjs +309 -0
  53. package/hud/providers/codex.mjs +151 -0
  54. package/hud/providers/gemini.mjs +320 -0
  55. package/hud/renderers.mjs +424 -0
  56. package/hud/terminal.mjs +140 -0
  57. package/hud/utils.mjs +271 -0
  58. package/package.json +1 -2
  59. package/scripts/__tests__/keyword-detector.test.mjs +234 -234
  60. package/scripts/headless-guard-fast.sh +21 -0
  61. package/scripts/headless-guard.mjs +26 -6
  62. package/scripts/lib/keyword-rules.mjs +166 -168
  63. package/scripts/setup.mjs +720 -690
  64. package/scripts/tfx-route-post.mjs +424 -424
  65. package/scripts/tfx-route.sh +1663 -1650
  66. package/scripts/tmp-cleanup.mjs +74 -0
  67. package/skills/tfx-auto/SKILL.md +279 -278
  68. package/skills/tfx-auto-codex/SKILL.md +98 -77
  69. package/skills/tfx-codex/SKILL.md +65 -65
  70. package/skills/tfx-gemini/SKILL.md +83 -82
  71. package/skills/tfx-hub/SKILL.md +205 -136
  72. package/skills/tfx-multi/SKILL.md +11 -5
  73. package/.mcp.json +0 -8
@@ -1,302 +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
- const snapshot = this.createJobSnapshot(normalized);
197
- this.recordJob(snapshot);
198
-
199
- if (!this.worker) {
200
- return this._failJob(snapshot.job_id, 'worker가 설정되지 않았습니다');
201
- }
202
-
203
- const workerArgs = this._toWorkerArgs(normalized);
204
-
205
- try {
206
- const workerResult = await this.worker.delegate(workerArgs, null);
207
-
208
- // worker job ID 매핑 (reply/status에서 사용)
209
- const workerJobId = workerResult.jobId || workerResult.job_id;
210
- if (workerJobId) {
211
- this._workerJobMap.set(snapshot.job_id, workerJobId);
212
- }
213
-
214
- return this._applyWorkerResult(snapshot.job_id, workerResult);
215
- } catch (err) {
216
- return this._failJob(snapshot.job_id, err instanceof Error ? err.message : String(err));
217
- }
218
- }
219
-
220
- async reply(input = {}) {
221
- const jobId = input.job_id || input.jobId;
222
- if (!jobId) {
223
- return this._errorSnapshot('unknown', 'job_id is required');
224
- }
225
-
226
- const snapshot = this.jobs.get(jobId);
227
- if (!snapshot) {
228
- return this._errorSnapshot(jobId, 'job not found');
229
- }
230
-
231
- if (!snapshot.conversation_open) {
232
- return this._failJob(jobId, 'conversation is not open');
233
- }
234
-
235
- if (!this.worker) {
236
- return this._failJob(jobId, 'worker가 설정되지 않았습니다');
237
- }
238
-
239
- const workerJobId = this._workerJobMap.get(jobId);
240
- if (!workerJobId) {
241
- return this._failJob(jobId, 'worker job 매핑을 찾을 수 없습니다');
242
- }
243
-
244
- try {
245
- const workerResult = await this.worker.reply({
246
- job_id: workerJobId,
247
- reply: input.reply,
248
- done: input.done ?? false,
249
- }, null);
250
-
251
- return this._applyWorkerResult(jobId, workerResult);
252
- } catch (err) {
253
- return this._failJob(jobId, err instanceof Error ? err.message : String(err));
254
- }
255
- }
256
-
257
- async status({ job_id: jobId, jobId: jobIdAlias } = {}) {
258
- const resolvedId = jobId || jobIdAlias;
259
- const snapshot = this.getStatusSnapshot(resolvedId);
260
-
261
- if (!snapshot) {
262
- const timestamp = this.now().toISOString();
263
- return {
264
- ok: false,
265
- job_id: resolvedId || 'unknown-job',
266
- status: 'failed',
267
- mode: 'async',
268
- provider_requested: 'auto',
269
- provider_resolved: null,
270
- agent_type: 'executor',
271
- transport: 'resident-pending',
272
- created_at: timestamp,
273
- started_at: null,
274
- updated_at: timestamp,
275
- completed_at: null,
276
- output: '',
277
- stderr: '',
278
- error: 'job not found',
279
- thread_id: null,
280
- session_key: null,
281
- conversation_open: false,
282
- };
283
- }
284
-
285
- // running/queued 상태이면 worker에서 최신 상태 갱신
286
- if (this.worker && (snapshot.status === 'running' || snapshot.status === 'queued')) {
287
- const workerJobId = this._workerJobMap.get(resolvedId);
288
- if (workerJobId) {
289
- try {
290
- const workerResult = await this.worker.getJobStatus(workerJobId, null);
291
- if (workerResult && workerResult.ok !== undefined) {
292
- return this._applyWorkerResult(resolvedId, workerResult);
293
- }
294
- } catch {
295
- // worker 상태 확인 실패 시 캐시된 snapshot 반환
296
- }
297
- }
298
- }
299
-
300
- return snapshot;
301
- }
302
- }
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
+ }