triflux 10.3.4 → 10.7.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/LICENSE +21 -21
- package/bin/tfx-doctor-tui.mjs +1 -1
- package/bin/tfx-doctor.mjs +6 -1
- package/bin/tfx-profile.mjs +1 -1
- package/bin/tfx-setup-tui.mjs +1 -1
- package/bin/tfx-setup.mjs +6 -1
- package/bin/triflux.mjs +2396 -1140
- package/hooks/agent-route-guard.mjs +12 -8
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +19 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +108 -24
- package/hooks/hook-registry.json +11 -0
- package/hooks/keyword-rules.json +6 -10
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/pipeline-stop.mjs +3 -6
- package/hooks/safety-guard.mjs +99 -28
- package/hooks/session-start-fast.mjs +143 -0
- package/hooks/subagent-verifier.mjs +5 -4
- package/hub/account-broker.mjs +256 -60
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +95 -57
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +67 -30
- package/hub/bridge.mjs +0 -1
- package/hub/cli-adapter-base.mjs +200 -48
- package/hub/codex-adapter.mjs +76 -96
- package/hub/codex-compat.mjs +3 -3
- package/hub/codex-preflight.mjs +63 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +3 -3
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +5 -5
- package/hub/fullcycle.mjs +33 -17
- package/hub/gemini-adapter.mjs +69 -94
- package/hub/hitl.mjs +89 -30
- package/hub/intent.mjs +161 -38
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/mcp-response-cache.mjs +66 -32
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +35 -37
- package/hub/lib/process-utils.mjs +106 -37
- package/hub/lib/spawn-trace.mjs +527 -0
- package/hub/lib/ssh-command.mjs +34 -4
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +57 -48
- package/hub/promote-penalties.mjs +25 -7
- package/hub/quality/deslop.mjs +70 -49
- package/hub/research.mjs +32 -25
- package/hub/router.mjs +240 -107
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/server.mjs +4 -4
- package/hub/session-fingerprint.mjs +126 -60
- package/hub/state.mjs +84 -43
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +22 -7
- package/hub/team/ansi.mjs +186 -122
- package/hub/team/backend.mjs +28 -10
- package/hub/team/cli/commands/attach.mjs +29 -9
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +32 -11
- package/hub/team/cli/commands/focus.mjs +38 -11
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +11 -4
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -31
- package/hub/team/cli/commands/start/parse-args.mjs +38 -11
- package/hub/team/cli/commands/start/start-headless.mjs +112 -36
- package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
- package/hub/team/cli/commands/start/start-mux.mjs +70 -21
- package/hub/team/cli/commands/start/start-wt.mjs +29 -12
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +30 -8
- package/hub/team/cli/services/attach-fallback.mjs +31 -11
- package/hub/team/cli/services/hub-client.mjs +42 -14
- package/hub/team/cli/services/member-selector.mjs +11 -4
- package/hub/team/cli/services/native-control.mjs +48 -21
- package/hub/team/cli/services/runtime-mode.mjs +2 -1
- package/hub/team/cli/services/state-store.mjs +25 -8
- package/hub/team/cli/services/task-model.mjs +16 -6
- package/hub/team/conductor-mesh-bridge.mjs +24 -23
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +41 -21
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/handoff.mjs +31 -15
- package/hub/team/headless.mjs +2 -1
- package/hub/team/health-probe.mjs +69 -54
- package/hub/team/launcher-template.mjs +16 -13
- package/hub/team/native-supervisor.mjs +65 -21
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +119 -76
- package/hub/team/orchestrator.mjs +9 -4
- package/hub/team/pane.mjs +12 -7
- package/hub/team/process-cleanup.mjs +25 -16
- package/hub/team/psmux.mjs +491 -201
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +117 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +3 -1
- package/hub/team/session.mjs +98 -34
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-locks.mjs +15 -13
- package/hub/team/swarm-planner.mjs +32 -21
- package/hub/team/swarm-reconciler.mjs +48 -23
- package/hub/team/tui-lite.mjs +266 -68
- package/hub/team/tui-remote-adapter.mjs +14 -10
- package/hub/team/tui-viewer.mjs +99 -43
- package/hub/team/tui.mjs +708 -271
- package/hub/team/worktree-lifecycle.mjs +152 -58
- package/hub/team/wt-manager.mjs +24 -14
- package/hub/token-mode.mjs +71 -71
- package/hub/tray.mjs +66 -23
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-mcp.mjs +192 -141
- package/hub/workers/delegator-mcp.mjs +507 -333
- package/hub/workers/factory.mjs +8 -8
- package/hub/workers/gemini-worker.mjs +115 -84
- package/hub/workers/interface.mjs +6 -1
- package/hub/workers/worker-utils.mjs +21 -14
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +162 -26
- package/hud/context-monitor.mjs +82 -41
- package/hud/hud-qos-status.mjs +129 -49
- package/hud/mission-board.mjs +6 -3
- package/hud/providers/claude.mjs +226 -115
- package/hud/providers/codex.mjs +62 -22
- package/hud/providers/gemini.mjs +168 -56
- package/hud/renderers.mjs +384 -119
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +78 -38
- package/mesh/index.mjs +11 -5
- package/mesh/mesh-budget.mjs +18 -9
- package/mesh/mesh-heartbeat.mjs +1 -1
- package/mesh/mesh-queue.mjs +3 -5
- package/mesh/mesh-router.mjs +5 -4
- package/package.json +2 -1
- package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
- package/scripts/__tests__/keyword-detector.test.mjs +77 -28
- package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
- package/scripts/__tests__/remote-spawn.test.mjs +10 -4
- package/scripts/__tests__/session-start-fast.test.mjs +36 -0
- package/scripts/__tests__/skill-template.test.mjs +98 -50
- package/scripts/__tests__/smoke.test.mjs +1 -1
- package/scripts/__tests__/spawn-trace.test.mjs +102 -0
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
- package/scripts/cache-doctor.mjs +11 -4
- package/scripts/cache-warmup.mjs +96 -37
- package/scripts/claudemd-sync.mjs +27 -17
- package/scripts/codex-gateway-preflight.mjs +52 -37
- package/scripts/codex-mcp-gateway-sync.mjs +59 -39
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/config-audit.mjs +232 -0
- package/scripts/convert-to-tmpl.mjs +54 -0
- package/scripts/cross-review-gate.mjs +35 -12
- package/scripts/cross-review-tracker.mjs +21 -8
- package/scripts/demo.mjs +35 -17
- package/scripts/doctor-diagnose.mjs +284 -0
- package/scripts/gen-skill-docs.mjs +7 -2
- package/scripts/gen-skill-manifest.mjs +2 -1
- package/scripts/headless-guard.mjs +86 -48
- package/scripts/hub-ensure.mjs +45 -26
- package/scripts/keyword-detector.mjs +41 -20
- package/scripts/keyword-rules-expander.mjs +47 -30
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +6 -3
- package/scripts/lib/env-probe.mjs +47 -28
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/keyword-rules.mjs +43 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +377 -239
- package/scripts/lib/mcp-guard-engine.mjs +194 -79
- package/scripts/lib/mcp-manifest.mjs +23 -13
- package/scripts/lib/mcp-server-catalog.mjs +300 -63
- package/scripts/lib/psmux-info.mjs +11 -6
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-template.mjs +30 -7
- package/scripts/mcp-check.mjs +58 -39
- package/scripts/mcp-gateway-config.mjs +83 -39
- package/scripts/mcp-gateway-ensure.mjs +43 -35
- package/scripts/mcp-gateway-integration-test.mjs +70 -58
- package/scripts/mcp-gateway-start.mjs +126 -60
- package/scripts/mcp-gateway-verify.mjs +24 -22
- package/scripts/mcp-safety-guard.mjs +44 -11
- package/scripts/notion-read.mjs +199 -84
- package/scripts/pack.mjs +94 -89
- package/scripts/preflight-cache.mjs +27 -10
- package/scripts/preinstall.mjs +42 -13
- package/scripts/remote-spawn.mjs +309 -94
- package/scripts/run.cjs +8 -5
- package/scripts/session-spawn-helper.mjs +130 -39
- package/scripts/session-stale-cleanup.mjs +123 -0
- package/scripts/setup.mjs +941 -492
- package/scripts/test-lock.mjs +20 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
- package/scripts/tfx-batch-stats.mjs +32 -11
- package/scripts/tfx-gate-activate.mjs +11 -4
- package/scripts/tfx-route-post.mjs +87 -20
- package/scripts/tfx-route-worker.mjs +57 -51
- package/scripts/tfx-route.sh +41 -124
- package/scripts/tmp-cleanup.mjs +21 -7
- package/scripts/token-snapshot.mjs +204 -85
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
- package/skills/.omc/state/idle-notif-cooldown.json +3 -0
- package/skills/.omc/state/last-tool-error.json +7 -0
- package/skills/.omc/state/subagent-tracking.json +7 -0
- package/skills/_templates/base.md +1 -6
- package/skills/merge-worktree/SKILL.md.tmpl +144 -0
- package/skills/shared/telemetry-segment.md +6 -0
- package/skills/star-prompt/SKILL.md.tmpl +222 -0
- package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
- package/skills/tfx-analysis/skill.json +1 -6
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
- package/skills/tfx-auto-codex/skill.json +1 -3
- package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
- package/skills/tfx-autopilot/skill.json +1 -5
- package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
- package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
- package/skills/tfx-autoroute/skill.json +1 -7
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex/skill.json +1 -3
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
- package/skills/tfx-codex-swarm/evals/evals.json +1 -1
- package/skills/tfx-codex-swarm/skill.json +1 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
- package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
- package/skills/tfx-debate/SKILL.md.tmpl +192 -0
- package/skills/tfx-debate/skill.json +1 -7
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
- package/skills/tfx-deep-analysis/skill.json +1 -5
- package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
- package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
- package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
- package/skills/tfx-deep-qa/skill.json +1 -6
- package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
- package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
- package/skills/tfx-doctor/SKILL.md +21 -0
- package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
- package/skills/tfx-doctor/skill.json +1 -3
- package/skills/tfx-find/SKILL.md +1 -0
- package/skills/tfx-forge/SKILL.md.tmpl +187 -0
- package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
- package/skills/tfx-fullcycle/skill.json +1 -6
- package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
- package/skills/tfx-gemini/skill.json +1 -3
- package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
- package/skills/tfx-hooks/skill.json +1 -3
- package/skills/tfx-hub/SKILL.md.tmpl +212 -0
- package/skills/tfx-hub/skill.json +1 -3
- package/skills/tfx-index/SKILL.md +1 -0
- package/skills/tfx-index/skill.json +1 -6
- package/skills/tfx-interview/SKILL.md.tmpl +285 -0
- package/skills/tfx-multi/SKILL.md.tmpl +183 -0
- package/skills/tfx-multi/skill.json +1 -3
- package/skills/tfx-panel/SKILL.md.tmpl +189 -0
- package/skills/tfx-panel/skill.json +1 -7
- package/skills/tfx-persist/SKILL.md.tmpl +270 -0
- package/skills/tfx-persist/skill.json +1 -7
- package/skills/tfx-plan/SKILL.md +1 -0
- package/skills/tfx-plan/skill.json +1 -6
- package/skills/tfx-profile/SKILL.md.tmpl +239 -0
- package/skills/tfx-profile/skill.json +1 -3
- package/skills/tfx-prune/SKILL.md.tmpl +200 -0
- package/skills/tfx-prune/skill.json +1 -7
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
- package/skills/tfx-psmux-rules/skill.json +1 -4
- package/skills/tfx-qa/SKILL.md +1 -0
- package/skills/tfx-qa/skill.json +1 -6
- package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
- package/skills/tfx-ralph/skill.json +1 -4
- package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
- package/skills/tfx-remote-setup/skill.json +1 -3
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
- package/skills/tfx-remote-spawn/references/hosts.json +16 -0
- package/skills/tfx-remote-spawn/skill.json +1 -4
- package/skills/tfx-research/SKILL.md +1 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-review/skill.json +1 -6
- package/skills/tfx-setup/SKILL.md.tmpl +504 -0
- package/skills/tfx-setup/skill.json +1 -3
- package/skills/tfx-swarm/SKILL.md +22 -0
- package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
- package/tui/codex-profile.mjs +88 -33
- package/tui/core.mjs +45 -15
- package/tui/doctor.mjs +75 -28
- package/tui/gemini-profile.mjs +74 -29
- package/tui/monitor-data.mjs +8 -4
- package/tui/monitor.mjs +71 -27
- package/tui/setup.mjs +133 -42
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import * as childProcess from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { createWriteStream, mkdirSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { basename, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const LOG_DIR = join(homedir(), ".triflux", "logs");
|
|
8
|
+
const DEDUPE_WINDOW_MS = 5_000;
|
|
9
|
+
const RATE_WINDOW_MS = 1_000;
|
|
10
|
+
const WINDOWS_TERMINAL_COMMANDS = new Set([
|
|
11
|
+
"wt",
|
|
12
|
+
"wt.exe",
|
|
13
|
+
"windowsterminal.exe",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export const MAX_WT_TABS = 8;
|
|
17
|
+
export const MAX_SPAWN_PER_SEC = resolvePositiveInteger(
|
|
18
|
+
process.env.TRIFLUX_MAX_SPAWN_RATE,
|
|
19
|
+
10,
|
|
20
|
+
);
|
|
21
|
+
export const MAX_TOTAL_DESCENDANTS = resolvePositiveInteger(
|
|
22
|
+
process.env.TRIFLUX_MAX_DESCENDANTS,
|
|
23
|
+
50,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
let logDay = "";
|
|
27
|
+
let logStream = null;
|
|
28
|
+
let traceSequence = 0;
|
|
29
|
+
|
|
30
|
+
const recentSpawnTimes = [];
|
|
31
|
+
const dedupeEntries = new Map();
|
|
32
|
+
const activeChildren = new Map();
|
|
33
|
+
const activeWtChildren = new Set();
|
|
34
|
+
|
|
35
|
+
function resolvePositiveInteger(...values) {
|
|
36
|
+
for (const value of values) {
|
|
37
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
38
|
+
if (Number.isInteger(parsed) && parsed > 0) {
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function nowIso() {
|
|
47
|
+
return new Date().toISOString();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getLogPath(day = nowIso().slice(0, 10)) {
|
|
51
|
+
return join(LOG_DIR, `spawn-trace-${day}.jsonl`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ensureLogStream() {
|
|
55
|
+
const day = nowIso().slice(0, 10);
|
|
56
|
+
if (logStream && logDay === day) {
|
|
57
|
+
return logStream;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
61
|
+
|
|
62
|
+
if (logStream) {
|
|
63
|
+
try {
|
|
64
|
+
logStream.end();
|
|
65
|
+
} catch {
|
|
66
|
+
/* ignore */
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
logDay = day;
|
|
71
|
+
logStream = createWriteStream(getLogPath(day), { flags: "a" });
|
|
72
|
+
logStream.on("error", () => {
|
|
73
|
+
/* ignore logging failures */
|
|
74
|
+
});
|
|
75
|
+
return logStream;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function appendTrace(data, { sync = false } = {}) {
|
|
79
|
+
const entry = {
|
|
80
|
+
ts: nowIso(),
|
|
81
|
+
session_id: process.env.TRIFLUX_SESSION_ID ?? null,
|
|
82
|
+
parent_pid: process.pid,
|
|
83
|
+
...data,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
ensureLogStream().write(`${JSON.stringify(entry)}\n`);
|
|
88
|
+
} catch {
|
|
89
|
+
if (!sync) {
|
|
90
|
+
/* ignore logging failures */
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function nextTraceId() {
|
|
96
|
+
traceSequence += 1;
|
|
97
|
+
return `spawn-trace-${Date.now()}-${traceSequence}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function trimRecentSpawnTimes(now) {
|
|
101
|
+
while (
|
|
102
|
+
recentSpawnTimes.length > 0 &&
|
|
103
|
+
now - recentSpawnTimes[0] >= RATE_WINDOW_MS
|
|
104
|
+
) {
|
|
105
|
+
recentSpawnTimes.shift();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function trimDedupeEntries(now) {
|
|
110
|
+
for (const [key, ts] of dedupeEntries.entries()) {
|
|
111
|
+
if (now - ts >= DEDUPE_WINDOW_MS) {
|
|
112
|
+
dedupeEntries.delete(key);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function stripTraceOptions(options) {
|
|
118
|
+
if (!options || typeof options !== "object") {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const { reason: _reason, dedupe: _dedupe, ...rest } = options;
|
|
123
|
+
return rest;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getReason(options) {
|
|
127
|
+
if (!options || typeof options !== "object") {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return options.reason ?? null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getDedupeKey(options) {
|
|
135
|
+
if (!options || typeof options !== "object") {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return typeof options.dedupe === "string" && options.dedupe.trim()
|
|
140
|
+
? options.dedupe.trim()
|
|
141
|
+
: null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getCwd(options) {
|
|
145
|
+
if (!options || typeof options !== "object") {
|
|
146
|
+
return process.cwd();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return options.cwd || process.cwd();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getCommandBasename(command) {
|
|
153
|
+
return basename(String(command || "")).toLowerCase();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isWindowsTerminalSpawn(command) {
|
|
157
|
+
return WINDOWS_TERMINAL_COMMANDS.has(getCommandBasename(command));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function createPolicyError(reasonCode, message, meta = {}) {
|
|
161
|
+
const error = new Error(message);
|
|
162
|
+
error.code = "TRIFLUX_SPAWN_BLOCKED";
|
|
163
|
+
error.reasonCode = reasonCode;
|
|
164
|
+
Object.assign(error, meta);
|
|
165
|
+
return error;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function logBlocked(traceId, command, args, options, error, extra = {}) {
|
|
169
|
+
appendTrace({
|
|
170
|
+
event: "blocked",
|
|
171
|
+
trace_id: traceId,
|
|
172
|
+
command,
|
|
173
|
+
args,
|
|
174
|
+
cwd: getCwd(options),
|
|
175
|
+
reason: getReason(options),
|
|
176
|
+
warning: error.message,
|
|
177
|
+
warning_code: error.reasonCode || error.code || "unknown",
|
|
178
|
+
...extra,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function enforceGuards(command, args, options) {
|
|
183
|
+
const now = Date.now();
|
|
184
|
+
trimRecentSpawnTimes(now);
|
|
185
|
+
trimDedupeEntries(now);
|
|
186
|
+
|
|
187
|
+
const dedupeKey = getDedupeKey(options);
|
|
188
|
+
if (dedupeKey) {
|
|
189
|
+
const lastSeenAt = dedupeEntries.get(dedupeKey);
|
|
190
|
+
if (lastSeenAt != null && now - lastSeenAt < DEDUPE_WINDOW_MS) {
|
|
191
|
+
return createPolicyError(
|
|
192
|
+
"dedupe",
|
|
193
|
+
`spawn-trace dedupe blocked for key "${dedupeKey}"`,
|
|
194
|
+
{ dedupeKey },
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (recentSpawnTimes.length >= MAX_SPAWN_PER_SEC) {
|
|
200
|
+
return createPolicyError(
|
|
201
|
+
"rate_limit",
|
|
202
|
+
`spawn-trace rate limit exceeded (${MAX_SPAWN_PER_SEC}/sec)`,
|
|
203
|
+
{ maxPerSec: MAX_SPAWN_PER_SEC },
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (activeChildren.size >= MAX_TOTAL_DESCENDANTS) {
|
|
208
|
+
return createPolicyError(
|
|
209
|
+
"max_descendants",
|
|
210
|
+
`spawn-trace max descendants exceeded (${MAX_TOTAL_DESCENDANTS})`,
|
|
211
|
+
{ maxDescendants: MAX_TOTAL_DESCENDANTS },
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (isWindowsTerminalSpawn(command) && activeWtChildren.size >= MAX_WT_TABS) {
|
|
216
|
+
return createPolicyError(
|
|
217
|
+
"wt_tab_cap",
|
|
218
|
+
`spawn-trace Windows Terminal cap exceeded (${MAX_WT_TABS})`,
|
|
219
|
+
{ maxWtTabs: MAX_WT_TABS },
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
recentSpawnTimes.push(now);
|
|
224
|
+
if (dedupeKey) {
|
|
225
|
+
dedupeEntries.set(dedupeKey, now);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function trackChild(child, meta) {
|
|
232
|
+
if (!child || typeof child.once !== "function") {
|
|
233
|
+
return child;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
activeChildren.set(child, meta);
|
|
237
|
+
if (meta.isWindowsTerminalSpawn) {
|
|
238
|
+
activeWtChildren.add(child);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let finalized = false;
|
|
242
|
+
const finalize = (event, payload = {}) => {
|
|
243
|
+
if (finalized) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
finalized = true;
|
|
248
|
+
activeChildren.delete(child);
|
|
249
|
+
activeWtChildren.delete(child);
|
|
250
|
+
|
|
251
|
+
appendTrace({
|
|
252
|
+
event,
|
|
253
|
+
trace_id: meta.traceId,
|
|
254
|
+
command: meta.command,
|
|
255
|
+
args: meta.args,
|
|
256
|
+
cwd: meta.cwd,
|
|
257
|
+
reason: meta.reason,
|
|
258
|
+
child_pid: child.pid ?? null,
|
|
259
|
+
duration_ms: Date.now() - meta.startedAt,
|
|
260
|
+
...payload,
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
child.once("exit", (code, signal) => {
|
|
265
|
+
finalize("exit", {
|
|
266
|
+
exit_code: code,
|
|
267
|
+
signal: signal ?? null,
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
child.once("error", (error) => {
|
|
272
|
+
finalize("error", {
|
|
273
|
+
error: error.message,
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return child;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function createRejectedChild(command, args, error) {
|
|
281
|
+
const child = new EventEmitter();
|
|
282
|
+
child.pid = undefined;
|
|
283
|
+
child.stdin = null;
|
|
284
|
+
child.stdout = null;
|
|
285
|
+
child.stderr = null;
|
|
286
|
+
child.stdio = [null, null, null];
|
|
287
|
+
child.spawnfile = String(command);
|
|
288
|
+
child.spawnargs = [String(command), ...args.map((arg) => String(arg))];
|
|
289
|
+
child.kill = () => false;
|
|
290
|
+
child.killed = false;
|
|
291
|
+
child.connected = false;
|
|
292
|
+
child.exitCode = 1;
|
|
293
|
+
child.signalCode = null;
|
|
294
|
+
|
|
295
|
+
queueMicrotask(() => {
|
|
296
|
+
child.emit("error", error);
|
|
297
|
+
child.emit("exit", 1, null);
|
|
298
|
+
child.emit("close", 1, null);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return child;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function normalizeSpawnArgs(args, options) {
|
|
305
|
+
if (Array.isArray(args)) {
|
|
306
|
+
return {
|
|
307
|
+
argsList: [...args],
|
|
308
|
+
options,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
argsList: [],
|
|
314
|
+
options: args,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function normalizeExecFileArgs(args, options, callback) {
|
|
319
|
+
let argsList = [];
|
|
320
|
+
let normalizedOptions;
|
|
321
|
+
let normalizedCallback;
|
|
322
|
+
|
|
323
|
+
if (typeof args === "function") {
|
|
324
|
+
normalizedCallback = args;
|
|
325
|
+
} else if (Array.isArray(args)) {
|
|
326
|
+
argsList = [...args];
|
|
327
|
+
if (typeof options === "function") {
|
|
328
|
+
normalizedCallback = options;
|
|
329
|
+
} else {
|
|
330
|
+
normalizedOptions = options;
|
|
331
|
+
if (typeof callback === "function") {
|
|
332
|
+
normalizedCallback = callback;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
normalizedOptions = args;
|
|
337
|
+
if (typeof options === "function") {
|
|
338
|
+
normalizedCallback = options;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
argsList,
|
|
344
|
+
options: normalizedOptions,
|
|
345
|
+
callback: normalizedCallback,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function spawn(command, args, options) {
|
|
350
|
+
const { argsList, options: normalizedOptions } = normalizeSpawnArgs(
|
|
351
|
+
args,
|
|
352
|
+
options,
|
|
353
|
+
);
|
|
354
|
+
const traceId = nextTraceId();
|
|
355
|
+
const blockedError = enforceGuards(command, argsList, normalizedOptions);
|
|
356
|
+
if (blockedError) {
|
|
357
|
+
logBlocked(traceId, command, argsList, normalizedOptions, blockedError);
|
|
358
|
+
throw blockedError;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const startedAt = Date.now();
|
|
362
|
+
const child = childProcess.spawn(
|
|
363
|
+
command,
|
|
364
|
+
argsList,
|
|
365
|
+
stripTraceOptions(normalizedOptions),
|
|
366
|
+
);
|
|
367
|
+
appendTrace({
|
|
368
|
+
event: "spawn",
|
|
369
|
+
trace_id: traceId,
|
|
370
|
+
command,
|
|
371
|
+
args: argsList,
|
|
372
|
+
cwd: getCwd(normalizedOptions),
|
|
373
|
+
reason: getReason(normalizedOptions),
|
|
374
|
+
child_pid: child.pid ?? null,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return trackChild(child, {
|
|
378
|
+
traceId,
|
|
379
|
+
startedAt,
|
|
380
|
+
command,
|
|
381
|
+
args: argsList,
|
|
382
|
+
cwd: getCwd(normalizedOptions),
|
|
383
|
+
reason: getReason(normalizedOptions),
|
|
384
|
+
isWindowsTerminalSpawn: isWindowsTerminalSpawn(command),
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function execFile(file, args, options, callback) {
|
|
389
|
+
const normalized = normalizeExecFileArgs(args, options, callback);
|
|
390
|
+
const traceId = nextTraceId();
|
|
391
|
+
const blockedError = enforceGuards(
|
|
392
|
+
file,
|
|
393
|
+
normalized.argsList,
|
|
394
|
+
normalized.options,
|
|
395
|
+
);
|
|
396
|
+
if (blockedError) {
|
|
397
|
+
logBlocked(traceId, file, normalized.argsList, normalized.options, blockedError);
|
|
398
|
+
if (typeof normalized.callback === "function") {
|
|
399
|
+
queueMicrotask(() => normalized.callback(blockedError, "", ""));
|
|
400
|
+
return createRejectedChild(file, normalized.argsList, blockedError);
|
|
401
|
+
}
|
|
402
|
+
throw blockedError;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const startedAt = Date.now();
|
|
406
|
+
const wrappedCallback =
|
|
407
|
+
typeof normalized.callback === "function"
|
|
408
|
+
? (error, stdout, stderr) => normalized.callback(error, stdout, stderr)
|
|
409
|
+
: undefined;
|
|
410
|
+
|
|
411
|
+
const child = childProcess.execFile(
|
|
412
|
+
file,
|
|
413
|
+
normalized.argsList,
|
|
414
|
+
stripTraceOptions(normalized.options),
|
|
415
|
+
wrappedCallback,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
appendTrace({
|
|
419
|
+
event: "spawn",
|
|
420
|
+
trace_id: traceId,
|
|
421
|
+
command: file,
|
|
422
|
+
args: normalized.argsList,
|
|
423
|
+
cwd: getCwd(normalized.options),
|
|
424
|
+
reason: getReason(normalized.options),
|
|
425
|
+
child_pid: child.pid ?? null,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
return trackChild(child, {
|
|
429
|
+
traceId,
|
|
430
|
+
startedAt,
|
|
431
|
+
command: file,
|
|
432
|
+
args: normalized.argsList,
|
|
433
|
+
cwd: getCwd(normalized.options),
|
|
434
|
+
reason: getReason(normalized.options),
|
|
435
|
+
isWindowsTerminalSpawn: isWindowsTerminalSpawn(file),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function execFileSync(file, args, options) {
|
|
440
|
+
const normalized = Array.isArray(args)
|
|
441
|
+
? { argsList: [...args], options }
|
|
442
|
+
: { argsList: [], options: args };
|
|
443
|
+
const traceId = nextTraceId();
|
|
444
|
+
const blockedError = enforceGuards(
|
|
445
|
+
file,
|
|
446
|
+
normalized.argsList,
|
|
447
|
+
normalized.options,
|
|
448
|
+
);
|
|
449
|
+
if (blockedError) {
|
|
450
|
+
logBlocked(traceId, file, normalized.argsList, normalized.options, blockedError, {
|
|
451
|
+
sync: true,
|
|
452
|
+
});
|
|
453
|
+
throw blockedError;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const startedAt = Date.now();
|
|
457
|
+
appendTrace(
|
|
458
|
+
{
|
|
459
|
+
event: "spawn",
|
|
460
|
+
trace_id: traceId,
|
|
461
|
+
command: file,
|
|
462
|
+
args: normalized.argsList,
|
|
463
|
+
cwd: getCwd(normalized.options),
|
|
464
|
+
reason: getReason(normalized.options),
|
|
465
|
+
sync: true,
|
|
466
|
+
},
|
|
467
|
+
{ sync: true },
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
const result = childProcess.execFileSync(
|
|
472
|
+
file,
|
|
473
|
+
normalized.argsList,
|
|
474
|
+
stripTraceOptions(normalized.options),
|
|
475
|
+
);
|
|
476
|
+
appendTrace(
|
|
477
|
+
{
|
|
478
|
+
event: "exit",
|
|
479
|
+
trace_id: traceId,
|
|
480
|
+
command: file,
|
|
481
|
+
args: normalized.argsList,
|
|
482
|
+
cwd: getCwd(normalized.options),
|
|
483
|
+
reason: getReason(normalized.options),
|
|
484
|
+
exit_code: 0,
|
|
485
|
+
duration_ms: Date.now() - startedAt,
|
|
486
|
+
sync: true,
|
|
487
|
+
},
|
|
488
|
+
{ sync: true },
|
|
489
|
+
);
|
|
490
|
+
return result;
|
|
491
|
+
} catch (error) {
|
|
492
|
+
appendTrace(
|
|
493
|
+
{
|
|
494
|
+
event: "exit",
|
|
495
|
+
trace_id: traceId,
|
|
496
|
+
command: file,
|
|
497
|
+
args: normalized.argsList,
|
|
498
|
+
cwd: getCwd(normalized.options),
|
|
499
|
+
reason: getReason(normalized.options),
|
|
500
|
+
exit_code: error?.status ?? null,
|
|
501
|
+
signal: error?.signal ?? null,
|
|
502
|
+
duration_ms: Date.now() - startedAt,
|
|
503
|
+
error: error?.message,
|
|
504
|
+
sync: true,
|
|
505
|
+
},
|
|
506
|
+
{ sync: true },
|
|
507
|
+
);
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export const ChildProcess = childProcess.ChildProcess;
|
|
513
|
+
export const _forkChild = childProcess._forkChild;
|
|
514
|
+
export const exec = childProcess.exec;
|
|
515
|
+
export const execSync = childProcess.execSync;
|
|
516
|
+
export const fork = childProcess.fork;
|
|
517
|
+
export const spawnSync = childProcess.spawnSync;
|
|
518
|
+
|
|
519
|
+
export default {
|
|
520
|
+
...childProcess,
|
|
521
|
+
spawn,
|
|
522
|
+
execFile,
|
|
523
|
+
execFileSync,
|
|
524
|
+
MAX_WT_TABS,
|
|
525
|
+
MAX_SPAWN_PER_SEC,
|
|
526
|
+
MAX_TOTAL_DESCENDANTS,
|
|
527
|
+
};
|
package/hub/lib/ssh-command.mjs
CHANGED
|
@@ -112,8 +112,10 @@ export function buildSshArgs(host, remoteCmd, opts = {}) {
|
|
|
112
112
|
const remoteCmdStr = [cmd, ...quotedArgs].join(" ");
|
|
113
113
|
|
|
114
114
|
const sshArgs = [
|
|
115
|
-
"-o",
|
|
116
|
-
|
|
115
|
+
"-o",
|
|
116
|
+
`ConnectTimeout=${connectTimeout}`,
|
|
117
|
+
"-o",
|
|
118
|
+
"BatchMode=yes",
|
|
117
119
|
];
|
|
118
120
|
|
|
119
121
|
if (keepalive) {
|
|
@@ -189,9 +191,10 @@ export function resolveHostAlias(alias, repoRoot) {
|
|
|
189
191
|
|
|
190
192
|
/**
|
|
191
193
|
* 특정 capability를 보유한 호스트 목록을 반환한다.
|
|
194
|
+
* 명시적 선택 UI에서 바로 리소스 정보를 보여줄 수 있도록 specs를 평탄화한다.
|
|
192
195
|
* @param {string} capability — e.g. "codex", "claude", "gpu"
|
|
193
196
|
* @param {string} [repoRoot]
|
|
194
|
-
* @returns {Array<{ name: string, config: object }>}
|
|
197
|
+
* @returns {Array<{ name: string, config: object, specs: { cores?: number, ram_gb?: number } }>}
|
|
195
198
|
*/
|
|
196
199
|
export function selectHostForCapability(capability, repoRoot) {
|
|
197
200
|
loadHostsCache(repoRoot);
|
|
@@ -199,12 +202,39 @@ export function selectHostForCapability(capability, repoRoot) {
|
|
|
199
202
|
const matches = [];
|
|
200
203
|
for (const [name, cfg] of Object.entries(hostsCache.hosts || {})) {
|
|
201
204
|
if (cfg.capabilities?.includes(capability)) {
|
|
202
|
-
matches.push({
|
|
205
|
+
matches.push({
|
|
206
|
+
name,
|
|
207
|
+
config: cfg,
|
|
208
|
+
specs: {
|
|
209
|
+
cores: cfg.specs?.cores,
|
|
210
|
+
ram_gb: cfg.specs?.ram_gb,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
203
213
|
}
|
|
204
214
|
}
|
|
205
215
|
return matches;
|
|
206
216
|
}
|
|
207
217
|
|
|
218
|
+
/**
|
|
219
|
+
* 특정 capability를 가진 호스트를 리소스 기준으로 정렬해 반환한다.
|
|
220
|
+
* 자동 선택은 하지 않고, 명시적 선택 목록 정렬에만 사용한다.
|
|
221
|
+
* @param {string} capability — e.g. "codex", "claude", "gpu"
|
|
222
|
+
* @param {"cores"|"ram_gb"} sortBy
|
|
223
|
+
* @param {string} [repoRoot]
|
|
224
|
+
* @returns {Array<{ name: string, config: object, specs: { cores?: number, ram_gb?: number } }>}
|
|
225
|
+
*/
|
|
226
|
+
export function selectHostByResources(capability, sortBy, repoRoot) {
|
|
227
|
+
if (sortBy !== "cores" && sortBy !== "ram_gb") {
|
|
228
|
+
throw new TypeError('sortBy must be "cores" or "ram_gb"');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return selectHostForCapability(capability, repoRoot).sort((a, b) => {
|
|
232
|
+
const aValue = a.specs?.[sortBy] ?? -Infinity;
|
|
233
|
+
const bValue = b.specs?.[sortBy] ?? -Infinity;
|
|
234
|
+
return bValue - aValue;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
208
238
|
/** hosts.json 캐시 초기화 (테스트용) */
|
|
209
239
|
export function resetHostsCache() {
|
|
210
240
|
hostsCache = null;
|
package/hub/lib/ssh-retry.mjs
CHANGED
|
@@ -21,7 +21,11 @@ const TRANSIENT_RE =
|
|
|
21
21
|
* @returns {string|Buffer} ssh stdout
|
|
22
22
|
*/
|
|
23
23
|
export function execSshWithRetry(args, opts = {}) {
|
|
24
|
-
const {
|
|
24
|
+
const {
|
|
25
|
+
maxRetries = DEFAULT_MAX_RETRIES,
|
|
26
|
+
baseDelayMs = DEFAULT_BASE_DELAY_MS,
|
|
27
|
+
...execOpts
|
|
28
|
+
} = opts;
|
|
25
29
|
|
|
26
30
|
let lastError;
|
|
27
31
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
package/hub/lib/uuidv7.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// hub/lib/uuidv7.mjs — UUIDv7 generator (RFC 9562, monotonic)
|
|
2
|
-
import { randomBytes } from
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
3
|
|
|
4
|
-
let _rndPool = Buffer.alloc(0),
|
|
4
|
+
let _rndPool = Buffer.alloc(0),
|
|
5
|
+
_rndOff = 0;
|
|
5
6
|
|
|
6
7
|
function pooledRandom(n) {
|
|
7
8
|
if (_rndOff + n > _rndPool.length) {
|
|
@@ -39,6 +40,6 @@ export function uuidv7() {
|
|
|
39
40
|
buf[6] = ((_seq >> 8) & 0x0f) | 0x70;
|
|
40
41
|
buf[7] = _seq & 0xff;
|
|
41
42
|
buf[8] = (buf[8] & 0x3f) | 0x80;
|
|
42
|
-
const h = buf.toString(
|
|
43
|
+
const h = buf.toString("hex");
|
|
43
44
|
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
44
45
|
}
|