@triflux/core 10.43.0 → 10.45.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/hooks/codex-session-hook.mjs +67 -1
- package/hooks/cto-north-star-brief.mjs +3 -16
- package/hooks/error-context.mjs +3 -6
- package/hooks/hook-manager.mjs +1 -1
- package/hooks/hook-registry.json +0 -11
- package/hooks/keyword-rules.json +147 -23
- package/hooks/permission-safe-allow.mjs +36 -2
- package/hooks/session-start-fast.mjs +50 -1
- package/hooks/session-start-lake.mjs +3 -16
- package/hub/bridge.mjs +111 -5
- package/hub/cli-adapter-base.mjs +11 -2
- package/hub/codex-adapter.mjs +3 -0
- package/hub/gemini-adapter.mjs +29 -11
- package/hub/intent.mjs +24 -32
- package/hub/lib/memory-store.mjs +66 -30
- package/hub/lib/tfx-route-args.mjs +41 -4
- package/hub/reflexion.mjs +0 -57
- package/hub/router.mjs +474 -24
- package/hub/team/claude-daemon-control.mjs +2 -0
- package/hub/team/intervention.mjs +1 -1
- package/hub/team/retry-state-machine.mjs +41 -13
- package/hub/team/synapse-http.mjs +149 -4
- package/hud/cli-policy.mjs +69 -0
- package/hud/constants.mjs +20 -17
- package/hud/context-monitor.mjs +31 -21
- package/hud/hud-qos-status.mjs +81 -91
- package/hud/providers/claude.mjs +150 -84
- package/hud/providers/codex.mjs +10 -1
- package/hud/renderers.mjs +21 -59
- package/hud/utils.mjs +1 -15
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +367 -0
- package/scripts/lib/cli-agy.mjs +24 -4
- package/scripts/lib/cli-codex.mjs +22 -160
- package/scripts/lib/codex-profile-config.mjs +48 -4
- package/scripts/lib/gemini-profiles.mjs +113 -29
- package/scripts/lib/keyword-rules.mjs +24 -0
- package/scripts/lib/logger.mjs +60 -43
- package/scripts/lib/machine-profile.mjs +293 -0
- package/scripts/lib/mcp-health.mjs +44 -18
- package/hub/log-retention.mjs +0 -726
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:
|
|
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;
|
package/hub/cli-adapter-base.mjs
CHANGED
|
@@ -167,7 +167,7 @@ export const CODEX_MCP_EXECUTION_EXIT_CODE = 1;
|
|
|
167
167
|
*
|
|
168
168
|
* @param {string} prompt
|
|
169
169
|
* @param {string|null} resultFile — null이면 --output-last-message 생략
|
|
170
|
-
* @param {{ profile?: string, skipGitRepoCheck?: boolean, sandboxBypass?: boolean, cwd?: string, mcpServers?: string[], stdinPrompt?: boolean }} [opts]
|
|
170
|
+
* @param {{ profile?: string, codexHome?: string, disallowUltra?: boolean, enforceCanonicalProfile?: boolean, skipGitRepoCheck?: boolean, sandboxBypass?: boolean, cwd?: string, mcpServers?: string[], stdinPrompt?: boolean }} [opts]
|
|
171
171
|
* @returns {string} 실행할 셸 커맨드
|
|
172
172
|
*/
|
|
173
173
|
export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
@@ -177,6 +177,9 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
|
177
177
|
sandboxBypass = true,
|
|
178
178
|
mcpServers,
|
|
179
179
|
stdinPrompt,
|
|
180
|
+
codexHome,
|
|
181
|
+
disallowUltra,
|
|
182
|
+
enforceCanonicalProfile,
|
|
180
183
|
} = opts;
|
|
181
184
|
|
|
182
185
|
const parts = ["codex"];
|
|
@@ -185,7 +188,13 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
|
185
188
|
// still contains an inline [profiles.X] table (and codex re-injects such
|
|
186
189
|
// tables when it rewrites config.toml), so the `-c model=.. -c
|
|
187
190
|
// model_reasoning_effort=..` form is immune and mutates no config.
|
|
188
|
-
const profileOverrides = profile
|
|
191
|
+
const profileOverrides = profile
|
|
192
|
+
? codexProfileConfigOverrides(profile, {
|
|
193
|
+
codexHome,
|
|
194
|
+
disallowUltra,
|
|
195
|
+
enforceCanonicalProfile,
|
|
196
|
+
})
|
|
197
|
+
: [];
|
|
189
198
|
|
|
190
199
|
if (FEATURES.execSubcommand) {
|
|
191
200
|
parts.push("exec");
|
package/hub/codex-adapter.mjs
CHANGED
|
@@ -118,6 +118,9 @@ export function buildExecArgs(opts = {}) {
|
|
|
118
118
|
const prompt = typeof opts.prompt === "string" ? opts.prompt : "";
|
|
119
119
|
const command = buildExecCommand(prompt, opts.resultFile || null, {
|
|
120
120
|
profile: opts.profile,
|
|
121
|
+
codexHome: opts.codexHome,
|
|
122
|
+
disallowUltra: opts.disallowUltra,
|
|
123
|
+
enforceCanonicalProfile: opts.enforceCanonicalProfile,
|
|
121
124
|
skipGitRepoCheck: true,
|
|
122
125
|
sandboxBypass: true,
|
|
123
126
|
cwd: opts.cwd,
|
package/hub/gemini-adapter.mjs
CHANGED
|
@@ -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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
|
119
|
+
model: resolveAntigravityModel(opts),
|
|
102
120
|
allowedMcpServers: opts.mcpServers,
|
|
103
121
|
});
|
|
104
122
|
}
|
package/hub/intent.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import crypto from "node:crypto";
|
|
6
|
+
import { resolveCodexAgentPolicy } from "../scripts/lib/agent-route-policy.mjs";
|
|
6
7
|
import { whichCommand } from "./platform.mjs";
|
|
7
8
|
|
|
8
9
|
/** 캐시 엔트리: { category, confidence, ts } */
|
|
@@ -61,31 +62,27 @@ function _tryCodexClassify(prompt) {
|
|
|
61
62
|
* triflux 특화 의도 카테고리 (10개)
|
|
62
63
|
*/
|
|
63
64
|
export const INTENT_CATEGORIES = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
},
|
|
74
|
-
debug: { agent: "debugger", mcp: "implement", effort: "gpt56_sol_xhigh" },
|
|
75
|
-
analyze: { agent: "analyst", mcp: "analyze", effort: "gpt56_sol_xhigh" },
|
|
76
|
-
design: { agent: "architect", mcp: "analyze", effort: "gpt56_sol_xhigh" },
|
|
77
|
-
review: { agent: "code-reviewer", mcp: "review", effort: "gpt56_terra_high" },
|
|
78
|
-
document: { agent: "writer", mcp: "docs", effort: "pro" },
|
|
79
|
-
research: { agent: "scientist", mcp: "analyze", effort: "gpt56_terra_high" },
|
|
80
|
-
"quick-fix": {
|
|
81
|
-
agent: "build-fixer",
|
|
82
|
-
mcp: "implement",
|
|
83
|
-
effort: "gpt56_luna_low",
|
|
84
|
-
},
|
|
85
|
-
explain: { agent: "writer", mcp: "docs", effort: "flash" },
|
|
86
|
-
test: { agent: "test-engineer", mcp: null, effort: null },
|
|
65
|
+
implement: { agent: "executor", mcp: "implement" },
|
|
66
|
+
debug: { agent: "debugger", mcp: "implement" },
|
|
67
|
+
analyze: { agent: "analyst", mcp: "analyze" },
|
|
68
|
+
design: { agent: "architect", mcp: "analyze" },
|
|
69
|
+
review: { agent: "code-reviewer", mcp: "review" },
|
|
70
|
+
document: { agent: "writer", mcp: "docs" },
|
|
71
|
+
research: { agent: "scientist", mcp: "analyze" },
|
|
72
|
+
"quick-fix": { agent: "build-fixer", mcp: "implement" },
|
|
73
|
+
explain: { agent: "writer", mcp: "docs" },
|
|
74
|
+
test: { agent: "test-engineer", mcp: null },
|
|
87
75
|
};
|
|
88
76
|
|
|
77
|
+
function routingFor(category) {
|
|
78
|
+
const route = INTENT_CATEGORIES[category] || INTENT_CATEGORIES.implement;
|
|
79
|
+
return {
|
|
80
|
+
agent: route.agent,
|
|
81
|
+
mcp: route.mcp,
|
|
82
|
+
effort: resolveCodexAgentPolicy(route.agent).profile,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
89
86
|
/** @internal 키워드 → 카테고리 매핑 패턴 */
|
|
90
87
|
const KEYWORD_PATTERNS = [
|
|
91
88
|
{
|
|
@@ -267,17 +264,12 @@ export function classifyIntent(prompt) {
|
|
|
267
264
|
// 캐시 확인
|
|
268
265
|
const cached = _getCached(hash);
|
|
269
266
|
if (cached) {
|
|
270
|
-
const routing =
|
|
271
|
-
INTENT_CATEGORIES[cached.category] || INTENT_CATEGORIES.implement;
|
|
267
|
+
const routing = routingFor(cached.category);
|
|
272
268
|
return {
|
|
273
269
|
category: cached.category,
|
|
274
270
|
confidence: cached.confidence,
|
|
275
271
|
reasoning: `cache-hit: ${cached.category} (${cached.confidence.toFixed(2)})`,
|
|
276
|
-
routing
|
|
277
|
-
agent: routing.agent,
|
|
278
|
-
mcp: routing.mcp,
|
|
279
|
-
effort: routing.effort,
|
|
280
|
-
},
|
|
272
|
+
routing,
|
|
281
273
|
};
|
|
282
274
|
}
|
|
283
275
|
|
|
@@ -308,12 +300,12 @@ export function classifyIntent(prompt) {
|
|
|
308
300
|
// 캐시 저장
|
|
309
301
|
_intentCache.set(hash, { category, confidence, ts: Date.now() });
|
|
310
302
|
|
|
311
|
-
const routing =
|
|
303
|
+
const routing = routingFor(category);
|
|
312
304
|
return {
|
|
313
305
|
category,
|
|
314
306
|
confidence,
|
|
315
307
|
reasoning,
|
|
316
|
-
routing
|
|
308
|
+
routing,
|
|
317
309
|
};
|
|
318
310
|
}
|
|
319
311
|
|
package/hub/lib/memory-store.mjs
CHANGED
|
@@ -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 (
|
|
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: {
|
|
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: {
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
690
|
-
let expiredMessages = 0;
|
|
711
|
+
sweepExpired({ now_ms = Date.now() } = {}) {
|
|
712
|
+
const transitions = [];
|
|
691
713
|
for (const message of messages.values()) {
|
|
692
|
-
if (
|
|
693
|
-
message.status
|
|
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:
|
|
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
|
-
|
|
706
|
-
|
|
740
|
+
now_ms,
|
|
741
|
+
messages: transitions.length,
|
|
742
|
+
transitions,
|
|
707
743
|
};
|
|
708
744
|
},
|
|
709
745
|
|