@massa-ai/tools-api 1.52.0 → 1.53.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 (2) hide show
  1. package/dist/index.js +962 -109
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -7836,17 +7836,11 @@ function detectRoute(platform, host) {
7836
7836
  if (route === "file")
7837
7837
  return { kind: "proceed" };
7838
7838
  if (route === "marketplace") {
7839
- if (host === "claude")
7839
+ if (host === "claude" || host === "codex")
7840
7840
  return { kind: "proceed" };
7841
- if (host === "codex") {
7842
- return {
7843
- kind: "refuse",
7844
- reason: "codex marketplace-route installs are refused (in-place bundle rewrite would dirty a checkout " + "and break the drift gate) \u2014 use the dev path: MASSA_AI_MODEL_PROFILE + regenerate."
7845
- };
7846
- }
7847
7841
  return {
7848
7842
  kind: "refuse",
7849
- reason: "claude/codex marketplace-route installs are refused (in-place bundle rewrite would dirty a checkout " + "and break the drift gate) \u2014 use the dev path: MASSA_AI_MODEL_PROFILE + regenerate."
7843
+ reason: "marketplace-route installs need an explicit, supported host before switching " + "(claude and codex proceed; no other host resolves a live write target yet) \u2014 " + "use the dev path: MASSA_AI_MODEL_PROFILE + regenerate."
7850
7844
  };
7851
7845
  }
7852
7846
  return {
@@ -8045,6 +8039,61 @@ var init_lock = __esm(() => {
8045
8039
  import fs7 from "fs";
8046
8040
  import os5 from "os";
8047
8041
  import path11 from "path";
8042
+ function splitPluginKey(pluginKey) {
8043
+ const at = pluginKey.indexOf("@");
8044
+ if (at === -1)
8045
+ return null;
8046
+ const pluginName = pluginKey.slice(0, at);
8047
+ const marketplaceName = pluginKey.slice(at + 1);
8048
+ if (!pluginName || !marketplaceName)
8049
+ return null;
8050
+ return { pluginName, marketplaceName };
8051
+ }
8052
+ function isContainedPath(root, candidate) {
8053
+ const rel = path11.relative(root, candidate);
8054
+ return rel === "" || !rel.startsWith("..") && !path11.isAbsolute(rel);
8055
+ }
8056
+ function resolveDirectorySourceRoot(targetHome, pluginKey) {
8057
+ const split = splitPluginKey(pluginKey);
8058
+ if (!split)
8059
+ return;
8060
+ const { pluginName, marketplaceName } = split;
8061
+ const knownMarketplacesPath = path11.join(targetHome, ".claude", "plugins", "known_marketplaces.json");
8062
+ let known;
8063
+ try {
8064
+ known = JSON.parse(fs7.readFileSync(knownMarketplacesPath, "utf8"));
8065
+ } catch {
8066
+ return;
8067
+ }
8068
+ const entry = known?.[marketplaceName];
8069
+ if (!entry || entry.source?.source !== "directory")
8070
+ return;
8071
+ const installLocation = entry.installLocation;
8072
+ if (!installLocation)
8073
+ return null;
8074
+ const manifestPath = path11.join(installLocation, ".claude-plugin", "marketplace.json");
8075
+ let manifest;
8076
+ try {
8077
+ manifest = JSON.parse(fs7.readFileSync(manifestPath, "utf8"));
8078
+ } catch {
8079
+ return null;
8080
+ }
8081
+ const plugin2 = manifest.plugins?.find((p) => p?.name === pluginName);
8082
+ const relSource = plugin2?.source;
8083
+ if (!relSource)
8084
+ return null;
8085
+ const resolvedInstallLocation = path11.resolve(installLocation);
8086
+ const composed = path11.resolve(resolvedInstallLocation, relSource);
8087
+ if (!isContainedPath(resolvedInstallLocation, composed))
8088
+ return null;
8089
+ try {
8090
+ if (!fs7.existsSync(composed))
8091
+ return null;
8092
+ } catch {
8093
+ return null;
8094
+ }
8095
+ return composed;
8096
+ }
8048
8097
  function selectRecord(records) {
8049
8098
  if (records.length === 0)
8050
8099
  return;
@@ -8064,6 +8113,9 @@ function selectRecord(records) {
8064
8113
  function resolveClaudeMarketplaceRoot(opts = {}) {
8065
8114
  const targetHome = opts.targetHome ?? os5.homedir();
8066
8115
  const pluginKey = opts.pluginKey ?? DEFAULT_PLUGIN_KEY;
8116
+ const directoryResult = resolveDirectorySourceRoot(targetHome, pluginKey);
8117
+ if (directoryResult !== undefined)
8118
+ return directoryResult;
8067
8119
  const registryPath = path11.join(targetHome, ".claude", "plugins", "installed_plugins.json");
8068
8120
  let records;
8069
8121
  try {
@@ -8095,6 +8147,7 @@ import fs8 from "fs";
8095
8147
  import path12 from "path";
8096
8148
  import os6 from "os";
8097
8149
  import crypto5 from "crypto";
8150
+ import { execFileSync as execFileSync2 } from "child_process";
8098
8151
  function namedError3(name, message) {
8099
8152
  const err = new SwitchEngineError(message);
8100
8153
  err.name = name;
@@ -8173,6 +8226,46 @@ function matchesGlob(filename, glob) {
8173
8226
  const suffix = glob.slice(starIdx + 1);
8174
8227
  return filename.length >= prefix.length + suffix.length && filename.startsWith(prefix) && filename.endsWith(suffix);
8175
8228
  }
8229
+ function matchingFileNames(dir, glob) {
8230
+ return fs8.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && matchesGlob(e.name, glob)).map((e) => e.name);
8231
+ }
8232
+ function detectGitAvailability(dir) {
8233
+ try {
8234
+ const out = execFileSync2("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], {
8235
+ stdio: ["ignore", "pipe", "ignore"]
8236
+ });
8237
+ return out.toString().trim() === "true" ? "in-repo" : "not-a-repo";
8238
+ } catch (err) {
8239
+ return err.code === "ENOENT" ? "no-git" : "not-a-repo";
8240
+ }
8241
+ }
8242
+ function gitTrackedFileNames(dir, filenames) {
8243
+ if (filenames.length === 0)
8244
+ return new Set;
8245
+ try {
8246
+ const out = execFileSync2("git", ["-C", dir, "ls-files", "--", ...filenames], {
8247
+ stdio: ["ignore", "pipe", "ignore"]
8248
+ });
8249
+ return new Set(out.toString().split(`
8250
+ `).map((line) => line.trim()).filter(Boolean));
8251
+ } catch {
8252
+ return new Set;
8253
+ }
8254
+ }
8255
+ function checkTrackedPathGuard(activeDir, filenames) {
8256
+ if (filenames.length === 0 || !fs8.existsSync(activeDir))
8257
+ return GUARD_PASS;
8258
+ const availability = detectGitAvailability(activeDir);
8259
+ if (availability === "no-git")
8260
+ return GUARD_UNCHECKED;
8261
+ if (availability === "not-a-repo")
8262
+ return GUARD_PASS;
8263
+ const tracked = gitTrackedFileNames(activeDir, filenames);
8264
+ if (tracked.size === 0)
8265
+ return GUARD_PASS;
8266
+ const offending = filenames.find((name) => tracked.has(name));
8267
+ return { blocked: true, path: path12.join(activeDir, offending), unchecked: false };
8268
+ }
8176
8269
  function assertStateWritable(stateFilePath) {
8177
8270
  const dir = path12.dirname(stateFilePath);
8178
8271
  try {
@@ -8302,12 +8395,27 @@ function switchProfile(opts) {
8302
8395
  rows.push({ host: h.host, status: "switched" });
8303
8396
  continue;
8304
8397
  }
8398
+ const candidateNames = matchingFileNames(h.variantDir, h.layout.activeGlob);
8399
+ const guard2 = checkTrackedPathGuard(h.layout.activeDir, candidateNames);
8400
+ if (guard2.blocked) {
8401
+ rows.push({
8402
+ host: h.host,
8403
+ status: "failed",
8404
+ reason: `refusing to write ${guard2.path} \u2014 it is tracked by git and this switch would dirty the checkout`
8405
+ });
8406
+ continue;
8407
+ }
8305
8408
  try {
8306
8409
  const filesChanged = copyVariant(h.host, h.layout, h.variantDir);
8307
8410
  updatePlatform(stateFilePath, h.host, {
8308
8411
  modelProfile: { profile: opts.profile, switchedAt: new Date().toISOString() }
8309
8412
  });
8310
- rows.push({ host: h.host, status: "switched", filesChanged });
8413
+ rows.push({
8414
+ host: h.host,
8415
+ status: "switched",
8416
+ filesChanged,
8417
+ ...guard2.unchecked ? { reason: "tracked-path guard could not verify (git unavailable) \u2014 proceeded unchecked" } : {}
8418
+ });
8311
8419
  } catch (err) {
8312
8420
  rows.push({ host: h.host, status: "failed", reason: err.message });
8313
8421
  }
@@ -8323,7 +8431,7 @@ function orderRows(universe, rows) {
8323
8431
  const byHost = new Map(rows.map((r2) => [r2.host, r2]));
8324
8432
  return HOSTS.filter((h) => universe.includes(h) && byHost.has(h)).map((h) => byHost.get(h));
8325
8433
  }
8326
- var SwitchEngineError, UnknownProfileError = (profile, known) => namedError3("UnknownProfileError", `unknown profile "${profile}" \u2014 installed: ${known.length > 0 ? known.join(", ") : "none"}`), NoHostsDetectedError = () => namedError3("NoHostsDetectedError", "no installed hosts found");
8434
+ var SwitchEngineError, UnknownProfileError = (profile, known) => namedError3("UnknownProfileError", `unknown profile "${profile}" \u2014 installed: ${known.length > 0 ? known.join(", ") : "none"}`), NoHostsDetectedError = () => namedError3("NoHostsDetectedError", "no installed hosts found"), GUARD_PASS, GUARD_UNCHECKED;
8327
8435
  var init_engine = __esm(() => {
8328
8436
  init_hosts();
8329
8437
  init_state();
@@ -8335,6 +8443,8 @@ var init_engine = __esm(() => {
8335
8443
  this.name = "SwitchEngineError";
8336
8444
  }
8337
8445
  };
8446
+ GUARD_PASS = { blocked: false, unchecked: false };
8447
+ GUARD_UNCHECKED = { blocked: false, unchecked: true };
8338
8448
  });
8339
8449
 
8340
8450
  // ../../packages/shared/dist/profile-switch/report.js
@@ -128577,7 +128687,7 @@ var init_git_ref_validation = __esm(() => {
128577
128687
  });
128578
128688
 
128579
128689
  // ../../packages/core/dist/services/symbol/impact-analysis.js
128580
- import { execFileSync as execFileSync2 } from "child_process";
128690
+ import { execFileSync as execFileSync3 } from "child_process";
128581
128691
  function readBfsCteFlag() {
128582
128692
  try {
128583
128693
  return Boolean(config.get("impact")?.bfsCteEnabled);
@@ -128903,19 +129013,19 @@ function defaultDiffRunner(projectPath2, scope, baseBranch, since) {
128903
129013
  let diffRange;
128904
129014
  if (since) {
128905
129015
  try {
128906
- ref = execFileSync2("git", ["-C", projectPath2, "rev-parse", "--verify", `${since}^{commit}`], {
129016
+ ref = execFileSync3("git", ["-C", projectPath2, "rev-parse", "--verify", `${since}^{commit}`], {
128907
129017
  cwd: projectPath2,
128908
129018
  encoding: "utf-8",
128909
129019
  stdio: ["ignore", "pipe", "pipe"]
128910
129020
  }).trim();
128911
129021
  } catch {
128912
- ref = execFileSync2("git", ["-C", projectPath2, "rev-list", "-1", `--before=${since}`, "HEAD"], {
129022
+ ref = execFileSync3("git", ["-C", projectPath2, "rev-list", "-1", `--before=${since}`, "HEAD"], {
128913
129023
  cwd: projectPath2,
128914
129024
  encoding: "utf-8",
128915
129025
  stdio: ["ignore", "pipe", "pipe"]
128916
129026
  }).trim();
128917
129027
  if (!ref) {
128918
- const emptyTree = execFileSync2("git", ["hash-object", "-t", "tree", "--stdin"], {
129028
+ const emptyTree = execFileSync3("git", ["hash-object", "-t", "tree", "--stdin"], {
128919
129029
  cwd: projectPath2,
128920
129030
  encoding: "utf-8",
128921
129031
  input: "",
@@ -128927,7 +129037,7 @@ function defaultDiffRunner(projectPath2, scope, baseBranch, since) {
128927
129037
  }
128928
129038
  args.push(diffRange ?? `${ref}...HEAD`);
128929
129039
  }
128930
- const out = execFileSync2("git", args, {
129040
+ const out = execFileSync3("git", args, {
128931
129041
  cwd: projectPath2,
128932
129042
  encoding: "utf-8",
128933
129043
  stdio: ["ignore", "pipe", "pipe"],
@@ -128939,7 +129049,7 @@ function defaultDiffRunner(projectPath2, scope, baseBranch, since) {
128939
129049
  let untrackedFiltered = 0;
128940
129050
  const merged = new Set(tracked);
128941
129051
  if (includeUntracked) {
128942
- const untracked = execFileSync2("git", ["-C", projectPath2, "ls-files", "--others", "--exclude-standard"], {
129052
+ const untracked = execFileSync3("git", ["-C", projectPath2, "ls-files", "--others", "--exclude-standard"], {
128943
129053
  cwd: projectPath2,
128944
129054
  encoding: "utf-8",
128945
129055
  stdio: ["ignore", "pipe", "pipe"],
@@ -128977,7 +129087,7 @@ var init_impact_analysis = __esm(() => {
128977
129087
  });
128978
129088
 
128979
129089
  // ../../packages/core/dist/services/executor/runtime.js
128980
- import { execFileSync as execFileSync3, execSync } from "child_process";
129090
+ import { execFileSync as execFileSync4, execSync } from "child_process";
128981
129091
  function commandExists(cmd) {
128982
129092
  try {
128983
129093
  const check3 = isWindows ? `where ${cmd}` : `command -v ${cmd}`;
@@ -129010,7 +129120,7 @@ function getVersion(cmd, args = ["--version"], deps) {
129010
129120
  timeout: 5000
129011
129121
  }).trim().split(/\r?\n/)[0];
129012
129122
  }
129013
- return execFileSync3(cmd, args, {
129123
+ return execFileSync4(cmd, args, {
129014
129124
  encoding: "utf-8",
129015
129125
  stdio: ["pipe", "pipe", "pipe"],
129016
129126
  timeout: 5000
@@ -129151,12 +129261,12 @@ var init_runtime = __esm(() => {
129151
129261
 
129152
129262
  // ../../packages/core/dist/services/executor/sandbox.js
129153
129263
  import { realpathSync as realpathSync2 } from "fs";
129154
- import { execFileSync as execFileSync4 } from "child_process";
129264
+ import { execFileSync as execFileSync5 } from "child_process";
129155
129265
  function isDockerAvailable() {
129156
129266
  if (_dockerAvailable !== null)
129157
129267
  return _dockerAvailable;
129158
129268
  try {
129159
- execFileSync4("docker", ["--version"], { stdio: "pipe", timeout: 5000 });
129269
+ execFileSync5("docker", ["--version"], { stdio: "pipe", timeout: 5000 });
129160
129270
  _dockerAvailable = true;
129161
129271
  } catch {
129162
129272
  _dockerAvailable = false;
@@ -129167,7 +129277,7 @@ function isSeatbeltAvailable() {
129167
129277
  if (_seatbeltAvailable !== null)
129168
129278
  return _seatbeltAvailable;
129169
129279
  try {
129170
- execFileSync4("sandbox-exec", ["--version"], { stdio: "pipe", timeout: 5000 });
129280
+ execFileSync5("sandbox-exec", ["--version"], { stdio: "pipe", timeout: 5000 });
129171
129281
  _seatbeltAvailable = true;
129172
129282
  } catch {
129173
129283
  _seatbeltAvailable = false;
@@ -129272,7 +129382,7 @@ var init_sandbox = __esm(() => {
129272
129382
  });
129273
129383
 
129274
129384
  // ../../packages/core/dist/services/executor/executor.js
129275
- import { spawn, execSync as execSync2, execFileSync as execFileSync5 } from "child_process";
129385
+ import { spawn, execSync as execSync2, execFileSync as execFileSync6 } from "child_process";
129276
129386
  import { mkdtempSync, writeFileSync as writeFileSync2, rmSync, realpathSync as realpathSync3 } from "fs";
129277
129387
  import { join as join3, resolve as resolve5, isAbsolute, relative as relative2 } from "path";
129278
129388
  import { tmpdir } from "os";
@@ -129475,7 +129585,7 @@ ${body}`;
129475
129585
  const binPath = srcPath.replace(/\.rs$/, isWin ? ".exe" : "");
129476
129586
  try {
129477
129587
  try {
129478
- execFileSync5("rustc", [srcPath, "-o", binPath], {
129588
+ execFileSync6("rustc", [srcPath, "-o", binPath], {
129479
129589
  cwd,
129480
129590
  timeout: Math.min(timeout, 60000),
129481
129591
  encoding: "utf-8",
@@ -132417,23 +132527,94 @@ var init_scheduler = __esm(() => {
132417
132527
  };
132418
132528
  });
132419
132529
 
132530
+ // ../../packages/core/dist/data/proposal/proposal-payload-validation.js
132531
+ function isRecord(value) {
132532
+ return value !== null && typeof value === "object" && !Array.isArray(value);
132533
+ }
132534
+ function isStringArray(value) {
132535
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
132536
+ }
132537
+ function isString(value) {
132538
+ return typeof value === "string";
132539
+ }
132540
+ function isNumber(value) {
132541
+ return typeof value === "number";
132542
+ }
132543
+ function isValidProposalPayload(kind, value) {
132544
+ const rule = PROPOSAL_PAYLOAD_RULES[kind];
132545
+ if (!Object.keys(value).every((key) => rule.allowedKeys.includes(key)))
132546
+ return false;
132547
+ if (rule.requireNonEmpty && Object.keys(value).length === 0)
132548
+ return false;
132549
+ for (const [field3, fieldRule] of Object.entries(rule.fields)) {
132550
+ const present = value[field3] !== undefined;
132551
+ if (!present) {
132552
+ if (!fieldRule.optional)
132553
+ return false;
132554
+ continue;
132555
+ }
132556
+ if (!fieldRule.validate(value[field3]))
132557
+ return false;
132558
+ }
132559
+ return true;
132560
+ }
132561
+ function assertValidProposalPayload(kind, value) {
132562
+ if (!isRecord(value)) {
132563
+ throw new ProposalPayloadValidationError(kind, `proposal payload must be an object for kind "${kind}"`);
132564
+ }
132565
+ if (!isValidProposalPayload(kind, value)) {
132566
+ throw new ProposalPayloadValidationError(kind, `proposal payload is invalid for kind "${kind}"`);
132567
+ }
132568
+ }
132569
+ var PROPOSAL_PAYLOAD_RULES, ProposalPayloadValidationError;
132570
+ var init_proposal_payload_validation = __esm(() => {
132571
+ PROPOSAL_PAYLOAD_RULES = {
132572
+ "memory.create": {
132573
+ allowedKeys: ["content", "type", "level", "importance", "tags"],
132574
+ fields: {
132575
+ content: { optional: false, validate: isString },
132576
+ type: { optional: true, validate: isString },
132577
+ level: { optional: true, validate: isNumber },
132578
+ importance: { optional: true, validate: isNumber },
132579
+ tags: { optional: true, validate: isStringArray }
132580
+ }
132581
+ },
132582
+ "memory.update": {
132583
+ allowedKeys: ["content", "importance", "tags"],
132584
+ requireNonEmpty: true,
132585
+ fields: {
132586
+ content: { optional: true, validate: isString },
132587
+ importance: { optional: true, validate: isNumber },
132588
+ tags: { optional: true, validate: isStringArray }
132589
+ }
132590
+ },
132591
+ "memory.tag": {
132592
+ allowedKeys: ["tags"],
132593
+ fields: {
132594
+ tags: { optional: false, validate: isStringArray }
132595
+ }
132596
+ }
132597
+ };
132598
+ ProposalPayloadValidationError = class ProposalPayloadValidationError extends Error {
132599
+ kind;
132600
+ statusCode = 400;
132601
+ constructor(kind, message) {
132602
+ super(message);
132603
+ this.kind = kind;
132604
+ this.name = "ProposalPayloadValidationError";
132605
+ }
132606
+ };
132607
+ });
132608
+
132420
132609
  // ../../packages/core/dist/data/proposal/proposal-contract.js
132421
132610
  var PROPOSAL_STATUSES, PROPOSAL_KINDS;
132422
132611
  var init_proposal_contract = __esm(() => {
132612
+ init_proposal_payload_validation();
132423
132613
  PROPOSAL_STATUSES = ["pending", "approved", "rejected"];
132424
132614
  PROPOSAL_KINDS = ["memory.create", "memory.update", "memory.tag"];
132425
132615
  });
132426
132616
 
132427
132617
  // ../../packages/core/dist/data/proposal/proposal-repository-pg.js
132428
- function isRecord(value) {
132429
- return value !== null && typeof value === "object" && !Array.isArray(value);
132430
- }
132431
- function stringArray(value) {
132432
- return Array.isArray(value) && value.every((item) => typeof item === "string");
132433
- }
132434
- function validKeys(value, allowed) {
132435
- return Object.keys(value).every((key) => allowed.includes(key));
132436
- }
132437
132618
  function parsePayload(raw2, kind) {
132438
132619
  let value;
132439
132620
  try {
@@ -132444,15 +132625,7 @@ function parsePayload(raw2, kind) {
132444
132625
  if (!isRecord(value)) {
132445
132626
  throw storeCorruption("proposal.payload_json", new TypeError("expected object"));
132446
132627
  }
132447
- let valid = false;
132448
- if (kind === "memory.create") {
132449
- valid = validKeys(value, ["content", "type", "level", "importance", "tags"]) && typeof value.content === "string" && (value.type === undefined || typeof value.type === "string") && (value.level === undefined || typeof value.level === "number") && (value.importance === undefined || typeof value.importance === "number") && (value.tags === undefined || stringArray(value.tags));
132450
- } else if (kind === "memory.update") {
132451
- valid = validKeys(value, ["content", "importance", "tags"]) && Object.keys(value).length > 0 && (value.content === undefined || typeof value.content === "string") && (value.importance === undefined || typeof value.importance === "number") && (value.tags === undefined || stringArray(value.tags));
132452
- } else {
132453
- valid = validKeys(value, ["tags"]) && stringArray(value.tags);
132454
- }
132455
- if (!valid) {
132628
+ if (!isValidProposalPayload(kind, value)) {
132456
132629
  throw storeCorruption("proposal.payload_json", new TypeError("invalid proposal payload"));
132457
132630
  }
132458
132631
  return value;
@@ -132586,6 +132759,65 @@ class PgProposalStore {
132586
132759
  this.mirror.set(id, persisted);
132587
132760
  return structuredClone(persisted);
132588
132761
  }
132762
+ async create(input) {
132763
+ assertValidProposalPayload(input.kind, input.payload);
132764
+ const record2 = {
132765
+ id: input.id,
132766
+ projectId: input.projectId,
132767
+ kind: input.kind,
132768
+ targetMemoryId: input.targetMemoryId ?? null,
132769
+ payload: input.payload,
132770
+ rationale: input.rationale ?? "",
132771
+ status: "pending",
132772
+ createdAt: input.createdAt ?? Date.now(),
132773
+ decidedAt: null
132774
+ };
132775
+ await this.insert(record2);
132776
+ return record2;
132777
+ }
132778
+ async update(id, patch) {
132779
+ await this.ensureHydrated();
132780
+ const current = this.mirror.get(id);
132781
+ if (!current)
132782
+ return null;
132783
+ if (patch.payload !== undefined) {
132784
+ assertValidProposalPayload(current.kind, patch.payload);
132785
+ }
132786
+ const merged = {
132787
+ ...current,
132788
+ ...patch.rationale !== undefined ? { rationale: patch.rationale } : {},
132789
+ ...patch.payload !== undefined ? { payload: patch.payload } : {}
132790
+ };
132791
+ let rows;
132792
+ try {
132793
+ rows = await this.getClient().$queryRaw`
132794
+ UPDATE proposals
132795
+ SET rationale = ${merged.rationale}, payload_json = ${JSON.stringify(merged.payload)}
132796
+ WHERE id = ${id}
132797
+ RETURNING id, project_id, kind, target_memory_id, payload_json,
132798
+ rationale, status, created_at, decided_at`;
132799
+ } catch (error51) {
132800
+ throw searchBackendUnavailable("proposal_store", error51);
132801
+ }
132802
+ if (!rows[0])
132803
+ return null;
132804
+ const persisted = toRecord(rows[0]);
132805
+ this.mirror.set(id, persisted);
132806
+ return structuredClone(persisted);
132807
+ }
132808
+ async delete(id) {
132809
+ await this.ensureHydrated();
132810
+ let affected;
132811
+ try {
132812
+ affected = await this.getClient().$executeRaw`DELETE FROM proposals WHERE id = ${id}`;
132813
+ } catch (error51) {
132814
+ throw searchBackendUnavailable("proposal_store", error51);
132815
+ }
132816
+ if (affected === 0)
132817
+ return null;
132818
+ this.mirror.delete(id);
132819
+ return id;
132820
+ }
132589
132821
  async journalMode() {
132590
132822
  await this.ensureHydrated();
132591
132823
  return "postgres";
@@ -132602,6 +132834,7 @@ var init_proposal_repository_pg = __esm(() => {
132602
132834
  init_alias_resolver();
132603
132835
  init_search_diagnostics();
132604
132836
  init_proposal_contract();
132837
+ init_proposal_payload_validation();
132605
132838
  });
132606
132839
 
132607
132840
  // ../../packages/core/dist/data/proposal/proposal-repository.js
@@ -179181,6 +179414,53 @@ class PgHandoffStore {
179181
179414
  this.mirror.set(id, persisted);
179182
179415
  return structuredClone(persisted);
179183
179416
  }
179417
+ async update(id, patch) {
179418
+ await this.ensureHydrated();
179419
+ const current = this.mirror.get(id);
179420
+ if (!current)
179421
+ return null;
179422
+ const merged = {
179423
+ ...current,
179424
+ ...patch.targetAgent !== undefined ? { targetAgent: patch.targetAgent } : {},
179425
+ ...patch.summary !== undefined ? { summary: patch.summary } : {},
179426
+ ...patch.openQuestions !== undefined ? { openQuestions: patch.openQuestions } : {},
179427
+ ...patch.nextSteps !== undefined ? { nextSteps: patch.nextSteps } : {},
179428
+ ...patch.files !== undefined ? { files: patch.files } : {}
179429
+ };
179430
+ let rows;
179431
+ try {
179432
+ rows = await this.getClient().$queryRaw`
179433
+ UPDATE handoffs
179434
+ SET target_agent = ${merged.targetAgent}, summary = ${merged.summary},
179435
+ open_questions_json = ${JSON.stringify(merged.openQuestions ?? [])},
179436
+ next_steps_json = ${JSON.stringify(merged.nextSteps ?? [])},
179437
+ files_json = ${JSON.stringify(merged.files ?? [])}
179438
+ WHERE id = ${id}
179439
+ RETURNING id, project_id, source_session_id, target_agent, summary,
179440
+ open_questions_json, next_steps_json, files_json, status,
179441
+ created_at, accepted_at`;
179442
+ } catch (error51) {
179443
+ throw searchBackendUnavailable("handoff_store", error51);
179444
+ }
179445
+ if (!rows[0])
179446
+ return null;
179447
+ const persisted = toRecord2(rows[0]);
179448
+ this.mirror.set(id, persisted);
179449
+ return structuredClone(persisted);
179450
+ }
179451
+ async delete(id) {
179452
+ await this.ensureHydrated();
179453
+ let affected;
179454
+ try {
179455
+ affected = await this.getClient().$executeRaw`DELETE FROM handoffs WHERE id = ${id}`;
179456
+ } catch (error51) {
179457
+ throw searchBackendUnavailable("handoff_store", error51);
179458
+ }
179459
+ if (affected === 0)
179460
+ return null;
179461
+ this.mirror.delete(id);
179462
+ return id;
179463
+ }
179184
179464
  async journalMode() {
179185
179465
  await this.ensureHydrated();
179186
179466
  return "postgres";
@@ -179205,7 +179485,6 @@ function newHandoffId() {
179205
179485
  // ../../packages/core/dist/services/handoff/handoff-service.js
179206
179486
  init_zod();
179207
179487
  init_dist();
179208
- import { randomUUID as randomUUID10 } from "crypto";
179209
179488
  init_memory_repository_factory();
179210
179489
  init_event_bus();
179211
179490
  init_llm_client();
@@ -179226,7 +179505,8 @@ class HandoffService {
179226
179505
  this.idFactory = deps.idFactory ?? (() => newHandoffId());
179227
179506
  const injectedRepo = deps.memoryRepo;
179228
179507
  this.memoryRepo = injectedRepo ?? {
179229
- insert: (i) => getMemoryRepository().insert(i)
179508
+ insert: (i) => getMemoryRepository().insert(i),
179509
+ update: (id, p) => getMemoryRepository().update(id, p)
179230
179510
  };
179231
179511
  }
179232
179512
  async begin(input) {
@@ -179268,7 +179548,7 @@ class HandoffService {
179268
179548
  return { ok: true, id, status: "open", memoryId };
179269
179549
  }
179270
179550
  async dualWrite(record2) {
179271
- const memId = `handoff-mem-${record2.id}-${randomUUID10().slice(0, 8)}`;
179551
+ const memId = dualWriteMemoryId(record2.id);
179272
179552
  const input = buildHandoffMemoryInput(memId, record2);
179273
179553
  await Promise.resolve(this.memoryRepo.insert(input));
179274
179554
  return memId;
@@ -179312,6 +179592,50 @@ class HandoffService {
179312
179592
  async listPending(projectId, targetAgent) {
179313
179593
  return this.store.listPending(projectId, targetAgent ?? undefined);
179314
179594
  }
179595
+ async update(params) {
179596
+ if (!params || !params.id) {
179597
+ return { ok: false, reason: "missing-id" };
179598
+ }
179599
+ const row = await this.store.getById(params.id);
179600
+ if (!row)
179601
+ return { ok: false, reason: "not-found" };
179602
+ if (params.projectId && row.projectId !== params.projectId) {
179603
+ return { ok: false, reason: "project-mismatch" };
179604
+ }
179605
+ const updated = await this.store.update(params.id, params.patch);
179606
+ if (!updated)
179607
+ return { ok: false, reason: "not-found" };
179608
+ const touchesMemoryContent = params.patch.summary !== undefined || params.patch.openQuestions !== undefined || params.patch.nextSteps !== undefined || params.patch.files !== undefined;
179609
+ if (touchesMemoryContent) {
179610
+ await this.refreshDualWriteMemory(updated);
179611
+ }
179612
+ return { ok: true, handoff: updated };
179613
+ }
179614
+ async refreshDualWriteMemory(record2) {
179615
+ try {
179616
+ const memId = dualWriteMemoryId(record2.id);
179617
+ const content = formatMemoryContent(record2);
179618
+ await Promise.resolve(this.memoryRepo.update(memId, { content }));
179619
+ } catch {}
179620
+ }
179621
+ async delete(params) {
179622
+ if (!params || !params.id) {
179623
+ return { ok: false, reason: "missing-id" };
179624
+ }
179625
+ const row = await this.store.getById(params.id);
179626
+ if (!row)
179627
+ return { ok: false, reason: "not-found" };
179628
+ if (params.projectId && row.projectId !== params.projectId) {
179629
+ return { ok: false, reason: "project-mismatch" };
179630
+ }
179631
+ const deletedId = await this.store.delete(params.id);
179632
+ if (!deletedId)
179633
+ return { ok: false, reason: "not-found" };
179634
+ return { ok: true, id: deletedId };
179635
+ }
179636
+ }
179637
+ function dualWriteMemoryId(handoffId) {
179638
+ return `handoff-mem-${handoffId}`;
179315
179639
  }
179316
179640
  function buildHandoffMemoryInput(memId, record2) {
179317
179641
  const content = formatMemoryContent(record2);
@@ -179406,6 +179730,7 @@ init_event_bus();
179406
179730
 
179407
179731
  // ../../packages/core/dist/index.js
179408
179732
  init_proposal_repository();
179733
+ init_proposal_payload_validation();
179409
179734
  init_auto_improve_job();
179410
179735
 
179411
179736
  // src/routes/search.ts
@@ -180400,12 +180725,35 @@ var systemRoutes = new Elysia({ prefix: "/api/v1/system" }).get("/info", async (
180400
180725
  }
180401
180726
  });
180402
180727
 
180728
+ // src/routes/sse-keepalive.ts
180729
+ var SSE_HEARTBEAT_MS_DEFAULT = 5000;
180730
+ var SSE_MAX_DURATION_MS_DEFAULT = 10 * 60 * 1000;
180731
+ var SSE_REQUEST_TIMEOUT_SECONDS = 120;
180732
+ function resolveHeartbeatMs() {
180733
+ return Number(process.env.MASSA_AI_SSE_HEARTBEAT_MS) || SSE_HEARTBEAT_MS_DEFAULT;
180734
+ }
180735
+ function resolveMaxDurationMs() {
180736
+ return Number(process.env.MASSA_AI_SSE_MAX_DURATION_MS) || SSE_MAX_DURATION_MS_DEFAULT;
180737
+ }
180738
+ var requestTimeoutSource;
180739
+ var applyCount = 0;
180740
+ function setSseRequestTimeoutSource(source) {
180741
+ requestTimeoutSource = source;
180742
+ }
180743
+ function applySseRequestTimeout(request) {
180744
+ const source = requestTimeoutSource;
180745
+ if (!source || typeof source.timeout !== "function")
180746
+ return;
180747
+ try {
180748
+ source.timeout(request, SSE_REQUEST_TIMEOUT_SECONDS);
180749
+ applyCount++;
180750
+ } catch {}
180751
+ }
180752
+
180403
180753
  // src/routes/events.ts
180404
- var HEARTBEAT_MS_DEFAULT = 15000;
180405
- var MAX_DURATION_MS_DEFAULT = 10 * 60 * 1000;
180406
- var eventsRoutes = new Elysia({ prefix: "/api/v1" }).get("/events", async ({ query, set: set3 }) => {
180407
- const HEARTBEAT_MS = Number(process.env.MASSA_AI_SSE_HEARTBEAT_MS) || HEARTBEAT_MS_DEFAULT;
180408
- const MAX_DURATION_MS = Number(process.env.MASSA_AI_SSE_MAX_DURATION_MS) || MAX_DURATION_MS_DEFAULT;
180754
+ var eventsRoutes = new Elysia({ prefix: "/api/v1" }).get("/events", async ({ query, set: set3, request }) => {
180755
+ const HEARTBEAT_MS = resolveHeartbeatMs();
180756
+ const MAX_DURATION_MS = resolveMaxDurationMs();
180409
180757
  const projectIdFilter = query.projectId;
180410
180758
  const jobIdFilter = query.jobId;
180411
180759
  set3.headers["Content-Type"] = "text/event-stream";
@@ -180419,6 +180767,18 @@ var eventsRoutes = new Elysia({ prefix: "/api/v1" }).get("/events", async ({ que
180419
180767
  let closeTimer;
180420
180768
  const stream2 = new ReadableStream({
180421
180769
  start(controller) {
180770
+ applySseRequestTimeout(request);
180771
+ const teardown = () => {
180772
+ closed = true;
180773
+ unsubscribers.forEach((u) => u());
180774
+ if (heartbeatTimer)
180775
+ clearInterval(heartbeatTimer);
180776
+ if (closeTimer)
180777
+ clearTimeout(closeTimer);
180778
+ try {
180779
+ controller.close();
180780
+ } catch {}
180781
+ };
180422
180782
  const enqueue = (data) => {
180423
180783
  if (closed)
180424
180784
  return;
@@ -180427,7 +180787,7 @@ var eventsRoutes = new Elysia({ prefix: "/api/v1" }).get("/events", async ({ que
180427
180787
 
180428
180788
  `));
180429
180789
  } catch {
180430
- closed = true;
180790
+ teardown();
180431
180791
  }
180432
180792
  };
180433
180793
  const events = [
@@ -180455,18 +180815,10 @@ var eventsRoutes = new Elysia({ prefix: "/api/v1" }).get("/events", async ({ que
180455
180815
 
180456
180816
  `));
180457
180817
  } catch {
180458
- closed = true;
180459
- clearInterval(heartbeatTimer);
180818
+ teardown();
180460
180819
  }
180461
180820
  }, HEARTBEAT_MS);
180462
- closeTimer = setTimeout(() => {
180463
- closed = true;
180464
- unsubscribers.forEach((u) => u());
180465
- clearInterval(heartbeatTimer);
180466
- try {
180467
- controller.close();
180468
- } catch {}
180469
- }, MAX_DURATION_MS);
180821
+ closeTimer = setTimeout(teardown, MAX_DURATION_MS);
180470
180822
  enqueue({
180471
180823
  event: "connected",
180472
180824
  payload: {
@@ -181568,6 +181920,16 @@ function rethrowCanonicalHandoffError(error51) {
181568
181920
  var EVENT_DETAIL3 = {
181569
181921
  tags: ["handoffs"]
181570
181922
  };
181923
+ var HANDOFF_PATCH_ALLOWED_FIELDS = [
181924
+ "targetAgent",
181925
+ "summary",
181926
+ "openQuestions",
181927
+ "nextSteps",
181928
+ "files"
181929
+ ];
181930
+ function isStringArray2(value) {
181931
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
181932
+ }
181571
181933
  var handoffRoutes = new Elysia({ prefix: "/api/v1/handoff" }).post("/begin", async ({ body, set: set3 }) => {
181572
181934
  if (handoffsDisabled()) {
181573
181935
  set3.status = 423;
@@ -181708,6 +182070,121 @@ var handoffRoutes = new Elysia({ prefix: "/api/v1/handoff" }).post("/begin", asy
181708
182070
  summary: "List pending (open) handoffs",
181709
182071
  description: "Lists open handoffs for a project, optionally filtered by target agent, ordered oldest-first."
181710
182072
  }
182073
+ }).patch("/:id", async ({ params, query, body, set: set3 }) => {
182074
+ if (handoffsDisabled()) {
182075
+ set3.status = 423;
182076
+ return { status: 423, error: "handoffs disabled" };
182077
+ }
182078
+ const raw2 = body ?? {};
182079
+ const keys = Object.keys(raw2);
182080
+ const rejected = keys.filter((k2) => !HANDOFF_PATCH_ALLOWED_FIELDS.includes(k2));
182081
+ if (rejected.length > 0) {
182082
+ set3.status = 400;
182083
+ return { status: 400, error: `field not allowed: ${rejected.join(", ")}` };
182084
+ }
182085
+ const patch = {};
182086
+ if ("targetAgent" in raw2) {
182087
+ const v = raw2.targetAgent;
182088
+ if (v !== null && typeof v !== "string") {
182089
+ set3.status = 400;
182090
+ return { status: 400, error: "targetAgent must be a string or null" };
182091
+ }
182092
+ patch.targetAgent = v;
182093
+ }
182094
+ if ("summary" in raw2) {
182095
+ if (typeof raw2.summary !== "string") {
182096
+ set3.status = 400;
182097
+ return { status: 400, error: "summary must be a string" };
182098
+ }
182099
+ patch.summary = raw2.summary;
182100
+ }
182101
+ if ("openQuestions" in raw2) {
182102
+ if (!isStringArray2(raw2.openQuestions)) {
182103
+ set3.status = 400;
182104
+ return { status: 400, error: "openQuestions must be an array of strings" };
182105
+ }
182106
+ patch.openQuestions = raw2.openQuestions;
182107
+ }
182108
+ if ("nextSteps" in raw2) {
182109
+ if (!isStringArray2(raw2.nextSteps)) {
182110
+ set3.status = 400;
182111
+ return { status: 400, error: "nextSteps must be an array of strings" };
182112
+ }
182113
+ patch.nextSteps = raw2.nextSteps;
182114
+ }
182115
+ if ("files" in raw2) {
182116
+ if (!isStringArray2(raw2.files)) {
182117
+ set3.status = 400;
182118
+ return { status: 400, error: "files must be an array of strings" };
182119
+ }
182120
+ patch.files = raw2.files;
182121
+ }
182122
+ if (Object.keys(patch).length === 0) {
182123
+ set3.status = 400;
182124
+ return { status: 400, error: "no editable field provided" };
182125
+ }
182126
+ try {
182127
+ const result = await service3().update({
182128
+ id: params.id,
182129
+ projectId: query.projectId,
182130
+ patch
182131
+ });
182132
+ if (!result.ok) {
182133
+ set3.status = result.reason === "not-found" || result.reason === "project-mismatch" ? 404 : 400;
182134
+ return { status: set3.status, error: result.reason };
182135
+ }
182136
+ set3.status = 200;
182137
+ return { success: true, data: result.handoff };
182138
+ } catch (e) {
182139
+ rethrowCanonicalHandoffError(e);
182140
+ const err = e;
182141
+ logger.error("handoff update failed", err);
182142
+ set3.status = 500;
182143
+ return { success: false, error: `handoff update failed: ${err.message}` };
182144
+ }
182145
+ }, {
182146
+ params: t.Object({ id: t.String({ description: "Handoff id" }) }),
182147
+ query: t.Object({
182148
+ projectId: t.Optional(t.String({ description: "If supplied, must match the row's projectId or the request 404s" }))
182149
+ }),
182150
+ body: t.Record(t.String(), t.Unknown(), {
182151
+ description: "Only targetAgent/summary/openQuestions/nextSteps/files are accepted; any other key is a 400 naming it."
182152
+ }),
182153
+ detail: {
182154
+ ...EVENT_DETAIL3,
182155
+ summary: "Edit a handoff (targetAgent/summary/openQuestions/nextSteps/files only)",
182156
+ description: "Allowlist PATCH: status/acceptedAt/id/projectId/createdAt/sourceSessionId and any unknown key are rejected " + "by name with 400. Refreshes the dual-written memory's content when a content-feeding field changes."
182157
+ }
182158
+ }).delete("/:id", async ({ params, query, set: set3 }) => {
182159
+ if (handoffsDisabled()) {
182160
+ set3.status = 423;
182161
+ return { status: 423, error: "handoffs disabled" };
182162
+ }
182163
+ try {
182164
+ const result = await service3().delete({ id: params.id, projectId: query.projectId });
182165
+ if (!result.ok) {
182166
+ set3.status = 404;
182167
+ return { status: 404, error: result.reason };
182168
+ }
182169
+ set3.status = 200;
182170
+ return { success: true, data: { id: result.id } };
182171
+ } catch (e) {
182172
+ rethrowCanonicalHandoffError(e);
182173
+ const err = e;
182174
+ logger.error("handoff delete failed", err);
182175
+ set3.status = 500;
182176
+ return { success: false, error: `handoff delete failed: ${err.message}` };
182177
+ }
182178
+ }, {
182179
+ params: t.Object({ id: t.String({ description: "Handoff id" }) }),
182180
+ query: t.Object({
182181
+ projectId: t.Optional(t.String({ description: "If supplied, must match the row's projectId or the request 404s" }))
182182
+ }),
182183
+ detail: {
182184
+ ...EVENT_DETAIL3,
182185
+ summary: "Hard-delete a handoff",
182186
+ description: "Permitted in any status, including open. The dual-written memory row is left intact (dangling metadata.handoffId is accepted)."
182187
+ }
181711
182188
  });
181712
182189
 
181713
182190
  // src/routes/proposals.ts
@@ -181718,6 +182195,12 @@ function job() {
181718
182195
  cachedJob2 = getAutoImproveJob();
181719
182196
  return cachedJob2;
181720
182197
  }
182198
+ var cachedStore4 = null;
182199
+ function store6() {
182200
+ if (!cachedStore4)
182201
+ cachedStore4 = getProposalStore();
182202
+ return cachedStore4;
182203
+ }
181721
182204
  function autoImproveDisabled() {
181722
182205
  try {
181723
182206
  return config.get("memory")?.autoImprove?.enabled === false;
@@ -181728,6 +182211,16 @@ function autoImproveDisabled() {
181728
182211
  var EVENT_DETAIL4 = {
181729
182212
  tags: ["proposals"]
181730
182213
  };
182214
+ var PROPOSAL_PATCH_ALLOWED_FIELDS = ["rationale", "payload"];
182215
+ function isRecord2(value) {
182216
+ return value !== null && typeof value === "object" && !Array.isArray(value);
182217
+ }
182218
+ function isProposalKind(value) {
182219
+ return typeof value === "string" && PROPOSAL_KINDS.includes(value);
182220
+ }
182221
+ function proposalPayloadValidationStatus(error51) {
182222
+ return error51 instanceof ProposalPayloadValidationError ? error51.statusCode : null;
182223
+ }
181731
182224
  var proposalRoutes = new Elysia({ prefix: "/api/v1/proposal" }).post("/list", async ({ body, set: set3 }) => {
181732
182225
  if (autoImproveDisabled()) {
181733
182226
  set3.status = 423;
@@ -181828,6 +182321,191 @@ var proposalRoutes = new Elysia({ prefix: "/api/v1/proposal" }).post("/list", as
181828
182321
  summary: "Reject a pending proposal",
181829
182322
  description: "Flips status pending\u2192rejected (no apply, no event). Same failure semantics as approve on missing/non-pending/project-mismatch."
181830
182323
  }
182324
+ }).post("/create", async ({ body, set: set3 }) => {
182325
+ if (autoImproveDisabled()) {
182326
+ set3.status = 423;
182327
+ return { status: 423, error: "auto-improve disabled" };
182328
+ }
182329
+ const raw2 = body ?? {};
182330
+ if (typeof raw2.projectId !== "string" || !raw2.projectId.trim()) {
182331
+ set3.status = 400;
182332
+ return { status: 400, error: "projectId required" };
182333
+ }
182334
+ if (!isProposalKind(raw2.kind)) {
182335
+ set3.status = 400;
182336
+ return { status: 400, error: `kind must be one of ${PROPOSAL_KINDS.join(", ")}` };
182337
+ }
182338
+ if (!isRecord2(raw2.payload)) {
182339
+ set3.status = 400;
182340
+ return { status: 400, error: "payload must be an object" };
182341
+ }
182342
+ if (raw2.rationale !== undefined && typeof raw2.rationale !== "string") {
182343
+ set3.status = 400;
182344
+ return { status: 400, error: "rationale must be a string" };
182345
+ }
182346
+ if (raw2.targetMemoryId !== undefined && raw2.targetMemoryId !== null && typeof raw2.targetMemoryId !== "string") {
182347
+ set3.status = 400;
182348
+ return { status: 400, error: "targetMemoryId must be a string or null" };
182349
+ }
182350
+ const projectId = raw2.projectId;
182351
+ const kind = raw2.kind;
182352
+ const payload = raw2.payload;
182353
+ try {
182354
+ const record2 = await store6().create({
182355
+ id: newProposalId(),
182356
+ projectId,
182357
+ kind,
182358
+ payload,
182359
+ rationale: raw2.rationale,
182360
+ targetMemoryId: raw2.targetMemoryId
182361
+ });
182362
+ set3.status = 200;
182363
+ return { success: true, data: record2 };
182364
+ } catch (e) {
182365
+ if (e instanceof SearchServiceError)
182366
+ throw e;
182367
+ const validationStatus = proposalPayloadValidationStatus(e);
182368
+ if (validationStatus !== null) {
182369
+ set3.status = validationStatus;
182370
+ return { status: validationStatus, error: e.message };
182371
+ }
182372
+ const err = e;
182373
+ logger.error("proposal create failed", err);
182374
+ set3.status = 500;
182375
+ return { success: false, error: `proposal create failed: ${err.message}` };
182376
+ }
182377
+ }, {
182378
+ body: t.Record(t.String(), t.Unknown(), {
182379
+ description: "projectId, kind (memory.create|memory.update|memory.tag), payload, rationale?, targetMemoryId?"
182380
+ }),
182381
+ detail: {
182382
+ ...EVENT_DETAIL4,
182383
+ summary: "Create a pending proposal",
182384
+ description: "kind must be memory.create/memory.update/memory.tag; payload is validated against the same per-kind " + "rules the store enforces on read (AC-02.2), so a write the reader would reject is refused here instead."
182385
+ }
182386
+ }).patch("/:id", async ({ params, query, body, set: set3 }) => {
182387
+ if (autoImproveDisabled()) {
182388
+ set3.status = 423;
182389
+ return { status: 423, error: "auto-improve disabled" };
182390
+ }
182391
+ const raw2 = body ?? {};
182392
+ const keys = Object.keys(raw2);
182393
+ const rejected = keys.filter((k2) => !PROPOSAL_PATCH_ALLOWED_FIELDS.includes(k2));
182394
+ if (rejected.length > 0) {
182395
+ set3.status = 400;
182396
+ return { status: 400, error: `field not allowed: ${rejected.join(", ")}` };
182397
+ }
182398
+ const patch = {};
182399
+ if ("rationale" in raw2) {
182400
+ if (typeof raw2.rationale !== "string") {
182401
+ set3.status = 400;
182402
+ return { status: 400, error: "rationale must be a string" };
182403
+ }
182404
+ patch.rationale = raw2.rationale;
182405
+ }
182406
+ if ("payload" in raw2) {
182407
+ if (!isRecord2(raw2.payload)) {
182408
+ set3.status = 400;
182409
+ return { status: 400, error: "payload must be an object" };
182410
+ }
182411
+ patch.payload = raw2.payload;
182412
+ }
182413
+ if (Object.keys(patch).length === 0) {
182414
+ set3.status = 400;
182415
+ return { status: 400, error: "no editable field provided" };
182416
+ }
182417
+ try {
182418
+ const current = await store6().getById(params.id);
182419
+ if (!current) {
182420
+ set3.status = 404;
182421
+ return { status: 404, error: "not-found" };
182422
+ }
182423
+ if (query.projectId && current.projectId !== query.projectId) {
182424
+ set3.status = 404;
182425
+ return { status: 404, error: "project-mismatch" };
182426
+ }
182427
+ const updated = await store6().update(params.id, {
182428
+ ...patch.rationale !== undefined ? { rationale: patch.rationale } : {},
182429
+ ...patch.payload !== undefined ? { payload: patch.payload } : {}
182430
+ });
182431
+ if (!updated) {
182432
+ set3.status = 404;
182433
+ return { status: 404, error: "not-found" };
182434
+ }
182435
+ set3.status = 200;
182436
+ return { success: true, data: updated };
182437
+ } catch (e) {
182438
+ if (e instanceof SearchServiceError)
182439
+ throw e;
182440
+ const validationStatus = proposalPayloadValidationStatus(e);
182441
+ if (validationStatus !== null) {
182442
+ set3.status = validationStatus;
182443
+ return { status: validationStatus, error: e.message };
182444
+ }
182445
+ const err = e;
182446
+ logger.error("proposal update failed", err);
182447
+ set3.status = 500;
182448
+ return { success: false, error: `proposal update failed: ${err.message}` };
182449
+ }
182450
+ }, {
182451
+ params: t.Object({ id: t.String({ description: "Proposal id" }) }),
182452
+ query: t.Object({
182453
+ projectId: t.Optional(t.String({ description: "If supplied, must match the row's projectId or the request 404s" }))
182454
+ }),
182455
+ body: t.Record(t.String(), t.Unknown(), {
182456
+ description: "Only rationale/payload are accepted; any other key is a 400 naming it."
182457
+ }),
182458
+ detail: {
182459
+ ...EVENT_DETAIL4,
182460
+ summary: "Edit a proposal (rationale/payload only)",
182461
+ description: "Allowlist PATCH: kind/targetMemoryId/status/decidedAt/id/projectId/createdAt and any unknown key are " + "rejected by name with 400. A payload edit re-validates against the row's existing kind (AC-02.3/AC-02.4)."
182462
+ }
182463
+ }).delete("/:id", async ({ params, query, set: set3 }) => {
182464
+ if (autoImproveDisabled()) {
182465
+ set3.status = 423;
182466
+ return { status: 423, error: "auto-improve disabled" };
182467
+ }
182468
+ try {
182469
+ const current = await store6().getById(params.id);
182470
+ if (!current) {
182471
+ set3.status = 404;
182472
+ return { status: 404, error: "not-found" };
182473
+ }
182474
+ if (query.projectId && current.projectId !== query.projectId) {
182475
+ set3.status = 404;
182476
+ return { status: 404, error: "project-mismatch" };
182477
+ }
182478
+ const deletedId = await store6().delete(params.id);
182479
+ if (!deletedId) {
182480
+ set3.status = 404;
182481
+ return { status: 404, error: "not-found" };
182482
+ }
182483
+ set3.status = 200;
182484
+ return {
182485
+ success: true,
182486
+ data: {
182487
+ id: deletedId,
182488
+ ...current.status === "approved" ? { note: "approved proposal's applied memory edit was not reversed" } : {}
182489
+ }
182490
+ };
182491
+ } catch (e) {
182492
+ if (e instanceof SearchServiceError)
182493
+ throw e;
182494
+ const err = e;
182495
+ logger.error("proposal delete failed", err);
182496
+ set3.status = 500;
182497
+ return { success: false, error: `proposal delete failed: ${err.message}` };
182498
+ }
182499
+ }, {
182500
+ params: t.Object({ id: t.String({ description: "Proposal id" }) }),
182501
+ query: t.Object({
182502
+ projectId: t.Optional(t.String({ description: "If supplied, must match the row's projectId or the request 404s" }))
182503
+ }),
182504
+ detail: {
182505
+ ...EVENT_DETAIL4,
182506
+ summary: "Hard-delete a proposal",
182507
+ description: "Permitted in any status. Deleting an already-approved proposal does not reverse the memory edit it " + "already applied (AC-02.5) \u2014 the response says so explicitly via a `note` field."
182508
+ }
181831
182509
  });
181832
182510
 
181833
182511
  // src/routes/executor.ts
@@ -182710,6 +183388,7 @@ var configRoutes = new Elysia({ prefix: "/api/v1/config" }).get("/", ({ set: set
182710
183388
  // src/routes/model-registry-stream.ts
182711
183389
  init_config();
182712
183390
  init_dist();
183391
+ import fs28 from "fs";
182713
183392
  import path42 from "path";
182714
183393
  import { spawn as spawn3 } from "child_process";
182715
183394
  var encoder3 = new TextEncoder;
@@ -182718,6 +183397,58 @@ function sseFrame(data) {
182718
183397
 
182719
183398
  `);
182720
183399
  }
183400
+ var KNOWN_GENERATOR_FILENAMES = ["generate-skill-artifacts.ts", "generate-subagent-artifacts.ts"];
183401
+ function deriveGeneratorScripts(root2) {
183402
+ const pkgPath = path42.join(root2, "package.json");
183403
+ let raw2;
183404
+ try {
183405
+ raw2 = fs28.readFileSync(pkgPath, "utf-8");
183406
+ } catch (e) {
183407
+ throw new Error(`cannot read ${pkgPath}: ${e.message}`);
183408
+ }
183409
+ let pkg;
183410
+ try {
183411
+ pkg = JSON.parse(raw2);
183412
+ } catch (e) {
183413
+ throw new Error(`${pkgPath} is not valid JSON: ${e.message}`);
183414
+ }
183415
+ const scriptsField = pkg?.scripts;
183416
+ const command = scriptsField?.["generate:artifacts"];
183417
+ if (typeof command !== "string" || command.trim().length === 0) {
183418
+ throw new Error(`${pkgPath}'s scripts."generate:artifacts" is missing or not a string`);
183419
+ }
183420
+ const segments = command.split("&&").map((s) => s.trim()).filter((s) => s.length > 0);
183421
+ if (segments.length === 0) {
183422
+ throw new Error(`"generate:artifacts" parsed to zero commands: ${JSON.stringify(command)}`);
183423
+ }
183424
+ return segments.map((segment) => {
183425
+ const match2 = /^bun\s+(\S+\.ts)$/.exec(segment);
183426
+ if (!match2) {
183427
+ throw new Error(`"generate:artifacts" segment does not match the expected "bun <script.ts>" shape: ${JSON.stringify(segment)}`);
183428
+ }
183429
+ const relPath = match2[1];
183430
+ return { relPath, name: path42.basename(relPath) };
183431
+ });
183432
+ }
183433
+ function assertGeneratorBackstop(scripts) {
183434
+ const names = scripts.map((s) => s.name);
183435
+ const missing = KNOWN_GENERATOR_FILENAMES.filter((f) => !names.includes(f));
183436
+ if (scripts.length < 2 || missing.length > 0) {
183437
+ throw new Error(`generator list ${JSON.stringify(names)} does not contain the known generators ` + `${KNOWN_GENERATOR_FILENAMES.join(", ")} \u2014 refusing to spawn an implausibly short list`);
183438
+ }
183439
+ }
183440
+ function emitSkillsFrames(controller2, closedRef) {
183441
+ for (const host of HOSTS) {
183442
+ if (closedRef.closed)
183443
+ return;
183444
+ try {
183445
+ controller2.enqueue(sseFrame({ type: "skills", host, status: "generated" }));
183446
+ } catch {
183447
+ closedRef.closed = true;
183448
+ return;
183449
+ }
183450
+ }
183451
+ }
182721
183452
  function deriveInstallStatus(report) {
182722
183453
  if (report.hosts.some((h) => h.status === "switched"))
182723
183454
  return "switched";
@@ -182792,17 +183523,15 @@ function createRegenerateStreamHandler() {
182792
183523
  closedRef.closed = true;
182793
183524
  return;
182794
183525
  }
182795
- const generateScript = path42.join(root2, "scripts", "generate-subagent-artifacts.ts");
183526
+ let generatorScripts;
182796
183527
  try {
182797
- child = spawn3("bun", [generateScript], {
182798
- env: { ...process.env },
182799
- stdio: ["pipe", "pipe", "pipe"]
182800
- });
183528
+ generatorScripts = deriveGeneratorScripts(root2);
183529
+ assertGeneratorBackstop(generatorScripts);
182801
183530
  } catch (e) {
182802
183531
  controller2.enqueue(sseFrame({
182803
183532
  type: "done",
182804
183533
  exitCode: null,
182805
- error: `spawn failed: ${e.message}`
183534
+ error: `could not derive the generator list: ${e.message}`
182806
183535
  }));
182807
183536
  controller2.close();
182808
183537
  closedRef.closed = true;
@@ -182825,35 +183554,57 @@ function createRegenerateStreamHandler() {
182825
183554
  }
182826
183555
  }
182827
183556
  };
182828
- child.stdout?.on("data", (chunk) => emitLine("stdout", chunk));
182829
- child.stderr?.on("data", (chunk) => emitLine("stderr", chunk));
182830
- child.on("error", (e) => {
183557
+ const finish = (exitCode, error51) => {
182831
183558
  if (closedRef.closed)
182832
183559
  return;
182833
183560
  closedRef.closed = true;
182834
183561
  try {
182835
- controller2.enqueue(sseFrame({ type: "done", exitCode: null, error: `spawn error: ${e.message}` }));
183562
+ controller2.enqueue(sseFrame(error51 !== undefined ? { type: "done", exitCode, error: error51 } : { type: "done", exitCode }));
182836
183563
  controller2.close();
182837
183564
  } catch {}
182838
- });
182839
- child.on("close", (code) => {
183565
+ };
183566
+ const runGenerator = (index) => {
182840
183567
  if (closedRef.closed)
182841
183568
  return;
182842
- if (code === 0) {
183569
+ if (index >= generatorScripts.length) {
182843
183570
  try {
182844
183571
  controller2.enqueue(sseFrame({ type: "line", stream: "stdout", text: "Installing regenerated agents to active directories..." }));
182845
183572
  } catch {
182846
183573
  closedRef.closed = true;
182847
183574
  return;
182848
183575
  }
183576
+ emitSkillsFrames(controller2, closedRef);
182849
183577
  installActiveProfiles(controller2, closedRef);
183578
+ finish(0);
183579
+ return;
182850
183580
  }
182851
- closedRef.closed = true;
183581
+ const generator = generatorScripts[index];
183582
+ const scriptPath = path42.join(root2, generator.relPath);
182852
183583
  try {
182853
- controller2.enqueue(sseFrame({ type: "done", exitCode: code }));
182854
- controller2.close();
182855
- } catch {}
182856
- });
183584
+ child = spawn3("bun", [scriptPath], {
183585
+ env: { ...process.env },
183586
+ stdio: ["pipe", "pipe", "pipe"]
183587
+ });
183588
+ } catch (e) {
183589
+ finish(null, `spawn failed (${generator.name}): ${e.message}`);
183590
+ return;
183591
+ }
183592
+ child.stdout?.on("data", (chunk) => emitLine("stdout", chunk));
183593
+ child.stderr?.on("data", (chunk) => emitLine("stderr", chunk));
183594
+ child.on("error", (e) => {
183595
+ finish(null, `spawn error (${generator.name}): ${e.message}`);
183596
+ });
183597
+ child.on("close", (code) => {
183598
+ if (closedRef.closed)
183599
+ return;
183600
+ if (code !== 0) {
183601
+ finish(code, `${generator.name} failed with exit code ${code}`);
183602
+ return;
183603
+ }
183604
+ runGenerator(index + 1);
183605
+ });
183606
+ };
183607
+ runGenerator(0);
182857
183608
  },
182858
183609
  cancel() {
182859
183610
  closedRef.closed = true;
@@ -182875,8 +183626,8 @@ function createRegenerateStreamHandler() {
182875
183626
  var modelRegistryStreamRoutes = new Elysia({ prefix: "/api/v1/model-registry" }).post("/regenerate-and-install-stream", createRegenerateStreamHandler(), {
182876
183627
  detail: {
182877
183628
  tags: ["model-registry"],
182878
- summary: "Regenerate subagent artifacts + auto-install to active dirs (streaming SSE)",
182879
- description: "Spawns `bun scripts/generate-subagent-artifacts.ts` with child_process.spawn (non-blocking). Pipes stdout/stderr line-by-line as SSE. After the generator succeeds (exit 0), bridges the freshly generated agent-profiles trees into each host's installed variant root (`variant-sync` events per host), then calls switchProfile for every detected host to reinstall the active profile's agents from those synced variant dirs. Emits `install` events per host, then a terminal `done` event with the exit code."
183629
+ summary: "Regenerate skill + subagent artifacts, then auto-install to active dirs (streaming SSE)",
183630
+ description: "Spawns every generator named by package.json's `generate:artifacts` script (today `generate-skill-artifacts.ts` then `generate-subagent-artifacts.ts`), one at a time in that order, with child_process.spawn (non-blocking). Pipes stdout/stderr line-by-line as SSE; the first generator to exit non-zero stops the chain and names itself in the terminal `done` frame. Once every generator exits 0, emits one `skills` event per host, bridges the freshly generated agent-profiles trees into each host's installed variant root (`variant-sync` events per host), then calls switchProfile for every detected host to reinstall the active profile's agents from those synced variant dirs. Emits `install` events per host, then a terminal `done` event with the exit code."
182880
183631
  }
182881
183632
  }).post("/regenerate-stream", createRegenerateStreamHandler(), {
182882
183633
  detail: {
@@ -182986,7 +183737,7 @@ var restartRoutes = new Elysia({ prefix: "/api/v1/system" }).onAfterResponse(()
182986
183737
 
182987
183738
  // src/routes/logs.ts
182988
183739
  init_dist();
182989
- import fs28 from "fs";
183740
+ import fs29 from "fs";
182990
183741
  var LOGS_DETAIL = { tags: ["logs"] };
182991
183742
  var MAX_SCAN_BYTES = 64 * 1024 * 1024;
182992
183743
  var MAX_LIMIT = 1000;
@@ -183066,7 +183817,7 @@ function parseLine(line, prevTs) {
183066
183817
  function realReadTail(filePath, maxBytes) {
183067
183818
  let size;
183068
183819
  try {
183069
- size = fs28.statSync(filePath).size;
183820
+ size = fs29.statSync(filePath).size;
183070
183821
  } catch {
183071
183822
  return { content: "", truncated: false };
183072
183823
  }
@@ -183074,24 +183825,24 @@ function realReadTail(filePath, maxBytes) {
183074
183825
  return { content: "", truncated: false };
183075
183826
  if (size <= maxBytes) {
183076
183827
  try {
183077
- return { content: fs28.readFileSync(filePath, "utf8"), truncated: false };
183828
+ return { content: fs29.readFileSync(filePath, "utf8"), truncated: false };
183078
183829
  } catch {
183079
183830
  return { content: "", truncated: false };
183080
183831
  }
183081
183832
  }
183082
183833
  try {
183083
- const fd = fs28.openSync(filePath, "r");
183834
+ const fd = fs29.openSync(filePath, "r");
183084
183835
  try {
183085
183836
  const start = size - maxBytes;
183086
183837
  const buf = Buffer.alloc(maxBytes);
183087
- fs28.readSync(fd, buf, 0, maxBytes, start);
183838
+ fs29.readSync(fd, buf, 0, maxBytes, start);
183088
183839
  let text3 = buf.toString("utf8");
183089
183840
  const firstNewline = text3.indexOf(`
183090
183841
  `);
183091
183842
  text3 = firstNewline !== -1 ? text3.slice(firstNewline + 1) : "";
183092
183843
  return { content: text3, truncated: true };
183093
183844
  } finally {
183094
- fs28.closeSync(fd);
183845
+ fs29.closeSync(fd);
183095
183846
  }
183096
183847
  } catch {
183097
183848
  return { content: "", truncated: true };
@@ -183101,7 +183852,7 @@ var realReader = {
183101
183852
  listFiles(filePath, maxFiles) {
183102
183853
  return sinkFiles(filePath, maxFiles).filter((f) => {
183103
183854
  try {
183104
- fs28.accessSync(f, fs28.constants.R_OK);
183855
+ fs29.accessSync(f, fs29.constants.R_OK);
183105
183856
  return true;
183106
183857
  } catch {
183107
183858
  return false;
@@ -183177,8 +183928,6 @@ function renderTxtLine(entry2) {
183177
183928
  const metaStr = entry2.meta ? ` ${JSON.stringify(entry2.meta)}` : "";
183178
183929
  return `[${entry2.ts}] [${entry2.level.toUpperCase()}] ${entry2.message}${metaStr}`;
183179
183930
  }
183180
- var SSE_HEARTBEAT_MS_DEFAULT = 15000;
183181
- var SSE_MAX_DURATION_MS_DEFAULT = 10 * 60 * 1000;
183182
183931
  var SINK_POLL_MS_DEFAULT = 1000;
183183
183932
  var SINK_POLL_MAX_BYTES = 1024 * 1024;
183184
183933
  function startSinkTail(enqueue) {
@@ -183192,7 +183941,7 @@ function startSinkTail(enqueue) {
183192
183941
  let currentFile = initial[0];
183193
183942
  let offset;
183194
183943
  try {
183195
- offset = fs28.statSync(currentFile).size;
183944
+ offset = fs29.statSync(currentFile).size;
183196
183945
  } catch {
183197
183946
  return;
183198
183947
  }
@@ -183208,7 +183957,7 @@ function startSinkTail(enqueue) {
183208
183957
  offset = 0;
183209
183958
  carry = "";
183210
183959
  }
183211
- const size = fs28.statSync(currentFile).size;
183960
+ const size = fs29.statSync(currentFile).size;
183212
183961
  if (size < offset) {
183213
183962
  offset = 0;
183214
183963
  carry = "";
@@ -183217,11 +183966,11 @@ function startSinkTail(enqueue) {
183217
183966
  return;
183218
183967
  const length = Math.min(size - offset, SINK_POLL_MAX_BYTES);
183219
183968
  const buf = Buffer.alloc(length);
183220
- const fd = fs28.openSync(currentFile, "r");
183969
+ const fd = fs29.openSync(currentFile, "r");
183221
183970
  try {
183222
- fs28.readSync(fd, buf, 0, length, offset);
183971
+ fs29.readSync(fd, buf, 0, length, offset);
183223
183972
  } finally {
183224
- fs28.closeSync(fd);
183973
+ fs29.closeSync(fd);
183225
183974
  }
183226
183975
  offset += length;
183227
183976
  const text3 = carry + buf.toString("utf8");
@@ -183284,9 +184033,9 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183284
184033
  summary: "Download the queried log range as jsonl or txt",
183285
184034
  description: "Same query surface as GET /api/v1/logs (minus limit/offset \u2014 the full matching range is returned) plus format=jsonl|txt. Responds with Content-Disposition: attachment and a filename carrying the range. A range matching zero entries still returns an empty (200) download."
183286
184035
  }
183287
- }).get("/stream", () => {
183288
- const HEARTBEAT_MS = Number(process.env.MASSA_AI_SSE_HEARTBEAT_MS) || SSE_HEARTBEAT_MS_DEFAULT;
183289
- const MAX_DURATION_MS = Number(process.env.MASSA_AI_SSE_MAX_DURATION_MS) || SSE_MAX_DURATION_MS_DEFAULT;
184036
+ }).get("/stream", ({ request }) => {
184037
+ const HEARTBEAT_MS = resolveHeartbeatMs();
184038
+ const MAX_DURATION_MS = resolveMaxDurationMs();
183290
184039
  const encoder4 = new TextEncoder;
183291
184040
  let closed = false;
183292
184041
  let unsubscribe;
@@ -183294,6 +184043,18 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183294
184043
  let closeTimer;
183295
184044
  const stream2 = new ReadableStream({
183296
184045
  start(controller2) {
184046
+ applySseRequestTimeout(request);
184047
+ const teardown = () => {
184048
+ closed = true;
184049
+ unsubscribe?.();
184050
+ if (heartbeatTimer)
184051
+ clearInterval(heartbeatTimer);
184052
+ if (closeTimer)
184053
+ clearTimeout(closeTimer);
184054
+ try {
184055
+ controller2.close();
184056
+ } catch {}
184057
+ };
183297
184058
  const enqueue = (data) => {
183298
184059
  if (closed)
183299
184060
  return;
@@ -183302,7 +184063,7 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183302
184063
 
183303
184064
  `));
183304
184065
  } catch {
183305
- closed = true;
184066
+ teardown();
183306
184067
  }
183307
184068
  };
183308
184069
  const tail = startSinkTail(enqueue);
@@ -183323,18 +184084,10 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183323
184084
 
183324
184085
  `));
183325
184086
  } catch {
183326
- closed = true;
183327
- clearInterval(heartbeatTimer);
184087
+ teardown();
183328
184088
  }
183329
184089
  }, HEARTBEAT_MS);
183330
- closeTimer = setTimeout(() => {
183331
- closed = true;
183332
- unsubscribe?.();
183333
- clearInterval(heartbeatTimer);
183334
- try {
183335
- controller2.close();
183336
- } catch {}
183337
- }, MAX_DURATION_MS);
184090
+ closeTimer = setTimeout(teardown, MAX_DURATION_MS);
183338
184091
  },
183339
184092
  cancel() {
183340
184093
  closed = true;
@@ -183361,6 +184114,104 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183361
184114
  }
183362
184115
  });
183363
184116
 
184117
+ // src/middleware/write-mode-classification.ts
184118
+ var READ_ONLY_ROUTES = [
184119
+ {
184120
+ method: "POST",
184121
+ path: "/api/v1/search/project",
184122
+ justification: "WRITES when autoReindex+projectPath are both set. " + "apps/tools-api/src/routes/search.ts:48-106 passes the raw request body to " + "SearchProjectTool.handle(), which forwards it unchanged to " + "SearchController.searchProject " + "(packages/core/src/services/search/search-controller.ts:132-179): autoReindex " + "defaults to false at :140, and at :177-179 `if (autoReindex && projectPath) " + "this.handleAutoReindex(projectId, projectPath)` calls the private helper at " + ":364-389, which calls `this.contextualSearch.ensureFreshIndex(...)` \u2014 a real " + "index write. Kept on the allowlist with a sanitizer (AC-03.3a) instead of being " + "dropped, so read-only mode does not 403 project search entirely.",
184123
+ sanitizeBody: (body) => {
184124
+ body.autoReindex = false;
184125
+ }
184126
+ },
184127
+ {
184128
+ method: "POST",
184129
+ path: "/api/v1/search/code",
184130
+ justification: "Clean. apps/tools-api/src/routes/search.ts:108-126's request schema exposes only " + "{query, projectId, limit} \u2014 no `autoReindex` field exists on this route to flip. " + "packages/core/src/tools/search_code.ts:56-62 delegates to SearchProjectTool.handle " + "with `autoReindex: false` hardcoded at line 61 regardless of caller input, so the " + "write path proven above for /search/project is structurally unreachable from here."
184131
+ },
184132
+ {
184133
+ method: "POST",
184134
+ path: "/api/v1/memory/search",
184135
+ justification: "Clean. apps/tools-api/src/routes/memory.ts:126-138 (`POST /search`) delegates the " + "raw body to SearchMemoriesTool.handle(), which " + "(packages/core/src/tools/search_memories.ts:93-121) only calls " + "`MemoryController.search(...)` and formats the result for the response \u2014 no " + "repository mutation on any code path this handler can reach."
184136
+ },
184137
+ {
184138
+ method: "POST",
184139
+ path: "/api/v1/memory/list",
184140
+ justification: "Clean. apps/tools-api/src/routes/memory.ts:243-268 calls only " + "`getMemoryRepository().search(...)`, then filters/paginates the result in the " + "route handler itself \u2014 no repository write method is reachable from this handler."
184141
+ },
184142
+ {
184143
+ method: "POST",
184144
+ path: "/api/v1/checkpoints/list",
184145
+ justification: "Clean. apps/tools-api/src/routes/checkpoints.ts:56-68 delegates to " + "ListCheckpointsTool.handle(), which " + "(packages/core/src/tools/list_checkpoints.ts:90-99) calls only " + "`checkpointManager.listCheckpoints(...)` and `checkpointManager.getStats()` \u2014 both " + "reads over existing state, never a checkpoint create/delete/restore call."
184146
+ },
184147
+ {
184148
+ method: "POST",
184149
+ path: "/api/v1/handoff/list",
184150
+ justification: "Clean. apps/tools-api/src/routes/handoff.ts:174-213 calls only " + "`service().listPending(...)`, which " + "(packages/core/src/services/handoff/handoff-service.ts:228-229) is a direct " + "pass-through to `this.store.listPending(...)` \u2014 never `begin` (:115), `accept` " + "(:173) or `cancel` (:182), the service's three write entry points."
184151
+ },
184152
+ {
184153
+ method: "POST",
184154
+ path: "/api/v1/proposal/list",
184155
+ justification: "Clean. apps/tools-api/src/routes/proposals.ts:36-73 calls only " + "`job().listPending(...)`, which " + "(packages/core/src/services/jobs/auto-improve-job.ts:113) is a direct pass-through " + "to `this.proposalStore.listPending(...)` \u2014 never `approve` (:111) or `reject` " + "(:112), the job's two write entry points."
184156
+ },
184157
+ {
184158
+ method: "POST",
184159
+ path: "/api/v1/context/compress",
184160
+ justification: "Clean. apps/tools-api/src/routes/context.ts:29-68 delegates to " + "CompressContextTool.handle(), which " + "(packages/core/src/tools/compress_context.ts:67-118) runs `compressWithMetrics` " + "over the request's own `content` string in memory and returns the result \u2014 no " + "repository, index, or filesystem write on any code path."
184161
+ },
184162
+ {
184163
+ method: "POST",
184164
+ path: "/api/v1/context/optimized",
184165
+ justification: "Clean. apps/tools-api/src/routes/context.ts:70-101 delegates to " + "GetOptimizedContextTool.handle(), which " + "(packages/core/src/tools/get_optimized_context.ts:105-110) calls " + "`ContextController.getOptimizedContext(p)`. That controller " + "(packages/core/src/services/context/context-controller.ts:161) hardcodes " + "`autoReindex: false` on its own internal search call, so the write path proven " + "for /search/project is structurally unreachable here even though this route's " + "schema also carries `projectPath`."
184166
+ },
184167
+ {
184168
+ method: "POST",
184169
+ path: "/api/v1/symbol/impact",
184170
+ justification: "Clean. apps/tools-api/src/routes/workspace.ts:542-673 calls " + "`getGraphController().analyzeImpact(...)`, which " + "(packages/core/src/services/symbol/graph-controller.ts:189-232) delegates to " + "`impactAnalysisService.analyze(...)` " + "(packages/core/src/services/symbol/impact-analysis.ts). Every git invocation there " + "runs through `execFileSync` for `rev-parse`/`diff`/`status`-class read commands, " + "plus one `hash-object -t tree --stdin` with no `-w` flag (so nothing is written to " + "the git object database) \u2014 no `git commit`, `git add`, or `-w` flag anywhere in " + "the module."
184171
+ }
184172
+ ];
184173
+ function normalizeRoutePath(path43) {
184174
+ return path43.length > 1 && path43.endsWith("/") ? path43.slice(0, -1) : path43;
184175
+ }
184176
+ var READ_ONLY_INDEX = new Map(READ_ONLY_ROUTES.map((entry2) => [`${entry2.method} ${entry2.path}`, entry2]));
184177
+ function findReadOnlyRoute(method, path43) {
184178
+ return READ_ONLY_INDEX.get(`${method.toUpperCase()} ${normalizeRoutePath(path43)}`);
184179
+ }
184180
+
184181
+ // src/middleware/write-mode.ts
184182
+ function defaultReadOnlyModeAccessor() {
184183
+ return process.env.MASSA_AI_READ_ONLY_MODE === "true";
184184
+ }
184185
+ var readOnlyModeAccessor = defaultReadOnlyModeAccessor;
184186
+ function isReadOnlyModeActive() {
184187
+ try {
184188
+ return readOnlyModeAccessor();
184189
+ } catch {
184190
+ return true;
184191
+ }
184192
+ }
184193
+ var WRITE_REFUSED = {
184194
+ success: false,
184195
+ error: "Write refused: read-only mode is active"
184196
+ };
184197
+ var writeModeMiddleware = new Elysia({ name: "write-mode" }).onBeforeHandle({ as: "global" }, ({ request, path: path43, set: set3, body }) => {
184198
+ if (request.method.toUpperCase() === "GET")
184199
+ return;
184200
+ if (isPublicPath(path43))
184201
+ return;
184202
+ if (!isReadOnlyModeActive())
184203
+ return;
184204
+ const entry2 = findReadOnlyRoute(request.method, path43);
184205
+ if (entry2) {
184206
+ if (entry2.sanitizeBody && body && typeof body === "object") {
184207
+ entry2.sanitizeBody(body);
184208
+ }
184209
+ return;
184210
+ }
184211
+ set3.status = 403;
184212
+ return { ...WRITE_REFUSED };
184213
+ });
184214
+
183364
184215
  // src/middleware/error.ts
183365
184216
  init_dist();
183366
184217
  var errorHandler = new Elysia({ name: "error-handler" }).onError(({ code, error: error51, set: set3, path: path43, request }) => {
@@ -183484,7 +184335,7 @@ var app = new Elysia({ adapter: node() }).use(cors(buildCorsOptions(config.get("
183484
184335
  },
183485
184336
  security: [{ ApiKeyAuth: [] }]
183486
184337
  }
183487
- })).use(errorHandler).use(authMiddleware).use(searchRoutes).use(memoryRoutes).use(checkpointRoutes).use(projectRoutes).use(contextRoutes).use(analyticsRoutes).use(systemRoutes).use(eventsRoutes).use(workspaceRoutes).use(fileRoutes).use(synapseRoutes).use(hookRoutes).use(bootstrapRoutes).use(handoffRoutes).use(proposalRoutes).use(executorRoutes).use(webRoutes).use(webUiRoutes).use(architectureRoutes).use(dashboardRoutes).use(profileRoutes).use(configRoutes).use(modelRegistryRoutes).use(modelRegistryStreamRoutes).use(restartRoutes).use(logsRoutes).get("/health", () => buildHealthResponse(getParserReadiness()));
184338
+ })).use(errorHandler).use(authMiddleware).use(writeModeMiddleware).use(searchRoutes).use(memoryRoutes).use(checkpointRoutes).use(projectRoutes).use(contextRoutes).use(analyticsRoutes).use(systemRoutes).use(eventsRoutes).use(workspaceRoutes).use(fileRoutes).use(synapseRoutes).use(hookRoutes).use(bootstrapRoutes).use(handoffRoutes).use(proposalRoutes).use(executorRoutes).use(webRoutes).use(webUiRoutes).use(architectureRoutes).use(dashboardRoutes).use(profileRoutes).use(configRoutes).use(modelRegistryRoutes).use(modelRegistryStreamRoutes).use(restartRoutes).use(logsRoutes).get("/health", () => buildHealthResponse(getParserReadiness()));
183488
184339
  initAuthOrExit();
183489
184340
  warnIfTrustOverrideEnabled();
183490
184341
  await listenAfterParserValidation({
@@ -183493,6 +184344,8 @@ await listenAfterParserValidation({
183493
184344
  try {
183494
184345
  app.listen({ port: Number(PORT), reusePort: false }, (server) => {
183495
184346
  setServerStopper(() => void server.stop());
184347
+ const bunServer = server.bun?.server;
184348
+ setSseRequestTimeoutSource(bunServer);
183496
184349
  });
183497
184350
  } catch (error51) {
183498
184351
  if (error51?.code === "EADDRINUSE") {