@triflux/core 10.31.0 → 10.33.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.
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { argv, exit, stdin, stdout } from "node:process";
4
+ import { pathToFileURL } from "node:url";
5
+ import { drainPendingSynapse as defaultDrainPendingSynapse } from "../hub/team/synapse-http.mjs";
6
+ import {
7
+ heartbeatInteractiveSession as defaultHeartbeatInteractiveSession,
8
+ registerInteractiveSession as defaultRegisterInteractiveSession,
9
+ } from "./session-start-fast.mjs";
10
+
11
+ // hub-ensure is loaded lazily so the byte-identical packages/core mirror of this
12
+ // file loads cleanly. packages/core mirrors scripts/lib only (not scripts/*), so a
13
+ // static `../scripts/hub-ensure.mjs` import would make the core copy throw
14
+ // ERR_MODULE_NOT_FOUND at load time. The core copy is published-but-dormant — the
15
+ // live codex hook always runs from the installed triflux package where the path
16
+ // resolves. This mirrors session-start-fast.mjs's dynamic script-import pattern.
17
+ async function defaultHubEnsureRun(stdinData) {
18
+ const { run } = await import(
19
+ new URL("../scripts/hub-ensure.mjs", import.meta.url).href
20
+ );
21
+ return run(stdinData);
22
+ }
23
+
24
+ function parsePayload(stdinData) {
25
+ try {
26
+ const raw = typeof stdinData === "string" ? stdinData : "";
27
+ return raw.trim()
28
+ ? { ok: true, payload: JSON.parse(raw) }
29
+ : { ok: false, payload: {} };
30
+ } catch {
31
+ return { ok: false, payload: {} };
32
+ }
33
+ }
34
+
35
+ function normalizeMode(mode, payload) {
36
+ const direct = String(mode || "")
37
+ .trim()
38
+ .toLowerCase();
39
+ if (direct === "register" || direct === "heartbeat") return direct;
40
+
41
+ const eventName = String(payload?.hook_event_name || "")
42
+ .trim()
43
+ .toLowerCase()
44
+ .replace(/-/g, "_");
45
+ if (eventName === "session_start" || eventName === "sessionstart") {
46
+ return "register";
47
+ }
48
+ if (eventName === "user_prompt_submit" || eventName === "userpromptsubmit") {
49
+ return "heartbeat";
50
+ }
51
+ return "";
52
+ }
53
+
54
+ export async function runCodexSessionHook(stdinData, opts = {}) {
55
+ const output = "{}\n";
56
+ const parsed = parsePayload(stdinData);
57
+ const mode = parsed.ok
58
+ ? normalizeMode(opts.argvMode ?? argv[2], parsed.payload)
59
+ : "";
60
+ const hubEnsureRun = opts.hubEnsureRun || defaultHubEnsureRun;
61
+ const registerInteractiveSession =
62
+ opts.registerInteractiveSession || defaultRegisterInteractiveSession;
63
+ const heartbeatInteractiveSession =
64
+ opts.heartbeatInteractiveSession || defaultHeartbeatInteractiveSession;
65
+ const drainPendingSynapse =
66
+ opts.drainPendingSynapse || defaultDrainPendingSynapse;
67
+
68
+ try {
69
+ if (mode === "register") {
70
+ try {
71
+ await hubEnsureRun(stdinData);
72
+ } catch {}
73
+ try {
74
+ registerInteractiveSession(stdinData);
75
+ } catch {}
76
+ try {
77
+ await drainPendingSynapse(1000);
78
+ } catch {}
79
+ } else if (mode === "heartbeat") {
80
+ try {
81
+ heartbeatInteractiveSession(stdinData);
82
+ } catch {}
83
+ try {
84
+ await drainPendingSynapse(500);
85
+ } catch {}
86
+ }
87
+ } catch {
88
+ // Codex session hooks are observational and must never block the session.
89
+ }
90
+
91
+ if (opts.writeStdout !== false) {
92
+ stdout.write(output);
93
+ }
94
+ return output;
95
+ }
96
+
97
+ function readStdin() {
98
+ return new Promise((resolve) => {
99
+ let data = "";
100
+ stdin.setEncoding("utf8");
101
+ stdin.on("data", (chunk) => {
102
+ data += chunk;
103
+ });
104
+ stdin.on("end", () => resolve(data));
105
+ stdin.on("error", () => resolve(data));
106
+ });
107
+ }
108
+
109
+ if (argv[1] && import.meta.url === pathToFileURL(argv[1]).href) {
110
+ const stdinData = await readStdin();
111
+ await runCodexSessionHook(stdinData);
112
+ exit(0);
113
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triflux/core",
3
- "version": "10.31.0",
3
+ "version": "10.33.0",
4
4
  "description": "triflux core — CLI routing, pipeline, adapters. Zero native dependencies.",
5
5
  "type": "module",
6
6
  "main": "hub/index.mjs",
@@ -4,6 +4,7 @@ import { existsSync, readFileSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
+ import { resolveHubPortForContext } from "../../hub/hub-lifecycle.mjs";
7
8
  import { whichCommand, whichCommandAsync } from "../../hub/platform.mjs";
8
9
 
9
10
  const HUB_DEFAULT_PORT = 27888;
@@ -37,13 +38,36 @@ function fetchHubStatus({
37
38
  };
38
39
  }
39
40
 
40
- export function resolveDefaultStatusUrl(env = process.env) {
41
- const envPort = Number.parseInt(String(env?.TFX_HUB_PORT ?? ""), 10);
42
- const port =
43
- Number.isFinite(envPort) && envPort > 0 ? envPort : HUB_DEFAULT_PORT;
41
+ export function resolveDefaultStatusUrl(env = process.env, cwd = process.cwd()) {
42
+ const port = resolveHubPortForContext({
43
+ env,
44
+ cwd,
45
+ defaultPort: HUB_DEFAULT_PORT,
46
+ });
44
47
  return `http://127.0.0.1:${port}/status`;
45
48
  }
46
49
 
50
+ function resolveStatusUrlForContext({
51
+ statusUrl,
52
+ env = process.env,
53
+ cwd = process.cwd(),
54
+ } = {}) {
55
+ try {
56
+ const url = new URL(String(statusUrl));
57
+ url.port = String(
58
+ resolveHubPortForContext({
59
+ port: url.port,
60
+ env,
61
+ cwd,
62
+ defaultPort: HUB_DEFAULT_PORT,
63
+ }),
64
+ );
65
+ return url.toString();
66
+ } catch {
67
+ return resolveDefaultStatusUrl(env, cwd);
68
+ }
69
+ }
70
+
47
71
  function normalizeCliName(name) {
48
72
  return String(name ?? "").trim() || null;
49
73
  }
@@ -212,8 +236,10 @@ export function detectCodexPlan(options = {}) {
212
236
  }
213
237
 
214
238
  export function checkHub({
239
+ env = process.env,
240
+ cwd = process.cwd(),
215
241
  pkgRoot = DEFAULT_PKG_ROOT,
216
- statusUrl = resolveDefaultStatusUrl(),
242
+ statusUrl = resolveDefaultStatusUrl(env, cwd),
217
243
  restart = true,
218
244
  requestTimeoutMs = 3000,
219
245
  pollAttempts = 8,
@@ -223,10 +249,11 @@ export function checkHub({
223
249
  existsSyncFn = existsSync,
224
250
  sleepSyncFn = sleepSync,
225
251
  } = {}) {
252
+ const guardedStatusUrl = resolveStatusUrlForContext({ statusUrl, env, cwd });
226
253
  try {
227
254
  return fetchHubStatus({
228
255
  execSyncFn,
229
- statusUrl,
256
+ statusUrl: guardedStatusUrl,
230
257
  timeout: requestTimeoutMs,
231
258
  });
232
259
  } catch {}
@@ -239,7 +266,7 @@ export function checkHub({
239
266
 
240
267
  try {
241
268
  const child = spawnFn(process.execPath, [serverPath], {
242
- env: { ...process.env, TFX_HUB_PORT: String(new URL(statusUrl).port) },
269
+ env: { ...env, TFX_HUB_PORT: String(new URL(guardedStatusUrl).port) },
243
270
  detached: true,
244
271
  stdio: "ignore",
245
272
  windowsHide: true,
@@ -254,7 +281,7 @@ export function checkHub({
254
281
  try {
255
282
  const status = fetchHubStatus({
256
283
  execSyncFn,
257
- statusUrl,
284
+ statusUrl: guardedStatusUrl,
258
285
  timeout: Math.min(requestTimeoutMs, 1000),
259
286
  });
260
287
  if (status.state === "healthy") {