@triflux/remote 10.87.0 → 10.88.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.
- package/hub/team/cli/commands/debug.mjs +11 -4
- package/hub/team/handoff.mjs +100 -0
- package/hub/team/headless.mjs +49 -10
- package/hub/team/lead-launch.mjs +186 -0
- package/hub/team/native-peer.mjs +97 -3
- package/hub/team/orchestrator.mjs +80 -0
- package/hub/team/pane.mjs +99 -0
- package/hub/team/results-index.mjs +35 -0
- package/hub/team/retry-state-machine.mjs +23 -3
- package/hub/team/uds-orchestrator.mjs +30 -8
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +1 -1
- package/scripts/lib/codex-profile-config.mjs +1 -1
- package/scripts/lib/doctor-env-checks.mjs +377 -0
- package/scripts/lib/gemini-profiles.mjs +1 -1
- package/scripts/lib/mcp-filter.mjs +86 -3
- package/scripts/lib/model-effort.mjs +134 -0
package/hub/team/pane.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, unlinkSync, writeFileSync } from "node:fs";
|
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { judgeComposerState } from "@triflux/core/hub/lib/jev-addon.mjs";
|
|
7
8
|
import { privateTmpDir } from "@triflux/core/hub/lib/private-tmp.mjs";
|
|
8
9
|
import { psmuxExec } from "./psmux.mjs";
|
|
9
10
|
import { detectMultiplexer, tmuxExec } from "./session.mjs";
|
|
@@ -251,6 +252,104 @@ export function submitPromptWithVerification({
|
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
|
|
255
|
+
/**
|
|
256
|
+
* 결정론적 컴포저 상태 판정이 모호할 때 Jev로 컴포저 상태를 판별해 Enter 재전송 여부를 결정한다.
|
|
257
|
+
*/
|
|
258
|
+
export async function submitPromptWithJevVerification({
|
|
259
|
+
cli = "claude",
|
|
260
|
+
prompt = "",
|
|
261
|
+
capture,
|
|
262
|
+
sendEnter,
|
|
263
|
+
sleep = sleepMs,
|
|
264
|
+
settleMs = PASTE_SETTLE_MS,
|
|
265
|
+
retryLimit = SUBMIT_RETRY_LIMIT,
|
|
266
|
+
jevOptions = {},
|
|
267
|
+
} = {}) {
|
|
268
|
+
const strictVerification = isAntigravityCli(cli);
|
|
269
|
+
const promptPrefix = promptComposerNeedle(prompt);
|
|
270
|
+
const isConfirmedSubmitted = (text) => {
|
|
271
|
+
if (!promptPrefix) return true;
|
|
272
|
+
if (!text) return !strictVerification;
|
|
273
|
+
if (hasBusyMarkerOutsideComposer(text)) return true;
|
|
274
|
+
const state = composerPromptState(text, promptPrefix);
|
|
275
|
+
if (state === "absent") return true;
|
|
276
|
+
if (state === "unknown" && !strictVerification) return true;
|
|
277
|
+
return false;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
submitPromptWithVerification({
|
|
282
|
+
cli,
|
|
283
|
+
prompt,
|
|
284
|
+
capture,
|
|
285
|
+
sendEnter,
|
|
286
|
+
sleep,
|
|
287
|
+
settleMs,
|
|
288
|
+
retryLimit,
|
|
289
|
+
});
|
|
290
|
+
} catch (err) {
|
|
291
|
+
const capturedText =
|
|
292
|
+
typeof capture === "function" ? String(capture() ?? "") : "";
|
|
293
|
+
const verdict = await judgeComposerState(
|
|
294
|
+
{ capturedText, promptPrefix },
|
|
295
|
+
jevOptions,
|
|
296
|
+
);
|
|
297
|
+
if (verdict?.state === "submitted_running") {
|
|
298
|
+
return { submitted: true, jevState: verdict.state };
|
|
299
|
+
}
|
|
300
|
+
if (
|
|
301
|
+
verdict?.state === "stuck_in_composer" &&
|
|
302
|
+
typeof sendEnter === "function"
|
|
303
|
+
) {
|
|
304
|
+
sendEnter();
|
|
305
|
+
sleep(400);
|
|
306
|
+
const afterText =
|
|
307
|
+
typeof capture === "function" ? String(capture() ?? "") : "";
|
|
308
|
+
if (isConfirmedSubmitted(afterText)) {
|
|
309
|
+
return { submitted: true, jevState: "submitted_running" };
|
|
310
|
+
}
|
|
311
|
+
const postVerdict = await judgeComposerState(
|
|
312
|
+
{ capturedText: afterText, promptPrefix },
|
|
313
|
+
jevOptions,
|
|
314
|
+
);
|
|
315
|
+
if (postVerdict?.state === "submitted_running") {
|
|
316
|
+
return { submitted: true, jevState: postVerdict.state };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
const capturedText =
|
|
322
|
+
typeof capture === "function" ? String(capture() ?? "") : "";
|
|
323
|
+
const verdict = await judgeComposerState(
|
|
324
|
+
{ capturedText, promptPrefix },
|
|
325
|
+
jevOptions,
|
|
326
|
+
);
|
|
327
|
+
if (
|
|
328
|
+
verdict?.state === "stuck_in_composer" &&
|
|
329
|
+
typeof sendEnter === "function"
|
|
330
|
+
) {
|
|
331
|
+
sendEnter();
|
|
332
|
+
sleep(400);
|
|
333
|
+
const afterText =
|
|
334
|
+
typeof capture === "function" ? String(capture() ?? "") : "";
|
|
335
|
+
if (isConfirmedSubmitted(afterText)) {
|
|
336
|
+
return { submitted: true, jevState: "submitted_running" };
|
|
337
|
+
}
|
|
338
|
+
const postVerdict = await judgeComposerState(
|
|
339
|
+
{ capturedText: afterText, promptPrefix },
|
|
340
|
+
jevOptions,
|
|
341
|
+
);
|
|
342
|
+
return {
|
|
343
|
+
submitted: postVerdict?.state === "submitted_running",
|
|
344
|
+
jevState: postVerdict?.state || verdict.state,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
submitted: verdict?.state !== "modal_blocking",
|
|
349
|
+
jevState: verdict?.state || null,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
254
353
|
function confirmSubmit(target, prompt, cli, sendEnter) {
|
|
255
354
|
submitPromptWithVerification({
|
|
256
355
|
cli,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// headless 세션 결과 색인. 화면의 HEADLESS_COMPLETE 문자열 대신 읽도록 세션당 JSON 하나를 둔다 (ADR-0034).
|
|
2
2
|
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { judgeInstructionAck } from "@triflux/core/hub/lib/jev-addon.mjs";
|
|
4
5
|
|
|
5
6
|
export const RESULTS_INDEX_VERSION = 1;
|
|
6
7
|
|
|
@@ -34,6 +35,9 @@ export function createResultsIndex(path, sessionName, workers, canWrite) {
|
|
|
34
35
|
startedAt: null,
|
|
35
36
|
finishedAt: null,
|
|
36
37
|
resultFile: w.resultFile,
|
|
38
|
+
ackStatus: null,
|
|
39
|
+
semanticState: null,
|
|
40
|
+
ackConfidence: null,
|
|
37
41
|
})),
|
|
38
42
|
};
|
|
39
43
|
const byPane = new Map(index.workers.map((w) => [w.paneName, w]));
|
|
@@ -88,5 +92,36 @@ export function createResultsIndex(path, sessionName, workers, canWrite) {
|
|
|
88
92
|
index.completed = Boolean(completed);
|
|
89
93
|
flush();
|
|
90
94
|
},
|
|
95
|
+
recordInstructionAck(
|
|
96
|
+
paneName,
|
|
97
|
+
{ ackStatus = null, semanticState = null, confidence = null } = {},
|
|
98
|
+
) {
|
|
99
|
+
const w = byPane.get(paneName);
|
|
100
|
+
if (!w) return null;
|
|
101
|
+
w.ackStatus = ackStatus;
|
|
102
|
+
w.semanticState = semanticState;
|
|
103
|
+
w.ackConfidence = confidence;
|
|
104
|
+
flush();
|
|
105
|
+
return w;
|
|
106
|
+
},
|
|
107
|
+
async verifyInstructionAckWithJev(
|
|
108
|
+
paneName,
|
|
109
|
+
{ instruction = "", recentOutput = "" } = {},
|
|
110
|
+
jevOptions = {},
|
|
111
|
+
) {
|
|
112
|
+
const verdict = await judgeInstructionAck(
|
|
113
|
+
{ instruction, recentOutput },
|
|
114
|
+
jevOptions,
|
|
115
|
+
);
|
|
116
|
+
if (!verdict) return null;
|
|
117
|
+
this.recordInstructionAck(paneName, {
|
|
118
|
+
ackStatus: verdict.ackStatus,
|
|
119
|
+
semanticState: verdict.acknowledged
|
|
120
|
+
? "following_instruction"
|
|
121
|
+
: "unverified_instruction",
|
|
122
|
+
confidence: verdict.confidence,
|
|
123
|
+
});
|
|
124
|
+
return verdict;
|
|
125
|
+
},
|
|
91
126
|
};
|
|
92
127
|
}
|
|
@@ -29,8 +29,9 @@ import {
|
|
|
29
29
|
modelEffortFromProfile,
|
|
30
30
|
profileFromModelEffort,
|
|
31
31
|
resolveCodexModelEffort,
|
|
32
|
-
} from "
|
|
32
|
+
} from "../../scripts/lib/model-effort.mjs";
|
|
33
33
|
import { AGY_FAMILY_PROFILES } from "../../scripts/lib/model-family-resolver.mjs";
|
|
34
|
+
import { judgeSameFailureCause } from "@triflux/core/hub/lib/jev-addon.mjs";
|
|
34
35
|
|
|
35
36
|
export const STATES = Object.freeze({
|
|
36
37
|
PLANNING: "PLANNING",
|
|
@@ -126,10 +127,14 @@ export function createRetryStateMachine(options = {}) {
|
|
|
126
127
|
return transition(STATES.DONE);
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
function reportVerifyFail(failureReason) {
|
|
130
|
+
function reportVerifyFail(failureReason, failOptions = {}) {
|
|
130
131
|
const reason = String(failureReason || "unknown");
|
|
131
|
-
|
|
132
|
+
const isSameCause =
|
|
133
|
+
reason === state.lastFailureReason ||
|
|
134
|
+
(Boolean(state.lastFailureReason) && failOptions.sameCause === true);
|
|
135
|
+
if (isSameCause) {
|
|
132
136
|
state.stuckCounter += 1;
|
|
137
|
+
state.lastFailureReason = reason;
|
|
133
138
|
} else {
|
|
134
139
|
state.stuckCounter = 1;
|
|
135
140
|
state.lastFailureReason = reason;
|
|
@@ -155,6 +160,20 @@ export function createRetryStateMachine(options = {}) {
|
|
|
155
160
|
return transition(STATES.DIAGNOSING, { reason });
|
|
156
161
|
}
|
|
157
162
|
|
|
163
|
+
async function reportVerifyFailAsync(failureReason, jevOptions = {}) {
|
|
164
|
+
const reason = String(failureReason || "unknown");
|
|
165
|
+
let sameCause = false;
|
|
166
|
+
if (state.lastFailureReason && reason !== state.lastFailureReason) {
|
|
167
|
+
const verdict = await judgeSameFailureCause(
|
|
168
|
+
state.lastFailureReason,
|
|
169
|
+
reason,
|
|
170
|
+
jevOptions,
|
|
171
|
+
);
|
|
172
|
+
sameCause = verdict?.sameCause === true;
|
|
173
|
+
}
|
|
174
|
+
return reportVerifyFail(reason, { sameCause });
|
|
175
|
+
}
|
|
176
|
+
|
|
158
177
|
function escalate() {
|
|
159
178
|
if (state.cliIndex + 1 >= state.cliChain.length) {
|
|
160
179
|
return transition(STATES.BUDGET_EXCEEDED, {
|
|
@@ -271,6 +290,7 @@ export function createRetryStateMachine(options = {}) {
|
|
|
271
290
|
startIteration,
|
|
272
291
|
reportVerifySuccess,
|
|
273
292
|
reportVerifyFail,
|
|
293
|
+
reportVerifyFailAsync,
|
|
274
294
|
escalate,
|
|
275
295
|
on,
|
|
276
296
|
serialize,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
2
|
import { resolve as pathResolve } from "node:path";
|
|
3
|
+
import { judgeBusyAction } from "@triflux/core/hub/lib/jev-addon.mjs";
|
|
3
4
|
import {
|
|
4
5
|
createActivityLifecycle,
|
|
5
6
|
isActivityLifecycleEnabled,
|
|
@@ -299,12 +300,13 @@ export async function askCodexAppServerThread({
|
|
|
299
300
|
noWait = false,
|
|
300
301
|
warnContextPct,
|
|
301
302
|
maxContextPct,
|
|
303
|
+
jevOptions,
|
|
302
304
|
clientFactory = (opts) => new JsonRpcWsUdsClient(opts),
|
|
303
305
|
} = {}) {
|
|
304
306
|
if (typeof prompt !== "string" || !prompt.trim())
|
|
305
307
|
throw new Error("codex UDS ask requires a non-empty prompt");
|
|
306
308
|
if (!threadId) throw new Error("codex UDS ask requires --thread <id|auto>");
|
|
307
|
-
if (!["wait", "fail", "steer"].includes(ifBusy))
|
|
309
|
+
if (!["wait", "fail", "steer", "auto"].includes(ifBusy))
|
|
308
310
|
throw new Error(`invalid --if-busy: ${ifBusy}`);
|
|
309
311
|
const client = codexClient(
|
|
310
312
|
socketPath,
|
|
@@ -327,11 +329,13 @@ export async function askCodexAppServerThread({
|
|
|
327
329
|
throw new Error(
|
|
328
330
|
"no loaded Codex threads; open the TUI and send its first message before using --thread auto",
|
|
329
331
|
);
|
|
330
|
-
if (candidates.length
|
|
332
|
+
if (candidates.length === 1) {
|
|
333
|
+
selectedId = candidates[0].threadId;
|
|
334
|
+
} else {
|
|
331
335
|
throw new Error(
|
|
332
336
|
`multiple loaded Codex threads; pass --thread ID: ${JSON.stringify(candidates)}`,
|
|
333
337
|
);
|
|
334
|
-
|
|
338
|
+
}
|
|
335
339
|
}
|
|
336
340
|
const busyStarted = Date.now();
|
|
337
341
|
let thread = await readCodexThread(
|
|
@@ -340,14 +344,30 @@ export async function askCodexAppServerThread({
|
|
|
340
344
|
DEFAULT_CODEX_APP_SERVER_UDS_BOOTSTRAP_MS,
|
|
341
345
|
);
|
|
342
346
|
const initiallyBusy = thread.status?.type === "active";
|
|
343
|
-
|
|
347
|
+
let effectiveIfBusy = ifBusy;
|
|
348
|
+
if (initiallyBusy && ifBusy === "auto") {
|
|
349
|
+
const latestTurnSummary =
|
|
350
|
+
thread.turns
|
|
351
|
+
?.slice(-2)
|
|
352
|
+
.map((t) => JSON.stringify(t))
|
|
353
|
+
.join("\n")
|
|
354
|
+
.slice(0, 800) || "";
|
|
355
|
+
const verdict = await judgeBusyAction(
|
|
356
|
+
{ prompt, threadSummary: latestTurnSummary },
|
|
357
|
+
jevOptions,
|
|
358
|
+
);
|
|
359
|
+
effectiveIfBusy = verdict?.action ?? "wait";
|
|
360
|
+
} else if (ifBusy === "auto") {
|
|
361
|
+
effectiveIfBusy = "wait";
|
|
362
|
+
}
|
|
363
|
+
if (thread.status?.type === "active" && effectiveIfBusy === "fail")
|
|
344
364
|
throw new Error(`target busy (--if-busy fail): ${selectedId}`);
|
|
345
365
|
const busyDeadline = busyStarted + busyTimeoutMs;
|
|
346
366
|
const busyError = () =>
|
|
347
367
|
new Error(
|
|
348
368
|
`target busy after ${Math.ceil(busyTimeoutMs / 1000)}s (--if-busy wait)`,
|
|
349
369
|
);
|
|
350
|
-
while (thread.status?.type === "active" &&
|
|
370
|
+
while (thread.status?.type === "active" && effectiveIfBusy === "wait") {
|
|
351
371
|
const waitMs = Math.min(
|
|
352
372
|
pollIntervalMs,
|
|
353
373
|
Math.max(0, busyDeadline - Date.now()),
|
|
@@ -373,7 +393,9 @@ export async function askCodexAppServerThread({
|
|
|
373
393
|
throw busyError();
|
|
374
394
|
}
|
|
375
395
|
const busyWaitedMs =
|
|
376
|
-
|
|
396
|
+
effectiveIfBusy === "wait" && initiallyBusy
|
|
397
|
+
? Date.now() - busyStarted
|
|
398
|
+
: 0;
|
|
377
399
|
const resumed = await client.request(
|
|
378
400
|
"thread/resume",
|
|
379
401
|
{ threadId: selectedId, excludeTurns: true },
|
|
@@ -405,7 +427,7 @@ export async function askCodexAppServerThread({
|
|
|
405
427
|
else if (!resumed?.thread && thread.status?.type === "active")
|
|
406
428
|
activeTurns = thread.turns;
|
|
407
429
|
const expectedTurnId =
|
|
408
|
-
|
|
430
|
+
effectiveIfBusy === "steer"
|
|
409
431
|
? activeTurns?.findLast((turn) => turn.status === "inProgress")?.id
|
|
410
432
|
: undefined;
|
|
411
433
|
const turn = await runCodexAppServerTurn({
|
|
@@ -434,7 +456,7 @@ export async function askCodexAppServerThread({
|
|
|
434
456
|
status: turn.status,
|
|
435
457
|
commentary: turn.commentary,
|
|
436
458
|
meta: { retries: turn.retries },
|
|
437
|
-
ifBusy,
|
|
459
|
+
ifBusy: effectiveIfBusy,
|
|
438
460
|
busyWaitedMs,
|
|
439
461
|
steered: turn.steered,
|
|
440
462
|
socketPath,
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ import { resolveGeminiProfileForPurpose } from "./gemini-profiles.mjs";
|
|
|
12
12
|
import {
|
|
13
13
|
modelEffortFromProfile,
|
|
14
14
|
profileFromModelEffort,
|
|
15
|
-
} from "
|
|
15
|
+
} from "./model-effort.mjs";
|
|
16
16
|
|
|
17
17
|
const policy = {
|
|
18
18
|
executor: {
|