triflux 10.44.0 → 10.46.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.
Files changed (203) hide show
  1. package/README.md +1 -1
  2. package/adapters/codex/skills/tfx-harness/SKILL.md +45 -9
  3. package/bin/tfx-live.mjs +1298 -100
  4. package/bin/triflux.mjs +9 -6
  5. package/config/routing-policy.json +1 -1
  6. package/hooks/codex-session-hook.mjs +67 -1
  7. package/hooks/cto-north-star-brief.mjs +3 -16
  8. package/hooks/error-context.mjs +3 -6
  9. package/hooks/hook-manager.mjs +1 -1
  10. package/hooks/hook-registry.json +0 -11
  11. package/hooks/keyword-rules.json +8 -4
  12. package/hooks/permission-safe-allow.mjs +36 -2
  13. package/hooks/session-start-fast.mjs +50 -1
  14. package/hooks/session-start-lake.mjs +3 -16
  15. package/hub/bridge.mjs +111 -5
  16. package/hub/dynamic-routing-engine.mjs +1 -1
  17. package/hub/gemini-adapter.mjs +29 -11
  18. package/hub/lib/memory-store.mjs +66 -30
  19. package/hub/lib/tfx-route-args.mjs +41 -4
  20. package/hub/pipe.mjs +10 -15
  21. package/hub/reflexion.mjs +0 -57
  22. package/hub/router.mjs +202 -24
  23. package/hub/schema.sql +1 -0
  24. package/hub/server.mjs +172 -93
  25. package/hub/store.mjs +106 -33
  26. package/hub/team/backend.mjs +4 -2
  27. package/hub/team/claude-daemon-control.mjs +2 -0
  28. package/hub/team/cli/commands/start/start-headless.mjs +2 -0
  29. package/hub/team/cli/commands/start/start-mux.mjs +24 -3
  30. package/hub/team/cli/services/runtime-mode.mjs +23 -9
  31. package/hub/team/codex-review.mjs +2 -3
  32. package/hub/team/conductor.mjs +80 -24
  33. package/hub/team/cto-auto-collect.mjs +2 -6
  34. package/hub/team/cto-pull.mjs +4 -0
  35. package/hub/team/dashboard-open.mjs +1 -0
  36. package/hub/team/execution-mode.mjs +38 -2
  37. package/hub/team/headless.mjs +493 -82
  38. package/hub/team/interactive-tui-transport.mjs +15 -1
  39. package/hub/team/launcher-template.mjs +19 -2
  40. package/hub/team/orchestrator.mjs +36 -9
  41. package/hub/team/pane.mjs +176 -14
  42. package/hub/team/psmux.mjs +79 -70
  43. package/hub/team/retry-state-machine.mjs +47 -16
  44. package/hub/team/session.mjs +57 -0
  45. package/hub/team/terminal-opener.mjs +68 -13
  46. package/hub/team/tui-viewer.mjs +65 -14
  47. package/hub/team/uds-orchestrator.mjs +384 -83
  48. package/hub/tools.mjs +6 -0
  49. package/hub/workers/codex-mcp.mjs +35 -6
  50. package/hud/cli-policy.mjs +69 -0
  51. package/hud/constants.mjs +20 -17
  52. package/hud/context-monitor.mjs +31 -21
  53. package/hud/hud-qos-status.mjs +81 -91
  54. package/hud/providers/claude.mjs +150 -84
  55. package/hud/providers/codex.mjs +10 -1
  56. package/hud/renderers.mjs +21 -59
  57. package/hud/utils.mjs +1 -15
  58. package/package.json +1 -1
  59. package/scripts/__tests__/keyword-detector.test.mjs +89 -1
  60. package/scripts/__tests__/release-governance.test.mjs +329 -12
  61. package/scripts/__tests__/release-publish-ancestor.test.mjs +67 -0
  62. package/scripts/__tests__/release-verify-wait.test.mjs +169 -0
  63. package/scripts/__tests__/resolve-auto-release.test.mjs +216 -0
  64. package/scripts/__tests__/skill-surface.test.mjs +6 -4
  65. package/scripts/__tests__/tfx-route-bash-node-parity.test.mjs +7 -5
  66. package/scripts/__tests__/tfx-route-node-entry.test.mjs +27 -21
  67. package/scripts/claude-login-detect.mjs +107 -25
  68. package/scripts/ensure-agy-hooks.mjs +31 -1
  69. package/scripts/keyword-detector.mjs +11 -1
  70. package/scripts/lib/agent-route-policy.mjs +47 -39
  71. package/scripts/lib/cli-agy.mjs +24 -4
  72. package/scripts/lib/codex-profile-config.mjs +45 -13
  73. package/scripts/lib/codex-recovery.sh +19 -0
  74. package/scripts/lib/gemini-profiles.mjs +113 -29
  75. package/scripts/lib/keyword-rules.mjs +12 -0
  76. package/scripts/lib/logger.mjs +60 -43
  77. package/scripts/lib/machine-profile.mjs +293 -0
  78. package/scripts/lib/mcp-health.mjs +44 -18
  79. package/scripts/notion-read.mjs +2 -1
  80. package/scripts/release/check-packages-mirror.mjs +53 -1
  81. package/scripts/release/lib.mjs +1 -2
  82. package/scripts/release/prepare.mjs +24 -9
  83. package/scripts/release/publish.mjs +16 -1
  84. package/scripts/release/resolve-auto-release.mjs +136 -0
  85. package/scripts/release/verify.mjs +74 -25
  86. package/scripts/remote-spawn.mjs +830 -32
  87. package/scripts/session-stale-cleanup.mjs +0 -1
  88. package/scripts/setup.mjs +487 -150
  89. package/scripts/sync-hub-mcp-settings.mjs +11 -3
  90. package/scripts/test-lock.mjs +15 -1
  91. package/scripts/tfx-gate-activate.mjs +4 -4
  92. package/scripts/tfx-route-post.mjs +90 -13
  93. package/scripts/tfx-route.sh +520 -141
  94. package/skills/merge-worktree/SKILL.md +3 -3
  95. package/skills/star-prompt/skill.json +1 -1
  96. package/skills/tfx-analysis/SKILL.md +8 -9
  97. package/skills/tfx-analysis/skill.json +1 -10
  98. package/skills/tfx-auto/SKILL.md +208 -50
  99. package/skills/tfx-auto/skill.json +2 -23
  100. package/skills/tfx-doctor/skill.json +0 -3
  101. package/skills/tfx-find/skill.json +0 -7
  102. package/skills/tfx-forge/SKILL.md +2 -2
  103. package/skills/tfx-forge/skill.json +0 -7
  104. package/skills/tfx-goal-clarify/skill.json +5 -0
  105. package/skills/tfx-harness/SKILL.md +26 -3
  106. package/skills/tfx-harness/skill.json +5 -0
  107. package/skills/tfx-hooks/SKILL.md +0 -2
  108. package/skills/tfx-hooks/skill.json +0 -3
  109. package/skills/tfx-hub/SKILL.md +1 -6
  110. package/skills/tfx-hub/skill.json +0 -3
  111. package/skills/tfx-index/skill.json +0 -6
  112. package/skills/tfx-interview/SKILL.md +0 -2
  113. package/skills/tfx-live/SKILL.md +131 -5
  114. package/skills/tfx-live/skill.json +0 -5
  115. package/skills/tfx-plan/SKILL.md +7 -8
  116. package/skills/tfx-plan/skill.json +1 -12
  117. package/skills/tfx-profile/SKILL.md +15 -13
  118. package/skills/tfx-profile/skill.json +0 -3
  119. package/skills/tfx-prune/SKILL.md +4 -6
  120. package/skills/tfx-qa/SKILL.md +7 -7
  121. package/skills/tfx-qa/skill.json +1 -11
  122. package/skills/tfx-ralph/SKILL.md +8 -11
  123. package/skills/tfx-remote/SKILL.md +2 -2
  124. package/skills/tfx-remote/skill.json +0 -3
  125. package/skills/tfx-research/SKILL.md +2 -2
  126. package/skills/tfx-research/skill.json +1 -16
  127. package/skills/tfx-review/SKILL.md +11 -11
  128. package/skills/tfx-review/skill.json +1 -11
  129. package/skills/tfx-setup/SKILL.md +5 -6
  130. package/skills/tfx-setup/skill.json +0 -3
  131. package/skills/tfx-ship/SKILL.md +43 -36
  132. package/skills/tfx-ship/skill.json +1 -9
  133. package/skills/tfx-wt/skill.json +0 -7
  134. package/tui/codex-profile.mjs +3 -3
  135. package/tui/gemini-profile.mjs +11 -24
  136. package/tui/monitor.mjs +1 -0
  137. package/tui/setup.mjs +7 -6
  138. package/scripts/headless-guard-fast.sh +0 -29
  139. package/scripts/headless-guard.mjs +0 -523
  140. package/skills/tfx-autopilot/SKILL.md +0 -51
  141. package/skills/tfx-autopilot/skill.json +0 -12
  142. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +0 -75
  143. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/eval_metadata.json +0 -42
  144. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +0 -39
  145. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/analysis.md +0 -87
  146. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/classification.md +0 -35
  147. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/commands.sh +0 -275
  148. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/routing.md +0 -56
  149. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/timing.json +0 -5
  150. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +0 -39
  151. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/analysis.md +0 -92
  152. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/classification.md +0 -71
  153. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/commands.sh +0 -264
  154. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/routing.md +0 -113
  155. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/timing.json +0 -5
  156. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/eval_metadata.json +0 -32
  157. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +0 -29
  158. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/analysis.md +0 -96
  159. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/classification.md +0 -38
  160. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/commands.sh +0 -151
  161. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/routing.md +0 -51
  162. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/timing.json +0 -5
  163. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +0 -29
  164. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/analysis.md +0 -127
  165. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/classification.md +0 -57
  166. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/commands.sh +0 -129
  167. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/routing.md +0 -84
  168. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/timing.json +0 -5
  169. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/eval_metadata.json +0 -27
  170. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +0 -24
  171. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/analysis.md +0 -98
  172. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/classification.md +0 -65
  173. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/commands.sh +0 -123
  174. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/routing.md +0 -66
  175. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/timing.json +0 -5
  176. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +0 -20
  177. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/analysis.md +0 -88
  178. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/classification.md +0 -40
  179. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/commands.sh +0 -130
  180. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/routing.md +0 -61
  181. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/timing.json +0 -5
  182. package/skills/tfx-consensus/SKILL.md +0 -56
  183. package/skills/tfx-consensus/skill.json +0 -8
  184. package/skills/tfx-debate/SKILL.md +0 -60
  185. package/skills/tfx-debate/skill.json +0 -14
  186. package/skills/tfx-fullcycle/SKILL.md +0 -47
  187. package/skills/tfx-fullcycle/skill.json +0 -13
  188. package/skills/tfx-multi/SKILL.md +0 -45
  189. package/skills/tfx-multi/references/agent-wrapper-rules.md +0 -94
  190. package/skills/tfx-multi/references/thorough-pipeline.md +0 -105
  191. package/skills/tfx-multi/skill.json +0 -10
  192. package/skills/tfx-panel/SKILL.md +0 -60
  193. package/skills/tfx-panel/skill.json +0 -14
  194. package/skills/tfx-persist/SKILL.md +0 -63
  195. package/skills/tfx-persist/skill.json +0 -14
  196. package/skills/tfx-psmux-rules/SKILL.md +0 -41
  197. package/skills/tfx-psmux-rules/skill.json +0 -10
  198. package/skills/tfx-remote-setup/SKILL.md +0 -42
  199. package/skills/tfx-remote-setup/skill.json +0 -10
  200. package/skills/tfx-remote-spawn/SKILL.md +0 -52
  201. package/skills/tfx-remote-spawn/skill.json +0 -10
  202. package/skills/tfx-swarm/SKILL.md +0 -49
  203. package/skills/tfx-swarm/skill.json +0 -13
package/hub/bridge.mjs CHANGED
@@ -400,6 +400,16 @@ export function parseArgs(argv) {
400
400
  command: { type: "string" },
401
401
  role: { type: "string" },
402
402
  "session-id": { type: "string" },
403
+ cwd: { type: "string" },
404
+ "worktree-path": { type: "string" },
405
+ branch: { type: "string" },
406
+ host: { type: "string" },
407
+ "session-kind": { type: "string" },
408
+ "is-remote": { type: "string" },
409
+ "tmux-session": { type: "string" },
410
+ "tmux-pane": { type: "string" },
411
+ "omx-session-id": { type: "string" },
412
+ "codex-session-id": { type: "string" },
403
413
  reason: { type: "string" },
404
414
  type: { type: "string" },
405
415
  from: { type: "string" },
@@ -606,6 +616,71 @@ function unavailableResult() {
606
616
  return { ok: false, reason: "hub_unavailable" };
607
617
  }
608
618
 
619
+ function nonEmptyString(value, fallback = "") {
620
+ const normalized = String(value ?? "").trim();
621
+ return normalized || fallback;
622
+ }
623
+
624
+ function readBoolean(value) {
625
+ return ["1", "true", "yes"].includes(nonEmptyString(value).toLowerCase());
626
+ }
627
+
628
+ function sessionAgentId(sessionId) {
629
+ return `codex-session-${sessionId.replace(/[^A-Za-z0-9._-]/g, "_")}`;
630
+ }
631
+
632
+ export function buildInteractiveSessionRegistrationPayload(args = {}) {
633
+ const sessionId = nonEmptyString(args["session-id"]);
634
+ if (!sessionId) return null;
635
+
636
+ const cwd = nonEmptyString(args.cwd, process.cwd());
637
+ const worktreePath = nonEmptyString(args["worktree-path"], cwd);
638
+ const host = nonEmptyString(args.host, "local");
639
+ const sessionKind = nonEmptyString(args["session-kind"], "interactive");
640
+ const agentId = nonEmptyString(args.agent, sessionAgentId(sessionId));
641
+ const metadata = {
642
+ pid: process.ppid,
643
+ registered_at: Date.now(),
644
+ cwd,
645
+ worktree_path: worktreePath,
646
+ branch: nonEmptyString(args.branch),
647
+ host,
648
+ session_kind: sessionKind,
649
+ is_remote: readBoolean(args["is-remote"]),
650
+ ...(nonEmptyString(args["tmux-session"])
651
+ ? { tmux_session: nonEmptyString(args["tmux-session"]) }
652
+ : {}),
653
+ ...(nonEmptyString(args["tmux-pane"])
654
+ ? { tmux_pane: nonEmptyString(args["tmux-pane"]) }
655
+ : {}),
656
+ ...(nonEmptyString(args["omx-session-id"])
657
+ ? { omx_session_id: nonEmptyString(args["omx-session-id"]) }
658
+ : {}),
659
+ ...(nonEmptyString(args["codex-session-id"])
660
+ ? { codex_session_id: nonEmptyString(args["codex-session-id"]) }
661
+ : {}),
662
+ };
663
+
664
+ return {
665
+ sessionId,
666
+ cwd,
667
+ worktreePath,
668
+ branch: nonEmptyString(args.branch),
669
+ host,
670
+ sessionKind,
671
+ isRemote: readBoolean(args["is-remote"]),
672
+ agent_id: agentId,
673
+ cli: nonEmptyString(args.cli, "codex"),
674
+ capabilities: args.capabilities
675
+ ? String(args.capabilities).split(",")
676
+ : ["code"],
677
+ topics: args.topics ? String(args.topics).split(",") : [],
678
+ heartbeat_ttl_ms: 300000,
679
+ session_id: sessionId,
680
+ metadata,
681
+ };
682
+ }
683
+
609
684
  function emitJson(payload) {
610
685
  if (payload !== undefined) {
611
686
  console.log(JSON.stringify(payload));
@@ -613,17 +688,33 @@ function emitJson(payload) {
613
688
  return payload?.ok !== false;
614
689
  }
615
690
 
691
+ const HUB_STORE_CLI_VALUES = new Set(["codex", "gemini", "claude", "other"]);
692
+ function normalizeCliForStore(cli) {
693
+ return HUB_STORE_CLI_VALUES.has(cli) ? cli : "other";
694
+ }
695
+
616
696
  async function cmdRegister(args) {
697
+ const interactiveSession = buildInteractiveSessionRegistrationPayload(args);
698
+ if (interactiveSession) {
699
+ const result = await requestJson("/synapse/register", {
700
+ body: interactiveSession,
701
+ timeoutMs: 3000,
702
+ });
703
+ return emitJson(result || unavailableResult());
704
+ }
705
+
617
706
  const agentId = args.agent;
618
707
  const timeoutSec = parseInt(args.timeout || "600", 10);
708
+ const rawCli = args.cli || "other";
619
709
  const outcome = await requestHub(HUB_OPERATIONS.register, {
620
710
  agent_id: agentId,
621
- cli: args.cli || "other",
711
+ cli: normalizeCliForStore(rawCli),
622
712
  timeout_sec: timeoutSec,
623
713
  heartbeat_ttl_ms: (timeoutSec + 120) * 1000,
624
714
  topics: args.topics ? args.topics.split(",") : [],
625
715
  capabilities: args.capabilities ? args.capabilities.split(",") : ["code"],
626
716
  metadata: {
717
+ cli: rawCli,
627
718
  pid: process.ppid,
628
719
  registered_at: Date.now(),
629
720
  },
@@ -634,8 +725,12 @@ async function cmdRegister(args) {
634
725
  return emitJson({
635
726
  ok: true,
636
727
  agent_id: agentId,
728
+ lease_id: result.data?.lease_id,
729
+ presence_generation: result.data?.presence_generation,
637
730
  lease_expires_ms: result.data?.lease_expires_ms,
638
731
  pipe_path: result.data?.pipe_path || getHubPipePath(),
732
+ hub_instance_id: result.data?.hub_instance_id,
733
+ store_fingerprint: result.data?.store_fingerprint,
639
734
  });
640
735
  }
641
736
 
@@ -646,6 +741,10 @@ async function cmdHeartbeat(args) {
646
741
  const outcome = await requestHub(HUB_OPERATIONS.heartbeat, {
647
742
  agent_id: args.agent,
648
743
  ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
744
+ presence_generation:
745
+ args.generation != null ? Number(args.generation) : undefined,
746
+ hub_instance_id: args["hub-instance-id"],
747
+ store_fingerprint: args["store-fingerprint"],
649
748
  });
650
749
  return emitJson(outcome?.result || unavailableResult());
651
750
  }
@@ -731,6 +830,8 @@ async function cmdTakeoverRole(args) {
731
830
  agent_id: agentId,
732
831
  reason: args.reason || "manual",
733
832
  requested_by: args["requested-by"] || "bridge",
833
+ hub_instance_id: args["hub-instance-id"],
834
+ store_fingerprint: args["store-fingerprint"],
734
835
  });
735
836
  const result = outcome?.result;
736
837
  return emitJson(result || unavailableResult());
@@ -748,6 +849,8 @@ async function cmdSendInput(args) {
748
849
  async function cmdContext(args) {
749
850
  const outcome = await requestHub(HUB_OPERATIONS.context, {
750
851
  agent_id: args.agent,
852
+ presence_generation:
853
+ args.generation != null ? Number(args.generation) : undefined,
751
854
  topics: args.topics ? args.topics.split(",") : undefined,
752
855
  max_messages: parseInt(args.max || "10", 10),
753
856
  auto_ack: true,
@@ -793,13 +896,13 @@ async function cmdContext(args) {
793
896
  async function cmdDeregister(args) {
794
897
  const outcome = await requestHub(HUB_OPERATIONS.deregister, {
795
898
  agent_id: args.agent,
899
+ presence_generation:
900
+ args.generation != null ? Number(args.generation) : undefined,
901
+ hub_instance_id: args["hub-instance-id"],
902
+ store_fingerprint: args["store-fingerprint"],
796
903
  });
797
904
  const result = outcome?.result;
798
905
 
799
- if (result?.ok) {
800
- return emitJson({ ok: true, agent_id: args.agent, status: "offline" });
801
- }
802
-
803
906
  return emitJson(result || unavailableResult());
804
907
  }
805
908
 
@@ -1291,6 +1394,7 @@ async function cmdDaemonProbe(args) {
1291
1394
  short: payload.short,
1292
1395
  sessionId: payload.sessionId,
1293
1396
  timeoutMs: numericOption(payload.timeoutMs, 6000),
1397
+ tmpRoot: payload.tmpRoot,
1294
1398
  });
1295
1399
  return emitJson(probe);
1296
1400
  } catch (error) {
@@ -1314,6 +1418,7 @@ async function cmdDaemonAttach(args) {
1314
1418
  short: payload.short,
1315
1419
  sessionId: payload.sessionId,
1316
1420
  timeoutMs: numericOption(payload.timeoutMs, 6000),
1421
+ tmpRoot: payload.tmpRoot,
1317
1422
  });
1318
1423
  if (!probe.ok) return emitJson(daemonAttachProbeFailureResult(probe));
1319
1424
  const short = payload.short ?? probe.target?.short;
@@ -1382,6 +1487,7 @@ async function cmdDaemonInterrupt(args) {
1382
1487
  short: payload.short,
1383
1488
  sessionId: payload.sessionId,
1384
1489
  timeoutMs: numericOption(payload.timeoutMs, 6000),
1490
+ tmpRoot: payload.tmpRoot,
1385
1491
  });
1386
1492
  if (!probe.ok) return emitJson(daemonInterruptProbeFailureResult(probe));
1387
1493
  const short = payload.short ?? probe.target?.short;
@@ -45,7 +45,7 @@ function makeFallback(policy, trigger, inputsHash, now) {
45
45
  {
46
46
  cli: fb.default_cli ?? "codex",
47
47
  accountId: null,
48
- model: fb.default_model ?? "gpt-5.6-terra",
48
+ model: fb.default_model ?? "gpt-6-sol",
49
49
  riskTier: "safe",
50
50
  reason: trigger,
51
51
  },
@@ -4,6 +4,10 @@
4
4
  import { mkdirSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
+ import {
8
+ resolveGeminiModel,
9
+ resolveGeminiProfileForPurpose,
10
+ } from "../scripts/lib/gemini-profiles.mjs";
7
11
  import {
8
12
  executeWithCircuitBroker,
9
13
  normalizePathForShell,
@@ -61,15 +65,13 @@ async function runPreflight(opts = {}) {
61
65
  // ── Command building ────────────────────────────────────────────
62
66
 
63
67
  function buildGeminiCommand(prompt, resultFile, opts = {}) {
64
- const parts = [
65
- "printf",
66
- "%s",
67
- shellSingleQuote(prompt),
68
- "|",
69
- "agy",
70
- "--print",
71
- "--dangerously-skip-permissions",
72
- ];
68
+ // agy 의 --print 는 값(프롬프트)을 받는 플래그다. 1.1.27 부터 stdin 프롬프트는
69
+ // "empty prompt" 로 거부되므로 프롬프트를 --print 의 값으로, 맨 끝에 넘긴다.
70
+ const parts = ["agy", "--dangerously-skip-permissions"];
71
+ if (typeof opts.model === "string" && opts.model.trim()) {
72
+ parts.push("--model", shellSingleQuote(opts.model.trim()));
73
+ }
74
+ parts.push("--print", shellSingleQuote(prompt));
73
75
 
74
76
  if (resultFile) {
75
77
  return `${parts.join(" ")} > ${shellSingleQuote(normalizePathForShell(resultFile))} 2>${shellSingleQuote(normalizePathForShell(resultFile + ".err"))}`;
@@ -78,11 +80,27 @@ function buildGeminiCommand(prompt, resultFile, opts = {}) {
78
80
  return parts.join(" ");
79
81
  }
80
82
 
83
+ function resolveAntigravityModel(opts = {}) {
84
+ // effort 는 역할별 SSOT(scripts/lib/gemini-profiles.mjs). 명시 model 이 우선한다.
85
+ const explicit = typeof opts.model === "string" ? opts.model.trim() : "";
86
+ if (explicit)
87
+ return resolveGeminiModel(explicit, {
88
+ profilesPath: opts.geminiProfilesPath,
89
+ });
90
+ if (!opts.role && !opts.agent) return "";
91
+ return resolveGeminiModel(
92
+ resolveGeminiProfileForPurpose(opts.role || opts.agent),
93
+ {
94
+ profilesPath: opts.geminiProfilesPath,
95
+ },
96
+ );
97
+ }
98
+
81
99
  function buildAttempts(opts, preflight) {
82
100
  const timeout = Number.isFinite(opts.timeout) ? opts.timeout : 900_000;
83
101
  const base = {
84
102
  timeout,
85
- model: opts.model,
103
+ model: resolveAntigravityModel(opts),
86
104
  allowedMcpServers: Array.isArray(opts.mcpServers)
87
105
  ? [...opts.mcpServers]
88
106
  : [],
@@ -98,7 +116,7 @@ function buildAttempts(opts, preflight) {
98
116
  export function buildExecArgs(opts = {}) {
99
117
  const prompt = typeof opts.prompt === "string" ? opts.prompt : "";
100
118
  return buildGeminiCommand(prompt, opts.resultFile || null, {
101
- model: opts.model,
119
+ model: resolveAntigravityModel(opts),
102
120
  allowedMcpServers: opts.mcpServers,
103
121
  });
104
122
  }
@@ -142,6 +142,7 @@ export function createMemoryStore() {
142
142
  const store = {
143
143
  type: "memory",
144
144
  db: null,
145
+ fingerprint: "memory:ephemeral",
145
146
  uuidv7,
146
147
 
147
148
  close() {},
@@ -166,6 +167,7 @@ export function createMemoryStore() {
166
167
  topics: clone(topics),
167
168
  last_seen_ms: now,
168
169
  lease_expires_ms: now + heartbeat_ttl_ms,
170
+ presence_generation: Number(current.presence_generation || 0) + 1,
169
171
  status: "online",
170
172
  metadata: clone(metadata),
171
173
  };
@@ -173,6 +175,7 @@ export function createMemoryStore() {
173
175
  return {
174
176
  agent_id,
175
177
  lease_id: uuidv7(),
178
+ presence_generation: next.presence_generation,
176
179
  lease_expires_ms: next.lease_expires_ms,
177
180
  server_time_ms: now,
178
181
  effective: {
@@ -183,6 +186,7 @@ export function createMemoryStore() {
183
186
  lease_expires_ms: next.lease_expires_ms,
184
187
  store_type: "memory",
185
188
  db_path: null,
189
+ store_fingerprint: "memory:ephemeral",
186
190
  },
187
191
  };
188
192
  },
@@ -191,14 +195,23 @@ export function createMemoryStore() {
191
195
  return clone(agents.get(id) || null);
192
196
  },
193
197
 
194
- refreshLease(agentId, ttlMs = 30000) {
198
+ refreshLease(agentId, ttlMs = 30000, presenceGeneration) {
195
199
  const current = agents.get(agentId);
196
- if (!current)
200
+ if (
201
+ !current ||
202
+ (presenceGeneration != null &&
203
+ current.presence_generation !== presenceGeneration)
204
+ )
197
205
  return {
198
206
  agent_id: agentId,
199
207
  lease_expires_ms: Date.now() + ttlMs,
200
208
  server_time_ms: Date.now(),
201
- effective: { updated: false, store_type: "memory", db_path: null },
209
+ effective: {
210
+ updated: false,
211
+ store_type: "memory",
212
+ db_path: null,
213
+ store_fingerprint: "memory:ephemeral",
214
+ },
202
215
  };
203
216
  const now = Date.now();
204
217
  current.last_seen_ms = now;
@@ -208,7 +221,12 @@ export function createMemoryStore() {
208
221
  agent_id: agentId,
209
222
  lease_expires_ms: current.lease_expires_ms,
210
223
  server_time_ms: now,
211
- effective: { updated: true, store_type: "memory", db_path: null },
224
+ effective: {
225
+ updated: true,
226
+ store_type: "memory",
227
+ db_path: null,
228
+ store_fingerprint: "memory:ephemeral",
229
+ },
212
230
  };
213
231
  },
214
232
 
@@ -243,9 +261,19 @@ export function createMemoryStore() {
243
261
 
244
262
  sweepStaleAgents() {
245
263
  const now = Date.now();
264
+ const candidates = Array.from(agents.values(), (agent) => ({
265
+ agent_id: agent.agent_id,
266
+ presence_generation: agent.presence_generation,
267
+ }));
246
268
  let stale = 0;
247
269
  let offline = 0;
248
- for (const agent of agents.values()) {
270
+ for (const candidate of candidates) {
271
+ const agent = agents.get(candidate.agent_id);
272
+ if (
273
+ !agent ||
274
+ agent.presence_generation !== candidate.presence_generation
275
+ )
276
+ continue;
249
277
  if (agent.status === "online" && agent.lease_expires_ms < now) {
250
278
  agent.status = "stale";
251
279
  stale += 1;
@@ -260,9 +288,14 @@ export function createMemoryStore() {
260
288
  return { stale, offline };
261
289
  },
262
290
 
263
- updateAgentStatus(agentId, status) {
291
+ updateAgentStatus(agentId, status, presenceGeneration) {
264
292
  const current = agents.get(agentId);
265
- if (!current) return false;
293
+ if (
294
+ !current ||
295
+ (presenceGeneration != null &&
296
+ current.presence_generation !== presenceGeneration)
297
+ )
298
+ return false;
266
299
  current.status = status;
267
300
  return true;
268
301
  },
@@ -421,21 +454,11 @@ export function createMemoryStore() {
421
454
  .map((request) => clone(request));
422
455
  },
423
456
 
424
- expireHumanRequests() {
425
- let changed = 0;
426
- const now = Date.now();
427
- for (const request of humanRequests.values()) {
428
- if (request.state === "pending" && request.deadline_ms < now) {
429
- request.state = "timed_out";
430
- changed += 1;
431
- }
432
- }
433
- return changed;
434
- },
435
-
436
457
  moveToDeadLetter(messageId, reason, lastError = null) {
437
458
  const current = messages.get(messageId);
438
- if (current) current.status = "dead_letter";
459
+ if (!current || current.status === "dead_letter") return false;
460
+ current.status = "dead_letter";
461
+ if (deadLetters.has(messageId)) return true;
439
462
  deadLetters.set(messageId, {
440
463
  message_id: messageId,
441
464
  reason,
@@ -685,25 +708,38 @@ export function createMemoryStore() {
685
708
  });
686
709
  },
687
710
 
688
- sweepExpired() {
689
- const now = Date.now();
690
- let expiredMessages = 0;
711
+ sweepExpired({ now_ms = Date.now() } = {}) {
712
+ const transitions = [];
691
713
  for (const message of messages.values()) {
692
- if (message.status === "queued" && message.expires_at_ms < now) {
693
- message.status = "dead_letter";
714
+ if (
715
+ !["queued", "delivered"].includes(message.status) ||
716
+ message.expires_at_ms > now_ms
717
+ )
718
+ continue;
719
+ const snapshot = clone(message);
720
+ const previousStatus = message.status;
721
+ message.status = "dead_letter";
722
+ const dlqInserted = !deadLetters.has(message.id);
723
+ if (dlqInserted) {
694
724
  deadLetters.set(message.id, {
695
725
  message_id: message.id,
696
726
  reason: "ttl_expired",
697
- failed_at_ms: now,
727
+ failed_at_ms: now_ms,
698
728
  last_error: null,
699
729
  });
700
- expiredMessages += 1;
701
730
  }
731
+ transitions.push({
732
+ message: snapshot,
733
+ previous_status: previousStatus,
734
+ reason: "ttl_expired",
735
+ failed_at_ms: now_ms,
736
+ dlq_inserted: dlqInserted,
737
+ });
702
738
  }
703
- const humanRequestsExpired = store.expireHumanRequests();
704
739
  return {
705
- messages: expiredMessages,
706
- human_requests: humanRequestsExpired,
740
+ now_ms,
741
+ messages: transitions.length,
742
+ transitions,
707
743
  };
708
744
  },
709
745
 
@@ -8,11 +8,12 @@
8
8
  //
9
9
  // 기존 플래그 (Phase 2 v10.9.33+):
10
10
  // --cli {auto|codex|antigravity|claude}
11
- // --mode {quick|deep|consensus}
11
+ // --mode {quick|deep|consensus|live}
12
12
  // --parallel {1|N|swarm}
13
13
  // --retry {0|1|ralph|auto-escalate} (Phase 3 에서 ralph/auto-escalate 신규)
14
14
  // --isolation {none|worktree}
15
15
  // --remote {none|<host>}
16
+ // --rounds <N> (--mode live 전용, 기본 4)
16
17
  //
17
18
  // Phase 3 신규:
18
19
  // --lead {claude|codex} (tfx-auto-codex 의미 흡수)
@@ -31,11 +32,12 @@ export const DEFAULT_OPTIONS = Object.freeze({
31
32
  lead: "claude",
32
33
  noClaudeNative: false,
33
34
  maxIterations: 0,
35
+ rounds: 4,
34
36
  });
35
37
 
36
38
  const VALID_VALUES = Object.freeze({
37
39
  cli: ["auto", "codex", "antigravity", "claude"],
38
- mode: ["quick", "deep", "consensus"],
40
+ mode: ["quick", "deep", "consensus", "live"],
39
41
  retry: ["0", "1", "ralph", "auto-escalate"],
40
42
  isolation: ["none", "worktree"],
41
43
  lead: ["claude", "codex"],
@@ -50,6 +52,7 @@ const VALUE_FLAGS = new Set([
50
52
  "--remote",
51
53
  "--lead",
52
54
  "--max-iterations",
55
+ "--rounds",
53
56
  ]);
54
57
 
55
58
  const BOOL_FLAGS = new Set(["--no-claude-native"]);
@@ -61,12 +64,14 @@ export function parseArgs(input) {
61
64
  const opts = { ...DEFAULT_OPTIONS };
62
65
  const warnings = [];
63
66
  const taskTokens = [];
67
+ const providedFlags = new Set();
64
68
 
65
69
  let i = 0;
66
70
  while (i < tokens.length) {
67
71
  const raw = tokens[i];
68
72
 
69
73
  if (BOOL_FLAGS.has(raw)) {
74
+ providedFlags.add(raw);
70
75
  applyBool(opts, raw);
71
76
  i += 1;
72
77
  continue;
@@ -82,6 +87,7 @@ export function parseArgs(input) {
82
87
  }
83
88
 
84
89
  if (VALUE_FLAGS.has(flag)) {
90
+ providedFlags.add(flag);
85
91
  if (value === null) {
86
92
  const next = tokens[i + 1];
87
93
  if (next === undefined || next.startsWith("--")) {
@@ -108,7 +114,7 @@ export function parseArgs(input) {
108
114
  i += 1;
109
115
  }
110
116
 
111
- validate(opts, warnings);
117
+ validate(opts, warnings, providedFlags);
112
118
 
113
119
  return {
114
120
  ...opts,
@@ -196,10 +202,19 @@ function applyValue(opts, flag, value, warnings) {
196
202
  }
197
203
  break;
198
204
  }
205
+ case "--rounds": {
206
+ const n = Number(value);
207
+ if (!Number.isInteger(n) || n <= 0) {
208
+ warnings.push(`invalid --rounds=${value}, expected positive integer`);
209
+ } else {
210
+ opts.rounds = n;
211
+ }
212
+ break;
213
+ }
199
214
  }
200
215
  }
201
216
 
202
- function validate(opts, warnings) {
217
+ function validate(opts, warnings, providedFlags = new Set()) {
203
218
  for (const key of Object.keys(VALID_VALUES)) {
204
219
  if (!VALID_VALUES[key].includes(opts[key])) {
205
220
  warnings.push(
@@ -214,6 +229,28 @@ function validate(opts, warnings) {
214
229
  ) {
215
230
  warnings.push(`invalid --parallel=${opts.parallel}, expected 1|N|swarm`);
216
231
  }
232
+
233
+ if (opts.mode === "live") {
234
+ const ignored = [
235
+ ["--parallel", "parallel", DEFAULT_OPTIONS.parallel],
236
+ ["--isolation", "isolation", DEFAULT_OPTIONS.isolation],
237
+ ["--remote", "remote", DEFAULT_OPTIONS.remote],
238
+ ];
239
+ for (const [flag, key, fallback] of ignored) {
240
+ if (providedFlags.has(flag)) {
241
+ warnings.push(`${flag} ignored with --mode live`);
242
+ opts[key] = fallback;
243
+ }
244
+ }
245
+ if (providedFlags.has("--retry") && opts.retry === "ralph") {
246
+ warnings.push("--retry ralph ignored with --mode live");
247
+ opts.retry = DEFAULT_OPTIONS.retry;
248
+ }
249
+ } else if (providedFlags.has("--rounds")) {
250
+ warnings.push("--rounds ignored unless --mode live");
251
+ opts.rounds = DEFAULT_OPTIONS.rounds;
252
+ }
253
+
217
254
  const parallelOne = opts.parallel === "1" || opts.parallel === 1;
218
255
  if (parallelOne && opts.isolation === "worktree") {
219
256
  warnings.push(
package/hub/pipe.mjs CHANGED
@@ -267,18 +267,11 @@ export function createPipeServer({
267
267
  const result = router.refreshAgentLease(
268
268
  agentId,
269
269
  payload.heartbeat_ttl_ms || heartbeatTtlMs,
270
+ payload.presence_generation,
271
+ payload,
270
272
  );
271
273
  if (client) touchClient(client);
272
- if (result?.effective?.updated === false) {
273
- return {
274
- ok: false,
275
- error: {
276
- code: "AGENT_NOT_FOUND",
277
- message: `heartbeat target not registered: ${agentId}`,
278
- },
279
- };
280
- }
281
- return { ok: true, data: result };
274
+ return result;
282
275
  }
283
276
 
284
277
  case "publish": {
@@ -357,12 +350,14 @@ export function createPipeServer({
357
350
 
358
351
  case "deregister": {
359
352
  const agentId = resolveAgentId(client, payload);
360
- router.updateAgentStatus(agentId, "offline");
353
+ const result = router.transitionAgentStatus(
354
+ agentId,
355
+ "offline",
356
+ payload.presence_generation,
357
+ payload,
358
+ );
361
359
  if (client) touchClient(client);
362
- return {
363
- ok: true,
364
- data: { agent_id: agentId, status: "offline" },
365
- };
360
+ return result;
366
361
  }
367
362
 
368
363
  case "send_input": {
package/hub/reflexion.mjs CHANGED
@@ -7,8 +7,6 @@ const DEFAULT_CONFIDENCE = 0.5;
7
7
  const ACTIVE_RULE_CONFIDENCE = 0.5;
8
8
  const ADAPTIVE_PROMOTION_STEP = 0.1;
9
9
  const ADAPTIVE_DECAY_STEP = 0.1;
10
- /** @deprecated 세션 기반 decay에서 시간 기반으로 변경됨. 미사용. */
11
- const _ADAPTIVE_DECAY_WINDOW = 5;
12
10
  const ADAPTIVE_DELETE_THRESHOLD = 0.3;
13
11
 
14
12
  function clampConfidence(value) {
@@ -102,61 +100,6 @@ function buildAdaptiveSolution(errorContext = {}, errorText = "") {
102
100
  return `${toolName} 재시도 전 실패 원인을 검증하세요: ${summary}`;
103
101
  }
104
102
 
105
- function normalizeSessionIds(sessionIds) {
106
- if (!Array.isArray(sessionIds)) return [];
107
- return [
108
- ...new Set(
109
- sessionIds.filter((value) => typeof value === "string" && value.trim()),
110
- ),
111
- ];
112
- }
113
-
114
- /** @deprecated reflexion_entries 기반. adaptive_rules에서는 미사용. 세션 인식 복원 시 재활용 예정. */
115
- function getAdaptiveState(rule = {}) {
116
- const state = rule.adaptive_state || {};
117
- const session_ids = normalizeSessionIds(state.session_ids);
118
- return {
119
- ...state,
120
- project_slug: pickString(state.project_slug, rule.context?.project_slug),
121
- session_ids,
122
- session_occurrences: Math.max(
123
- state.session_occurrences || 0,
124
- session_ids.length,
125
- ),
126
- last_seen_session: pickSessionCount(state.last_seen_session),
127
- last_decay_session: pickSessionCount(state.last_decay_session),
128
- };
129
- }
130
-
131
- /** @deprecated reflexion_entries 기반. adaptive_rules에서는 미사용. 세션 인식 복원 시 재활용 예정. */
132
- function _mergeAdaptiveState(rule, errorContext = {}) {
133
- const current = getAdaptiveState(rule);
134
- const sessionId = pickSessionId(errorContext);
135
- const sessionCount = pickSessionCount(
136
- errorContext.sessionCount,
137
- errorContext.session_count,
138
- errorContext.context?.sessionCount,
139
- errorContext.context?.session_count,
140
- );
141
- const session_ids = normalizeSessionIds(
142
- sessionId ? [...current.session_ids, sessionId] : current.session_ids,
143
- );
144
- return {
145
- ...current,
146
- project_slug: pickString(
147
- pickProjectSlug(errorContext),
148
- current.project_slug,
149
- ),
150
- session_ids,
151
- session_occurrences: Math.max(
152
- current.session_occurrences,
153
- session_ids.length,
154
- ),
155
- last_seen_session: Math.max(current.last_seen_session, sessionCount),
156
- last_decay_session: current.last_decay_session || sessionCount,
157
- };
158
- }
159
-
160
103
  function filterEntriesByType(entries, type) {
161
104
  return entries.filter(
162
105
  (entry) => (entry.type || DEFAULT_REFLEXION_TYPE) === type,