@massa-ai/tools-api 1.51.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 +981 -118
  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
@@ -181998,8 +182676,8 @@ function buildStaticDirCandidates(moduleDir, cwd) {
181998
182676
  for (const root2 of [moduleDir, cwd]) {
181999
182677
  let dir = root2;
182000
182678
  for (let i = 0;i < 10; i++) {
182001
- candidates2.push(path39.resolve(dir, "apps/web-ui/src/static"));
182002
- candidates2.push(path39.resolve(dir, "web-ui/src/static"));
182679
+ candidates2.push(path39.resolve(dir, "apps/web-ui/dist/static"));
182680
+ candidates2.push(path39.resolve(dir, "web-ui/dist/static"));
182003
182681
  const parent = path39.dirname(dir);
182004
182682
  if (parent === dir)
182005
182683
  break;
@@ -182352,6 +183030,13 @@ var REGISTRY_DETAIL = {
182352
183030
  tags: ["model-registry"]
182353
183031
  };
182354
183032
  var OVERLAY_PATH = path41.join(configDir("massa-ai"), "model-profiles.json");
183033
+ var ZERO_OVERLAY_OVERRIDE_BREAKDOWN = {
183034
+ hostDefaults: 0,
183035
+ workflowTiers: 0,
183036
+ agentTiers: 0,
183037
+ tiers: 0,
183038
+ profiles: 0
183039
+ };
182355
183040
  var modelRegistryRoutes = new Elysia({ prefix: "/api/v1/model-registry" }).get("/", async ({ set: set3 }) => {
182356
183041
  const root2 = getDeploymentRoot();
182357
183042
  if (!root2) {
@@ -182368,6 +183053,7 @@ var modelRegistryRoutes = new Elysia({ prefix: "/api/v1/model-registry" }).get("
182368
183053
  registry: result.registry,
182369
183054
  source: result.source,
182370
183055
  overlayOverrideCount: result.overlayOverrideCount ?? 0,
183056
+ overlayOverrideBreakdown: result.overlayOverrideBreakdown ?? ZERO_OVERLAY_OVERRIDE_BREAKDOWN,
182371
183057
  ...result.overlayError ? { overlayError: result.overlayError } : {},
182372
183058
  agents,
182373
183059
  ...agentsError ? { agentsError } : {}
@@ -182377,7 +183063,7 @@ var modelRegistryRoutes = new Elysia({ prefix: "/api/v1/model-registry" }).get("
182377
183063
  detail: {
182378
183064
  ...REGISTRY_DETAIL,
182379
183065
  summary: "Get effective registry (builtin + overlay) with source attribution",
182380
- description: "Returns the merged registry (builtin + overlay), source attribution (builtin, overlay, tombstoned), overlayOverrideCount (APCR-01.10 \u2014 count of overlay entries surviving normalization, so an operator can see how much of the registry their overlay is overriding), overlayError if the overlay is corrupted, and agents (design D-3, APUX-03 \u2014 {name, charterTier} for every charter under skills/agents/, best-effort with agentsError on failure) (200 status, never fails)."
183066
+ description: "Returns the merged registry (builtin + overlay), source attribution (builtin, overlay, tombstoned), overlayOverrideCount (APCR-01.10 \u2014 count of overlay entries surviving normalization, so an operator can see how much of the registry their overlay is overriding), overlayOverrideBreakdown (WUT-17 \u2014 the same count broken down per category: hostDefaults, workflowTiers, agentTiers, tiers, profiles), overlayError if the overlay is corrupted, and agents (design D-3, APUX-03 \u2014 {name, charterTier} for every charter under skills/agents/, best-effort with agentsError on failure) (200 status, never fails)."
182381
183067
  }
182382
183068
  }).put("/", ({ body, set: set3 }) => {
182383
183069
  const root2 = getDeploymentRoot();
@@ -182418,7 +183104,8 @@ var modelRegistryRoutes = new Elysia({ prefix: "/api/v1/model-registry" }).get("
182418
183104
  data: {
182419
183105
  registry: result.registry,
182420
183106
  source: result.source,
182421
- overlayOverrideCount: result.overlayOverrideCount ?? 0
183107
+ overlayOverrideCount: result.overlayOverrideCount ?? 0,
183108
+ overlayOverrideBreakdown: result.overlayOverrideBreakdown ?? ZERO_OVERLAY_OVERRIDE_BREAKDOWN
182422
183109
  }
182423
183110
  };
182424
183111
  }, {
@@ -182426,7 +183113,7 @@ var modelRegistryRoutes = new Elysia({ prefix: "/api/v1/model-registry" }).get("
182426
183113
  detail: {
182427
183114
  ...REGISTRY_DETAIL,
182428
183115
  summary: "Write overlay (full-replace, validated, atomic)",
182429
- description: "Accepts the full overlay object. Validates the merged result (builtin + overlay) via validateRegistry(). On success, writes atomically to ~/.config/massa-ai/model-profiles.json and returns the updated effective registry, including overlayOverrideCount (APCR-01.10). On failure, returns 400 with all violations."
183116
+ description: "Accepts the full overlay object. Validates the merged result (builtin + overlay) via validateRegistry(). On success, writes atomically to ~/.config/massa-ai/model-profiles.json and returns the updated effective registry, including overlayOverrideCount (APCR-01.10) and overlayOverrideBreakdown (WUT-17, per-category). On failure, returns 400 with all violations."
182430
183117
  }
182431
183118
  }).post("/regenerate", ({ set: set3 }) => {
182432
183119
  const root2 = getDeploymentRoot();
@@ -182631,16 +183318,17 @@ var configRoutes = new Elysia({ prefix: "/api/v1/config" }).get("/", ({ set: set
182631
183318
  const config3 = loadConfig();
182632
183319
  const masked = maskSensitive(config3);
182633
183320
  const restart = restartNeededSections(config3);
183321
+ const defaults2 = maskSensitive(defaultMassaAiConfig);
182634
183322
  set3.status = 200;
182635
183323
  return {
182636
183324
  success: true,
182637
- data: { config: masked, restartNeededSections: restart }
183325
+ data: { config: masked, restartNeededSections: restart, defaults: defaults2 }
182638
183326
  };
182639
183327
  }, {
182640
183328
  detail: {
182641
183329
  ...CONFIG_DETAIL,
182642
183330
  summary: "Get current config with sensitive fields masked",
182643
- description: "Returns the current config.json with security.apiKey, llm.apiKey, embedding.apiKey, and database.url masked to '***'. Includes restartNeededSections \u2014 the subset of [database, embedding, llm, security] present in the config."
183331
+ description: "Returns the current config.json with security.apiKey, llm.apiKey, embedding.apiKey, and database.url masked to '***'. Includes restartNeededSections \u2014 the subset of [database, embedding, llm, security] present in the config \u2014 and defaults, the shipped default config (also masked) the Config tab falls back to for any field the persisted file omits."
182644
183332
  }
182645
183333
  }).get("/reveal", ({ query, set: set3 }) => {
182646
183334
  const section = query.section;
@@ -182700,6 +183388,7 @@ var configRoutes = new Elysia({ prefix: "/api/v1/config" }).get("/", ({ set: set
182700
183388
  // src/routes/model-registry-stream.ts
182701
183389
  init_config();
182702
183390
  init_dist();
183391
+ import fs28 from "fs";
182703
183392
  import path42 from "path";
182704
183393
  import { spawn as spawn3 } from "child_process";
182705
183394
  var encoder3 = new TextEncoder;
@@ -182708,6 +183397,58 @@ function sseFrame(data) {
182708
183397
 
182709
183398
  `);
182710
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
+ }
182711
183452
  function deriveInstallStatus(report) {
182712
183453
  if (report.hosts.some((h) => h.status === "switched"))
182713
183454
  return "switched";
@@ -182782,17 +183523,15 @@ function createRegenerateStreamHandler() {
182782
183523
  closedRef.closed = true;
182783
183524
  return;
182784
183525
  }
182785
- const generateScript = path42.join(root2, "scripts", "generate-subagent-artifacts.ts");
183526
+ let generatorScripts;
182786
183527
  try {
182787
- child = spawn3("bun", [generateScript], {
182788
- env: { ...process.env },
182789
- stdio: ["pipe", "pipe", "pipe"]
182790
- });
183528
+ generatorScripts = deriveGeneratorScripts(root2);
183529
+ assertGeneratorBackstop(generatorScripts);
182791
183530
  } catch (e) {
182792
183531
  controller2.enqueue(sseFrame({
182793
183532
  type: "done",
182794
183533
  exitCode: null,
182795
- error: `spawn failed: ${e.message}`
183534
+ error: `could not derive the generator list: ${e.message}`
182796
183535
  }));
182797
183536
  controller2.close();
182798
183537
  closedRef.closed = true;
@@ -182815,35 +183554,57 @@ function createRegenerateStreamHandler() {
182815
183554
  }
182816
183555
  }
182817
183556
  };
182818
- child.stdout?.on("data", (chunk) => emitLine("stdout", chunk));
182819
- child.stderr?.on("data", (chunk) => emitLine("stderr", chunk));
182820
- child.on("error", (e) => {
183557
+ const finish = (exitCode, error51) => {
182821
183558
  if (closedRef.closed)
182822
183559
  return;
182823
183560
  closedRef.closed = true;
182824
183561
  try {
182825
- 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 }));
182826
183563
  controller2.close();
182827
183564
  } catch {}
182828
- });
182829
- child.on("close", (code) => {
183565
+ };
183566
+ const runGenerator = (index) => {
182830
183567
  if (closedRef.closed)
182831
183568
  return;
182832
- if (code === 0) {
183569
+ if (index >= generatorScripts.length) {
182833
183570
  try {
182834
183571
  controller2.enqueue(sseFrame({ type: "line", stream: "stdout", text: "Installing regenerated agents to active directories..." }));
182835
183572
  } catch {
182836
183573
  closedRef.closed = true;
182837
183574
  return;
182838
183575
  }
183576
+ emitSkillsFrames(controller2, closedRef);
182839
183577
  installActiveProfiles(controller2, closedRef);
183578
+ finish(0);
183579
+ return;
182840
183580
  }
182841
- closedRef.closed = true;
183581
+ const generator = generatorScripts[index];
183582
+ const scriptPath = path42.join(root2, generator.relPath);
182842
183583
  try {
182843
- controller2.enqueue(sseFrame({ type: "done", exitCode: code }));
182844
- controller2.close();
182845
- } catch {}
182846
- });
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);
182847
183608
  },
182848
183609
  cancel() {
182849
183610
  closedRef.closed = true;
@@ -182865,8 +183626,8 @@ function createRegenerateStreamHandler() {
182865
183626
  var modelRegistryStreamRoutes = new Elysia({ prefix: "/api/v1/model-registry" }).post("/regenerate-and-install-stream", createRegenerateStreamHandler(), {
182866
183627
  detail: {
182867
183628
  tags: ["model-registry"],
182868
- summary: "Regenerate subagent artifacts + auto-install to active dirs (streaming SSE)",
182869
- 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."
182870
183631
  }
182871
183632
  }).post("/regenerate-stream", createRegenerateStreamHandler(), {
182872
183633
  detail: {
@@ -182976,7 +183737,7 @@ var restartRoutes = new Elysia({ prefix: "/api/v1/system" }).onAfterResponse(()
182976
183737
 
182977
183738
  // src/routes/logs.ts
182978
183739
  init_dist();
182979
- import fs28 from "fs";
183740
+ import fs29 from "fs";
182980
183741
  var LOGS_DETAIL = { tags: ["logs"] };
182981
183742
  var MAX_SCAN_BYTES = 64 * 1024 * 1024;
182982
183743
  var MAX_LIMIT = 1000;
@@ -183056,7 +183817,7 @@ function parseLine(line, prevTs) {
183056
183817
  function realReadTail(filePath, maxBytes) {
183057
183818
  let size;
183058
183819
  try {
183059
- size = fs28.statSync(filePath).size;
183820
+ size = fs29.statSync(filePath).size;
183060
183821
  } catch {
183061
183822
  return { content: "", truncated: false };
183062
183823
  }
@@ -183064,24 +183825,24 @@ function realReadTail(filePath, maxBytes) {
183064
183825
  return { content: "", truncated: false };
183065
183826
  if (size <= maxBytes) {
183066
183827
  try {
183067
- return { content: fs28.readFileSync(filePath, "utf8"), truncated: false };
183828
+ return { content: fs29.readFileSync(filePath, "utf8"), truncated: false };
183068
183829
  } catch {
183069
183830
  return { content: "", truncated: false };
183070
183831
  }
183071
183832
  }
183072
183833
  try {
183073
- const fd = fs28.openSync(filePath, "r");
183834
+ const fd = fs29.openSync(filePath, "r");
183074
183835
  try {
183075
183836
  const start = size - maxBytes;
183076
183837
  const buf = Buffer.alloc(maxBytes);
183077
- fs28.readSync(fd, buf, 0, maxBytes, start);
183838
+ fs29.readSync(fd, buf, 0, maxBytes, start);
183078
183839
  let text3 = buf.toString("utf8");
183079
183840
  const firstNewline = text3.indexOf(`
183080
183841
  `);
183081
183842
  text3 = firstNewline !== -1 ? text3.slice(firstNewline + 1) : "";
183082
183843
  return { content: text3, truncated: true };
183083
183844
  } finally {
183084
- fs28.closeSync(fd);
183845
+ fs29.closeSync(fd);
183085
183846
  }
183086
183847
  } catch {
183087
183848
  return { content: "", truncated: true };
@@ -183091,7 +183852,7 @@ var realReader = {
183091
183852
  listFiles(filePath, maxFiles) {
183092
183853
  return sinkFiles(filePath, maxFiles).filter((f) => {
183093
183854
  try {
183094
- fs28.accessSync(f, fs28.constants.R_OK);
183855
+ fs29.accessSync(f, fs29.constants.R_OK);
183095
183856
  return true;
183096
183857
  } catch {
183097
183858
  return false;
@@ -183167,8 +183928,6 @@ function renderTxtLine(entry2) {
183167
183928
  const metaStr = entry2.meta ? ` ${JSON.stringify(entry2.meta)}` : "";
183168
183929
  return `[${entry2.ts}] [${entry2.level.toUpperCase()}] ${entry2.message}${metaStr}`;
183169
183930
  }
183170
- var SSE_HEARTBEAT_MS_DEFAULT = 15000;
183171
- var SSE_MAX_DURATION_MS_DEFAULT = 10 * 60 * 1000;
183172
183931
  var SINK_POLL_MS_DEFAULT = 1000;
183173
183932
  var SINK_POLL_MAX_BYTES = 1024 * 1024;
183174
183933
  function startSinkTail(enqueue) {
@@ -183182,7 +183941,7 @@ function startSinkTail(enqueue) {
183182
183941
  let currentFile = initial[0];
183183
183942
  let offset;
183184
183943
  try {
183185
- offset = fs28.statSync(currentFile).size;
183944
+ offset = fs29.statSync(currentFile).size;
183186
183945
  } catch {
183187
183946
  return;
183188
183947
  }
@@ -183198,7 +183957,7 @@ function startSinkTail(enqueue) {
183198
183957
  offset = 0;
183199
183958
  carry = "";
183200
183959
  }
183201
- const size = fs28.statSync(currentFile).size;
183960
+ const size = fs29.statSync(currentFile).size;
183202
183961
  if (size < offset) {
183203
183962
  offset = 0;
183204
183963
  carry = "";
@@ -183207,11 +183966,11 @@ function startSinkTail(enqueue) {
183207
183966
  return;
183208
183967
  const length = Math.min(size - offset, SINK_POLL_MAX_BYTES);
183209
183968
  const buf = Buffer.alloc(length);
183210
- const fd = fs28.openSync(currentFile, "r");
183969
+ const fd = fs29.openSync(currentFile, "r");
183211
183970
  try {
183212
- fs28.readSync(fd, buf, 0, length, offset);
183971
+ fs29.readSync(fd, buf, 0, length, offset);
183213
183972
  } finally {
183214
- fs28.closeSync(fd);
183973
+ fs29.closeSync(fd);
183215
183974
  }
183216
183975
  offset += length;
183217
183976
  const text3 = carry + buf.toString("utf8");
@@ -183274,9 +184033,9 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183274
184033
  summary: "Download the queried log range as jsonl or txt",
183275
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."
183276
184035
  }
183277
- }).get("/stream", () => {
183278
- const HEARTBEAT_MS = Number(process.env.MASSA_AI_SSE_HEARTBEAT_MS) || SSE_HEARTBEAT_MS_DEFAULT;
183279
- 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();
183280
184039
  const encoder4 = new TextEncoder;
183281
184040
  let closed = false;
183282
184041
  let unsubscribe;
@@ -183284,6 +184043,18 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183284
184043
  let closeTimer;
183285
184044
  const stream2 = new ReadableStream({
183286
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
+ };
183287
184058
  const enqueue = (data) => {
183288
184059
  if (closed)
183289
184060
  return;
@@ -183292,7 +184063,7 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183292
184063
 
183293
184064
  `));
183294
184065
  } catch {
183295
- closed = true;
184066
+ teardown();
183296
184067
  }
183297
184068
  };
183298
184069
  const tail = startSinkTail(enqueue);
@@ -183313,18 +184084,10 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183313
184084
 
183314
184085
  `));
183315
184086
  } catch {
183316
- closed = true;
183317
- clearInterval(heartbeatTimer);
184087
+ teardown();
183318
184088
  }
183319
184089
  }, HEARTBEAT_MS);
183320
- closeTimer = setTimeout(() => {
183321
- closed = true;
183322
- unsubscribe?.();
183323
- clearInterval(heartbeatTimer);
183324
- try {
183325
- controller2.close();
183326
- } catch {}
183327
- }, MAX_DURATION_MS);
184090
+ closeTimer = setTimeout(teardown, MAX_DURATION_MS);
183328
184091
  },
183329
184092
  cancel() {
183330
184093
  closed = true;
@@ -183346,9 +184109,107 @@ var logsRoutes = new Elysia({ prefix: "/api/v1/logs" }).get("/", ({ query, set:
183346
184109
  }, {
183347
184110
  detail: {
183348
184111
  ...LOGS_DETAIL,
183349
- summary: "SSE tail of newly buffered log entries",
183350
- description: "Server-Sent Events stream emitting `data: <LogEntry JSON>` for every entry subsequently pushed into the in-process ring buffer, plus `: heartbeat` comments. Same heartbeat and max-duration auto-close behavior as GET /api/v1/events. Scoped to this server process \u2014 a separate range query over the file sink may contain entries this stream never showed (e.g. from the stdio MCP server)."
184112
+ summary: "SSE tail of newly appended log entries",
184113
+ description: "Server-Sent Events stream emitting `data: <LogEntry JSON>` for every entry subsequently appended, plus `: heartbeat` comments. Same heartbeat and max-duration auto-close behavior as GET /api/v1/events. Tails the shared file sink when one is readable \u2014 matching every massa-ai process, including the stdio MCP server, the same scope as GET /api/v1/logs \u2014 and falls back to the in-process ring buffer, scoped to this server process only, when no sink file is readable."
184114
+ }
184115
+ });
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;
183351
184210
  }
184211
+ set3.status = 403;
184212
+ return { ...WRITE_REFUSED };
183352
184213
  });
183353
184214
 
183354
184215
  // src/middleware/error.ts
@@ -183474,7 +184335,7 @@ var app = new Elysia({ adapter: node() }).use(cors(buildCorsOptions(config.get("
183474
184335
  },
183475
184336
  security: [{ ApiKeyAuth: [] }]
183476
184337
  }
183477
- })).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()));
183478
184339
  initAuthOrExit();
183479
184340
  warnIfTrustOverrideEnabled();
183480
184341
  await listenAfterParserValidation({
@@ -183483,6 +184344,8 @@ await listenAfterParserValidation({
183483
184344
  try {
183484
184345
  app.listen({ port: Number(PORT), reusePort: false }, (server) => {
183485
184346
  setServerStopper(() => void server.stop());
184347
+ const bunServer = server.bun?.server;
184348
+ setSseRequestTimeoutSource(bunServer);
183486
184349
  });
183487
184350
  } catch (error51) {
183488
184351
  if (error51?.code === "EADDRINUSE") {