@massa-ai/mcp-client 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.
package/dist/index.js CHANGED
@@ -27171,17 +27171,11 @@ function detectRoute(platform, host) {
27171
27171
  if (route === "file")
27172
27172
  return { kind: "proceed" };
27173
27173
  if (route === "marketplace") {
27174
- if (host === "claude")
27174
+ if (host === "claude" || host === "codex")
27175
27175
  return { kind: "proceed" };
27176
- if (host === "codex") {
27177
- return {
27178
- kind: "refuse",
27179
- 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."
27180
- };
27181
- }
27182
27176
  return {
27183
27177
  kind: "refuse",
27184
- 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."
27178
+ 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."
27185
27179
  };
27186
27180
  }
27187
27181
  return {
@@ -27380,6 +27374,61 @@ var init_lock = __esm(() => {
27380
27374
  import fs5 from "fs";
27381
27375
  import os5 from "os";
27382
27376
  import path9 from "path";
27377
+ function splitPluginKey(pluginKey) {
27378
+ const at = pluginKey.indexOf("@");
27379
+ if (at === -1)
27380
+ return null;
27381
+ const pluginName = pluginKey.slice(0, at);
27382
+ const marketplaceName = pluginKey.slice(at + 1);
27383
+ if (!pluginName || !marketplaceName)
27384
+ return null;
27385
+ return { pluginName, marketplaceName };
27386
+ }
27387
+ function isContainedPath(root, candidate) {
27388
+ const rel = path9.relative(root, candidate);
27389
+ return rel === "" || !rel.startsWith("..") && !path9.isAbsolute(rel);
27390
+ }
27391
+ function resolveDirectorySourceRoot(targetHome, pluginKey) {
27392
+ const split = splitPluginKey(pluginKey);
27393
+ if (!split)
27394
+ return;
27395
+ const { pluginName, marketplaceName } = split;
27396
+ const knownMarketplacesPath = path9.join(targetHome, ".claude", "plugins", "known_marketplaces.json");
27397
+ let known;
27398
+ try {
27399
+ known = JSON.parse(fs5.readFileSync(knownMarketplacesPath, "utf8"));
27400
+ } catch {
27401
+ return;
27402
+ }
27403
+ const entry = known?.[marketplaceName];
27404
+ if (!entry || entry.source?.source !== "directory")
27405
+ return;
27406
+ const installLocation = entry.installLocation;
27407
+ if (!installLocation)
27408
+ return null;
27409
+ const manifestPath = path9.join(installLocation, ".claude-plugin", "marketplace.json");
27410
+ let manifest;
27411
+ try {
27412
+ manifest = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
27413
+ } catch {
27414
+ return null;
27415
+ }
27416
+ const plugin = manifest.plugins?.find((p) => p?.name === pluginName);
27417
+ const relSource = plugin?.source;
27418
+ if (!relSource)
27419
+ return null;
27420
+ const resolvedInstallLocation = path9.resolve(installLocation);
27421
+ const composed = path9.resolve(resolvedInstallLocation, relSource);
27422
+ if (!isContainedPath(resolvedInstallLocation, composed))
27423
+ return null;
27424
+ try {
27425
+ if (!fs5.existsSync(composed))
27426
+ return null;
27427
+ } catch {
27428
+ return null;
27429
+ }
27430
+ return composed;
27431
+ }
27383
27432
  function selectRecord(records) {
27384
27433
  if (records.length === 0)
27385
27434
  return;
@@ -27399,6 +27448,9 @@ function selectRecord(records) {
27399
27448
  function resolveClaudeMarketplaceRoot(opts = {}) {
27400
27449
  const targetHome = opts.targetHome ?? os5.homedir();
27401
27450
  const pluginKey = opts.pluginKey ?? DEFAULT_PLUGIN_KEY;
27451
+ const directoryResult = resolveDirectorySourceRoot(targetHome, pluginKey);
27452
+ if (directoryResult !== undefined)
27453
+ return directoryResult;
27402
27454
  const registryPath = path9.join(targetHome, ".claude", "plugins", "installed_plugins.json");
27403
27455
  let records;
27404
27456
  try {
@@ -27430,6 +27482,7 @@ import fs6 from "fs";
27430
27482
  import path10 from "path";
27431
27483
  import os6 from "os";
27432
27484
  import crypto4 from "crypto";
27485
+ import { execFileSync as execFileSync2 } from "child_process";
27433
27486
  function namedError3(name, message) {
27434
27487
  const err = new SwitchEngineError(message);
27435
27488
  err.name = name;
@@ -27508,6 +27561,46 @@ function matchesGlob(filename, glob) {
27508
27561
  const suffix = glob.slice(starIdx + 1);
27509
27562
  return filename.length >= prefix.length + suffix.length && filename.startsWith(prefix) && filename.endsWith(suffix);
27510
27563
  }
27564
+ function matchingFileNames(dir, glob) {
27565
+ return fs6.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && matchesGlob(e.name, glob)).map((e) => e.name);
27566
+ }
27567
+ function detectGitAvailability(dir) {
27568
+ try {
27569
+ const out = execFileSync2("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], {
27570
+ stdio: ["ignore", "pipe", "ignore"]
27571
+ });
27572
+ return out.toString().trim() === "true" ? "in-repo" : "not-a-repo";
27573
+ } catch (err) {
27574
+ return err.code === "ENOENT" ? "no-git" : "not-a-repo";
27575
+ }
27576
+ }
27577
+ function gitTrackedFileNames(dir, filenames) {
27578
+ if (filenames.length === 0)
27579
+ return new Set;
27580
+ try {
27581
+ const out = execFileSync2("git", ["-C", dir, "ls-files", "--", ...filenames], {
27582
+ stdio: ["ignore", "pipe", "ignore"]
27583
+ });
27584
+ return new Set(out.toString().split(`
27585
+ `).map((line) => line.trim()).filter(Boolean));
27586
+ } catch {
27587
+ return new Set;
27588
+ }
27589
+ }
27590
+ function checkTrackedPathGuard(activeDir, filenames) {
27591
+ if (filenames.length === 0 || !fs6.existsSync(activeDir))
27592
+ return GUARD_PASS;
27593
+ const availability = detectGitAvailability(activeDir);
27594
+ if (availability === "no-git")
27595
+ return GUARD_UNCHECKED;
27596
+ if (availability === "not-a-repo")
27597
+ return GUARD_PASS;
27598
+ const tracked = gitTrackedFileNames(activeDir, filenames);
27599
+ if (tracked.size === 0)
27600
+ return GUARD_PASS;
27601
+ const offending = filenames.find((name) => tracked.has(name));
27602
+ return { blocked: true, path: path10.join(activeDir, offending), unchecked: false };
27603
+ }
27511
27604
  function assertStateWritable(stateFilePath) {
27512
27605
  const dir = path10.dirname(stateFilePath);
27513
27606
  try {
@@ -27637,12 +27730,27 @@ function switchProfile(opts) {
27637
27730
  rows.push({ host: h.host, status: "switched" });
27638
27731
  continue;
27639
27732
  }
27733
+ const candidateNames = matchingFileNames(h.variantDir, h.layout.activeGlob);
27734
+ const guard = checkTrackedPathGuard(h.layout.activeDir, candidateNames);
27735
+ if (guard.blocked) {
27736
+ rows.push({
27737
+ host: h.host,
27738
+ status: "failed",
27739
+ reason: `refusing to write ${guard.path} \u2014 it is tracked by git and this switch would dirty the checkout`
27740
+ });
27741
+ continue;
27742
+ }
27640
27743
  try {
27641
27744
  const filesChanged = copyVariant(h.host, h.layout, h.variantDir);
27642
27745
  updatePlatform(stateFilePath, h.host, {
27643
27746
  modelProfile: { profile: opts.profile, switchedAt: new Date().toISOString() }
27644
27747
  });
27645
- rows.push({ host: h.host, status: "switched", filesChanged });
27748
+ rows.push({
27749
+ host: h.host,
27750
+ status: "switched",
27751
+ filesChanged,
27752
+ ...guard.unchecked ? { reason: "tracked-path guard could not verify (git unavailable) \u2014 proceeded unchecked" } : {}
27753
+ });
27646
27754
  } catch (err) {
27647
27755
  rows.push({ host: h.host, status: "failed", reason: err.message });
27648
27756
  }
@@ -27658,7 +27766,7 @@ function orderRows(universe, rows) {
27658
27766
  const byHost = new Map(rows.map((r) => [r.host, r]));
27659
27767
  return HOSTS.filter((h) => universe.includes(h) && byHost.has(h)).map((h) => byHost.get(h));
27660
27768
  }
27661
- 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");
27769
+ 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;
27662
27770
  var init_engine = __esm(() => {
27663
27771
  init_hosts();
27664
27772
  init_state();
@@ -27670,6 +27778,8 @@ var init_engine = __esm(() => {
27670
27778
  this.name = "SwitchEngineError";
27671
27779
  }
27672
27780
  };
27781
+ GUARD_PASS = { blocked: false, unchecked: false };
27782
+ GUARD_UNCHECKED = { blocked: false, unchecked: true };
27673
27783
  });
27674
27784
 
27675
27785
  // ../../packages/shared/dist/profile-switch/report.js
@@ -138783,7 +138893,7 @@ var init_git_ref_validation = __esm(() => {
138783
138893
  });
138784
138894
 
138785
138895
  // ../../packages/core/dist/services/symbol/impact-analysis.js
138786
- import { execFileSync as execFileSync2 } from "child_process";
138896
+ import { execFileSync as execFileSync3 } from "child_process";
138787
138897
  function readBfsCteFlag() {
138788
138898
  try {
138789
138899
  return Boolean(config2.get("impact")?.bfsCteEnabled);
@@ -139109,19 +139219,19 @@ function defaultDiffRunner(projectPath2, scope, baseBranch, since) {
139109
139219
  let diffRange;
139110
139220
  if (since) {
139111
139221
  try {
139112
- ref = execFileSync2("git", ["-C", projectPath2, "rev-parse", "--verify", `${since}^{commit}`], {
139222
+ ref = execFileSync3("git", ["-C", projectPath2, "rev-parse", "--verify", `${since}^{commit}`], {
139113
139223
  cwd: projectPath2,
139114
139224
  encoding: "utf-8",
139115
139225
  stdio: ["ignore", "pipe", "pipe"]
139116
139226
  }).trim();
139117
139227
  } catch {
139118
- ref = execFileSync2("git", ["-C", projectPath2, "rev-list", "-1", `--before=${since}`, "HEAD"], {
139228
+ ref = execFileSync3("git", ["-C", projectPath2, "rev-list", "-1", `--before=${since}`, "HEAD"], {
139119
139229
  cwd: projectPath2,
139120
139230
  encoding: "utf-8",
139121
139231
  stdio: ["ignore", "pipe", "pipe"]
139122
139232
  }).trim();
139123
139233
  if (!ref) {
139124
- const emptyTree = execFileSync2("git", ["hash-object", "-t", "tree", "--stdin"], {
139234
+ const emptyTree = execFileSync3("git", ["hash-object", "-t", "tree", "--stdin"], {
139125
139235
  cwd: projectPath2,
139126
139236
  encoding: "utf-8",
139127
139237
  input: "",
@@ -139133,7 +139243,7 @@ function defaultDiffRunner(projectPath2, scope, baseBranch, since) {
139133
139243
  }
139134
139244
  args.push(diffRange ?? `${ref}...HEAD`);
139135
139245
  }
139136
- const out = execFileSync2("git", args, {
139246
+ const out = execFileSync3("git", args, {
139137
139247
  cwd: projectPath2,
139138
139248
  encoding: "utf-8",
139139
139249
  stdio: ["ignore", "pipe", "pipe"],
@@ -139145,7 +139255,7 @@ function defaultDiffRunner(projectPath2, scope, baseBranch, since) {
139145
139255
  let untrackedFiltered = 0;
139146
139256
  const merged = new Set(tracked);
139147
139257
  if (includeUntracked) {
139148
- const untracked = execFileSync2("git", ["-C", projectPath2, "ls-files", "--others", "--exclude-standard"], {
139258
+ const untracked = execFileSync3("git", ["-C", projectPath2, "ls-files", "--others", "--exclude-standard"], {
139149
139259
  cwd: projectPath2,
139150
139260
  encoding: "utf-8",
139151
139261
  stdio: ["ignore", "pipe", "pipe"],
@@ -140181,7 +140291,7 @@ var init_tools = __esm(() => {
140181
140291
  });
140182
140292
 
140183
140293
  // ../../packages/core/dist/services/executor/runtime.js
140184
- import { execFileSync as execFileSync3, execSync } from "child_process";
140294
+ import { execFileSync as execFileSync4, execSync } from "child_process";
140185
140295
  function commandExists(cmd) {
140186
140296
  try {
140187
140297
  const check2 = isWindows ? `where ${cmd}` : `command -v ${cmd}`;
@@ -140214,7 +140324,7 @@ function getVersion(cmd, args = ["--version"], deps) {
140214
140324
  timeout: 5000
140215
140325
  }).trim().split(/\r?\n/)[0];
140216
140326
  }
140217
- return execFileSync3(cmd, args, {
140327
+ return execFileSync4(cmd, args, {
140218
140328
  encoding: "utf-8",
140219
140329
  stdio: ["pipe", "pipe", "pipe"],
140220
140330
  timeout: 5000
@@ -140355,12 +140465,12 @@ var init_runtime = __esm(() => {
140355
140465
 
140356
140466
  // ../../packages/core/dist/services/executor/sandbox.js
140357
140467
  import { realpathSync as realpathSync2 } from "fs";
140358
- import { execFileSync as execFileSync4 } from "child_process";
140468
+ import { execFileSync as execFileSync5 } from "child_process";
140359
140469
  function isDockerAvailable() {
140360
140470
  if (_dockerAvailable !== null)
140361
140471
  return _dockerAvailable;
140362
140472
  try {
140363
- execFileSync4("docker", ["--version"], { stdio: "pipe", timeout: 5000 });
140473
+ execFileSync5("docker", ["--version"], { stdio: "pipe", timeout: 5000 });
140364
140474
  _dockerAvailable = true;
140365
140475
  } catch {
140366
140476
  _dockerAvailable = false;
@@ -140371,7 +140481,7 @@ function isSeatbeltAvailable() {
140371
140481
  if (_seatbeltAvailable !== null)
140372
140482
  return _seatbeltAvailable;
140373
140483
  try {
140374
- execFileSync4("sandbox-exec", ["--version"], { stdio: "pipe", timeout: 5000 });
140484
+ execFileSync5("sandbox-exec", ["--version"], { stdio: "pipe", timeout: 5000 });
140375
140485
  _seatbeltAvailable = true;
140376
140486
  } catch {
140377
140487
  _seatbeltAvailable = false;
@@ -140476,7 +140586,7 @@ var init_sandbox = __esm(() => {
140476
140586
  });
140477
140587
 
140478
140588
  // ../../packages/core/dist/services/executor/executor.js
140479
- import { spawn, execSync as execSync2, execFileSync as execFileSync5 } from "child_process";
140589
+ import { spawn, execSync as execSync2, execFileSync as execFileSync6 } from "child_process";
140480
140590
  import { mkdtempSync, writeFileSync as writeFileSync2, rmSync, realpathSync as realpathSync3 } from "fs";
140481
140591
  import { join as join3, resolve as resolve5, isAbsolute, relative as relative2 } from "path";
140482
140592
  import { tmpdir } from "os";
@@ -140679,7 +140789,7 @@ ${body}`;
140679
140789
  const binPath = srcPath.replace(/\.rs$/, isWin ? ".exe" : "");
140680
140790
  try {
140681
140791
  try {
140682
- execFileSync5("rustc", [srcPath, "-o", binPath], {
140792
+ execFileSync6("rustc", [srcPath, "-o", binPath], {
140683
140793
  cwd,
140684
140794
  timeout: Math.min(timeout, 60000),
140685
140795
  encoding: "utf-8",
@@ -143621,6 +143731,85 @@ var init_scheduler = __esm(() => {
143621
143731
  };
143622
143732
  });
143623
143733
 
143734
+ // ../../packages/core/dist/data/proposal/proposal-payload-validation.js
143735
+ function isRecord(value) {
143736
+ return value !== null && typeof value === "object" && !Array.isArray(value);
143737
+ }
143738
+ function isStringArray(value) {
143739
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
143740
+ }
143741
+ function isString(value) {
143742
+ return typeof value === "string";
143743
+ }
143744
+ function isNumber(value) {
143745
+ return typeof value === "number";
143746
+ }
143747
+ function isValidProposalPayload(kind, value) {
143748
+ const rule = PROPOSAL_PAYLOAD_RULES[kind];
143749
+ if (!Object.keys(value).every((key) => rule.allowedKeys.includes(key)))
143750
+ return false;
143751
+ if (rule.requireNonEmpty && Object.keys(value).length === 0)
143752
+ return false;
143753
+ for (const [field3, fieldRule] of Object.entries(rule.fields)) {
143754
+ const present = value[field3] !== undefined;
143755
+ if (!present) {
143756
+ if (!fieldRule.optional)
143757
+ return false;
143758
+ continue;
143759
+ }
143760
+ if (!fieldRule.validate(value[field3]))
143761
+ return false;
143762
+ }
143763
+ return true;
143764
+ }
143765
+ function assertValidProposalPayload(kind, value) {
143766
+ if (!isRecord(value)) {
143767
+ throw new ProposalPayloadValidationError(kind, `proposal payload must be an object for kind "${kind}"`);
143768
+ }
143769
+ if (!isValidProposalPayload(kind, value)) {
143770
+ throw new ProposalPayloadValidationError(kind, `proposal payload is invalid for kind "${kind}"`);
143771
+ }
143772
+ }
143773
+ var PROPOSAL_PAYLOAD_RULES, ProposalPayloadValidationError;
143774
+ var init_proposal_payload_validation = __esm(() => {
143775
+ PROPOSAL_PAYLOAD_RULES = {
143776
+ "memory.create": {
143777
+ allowedKeys: ["content", "type", "level", "importance", "tags"],
143778
+ fields: {
143779
+ content: { optional: false, validate: isString },
143780
+ type: { optional: true, validate: isString },
143781
+ level: { optional: true, validate: isNumber },
143782
+ importance: { optional: true, validate: isNumber },
143783
+ tags: { optional: true, validate: isStringArray }
143784
+ }
143785
+ },
143786
+ "memory.update": {
143787
+ allowedKeys: ["content", "importance", "tags"],
143788
+ requireNonEmpty: true,
143789
+ fields: {
143790
+ content: { optional: true, validate: isString },
143791
+ importance: { optional: true, validate: isNumber },
143792
+ tags: { optional: true, validate: isStringArray }
143793
+ }
143794
+ },
143795
+ "memory.tag": {
143796
+ allowedKeys: ["tags"],
143797
+ fields: {
143798
+ tags: { optional: false, validate: isStringArray }
143799
+ }
143800
+ }
143801
+ };
143802
+ ProposalPayloadValidationError = class ProposalPayloadValidationError extends Error {
143803
+ kind;
143804
+ statusCode = 400;
143805
+ constructor(kind, message) {
143806
+ super(message);
143807
+ this.kind = kind;
143808
+ this.name = "ProposalPayloadValidationError";
143809
+ }
143810
+ };
143811
+ });
143812
+
143624
143813
  // ../../packages/core/dist/data/proposal/proposal-contract.js
143625
143814
  class MemoryProposalStore {
143626
143815
  rows = [];
@@ -143644,26 +143833,53 @@ class MemoryProposalStore {
143644
143833
  row.decidedAt = decidedAt ?? Date.now();
143645
143834
  return structuredClone(row);
143646
143835
  }
143836
+ async create(input) {
143837
+ assertValidProposalPayload(input.kind, input.payload);
143838
+ const record3 = {
143839
+ id: input.id,
143840
+ projectId: input.projectId,
143841
+ kind: input.kind,
143842
+ targetMemoryId: input.targetMemoryId ?? null,
143843
+ payload: input.payload,
143844
+ rationale: input.rationale ?? "",
143845
+ status: "pending",
143846
+ createdAt: input.createdAt ?? Date.now(),
143847
+ decidedAt: null
143848
+ };
143849
+ this.rows.push(structuredClone(record3));
143850
+ return structuredClone(record3);
143851
+ }
143852
+ async update(id, patch) {
143853
+ const row = this.rows.find((item) => item.id === id);
143854
+ if (!row)
143855
+ return null;
143856
+ if (patch.payload !== undefined)
143857
+ assertValidProposalPayload(row.kind, patch.payload);
143858
+ if (patch.rationale !== undefined)
143859
+ row.rationale = patch.rationale;
143860
+ if (patch.payload !== undefined)
143861
+ row.payload = patch.payload;
143862
+ return structuredClone(row);
143863
+ }
143864
+ async delete(id) {
143865
+ const index = this.rows.findIndex((item) => item.id === id);
143866
+ if (index === -1)
143867
+ return null;
143868
+ this.rows.splice(index, 1);
143869
+ return id;
143870
+ }
143647
143871
  async journalMode() {
143648
143872
  return "memory";
143649
143873
  }
143650
143874
  }
143651
143875
  var PROPOSAL_STATUSES, PROPOSAL_KINDS;
143652
143876
  var init_proposal_contract = __esm(() => {
143877
+ init_proposal_payload_validation();
143653
143878
  PROPOSAL_STATUSES = ["pending", "approved", "rejected"];
143654
143879
  PROPOSAL_KINDS = ["memory.create", "memory.update", "memory.tag"];
143655
143880
  });
143656
143881
 
143657
143882
  // ../../packages/core/dist/data/proposal/proposal-repository-pg.js
143658
- function isRecord(value) {
143659
- return value !== null && typeof value === "object" && !Array.isArray(value);
143660
- }
143661
- function stringArray(value) {
143662
- return Array.isArray(value) && value.every((item) => typeof item === "string");
143663
- }
143664
- function validKeys(value, allowed) {
143665
- return Object.keys(value).every((key) => allowed.includes(key));
143666
- }
143667
143883
  function parsePayload(raw2, kind) {
143668
143884
  let value;
143669
143885
  try {
@@ -143674,15 +143890,7 @@ function parsePayload(raw2, kind) {
143674
143890
  if (!isRecord(value)) {
143675
143891
  throw storeCorruption("proposal.payload_json", new TypeError("expected object"));
143676
143892
  }
143677
- let valid = false;
143678
- if (kind === "memory.create") {
143679
- 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));
143680
- } else if (kind === "memory.update") {
143681
- 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));
143682
- } else {
143683
- valid = validKeys(value, ["tags"]) && stringArray(value.tags);
143684
- }
143685
- if (!valid) {
143893
+ if (!isValidProposalPayload(kind, value)) {
143686
143894
  throw storeCorruption("proposal.payload_json", new TypeError("invalid proposal payload"));
143687
143895
  }
143688
143896
  return value;
@@ -143816,6 +144024,65 @@ class PgProposalStore {
143816
144024
  this.mirror.set(id, persisted);
143817
144025
  return structuredClone(persisted);
143818
144026
  }
144027
+ async create(input) {
144028
+ assertValidProposalPayload(input.kind, input.payload);
144029
+ const record3 = {
144030
+ id: input.id,
144031
+ projectId: input.projectId,
144032
+ kind: input.kind,
144033
+ targetMemoryId: input.targetMemoryId ?? null,
144034
+ payload: input.payload,
144035
+ rationale: input.rationale ?? "",
144036
+ status: "pending",
144037
+ createdAt: input.createdAt ?? Date.now(),
144038
+ decidedAt: null
144039
+ };
144040
+ await this.insert(record3);
144041
+ return record3;
144042
+ }
144043
+ async update(id, patch) {
144044
+ await this.ensureHydrated();
144045
+ const current = this.mirror.get(id);
144046
+ if (!current)
144047
+ return null;
144048
+ if (patch.payload !== undefined) {
144049
+ assertValidProposalPayload(current.kind, patch.payload);
144050
+ }
144051
+ const merged = {
144052
+ ...current,
144053
+ ...patch.rationale !== undefined ? { rationale: patch.rationale } : {},
144054
+ ...patch.payload !== undefined ? { payload: patch.payload } : {}
144055
+ };
144056
+ let rows;
144057
+ try {
144058
+ rows = await this.getClient().$queryRaw`
144059
+ UPDATE proposals
144060
+ SET rationale = ${merged.rationale}, payload_json = ${JSON.stringify(merged.payload)}
144061
+ WHERE id = ${id}
144062
+ RETURNING id, project_id, kind, target_memory_id, payload_json,
144063
+ rationale, status, created_at, decided_at`;
144064
+ } catch (error51) {
144065
+ throw searchBackendUnavailable("proposal_store", error51);
144066
+ }
144067
+ if (!rows[0])
144068
+ return null;
144069
+ const persisted = toRecord(rows[0]);
144070
+ this.mirror.set(id, persisted);
144071
+ return structuredClone(persisted);
144072
+ }
144073
+ async delete(id) {
144074
+ await this.ensureHydrated();
144075
+ let affected;
144076
+ try {
144077
+ affected = await this.getClient().$executeRaw`DELETE FROM proposals WHERE id = ${id}`;
144078
+ } catch (error51) {
144079
+ throw searchBackendUnavailable("proposal_store", error51);
144080
+ }
144081
+ if (affected === 0)
144082
+ return null;
144083
+ this.mirror.delete(id);
144084
+ return id;
144085
+ }
143819
144086
  async journalMode() {
143820
144087
  await this.ensureHydrated();
143821
144088
  return "postgres";
@@ -143832,6 +144099,7 @@ var init_proposal_repository_pg = __esm(() => {
143832
144099
  init_alias_resolver();
143833
144100
  init_search_diagnostics();
143834
144101
  init_proposal_contract();
144102
+ init_proposal_payload_validation();
143835
144103
  });
143836
144104
 
143837
144105
  // ../../packages/core/dist/data/proposal/proposal-repository.js
@@ -164234,6 +164502,29 @@ class MemoryHandoffStore {
164234
164502
  row.acceptedAt = status === "accepted" ? acceptedAt ?? Date.now() : null;
164235
164503
  return structuredClone(row);
164236
164504
  }
164505
+ async update(id, patch) {
164506
+ const row = this.rows.find((item) => item.id === id);
164507
+ if (!row)
164508
+ return null;
164509
+ if (patch.targetAgent !== undefined)
164510
+ row.targetAgent = patch.targetAgent;
164511
+ if (patch.summary !== undefined)
164512
+ row.summary = patch.summary;
164513
+ if (patch.openQuestions !== undefined)
164514
+ row.openQuestions = patch.openQuestions;
164515
+ if (patch.nextSteps !== undefined)
164516
+ row.nextSteps = patch.nextSteps;
164517
+ if (patch.files !== undefined)
164518
+ row.files = patch.files;
164519
+ return structuredClone(row);
164520
+ }
164521
+ async delete(id) {
164522
+ const index = this.rows.findIndex((item) => item.id === id);
164523
+ if (index === -1)
164524
+ return null;
164525
+ this.rows.splice(index, 1);
164526
+ return id;
164527
+ }
164237
164528
  async journalMode() {
164238
164529
  return "memory";
164239
164530
  }
@@ -164389,6 +164680,53 @@ class PgHandoffStore {
164389
164680
  this.mirror.set(id, persisted);
164390
164681
  return structuredClone(persisted);
164391
164682
  }
164683
+ async update(id, patch) {
164684
+ await this.ensureHydrated();
164685
+ const current = this.mirror.get(id);
164686
+ if (!current)
164687
+ return null;
164688
+ const merged = {
164689
+ ...current,
164690
+ ...patch.targetAgent !== undefined ? { targetAgent: patch.targetAgent } : {},
164691
+ ...patch.summary !== undefined ? { summary: patch.summary } : {},
164692
+ ...patch.openQuestions !== undefined ? { openQuestions: patch.openQuestions } : {},
164693
+ ...patch.nextSteps !== undefined ? { nextSteps: patch.nextSteps } : {},
164694
+ ...patch.files !== undefined ? { files: patch.files } : {}
164695
+ };
164696
+ let rows;
164697
+ try {
164698
+ rows = await this.getClient().$queryRaw`
164699
+ UPDATE handoffs
164700
+ SET target_agent = ${merged.targetAgent}, summary = ${merged.summary},
164701
+ open_questions_json = ${JSON.stringify(merged.openQuestions ?? [])},
164702
+ next_steps_json = ${JSON.stringify(merged.nextSteps ?? [])},
164703
+ files_json = ${JSON.stringify(merged.files ?? [])}
164704
+ WHERE id = ${id}
164705
+ RETURNING id, project_id, source_session_id, target_agent, summary,
164706
+ open_questions_json, next_steps_json, files_json, status,
164707
+ created_at, accepted_at`;
164708
+ } catch (error51) {
164709
+ throw searchBackendUnavailable("handoff_store", error51);
164710
+ }
164711
+ if (!rows[0])
164712
+ return null;
164713
+ const persisted = toRecord2(rows[0]);
164714
+ this.mirror.set(id, persisted);
164715
+ return structuredClone(persisted);
164716
+ }
164717
+ async delete(id) {
164718
+ await this.ensureHydrated();
164719
+ let affected;
164720
+ try {
164721
+ affected = await this.getClient().$executeRaw`DELETE FROM handoffs WHERE id = ${id}`;
164722
+ } catch (error51) {
164723
+ throw searchBackendUnavailable("handoff_store", error51);
164724
+ }
164725
+ if (affected === 0)
164726
+ return null;
164727
+ this.mirror.delete(id);
164728
+ return id;
164729
+ }
164392
164730
  async journalMode() {
164393
164731
  await this.ensureHydrated();
164394
164732
  return "postgres";
@@ -164427,8 +164765,6 @@ var init_handoff_repository = __esm(() => {
164427
164765
  });
164428
164766
 
164429
164767
  // ../../packages/core/dist/services/handoff/handoff-service.js
164430
- import { randomUUID as randomUUID10 } from "crypto";
164431
-
164432
164768
  class HandoffService {
164433
164769
  store;
164434
164770
  memoryRepo;
@@ -164440,7 +164776,8 @@ class HandoffService {
164440
164776
  this.idFactory = deps.idFactory ?? (() => newHandoffId());
164441
164777
  const injectedRepo = deps.memoryRepo;
164442
164778
  this.memoryRepo = injectedRepo ?? {
164443
- insert: (i) => getMemoryRepository().insert(i)
164779
+ insert: (i) => getMemoryRepository().insert(i),
164780
+ update: (id, p) => getMemoryRepository().update(id, p)
164444
164781
  };
164445
164782
  }
164446
164783
  async begin(input) {
@@ -164482,7 +164819,7 @@ class HandoffService {
164482
164819
  return { ok: true, id, status: "open", memoryId };
164483
164820
  }
164484
164821
  async dualWrite(record3) {
164485
- const memId = `handoff-mem-${record3.id}-${randomUUID10().slice(0, 8)}`;
164822
+ const memId = dualWriteMemoryId(record3.id);
164486
164823
  const input = buildHandoffMemoryInput(memId, record3);
164487
164824
  await Promise.resolve(this.memoryRepo.insert(input));
164488
164825
  return memId;
@@ -164526,6 +164863,50 @@ class HandoffService {
164526
164863
  async listPending(projectId, targetAgent) {
164527
164864
  return this.store.listPending(projectId, targetAgent ?? undefined);
164528
164865
  }
164866
+ async update(params) {
164867
+ if (!params || !params.id) {
164868
+ return { ok: false, reason: "missing-id" };
164869
+ }
164870
+ const row = await this.store.getById(params.id);
164871
+ if (!row)
164872
+ return { ok: false, reason: "not-found" };
164873
+ if (params.projectId && row.projectId !== params.projectId) {
164874
+ return { ok: false, reason: "project-mismatch" };
164875
+ }
164876
+ const updated = await this.store.update(params.id, params.patch);
164877
+ if (!updated)
164878
+ return { ok: false, reason: "not-found" };
164879
+ const touchesMemoryContent = params.patch.summary !== undefined || params.patch.openQuestions !== undefined || params.patch.nextSteps !== undefined || params.patch.files !== undefined;
164880
+ if (touchesMemoryContent) {
164881
+ await this.refreshDualWriteMemory(updated);
164882
+ }
164883
+ return { ok: true, handoff: updated };
164884
+ }
164885
+ async refreshDualWriteMemory(record3) {
164886
+ try {
164887
+ const memId = dualWriteMemoryId(record3.id);
164888
+ const content = formatMemoryContent(record3);
164889
+ await Promise.resolve(this.memoryRepo.update(memId, { content }));
164890
+ } catch {}
164891
+ }
164892
+ async delete(params) {
164893
+ if (!params || !params.id) {
164894
+ return { ok: false, reason: "missing-id" };
164895
+ }
164896
+ const row = await this.store.getById(params.id);
164897
+ if (!row)
164898
+ return { ok: false, reason: "not-found" };
164899
+ if (params.projectId && row.projectId !== params.projectId) {
164900
+ return { ok: false, reason: "project-mismatch" };
164901
+ }
164902
+ const deletedId = await this.store.delete(params.id);
164903
+ if (!deletedId)
164904
+ return { ok: false, reason: "not-found" };
164905
+ return { ok: true, id: deletedId };
164906
+ }
164907
+ }
164908
+ function dualWriteMemoryId(handoffId) {
164909
+ return `handoff-mem-${handoffId}`;
164529
164910
  }
164530
164911
  function buildHandoffMemoryInput(memId, record3) {
164531
164912
  const content = formatMemoryContent(record3);
@@ -164739,6 +165120,7 @@ __export(exports_dist, {
164739
165120
  newObservationId: () => newObservationId,
164740
165121
  newHandoffId: () => newHandoffId,
164741
165122
  jsonToKeyPathChunks: () => jsonToKeyPathChunks,
165123
+ isValidProposalPayload: () => isValidProposalPayload,
164742
165124
  isKnownRegistryTable: () => isKnownRegistryTable,
164743
165125
  intentSearch: () => intentSearch,
164744
165126
  installProjectIdentityGuardsFromPool: () => installProjectIdentityGuardsFromPool,
@@ -164831,6 +165213,7 @@ __export(exports_dist, {
164831
165213
  boundDiagnostics: () => boundDiagnostics,
164832
165214
  bootstrapService: () => bootstrapService,
164833
165215
  autoImproveJob: () => autoImproveJob,
165216
+ assertValidProposalPayload: () => assertValidProposalPayload,
164834
165217
  assertUrlSafe: () => assertUrlSafe,
164835
165218
  assertParserReadyForIndexing: () => assertParserReadyForIndexing,
164836
165219
  assertGenerationNotStale: () => assertGenerationNotStale,
@@ -164895,6 +165278,7 @@ __export(exports_dist, {
164895
165278
  RUBY_QUERY_PACK: () => RUBY_QUERY_PACK,
164896
165279
  REFERENCE_TOKEN_COST: () => REFERENCE_TOKEN_COST,
164897
165280
  QueueSaturatedError: () => QueueSaturatedError,
165281
+ ProposalPayloadValidationError: () => ProposalPayloadValidationError,
164898
165282
  ProposalEnrichmentSchema: () => ProposalEnrichmentSchema,
164899
165283
  ProjectIdentityPreviewRequestSchema: () => ProjectIdentityPreviewRequestSchema,
164900
165284
  ProjectIdentityPreviewPlanner: () => ProjectIdentityPreviewPlanner,
@@ -164910,6 +165294,7 @@ __export(exports_dist, {
164910
165294
  ParserAcquireTimeoutError: () => ParserAcquireTimeoutError,
164911
165295
  PYTHON_QUERY_PACK: () => PYTHON_QUERY_PACK,
164912
165296
  PROPOSAL_STATUSES: () => PROPOSAL_STATUSES,
165297
+ PROPOSAL_PAYLOAD_RULES: () => PROPOSAL_PAYLOAD_RULES,
164913
165298
  PROPOSAL_KINDS: () => PROPOSAL_KINDS,
164914
165299
  PROJECT_IDENTITY_REGISTRY_VERSION: () => PROJECT_IDENTITY_REGISTRY_VERSION,
164915
165300
  PROJECT_IDENTITY_PLAN_VERSION: () => PROJECT_IDENTITY_PLAN_VERSION,
@@ -165025,6 +165410,7 @@ var init_dist15 = __esm(() => {
165025
165410
  init_handoff_service();
165026
165411
  init_handoff_auto_injector();
165027
165412
  init_proposal_repository();
165413
+ init_proposal_payload_validation();
165028
165414
  init_auto_improve_job();
165029
165415
  init_tools();
165030
165416
  init_services();
@@ -167667,6 +168053,26 @@ function boundedInt(val, def, min, max) {
167667
168053
  const i = Math.trunc(n);
167668
168054
  return Math.max(min, Math.min(max, i));
167669
168055
  }
168056
+ var HANDOFF_PATCH_ALLOWED_FIELDS = [
168057
+ "targetAgent",
168058
+ "summary",
168059
+ "openQuestions",
168060
+ "nextSteps",
168061
+ "files"
168062
+ ];
168063
+ var PROPOSAL_PATCH_ALLOWED_FIELDS = ["rationale", "payload"];
168064
+ function isStringArray2(value) {
168065
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
168066
+ }
168067
+ function isRecord2(value) {
168068
+ return value !== null && typeof value === "object" && !Array.isArray(value);
168069
+ }
168070
+ function isProposalKind(value) {
168071
+ return typeof value === "string" && PROPOSAL_KINDS.includes(value);
168072
+ }
168073
+ function proposalPayloadValidationStatus(error51) {
168074
+ return error51 instanceof ProposalPayloadValidationError ? error51.statusCode : null;
168075
+ }
167670
168076
  function serializeSession(s) {
167671
168077
  if (!s)
167672
168078
  return null;
@@ -167921,6 +168327,8 @@ class EmbeddedApiClient {
167921
168327
  return await this.handleProposalApprove(body);
167922
168328
  case "/api/v1/proposal/reject":
167923
168329
  return await this.handleProposalReject(body);
168330
+ case "/api/v1/proposal/create":
168331
+ return await this.handleProposalCreate(body);
167924
168332
  case "/api/v1/synapse/session":
167925
168333
  return await this.handleSynapseSession(body);
167926
168334
  case "/api/v1/profiles/switch":
@@ -167983,6 +168391,14 @@ class EmbeddedApiClient {
167983
168391
  return { success: false, error: "Session not found or expired" };
167984
168392
  return { success: true, data: serializeSession(updated) };
167985
168393
  }
168394
+ const handoffMatch = endpoint.match(/^\/api\/v1\/handoff\/([^/]+)$/);
168395
+ if (handoffMatch) {
168396
+ return await this.handleHandoffUpdate(handoffMatch[1], body);
168397
+ }
168398
+ const proposalMatch = endpoint.match(/^\/api\/v1\/proposal\/([^/]+)$/);
168399
+ if (proposalMatch) {
168400
+ return await this.handleProposalUpdate(proposalMatch[1], body);
168401
+ }
167986
168402
  return httpError(404, `EmbeddedApiClient: no PATCH handler for ${endpoint}`);
167987
168403
  } catch (error51) {
167988
168404
  if (error51 instanceof ApiHttpError)
@@ -168003,6 +168419,14 @@ class EmbeddedApiClient {
168003
168419
  await workspaceManager.removeWorkspace(wsMatch[1]);
168004
168420
  return { success: true, data: { removed: wsMatch[1] } };
168005
168421
  }
168422
+ const handoffMatch = endpoint.match(/^\/api\/v1\/handoff\/([^/]+)$/);
168423
+ if (handoffMatch) {
168424
+ return await this.handleHandoffDelete(handoffMatch[1], _body);
168425
+ }
168426
+ const proposalMatch = endpoint.match(/^\/api\/v1\/proposal\/([^/]+)$/);
168427
+ if (proposalMatch) {
168428
+ return await this.handleProposalDelete(proposalMatch[1], _body);
168429
+ }
168006
168430
  return httpError(404, `EmbeddedApiClient: no DELETE handler for ${endpoint}`);
168007
168431
  } catch (error51) {
168008
168432
  if (error51 instanceof ApiHttpError)
@@ -168238,6 +168662,61 @@ class EmbeddedApiClient {
168238
168662
  throw httpError(500, `handoff list failed: ${e.message}`);
168239
168663
  }
168240
168664
  }
168665
+ async handleHandoffUpdate(id, body) {
168666
+ const raw2 = body ?? {};
168667
+ const { projectId, ...patchFields } = raw2;
168668
+ const keys = Object.keys(patchFields);
168669
+ const rejected = keys.filter((k) => !HANDOFF_PATCH_ALLOWED_FIELDS.includes(k));
168670
+ if (rejected.length > 0) {
168671
+ throw httpError(400, `field not allowed: ${rejected.join(", ")}`);
168672
+ }
168673
+ const patch = {};
168674
+ if ("targetAgent" in patchFields) {
168675
+ const v = patchFields.targetAgent;
168676
+ if (v !== null && typeof v !== "string")
168677
+ throw httpError(400, "targetAgent must be a string or null");
168678
+ patch.targetAgent = v;
168679
+ }
168680
+ if ("summary" in patchFields) {
168681
+ if (typeof patchFields.summary !== "string")
168682
+ throw httpError(400, "summary must be a string");
168683
+ patch.summary = patchFields.summary;
168684
+ }
168685
+ if ("openQuestions" in patchFields) {
168686
+ if (!isStringArray2(patchFields.openQuestions))
168687
+ throw httpError(400, "openQuestions must be an array of strings");
168688
+ patch.openQuestions = patchFields.openQuestions;
168689
+ }
168690
+ if ("nextSteps" in patchFields) {
168691
+ if (!isStringArray2(patchFields.nextSteps))
168692
+ throw httpError(400, "nextSteps must be an array of strings");
168693
+ patch.nextSteps = patchFields.nextSteps;
168694
+ }
168695
+ if ("files" in patchFields) {
168696
+ if (!isStringArray2(patchFields.files))
168697
+ throw httpError(400, "files must be an array of strings");
168698
+ patch.files = patchFields.files;
168699
+ }
168700
+ if (Object.keys(patch).length === 0)
168701
+ throw httpError(400, "no editable field provided");
168702
+ const result = await getHandoffService().update({
168703
+ id,
168704
+ projectId,
168705
+ patch
168706
+ });
168707
+ if (!result.ok) {
168708
+ const status = result.reason === "not-found" || result.reason === "project-mismatch" ? 404 : 400;
168709
+ throw httpError(status, result.reason ?? "update failed");
168710
+ }
168711
+ return { success: true, data: result.handoff };
168712
+ }
168713
+ async handleHandoffDelete(id, body) {
168714
+ const projectId = body?.projectId;
168715
+ const result = await getHandoffService().delete({ id, projectId });
168716
+ if (!result.ok)
168717
+ throw httpError(404, result.reason ?? "not-found");
168718
+ return { success: true, data: { id: result.id } };
168719
+ }
168241
168720
  async handleProposalList(body) {
168242
168721
  const { projectId } = body;
168243
168722
  if (!projectId || !String(projectId).trim()) {
@@ -168274,6 +168753,104 @@ class EmbeddedApiClient {
168274
168753
  throw httpError(500, `proposal reject failed: ${e.message}`);
168275
168754
  }
168276
168755
  }
168756
+ async handleProposalCreate(body) {
168757
+ const raw2 = body ?? {};
168758
+ if (typeof raw2.projectId !== "string" || !raw2.projectId.trim()) {
168759
+ throw httpError(400, "projectId required");
168760
+ }
168761
+ if (!isProposalKind(raw2.kind)) {
168762
+ throw httpError(400, `kind must be one of ${PROPOSAL_KINDS.join(", ")}`);
168763
+ }
168764
+ if (!isRecord2(raw2.payload)) {
168765
+ throw httpError(400, "payload must be an object");
168766
+ }
168767
+ if (raw2.rationale !== undefined && typeof raw2.rationale !== "string") {
168768
+ throw httpError(400, "rationale must be a string");
168769
+ }
168770
+ if (raw2.targetMemoryId !== undefined && raw2.targetMemoryId !== null && typeof raw2.targetMemoryId !== "string") {
168771
+ throw httpError(400, "targetMemoryId must be a string or null");
168772
+ }
168773
+ const projectId = raw2.projectId;
168774
+ const kind = raw2.kind;
168775
+ const payload = raw2.payload;
168776
+ try {
168777
+ const record3 = await getProposalStore().create({
168778
+ id: newProposalId(),
168779
+ projectId,
168780
+ kind,
168781
+ payload,
168782
+ rationale: raw2.rationale,
168783
+ targetMemoryId: raw2.targetMemoryId
168784
+ });
168785
+ return { success: true, data: record3 };
168786
+ } catch (e) {
168787
+ const validationStatus = proposalPayloadValidationStatus(e);
168788
+ if (validationStatus !== null)
168789
+ throw httpError(validationStatus, e.message);
168790
+ throw e;
168791
+ }
168792
+ }
168793
+ async handleProposalUpdate(id, body) {
168794
+ const raw2 = body ?? {};
168795
+ const { projectId, ...patchFields } = raw2;
168796
+ const keys = Object.keys(patchFields);
168797
+ const rejected = keys.filter((k) => !PROPOSAL_PATCH_ALLOWED_FIELDS.includes(k));
168798
+ if (rejected.length > 0) {
168799
+ throw httpError(400, `field not allowed: ${rejected.join(", ")}`);
168800
+ }
168801
+ const patch = {};
168802
+ if ("rationale" in patchFields) {
168803
+ if (typeof patchFields.rationale !== "string")
168804
+ throw httpError(400, "rationale must be a string");
168805
+ patch.rationale = patchFields.rationale;
168806
+ }
168807
+ if ("payload" in patchFields) {
168808
+ if (!isRecord2(patchFields.payload))
168809
+ throw httpError(400, "payload must be an object");
168810
+ patch.payload = patchFields.payload;
168811
+ }
168812
+ if (Object.keys(patch).length === 0)
168813
+ throw httpError(400, "no editable field provided");
168814
+ const current = await getProposalStore().getById(id);
168815
+ if (!current)
168816
+ throw httpError(404, "not-found");
168817
+ if (projectId && current.projectId !== projectId)
168818
+ throw httpError(404, "project-mismatch");
168819
+ try {
168820
+ const updated = await getProposalStore().update(id, {
168821
+ ...patch.rationale !== undefined ? { rationale: patch.rationale } : {},
168822
+ ...patch.payload !== undefined ? { payload: patch.payload } : {}
168823
+ });
168824
+ if (!updated)
168825
+ throw httpError(404, "not-found");
168826
+ return { success: true, data: updated };
168827
+ } catch (e) {
168828
+ if (e instanceof ApiHttpError)
168829
+ throw e;
168830
+ const validationStatus = proposalPayloadValidationStatus(e);
168831
+ if (validationStatus !== null)
168832
+ throw httpError(validationStatus, e.message);
168833
+ throw e;
168834
+ }
168835
+ }
168836
+ async handleProposalDelete(id, body) {
168837
+ const projectId = body?.projectId;
168838
+ const current = await getProposalStore().getById(id);
168839
+ if (!current)
168840
+ throw httpError(404, "not-found");
168841
+ if (projectId && current.projectId !== projectId)
168842
+ throw httpError(404, "project-mismatch");
168843
+ const deletedId = await getProposalStore().delete(id);
168844
+ if (!deletedId)
168845
+ throw httpError(404, "not-found");
168846
+ return {
168847
+ success: true,
168848
+ data: {
168849
+ id: deletedId,
168850
+ ...current.status === "approved" ? { note: "approved proposal's applied memory edit was not reversed" } : {}
168851
+ }
168852
+ };
168853
+ }
168277
168854
  async handleSynapseSession(body) {
168278
168855
  const registry3 = getSessionRegistry();
168279
168856
  const sessionId = body.sessionId ?? newSynapseSessionId();
@@ -169873,6 +170450,39 @@ var HOOKS_EXEC_TOOL_DEFINITIONS = [
169873
170450
  required: ["projectId"]
169874
170451
  }
169875
170452
  },
170453
+ {
170454
+ name: "handoff_update",
170455
+ description: "Edit a handoff's targetAgent/summary/openQuestions/nextSteps/files by id. Only these five fields are accepted \u2014 status, acceptedAt, id, projectId, createdAt and sourceSessionId are rejected by name; status/acceptedAt stay exclusively handoff_accept/handoff_cancel's. Refreshes the dual-written memory's content when a content-feeding field changes. Missing id or project mismatch return {ok:false, reason}. Never throws.",
170456
+ apiEndpoint: "/api/v1/handoff/:id",
170457
+ apiMethod: "PATCH",
170458
+ inputSchema: {
170459
+ type: "object",
170460
+ properties: {
170461
+ id: { type: "string", description: "Handoff id (required)" },
170462
+ projectId: { type: "string", description: "Optional project scope check (mismatch returns not-found)" },
170463
+ targetAgent: { type: "string", description: "Target agent name (or null to clear)" },
170464
+ summary: { type: "string", description: "Handoff summary" },
170465
+ openQuestions: { type: "array", items: { type: "string" } },
170466
+ nextSteps: { type: "array", items: { type: "string" } },
170467
+ files: { type: "array", items: { type: "string" } }
170468
+ },
170469
+ required: ["id"]
170470
+ }
170471
+ },
170472
+ {
170473
+ name: "handoff_delete",
170474
+ description: "Hard-delete a handoff by id, permitted in any status including open. The dual-written memory row is left intact (dangling metadata.handoffId is accepted, documented behaviour). Missing id or project mismatch return {ok:false, reason}. Never throws.",
170475
+ apiEndpoint: "/api/v1/handoff/:id",
170476
+ apiMethod: "DELETE",
170477
+ inputSchema: {
170478
+ type: "object",
170479
+ properties: {
170480
+ id: { type: "string", description: "Handoff id (required)" },
170481
+ projectId: { type: "string", description: "Optional project scope check (mismatch returns not-found)" }
170482
+ },
170483
+ required: ["id"]
170484
+ }
170485
+ },
169876
170486
  {
169877
170487
  name: "list_proposals",
169878
170488
  description: "List pending auto-improvement proposals for a project (newest-first). The review-gate surfacing primitive: proposals are generated by the auto-improve loop from recurring patterns (repeated queries, hot files, common fixes). Never throws.",
@@ -169920,6 +170530,60 @@ var HOOKS_EXEC_TOOL_DEFINITIONS = [
169920
170530
  required: ["id"]
169921
170531
  }
169922
170532
  },
170533
+ {
170534
+ name: "create_proposal",
170535
+ description: "Create a pending auto-improvement proposal by hand. kind must be memory.create/memory.update/memory.tag; payload is validated against the same per-kind rules the store enforces on read, so a write the reader would reject is refused here instead. Never throws.",
170536
+ apiEndpoint: "/api/v1/proposal/create",
170537
+ apiMethod: "POST",
170538
+ inputSchema: {
170539
+ type: "object",
170540
+ properties: {
170541
+ projectId: { type: "string", description: "Project identifier (required)" },
170542
+ kind: {
170543
+ type: "string",
170544
+ enum: ["memory.create", "memory.update", "memory.tag"],
170545
+ description: "Proposal kind (required)"
170546
+ },
170547
+ payload: {
170548
+ type: "object",
170549
+ description: "Kind-specific payload (required), validated against the same per-kind rules the store enforces on read"
170550
+ },
170551
+ rationale: { type: "string", description: "Optional rationale" },
170552
+ targetMemoryId: { type: "string", description: "Optional target memory id" }
170553
+ },
170554
+ required: ["projectId", "kind", "payload"]
170555
+ }
170556
+ },
170557
+ {
170558
+ name: "update_proposal",
170559
+ description: "Edit a proposal's rationale/payload by id, in any status. kind, targetMemoryId, status, decidedAt, id, projectId and createdAt are rejected by name \u2014 kind/targetMemoryId are immutable because applyProposal branches its whole behaviour on them. A payload edit re-validates against the row's existing kind. Missing id or project mismatch return {ok:false, reason}. Never throws.",
170560
+ apiEndpoint: "/api/v1/proposal/:id",
170561
+ apiMethod: "PATCH",
170562
+ inputSchema: {
170563
+ type: "object",
170564
+ properties: {
170565
+ id: { type: "string", description: "Proposal id (required)" },
170566
+ projectId: { type: "string", description: "Optional project scope check (mismatch returns not-found)" },
170567
+ rationale: { type: "string", description: "Replacement rationale" },
170568
+ payload: { type: "object", description: "Replacement payload, re-validated against the row's existing kind" }
170569
+ },
170570
+ required: ["id"]
170571
+ }
170572
+ },
170573
+ {
170574
+ name: "delete_proposal",
170575
+ description: "Hard-delete a proposal by id, permitted in any status. Deleting an already-approved proposal does not reverse the memory edit it already applied \u2014 the response says so explicitly via a note field. Missing id or project mismatch return {ok:false, reason}. Never throws.",
170576
+ apiEndpoint: "/api/v1/proposal/:id",
170577
+ apiMethod: "DELETE",
170578
+ inputSchema: {
170579
+ type: "object",
170580
+ properties: {
170581
+ id: { type: "string", description: "Proposal id (required)" },
170582
+ projectId: { type: "string", description: "Optional project scope check (mismatch returns not-found)" }
170583
+ },
170584
+ required: ["id"]
170585
+ }
170586
+ },
169923
170587
  {
169924
170588
  name: "execute",
169925
170589
  description: "Run code in a detected polyglot sandbox runtime (js/ts/python/shell/ruby/go/rust/php/perl/r). " + "Returns stdout/stderr. Local-dev trust model: code runs on the host as the current user \u2014 " + "no OS-level isolation. Timeout default 30s, cap 300s. Pass `intent` to trim large outputs.",
@@ -170045,7 +170709,7 @@ var HOOKS_EXEC_TOOL_DEFINITIONS = [
170045
170709
 
170046
170710
  // src/tool-definitions.ts
170047
170711
  var BY_NAME = new Map([...PROJECT_TOOL_DEFINITIONS, ...SEARCH_TOOL_DEFINITIONS, ...MEMORY_TOOL_DEFINITIONS, ...SYNAPSE_TOOL_DEFINITIONS, ...HOOKS_EXEC_TOOL_DEFINITIONS].map((t) => [t.name, t]));
170048
- var CANONICAL_ORDER = ["index", "index_status", "search", "remember", "recall", "memory_update", "memory_delete", "list_checkpoints", "create_checkpoint", "restore_checkpoint", "compress", "optimized_context", "analytics", "list_projects", "project_map", "get_architecture", "search_definitions", "get_references", "go_to_definition", "trace_path", "impact_analysis", "reset_project", "read_file", "synapse_session", "synapse_get", "synapse_update", "synapse_end", "synapse_prime", "synapse_access", "synapse_prefetch", "synapse_list", "synapse_task_begin", "synapse_task_end", "symbol_snippet", "memory_list", "reindex", "hook_ingest", "compact_snapshot", "bootstrap", "handoff_begin", "handoff_accept", "handoff_cancel", "handoff_list_pending", "list_proposals", "approve_proposal", "reject_proposal", "execute", "execute_file", "batch_execute", "fetch_and_index", "rename_project", "merge_projects", "profile_list", "profile_set"];
170712
+ var CANONICAL_ORDER = ["index", "index_status", "search", "remember", "recall", "memory_update", "memory_delete", "list_checkpoints", "create_checkpoint", "restore_checkpoint", "compress", "optimized_context", "analytics", "list_projects", "project_map", "get_architecture", "search_definitions", "get_references", "go_to_definition", "trace_path", "impact_analysis", "reset_project", "read_file", "synapse_session", "synapse_get", "synapse_update", "synapse_end", "synapse_prime", "synapse_access", "synapse_prefetch", "synapse_list", "synapse_task_begin", "synapse_task_end", "symbol_snippet", "memory_list", "reindex", "hook_ingest", "compact_snapshot", "bootstrap", "handoff_begin", "handoff_accept", "handoff_cancel", "handoff_list_pending", "handoff_update", "handoff_delete", "list_proposals", "approve_proposal", "reject_proposal", "create_proposal", "update_proposal", "delete_proposal", "execute", "execute_file", "batch_execute", "fetch_and_index", "rename_project", "merge_projects", "profile_list", "profile_set"];
170049
170713
  var TOOL_DEFINITIONS = CANONICAL_ORDER.map((name26) => BY_NAME.get(name26));
170050
170714
  function getToolDefinition(name26) {
170051
170715
  return TOOL_DEFINITIONS.find((t) => t.name === name26);