@moreih29/nexus-core 0.16.1 → 0.17.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/assets/capability-matrix.yml +7 -5
- package/assets/hooks/agent-bootstrap/handler.test.ts +18 -17
- package/assets/hooks/agent-bootstrap/handler.ts +20 -7
- package/assets/hooks/post-tool-telemetry/meta.yml +1 -2
- package/assets/hooks/session-init/handler.ts +8 -7
- package/dist/assets/hooks/agent-bootstrap/handler.d.ts.map +1 -1
- package/dist/assets/hooks/agent-bootstrap/handler.js +22 -8
- package/dist/assets/hooks/agent-bootstrap/handler.js.map +1 -1
- package/dist/assets/hooks/session-init/handler.d.ts.map +1 -1
- package/dist/assets/hooks/session-init/handler.js +5 -6
- package/dist/assets/hooks/session-init/handler.js.map +1 -1
- package/dist/claude/agents/architect.md +1 -1
- package/dist/claude/agents/designer.md +1 -1
- package/dist/claude/agents/engineer.md +1 -1
- package/dist/claude/agents/lead.md +1 -1
- package/dist/claude/agents/postdoc.md +1 -1
- package/dist/claude/agents/researcher.md +1 -1
- package/dist/claude/agents/reviewer.md +1 -1
- package/dist/claude/agents/strategist.md +1 -1
- package/dist/claude/agents/tester.md +1 -1
- package/dist/claude/agents/writer.md +1 -1
- package/dist/claude/dist/hooks/agent-bootstrap.js +128 -11
- package/dist/claude/dist/hooks/agent-finalize.js +1 -1
- package/dist/claude/dist/hooks/post-tool-telemetry.js +71 -0
- package/dist/claude/dist/hooks/prompt-router.js +1 -1
- package/dist/claude/dist/hooks/session-init.js +14 -1
- package/dist/claude/hooks/hooks.json +12 -0
- package/dist/codex/agents/architect.toml +3 -0
- package/dist/codex/agents/designer.toml +3 -0
- package/dist/codex/agents/engineer.toml +3 -0
- package/dist/codex/agents/postdoc.toml +3 -0
- package/dist/codex/agents/researcher.toml +3 -0
- package/dist/codex/agents/reviewer.toml +3 -0
- package/dist/codex/agents/strategist.toml +3 -0
- package/dist/codex/agents/tester.toml +3 -0
- package/dist/codex/agents/writer.toml +3 -0
- package/dist/codex/dist/hooks/agent-bootstrap.js +128 -11
- package/dist/codex/dist/hooks/agent-finalize.js +1 -1
- package/dist/codex/dist/hooks/prompt-router.js +1 -1
- package/dist/codex/dist/hooks/session-init.js +14 -1
- package/dist/hooks/agent-bootstrap.js +128 -11
- package/dist/hooks/agent-finalize.js +1 -1
- package/dist/hooks/post-tool-telemetry.js +71 -0
- package/dist/hooks/prompt-router.js +1 -1
- package/dist/hooks/session-init.js +14 -1
- package/dist/manifests/claude-hooks.json +12 -0
- package/dist/manifests/opencode-manifest.json +10 -0
- package/dist/manifests/portability-report.json +6 -18
- package/dist/scripts/build-agents.d.ts +6 -0
- package/dist/scripts/build-agents.d.ts.map +1 -1
- package/dist/scripts/build-agents.js +22 -1
- package/dist/scripts/build-agents.js.map +1 -1
- package/dist/scripts/build-hooks.d.ts.map +1 -1
- package/dist/scripts/build-hooks.js +7 -0
- package/dist/scripts/build-hooks.js.map +1 -1
- package/dist/scripts/smoke/smoke-consumer.js +153 -3
- package/dist/scripts/smoke/smoke-consumer.js.map +1 -1
- package/dist/src/shared/paths.d.ts +3 -1
- package/dist/src/shared/paths.d.ts.map +1 -1
- package/dist/src/shared/paths.js +38 -2
- package/dist/src/shared/paths.js.map +1 -1
- package/docs/contract/harness-io.md +7 -2
- package/package.json +1 -1
|
@@ -162,8 +162,10 @@ capabilities:
|
|
|
162
162
|
# Source: https://opencode.ai/docs/agents/ (model field is optional)
|
|
163
163
|
# Verified: .nexus/memory/external-opencode-plugin.md §5
|
|
164
164
|
#
|
|
165
|
-
# claude model slugs (
|
|
166
|
-
#
|
|
165
|
+
# claude model slugs (alias form — version-tolerant; Claude Code resolves to the
|
|
166
|
+
# latest dated model in the series. Prefer aliases (opus/sonnet/haiku) over
|
|
167
|
+
# dated IDs (claude-opus-4-7 등) to avoid rot as new versions ship.):
|
|
168
|
+
# Source: https://code.claude.com/docs/en/sub-agents (model field accepts aliases)
|
|
167
169
|
# Verified: .nexus/memory/external-claude-code-hooks-tools.md §6 (Agent tool: model param)
|
|
168
170
|
#
|
|
169
171
|
# codex model slugs:
|
|
@@ -178,7 +180,7 @@ model_tier:
|
|
|
178
180
|
# high — used by: architect, designer, postdoc, strategist
|
|
179
181
|
# Reasoning-intensive advisory roles that require deep analysis.
|
|
180
182
|
high:
|
|
181
|
-
claude:
|
|
183
|
+
claude: opus # alias — Claude Code resolves to latest Opus
|
|
182
184
|
codex: gpt-5.4 # UNVERIFIED slug — confirm against current Codex model list
|
|
183
185
|
opencode: null # inherits user config
|
|
184
186
|
|
|
@@ -186,13 +188,13 @@ model_tier:
|
|
|
186
188
|
# Execution and verification roles where throughput matters more than
|
|
187
189
|
# top-tier reasoning.
|
|
188
190
|
standard:
|
|
189
|
-
claude:
|
|
191
|
+
claude: sonnet # alias — Claude Code resolves to latest Sonnet
|
|
190
192
|
codex: gpt-5.3-codex # UNVERIFIED slug — confirm against current Codex model list
|
|
191
193
|
opencode: null # inherits user config
|
|
192
194
|
|
|
193
195
|
# low — reserved tier; no agents currently assigned.
|
|
194
196
|
# Intended for lightweight utility agents (fast, cheap, simple tasks).
|
|
195
197
|
low:
|
|
196
|
-
claude:
|
|
198
|
+
claude: haiku # alias — Claude Code resolves to latest Haiku
|
|
197
199
|
codex: gpt-5.4-mini # UNVERIFIED slug — confirm against current Codex model list
|
|
198
200
|
opencode: null # inherits user config
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (3) resume_count > 0 → skip (not fresh)
|
|
8
8
|
* (4) .nexus/rules/<role>.md absent → core index only
|
|
9
9
|
* (5) core index > 2KB → truncated to recent-modified N entries
|
|
10
|
-
* (6) tracker
|
|
10
|
+
* (6) tracker append: new entry created; same agent_id is idempotent (no duplicate)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
|
|
@@ -256,43 +256,44 @@ describe("scenario 5: 2KB truncation of core index", () => {
|
|
|
256
256
|
});
|
|
257
257
|
|
|
258
258
|
// ---------------------------------------------------------------------------
|
|
259
|
-
// Scenario (6):
|
|
259
|
+
// Scenario (6): tracker append
|
|
260
260
|
// ---------------------------------------------------------------------------
|
|
261
261
|
|
|
262
|
-
describe("scenario 6:
|
|
262
|
+
describe("scenario 6: tracker append", () => {
|
|
263
263
|
let cleanup: () => void;
|
|
264
264
|
|
|
265
265
|
afterAll(() => cleanup?.());
|
|
266
266
|
|
|
267
|
-
test("agent-tracker.json is
|
|
267
|
+
test("agent-tracker.json is created with running entry for fresh agent", async () => {
|
|
268
268
|
const { cwd, sessionId, cleanup: c } = makeFixture({ withTracker: undefined });
|
|
269
269
|
cleanup = c;
|
|
270
270
|
|
|
271
271
|
const trackerPath = join(cwd, ".nexus/state", sessionId, "agent-tracker.json");
|
|
272
272
|
|
|
273
|
-
// Tracker must not exist before the call
|
|
274
273
|
expect(existsSync(trackerPath)).toBe(false);
|
|
275
274
|
|
|
276
|
-
await handler(makeInput(cwd, sessionId, "architect"));
|
|
275
|
+
await handler(makeInput(cwd, sessionId, "architect", "agent-001"));
|
|
277
276
|
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
expect(existsSync(trackerPath)).toBe(true);
|
|
278
|
+
const tracker = JSON.parse(readFileSync(trackerPath, "utf-8")) as Array<Record<string, unknown>>;
|
|
279
|
+
expect(tracker).toHaveLength(1);
|
|
280
|
+
expect(tracker[0]["agent_id"]).toBe("agent-001");
|
|
281
|
+
expect(tracker[0]["agent_type"]).toBe("architect");
|
|
282
|
+
expect(tracker[0]["status"]).toBe("running");
|
|
283
|
+
expect(typeof tracker[0]["started_at"]).toBe("string");
|
|
280
284
|
});
|
|
281
285
|
|
|
282
|
-
test("
|
|
283
|
-
const
|
|
284
|
-
const { cwd, sessionId, cleanup: c } = makeFixture({
|
|
285
|
-
withTracker: { agentId, resumeCount: 0 },
|
|
286
|
-
});
|
|
286
|
+
test("duplicate agent_id on second call does not create a second entry", async () => {
|
|
287
|
+
const { cwd, sessionId, cleanup: c } = makeFixture({ withTracker: undefined });
|
|
287
288
|
cleanup = c;
|
|
288
289
|
|
|
289
290
|
const trackerPath = join(cwd, ".nexus/state", sessionId, "agent-tracker.json");
|
|
290
|
-
const before = readFileSync(trackerPath, "utf-8");
|
|
291
291
|
|
|
292
|
-
await handler(makeInput(cwd, sessionId, "architect",
|
|
292
|
+
await handler(makeInput(cwd, sessionId, "architect", "agent-001"));
|
|
293
|
+
await handler(makeInput(cwd, sessionId, "architect", "agent-001"));
|
|
293
294
|
|
|
294
|
-
const
|
|
295
|
-
expect(
|
|
295
|
+
const tracker = JSON.parse(readFileSync(trackerPath, "utf-8")) as Array<Record<string, unknown>>;
|
|
296
|
+
expect(tracker.filter((e) => e["agent_id"] === "agent-001")).toHaveLength(1);
|
|
296
297
|
});
|
|
297
298
|
});
|
|
298
299
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { HookHandler } from "../../../src/hooks/types.js";
|
|
2
|
+
import { updateJsonFileLocked } from "../../../src/shared/json-store.js";
|
|
2
3
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
|
|
5
6
|
const CORE_INDEX_SIZE_LIMIT = 2 * 1024; // 2KB
|
|
6
7
|
|
|
7
8
|
function loadValidRoles(cwd: string): string[] {
|
|
9
|
+
const inlined = (globalThis as unknown as { __NEXUS_INLINE_AGENT_ROLES__?: string[] }).__NEXUS_INLINE_AGENT_ROLES__;
|
|
10
|
+
if (Array.isArray(inlined)) return inlined;
|
|
8
11
|
const agentsDir = join(cwd, "assets/agents");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return roles;
|
|
12
|
+
if (!existsSync(agentsDir)) return [];
|
|
13
|
+
return readdirSync(agentsDir, { withFileTypes: true })
|
|
14
|
+
.filter((e) => e.isDirectory())
|
|
15
|
+
.map((e) => e.name);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function readFirstLine(path: string): string {
|
|
@@ -95,6 +95,19 @@ const handler: HookHandler = async (input) => {
|
|
|
95
95
|
const validRoles = loadValidRoles(cwd);
|
|
96
96
|
if (!validRoles.includes(agent_type)) return;
|
|
97
97
|
|
|
98
|
+
const trackerPath = join(cwd, ".nexus/state", session_id, "agent-tracker.json");
|
|
99
|
+
await updateJsonFileLocked(trackerPath, [], (tracker: Array<Record<string, unknown>>) => {
|
|
100
|
+
const list = Array.isArray(tracker) ? tracker : [];
|
|
101
|
+
if (list.find((e) => e["agent_id"] === agent_id)) return list;
|
|
102
|
+
list.push({
|
|
103
|
+
agent_id,
|
|
104
|
+
agent_type,
|
|
105
|
+
started_at: new Date().toISOString(),
|
|
106
|
+
status: "running",
|
|
107
|
+
});
|
|
108
|
+
return list;
|
|
109
|
+
});
|
|
110
|
+
|
|
98
111
|
const parts: string[] = [];
|
|
99
112
|
|
|
100
113
|
const coreIndex = buildCoreIndex(cwd);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
name: post-tool-telemetry
|
|
2
2
|
description: Track memory access and file-edit operations for strength/forgetting signals and audit
|
|
3
3
|
events: [PostToolUse]
|
|
4
|
-
matcher: "Read|Edit|Write|MultiEdit|ApplyPatch|NotebookEdit
|
|
4
|
+
matcher: "Read|Edit|Write|MultiEdit|ApplyPatch|NotebookEdit"
|
|
5
5
|
timeout: 5
|
|
6
6
|
fallback: warn
|
|
7
7
|
priority: 10
|
|
8
8
|
requires_capabilities:
|
|
9
9
|
- event.post_tool_use.read
|
|
10
10
|
- event.post_tool_use.edit
|
|
11
|
-
- event.post_tool_use.bash_parsed
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { HookHandler } from "../../../src/hooks/types.js";
|
|
2
2
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, basename } from "node:path";
|
|
4
|
+
import { getParentPid } from "../../../src/shared/paths.js";
|
|
4
5
|
|
|
5
6
|
const handler: HookHandler = async (input) => {
|
|
6
7
|
if (input.hook_event_name !== "SessionStart") return;
|
|
7
8
|
|
|
8
|
-
// Sanitize session_id to prevent path traversal
|
|
9
9
|
const safeSid = basename(input.session_id);
|
|
10
10
|
if (!safeSid || safeSid.startsWith(".") || safeSid.includes("/")) {
|
|
11
11
|
process.stderr.write(`[session-init] invalid session_id: ${input.session_id}\n`);
|
|
@@ -14,17 +14,18 @@ const handler: HookHandler = async (input) => {
|
|
|
14
14
|
|
|
15
15
|
const sessionDir = join(input.cwd, ".nexus/state", safeSid);
|
|
16
16
|
|
|
17
|
-
// Ensure directory exists (idempotent)
|
|
18
17
|
mkdirSync(sessionDir, { recursive: true });
|
|
19
18
|
|
|
20
|
-
// Initialize per-session state files — overwrite unconditionally (resume is intentional)
|
|
21
19
|
writeFileSync(join(sessionDir, "agent-tracker.json"), "[]");
|
|
22
20
|
writeFileSync(join(sessionDir, "tool-log.jsonl"), "");
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const ppid = getParentPid();
|
|
23
|
+
const byPpidDir = join(input.cwd, ".nexus/state/runtime/by-ppid");
|
|
24
|
+
mkdirSync(byPpidDir, { recursive: true });
|
|
25
|
+
writeFileSync(
|
|
26
|
+
join(byPpidDir, `${ppid}.json`),
|
|
27
|
+
JSON.stringify({ session_id: input.session_id, updated_at: new Date().toISOString(), cwd: input.cwd }),
|
|
28
|
+
);
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
export default handler;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../assets/hooks/agent-bootstrap/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAoF/D,QAAA,MAAM,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../assets/hooks/agent-bootstrap/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAoF/D,QAAA,MAAM,OAAO,EAAE,WA6Cd,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
import { updateJsonFileLocked } from "../../../src/shared/json-store.js";
|
|
1
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
const CORE_INDEX_SIZE_LIMIT = 2 * 1024; // 2KB
|
|
4
5
|
function loadValidRoles(cwd) {
|
|
6
|
+
const inlined = globalThis.__NEXUS_INLINE_AGENT_ROLES__;
|
|
7
|
+
if (Array.isArray(inlined))
|
|
8
|
+
return inlined;
|
|
5
9
|
const agentsDir = join(cwd, "assets/agents");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return roles;
|
|
10
|
+
if (!existsSync(agentsDir))
|
|
11
|
+
return [];
|
|
12
|
+
return readdirSync(agentsDir, { withFileTypes: true })
|
|
13
|
+
.filter((e) => e.isDirectory())
|
|
14
|
+
.map((e) => e.name);
|
|
14
15
|
}
|
|
15
16
|
function readFirstLine(path) {
|
|
16
17
|
try {
|
|
@@ -80,6 +81,19 @@ const handler = async (input) => {
|
|
|
80
81
|
const validRoles = loadValidRoles(cwd);
|
|
81
82
|
if (!validRoles.includes(agent_type))
|
|
82
83
|
return;
|
|
84
|
+
const trackerPath = join(cwd, ".nexus/state", session_id, "agent-tracker.json");
|
|
85
|
+
await updateJsonFileLocked(trackerPath, [], (tracker) => {
|
|
86
|
+
const list = Array.isArray(tracker) ? tracker : [];
|
|
87
|
+
if (list.find((e) => e["agent_id"] === agent_id))
|
|
88
|
+
return list;
|
|
89
|
+
list.push({
|
|
90
|
+
agent_id,
|
|
91
|
+
agent_type,
|
|
92
|
+
started_at: new Date().toISOString(),
|
|
93
|
+
status: "running",
|
|
94
|
+
});
|
|
95
|
+
return list;
|
|
96
|
+
});
|
|
83
97
|
const parts = [];
|
|
84
98
|
const coreIndex = buildCoreIndex(cwd);
|
|
85
99
|
if (coreIndex) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../../assets/hooks/agent-bootstrap/handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,qBAAqB,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM;AAE9C,SAAS,cAAc,CAAC,GAAW;IACjC,MAAM,
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../../assets/hooks/agent-bootstrap/handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../assets/hooks/session-init/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../assets/hooks/session-init/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAK/D,QAAA,MAAM,OAAO,EAAE,WAuBd,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, basename } from "node:path";
|
|
3
|
+
import { getParentPid } from "../../../src/shared/paths.js";
|
|
3
4
|
const handler = async (input) => {
|
|
4
5
|
if (input.hook_event_name !== "SessionStart")
|
|
5
6
|
return;
|
|
6
|
-
// Sanitize session_id to prevent path traversal
|
|
7
7
|
const safeSid = basename(input.session_id);
|
|
8
8
|
if (!safeSid || safeSid.startsWith(".") || safeSid.includes("/")) {
|
|
9
9
|
process.stderr.write(`[session-init] invalid session_id: ${input.session_id}\n`);
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
const sessionDir = join(input.cwd, ".nexus/state", safeSid);
|
|
13
|
-
// Ensure directory exists (idempotent)
|
|
14
13
|
mkdirSync(sessionDir, { recursive: true });
|
|
15
|
-
// Initialize per-session state files — overwrite unconditionally (resume is intentional)
|
|
16
14
|
writeFileSync(join(sessionDir, "agent-tracker.json"), "[]");
|
|
17
15
|
writeFileSync(join(sessionDir, "tool-log.jsonl"), "");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const ppid = getParentPid();
|
|
17
|
+
const byPpidDir = join(input.cwd, ".nexus/state/runtime/by-ppid");
|
|
18
|
+
mkdirSync(byPpidDir, { recursive: true });
|
|
19
|
+
writeFileSync(join(byPpidDir, `${ppid}.json`), JSON.stringify({ session_id: input.session_id, updated_at: new Date().toISOString(), cwd: input.cwd }));
|
|
21
20
|
};
|
|
22
21
|
export default handler;
|
|
23
22
|
//# sourceMappingURL=handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../../assets/hooks/session-init/handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../../assets/hooks/session-init/handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D,MAAM,OAAO,GAAgB,KAAK,EAAE,KAAK,EAAE,EAAE;IAC3C,IAAI,KAAK,CAAC,eAAe,KAAK,cAAc;QAAE,OAAO;IAErD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;QACjF,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;IAE5D,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,EAAE,IAAI,CAAC,CAAC;IAC5D,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,gBAAgB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEtD,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,8BAA8B,CAAC,CAAC;IAClE,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,aAAa,CACX,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,OAAO,CAAC,EAC/B,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CACvG,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
|
@@ -1,21 +1,124 @@
|
|
|
1
|
+
// src/shared/json-store.js
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { constants as fsConstants, appendFileSync, mkdirSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
var inProcessQueues = new Map;
|
|
7
|
+
async function runWithInProcessLock(filePath, action) {
|
|
8
|
+
const previous = inProcessQueues.get(filePath) ?? Promise.resolve();
|
|
9
|
+
let release = () => {};
|
|
10
|
+
const gate = new Promise((resolve) => {
|
|
11
|
+
release = resolve;
|
|
12
|
+
});
|
|
13
|
+
const entry = previous.then(() => gate);
|
|
14
|
+
inProcessQueues.set(filePath, entry);
|
|
15
|
+
await previous;
|
|
16
|
+
try {
|
|
17
|
+
return await action();
|
|
18
|
+
} finally {
|
|
19
|
+
release();
|
|
20
|
+
entry.finally(() => {
|
|
21
|
+
if (inProcessQueues.get(filePath) === entry) {
|
|
22
|
+
inProcessQueues.delete(filePath);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
var LOCK_RETRY_INTERVAL_MS = 100;
|
|
28
|
+
var LOCK_MAX_RETRIES = 50;
|
|
29
|
+
var LOCK_STALE_MS = 30000;
|
|
30
|
+
function lockPath(filePath) {
|
|
31
|
+
return `${filePath}.lock`;
|
|
32
|
+
}
|
|
33
|
+
async function acquireFsLock(filePath) {
|
|
34
|
+
const lp = lockPath(filePath);
|
|
35
|
+
for (let attempt = 0;attempt <= LOCK_MAX_RETRIES; attempt++) {
|
|
36
|
+
try {
|
|
37
|
+
const fd = await fs.open(lp, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL);
|
|
38
|
+
await fd.close();
|
|
39
|
+
return;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
const e = err;
|
|
42
|
+
if (e.code !== "EEXIST")
|
|
43
|
+
throw err;
|
|
44
|
+
try {
|
|
45
|
+
const stat = await fs.stat(lp);
|
|
46
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
47
|
+
if (ageMs > LOCK_STALE_MS) {
|
|
48
|
+
await fs.unlink(lp).catch(() => {
|
|
49
|
+
return;
|
|
50
|
+
});
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (attempt === LOCK_MAX_RETRIES) {
|
|
57
|
+
throw new Error(`Failed to acquire lock for "${filePath}" after ${LOCK_MAX_RETRIES} retries`);
|
|
58
|
+
}
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function releaseFsLock(filePath) {
|
|
64
|
+
await fs.unlink(lockPath(filePath)).catch(() => {
|
|
65
|
+
return;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function readJsonFile(filePath, defaultValue) {
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
72
|
+
} catch (err) {
|
|
73
|
+
const e = err;
|
|
74
|
+
if (e.code === "ENOENT")
|
|
75
|
+
return defaultValue;
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(raw);
|
|
80
|
+
} catch {
|
|
81
|
+
return defaultValue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function writeJsonFile(filePath, data) {
|
|
85
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
86
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}.${randomUUID()}`;
|
|
87
|
+
await fs.writeFile(tmpPath, JSON.stringify(data, null, 2) + `
|
|
88
|
+
`, "utf8");
|
|
89
|
+
await fs.rename(tmpPath, filePath);
|
|
90
|
+
}
|
|
91
|
+
async function updateJsonFileLocked(filePath, defaultValue, updater) {
|
|
92
|
+
return runWithInProcessLock(filePath, async () => {
|
|
93
|
+
await acquireFsLock(filePath);
|
|
94
|
+
try {
|
|
95
|
+
const current = await readJsonFile(filePath, defaultValue);
|
|
96
|
+
const next = await updater(current);
|
|
97
|
+
await writeJsonFile(filePath, next);
|
|
98
|
+
return next;
|
|
99
|
+
} finally {
|
|
100
|
+
await releaseFsLock(filePath);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
var APPEND_SIZE_WARN_THRESHOLD = 4 * 1024;
|
|
105
|
+
|
|
1
106
|
// assets/hooks/agent-bootstrap/handler.ts
|
|
2
107
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
108
|
import { join } from "node:path";
|
|
4
109
|
var CORE_INDEX_SIZE_LIMIT = 2 * 1024;
|
|
5
110
|
function loadValidRoles(cwd) {
|
|
111
|
+
const inlined = globalThis.__NEXUS_INLINE_AGENT_ROLES__;
|
|
112
|
+
if (Array.isArray(inlined))
|
|
113
|
+
return inlined;
|
|
6
114
|
const agentsDir = join(cwd, "assets/agents");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (entry.isDirectory())
|
|
11
|
-
roles.push(entry.name);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return roles;
|
|
115
|
+
if (!existsSync(agentsDir))
|
|
116
|
+
return [];
|
|
117
|
+
return readdirSync(agentsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
15
118
|
}
|
|
16
|
-
function readFirstLine(
|
|
119
|
+
function readFirstLine(path2) {
|
|
17
120
|
try {
|
|
18
|
-
const content = readFileSync(
|
|
121
|
+
const content = readFileSync(path2, "utf-8");
|
|
19
122
|
const firstNonEmpty = content.split(`
|
|
20
123
|
`).find((l) => l.trim().length > 0) ?? "";
|
|
21
124
|
return firstNonEmpty.replace(/^#+\s*/, "").slice(0, 80);
|
|
@@ -76,6 +179,19 @@ var handler = async (input) => {
|
|
|
76
179
|
const validRoles = loadValidRoles(cwd);
|
|
77
180
|
if (!validRoles.includes(agent_type))
|
|
78
181
|
return;
|
|
182
|
+
const trackerPath = join(cwd, ".nexus/state", session_id, "agent-tracker.json");
|
|
183
|
+
await updateJsonFileLocked(trackerPath, [], (tracker) => {
|
|
184
|
+
const list = Array.isArray(tracker) ? tracker : [];
|
|
185
|
+
if (list.find((e) => e["agent_id"] === agent_id))
|
|
186
|
+
return list;
|
|
187
|
+
list.push({
|
|
188
|
+
agent_id,
|
|
189
|
+
agent_type,
|
|
190
|
+
started_at: new Date().toISOString(),
|
|
191
|
+
status: "running"
|
|
192
|
+
});
|
|
193
|
+
return list;
|
|
194
|
+
});
|
|
79
195
|
const parts = [];
|
|
80
196
|
const coreIndex = buildCoreIndex(cwd);
|
|
81
197
|
if (coreIndex) {
|
|
@@ -101,8 +217,9 @@ ${ruleContent}
|
|
|
101
217
|
};
|
|
102
218
|
var handler_default = handler;
|
|
103
219
|
|
|
104
|
-
// ../../../../../tmp/nexus-hook-entry-agent-bootstrap-
|
|
220
|
+
// ../../../../../tmp/nexus-hook-entry-agent-bootstrap-1776690665703/agent-bootstrap-entry.ts
|
|
105
221
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
222
|
+
globalThis.__NEXUS_INLINE_AGENT_ROLES__ = ["architect", "designer", "engineer", "reviewer", "strategist", "researcher", "postdoc", "lead", "tester", "writer"];
|
|
106
223
|
async function main() {
|
|
107
224
|
let raw = "";
|
|
108
225
|
try {
|
|
@@ -160,7 +160,7 @@ Subagent "${agent_type}" finished. Tasks still pending with this role: ${ids}. R
|
|
|
160
160
|
};
|
|
161
161
|
var handler_default = handler;
|
|
162
162
|
|
|
163
|
-
// ../../../../../tmp/nexus-hook-entry-agent-finalize-
|
|
163
|
+
// ../../../../../tmp/nexus-hook-entry-agent-finalize-1776690665695/agent-finalize-entry.ts
|
|
164
164
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
165
165
|
async function main() {
|
|
166
166
|
let raw = "";
|