@triflux/core 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.
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { spawn } from "node:child_process";
3
4
  import { argv, exit, stdin, stdout } from "node:process";
4
- import { pathToFileURL } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
6
  import { drainPendingSynapse as defaultDrainPendingSynapse } from "../hub/team/synapse-http.mjs";
6
7
  import {
7
8
  heartbeatInteractiveSession as defaultHeartbeatInteractiveSession,
@@ -51,6 +52,66 @@ function normalizeMode(mode, payload) {
51
52
  return "";
52
53
  }
53
54
 
55
+ /**
56
+ * Start a detached bridge call so Codex/OMX presence reaches both the Synapse
57
+ * session registry and the hub agents table. This is intentionally independent
58
+ * of the legacy session-start work below: a missing or unavailable hub must
59
+ * never delay or fail SessionStart.
60
+ */
61
+ export function launchCodexPresenceRegistration(payload, opts = {}) {
62
+ try {
63
+ const sessionId = String(payload?.session_id || "").trim();
64
+ if (!sessionId) return false;
65
+
66
+ const cwd =
67
+ typeof payload?.cwd === "string" && payload.cwd.trim()
68
+ ? payload.cwd
69
+ : process.cwd();
70
+ const bridgePath =
71
+ opts.bridgePath ||
72
+ fileURLToPath(new URL("../hub/bridge.mjs", import.meta.url));
73
+ const spawnFn = opts.spawnFn || spawn;
74
+ const canonicalSessionId = String(process.env.OMX_SESSION_ID || "").trim();
75
+ const tmuxSession = String(
76
+ process.env.OMX_TMUX_SESSION_NAME || process.env.OMX_TMUX_SESSION || "",
77
+ ).trim();
78
+ const tmuxPane = String(process.env.TMUX_PANE || "").trim();
79
+ const args = [
80
+ bridgePath,
81
+ "register",
82
+ "--session-id",
83
+ sessionId,
84
+ "--cwd",
85
+ cwd,
86
+ "--worktree-path",
87
+ cwd,
88
+ "--session-kind",
89
+ "interactive",
90
+ "--host",
91
+ "local",
92
+ "--codex-session-id",
93
+ sessionId,
94
+ ];
95
+ if (canonicalSessionId && canonicalSessionId !== sessionId) {
96
+ args.push("--omx-session-id", canonicalSessionId);
97
+ }
98
+ if (tmuxSession) args.push("--tmux-session", tmuxSession);
99
+ if (tmuxPane) args.push("--tmux-pane", tmuxPane);
100
+
101
+ const child = spawnFn(process.execPath, args, {
102
+ detached: true,
103
+ stdio: "ignore",
104
+ windowsHide: true,
105
+ env: process.env,
106
+ });
107
+ child?.once?.("error", () => {});
108
+ child?.unref?.();
109
+ return true;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
54
115
  function swallowStdoutWrite(_chunk, encodingOrCallback, callback) {
55
116
  const done =
56
117
  typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
@@ -91,10 +152,15 @@ export async function runCodexSessionHook(stdinData, opts = {}) {
91
152
  opts.heartbeatInteractiveSession || defaultHeartbeatInteractiveSession;
92
153
  const drainPendingSynapse =
93
154
  opts.drainPendingSynapse || defaultDrainPendingSynapse;
155
+ const launchPresenceRegistration =
156
+ opts.launchPresenceRegistration || launchCodexPresenceRegistration;
94
157
 
95
158
  try {
96
159
  await runHookSideEffectsWithStdoutSuppressed(async () => {
97
160
  if (mode === "register") {
161
+ try {
162
+ launchPresenceRegistration(parsed.payload);
163
+ } catch {}
98
164
  try {
99
165
  await hubEnsureRun(stdinData);
100
166
  } catch {}
@@ -11,6 +11,8 @@ import {
11
11
  } from "node:fs";
12
12
  import { dirname, join, resolve } from "node:path";
13
13
 
14
+ import { resolveRoleControlSnapshot } from "../hub/lib/cto-env.mjs";
15
+
14
16
  const MAX_CONTEXT_BYTES = 2048;
15
17
  const HEADER = "[CTO NORTH STAR - READ ONLY]";
16
18
  const INSTRUCTION =
@@ -45,21 +47,6 @@ function sha256(input) {
45
47
  return createHash("sha256").update(input).digest("hex");
46
48
  }
47
49
 
48
- function ctoNorthStarEnabled() {
49
- switch (process.env.TFX_CTO_NORTH_STAR || "1") {
50
- case "0":
51
- case "false":
52
- case "FALSE":
53
- case "off":
54
- case "OFF":
55
- case "no":
56
- case "NO":
57
- return false;
58
- default:
59
- return true;
60
- }
61
- }
62
-
63
50
  function wrapBrief(brief) {
64
51
  return [HEADER, INSTRUCTION, BEGIN, brief, END].join("\n");
65
52
  }
@@ -89,7 +76,7 @@ function writeMarker(markerPath, state) {
89
76
  }
90
77
 
91
78
  function main() {
92
- if (!ctoNorthStarEnabled()) return;
79
+ if (!resolveRoleControlSnapshot().north_star_enabled) return;
93
80
 
94
81
  const input = readStdinJson();
95
82
  if (input.hook_event_name && input.hook_event_name !== "UserPromptSubmit") {
@@ -129,9 +129,8 @@ function main() {
129
129
  JSON.stringify(input.tool_result || ""),
130
130
  ].join("\n");
131
131
 
132
- // ── reflexion 적응형 학습: safety-guard/headless-guard 차단을 패널티로 기록 ──
133
- const isSafetyBlock =
134
- /\[(?:safety-guard|headless-guard)\].*(?:BLOCKED|차단)/i.test(errorText);
132
+ // ── reflexion 적응형 학습: safety-guard 차단을 패널티로 기록 ──
133
+ const isSafetyBlock = /\[safety-guard\].*(?:BLOCKED|차단)/i.test(errorText);
135
134
  if (isSafetyBlock) {
136
135
  try {
137
136
  const home = process.env.HOME || process.env.USERPROFILE || "";
@@ -147,9 +146,7 @@ function main() {
147
146
  errorText.match(/\[.*?\]\s*(.{0,120})/)?.[1] ||
148
147
  errorText.slice(0, 120),
149
148
  command_preview: command.slice(0, 200),
150
- source: errorText.includes("safety-guard")
151
- ? "safety-guard"
152
- : "headless-guard",
149
+ source: "safety-guard",
153
150
  };
154
151
  writeFileSync(penaltyFile, JSON.stringify(entry) + "\n", { flag: "a" });
155
152
  } catch {
@@ -113,7 +113,7 @@ function identifySource(cmd) {
113
113
  if (/oh-my-claudecode|omc/i.test(cmd)) return "omc";
114
114
  if (/session-vault/i.test(cmd)) return "session-vault";
115
115
  if (/compact-helper/i.test(cmd)) return "compact-helper";
116
- if (/headless-guard|tfx-gate/i.test(cmd)) return "omc";
116
+ if (/tfx-gate/i.test(cmd)) return "omc";
117
117
  if (/mcp-cleanup/i.test(cmd)) return "system";
118
118
  return "unknown";
119
119
  }
@@ -68,17 +68,6 @@
68
68
  "blocking": true,
69
69
  "description": "git commit 전 교차 리뷰 미검증 파일 차단/경고"
70
70
  },
71
- {
72
- "id": "omc-headless-guard",
73
- "source": "omc",
74
- "matcher": "Bash|Agent",
75
- "command": "bash \"${HOME}/.claude/scripts/headless-guard-fast.sh\"",
76
- "priority": 50,
77
- "enabled": true,
78
- "timeout": 3,
79
- "blocking": false,
80
- "description": "primary multiplexer headless 가드"
81
- },
82
71
  {
83
72
  "id": "omc-tfx-gate-activate",
84
73
  "source": "omc",
@@ -55,7 +55,7 @@
55
55
  "flags": "i"
56
56
  }
57
57
  ],
58
- "skill": "tfx-swarm",
58
+ "skill": "tfx-auto",
59
59
  "priority": 1,
60
60
  "supersedes": ["tfx-codex"],
61
61
  "exclusive": false,
@@ -412,6 +412,7 @@
412
412
  }
413
413
  ],
414
414
  "skill": "tfx-wt",
415
+ "platform": ["win32"],
415
416
  "priority": 1,
416
417
  "supersedes": ["tfx-unified"],
417
418
  "exclusive": false,
@@ -431,6 +432,7 @@
431
432
  }
432
433
  ],
433
434
  "skill": "tfx-wt",
435
+ "platform": ["win32"],
434
436
  "priority": 1,
435
437
  "supersedes": ["tfx-unified"],
436
438
  "exclusive": false,
@@ -450,6 +452,7 @@
450
452
  }
451
453
  ],
452
454
  "skill": "tfx-wt",
455
+ "platform": ["win32"],
453
456
  "priority": 1,
454
457
  "supersedes": ["tfx-unified"],
455
458
  "exclusive": false,
@@ -469,6 +472,7 @@
469
472
  }
470
473
  ],
471
474
  "skill": "tfx-wt",
475
+ "platform": ["win32"],
472
476
  "priority": 1,
473
477
  "supersedes": ["tfx-unified"],
474
478
  "exclusive": false,
@@ -681,11 +685,11 @@
681
685
  "flags": "i"
682
686
  },
683
687
  {
684
- "source": "\\b(?:checkpoint|resume|pick\\s*up|where\\s*was\\s*I)\\b",
688
+ "source": "\\b(?:resume|pick\\s*up|where\\s*was\\s*I)\\b",
685
689
  "flags": "i"
686
690
  }
687
691
  ],
688
- "skill": "checkpoint",
692
+ "skill": "gstack-context-restore",
689
693
  "priority": 5,
690
694
  "supersedes": [],
691
695
  "exclusive": false,
@@ -719,7 +723,7 @@
719
723
  "flags": "i"
720
724
  },
721
725
  {
722
- "source": "\\bship\\b(?!.*tfx)",
726
+ "source": "(?<![\\w-])ship\\b(?!.*tfx)",
723
727
  "flags": "i"
724
728
  }
725
729
  ],
@@ -3,6 +3,8 @@
3
3
  import { stdin, stdout } from "node:process";
4
4
 
5
5
  const META_PATTERNS = [/&&/, /\|\|/, /[;|<>`]/, /\$\(/];
6
+ const SHELL_EXPANSION_PATTERN =
7
+ /(?:^|[^\\])\$(?:[A-Za-z_][A-Za-z0-9_]*|\{[^}]*\}|[?*#@$!0-9-])|(?:^|\s)~[A-Za-z0-9_-]*(?=\/|$)/;
6
8
  const RISKY_PATTERNS = [
7
9
  /(^|\s)rm(\s|$)/,
8
10
  /(^|\s)mv(\s|$)/,
@@ -19,6 +21,25 @@ const RISKY_PATTERNS = [
19
21
  /(^|\s)pnpm\s+publish(\s|$)/,
20
22
  /(^|\s)yarn\s+publish(\s|$)/,
21
23
  ];
24
+ const SEARCH_EXTERNAL_INPUT_FLAGS = new Set([
25
+ "--exclude-from",
26
+ "--file",
27
+ "--files-from",
28
+ "--ignore-file",
29
+ "--pre",
30
+ "--pre-glob",
31
+ ]);
32
+ const FIND_SIDE_EFFECT_OPERATORS = new Set([
33
+ "-delete",
34
+ "-exec",
35
+ "-execdir",
36
+ "-fprint",
37
+ "-fprint0",
38
+ "-fprintf",
39
+ "-fls",
40
+ "-ok",
41
+ "-okdir",
42
+ ]);
22
43
 
23
44
  function silentlyExit() {
24
45
  process.exit(0);
@@ -57,10 +78,21 @@ function isRepoRelativePath(value) {
57
78
  function hasUnsafeSurface(command) {
58
79
  return (
59
80
  META_PATTERNS.some((pattern) => pattern.test(command)) ||
81
+ SHELL_EXPANSION_PATTERN.test(command) ||
60
82
  RISKY_PATTERNS.some((pattern) => pattern.test(command))
61
83
  );
62
84
  }
63
85
 
86
+ function isExternalSearchInputFlag(word) {
87
+ return (
88
+ SEARCH_EXTERNAL_INPUT_FLAGS.has(word) ||
89
+ [...SEARCH_EXTERNAL_INPUT_FLAGS].some((flag) =>
90
+ word.startsWith(`${flag}=`),
91
+ ) ||
92
+ /^-[^-]*f/.test(word)
93
+ );
94
+ }
95
+
64
96
  function allowGit(words, command) {
65
97
  if (command === "git status" || command === "git status --short")
66
98
  return "git status";
@@ -97,8 +129,7 @@ function allowListing(words) {
97
129
 
98
130
  if (words[0] === "find") {
99
131
  const args = words.slice(1);
100
- if (args.some((word) => word === "-delete" || word.startsWith("-exec")))
101
- return null;
132
+ if (args.some((word) => FIND_SIDE_EFFECT_OPERATORS.has(word))) return null;
102
133
  const firstOption = args.findIndex((word) => word.startsWith("-"));
103
134
  const pathArgs = (
104
135
  firstOption === -1 ? args : args.slice(0, firstOption)
@@ -118,7 +149,10 @@ function allowSearch(words) {
118
149
  if (
119
150
  args.some(
120
151
  (word) =>
152
+ isExternalSearchInputFlag(word) ||
121
153
  word.startsWith("/") ||
154
+ word.startsWith("~") ||
155
+ word.startsWith("$") ||
122
156
  word === ".." ||
123
157
  word.startsWith("../") ||
124
158
  word.includes("/../"),
@@ -20,6 +20,10 @@ import {
20
20
  registerSynapseSession,
21
21
  } from "../hub/team/synapse-http.mjs";
22
22
  import { createModuleLogger } from "../scripts/lib/logger.mjs";
23
+ import {
24
+ formatCliPolicyLine,
25
+ resolveCliPolicy,
26
+ } from "../scripts/lib/machine-profile.mjs";
23
27
 
24
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
29
  const SCRIPTS = join(__dirname, "..", "scripts");
@@ -314,6 +318,36 @@ export function heartbeatInteractiveSession(stdinData, seams = {}) {
314
318
  }
315
319
  }
316
320
 
321
+ /**
322
+ * TFX_DISABLE_* SSOT 를 읽어 세션 문맥에 넣을 cli-policy 한 줄을 만든다.
323
+ *
324
+ * 형식은 scripts/lib/machine-profile.mjs 의 formatCliPolicyLine 이 고정한다.
325
+ * 리더는 파일 하나를 동기 읽기하므로 ms 단위이고, 어떤 실패도 빈 문자열로
326
+ * 흡수해서 SessionStart 가 이 단계 때문에 죽지 않게 한다.
327
+ *
328
+ * 프로파일 파싱 경고나 0/1 밖의 값이 있으면 둘째 줄로 함께 노출한다. 경고를
329
+ * 삼키면 잘못 적은 프로파일이 조용히 "허용"으로 읽혀서 원인을 못 찾는다.
330
+ *
331
+ * @param {NodeJS.ProcessEnv} [env] 판독할 환경변수
332
+ * @param {{ resolve?: Function }} [opts] resolve 는 리더 대체 주입용 테스트 시임
333
+ * @returns {string} 한 줄 또는 두 줄, 실패 시 빈 문자열
334
+ */
335
+ export function buildCliPolicyContext(env = process.env, opts = {}) {
336
+ try {
337
+ const resolvePolicy = opts.resolve || resolveCliPolicy;
338
+ const policy = resolvePolicy(env, opts);
339
+ if (!policy) return "";
340
+ const line = formatCliPolicyLine(policy);
341
+ const warnings = Array.isArray(policy.warnings) ? policy.warnings : [];
342
+ if (warnings.length === 0) return line;
343
+ // 경고에 줄바꿈이 섞이면 additionalContext 에 라벨 없는 줄이 생기므로 한 줄로 접는다.
344
+ const folded = warnings.join("; ").replace(/[\r\n]+/gu, " ");
345
+ return `${line}\ncli-policy-warning: ${folded}`;
346
+ } catch {
347
+ return "";
348
+ }
349
+ }
350
+
317
351
  /**
318
352
  * BLOCKING 훅을 순차 실행. 하나라도 throw하면 로그만 남기고 계속.
319
353
  * @param {string} stdinData
@@ -388,6 +422,21 @@ async function runBlocking(stdinData) {
388
422
  );
389
423
  }
390
424
 
425
+ // 4. cli-policy: TFX_DISABLE_* SSOT 를 세션 문맥에 한 줄로 주입
426
+ try {
427
+ const t0 = performance.now();
428
+ const line = buildCliPolicyContext();
429
+ const dur = performance.now() - t0;
430
+ timings.push({ hook: "cli-policy", dur_ms: Math.round(dur) });
431
+ if (line) output.stdout += line + "\n";
432
+ log.info({ hook: "cli-policy", dur_ms: Math.round(dur) }, "hook.completed");
433
+ } catch (err) {
434
+ log.error(
435
+ { hook: "cli-policy", err: String(err.message || err) },
436
+ "hook.failed",
437
+ );
438
+ }
439
+
391
440
  return { ...output, timings };
392
441
  }
393
442
 
@@ -509,7 +558,7 @@ export async function execute(stdinData, externalHooks = []) {
509
558
  log.info(
510
559
  {
511
560
  total_ms: Math.round(totalDur),
512
- blocking_count: 3,
561
+ blocking_count: 4,
513
562
  deferred_count: 2,
514
563
  bg_count: 1,
515
564
  },
@@ -4,6 +4,8 @@
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { join, resolve } from "node:path";
6
6
 
7
+ import { resolveRoleControlSnapshot } from "../hub/lib/cto-env.mjs";
8
+
7
9
  const MAX_CONTEXT_BYTES = 2048;
8
10
  const HEADER = "[CTO NORTH STAR - READ ONLY]";
9
11
  const INSTRUCTION =
@@ -34,21 +36,6 @@ function capUtf8(input, maxBytes) {
34
36
  return output;
35
37
  }
36
38
 
37
- function ctoNorthStarEnabled() {
38
- switch (process.env.TFX_CTO_NORTH_STAR || "1") {
39
- case "0":
40
- case "false":
41
- case "FALSE":
42
- case "off":
43
- case "OFF":
44
- case "no":
45
- case "NO":
46
- return false;
47
- default:
48
- return true;
49
- }
50
- }
51
-
52
39
  function wrapBrief(brief) {
53
40
  return [HEADER, INSTRUCTION, BEGIN, brief, END].join("\n");
54
41
  }
@@ -60,7 +47,7 @@ function buildAdditionalContext(brief) {
60
47
  }
61
48
 
62
49
  function main() {
63
- if (!ctoNorthStarEnabled()) return;
50
+ if (!resolveRoleControlSnapshot().north_star_enabled) return;
64
51
 
65
52
  const input = readStdinJson();
66
53
  if (input.hook_event_name && input.hook_event_name !== "SessionStart") {
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
  },