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 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
  }
@@ -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
- if (profile) parts.push("--profile", profile);
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);
@@ -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 = ["codex"];
76
- if (opts.profile) parts.push("--profile", shellQuote(opts.profile));
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
- '$(cat "$PROMPT_FILE")',
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: payload.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())