nexus-agents 8.55.8 → 8.56.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.
Files changed (24) hide show
  1. package/dist/{chunk-WL336Y4R.js → chunk-5W55WXLY.js} +2 -2
  2. package/dist/{chunk-MUSPT4J4.js → chunk-BWP3RTR2.js} +2 -2
  3. package/dist/{chunk-DZHGR5UO.js → chunk-HXITPFTR.js} +7 -7
  4. package/dist/{chunk-IEF3YTEH.js → chunk-M2ZQXDSO.js} +3 -3
  5. package/dist/{chunk-4XRGCSFR.js → chunk-MO6FSEFJ.js} +265 -148
  6. package/dist/{chunk-4XRGCSFR.js.map → chunk-MO6FSEFJ.js.map} +1 -1
  7. package/dist/{chunk-ZBZSSNRB.js → chunk-UURJGJTW.js} +2 -2
  8. package/dist/cli.d.ts +1 -1
  9. package/dist/cli.js +86 -27
  10. package/dist/cli.js.map +1 -1
  11. package/dist/{consensus-vote-CJKMBM47.js → consensus-vote-JHCRRDH5.js} +4 -4
  12. package/dist/{improvement-review-JIKJHIBZ.js → improvement-review-MTQWL4BT.js} +4 -4
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +6 -6
  15. package/dist/{setup-data-dir-YKEJYHAI.js → setup-data-dir-RXY2PWDX.js} +3 -3
  16. package/package.json +1 -1
  17. /package/dist/{chunk-WL336Y4R.js.map → chunk-5W55WXLY.js.map} +0 -0
  18. /package/dist/{chunk-MUSPT4J4.js.map → chunk-BWP3RTR2.js.map} +0 -0
  19. /package/dist/{chunk-DZHGR5UO.js.map → chunk-HXITPFTR.js.map} +0 -0
  20. /package/dist/{chunk-IEF3YTEH.js.map → chunk-M2ZQXDSO.js.map} +0 -0
  21. /package/dist/{chunk-ZBZSSNRB.js.map → chunk-UURJGJTW.js.map} +0 -0
  22. /package/dist/{consensus-vote-CJKMBM47.js.map → consensus-vote-JHCRRDH5.js.map} +0 -0
  23. /package/dist/{improvement-review-JIKJHIBZ.js.map → improvement-review-MTQWL4BT.js.map} +0 -0
  24. /package/dist/{setup-data-dir-YKEJYHAI.js.map → setup-data-dir-RXY2PWDX.js.map} +0 -0
@@ -7,7 +7,7 @@ import {
7
7
  toolStructuredError,
8
8
  toolSuccessStructured,
9
9
  wrapToolWithTimeout
10
- } from "./chunk-MUSPT4J4.js";
10
+ } from "./chunk-BWP3RTR2.js";
11
11
  import {
12
12
  JsonlStore,
13
13
  SECURITY_KEYWORDS,
@@ -2278,4 +2278,4 @@ export {
2278
2278
  runImprovementReview,
2279
2279
  registerImprovementReviewTool
2280
2280
  };
2281
- //# sourceMappingURL=chunk-WL336Y4R.js.map
2281
+ //# sourceMappingURL=chunk-5W55WXLY.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-ZBZSSNRB.js";
3
+ } from "./chunk-UURJGJTW.js";
4
4
  import {
5
5
  emitClawGuardViolation
6
6
  } from "./chunk-VZSMNIRN.js";
@@ -2378,4 +2378,4 @@ export {
2378
2378
  DEFAULT_CONSENSUS_CONFIG,
2379
2379
  ConsensusMetricsSchema
2380
2380
  };
2381
- //# sourceMappingURL=chunk-MUSPT4J4.js.map
2381
+ //# sourceMappingURL=chunk-BWP3RTR2.js.map
@@ -21,7 +21,7 @@ import {
21
21
  import {
22
22
  DEFAULT_TASK_TTL_MS,
23
23
  clampTaskTtl
24
- } from "./chunk-IEF3YTEH.js";
24
+ } from "./chunk-M2ZQXDSO.js";
25
25
  import {
26
26
  executeExpert
27
27
  } from "./chunk-QMGMD6JB.js";
@@ -62,7 +62,7 @@ import {
62
62
  toJobSummary,
63
63
  updateStage,
64
64
  writeJobCancelled
65
- } from "./chunk-4XRGCSFR.js";
65
+ } from "./chunk-MO6FSEFJ.js";
66
66
  import {
67
67
  allOf,
68
68
  anyOf
@@ -82,7 +82,7 @@ import {
82
82
  } from "./chunk-RNWUIQTZ.js";
83
83
  import {
84
84
  withPrerequisite
85
- } from "./chunk-WL336Y4R.js";
85
+ } from "./chunk-5W55WXLY.js";
86
86
  import {
87
87
  NOOP_NOTIFIER,
88
88
  RateLimiter,
@@ -107,7 +107,7 @@ import {
107
107
  withAuditTrail,
108
108
  withProgressHeartbeat,
109
109
  wrapToolWithTimeout
110
- } from "./chunk-MUSPT4J4.js";
110
+ } from "./chunk-BWP3RTR2.js";
111
111
  import {
112
112
  createAuditTrail,
113
113
  createGraphAuditBridge,
@@ -46182,7 +46182,7 @@ function buildVotingInput(plan, config) {
46182
46182
  }
46183
46183
  async function executeSingleVote(plan, config, log) {
46184
46184
  try {
46185
- const { executeVoting } = await import("./consensus-vote-CJKMBM47.js");
46185
+ const { executeVoting } = await import("./consensus-vote-JHCRRDH5.js");
46186
46186
  const input = buildVotingInput(plan, config);
46187
46187
  const result = await executeVoting(input, log);
46188
46188
  return parseVotingResult(result);
@@ -47980,7 +47980,7 @@ ${contextBlock}`;
47980
47980
  const strategy = config.votingStrategy ?? "higher_order";
47981
47981
  await postProgress(config, "Vote", `Running consensus with ${strategy} strategy...`);
47982
47982
  try {
47983
- const { executeVoting } = await import("./consensus-vote-CJKMBM47.js");
47983
+ const { executeVoting } = await import("./consensus-vote-JHCRRDH5.js");
47984
47984
  const votingResult = await executeVoting(
47985
47985
  {
47986
47986
  proposal: buildVoteProposal(plan, research),
@@ -57179,4 +57179,4 @@ export {
57179
57179
  startPipelineEventBridge,
57180
57180
  shutdownPipelineEventBridge
57181
57181
  };
57182
- //# sourceMappingURL=chunk-DZHGR5UO.js.map
57182
+ //# sourceMappingURL=chunk-HXITPFTR.js.map
@@ -11,7 +11,7 @@ import {
11
11
  NODE_ENGINE_RANGE,
12
12
  VERSION,
13
13
  isNodeVersionSupported
14
- } from "./chunk-ZBZSSNRB.js";
14
+ } from "./chunk-UURJGJTW.js";
15
15
  import {
16
16
  claimGlobalRegistry
17
17
  } from "./chunk-BXP5K5TU.js";
@@ -1314,7 +1314,7 @@ async function runDoctorFix(result) {
1314
1314
  writeLine2("\u2500".repeat(40));
1315
1315
  let fixCount = 0;
1316
1316
  if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
1317
- const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-YKEJYHAI.js");
1317
+ const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-RXY2PWDX.js");
1318
1318
  const outcome = describeDataDirFix2(initDataDirectories2());
1319
1319
  writeLine2(outcome.line);
1320
1320
  if (outcome.counted) fixCount++;
@@ -1416,4 +1416,4 @@ export {
1416
1416
  startStdioServer,
1417
1417
  closeServer
1418
1418
  };
1419
- //# sourceMappingURL=chunk-IEF3YTEH.js.map
1419
+ //# sourceMappingURL=chunk-M2ZQXDSO.js.map
@@ -25,10 +25,10 @@ import {
25
25
  toolSuccessStructured,
26
26
  withProgressHeartbeat,
27
27
  wrapToolWithTimeout
28
- } from "./chunk-MUSPT4J4.js";
28
+ } from "./chunk-BWP3RTR2.js";
29
29
  import {
30
30
  VERSION
31
- } from "./chunk-ZBZSSNRB.js";
31
+ } from "./chunk-UURJGJTW.js";
32
32
  import {
33
33
  UNRESOLVED_MODEL_ID,
34
34
  assessPanelIndependence,
@@ -6765,31 +6765,164 @@ function warnOnce(toolName, logger11) {
6765
6765
  }
6766
6766
 
6767
6767
  // src/mcp/jobs/job-result-store.ts
6768
- import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync, writeFileSync as writeFileSync3, chmodSync } from "node:fs";
6768
+ import {
6769
+ existsSync as existsSync5,
6770
+ readFileSync as readFileSync6,
6771
+ readdirSync,
6772
+ unlinkSync as unlinkSync2,
6773
+ writeFileSync as writeFileSync4,
6774
+ chmodSync
6775
+ } from "node:fs";
6776
+ import { z as z10 } from "zod";
6777
+
6778
+ // src/mcp/jobs/job-idempotency.ts
6779
+ import { createHash as createHash2, randomUUID } from "node:crypto";
6780
+ import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
6769
6781
  import { z as z9 } from "zod";
6770
- var logger7 = createLogger({ component: "job-result-store" });
6771
- var JobStatusSchema = z9.enum(["pending", "complete", "failed", "cancelled"]);
6772
- var JobResultSchema = z9.object({
6773
- /** Format version. Currently `1` — bump if the shape changes. */
6782
+ var logger7 = createLogger({ component: "job-idempotency" });
6783
+ var IdempotencyIndexEntrySchema = z9.object({
6774
6784
  v: z9.literal(1),
6785
+ /** MCP tool name (e.g. `orchestrate`, `run_workflow`, `consensus_vote`). */
6786
+ tool: z9.string().min(1),
6787
+ /** Caller-supplied idempotency key. */
6788
+ key: z9.string().min(1),
6789
+ /** sha256 of the canonical input JSON. */
6790
+ inputsHash: z9.string().length(64),
6791
+ /** Existing jobId for this (tool, key, inputs) tuple. */
6775
6792
  jobId: z9.string().min(1),
6793
+ /** ISO timestamp of first dispatch. */
6794
+ createdAt: z9.iso.datetime()
6795
+ });
6796
+ function sha256Hex(input) {
6797
+ return createHash2("sha256").update(input).digest("hex");
6798
+ }
6799
+ function canonicalJsonStringify(value) {
6800
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
6801
+ if (Array.isArray(value)) return `[${value.map(canonicalJsonStringify).join(",")}]`;
6802
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
6803
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJsonStringify(v)}`).join(",")}}`;
6804
+ }
6805
+ function computeInputsHash(inputs) {
6806
+ return sha256Hex(canonicalJsonStringify(inputs));
6807
+ }
6808
+ function indexFilePath(tool, key) {
6809
+ const filename = `key-${sha256Hex(`${tool}:${key}`)}.json`;
6810
+ return nexusDataPathEnsure("jobs", filename);
6811
+ }
6812
+ function indexFilePathReadOnly(tool, key) {
6813
+ const filename = `key-${sha256Hex(`${tool}:${key}`)}.json`;
6814
+ return nexusDataPath("jobs", filename);
6815
+ }
6816
+ function resolveIdempotency(tool, idempotencyKey, inputs, randomFallback = randomUUID) {
6817
+ if (idempotencyKey === void 0 || idempotencyKey === "") {
6818
+ return { kind: "fresh", jobId: randomFallback() };
6819
+ }
6820
+ const incomingInputsHash = computeInputsHash(inputs);
6821
+ const path2 = indexFilePathReadOnly(tool, idempotencyKey);
6822
+ if (existsSync4(path2)) {
6823
+ const entry = readIndexEntry(path2);
6824
+ if (entry !== null) {
6825
+ if (entry.inputsHash === incomingInputsHash) {
6826
+ return { kind: "replay", jobId: entry.jobId, entry };
6827
+ }
6828
+ return {
6829
+ kind: "collision",
6830
+ existingInputsHash: entry.inputsHash,
6831
+ incomingInputsHash,
6832
+ existingJobId: entry.jobId
6833
+ };
6834
+ }
6835
+ logger7.warn("Idempotency index file unreadable; treating as fresh dispatch", { tool, path: path2 });
6836
+ }
6837
+ const jobId = `job-${tool}-${sha256Hex(`${tool}:${idempotencyKey}:${incomingInputsHash}`).slice(0, 16)}`;
6838
+ return { kind: "fresh", jobId };
6839
+ }
6840
+ function registerIdempotentJob(params) {
6841
+ const path2 = indexFilePath(params.tool, params.idempotencyKey);
6842
+ if (existsSync4(path2)) {
6843
+ logger7.debug("Idempotency index entry already present; skipping write", {
6844
+ tool: params.tool,
6845
+ jobId: params.jobId
6846
+ });
6847
+ return;
6848
+ }
6849
+ const entry = {
6850
+ v: 1,
6851
+ tool: params.tool,
6852
+ key: params.idempotencyKey,
6853
+ inputsHash: computeInputsHash(params.inputs),
6854
+ jobId: params.jobId,
6855
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
6856
+ };
6857
+ writeFileSync3(path2, JSON.stringify(entry, null, 2));
6858
+ logger7.debug("Wrote idempotency index entry", { tool: params.tool, jobId: params.jobId });
6859
+ }
6860
+ function shortCircuitOrFreshJobId(params) {
6861
+ const r = resolveIdempotency(
6862
+ params.tool,
6863
+ params.idempotencyKey,
6864
+ params.inputs,
6865
+ params.freshJobId
6866
+ );
6867
+ if (r.kind === "collision") {
6868
+ return { kind: "shortCircuit", value: params.collisionEnvelope(r.existingJobId) };
6869
+ }
6870
+ if (r.kind === "replay") {
6871
+ return { kind: "shortCircuit", value: params.replayEnvelope(r.jobId) };
6872
+ }
6873
+ return { kind: "continue", jobId: r.jobId };
6874
+ }
6875
+ function readIndexEntry(path2) {
6876
+ try {
6877
+ const raw = JSON.parse(readFileSync5(path2, "utf-8"));
6878
+ const parsed = IdempotencyIndexEntrySchema.safeParse(raw);
6879
+ if (!parsed.success) {
6880
+ logger7.warn("Idempotency index file failed schema check", { path: path2 });
6881
+ return null;
6882
+ }
6883
+ return parsed.data;
6884
+ } catch (err2) {
6885
+ logger7.warn("Idempotency index file read failed", {
6886
+ path: path2,
6887
+ error: err2 instanceof Error ? err2.message : String(err2)
6888
+ });
6889
+ return null;
6890
+ }
6891
+ }
6892
+
6893
+ // src/mcp/jobs/job-result-store.ts
6894
+ var logger8 = createLogger({ component: "job-result-store" });
6895
+ var JobStatusSchema = z10.enum(["pending", "complete", "failed", "cancelled"]);
6896
+ var JobResultSchema = z10.object({
6897
+ /** Format version. Currently `1` — bump if the shape changes. */
6898
+ v: z10.literal(1),
6899
+ jobId: z10.string().min(1),
6776
6900
  /** Tool that was invoked (e.g. `orchestrate`). */
6777
- toolName: z9.string().min(1),
6901
+ toolName: z10.string().min(1),
6778
6902
  status: JobStatusSchema,
6779
- createdAt: z9.iso.datetime(),
6903
+ createdAt: z10.iso.datetime(),
6780
6904
  /** Set when the job leaves `pending` — either via `complete` or `failed` or `cancelled`. */
6781
- completedAt: z9.iso.datetime().optional(),
6905
+ completedAt: z10.iso.datetime().optional(),
6782
6906
  /**
6783
6907
  * Structured payload the synchronous mode would have returned. Present
6784
6908
  * only when `status === 'complete'`. Shape is tool-specific — readers
6785
6909
  * cast to the tool's known output type after status check.
6786
6910
  */
6787
- result: z9.unknown().optional(),
6911
+ result: z10.unknown().optional(),
6788
6912
  /**
6789
6913
  * Failure message when `status === 'failed'`. Cannot be paired with
6790
6914
  * `result` — the discriminator is `status`.
6791
6915
  */
6792
- error: z9.string().optional(),
6916
+ error: z10.string().optional(),
6917
+ /**
6918
+ * Machine-readable reason for a `failed` record that was NOT settled by the
6919
+ * process that ran the job (#6224). `abandoned` is written only by
6920
+ * {@link pruneJobRecords}: the record was `pending` past the runaway guard
6921
+ * (so no live process could still own it) AND past the retention window, and
6922
+ * the sweep rewrote it rather than deleting it. Absent on every record the
6923
+ * job's own process wrote — `error` alone carries those failures.
6924
+ */
6925
+ errorKind: z10.literal("abandoned").optional(),
6793
6926
  /**
6794
6927
  * Whether the tool's `run` callback accepts `runAsJob`'s `AbortSignal`
6795
6928
  * (#4972).
@@ -6805,7 +6938,7 @@ var JobResultSchema = z9.object({
6805
6938
  * "cancellation can reach this tool", not "the work stopped". Absent means
6806
6939
  * the writer did not report it, which is not the same as `false`.
6807
6940
  */
6808
- signalAccepted: z9.boolean().optional(),
6941
+ signalAccepted: z10.boolean().optional(),
6809
6942
  /**
6810
6943
  * `VERSION` of the nexus-agents process that WROTE this record (#5008) —
6811
6944
  * i.e. the build that ran the job, not the build that reads it back.
@@ -6826,28 +6959,36 @@ var JobResultSchema = z9.object({
6826
6959
  * `'dev'` — readers MUST run it through {@link isMeasuredBuildVersion}
6827
6960
  * before treating two stamps as comparable.
6828
6961
  */
6829
- producerVersion: z9.string().optional()
6962
+ producerVersion: z10.string().optional()
6830
6963
  });
6831
6964
  var UNMEASURED_BUILD_VERSION = "dev";
6832
6965
  function isMeasuredBuildVersion(version) {
6833
6966
  return version !== void 0 && version !== "" && version !== UNMEASURED_BUILD_VERSION;
6834
6967
  }
6968
+ var ASYNC_JOB_BODY_CLASS = "async-job-body";
6969
+ var ABANDONED_TERMINAL_WRITE_SLACK_MS = 3e4;
6835
6970
  function isAbandonedJob(record, nowMs) {
6971
+ return isAbandonedAt(record, nowMs, abandonedAfterMs());
6972
+ }
6973
+ function abandonedAfterMs() {
6974
+ return resolveClassGuardMs(ASYNC_JOB_BODY_CLASS) + ABANDONED_TERMINAL_WRITE_SLACK_MS;
6975
+ }
6976
+ function isAbandonedAt(record, nowMs, abandonedAfter) {
6836
6977
  if (record.status !== "pending") return false;
6837
6978
  const startedMs = Date.parse(record.createdAt);
6838
- return nowMs - startedMs > OPERATION_CLASSES["async-job-body"].guardMs;
6979
+ return nowMs - startedMs > abandonedAfter;
6839
6980
  }
6840
6981
  function jobResultPath(jobId) {
6841
6982
  return nexusDataPathEnsure("jobs", `result-${jobId}.json`);
6842
6983
  }
6843
6984
  function persistJobRecord(path2, record) {
6844
- writeFileSync3(path2, JSON.stringify(record, null, 2), { mode: 384 });
6985
+ writeFileSync4(path2, JSON.stringify(record, null, 2), { mode: 384 });
6845
6986
  chmodSync(path2, 384);
6846
6987
  }
6847
6988
  function writeJobPending(jobId, toolName, signalAccepted, producerVersion = VERSION) {
6848
6989
  const path2 = jobResultPath(jobId);
6849
- if (existsSync4(path2)) {
6850
- logger7.debug("Job result file already exists \u2014 leaving in place", { jobId });
6990
+ if (existsSync5(path2)) {
6991
+ logger8.debug("Job result file already exists \u2014 leaving in place", { jobId });
6851
6992
  return;
6852
6993
  }
6853
6994
  const record = {
@@ -6860,12 +7001,12 @@ function writeJobPending(jobId, toolName, signalAccepted, producerVersion = VERS
6860
7001
  producerVersion
6861
7002
  };
6862
7003
  persistJobRecord(path2, record);
6863
- logger7.debug("Wrote pending job record", { jobId, toolName, signalAccepted });
7004
+ logger8.debug("Wrote pending job record", { jobId, toolName, signalAccepted });
6864
7005
  }
6865
7006
  function writeJobComplete(jobId, toolName, result, producerVersion = VERSION) {
6866
7007
  const existing = readJobResult(jobId);
6867
7008
  if (existing?.status === "cancelled") {
6868
- logger7.debug("Skipping complete write \u2014 job already cancelled (preserving cancellation)", {
7009
+ logger8.debug("Skipping complete write \u2014 job already cancelled (preserving cancellation)", {
6869
7010
  jobId,
6870
7011
  toolName
6871
7012
  });
@@ -6882,12 +7023,12 @@ function writeJobComplete(jobId, toolName, result, producerVersion = VERSION) {
6882
7023
  producerVersion
6883
7024
  };
6884
7025
  persistJobRecord(jobResultPath(jobId), record);
6885
- logger7.debug("Wrote complete job record", { jobId, toolName });
7026
+ logger8.debug("Wrote complete job record", { jobId, toolName });
6886
7027
  }
6887
7028
  function writeJobFailed(jobId, toolName, error, producerVersion = VERSION) {
6888
7029
  const existing = readJobResult(jobId);
6889
7030
  if (existing?.status === "cancelled") {
6890
- logger7.debug("Skipping failed write \u2014 job already cancelled (preserving cancellation)", {
7031
+ logger8.debug("Skipping failed write \u2014 job already cancelled (preserving cancellation)", {
6891
7032
  jobId,
6892
7033
  toolName
6893
7034
  });
@@ -6904,7 +7045,7 @@ function writeJobFailed(jobId, toolName, error, producerVersion = VERSION) {
6904
7045
  producerVersion
6905
7046
  };
6906
7047
  persistJobRecord(jobResultPath(jobId), record);
6907
- logger7.debug("Wrote failed job record", { jobId, toolName, error });
7048
+ logger8.debug("Wrote failed job record", { jobId, toolName, error });
6908
7049
  }
6909
7050
  function writeJobCancelled(jobId, toolName, reason, producerVersion = VERSION) {
6910
7051
  const record = {
@@ -6918,21 +7059,21 @@ function writeJobCancelled(jobId, toolName, reason, producerVersion = VERSION) {
6918
7059
  producerVersion
6919
7060
  };
6920
7061
  persistJobRecord(jobResultPath(jobId), record);
6921
- logger7.debug("Wrote cancelled job record", { jobId, toolName, reason });
7062
+ logger8.debug("Wrote cancelled job record", { jobId, toolName, reason });
6922
7063
  }
6923
7064
  function readJobResult(jobId) {
6924
7065
  const path2 = nexusDataPath("jobs", `result-${jobId}.json`);
6925
- if (!existsSync4(path2)) return null;
7066
+ if (!existsSync5(path2)) return null;
6926
7067
  try {
6927
- const raw = JSON.parse(readFileSync5(path2, "utf-8"));
7068
+ const raw = JSON.parse(readFileSync6(path2, "utf-8"));
6928
7069
  const parsed = JobResultSchema.safeParse(raw);
6929
7070
  if (!parsed.success) {
6930
- logger7.warn("Job result file failed schema check", { jobId, path: path2 });
7071
+ logger8.warn("Job result file failed schema check", { jobId, path: path2 });
6931
7072
  return null;
6932
7073
  }
6933
7074
  return parsed.data;
6934
7075
  } catch (err2) {
6935
- logger7.warn("Job result file unreadable", {
7076
+ logger8.warn("Job result file unreadable", {
6936
7077
  jobId,
6937
7078
  path: path2,
6938
7079
  error: err2 instanceof Error ? err2.message : String(err2)
@@ -6952,14 +7093,14 @@ function toJobSummary(record) {
6952
7093
  }
6953
7094
  function listJobsWithDiagnostics() {
6954
7095
  const dir = nexusDataPath("jobs");
6955
- if (!existsSync4(dir)) {
7096
+ if (!existsSync5(dir)) {
6956
7097
  return { jobs: [], diagnostics: { dirUnreadable: false, unparseableRecords: 0 } };
6957
7098
  }
6958
7099
  let entries;
6959
7100
  try {
6960
7101
  entries = readdirSync(dir);
6961
7102
  } catch (err2) {
6962
- logger7.warn("jobs directory unreadable", {
7103
+ logger8.warn("jobs directory unreadable", {
6963
7104
  dir,
6964
7105
  error: err2 instanceof Error ? err2.message : String(err2)
6965
7106
  });
@@ -6984,6 +7125,94 @@ function listJobsWithDiagnostics() {
6984
7125
  diagnostics: { dirUnreadable: false, unparseableRecords }
6985
7126
  };
6986
7127
  }
7128
+ var JOB_RECORD_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
7129
+ var JOB_PRUNE_SWEEP_INTERVAL_MS = 36e5;
7130
+ function pruneJobRecords(options) {
7131
+ const dir = nexusDataPath("jobs");
7132
+ if (!existsSync5(dir)) {
7133
+ return { deleted: 0, markedAbandoned: 0, kept: 0, unreadable: 0, deletedKeys: 0 };
7134
+ }
7135
+ const entries = readdirSync(dir);
7136
+ const sweep = {
7137
+ nowMs: options.nowMs,
7138
+ retentionMs: options.retentionMs,
7139
+ dryRun: options.dryRun === true,
7140
+ abandonedAfter: abandonedAfterMs()
7141
+ };
7142
+ const counts = { deleted: 0, markedAbandoned: 0, kept: 0, unreadable: 0, deletedKeys: 0 };
7143
+ const removedJobIds = /* @__PURE__ */ new Set();
7144
+ const keyEntries = entries.filter((entry) => /^key-.+\.json$/.test(entry));
7145
+ for (const entry of entries) {
7146
+ const jobId = /^result-(.+)\.json$/.exec(entry)?.[1];
7147
+ if (jobId === void 0) continue;
7148
+ const verdict = sweepRecord(jobId, entry, sweep);
7149
+ counts[verdict] += 1;
7150
+ if (verdict === "deleted") removedJobIds.add(jobId);
7151
+ }
7152
+ for (const entry of keyEntries) {
7153
+ const verdict = sweepKeyEntry(entry, removedJobIds, sweep);
7154
+ if (verdict !== null) counts[verdict] += 1;
7155
+ }
7156
+ logger8.debug("Job record sweep", { dir, ...counts, dryRun: sweep.dryRun });
7157
+ return counts;
7158
+ }
7159
+ function sweepRecord(jobId, entry, sweep) {
7160
+ const record = readJobResult(jobId);
7161
+ if (record === null) return "unreadable";
7162
+ const path2 = nexusDataPath("jobs", entry);
7163
+ if (record.status === "pending") {
7164
+ const pastGuard = isAbandonedAt(record, sweep.nowMs, sweep.abandonedAfter);
7165
+ const pastWindow = isOlderThan(record.createdAt, sweep.nowMs, sweep.retentionMs);
7166
+ if (!(pastGuard && pastWindow)) return "kept";
7167
+ if (!sweep.dryRun) persistJobRecord(path2, abandonedRecord(record, sweep.nowMs));
7168
+ return "markedAbandoned";
7169
+ }
7170
+ const settledAt = record.completedAt ?? record.createdAt;
7171
+ if (!isOlderThan(settledAt, sweep.nowMs, sweep.retentionMs)) return "kept";
7172
+ if (!sweep.dryRun) unlinkSync2(path2);
7173
+ return "deleted";
7174
+ }
7175
+ function sweepKeyEntry(entry, removedJobIds, sweep) {
7176
+ const path2 = nexusDataPath("jobs", entry);
7177
+ const indexEntry = readIndexEntry(path2);
7178
+ if (indexEntry === null) return "unreadable";
7179
+ if (!isOlderThan(indexEntry.createdAt, sweep.nowMs, sweep.retentionMs)) return null;
7180
+ const recordGone = removedJobIds.has(indexEntry.jobId) || !existsSync5(nexusDataPath("jobs", `result-${indexEntry.jobId}.json`));
7181
+ if (!recordGone) return null;
7182
+ if (!sweep.dryRun) unlinkSync2(path2);
7183
+ return "deletedKeys";
7184
+ }
7185
+ function isOlderThan(isoTimestamp, nowMs, windowMs) {
7186
+ return nowMs - Date.parse(isoTimestamp) > windowMs;
7187
+ }
7188
+ function abandonedRecord(record, nowMs) {
7189
+ const producer = record.producerVersion ?? "unrecorded";
7190
+ return {
7191
+ ...record,
7192
+ status: "failed",
7193
+ completedAt: new Date(nowMs).toISOString(),
7194
+ error: `abandoned: pending since ${record.createdAt} under producer ${producer}; no process settled it within the runaway guard, and the retention sweep marked it failed rather than deleting the evidence`,
7195
+ errorKind: "abandoned",
7196
+ producerVersion: VERSION
7197
+ };
7198
+ }
7199
+ var lastSweepAtMsByDir = /* @__PURE__ */ new Map();
7200
+ function pruneJobRecordsIfDue(nowMs) {
7201
+ const dir = nexusDataPath("jobs");
7202
+ const lastSweepAtMs = lastSweepAtMsByDir.get(dir);
7203
+ if (lastSweepAtMs !== void 0 && nowMs - lastSweepAtMs < JOB_PRUNE_SWEEP_INTERVAL_MS) {
7204
+ return null;
7205
+ }
7206
+ lastSweepAtMsByDir.set(dir, nowMs);
7207
+ try {
7208
+ return pruneJobRecords({ nowMs, retentionMs: JOB_RECORD_RETENTION_MS });
7209
+ } catch (err2) {
7210
+ logger8.warn("Job record sweep failed", {
7211
+ error: err2 instanceof Error ? err2.message : String(err2)
7212
+ });
7213
+ return null;
7214
+ }
7215
+ }
6987
7216
 
6988
7217
  // src/mcp/jobs/job-abort-registry.ts
6989
7218
  var controllers = /* @__PURE__ */ new Map();
@@ -7003,121 +7232,6 @@ function unregisterJobAbort(jobId) {
7003
7232
  controllers.delete(jobId);
7004
7233
  }
7005
7234
 
7006
- // src/mcp/jobs/job-idempotency.ts
7007
- import { createHash as createHash2, randomUUID } from "node:crypto";
7008
- import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
7009
- import { z as z10 } from "zod";
7010
- var logger8 = createLogger({ component: "job-idempotency" });
7011
- var IdempotencyIndexEntrySchema = z10.object({
7012
- v: z10.literal(1),
7013
- /** MCP tool name (e.g. `orchestrate`, `run_workflow`, `consensus_vote`). */
7014
- tool: z10.string().min(1),
7015
- /** Caller-supplied idempotency key. */
7016
- key: z10.string().min(1),
7017
- /** sha256 of the canonical input JSON. */
7018
- inputsHash: z10.string().length(64),
7019
- /** Existing jobId for this (tool, key, inputs) tuple. */
7020
- jobId: z10.string().min(1),
7021
- /** ISO timestamp of first dispatch. */
7022
- createdAt: z10.iso.datetime()
7023
- });
7024
- function sha256Hex(input) {
7025
- return createHash2("sha256").update(input).digest("hex");
7026
- }
7027
- function canonicalJsonStringify(value) {
7028
- if (value === null || typeof value !== "object") return JSON.stringify(value);
7029
- if (Array.isArray(value)) return `[${value.map(canonicalJsonStringify).join(",")}]`;
7030
- const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
7031
- return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJsonStringify(v)}`).join(",")}}`;
7032
- }
7033
- function computeInputsHash(inputs) {
7034
- return sha256Hex(canonicalJsonStringify(inputs));
7035
- }
7036
- function indexFilePath(tool, key) {
7037
- const filename = `key-${sha256Hex(`${tool}:${key}`)}.json`;
7038
- return nexusDataPathEnsure("jobs", filename);
7039
- }
7040
- function indexFilePathReadOnly(tool, key) {
7041
- const filename = `key-${sha256Hex(`${tool}:${key}`)}.json`;
7042
- return nexusDataPath("jobs", filename);
7043
- }
7044
- function resolveIdempotency(tool, idempotencyKey, inputs, randomFallback = randomUUID) {
7045
- if (idempotencyKey === void 0 || idempotencyKey === "") {
7046
- return { kind: "fresh", jobId: randomFallback() };
7047
- }
7048
- const incomingInputsHash = computeInputsHash(inputs);
7049
- const path2 = indexFilePathReadOnly(tool, idempotencyKey);
7050
- if (existsSync5(path2)) {
7051
- const entry = readIndexEntry(path2);
7052
- if (entry !== null) {
7053
- if (entry.inputsHash === incomingInputsHash) {
7054
- return { kind: "replay", jobId: entry.jobId, entry };
7055
- }
7056
- return {
7057
- kind: "collision",
7058
- existingInputsHash: entry.inputsHash,
7059
- incomingInputsHash,
7060
- existingJobId: entry.jobId
7061
- };
7062
- }
7063
- logger8.warn("Idempotency index file unreadable; treating as fresh dispatch", { tool, path: path2 });
7064
- }
7065
- const jobId = `job-${tool}-${sha256Hex(`${tool}:${idempotencyKey}:${incomingInputsHash}`).slice(0, 16)}`;
7066
- return { kind: "fresh", jobId };
7067
- }
7068
- function registerIdempotentJob(params) {
7069
- const path2 = indexFilePath(params.tool, params.idempotencyKey);
7070
- if (existsSync5(path2)) {
7071
- logger8.debug("Idempotency index entry already present; skipping write", {
7072
- tool: params.tool,
7073
- jobId: params.jobId
7074
- });
7075
- return;
7076
- }
7077
- const entry = {
7078
- v: 1,
7079
- tool: params.tool,
7080
- key: params.idempotencyKey,
7081
- inputsHash: computeInputsHash(params.inputs),
7082
- jobId: params.jobId,
7083
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
7084
- };
7085
- writeFileSync4(path2, JSON.stringify(entry, null, 2));
7086
- logger8.debug("Wrote idempotency index entry", { tool: params.tool, jobId: params.jobId });
7087
- }
7088
- function shortCircuitOrFreshJobId(params) {
7089
- const r = resolveIdempotency(
7090
- params.tool,
7091
- params.idempotencyKey,
7092
- params.inputs,
7093
- params.freshJobId
7094
- );
7095
- if (r.kind === "collision") {
7096
- return { kind: "shortCircuit", value: params.collisionEnvelope(r.existingJobId) };
7097
- }
7098
- if (r.kind === "replay") {
7099
- return { kind: "shortCircuit", value: params.replayEnvelope(r.jobId) };
7100
- }
7101
- return { kind: "continue", jobId: r.jobId };
7102
- }
7103
- function readIndexEntry(path2) {
7104
- try {
7105
- const raw = JSON.parse(readFileSync6(path2, "utf-8"));
7106
- const parsed = IdempotencyIndexEntrySchema.safeParse(raw);
7107
- if (!parsed.success) {
7108
- logger8.warn("Idempotency index file failed schema check", { path: path2 });
7109
- return null;
7110
- }
7111
- return parsed.data;
7112
- } catch (err2) {
7113
- logger8.warn("Idempotency index file read failed", {
7114
- path: path2,
7115
- error: err2 instanceof Error ? err2.message : String(err2)
7116
- });
7117
- return null;
7118
- }
7119
- }
7120
-
7121
7235
  // src/mcp/jobs/job-concurrency.ts
7122
7236
  var logger9 = createLogger({ component: "job-concurrency" });
7123
7237
  var DEFAULT_JOB_CAPS = {
@@ -7793,6 +7907,7 @@ function runAsJob(params) {
7793
7907
  return env.busy(suggestRetryAfterMs(params.toolName), params.toolName);
7794
7908
  }
7795
7909
  const jobId = idempotency.jobId;
7910
+ pruneJobRecordsIfDue(getTimeProvider().now());
7796
7911
  writeJobPending(jobId, params.toolName, params.run.length >= 3, params.producerVersion);
7797
7912
  if (params.idempotencyKey !== void 0 && params.idempotencyKey !== "") {
7798
7913
  registerIdempotentJob({
@@ -8532,6 +8647,8 @@ function registerConsensusVoteTool(server, deps) {
8532
8647
 
8533
8648
  export {
8534
8649
  VOTER_ROLES,
8650
+ resolveIdempotency,
8651
+ registerIdempotentJob,
8535
8652
  JobStatusSchema,
8536
8653
  isMeasuredBuildVersion,
8537
8654
  isAbandonedJob,
@@ -8539,9 +8656,9 @@ export {
8539
8656
  readJobResult,
8540
8657
  toJobSummary,
8541
8658
  listJobsWithDiagnostics,
8659
+ JOB_RECORD_RETENTION_MS,
8660
+ pruneJobRecords,
8542
8661
  abortJob,
8543
- resolveIdempotency,
8544
- registerIdempotentJob,
8545
8662
  listTaskStateIds,
8546
8663
  initTaskState,
8547
8664
  appendBlocker,
@@ -8630,4 +8747,4 @@ export {
8630
8747
  CONSENSUS_VOTE_TOOL_SCHEMA,
8631
8748
  registerConsensusVoteTool
8632
8749
  };
8633
- //# sourceMappingURL=chunk-4XRGCSFR.js.map
8750
+ //# sourceMappingURL=chunk-MO6FSEFJ.js.map