nexus-prime 7.0.0 → 7.0.1

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.
@@ -23,6 +23,8 @@ export interface ContextChunk {
23
23
  entropy?: number;
24
24
  /** Memory trust (0-1) — used to boost high-trust memory chunks */
25
25
  trust?: number;
26
+ /** Pre-computed mtime so scoreRecency does not statSync per chunk in the hot loop. */
27
+ mtimeMs?: number;
26
28
  }
27
29
  export interface QualityWeights {
28
30
  relevance: number;
@@ -154,15 +154,12 @@ export class ContextAssembler {
154
154
  }
155
155
  /** Recency: how recently the file was modified (exponential decay) */
156
156
  scoreRecency(chunk) {
157
- try {
158
- const stat = fs.statSync(chunk.source);
159
- const ageHours = (Date.now() - stat.mtimeMs) / 3_600_000;
160
- // Exponential decay: half-life of 24 hours
161
- return Math.exp(-0.693 * ageHours / 24);
162
- }
163
- catch {
164
- return 0.3; // default for unresolvable files
165
- }
157
+ // Hot loop: rely on chunk.mtimeMs stamped at chunk creation time. No per-chunk fs.
158
+ // Chunks without a stamp (legacy producers) get a neutral 0.3 default.
159
+ if (typeof chunk.mtimeMs !== 'number' || chunk.mtimeMs <= 0)
160
+ return 0.3;
161
+ const ageHours = (Date.now() - chunk.mtimeMs) / 3_600_000;
162
+ return Math.exp(-0.693 * ageHours / 24);
166
163
  }
167
164
  /** Novelty: chunks not yet seen in this session score higher */
168
165
  scoreNovelty(chunk) {
@@ -216,16 +213,23 @@ export class ContextAssembler {
216
213
  chunkFile(filepath) {
217
214
  const resolved = path.isAbsolute(filepath) ? filepath : path.join(process.cwd(), filepath);
218
215
  let content;
216
+ let mtimeMs;
219
217
  try {
220
218
  content = fs.readFileSync(resolved, 'utf-8');
219
+ // Single stat per file — cached onto every chunk so scoreRecency stays sync-free.
220
+ try {
221
+ mtimeMs = fs.statSync(resolved).mtimeMs;
222
+ }
223
+ catch { /* ignore */ }
221
224
  }
222
225
  catch {
223
226
  return [];
224
227
  }
225
228
  const lines = content.split('\n');
229
+ let chunks;
226
230
  // For small files (< 100 lines), return as single chunk
227
231
  if (lines.length < 100) {
228
- return [{
232
+ chunks = [{
229
233
  source: resolved,
230
234
  content,
231
235
  tokens: Math.ceil(content.length / 4),
@@ -235,13 +239,17 @@ export class ContextAssembler {
235
239
  endLine: lines.length,
236
240
  }];
237
241
  }
238
- // Detect function/class boundaries for .ts/.js files
239
- const ext = path.extname(filepath).toLowerCase();
240
- if (['.ts', '.js', '.tsx', '.jsx'].includes(ext)) {
241
- return this.chunkByBoundaries(resolved, lines);
242
+ else {
243
+ const ext = path.extname(filepath).toLowerCase();
244
+ chunks = ['.ts', '.js', '.tsx', '.jsx'].includes(ext)
245
+ ? this.chunkByBoundaries(resolved, lines)
246
+ : this.chunkByWindow(resolved, lines, 80);
247
+ }
248
+ if (mtimeMs !== undefined) {
249
+ for (const chunk of chunks)
250
+ chunk.mtimeMs = mtimeMs;
242
251
  }
243
- // For other files, chunk by fixed window
244
- return this.chunkByWindow(resolved, lines, 80);
252
+ return chunks;
245
253
  }
246
254
  /** Chunk TypeScript/JavaScript by function and class boundaries */
247
255
  chunkByBoundaries(filepath, lines) {
@@ -1,4 +1,4 @@
1
- export type NexusEventType = 'system.boot' | 'planner.stage' | 'memory.store' | 'memory.dedup' | 'memory.recall' | 'memory.flushed' | 'memory.health.tick' | 'memory.sqlite.retry' | 'pod.signal' | 'tokens.optimized' | 'tokens.searchSaved' | 'session.summaryBootstrap' | 'phantom.worker.start' | 'phantom.worker.complete' | 'phantom.merge.complete' | 'phantom.merge' | 'guardrail.check' | 'ghost.pass' | 'graph.query' | 'graph.sync.failed' | 'graph.coverage.low' | 'graph.cr.build.start' | 'graph.cr.build.complete' | 'graph.cr.build.failed' | 'darwin.cycle' | 'darwin.cycle.complete' | 'session.dna' | 'skill.register' | 'skill.deploy' | 'skill.revoke' | 'hook.deploy' | 'hook.revoke' | 'hook.fire' | 'hook.error' | 'workflow.deploy' | 'workflow.run' | 'automation.deploy' | 'automation.revoke' | 'automation.run' | 'shield.decision' | 'memory.audit' | 'federation.heartbeat' | 'client.heartbeat' | 'client.inferred' | 'client.status' | 'dashboard.action' | 'workspace.changed' | 'nexus.shutdown' | 'nexus.daemon.error' | 'orchestrator.disposed' | 'orchestrator.funnel.stage' | 'orchestrator.warn' | 'nexusnet.publish' | 'nexusnet.sync' | 'mcp.call.start' | 'mcp.call.stream' | 'mcp.call.complete' | 'mcp.handler.complete' | 'mcp.handler.failed' | 'mcp.handler.retry' | 'mcp.handler.best-effort-failed' | 'tool.invocation' | 'nexus.deprecation.used' | 'memory.tier.promoted' | 'memory.tier.promoted.failed' | 'memory.pre-compaction' | 'memory.flush-requested' | 'entanglement.create' | 'entanglement.collapse' | 'entanglement.correlate' | 'cas.encode' | 'cas.decode' | 'cas.pattern_learned' | 'kv.merge' | 'kv.adapt' | 'kv.consensus' | 'byzantine.vote.started' | 'byzantine.vote.result' | 'synapse.ready' | 'synapse.operative.hired' | 'synapse.operative.retired' | 'synapse.operative.health.changed' | 'synapse.striketeam.deployed' | 'synapse.striketeam.completed' | 'synapse.mission.assigned' | 'synapse.mission.completed' | 'synapse.sortie.started' | 'synapse.sortie.completed' | 'synapse.sortie.failed' | 'synapse.fieldreport.submitted' | 'synapse.echo.fired' | 'synapse.budget.warning' | 'synapse.budget.exceeded' | 'synapse.approval.requested' | 'synapse.approval.resolved' | 'synapse.compaction.standdown' | 'synapse.compaction.resumed' | 'synapse.watchdog.stall' | 'synapse.watchdog.zombie' | 'architects.ready' | 'architects.blueprint.instantiated' | 'architects.worklist.created' | 'architects.workitem.claimed' | 'architects.workitem.completed' | 'architects.workitem.blocked' | 'architects.constructionlock.acquired' | 'architects.constructionlock.renewed' | 'architects.constructionlock.released' | 'architects.constructionlock.contested' | 'architects.relay.sent' | 'architects.relay.read' | 'architects.sentinel.patrol' | 'architects.sentinel.stall' | 'architects.sentinel.zombie' | 'architects.ward.patrol' | 'architects.ward.escalation' | 'architects.convergence.started' | 'architects.convergence.merged' | 'architects.convergence.failed' | 'architects.dispatch.go' | 'architects.dispatch.queued' | 'architects.relay.failed' | 'architects.event.failed' | 'ledger.duplicate-prevented' | 'nexus.circuit-open' | 'nexus.circuit-tripped' | 'license.activated' | 'license.deactivated' | 'license.expired' | 'license.cap.warning' | 'license.cap.reached' | 'dispatch.started' | 'dispatch.event' | 'dispatch.token-usage' | 'dispatch.complete' | 'dispatch.failed' | 'dispatch.cancelled' | 'dispatch.budget-exceeded' | 'mcp.auto-memory.failed' | 'daemon.self-heal.db-corrupt' | 'feature.time_to_first_bootstrap' | 'feature.time_to_first_memory' | 'feature.time_to_first_interaction' | 'feature.time_to_first_mission_complete' | 'async_gate.completed' | 'async_gate.failed' | 'install.step' | 'license.tierChanged' | 'license.upgradeNudge' | 'orchestrator.run.start' | 'orchestrator.run.complete';
1
+ export type NexusEventType = 'system.boot' | 'planner.stage' | 'memory.store' | 'memory.dedup' | 'memory.recall' | 'memory.flushed' | 'memory.health.tick' | 'memory.sqlite.retry' | 'pod.signal' | 'tokens.optimized' | 'tokens.searchSaved' | 'session.summaryBootstrap' | 'phantom.worker.start' | 'phantom.worker.complete' | 'phantom.merge.complete' | 'phantom.merge' | 'guardrail.check' | 'ghost.pass' | 'graph.query' | 'graph.sync.failed' | 'graph.coverage.low' | 'graph.cr.build.start' | 'graph.cr.build.complete' | 'graph.cr.build.failed' | 'darwin.cycle' | 'darwin.cycle.complete' | 'session.dna' | 'skill.register' | 'skill.deploy' | 'skill.revoke' | 'hook.deploy' | 'hook.revoke' | 'hook.fire' | 'hook.error' | 'workflow.deploy' | 'workflow.run' | 'automation.deploy' | 'automation.revoke' | 'automation.run' | 'shield.decision' | 'memory.audit' | 'federation.heartbeat' | 'client.heartbeat' | 'client.inferred' | 'client.status' | 'dashboard.action' | 'workspace.changed' | 'nexus.shutdown' | 'nexus.daemon.error' | 'orchestrator.disposed' | 'orchestrator.funnel.stage' | 'orchestrator.warn' | 'nexusnet.publish' | 'nexusnet.sync' | 'mcp.call.start' | 'mcp.call.stream' | 'mcp.call.complete' | 'mcp.handler.complete' | 'mcp.handler.failed' | 'mcp.handler.retry' | 'mcp.handler.best-effort-failed' | 'tool.invocation' | 'nexus.deprecation.used' | 'memory.tier.promoted' | 'memory.tier.promoted.failed' | 'memory.pre-compaction' | 'memory.flush-requested' | 'entanglement.create' | 'entanglement.collapse' | 'entanglement.correlate' | 'cas.encode' | 'cas.decode' | 'cas.pattern_learned' | 'kv.merge' | 'kv.adapt' | 'kv.consensus' | 'byzantine.vote.started' | 'byzantine.vote.result' | 'synapse.ready' | 'synapse.operative.hired' | 'synapse.operative.retired' | 'synapse.operative.health.changed' | 'synapse.striketeam.deployed' | 'synapse.striketeam.completed' | 'synapse.mission.assigned' | 'synapse.mission.completed' | 'synapse.sortie.started' | 'synapse.sortie.completed' | 'synapse.sortie.failed' | 'synapse.fieldreport.submitted' | 'synapse.echo.fired' | 'synapse.budget.warning' | 'synapse.budget.exceeded' | 'synapse.approval.requested' | 'synapse.approval.resolved' | 'synapse.compaction.standdown' | 'synapse.compaction.resumed' | 'synapse.coordination.publish.failed' | 'synapse.watchdog.stall' | 'synapse.watchdog.zombie' | 'architects.ready' | 'architects.blueprint.instantiated' | 'architects.worklist.created' | 'architects.workitem.claimed' | 'architects.workitem.completed' | 'architects.workitem.blocked' | 'architects.constructionlock.acquired' | 'architects.constructionlock.renewed' | 'architects.constructionlock.released' | 'architects.constructionlock.contested' | 'architects.relay.sent' | 'architects.relay.read' | 'architects.sentinel.patrol' | 'architects.sentinel.stall' | 'architects.sentinel.zombie' | 'architects.ward.patrol' | 'architects.ward.escalation' | 'architects.convergence.started' | 'architects.convergence.merged' | 'architects.convergence.failed' | 'architects.dispatch.go' | 'architects.dispatch.queued' | 'architects.relay.failed' | 'architects.event.failed' | 'ledger.duplicate-prevented' | 'nexus.circuit-open' | 'nexus.circuit-tripped' | 'license.activated' | 'license.deactivated' | 'license.expired' | 'license.cap.warning' | 'license.cap.reached' | 'dispatch.started' | 'dispatch.event' | 'dispatch.token-usage' | 'dispatch.complete' | 'dispatch.failed' | 'dispatch.cancelled' | 'dispatch.budget-exceeded' | 'mcp.auto-memory.failed' | 'daemon.self-heal.db-corrupt' | 'feature.time_to_first_bootstrap' | 'feature.time_to_first_memory' | 'feature.time_to_first_interaction' | 'feature.time_to_first_mission_complete' | 'async_gate.completed' | 'async_gate.failed' | 'install.step' | 'license.tierChanged' | 'license.upgradeNudge' | 'orchestrator.run.start' | 'orchestrator.run.complete';
2
2
  export interface NexusEventPayloads {
3
3
  'system.boot': {
4
4
  version: string;
@@ -521,6 +521,14 @@ export interface NexusEventPayloads {
521
521
  'synapse.compaction.resumed': {
522
522
  operativesResumed: number;
523
523
  };
524
+ 'synapse.coordination.publish.failed': {
525
+ stage: string;
526
+ phase: string;
527
+ operativeId?: string;
528
+ missionId?: string;
529
+ sortieId?: string;
530
+ error: string;
531
+ };
524
532
  'synapse.watchdog.stall': {
525
533
  operativeId: string;
526
534
  };
@@ -346,6 +346,7 @@ export declare function resolveNexusStateDir(): string;
346
346
  export declare class RuntimeRegistry {
347
347
  private readonly registryDir;
348
348
  constructor(rootDir?: string);
349
+ private ensureDir;
349
350
  list(): ListedRuntimeSnapshot[];
350
351
  read(runtimeId: string): RuntimeRegistrySnapshot | undefined;
351
352
  write(snapshot: RuntimeRegistrySnapshot): RuntimeRegistrySnapshot;
@@ -56,9 +56,19 @@ export class RuntimeRegistry {
56
56
  registryDir;
57
57
  constructor(rootDir) {
58
58
  this.registryDir = path.join(rootDir ?? resolveNexusStateDir(), 'runtime-registry');
59
- fs.mkdirSync(this.registryDir, { recursive: true });
59
+ this.ensureDir();
60
+ }
61
+ ensureDir() {
62
+ // Re-create on every entry. The directory may be wiped by `nexus-prime reset`,
63
+ // a stale-clean script, or the user — without this, list/write/pruneStale will
64
+ // ENOENT and crash nexus_doctor / nexus_runtime_health / nexus_orchestrate.
65
+ try {
66
+ fs.mkdirSync(this.registryDir, { recursive: true });
67
+ }
68
+ catch { /* best-effort */ }
60
69
  }
61
70
  list() {
71
+ this.ensureDir();
62
72
  this.pruneStale();
63
73
  const now = Date.now();
64
74
  return fs.readdirSync(this.registryDir)
@@ -72,6 +82,7 @@ export class RuntimeRegistry {
72
82
  .sort((left, right) => right.lastActivityAt - left.lastActivityAt);
73
83
  }
74
84
  read(runtimeId) {
85
+ this.ensureDir();
75
86
  const target = this.snapshotPath(runtimeId);
76
87
  if (!fs.existsSync(target))
77
88
  return undefined;
@@ -104,6 +115,7 @@ export class RuntimeRegistry {
104
115
  skipReasons: snapshot.skipReasons ?? [],
105
116
  lastToolCalls: snapshot.lastToolCalls ?? [],
106
117
  };
118
+ this.ensureDir();
107
119
  fs.writeFileSync(this.snapshotPath(snapshot.runtimeId), JSON.stringify(normalized, null, 2), 'utf8');
108
120
  return normalized;
109
121
  }
@@ -117,6 +129,7 @@ export class RuntimeRegistry {
117
129
  this.pruneStale();
118
130
  }
119
131
  pruneStale() {
132
+ this.ensureDir();
120
133
  const now = Date.now();
121
134
  for (const entry of fs.readdirSync(this.registryDir)) {
122
135
  if (!entry.endsWith('.json'))
@@ -0,0 +1,7 @@
1
+ import type { SynapseCoordinationSignal, SynapseProviders } from '../types.js';
2
+ /**
3
+ * Wrap providers.coordination.publish() so a coordination failure can never
4
+ * abort a sortie or mandate. Failures are emitted as a structured event so
5
+ * they remain observable instead of being silently swallowed.
6
+ */
7
+ export declare function safePublish(coordination: SynapseProviders['coordination'] | undefined, signal: SynapseCoordinationSignal, stage?: string): void;
@@ -0,0 +1,23 @@
1
+ import { nexusEventBus } from '../../engines/event-bus.js';
2
+ /**
3
+ * Wrap providers.coordination.publish() so a coordination failure can never
4
+ * abort a sortie or mandate. Failures are emitted as a structured event so
5
+ * they remain observable instead of being silently swallowed.
6
+ */
7
+ export function safePublish(coordination, signal, stage = 'coordination') {
8
+ if (!coordination)
9
+ return;
10
+ try {
11
+ coordination.publish(signal);
12
+ }
13
+ catch (err) {
14
+ nexusEventBus.emit('synapse.coordination.publish.failed', {
15
+ stage,
16
+ phase: signal.phase,
17
+ operativeId: signal.operativeId,
18
+ missionId: signal.workItemId,
19
+ sortieId: signal.sortieId,
20
+ error: err instanceof Error ? err.message : String(err),
21
+ });
22
+ }
23
+ }
@@ -6,6 +6,7 @@ import { matchNPAssets } from './asset-matcher.js';
6
6
  import { insertStrikeTeam, insertMission, getStrikeTeam } from '../missions/crud.js';
7
7
  import { insertOperative, updateOperativeMission } from '../operatives/crud.js';
8
8
  import { SynapseConfig } from '../config.js';
9
+ import { safePublish } from '../coordination/safe-publish.js';
9
10
  function unique(values) {
10
11
  return [...new Set(values)];
11
12
  }
@@ -122,19 +123,16 @@ export async function executeMandatePipeline(db, mandateText, providers, opts =
122
123
  worklistId,
123
124
  correlationId: mission.correlationId ?? mission.id,
124
125
  });
125
- try {
126
- providers.coordination?.publish({
127
- phase: 'mandate',
128
- summary: `Mission assigned: ${mission.title}`,
129
- strikeTeamId: teamId,
130
- worklistId,
131
- workItemId: mission.id,
132
- operativeId,
133
- correlationId: mission.correlationId ?? mission.id,
134
- status: 'assigned',
135
- });
136
- }
137
- catch { /* non-fatal: coordination publish failure should not abort mandate */ }
126
+ safePublish(providers.coordination, {
127
+ phase: 'mandate',
128
+ summary: `Mission assigned: ${mission.title}`,
129
+ strikeTeamId: teamId,
130
+ worklistId,
131
+ workItemId: mission.id,
132
+ operativeId,
133
+ correlationId: mission.correlationId ?? mission.id,
134
+ status: 'assigned',
135
+ }, 'mandate');
138
136
  return mission.id;
139
137
  });
140
138
  insertStrikeTeam(db, {
@@ -152,17 +150,14 @@ export async function executeMandatePipeline(db, mandateText, providers, opts =
152
150
  worklistId,
153
151
  correlationId: teamId,
154
152
  });
155
- try {
156
- providers.coordination?.publish({
157
- phase: 'mandate',
158
- summary: `Strike team deployed for mandate: ${mandateText.slice(0, 120)}`,
159
- strikeTeamId: teamId,
160
- worklistId,
161
- correlationId: teamId,
162
- status: 'deployed',
163
- });
164
- }
165
- catch { /* non-fatal: coordination publish failure should not abort mandate */ }
153
+ safePublish(providers.coordination, {
154
+ phase: 'mandate',
155
+ summary: `Strike team deployed for mandate: ${mandateText.slice(0, 120)}`,
156
+ strikeTeamId: teamId,
157
+ worklistId,
158
+ correlationId: teamId,
159
+ status: 'deployed',
160
+ }, 'mandate');
166
161
  providers.memory.store(`[Synapse:Mandate] ${mandateText}`, 0.85, ['#synapse', '#mandate', `#team:${teamId}`], undefined, 0, {
167
162
  provenance: {
168
163
  source: 'runtime',
@@ -12,6 +12,8 @@ import { enforceBudget } from '../budgets/enforcer.js';
12
12
  import { getMission, updateMissionStatus, updateStrikeTeamStatus } from '../missions/crud.js';
13
13
  import { getOperative, updateOperativeMission } from '../operatives/crud.js';
14
14
  import { transitionOperative } from '../operatives/state-machine.js';
15
+ import { safePublish as safePublishImpl } from '../coordination/safe-publish.js';
16
+ const safePublish = (coordination, signal) => safePublishImpl(coordination, signal, 'sortie');
15
17
  function mapSortie(row) {
16
18
  return {
17
19
  id: row.id,
@@ -88,7 +90,7 @@ export async function runSortie(db, operative, providers) {
88
90
  if (providers.claimWorkItem) {
89
91
  const lock = await providers.claimWorkItem(mission.id, operative.id);
90
92
  if (!lock) {
91
- providers.coordination?.publish({
93
+ safePublish(providers.coordination, {
92
94
  phase: 'worklist',
93
95
  summary: `Work item ${mission.id} is already claimed`,
94
96
  strikeTeamId: mission.strikeTeamId,
@@ -118,7 +120,7 @@ export async function runSortie(db, operative, providers) {
118
120
  worklistId,
119
121
  correlationId,
120
122
  });
121
- providers.coordination?.publish({
123
+ safePublish(providers.coordination, {
122
124
  phase: 'sortie',
123
125
  summary: `Sortie started for ${mission.title}`,
124
126
  strikeTeamId: mission.strikeTeamId,
@@ -207,7 +209,7 @@ export async function runSortie(db, operative, providers) {
207
209
  status: report.status,
208
210
  tokensUsed: report.tokensUsed,
209
211
  });
210
- providers.coordination?.publish({
212
+ safePublish(providers.coordination, {
211
213
  phase: 'field-report',
212
214
  summary: `Sortie ${sortie.id} finished with ${report.status}`,
213
215
  strikeTeamId: mission.strikeTeamId,
@@ -238,7 +240,7 @@ export async function runSortie(db, operative, providers) {
238
240
  correlationId,
239
241
  error: String(error?.message ?? error),
240
242
  });
241
- providers.coordination?.publish({
243
+ safePublish(providers.coordination, {
242
244
  phase: 'field-report',
243
245
  summary: `Sortie ${sortie.id} failed for ${mission.title}`,
244
246
  strikeTeamId: mission.strikeTeamId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-prime",
3
- "version": "7.0.0",
3
+ "version": "7.0.1",
4
4
  "description": "Local-first MCP control plane for coding agents with bootstrap-orchestrate execution, memory fabric, token budgeting, and worktree-backed swarms",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -77,7 +77,7 @@
77
77
  "express": "^4.18.2",
78
78
  "js-yaml": "^4.1.1",
79
79
  "pino": "^8.17.2",
80
- "uuid": "^9.0.1",
80
+ "uuid": "^14.0.0",
81
81
  "ws": "^8.16.0",
82
82
  "zod": "^3.22.4"
83
83
  },
@@ -99,7 +99,7 @@
99
99
  "@types/jest": "^29.5.11",
100
100
  "@types/js-yaml": "^4.0.9",
101
101
  "@types/node": "^20.11.0",
102
- "@types/uuid": "^9.0.7",
102
+ "@types/uuid": "^11.0.0",
103
103
  "@types/ws": "^8.5.10",
104
104
  "@typescript-eslint/eslint-plugin": "^6.19.0",
105
105
  "@typescript-eslint/parser": "^6.19.0",