triflux 10.39.0 → 10.40.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/bin/tfx-live.mjs +72 -0
- package/cto/status.mjs +11 -0
- package/hub/bridge.mjs +23 -1
- package/hub/cli-adapter-base.mjs +11 -1
- package/hub/codex-adapter.mjs +13 -5
- package/hub/pipe.mjs +39 -1
- package/hub/public/tray.html +195 -98
- package/hub/router.mjs +521 -4
- package/hub/server.mjs +39 -5
- package/hub/team/execution-mode.mjs +9 -1
- package/hub/tools.mjs +21 -1
- package/hub/tray-state.mjs +24 -2
- package/package.json +1 -1
- package/scripts/lib/codex-profile-config.mjs +83 -2
package/bin/tfx-live.mjs
CHANGED
|
@@ -734,6 +734,23 @@ function isClaudeTrustPrompt(text) {
|
|
|
734
734
|
return /Quick safety check|trust this folder/i.test(String(text));
|
|
735
735
|
}
|
|
736
736
|
|
|
737
|
+
function isClaudeExternalImportsPrompt(text) {
|
|
738
|
+
return /Allow external CLAUDE\.md file imports|allow external imports/i.test(
|
|
739
|
+
String(text),
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function isClaudeTourPrompt(text) {
|
|
744
|
+
// Claude's post-update welcome/tour screen ("Take the tour" / "Skip for now").
|
|
745
|
+
// Claude has no codex-style "Update available / Skip until next version" menu
|
|
746
|
+
// (its update is a non-blocking background auto-updater), so the recurring
|
|
747
|
+
// startup screen that needs a "skip" is this tour prompt. Require both labels
|
|
748
|
+
// so a stray "Skip for now" elsewhere cannot false-match.
|
|
749
|
+
return (
|
|
750
|
+
/Take the tour/i.test(String(text)) && /Skip for now/i.test(String(text))
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
737
754
|
function selectedLine(text) {
|
|
738
755
|
// Menu selector glyphs only (exclude ASCII '>' which appears in codex's
|
|
739
756
|
// "> You are in ..." trust-prompt header and would mis-target navigation).
|
|
@@ -843,6 +860,51 @@ async function dismissClaudeTrustPrompt(remote, session) {
|
|
|
843
860
|
return { dismissed: false, raw };
|
|
844
861
|
}
|
|
845
862
|
|
|
863
|
+
async function dismissClaudeExternalImportsPrompt(remote, session) {
|
|
864
|
+
let raw = await captureVisible(remote, session);
|
|
865
|
+
if (!isClaudeExternalImportsPrompt(raw)) {
|
|
866
|
+
return { dismissed: false, raw };
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
for (let iteration = 0; iteration < 4; iteration += 1) {
|
|
870
|
+
if (/Yes, allow external imports/.test(selectedLine(raw))) {
|
|
871
|
+
await runTmux(remote, ["send-keys", "-t", session, "Enter"]);
|
|
872
|
+
return { dismissed: true, raw };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
await runTmux(remote, ["send-keys", "-t", session, "Up"]);
|
|
876
|
+
await sleep(200);
|
|
877
|
+
raw = await captureVisible(remote, session);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
await runTmux(remote, ["send-keys", "-t", session, "Enter"]);
|
|
881
|
+
return { dismissed: true, raw };
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
async function dismissClaudeTourPrompt(remote, session) {
|
|
885
|
+
let raw = await captureVisible(remote, session);
|
|
886
|
+
if (!isClaudeTourPrompt(raw)) {
|
|
887
|
+
return { dismissed: false, raw };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
for (let iteration = 0; iteration < 4; iteration += 1) {
|
|
891
|
+
if (/Skip for now/.test(selectedLine(raw))) {
|
|
892
|
+
await runTmux(remote, ["send-keys", "-t", session, "Enter"]);
|
|
893
|
+
return { dismissed: true, raw };
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
await runTmux(remote, ["send-keys", "-t", session, "Down"]);
|
|
897
|
+
await sleep(200);
|
|
898
|
+
raw = await captureVisible(remote, session);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Fallback: Escape cancels the welcome/tour dialog (equivalent to "Skip for
|
|
902
|
+
// now"). Never blind-Enter here — the default selection may be "Take the
|
|
903
|
+
// tour", and Enter would launch the tour instead of skipping it.
|
|
904
|
+
await runTmux(remote, ["send-keys", "-t", session, "Escape"]);
|
|
905
|
+
return { dismissed: true, raw };
|
|
906
|
+
}
|
|
907
|
+
|
|
846
908
|
const ADAPTERS = {
|
|
847
909
|
codex: {
|
|
848
910
|
cli: "codex",
|
|
@@ -917,6 +979,16 @@ const ADAPTERS = {
|
|
|
917
979
|
isPresent: isClaudeTrustPrompt,
|
|
918
980
|
dismiss: dismissClaudeTrustPrompt,
|
|
919
981
|
},
|
|
982
|
+
{
|
|
983
|
+
name: "external-imports",
|
|
984
|
+
isPresent: isClaudeExternalImportsPrompt,
|
|
985
|
+
dismiss: dismissClaudeExternalImportsPrompt,
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
name: "tour",
|
|
989
|
+
isPresent: isClaudeTourPrompt,
|
|
990
|
+
dismiss: dismissClaudeTourPrompt,
|
|
991
|
+
},
|
|
920
992
|
],
|
|
921
993
|
},
|
|
922
994
|
};
|
package/cto/status.mjs
CHANGED
|
@@ -55,6 +55,17 @@ export function deriveRepoRootFromCwd(cwd) {
|
|
|
55
55
|
if (cwd.includes("\\") || /^[A-Za-z]:/u.test(cwd)) return cwd;
|
|
56
56
|
|
|
57
57
|
const normalized = cwd.replace(/\/+$/u, "");
|
|
58
|
+
const gitWorktreeMarker = "/.worktrees/";
|
|
59
|
+
const gitWorktreeIndex = normalized.indexOf(gitWorktreeMarker);
|
|
60
|
+
if (gitWorktreeIndex > 0) {
|
|
61
|
+
const suffix = normalized.slice(
|
|
62
|
+
gitWorktreeIndex + gitWorktreeMarker.length,
|
|
63
|
+
);
|
|
64
|
+
if (/^[^/]+(?:\/|$)/u.test(suffix)) {
|
|
65
|
+
return normalized.slice(0, gitWorktreeIndex);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
const claudeMarker = "/.claude/worktrees/";
|
|
59
70
|
const claudeIndex = normalized.indexOf(claudeMarker);
|
|
60
71
|
if (claudeIndex > 0) {
|
package/hub/bridge.mjs
CHANGED
|
@@ -119,6 +119,11 @@ const HUB_OPERATIONS = Object.freeze({
|
|
|
119
119
|
action: "publish",
|
|
120
120
|
httpPath: "/bridge/publish",
|
|
121
121
|
},
|
|
122
|
+
takeoverRole: {
|
|
123
|
+
transport: "command",
|
|
124
|
+
action: "takeover_role",
|
|
125
|
+
httpPath: "/bridge/takeover-role",
|
|
126
|
+
},
|
|
122
127
|
sendInput: {
|
|
123
128
|
transport: "command",
|
|
124
129
|
action: "send_input",
|
|
@@ -365,6 +370,7 @@ export function parseArgs(argv) {
|
|
|
365
370
|
args: argv,
|
|
366
371
|
options: {
|
|
367
372
|
agent: { type: "string" },
|
|
373
|
+
"agent-id": { type: "string" },
|
|
368
374
|
cli: { type: "string" },
|
|
369
375
|
timeout: { type: "string" },
|
|
370
376
|
topics: { type: "string" },
|
|
@@ -387,6 +393,7 @@ export function parseArgs(argv) {
|
|
|
387
393
|
claim: { type: "boolean" },
|
|
388
394
|
actor: { type: "string" },
|
|
389
395
|
command: { type: "string" },
|
|
396
|
+
role: { type: "string" },
|
|
390
397
|
"session-id": { type: "string" },
|
|
391
398
|
reason: { type: "string" },
|
|
392
399
|
type: { type: "string" },
|
|
@@ -703,6 +710,19 @@ async function cmdPublish(args) {
|
|
|
703
710
|
return emitJson(result || unavailableResult());
|
|
704
711
|
}
|
|
705
712
|
|
|
713
|
+
async function cmdTakeoverRole(args) {
|
|
714
|
+
const role = args.role || args[1] || "cto";
|
|
715
|
+
const agentId = args.agent || args["agent-id"] || args[2];
|
|
716
|
+
const outcome = await requestHub(HUB_OPERATIONS.takeoverRole, {
|
|
717
|
+
role,
|
|
718
|
+
agent_id: agentId,
|
|
719
|
+
reason: args.reason || "manual",
|
|
720
|
+
requested_by: args["requested-by"] || "bridge",
|
|
721
|
+
});
|
|
722
|
+
const result = outcome?.result;
|
|
723
|
+
return emitJson(result || unavailableResult());
|
|
724
|
+
}
|
|
725
|
+
|
|
706
726
|
async function cmdSendInput(args) {
|
|
707
727
|
const outcome = await requestHub(HUB_OPERATIONS.sendInput, {
|
|
708
728
|
session_id: args["session-id"],
|
|
@@ -1554,6 +1574,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1554
1574
|
return await cmdHandoff(args);
|
|
1555
1575
|
case "publish":
|
|
1556
1576
|
return await cmdPublish(args);
|
|
1577
|
+
case "takeover-role":
|
|
1578
|
+
return await cmdTakeoverRole(args);
|
|
1557
1579
|
case "send-input":
|
|
1558
1580
|
return await cmdSendInput(args);
|
|
1559
1581
|
case "context":
|
|
@@ -1610,7 +1632,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1610
1632
|
return await cmdRetryStatus(args);
|
|
1611
1633
|
default:
|
|
1612
1634
|
console.error(
|
|
1613
|
-
"사용법: bridge.mjs <register|result|control|handoff|publish|send-input|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status|hitl-request|hitl-submit|hitl-pending|daemon-probe|daemon-attach|daemon-interrupt|retry-run|retry-status> [--옵션]",
|
|
1635
|
+
"사용법: bridge.mjs <register|result|control|handoff|publish|takeover-role|send-input|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status|hitl-request|hitl-submit|hitl-pending|daemon-probe|daemon-attach|daemon-interrupt|retry-run|retry-status> [--옵션]",
|
|
1614
1636
|
);
|
|
1615
1637
|
process.exit(1);
|
|
1616
1638
|
}
|
package/hub/cli-adapter-base.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { execSync, spawn } from "node:child_process";
|
|
5
5
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
6
6
|
|
|
7
|
+
import { codexProfileConfigOverrides } from "../scripts/lib/codex-profile-config.mjs";
|
|
7
8
|
import { writePromptToTmpFile } from "./lib/prompt-tmp.mjs";
|
|
8
9
|
import { IS_WINDOWS, killProcess } from "./platform.mjs";
|
|
9
10
|
|
|
@@ -172,7 +173,12 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
|
172
173
|
} = opts;
|
|
173
174
|
|
|
174
175
|
const parts = ["codex"];
|
|
175
|
-
|
|
176
|
+
// Select the effort profile via `-c` config overrides instead of
|
|
177
|
+
// `--profile <name>`. codex 0.134+ rejects `--profile X` whenever config.toml
|
|
178
|
+
// still contains an inline [profiles.X] table (and codex re-injects such
|
|
179
|
+
// tables when it rewrites config.toml), so the `-c model=.. -c
|
|
180
|
+
// model_reasoning_effort=..` form is immune and mutates no config.
|
|
181
|
+
const profileOverrides = profile ? codexProfileConfigOverrides(profile) : [];
|
|
176
182
|
|
|
177
183
|
if (FEATURES.execSubcommand) {
|
|
178
184
|
parts.push("exec");
|
|
@@ -182,6 +188,8 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
|
182
188
|
parts.push("--output-last-message", resultFile);
|
|
183
189
|
}
|
|
184
190
|
if (FEATURES.colorNever) parts.push("--color", "never");
|
|
191
|
+
for (const override of profileOverrides)
|
|
192
|
+
parts.push("-c", shellQuote(override));
|
|
185
193
|
// NOTE: `codex exec`는 --cwd 플래그를 지원하지 않는다. Node spawn의 cwd
|
|
186
194
|
// 옵션으로 child process의 working directory를 제어한다 (conductor.mjs 참조).
|
|
187
195
|
// opts.cwd는 기록용으로만 받아두고 CLI command에는 반영하지 않는다.
|
|
@@ -194,6 +202,8 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
|
194
202
|
} else {
|
|
195
203
|
parts.push("--dangerously-bypass-approvals-and-sandbox");
|
|
196
204
|
if (skipGitRepoCheck) parts.push("--skip-git-repo-check");
|
|
205
|
+
for (const override of profileOverrides)
|
|
206
|
+
parts.push("-c", shellQuote(override));
|
|
197
207
|
}
|
|
198
208
|
|
|
199
209
|
const useStdin = resolveStdinPromptMode(stdinPrompt);
|
package/hub/codex-adapter.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { basename, join } from "node:path";
|
|
4
|
+
import { codexProfileConfigOverrides } from "../scripts/lib/codex-profile-config.mjs";
|
|
4
5
|
import {
|
|
5
6
|
buildExecCommand,
|
|
6
7
|
createResult,
|
|
@@ -72,14 +73,21 @@ function buildAttempts(opts, preflight) {
|
|
|
72
73
|
// ── Launch script ───────────────────────────────────────────────
|
|
73
74
|
|
|
74
75
|
function createLaunchScriptText(opts) {
|
|
75
|
-
const parts = [
|
|
76
|
-
|
|
77
|
-
parts.push(
|
|
76
|
+
const parts = [
|
|
77
|
+
"codex",
|
|
78
78
|
"exec",
|
|
79
79
|
"--dangerously-bypass-approvals-and-sandbox",
|
|
80
80
|
"--skip-git-repo-check",
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
];
|
|
82
|
+
// Select the effort profile via `-c` config overrides instead of
|
|
83
|
+
// `--profile <name>` (see buildExecCommand): codex 0.134+ rejects `--profile
|
|
84
|
+
// X` whenever config.toml still contains an inline [profiles.X] table.
|
|
85
|
+
if (opts.profile) {
|
|
86
|
+
for (const override of codexProfileConfigOverrides(opts.profile)) {
|
|
87
|
+
parts.push("-c", shellQuote(override));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
parts.push('$(cat "$PROMPT_FILE")');
|
|
83
91
|
return [
|
|
84
92
|
"#!/usr/bin/env bash",
|
|
85
93
|
"set -euo pipefail",
|
package/hub/pipe.mjs
CHANGED
|
@@ -29,6 +29,17 @@ function normalizeTopics(topics) {
|
|
|
29
29
|
return topics.map((topic) => String(topic || "").trim()).filter(Boolean);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function normalizeReplayTopics(topics) {
|
|
33
|
+
if (Array.isArray(topics)) return normalizeTopics(topics);
|
|
34
|
+
if (typeof topics === "string") {
|
|
35
|
+
return topics
|
|
36
|
+
.split(/[,\s]+/u)
|
|
37
|
+
.map((topic) => topic.trim())
|
|
38
|
+
.filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
function getTeamBridgeMethod(methodName) {
|
|
33
44
|
const method = getTeamBridge()?.[methodName];
|
|
34
45
|
return typeof method === "function" ? method : null;
|
|
@@ -266,6 +277,12 @@ export function createPipeServer({
|
|
|
266
277
|
return result;
|
|
267
278
|
}
|
|
268
279
|
|
|
280
|
+
case "takeover_role": {
|
|
281
|
+
const result = router.takeoverRole(payload);
|
|
282
|
+
if (client) touchClient(client);
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
|
|
269
286
|
case "handoff": {
|
|
270
287
|
const result = router.handleHandoff(payload);
|
|
271
288
|
if (client) touchClient(client);
|
|
@@ -454,13 +471,34 @@ export function createPipeServer({
|
|
|
454
471
|
return pending.slice(0, maxMessages);
|
|
455
472
|
}
|
|
456
473
|
|
|
474
|
+
const requestedTopics = normalizeReplayTopics(payload.topics);
|
|
475
|
+
const auditTopics = new Set(requestedTopics);
|
|
476
|
+
if (auditTopics.size === 0) {
|
|
477
|
+
const persistedTopics = store?.getAgent?.(agentId)?.topics || [];
|
|
478
|
+
for (const topic of persistedTopics) auditTopics.add(topic);
|
|
479
|
+
if (
|
|
480
|
+
typeof router.canReplayMessageForAgent === "function" &&
|
|
481
|
+
router.canReplayMessageForAgent(agentId, {
|
|
482
|
+
to_agent: "topic:cto",
|
|
483
|
+
topic: "cto",
|
|
484
|
+
})
|
|
485
|
+
) {
|
|
486
|
+
auditTopics.add("cto");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
457
490
|
const audit = store.getAuditMessagesForAgent(agentId, {
|
|
458
491
|
max_messages: maxMessages,
|
|
459
|
-
include_topics:
|
|
492
|
+
include_topics: Array.from(auditTopics),
|
|
460
493
|
});
|
|
461
494
|
const byId = new Map();
|
|
462
495
|
for (const message of [...pending, ...audit]) {
|
|
463
496
|
if (!message?.id || byId.has(message.id)) continue;
|
|
497
|
+
if (
|
|
498
|
+
typeof router.canReplayMessageForAgent === "function" &&
|
|
499
|
+
!router.canReplayMessageForAgent(agentId, message)
|
|
500
|
+
)
|
|
501
|
+
continue;
|
|
464
502
|
byId.set(message.id, message);
|
|
465
503
|
}
|
|
466
504
|
return Array.from(byId.values())
|