@mtreeai/msapling-cli 2.3.6-beta.30 → 2.3.6-beta.31

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 +953 -846
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -295,7 +295,15 @@ var init_localLlm = __esm({
295
295
 
296
296
  // ../api-client/src/journal.ts
297
297
  import { homedir as homedir2 } from "os";
298
- import { join as join2 } from "path";
298
+ import { join as join2, dirname } from "path";
299
+ import {
300
+ appendFileSync,
301
+ existsSync,
302
+ mkdirSync,
303
+ readFileSync,
304
+ renameSync,
305
+ writeFileSync
306
+ } from "fs";
299
307
  function getJournal() {
300
308
  if (!journalInstance) {
301
309
  journalInstance = new Journal();
@@ -365,10 +373,39 @@ var init_journal = __esm({
365
373
  }
366
374
  }
367
375
  loadJsonl() {
376
+ try {
377
+ mkdirSync(dirname(this.jsonlPath), { recursive: true });
378
+ } catch (e) {
379
+ console.warn("[Journal] Failed to ensure journal dir", e);
380
+ }
381
+ if (!existsSync(this.jsonlPath)) return;
382
+ try {
383
+ const content = readFileSync(this.jsonlPath, "utf-8");
384
+ let malformed = 0;
385
+ for (const line of content.split("\n")) {
386
+ const trimmed = line.trim();
387
+ if (!trimmed) continue;
388
+ try {
389
+ this.jsonlEntries.push(JSON.parse(trimmed));
390
+ } catch {
391
+ malformed++;
392
+ }
393
+ }
394
+ if (malformed > 0) {
395
+ console.warn(`[Journal] Skipped ${malformed} malformed JSONL line(s)`);
396
+ }
397
+ } catch (e) {
398
+ console.warn("[Journal] Failed to load JSONL fallback", e);
399
+ }
368
400
  }
369
401
  async journalAppend(entry) {
370
402
  if (this.useJsonl) {
371
403
  this.jsonlEntries.push(entry);
404
+ try {
405
+ appendFileSync(this.jsonlPath, JSON.stringify(entry) + "\n", "utf8");
406
+ } catch (e) {
407
+ console.warn("[Journal] Failed to append JSONL entry", e);
408
+ }
372
409
  } else if (this.db) {
373
410
  try {
374
411
  const stmt = this.db.prepare(`
@@ -424,9 +461,18 @@ var init_journal = __esm({
424
461
  async journalMarkSynced(ids) {
425
462
  if (this.useJsonl) {
426
463
  const now = Date.now();
464
+ const idSet = new Set(ids);
427
465
  this.jsonlEntries = this.jsonlEntries.map(
428
- (e) => ids.includes(e.id) ? { ...e, synced_at: now } : e
466
+ (e) => idSet.has(e.id) ? { ...e, synced_at: now } : e
429
467
  );
468
+ try {
469
+ const tmpPath = this.jsonlPath + ".tmp";
470
+ const body = this.jsonlEntries.map((e) => JSON.stringify(e)).join("\n") + (this.jsonlEntries.length > 0 ? "\n" : "");
471
+ writeFileSync(tmpPath, body, "utf8");
472
+ renameSync(tmpPath, this.jsonlPath);
473
+ } catch (e) {
474
+ console.warn("[Journal] Failed to persist mark-synced", e);
475
+ }
430
476
  return;
431
477
  }
432
478
  if (!this.db) return;
@@ -1309,11 +1355,16 @@ var init_src = __esm({
1309
1355
  body: JSON.stringify(params)
1310
1356
  });
1311
1357
  }
1312
- async *streamChat(params) {
1358
+ async *streamChat(params, externalSignal) {
1313
1359
  if (!params.frame_id) {
1314
1360
  params = { ...params, frame_id: "earth_surface" };
1315
1361
  }
1316
1362
  const controller = new AbortController();
1363
+ const externalAbortHandler = () => controller.abort();
1364
+ if (externalSignal) {
1365
+ if (externalSignal.aborted) controller.abort();
1366
+ else externalSignal.addEventListener("abort", externalAbortHandler);
1367
+ }
1317
1368
  const timeout = setTimeout(() => controller.abort(), 3e4);
1318
1369
  try {
1319
1370
  const headers = {
@@ -1378,11 +1429,15 @@ var init_src = __esm({
1378
1429
  }
1379
1430
  } catch (e) {
1380
1431
  if (e.name === "AbortError") {
1432
+ if (externalSignal?.aborted) throw e;
1381
1433
  throw new MSaplingError("Request timed out after 30s.", 408, "timeout");
1382
1434
  }
1383
1435
  throw e;
1384
1436
  } finally {
1385
1437
  clearTimeout(timeout);
1438
+ if (externalSignal) {
1439
+ externalSignal.removeEventListener("abort", externalAbortHandler);
1440
+ }
1386
1441
  }
1387
1442
  }
1388
1443
  /**
@@ -1420,7 +1475,7 @@ var init_BaseTool = __esm({
1420
1475
  // ../core/src/tools/ReadFileTool.ts
1421
1476
  import { resolve, normalize, relative, isAbsolute } from "path";
1422
1477
  import { readFile as readFile2 } from "fs/promises";
1423
- import { existsSync } from "fs";
1478
+ import { existsSync as existsSync2 } from "fs";
1424
1479
  var ReadFileTool;
1425
1480
  var init_ReadFileTool = __esm({
1426
1481
  "../core/src/tools/ReadFileTool.ts"() {
@@ -1463,7 +1518,7 @@ var init_ReadFileTool = __esm({
1463
1518
  };
1464
1519
  }
1465
1520
  const fullPath = normalizedTarget;
1466
- if (!existsSync(fullPath)) {
1521
+ if (!existsSync2(fullPath)) {
1467
1522
  return { content: `Error: file not found: ${args2.path}`, isError: true };
1468
1523
  }
1469
1524
  const offset = args2.offset ? parseInt(args2.offset) : 0;
@@ -1545,7 +1600,7 @@ var init_EditFileTool = __esm({
1545
1600
  // ../core/src/tools/WriteFileTool.ts
1546
1601
  import { resolve as resolve2, normalize as normalize2, relative as relative2, isAbsolute as isAbsolute2, join as join4 } from "path";
1547
1602
  import { writeFile, readFile as readFile3, mkdir } from "fs/promises";
1548
- import { existsSync as existsSync2 } from "fs";
1603
+ import { existsSync as existsSync3 } from "fs";
1549
1604
  import { homedir as homedir3 } from "os";
1550
1605
  import { randomBytes } from "crypto";
1551
1606
  var MAX_CONTENT_BYTES, WriteFileTool;
@@ -1620,7 +1675,7 @@ var init_WriteFileTool = __esm({
1620
1675
  }
1621
1676
  let backedUpTo = null;
1622
1677
  try {
1623
- if (existsSync2(resolvedTarget)) {
1678
+ if (existsSync3(resolvedTarget)) {
1624
1679
  const existingContent = await readFile3(resolvedTarget, "utf8");
1625
1680
  const filename = resolvedTarget.split(/[\\/]/).pop() ?? "file";
1626
1681
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -2281,7 +2336,7 @@ var init_SubShellTool = __esm({
2281
2336
  // ../core/src/tools/SearchTools.ts
2282
2337
  import { spawn as spawn4 } from "child_process";
2283
2338
  import { readFile as readFile4, readdir, stat } from "fs/promises";
2284
- import { existsSync as existsSync3, statSync } from "fs";
2339
+ import { existsSync as existsSync4, statSync } from "fs";
2285
2340
  import { join as join6, relative as relative4, sep, resolve as resolve4, normalize as normalize3, isAbsolute as isAbsolute4 } from "path";
2286
2341
  function globToRegExp(pattern) {
2287
2342
  const normalised = pattern.replace(/\\/g, "/");
@@ -2463,7 +2518,7 @@ var init_SearchTools = __esm({
2463
2518
  };
2464
2519
  }
2465
2520
  const candidate = normalizedTarget;
2466
- if (existsSync3(candidate) && statSync(candidate).isDirectory()) {
2521
+ if (existsSync4(candidate) && statSync(candidate).isDirectory()) {
2467
2522
  scanRoot = candidate;
2468
2523
  } else {
2469
2524
  return {
@@ -2531,7 +2586,7 @@ var init_SearchTools = __esm({
2531
2586
  };
2532
2587
  }
2533
2588
  const searchPath = args2?.path ? join6(projectRoot, String(args2.path)) : projectRoot;
2534
- if (!existsSync3(searchPath)) {
2589
+ if (!existsSync4(searchPath)) {
2535
2590
  return {
2536
2591
  content: `Error: path "${args2.path}" does not exist.`,
2537
2592
  isError: true
@@ -2591,7 +2646,7 @@ var init_SearchTools = __esm({
2591
2646
 
2592
2647
  // ../core/src/tools/ListDirectoryTool.ts
2593
2648
  import { resolve as resolve5, normalize as normalize4, relative as relative5, isAbsolute as isAbsolute5, join as join7, sep as sep2 } from "path";
2594
- import { readdirSync, statSync as statSync2, existsSync as existsSync4 } from "fs";
2649
+ import { readdirSync, statSync as statSync2, existsSync as existsSync5 } from "fs";
2595
2650
  function humanSize(bytes) {
2596
2651
  if (bytes < 1024) return `${bytes} B`;
2597
2652
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -2686,7 +2741,7 @@ var init_ListDirectoryTool = __esm({
2686
2741
  isError: true
2687
2742
  };
2688
2743
  }
2689
- if (!existsSync4(normalizedTarget)) {
2744
+ if (!existsSync5(normalizedTarget)) {
2690
2745
  return {
2691
2746
  content: `Error: path "${rawPath}" does not exist.`,
2692
2747
  isError: true
@@ -2928,7 +2983,7 @@ var init_TodoTools = __esm({
2928
2983
  // ../core/src/tools/PatchFileTool.ts
2929
2984
  import { resolve as resolve6, normalize as normalize5, relative as relative6, isAbsolute as isAbsolute6, join as join8 } from "path";
2930
2985
  import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
2931
- import { existsSync as existsSync5 } from "fs";
2986
+ import { existsSync as existsSync6 } from "fs";
2932
2987
  import { homedir as homedir4 } from "os";
2933
2988
  import { randomBytes as randomBytes2 } from "crypto";
2934
2989
  var PatchFileTool;
@@ -2983,7 +3038,7 @@ var init_PatchFileTool = __esm({
2983
3038
  };
2984
3039
  }
2985
3040
  const resolvedTarget = normalizedTarget;
2986
- if (!existsSync5(resolvedTarget)) {
3041
+ if (!existsSync6(resolvedTarget)) {
2987
3042
  return {
2988
3043
  content: `Error: file not found: ${args2.path}. Use write_file to create it first.`,
2989
3044
  isError: true
@@ -3672,7 +3727,7 @@ ${stderr}`;
3672
3727
  // ../core/src/tools/NotebookReadTool.ts
3673
3728
  import { resolve as resolve8, normalize as normalize7, relative as relative8, isAbsolute as isAbsolute8, extname } from "path";
3674
3729
  import { readFile as readFile6 } from "fs/promises";
3675
- import { existsSync as existsSync6 } from "fs";
3730
+ import { existsSync as existsSync7 } from "fs";
3676
3731
  function joinSource(source) {
3677
3732
  return Array.isArray(source) ? source.join("") : source ?? "";
3678
3733
  }
@@ -3833,7 +3888,7 @@ var init_NotebookReadTool = __esm({
3833
3888
  isError: true
3834
3889
  };
3835
3890
  }
3836
- if (!existsSync6(absPath)) {
3891
+ if (!existsSync7(absPath)) {
3837
3892
  return { content: `Error: file not found: ${absPath}`, isError: true };
3838
3893
  }
3839
3894
  let raw;
@@ -3897,7 +3952,7 @@ _(Note: notebook has ${nb.cells.length} cells; only first ${MAX_CELLS} shown.)_`
3897
3952
  // ../core/src/tools/NotebookEditTool.ts
3898
3953
  import { resolve as resolve9, normalize as normalize8, relative as relative9, isAbsolute as isAbsolute9, extname as extname2, join as join10 } from "path";
3899
3954
  import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
3900
- import { existsSync as existsSync7 } from "fs";
3955
+ import { existsSync as existsSync8 } from "fs";
3901
3956
  import { homedir as homedir5 } from "os";
3902
3957
  import { randomBytes as randomBytes3 } from "crypto";
3903
3958
  function normaliseSource(source) {
@@ -4027,7 +4082,7 @@ var init_NotebookEditTool = __esm({
4027
4082
  isError: true
4028
4083
  };
4029
4084
  }
4030
- if (!existsSync7(absPath)) {
4085
+ if (!existsSync8(absPath)) {
4031
4086
  return { content: `Error: file not found: ${absPath}`, isError: true };
4032
4087
  }
4033
4088
  let rawJson;
@@ -4156,7 +4211,7 @@ Backup: ${backedUpTo}`;
4156
4211
  // ../core/src/tools/MultiEditFileTool.ts
4157
4212
  import { resolve as resolve10, normalize as normalize9, relative as relative10, isAbsolute as isAbsolute10, join as join11 } from "path";
4158
4213
  import { readFile as readFile8, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
4159
- import { existsSync as existsSync8 } from "fs";
4214
+ import { existsSync as existsSync9 } from "fs";
4160
4215
  import { homedir as homedir6 } from "os";
4161
4216
  import { randomBytes as randomBytes4 } from "crypto";
4162
4217
  var MAX_EDITS, MultiEditFileTool;
@@ -4260,7 +4315,7 @@ var init_MultiEditFileTool = __esm({
4260
4315
  };
4261
4316
  }
4262
4317
  const resolvedTarget = normalizedTarget;
4263
- if (!existsSync8(resolvedTarget)) {
4318
+ if (!existsSync9(resolvedTarget)) {
4264
4319
  return {
4265
4320
  content: `Error: file not found: ${args2.path}. Use write_file to create it first.`,
4266
4321
  isError: true
@@ -4355,9 +4410,9 @@ Pre-edit content backed up to: ${backedUpTo}`;
4355
4410
  });
4356
4411
 
4357
4412
  // ../core/src/tools/MoveFileTool.ts
4358
- import { resolve as resolve11, normalize as normalize10, relative as relative11, isAbsolute as isAbsolute11, dirname } from "path";
4413
+ import { resolve as resolve11, normalize as normalize10, relative as relative11, isAbsolute as isAbsolute11, dirname as dirname2 } from "path";
4359
4414
  import { rename, mkdir as mkdir5, copyFile, rm, stat as stat2, readdir as readdir2 } from "fs/promises";
4360
- import { existsSync as existsSync9, statSync as statSync3 } from "fs";
4415
+ import { existsSync as existsSync10, statSync as statSync3 } from "fs";
4361
4416
  import { randomBytes as randomBytes5 } from "crypto";
4362
4417
  function containedPath(p, root) {
4363
4418
  const abs = isAbsolute11(p) ? normalize10(p) : resolve11(root, p.trim());
@@ -4448,7 +4503,7 @@ var init_MoveFileTool = __esm({
4448
4503
  return { content: `Security Block: ${srcCheck.reason}.`, isError: true };
4449
4504
  }
4450
4505
  const absSrc = srcCheck.abs;
4451
- if (!existsSync9(absSrc)) {
4506
+ if (!existsSync10(absSrc)) {
4452
4507
  return {
4453
4508
  content: `Error: source path "${args2.source}" does not exist.`,
4454
4509
  isError: true
@@ -4462,7 +4517,7 @@ var init_MoveFileTool = __esm({
4462
4517
  return { content: `Security Block: ${dstPrelimCheck.reason}.`, isError: true };
4463
4518
  }
4464
4519
  let absDst = dstPrelimCheck.abs;
4465
- if (existsSync9(absDst) && statSync3(absDst).isDirectory() && !srcIsDir) {
4520
+ if (existsSync10(absDst) && statSync3(absDst).isDirectory() && !srcIsDir) {
4466
4521
  const srcName = absSrc.split(/[\\/]/).pop();
4467
4522
  absDst = resolve11(absDst, srcName);
4468
4523
  const rel2 = relative11(projectRoot, absDst);
@@ -4480,7 +4535,7 @@ var init_MoveFileTool = __esm({
4480
4535
  };
4481
4536
  }
4482
4537
  let backedUpTo = null;
4483
- if (existsSync9(absDst)) {
4538
+ if (existsSync10(absDst)) {
4484
4539
  if (!overwrite) {
4485
4540
  return {
4486
4541
  content: `Error: destination "${args2.destination}" already exists. Pass \`overwrite: true\` to replace it.`,
@@ -4492,7 +4547,7 @@ var init_MoveFileTool = __esm({
4492
4547
  }
4493
4548
  }
4494
4549
  try {
4495
- await mkdir5(dirname(absDst), { recursive: true });
4550
+ await mkdir5(dirname2(absDst), { recursive: true });
4496
4551
  } catch (e) {
4497
4552
  return {
4498
4553
  content: `Error: failed to create destination parent directory: ${e.message}`,
@@ -5176,119 +5231,428 @@ var init_Hooks = __esm({
5176
5231
  }
5177
5232
  });
5178
5233
 
5179
- // ../core/src/agent/ToolExecutor.ts
5180
- import { readFile as readFile9 } from "fs/promises";
5181
- var APPROVAL_GATED, ToolExecutor;
5182
- var init_ToolExecutor = __esm({
5183
- "../core/src/agent/ToolExecutor.ts"() {
5184
- "use strict";
5185
- init_esm_shims();
5186
- init_ReadFileTool();
5187
- init_EditFileTool();
5188
- init_WriteFileTool();
5189
- init_RunCommandTool();
5190
- init_LSPTool();
5191
- init_SubShellTool();
5192
- init_SearchTools();
5193
- init_ListDirectoryTool();
5194
- init_TodoTools();
5195
- init_PatchFileTool();
5196
- init_DispatchAgentTool();
5197
- init_WebFetchTool();
5198
- init_BashTool();
5199
- init_NotebookReadTool();
5200
- init_NotebookEditTool();
5201
- init_MultiEditFileTool();
5202
- init_MoveFileTool();
5203
- init_DeleteFileTool();
5204
- init_MDrive();
5205
- init_Sandbox();
5206
- init_Voice();
5207
- init_ShadowService();
5208
- init_Hooks();
5209
- APPROVAL_GATED = /* @__PURE__ */ new Set(["run_command", "bash_command", "edit_file", "write_file", "patch_file", "multi_edit_file", "notebook_edit_cell", "move_file", "delete_file", "open_sub_shell"]);
5210
- ToolExecutor = class {
5211
- tools = /* @__PURE__ */ new Map();
5212
- sandbox;
5213
- mdrive;
5214
- voice;
5215
- shadow;
5216
- // CLI-TOOLS-ENABLED-DEFAULT-ON-01 (Iter 30): default to true so the CLI's
5217
- // 19 client-side tools are actually advertised to the backend in
5218
- // ChatMessagePayload.tools. Previously false, and nothing in the CLI ever
5219
- // called setToolsEnabled(true), so getToolSchemas() always returned [] and
5220
- // the LLM had no idea tools existed — which is why every chat refused
5221
- // file/shell access. Side effects are still gated by the permission mode
5222
- // (default prompts before each call) and the persistent trust store.
5223
- // Opt out via MSAPLING_TOOLS_ENABLED=false.
5224
- toolsEnabled = process.env.MSAPLING_TOOLS_ENABLED !== "false";
5225
- mode = "default";
5226
- approvalCallback = null;
5227
- /**
5228
- * CLI-PARITY-37: Two-tier trust set.
5229
- * `trustStore` — optional persistent store loaded from ~/.msapling/settings.json;
5230
- * provides cross-session memory for "always" decisions.
5231
- * `sessionTrust` in-memory fallback for the current session when no
5232
- * TrustStore is wired (preserves old behaviour exactly).
5233
- */
5234
- trustStore = null;
5235
- sessionTrust = /* @__PURE__ */ new Set();
5236
- mcpRegistry = null;
5237
- hooks = null;
5238
- constructor(client, projectRoot) {
5239
- this.sandbox = new Sandbox(projectRoot);
5240
- this.mdrive = new MDriveService(client);
5241
- this.voice = new VoiceService();
5242
- this.shadow = new ShadowService(client);
5243
- this.registerTool(new ReadFileTool());
5244
- this.registerTool(new EditFileTool());
5245
- this.registerTool(new WriteFileTool());
5246
- this.registerTool(new RunCommandTool());
5247
- this.registerTool(new LSPTool());
5248
- this.registerTool(new SubShellTool());
5249
- this.registerTool(new GlobFilesTool());
5250
- this.registerTool(new GrepSearchTool());
5251
- this.registerTool(new ListDirectoryTool());
5252
- this.registerTool(new TodoWriteTool());
5253
- this.registerTool(new TodoReadTool());
5254
- this.registerTool(new PatchFileTool());
5255
- this.registerTool(new DispatchAgentTool({
5256
- client,
5257
- projectRoot
5258
- }));
5259
- this.registerTool(new WebFetchTool());
5260
- this.registerTool(new BashTool());
5261
- this.registerTool(new NotebookReadTool());
5262
- this.registerTool(new NotebookEditTool());
5263
- this.registerTool(new MultiEditFileTool());
5264
- this.registerTool(new MoveFileTool());
5265
- this.registerTool(new DeleteFileTool());
5266
- }
5267
- setToolsEnabled(enabled) {
5268
- this.toolsEnabled = enabled;
5269
- }
5270
- setMode(mode) {
5271
- this.mode = mode;
5272
- }
5273
- getMode() {
5274
- return this.mode;
5275
- }
5276
- setApprovalCallback(cb) {
5277
- this.approvalCallback = cb;
5234
+ // ../core/src/diagnostics/specs.ts
5235
+ var specs_exports = {};
5236
+ __export(specs_exports, {
5237
+ collectSpecs: () => collectSpecs
5238
+ });
5239
+ import { cpus, totalmem, freemem, platform, arch } from "os";
5240
+ import { execSync } from "child_process";
5241
+ async function collectSpecs() {
5242
+ const cpuList = cpus();
5243
+ const totalMemBytes = totalmem();
5244
+ const freeMemBytes = freemem();
5245
+ const specs = {
5246
+ cpu: {
5247
+ cores: cpuList.length,
5248
+ model: cpuList[0]?.model ?? "Unknown",
5249
+ speed: cpuList[0]?.speed ?? 0
5250
+ // MHz -> convert below
5251
+ },
5252
+ memory: {
5253
+ totalGB: Math.round(totalMemBytes / 1024 ** 3 * 10) / 10,
5254
+ freeGB: Math.round(freeMemBytes / 1024 ** 3 * 10) / 10
5255
+ },
5256
+ gpu: [],
5257
+ disk: {
5258
+ freeGB: 0,
5259
+ totalGB: 0
5260
+ },
5261
+ platform: platform(),
5262
+ arch: arch(),
5263
+ nodeVersion: process.version.slice(1)
5264
+ // Remove 'v' prefix
5265
+ };
5266
+ specs.cpu.speed = Math.round(specs.cpu.speed / 1e3 * 10) / 10;
5267
+ try {
5268
+ const { statfs } = await import("fs/promises");
5269
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "/";
5270
+ const stat5 = await statfs(homeDir);
5271
+ specs.disk.freeGB = Math.round(stat5.bavail * stat5.bsize / 1024 ** 3 * 10) / 10;
5272
+ specs.disk.totalGB = Math.round(stat5.blocks * stat5.bsize / 1024 ** 3 * 10) / 10;
5273
+ } catch {
5274
+ specs.disk.freeGB = -1;
5275
+ specs.disk.totalGB = -1;
5276
+ }
5277
+ specs.gpu = await detectGpu();
5278
+ return specs;
5279
+ }
5280
+ async function detectGpu() {
5281
+ const currentPlatform = platform();
5282
+ if (currentPlatform === "win32") {
5283
+ return detectGpuWindows();
5284
+ } else if (currentPlatform === "darwin") {
5285
+ return detectGpuMacOS();
5286
+ } else {
5287
+ return detectGpuLinux();
5288
+ }
5289
+ }
5290
+ function detectGpuWindows() {
5291
+ try {
5292
+ const output = execSync(
5293
+ 'powershell -Command "Get-CimInstance Win32_VideoController | Select-Object Name, AdapterRAM"',
5294
+ { timeout: 2e3, encoding: "utf-8" }
5295
+ );
5296
+ const gpus = [];
5297
+ const lines = output.split("\n");
5298
+ for (const line of lines) {
5299
+ const parts = line.trim().split(/\s{2,}/);
5300
+ if (parts[0] && parts[0] !== "Name") {
5301
+ const memBytes = parseInt(parts[1] || "0");
5302
+ gpus.push({
5303
+ name: parts[0],
5304
+ memoryGB: memBytes > 0 ? Math.round(memBytes / 1024 ** 3 * 10) / 10 : void 0
5305
+ });
5278
5306
  }
5279
- setMCPRegistry(registry) {
5280
- this.mcpRegistry = registry;
5307
+ }
5308
+ return gpus;
5309
+ } catch {
5310
+ return [];
5311
+ }
5312
+ }
5313
+ function detectGpuMacOS() {
5314
+ try {
5315
+ const output = execSync("system_profiler SPDisplaysDataType -json", {
5316
+ timeout: 2e3,
5317
+ encoding: "utf-8"
5318
+ });
5319
+ const data = JSON.parse(output);
5320
+ const gpus = [];
5321
+ const displays = data.SPDisplaysDataType || [];
5322
+ for (const display of displays) {
5323
+ const chips = display["sppci_model_name"];
5324
+ if (chips) {
5325
+ gpus.push({ name: chips });
5281
5326
  }
5282
- setHookRunner(runner) {
5283
- this.hooks = runner;
5327
+ }
5328
+ return gpus;
5329
+ } catch {
5330
+ return [];
5331
+ }
5332
+ }
5333
+ function detectGpuLinux() {
5334
+ const gpus = [];
5335
+ try {
5336
+ const output = execSync(
5337
+ "nvidia-smi --query-gpu=name,memory.total --format=csv,noheader",
5338
+ { timeout: 2e3, encoding: "utf-8" }
5339
+ );
5340
+ const lines = output.trim().split("\n");
5341
+ for (const line of lines) {
5342
+ const [name, mem] = line.split(",");
5343
+ const memGB = mem ? Math.round(parseInt(mem) / 1024 * 10) / 10 : void 0;
5344
+ gpus.push({ name: name.trim(), memoryGB: memGB });
5345
+ }
5346
+ return gpus;
5347
+ } catch {
5348
+ }
5349
+ try {
5350
+ const output = execSync("rocm-smi --showproductname", {
5351
+ timeout: 2e3,
5352
+ encoding: "utf-8"
5353
+ });
5354
+ const lines = output.trim().split("\n");
5355
+ for (const line of lines) {
5356
+ if (line.includes("GPU")) {
5357
+ const match = line.match(/:\s*(.+)/);
5358
+ if (match) gpus.push({ name: match[1].trim() });
5284
5359
  }
5285
- /**
5286
- * CLI-PARITY-37: Wire in a pre-loaded TrustStore so that "always" approval
5287
- * decisions survive CLI restarts. Call this once at startup after `load()`
5288
- * has been awaited.
5289
- */
5290
- setTrustStore(store2) {
5291
- this.trustStore = store2;
5360
+ }
5361
+ return gpus;
5362
+ } catch {
5363
+ }
5364
+ try {
5365
+ const output = execSync("lspci | grep -i vga", {
5366
+ timeout: 2e3,
5367
+ encoding: "utf-8"
5368
+ });
5369
+ const lines = output.trim().split("\n");
5370
+ for (const line of lines) {
5371
+ const match = line.match(/:\s*(.+)/);
5372
+ if (match) gpus.push({ name: match[1].trim() });
5373
+ }
5374
+ return gpus;
5375
+ } catch {
5376
+ return [];
5377
+ }
5378
+ }
5379
+ var init_specs = __esm({
5380
+ "../core/src/diagnostics/specs.ts"() {
5381
+ "use strict";
5382
+ init_esm_shims();
5383
+ }
5384
+ });
5385
+
5386
+ // ../core/src/governor/ResourceGovernor.ts
5387
+ import { freemem as freemem2 } from "os";
5388
+ import { homedir as homedir7 } from "os";
5389
+ import { readFile as readFile9 } from "fs/promises";
5390
+ import { join as join14 } from "path";
5391
+ function determineTier(specs) {
5392
+ const memGB = specs.memory.totalGB;
5393
+ const cores = specs.cpu.cores;
5394
+ if (memGB < 8 || cores < 4) return "T1";
5395
+ if (memGB < 16 || cores < 8) return "T2";
5396
+ if (memGB < 32 || cores < 16) return "T3";
5397
+ return "T4";
5398
+ }
5399
+ function recommendLimits(specs) {
5400
+ const tier = determineTier(specs);
5401
+ return tierTable[tier];
5402
+ }
5403
+ async function readConfigOverrides() {
5404
+ try {
5405
+ const configPath = join14(homedir7(), ".msapling", "config.json");
5406
+ const content = await readFile9(configPath, "utf-8");
5407
+ const config = JSON.parse(content);
5408
+ return config.limits ?? null;
5409
+ } catch {
5410
+ return null;
5411
+ }
5412
+ }
5413
+ async function createResourceGovernor(specs) {
5414
+ const recommended = recommendLimits(specs);
5415
+ const overrides = await readConfigOverrides();
5416
+ return new ResourceGovernor(recommended, overrides ?? void 0);
5417
+ }
5418
+ async function getGlobalGovernor() {
5419
+ if (globalGovernor) return globalGovernor;
5420
+ if (!globalGovernorPromise) {
5421
+ globalGovernorPromise = (async () => {
5422
+ const { collectSpecs: collectSpecs2 } = await Promise.resolve().then(() => (init_specs(), specs_exports));
5423
+ const specs = await collectSpecs2();
5424
+ const g = await createResourceGovernor(specs);
5425
+ globalGovernor = g;
5426
+ return g;
5427
+ })();
5428
+ }
5429
+ return globalGovernorPromise;
5430
+ }
5431
+ var tierTable, ResourceGovernor, globalGovernor, globalGovernorPromise;
5432
+ var init_ResourceGovernor = __esm({
5433
+ "../core/src/governor/ResourceGovernor.ts"() {
5434
+ "use strict";
5435
+ init_esm_shims();
5436
+ tierTable = {
5437
+ T1: { maxAgents: 1, maxParallelTools: 1, maxFileWatchers: 50, localLlmTier: "3B" },
5438
+ T2: { maxAgents: 2, maxParallelTools: 4, maxFileWatchers: 200, localLlmTier: "7B" },
5439
+ T3: { maxAgents: 4, maxParallelTools: 8, maxFileWatchers: 500, localLlmTier: "13B" },
5440
+ T4: { maxAgents: 8, maxParallelTools: 16, maxFileWatchers: 2e3, localLlmTier: "32B+" }
5441
+ };
5442
+ ResourceGovernor = class {
5443
+ activeAgents = 0;
5444
+ maxAgents;
5445
+ activeTool = 0;
5446
+ maxParallelTools;
5447
+ maxFileWatchers;
5448
+ memoryWarningShown = false;
5449
+ memoryCheckInterval = null;
5450
+ minFreeMemGB = 1.5;
5451
+ agentWaiters = [];
5452
+ toolWaiters = [];
5453
+ constructor(limits, overrides) {
5454
+ this.maxAgents = overrides?.maxAgents ?? limits.maxAgents;
5455
+ this.maxParallelTools = overrides?.maxParallelTools ?? limits.maxParallelTools;
5456
+ this.maxFileWatchers = overrides?.maxFileWatchers ?? limits.maxFileWatchers;
5457
+ this.startMemoryMonitor();
5458
+ }
5459
+ /**
5460
+ * Monitor free memory every 5s; block new acquires if below threshold
5461
+ */
5462
+ startMemoryMonitor() {
5463
+ this.memoryCheckInterval = setInterval(() => {
5464
+ const freeMemBytes = freemem2();
5465
+ const freeMemGB = freeMemBytes / 1024 ** 3;
5466
+ if (freeMemGB < this.minFreeMemGB && !this.memoryWarningShown) {
5467
+ console.warn(
5468
+ `[ResourceGovernor] Low memory: ${freeMemGB.toFixed(2)}GB free (threshold: ${this.minFreeMemGB}GB)`
5469
+ );
5470
+ this.memoryWarningShown = true;
5471
+ } else if (freeMemGB >= this.minFreeMemGB) {
5472
+ this.memoryWarningShown = false;
5473
+ }
5474
+ }, 5e3);
5475
+ }
5476
+ /**
5477
+ * Acquire an agent slot, awaiting if the cap is currently full.
5478
+ * Low-memory state is logged via the monitor but does not block — the
5479
+ * single-warning behavior is intentional (see startMemoryMonitor).
5480
+ */
5481
+ async acquireAgent() {
5482
+ if (this.activeAgents < this.maxAgents) {
5483
+ this.activeAgents++;
5484
+ return;
5485
+ }
5486
+ await new Promise((resolve19) => this.agentWaiters.push(resolve19));
5487
+ this.activeAgents++;
5488
+ }
5489
+ /**
5490
+ * Release an agent slot
5491
+ */
5492
+ releaseAgent() {
5493
+ this.activeAgents = Math.max(0, this.activeAgents - 1);
5494
+ const next = this.agentWaiters.shift();
5495
+ if (next) next();
5496
+ }
5497
+ /**
5498
+ * Acquire a tool execution slot, awaiting if the cap is currently full.
5499
+ */
5500
+ async acquireTool() {
5501
+ if (this.activeTool < this.maxParallelTools) {
5502
+ this.activeTool++;
5503
+ return;
5504
+ }
5505
+ await new Promise((resolve19) => this.toolWaiters.push(resolve19));
5506
+ this.activeTool++;
5507
+ }
5508
+ /**
5509
+ * Release a tool execution slot
5510
+ */
5511
+ releaseTool() {
5512
+ this.activeTool = Math.max(0, this.activeTool - 1);
5513
+ const next = this.toolWaiters.shift();
5514
+ if (next) next();
5515
+ }
5516
+ /**
5517
+ * Get current active counts
5518
+ */
5519
+ getStatus() {
5520
+ return {
5521
+ activeAgents: this.activeAgents,
5522
+ activeTool: this.activeTool,
5523
+ maxAgents: this.maxAgents,
5524
+ maxParallelTools: this.maxParallelTools
5525
+ };
5526
+ }
5527
+ /**
5528
+ * Clean up monitor interval
5529
+ */
5530
+ destroy() {
5531
+ if (this.memoryCheckInterval) {
5532
+ clearInterval(this.memoryCheckInterval);
5533
+ this.memoryCheckInterval = null;
5534
+ }
5535
+ }
5536
+ };
5537
+ globalGovernor = null;
5538
+ globalGovernorPromise = null;
5539
+ }
5540
+ });
5541
+
5542
+ // ../core/src/agent/ToolExecutor.ts
5543
+ import { readFile as readFile10 } from "fs/promises";
5544
+ var APPROVAL_GATED, ToolExecutor;
5545
+ var init_ToolExecutor = __esm({
5546
+ "../core/src/agent/ToolExecutor.ts"() {
5547
+ "use strict";
5548
+ init_esm_shims();
5549
+ init_ReadFileTool();
5550
+ init_EditFileTool();
5551
+ init_WriteFileTool();
5552
+ init_RunCommandTool();
5553
+ init_LSPTool();
5554
+ init_SubShellTool();
5555
+ init_SearchTools();
5556
+ init_ListDirectoryTool();
5557
+ init_TodoTools();
5558
+ init_PatchFileTool();
5559
+ init_DispatchAgentTool();
5560
+ init_WebFetchTool();
5561
+ init_BashTool();
5562
+ init_NotebookReadTool();
5563
+ init_NotebookEditTool();
5564
+ init_MultiEditFileTool();
5565
+ init_MoveFileTool();
5566
+ init_DeleteFileTool();
5567
+ init_MDrive();
5568
+ init_Sandbox();
5569
+ init_Voice();
5570
+ init_ShadowService();
5571
+ init_Hooks();
5572
+ init_ResourceGovernor();
5573
+ APPROVAL_GATED = /* @__PURE__ */ new Set(["run_command", "bash_command", "edit_file", "write_file", "patch_file", "multi_edit_file", "notebook_edit_cell", "move_file", "delete_file", "open_sub_shell"]);
5574
+ ToolExecutor = class {
5575
+ tools = /* @__PURE__ */ new Map();
5576
+ sandbox;
5577
+ mdrive;
5578
+ voice;
5579
+ shadow;
5580
+ // CLI-TOOLS-ENABLED-DEFAULT-ON-01 (Iter 30): default to true so the CLI's
5581
+ // 19 client-side tools are actually advertised to the backend in
5582
+ // ChatMessagePayload.tools. Previously false, and nothing in the CLI ever
5583
+ // called setToolsEnabled(true), so getToolSchemas() always returned [] and
5584
+ // the LLM had no idea tools existed — which is why every chat refused
5585
+ // file/shell access. Side effects are still gated by the permission mode
5586
+ // (default prompts before each call) and the persistent trust store.
5587
+ // Opt out via MSAPLING_TOOLS_ENABLED=false.
5588
+ toolsEnabled = process.env.MSAPLING_TOOLS_ENABLED !== "false";
5589
+ mode = "default";
5590
+ approvalCallback = null;
5591
+ /**
5592
+ * CLI-PARITY-37: Two-tier trust set.
5593
+ * `trustStore` — optional persistent store loaded from ~/.msapling/settings.json;
5594
+ * provides cross-session memory for "always" decisions.
5595
+ * `sessionTrust` — in-memory fallback for the current session when no
5596
+ * TrustStore is wired (preserves old behaviour exactly).
5597
+ */
5598
+ trustStore = null;
5599
+ sessionTrust = /* @__PURE__ */ new Set();
5600
+ mcpRegistry = null;
5601
+ hooks = null;
5602
+ constructor(client, projectRoot) {
5603
+ this.sandbox = new Sandbox(projectRoot);
5604
+ this.mdrive = new MDriveService(client);
5605
+ this.voice = new VoiceService();
5606
+ this.shadow = new ShadowService(client);
5607
+ this.registerTool(new ReadFileTool());
5608
+ this.registerTool(new EditFileTool());
5609
+ this.registerTool(new WriteFileTool());
5610
+ this.registerTool(new RunCommandTool());
5611
+ this.registerTool(new LSPTool());
5612
+ this.registerTool(new SubShellTool());
5613
+ this.registerTool(new GlobFilesTool());
5614
+ this.registerTool(new GrepSearchTool());
5615
+ this.registerTool(new ListDirectoryTool());
5616
+ this.registerTool(new TodoWriteTool());
5617
+ this.registerTool(new TodoReadTool());
5618
+ this.registerTool(new PatchFileTool());
5619
+ this.registerTool(new DispatchAgentTool({
5620
+ client,
5621
+ projectRoot
5622
+ }));
5623
+ this.registerTool(new WebFetchTool());
5624
+ this.registerTool(new BashTool());
5625
+ this.registerTool(new NotebookReadTool());
5626
+ this.registerTool(new NotebookEditTool());
5627
+ this.registerTool(new MultiEditFileTool());
5628
+ this.registerTool(new MoveFileTool());
5629
+ this.registerTool(new DeleteFileTool());
5630
+ }
5631
+ setToolsEnabled(enabled) {
5632
+ this.toolsEnabled = enabled;
5633
+ }
5634
+ setMode(mode) {
5635
+ this.mode = mode;
5636
+ }
5637
+ getMode() {
5638
+ return this.mode;
5639
+ }
5640
+ setApprovalCallback(cb) {
5641
+ this.approvalCallback = cb;
5642
+ }
5643
+ setMCPRegistry(registry) {
5644
+ this.mcpRegistry = registry;
5645
+ }
5646
+ setHookRunner(runner) {
5647
+ this.hooks = runner;
5648
+ }
5649
+ /**
5650
+ * CLI-PARITY-37: Wire in a pre-loaded TrustStore so that "always" approval
5651
+ * decisions survive CLI restarts. Call this once at startup after `load()`
5652
+ * has been awaited.
5653
+ */
5654
+ setTrustStore(store2) {
5655
+ this.trustStore = store2;
5292
5656
  if (store2) {
5293
5657
  for (const key of this.sessionTrust) {
5294
5658
  store2.add(key).catch(() => {
@@ -5428,7 +5792,7 @@ ${blocker.stderr || "(empty)"}`,
5428
5792
  }
5429
5793
  await this.voice.speak(`Executing ${toolName}`, "natural");
5430
5794
  if (toolName === "edit_file") {
5431
- const content = await readFile9(args2.path, "utf-8");
5795
+ const content = await readFile10(args2.path, "utf-8");
5432
5796
  const parentHash = await this.mdrive.getHash(content);
5433
5797
  const block = await this.mdrive.proposeRemoteEdit(
5434
5798
  args2.path,
@@ -5442,7 +5806,14 @@ ${blocker.stderr || "(empty)"}`,
5442
5806
  Please approve the diff in the UI to sync this change locally.`
5443
5807
  };
5444
5808
  }
5445
- const result = await tool.execute(args2, projectRoot);
5809
+ const governor = await getGlobalGovernor();
5810
+ await governor.acquireTool();
5811
+ let result;
5812
+ try {
5813
+ result = await tool.execute(args2, projectRoot);
5814
+ } finally {
5815
+ governor.releaseTool();
5816
+ }
5446
5817
  if (this.hooks) {
5447
5818
  this.hooks.fire({
5448
5819
  event: "post-tool-use",
@@ -5515,14 +5886,14 @@ var init_Safety = __esm({
5515
5886
  });
5516
5887
 
5517
5888
  // ../core/src/ProjectConfig.ts
5518
- import { homedir as homedir7 } from "os";
5519
- import { join as join14, dirname as dirname2, parse as parsePath } from "path";
5520
- import { existsSync as existsSync10 } from "fs";
5521
- import { readFile as readFile10 } from "fs/promises";
5889
+ import { homedir as homedir8 } from "os";
5890
+ import { join as join15, dirname as dirname3, parse as parsePath } from "path";
5891
+ import { existsSync as existsSync11 } from "fs";
5892
+ import { readFile as readFile11 } from "fs/promises";
5522
5893
  async function readIfExists(path2) {
5523
5894
  try {
5524
- if (!existsSync10(path2)) return null;
5525
- const text = await readFile10(path2, "utf8");
5895
+ if (!existsSync11(path2)) return null;
5896
+ const text = await readFile11(path2, "utf8");
5526
5897
  return text.length > TRUNCATE_AT ? text.slice(0, TRUNCATE_AT) + "\n[...truncated]" : text;
5527
5898
  } catch {
5528
5899
  return null;
@@ -5530,7 +5901,7 @@ async function readIfExists(path2) {
5530
5901
  }
5531
5902
  async function findInDir(dir) {
5532
5903
  for (const filename of FILENAMES) {
5533
- const path2 = join14(dir, filename);
5904
+ const path2 = join15(dir, filename);
5534
5905
  const content = await readIfExists(path2);
5535
5906
  if (content !== null) {
5536
5907
  return { path: path2, filename, content };
@@ -5543,7 +5914,7 @@ async function findProjectConfig(start) {
5543
5914
  for (let i = 0; i < 64; i++) {
5544
5915
  const hit = await findInDir(dir);
5545
5916
  if (hit) return hit;
5546
- const parent = dirname2(dir);
5917
+ const parent = dirname3(dir);
5547
5918
  if (parent === dir || parent === parsePath(dir).root) {
5548
5919
  const rootHit = await findInDir(parent);
5549
5920
  return rootHit;
@@ -5553,9 +5924,9 @@ async function findProjectConfig(start) {
5553
5924
  return null;
5554
5925
  }
5555
5926
  async function findUserConfig() {
5556
- const home = homedir7();
5927
+ const home = homedir8();
5557
5928
  if (!home) return null;
5558
- const userDir = join14(home, ".msapling");
5929
+ const userDir = join15(home, ".msapling");
5559
5930
  return findInDir(userDir);
5560
5931
  }
5561
5932
  function buildCombined(user, project) {
@@ -5689,6 +6060,7 @@ var init_Agent = __esm({
5689
6060
  init_Hooks();
5690
6061
  init_ContextBudget();
5691
6062
  init_ToolExecutor();
6063
+ init_ResourceGovernor();
5692
6064
  Agent = class {
5693
6065
  client;
5694
6066
  executor;
@@ -5784,6 +6156,15 @@ var init_Agent = __esm({
5784
6156
  * old backend versions or local/offline models that omit billing data).
5785
6157
  */
5786
6158
  async runWorkerTurn(chatId, prompt4, model, onContent) {
6159
+ const governor = await getGlobalGovernor();
6160
+ await governor.acquireAgent();
6161
+ try {
6162
+ return await this._runWorkerTurnInner(chatId, prompt4, model, onContent);
6163
+ } finally {
6164
+ governor.releaseAgent();
6165
+ }
6166
+ }
6167
+ async _runWorkerTurnInner(chatId, prompt4, model, onContent) {
5787
6168
  if (this.hooks) {
5788
6169
  const outcomes = await this.hooks.fire({
5789
6170
  event: "user-prompt-submit",
@@ -5909,10 +6290,14 @@ ${next}`;
5909
6290
  const journal = getJournal();
5910
6291
  let backendReachable = false;
5911
6292
  let connectionTimeout = null;
6293
+ const controller = new AbortController();
6294
+ let timedOut = false;
5912
6295
  try {
5913
- const controller = new AbortController();
5914
- connectionTimeout = setTimeout(() => controller.abort(), 3e3);
5915
- const backendStream = this.client.streamChat(params);
6296
+ connectionTimeout = setTimeout(() => {
6297
+ timedOut = true;
6298
+ controller.abort();
6299
+ }, 3e3);
6300
+ const backendStream = this.client.streamChat(params, controller.signal);
5916
6301
  for await (const chunk of backendStream) {
5917
6302
  if (connectionTimeout) {
5918
6303
  clearTimeout(connectionTimeout);
@@ -5922,11 +6307,8 @@ ${next}`;
5922
6307
  yield chunk;
5923
6308
  }
5924
6309
  } catch (error) {
5925
- if (connectionTimeout) {
5926
- clearTimeout(connectionTimeout);
5927
- connectionTimeout = null;
5928
- }
5929
- const isNetworkError = error?.code === "ECONNREFUSED" || error?.code === "ECONNRESET" || error?.code === "ETIMEDOUT" || error?.status >= 500 || error?.message?.includes("timeout");
6310
+ const isAbortTimeout = timedOut || error?.name === "AbortError" && controller.signal.aborted;
6311
+ const isNetworkError = isAbortTimeout || error?.code === "ECONNREFUSED" || error?.code === "ECONNRESET" || error?.code === "ETIMEDOUT" || error?.status >= 500;
5930
6312
  if (!isNetworkError) {
5931
6313
  throw error;
5932
6314
  }
@@ -6006,6 +6388,11 @@ ${next}`;
6006
6388
  source: `offline_${dialect}`
6007
6389
  };
6008
6390
  await journal.journalAppend(assistantEntry);
6391
+ } finally {
6392
+ if (connectionTimeout) {
6393
+ clearTimeout(connectionTimeout);
6394
+ connectionTimeout = null;
6395
+ }
6009
6396
  }
6010
6397
  if (backendReachable) {
6011
6398
  const pending = await journal.journalListPending();
@@ -6055,9 +6442,9 @@ ${next}`;
6055
6442
  });
6056
6443
 
6057
6444
  // ../core/src/HardwareMonitor.ts
6058
- import { cpus, totalmem, freemem, platform, arch, release } from "os";
6445
+ import { cpus as cpus2, totalmem as totalmem2, freemem as freemem3, platform as platform2, arch as arch2, release as release2 } from "os";
6059
6446
  function cpuPercent() {
6060
- const all = cpus();
6447
+ const all = cpus2();
6061
6448
  let idle = 0;
6062
6449
  let total = 0;
6063
6450
  for (const c of all) {
@@ -6069,12 +6456,12 @@ function cpuPercent() {
6069
6456
  return Math.round((1 - idle / total) * 100);
6070
6457
  }
6071
6458
  function takeSnapshot() {
6072
- const total = totalmem();
6073
- const free = freemem();
6074
- const cpuList = cpus();
6459
+ const total = totalmem2();
6460
+ const free = freemem3();
6461
+ const cpuList = cpus2();
6075
6462
  return {
6076
- platform: platform(),
6077
- arch: arch(),
6463
+ platform: platform2(),
6464
+ arch: arch2(),
6078
6465
  cores: cpuList.length,
6079
6466
  cpuModel: cpuList[0]?.model ?? "unknown",
6080
6467
  ramGiB: Math.round(total / 1073741824 * 10) / 10,
@@ -6084,18 +6471,18 @@ function takeSnapshot() {
6084
6471
  };
6085
6472
  }
6086
6473
  function buildHwContext() {
6087
- const cpuList = cpus();
6088
- const totalMemBytes = totalmem();
6474
+ const cpuList = cpus2();
6475
+ const totalMemBytes = totalmem2();
6089
6476
  const totalMemGb = Math.round(totalMemBytes / 1073741824 * 100) / 100;
6090
6477
  return {
6091
- platform: platform(),
6092
- arch: arch(),
6478
+ platform: platform2(),
6479
+ arch: arch2(),
6093
6480
  cpu_model: cpuList[0]?.model ?? "unknown",
6094
6481
  cpu_count: cpuList.length,
6095
6482
  total_mem_gb: totalMemGb,
6096
6483
  node_version: process.version,
6097
6484
  bun_version: process.versions.bun,
6098
- os_release: release()
6485
+ os_release: release2()
6099
6486
  };
6100
6487
  }
6101
6488
  var init_HardwareMonitor = __esm({
@@ -6144,17 +6531,17 @@ var init_Mutex = __esm({
6144
6531
  });
6145
6532
 
6146
6533
  // ../core/src/TrustStore.ts
6147
- import { join as join15 } from "path";
6148
- import { homedir as homedir8, platform as platform2 } from "os";
6149
- import { existsSync as existsSync11, mkdirSync } from "fs";
6150
- import { readFile as readFile11, writeFile as writeFile5, chmod } from "fs/promises";
6534
+ import { join as join16 } from "path";
6535
+ import { homedir as homedir9, platform as platform3 } from "os";
6536
+ import { existsSync as existsSync12, mkdirSync as mkdirSync2 } from "fs";
6537
+ import { readFile as readFile12, writeFile as writeFile5, chmod } from "fs/promises";
6151
6538
  var USER_SETTINGS_PATH, TrustStore;
6152
6539
  var init_TrustStore = __esm({
6153
6540
  "../core/src/TrustStore.ts"() {
6154
6541
  "use strict";
6155
6542
  init_esm_shims();
6156
6543
  init_Mutex();
6157
- USER_SETTINGS_PATH = join15(homedir8(), ".msapling", "settings.json");
6544
+ USER_SETTINGS_PATH = join16(homedir9(), ".msapling", "settings.json");
6158
6545
  TrustStore = class {
6159
6546
  /** Current in-memory set of trusted `tool:command` keys. */
6160
6547
  trusted = /* @__PURE__ */ new Set();
@@ -6171,8 +6558,8 @@ var init_TrustStore = __esm({
6171
6558
  */
6172
6559
  async readSettings() {
6173
6560
  try {
6174
- if (!existsSync11(this.settingsPath)) return {};
6175
- const text = await readFile11(this.settingsPath, "utf8");
6561
+ if (!existsSync12(this.settingsPath)) return {};
6562
+ const text = await readFile12(this.settingsPath, "utf8");
6176
6563
  if (!text.trim()) return {};
6177
6564
  return JSON.parse(text);
6178
6565
  } catch {
@@ -6184,10 +6571,10 @@ var init_TrustStore = __esm({
6184
6571
  * updating `trustedCommands`.
6185
6572
  */
6186
6573
  async writeSettings(settings) {
6187
- const dir = join15(homedir8(), ".msapling");
6188
- if (!existsSync11(dir)) mkdirSync(dir, { recursive: true });
6574
+ const dir = join16(homedir9(), ".msapling");
6575
+ if (!existsSync12(dir)) mkdirSync2(dir, { recursive: true });
6189
6576
  await writeFile5(this.settingsPath, JSON.stringify(settings, null, 2), "utf8");
6190
- if (platform2() !== "win32") {
6577
+ if (platform3() !== "win32") {
6191
6578
  try {
6192
6579
  await chmod(this.settingsPath, 384);
6193
6580
  } catch {
@@ -7922,10 +8309,10 @@ var require_keytar2 = __commonJS({
7922
8309
  });
7923
8310
 
7924
8311
  // ../core/src/Storage.ts
7925
- import { join as join16 } from "path";
7926
- import { homedir as homedir9 } from "os";
7927
- import { chmodSync, existsSync as existsSync12, renameSync, unlinkSync, writeFileSync } from "fs";
7928
- import { mkdir as mkdir6, writeFile as writeFile6, readFile as readFile12, appendFile } from "fs/promises";
8312
+ import { join as join17 } from "path";
8313
+ import { homedir as homedir10 } from "os";
8314
+ import { chmodSync, existsSync as existsSync13, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
8315
+ import { mkdir as mkdir6, writeFile as writeFile6, readFile as readFile13, appendFile } from "fs/promises";
7929
8316
  import { randomBytes as randomBytes7, createHash as createHash3 } from "crypto";
7930
8317
  function hashLine(line) {
7931
8318
  return createHash3("sha256").update(line, "utf8").digest("hex");
@@ -7956,7 +8343,7 @@ var init_Storage = __esm({
7956
8343
  */
7957
8344
  _ready;
7958
8345
  constructor() {
7959
- this.baseDir = join16(homedir9(), ".msapling");
8346
+ this.baseDir = join17(homedir10(), ".msapling");
7960
8347
  this._ready = this.ensureDirs();
7961
8348
  }
7962
8349
  async ensureDirs() {
@@ -7975,7 +8362,7 @@ var init_Storage = __esm({
7975
8362
  "cache/recipes/objects"
7976
8363
  ];
7977
8364
  for (const sub of subdirs) {
7978
- const path2 = join16(this.baseDir, sub);
8365
+ const path2 = join17(this.baseDir, sub);
7979
8366
  await mkdir6(path2, { recursive: true });
7980
8367
  }
7981
8368
  if (process.platform !== "win32") {
@@ -7993,11 +8380,11 @@ var init_Storage = __esm({
7993
8380
  await this._ready;
7994
8381
  const KEYCHAIN_SERVICE = "msapling-cli";
7995
8382
  const KEYCHAIN_ACCOUNT = "auth_token";
7996
- const filePath = join16(this.baseDir, "vault", "token");
8383
+ const filePath = join17(this.baseDir, "vault", "token");
7997
8384
  try {
7998
8385
  await keytar.setPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, token);
7999
8386
  try {
8000
- if (existsSync12(filePath)) {
8387
+ if (existsSync13(filePath)) {
8001
8388
  unlinkSync(filePath);
8002
8389
  }
8003
8390
  } catch {
@@ -8013,7 +8400,7 @@ var init_Storage = __esm({
8013
8400
  async loadToken() {
8014
8401
  const KEYCHAIN_SERVICE = "msapling-cli";
8015
8402
  const KEYCHAIN_ACCOUNT = "auth_token";
8016
- const filePath = join16(this.baseDir, "vault", "token");
8403
+ const filePath = join17(this.baseDir, "vault", "token");
8017
8404
  try {
8018
8405
  const keychainToken = await keytar.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
8019
8406
  if (keychainToken) {
@@ -8022,8 +8409,8 @@ var init_Storage = __esm({
8022
8409
  } catch (e) {
8023
8410
  console.debug(`Keychain unavailable (${e instanceof Error ? e.message : String(e)}), falling back to file storage`);
8024
8411
  }
8025
- if (existsSync12(filePath)) {
8026
- const text = await readFile12(filePath, "utf8");
8412
+ if (existsSync13(filePath)) {
8413
+ const text = await readFile13(filePath, "utf8");
8027
8414
  return text.trim();
8028
8415
  }
8029
8416
  return null;
@@ -8034,14 +8421,14 @@ var init_Storage = __esm({
8034
8421
  async clearToken() {
8035
8422
  const KEYCHAIN_SERVICE = "msapling-cli";
8036
8423
  const KEYCHAIN_ACCOUNT = "auth_token";
8037
- const filePath = join16(this.baseDir, "vault", "token");
8424
+ const filePath = join17(this.baseDir, "vault", "token");
8038
8425
  try {
8039
8426
  await keytar.deletePassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
8040
8427
  } catch (e) {
8041
8428
  console.debug(`Keychain unavailable for deletion (${e instanceof Error ? e.message : String(e)})`);
8042
8429
  }
8043
8430
  try {
8044
- if (existsSync12(filePath)) {
8431
+ if (existsSync13(filePath)) {
8045
8432
  const fs3 = await import("fs/promises");
8046
8433
  await fs3.unlink(filePath);
8047
8434
  }
@@ -8073,15 +8460,15 @@ var init_Storage = __esm({
8073
8460
  async writeVaultRef(label, value) {
8074
8461
  await this._ready;
8075
8462
  const hash = createHash3("sha256").update(value, "utf8").digest("hex");
8076
- const objectPath = join16(this.baseDir, "vault", "objects", hash);
8077
- const refPath = join16(this.baseDir, "vault", "refs", label);
8463
+ const objectPath = join17(this.baseDir, "vault", "objects", hash);
8464
+ const refPath = join17(this.baseDir, "vault", "refs", label);
8078
8465
  const refTmp = `${refPath}.tmp`;
8079
8466
  await writeFile6(objectPath, value, "utf8");
8080
8467
  if (process.platform !== "win32") {
8081
8468
  chmodSync(objectPath, 384);
8082
8469
  }
8083
8470
  await writeFile6(refTmp, hash, "utf8");
8084
- renameSync(refTmp, refPath);
8471
+ renameSync2(refTmp, refPath);
8085
8472
  return hash;
8086
8473
  }
8087
8474
  /**
@@ -8090,12 +8477,12 @@ var init_Storage = __esm({
8090
8477
  */
8091
8478
  async readVaultRef(label) {
8092
8479
  await this._ready;
8093
- const refPath = join16(this.baseDir, "vault", "refs", label);
8094
- if (!existsSync12(refPath)) return null;
8095
- const hash = (await readFile12(refPath, "utf8")).trim();
8096
- const objectPath = join16(this.baseDir, "vault", "objects", hash);
8097
- if (!existsSync12(objectPath)) return null;
8098
- return readFile12(objectPath, "utf8");
8480
+ const refPath = join17(this.baseDir, "vault", "refs", label);
8481
+ if (!existsSync13(refPath)) return null;
8482
+ const hash = (await readFile13(refPath, "utf8")).trim();
8483
+ const objectPath = join17(this.baseDir, "vault", "objects", hash);
8484
+ if (!existsSync13(objectPath)) return null;
8485
+ return readFile13(objectPath, "utf8");
8099
8486
  }
8100
8487
  // ─────────────────────────────────────────────────────────────────────────
8101
8488
  // CLI-ARCH-RECIPE-AT-HASH-URI-01 — recipe hash registry helpers
@@ -8118,23 +8505,23 @@ var init_Storage = __esm({
8118
8505
  async registerRecipe(name, content) {
8119
8506
  await this._ready;
8120
8507
  const hash = createHash3("sha256").update(content, "utf8").digest("hex");
8121
- const objectPath = join16(this.baseDir, "cache", "recipes", "objects", hash);
8122
- const indexPath = join16(this.baseDir, "cache", "recipes", "index.json");
8508
+ const objectPath = join17(this.baseDir, "cache", "recipes", "objects", hash);
8509
+ const indexPath = join17(this.baseDir, "cache", "recipes", "index.json");
8123
8510
  const indexTmp = `${indexPath}.tmp`;
8124
- if (!existsSync12(objectPath)) {
8511
+ if (!existsSync13(objectPath)) {
8125
8512
  await writeFile6(objectPath, content, "utf8");
8126
8513
  }
8127
8514
  let index = {};
8128
- if (existsSync12(indexPath)) {
8515
+ if (existsSync13(indexPath)) {
8129
8516
  try {
8130
- index = JSON.parse(await readFile12(indexPath, "utf8"));
8517
+ index = JSON.parse(await readFile13(indexPath, "utf8"));
8131
8518
  } catch {
8132
8519
  index = {};
8133
8520
  }
8134
8521
  }
8135
8522
  index[name] = hash;
8136
8523
  await writeFile6(indexTmp, JSON.stringify(index, null, 2), "utf8");
8137
- renameSync(indexTmp, indexPath);
8524
+ renameSync2(indexTmp, indexPath);
8138
8525
  return hash;
8139
8526
  }
8140
8527
  /**
@@ -8143,26 +8530,26 @@ var init_Storage = __esm({
8143
8530
  */
8144
8531
  async resolveRecipe(nameOrRef) {
8145
8532
  await this._ready;
8146
- const indexPath = join16(this.baseDir, "cache", "recipes", "index.json");
8533
+ const indexPath = join17(this.baseDir, "cache", "recipes", "index.json");
8147
8534
  const atIdx = nameOrRef.indexOf("@");
8148
8535
  if (atIdx !== -1) {
8149
8536
  const hash2 = nameOrRef.slice(atIdx + 1);
8150
- const objectPath2 = join16(this.baseDir, "cache", "recipes", "objects", hash2);
8151
- if (!existsSync12(objectPath2)) return null;
8152
- return { hash: hash2, content: await readFile12(objectPath2, "utf8") };
8537
+ const objectPath2 = join17(this.baseDir, "cache", "recipes", "objects", hash2);
8538
+ if (!existsSync13(objectPath2)) return null;
8539
+ return { hash: hash2, content: await readFile13(objectPath2, "utf8") };
8153
8540
  }
8154
- if (!existsSync12(indexPath)) return null;
8541
+ if (!existsSync13(indexPath)) return null;
8155
8542
  let index;
8156
8543
  try {
8157
- index = JSON.parse(await readFile12(indexPath, "utf8"));
8544
+ index = JSON.parse(await readFile13(indexPath, "utf8"));
8158
8545
  } catch {
8159
8546
  return null;
8160
8547
  }
8161
8548
  const hash = index[nameOrRef];
8162
8549
  if (!hash) return null;
8163
- const objectPath = join16(this.baseDir, "cache", "recipes", "objects", hash);
8164
- if (!existsSync12(objectPath)) return null;
8165
- return { hash, content: await readFile12(objectPath, "utf8") };
8550
+ const objectPath = join17(this.baseDir, "cache", "recipes", "objects", hash);
8551
+ if (!existsSync13(objectPath)) return null;
8552
+ return { hash, content: await readFile13(objectPath, "utf8") };
8166
8553
  }
8167
8554
  // ─────────────────────────────────────────────────────────────────────────
8168
8555
  // CLI-ARCH-HASH-CHAIN-HISTORY-01 — append-only NDJSON history with hash chain
@@ -8184,18 +8571,18 @@ var init_Storage = __esm({
8184
8571
  */
8185
8572
  async appendHistoryEntry(content) {
8186
8573
  await this._ready;
8187
- const path2 = join16(this.baseDir, "history", "shell_history.jsonl");
8574
+ const path2 = join17(this.baseDir, "history", "shell_history.jsonl");
8188
8575
  return this.historyMutex.run(async () => {
8189
8576
  let release3 = null;
8190
8577
  try {
8191
- if (!existsSync12(path2)) {
8578
+ if (!existsSync13(path2)) {
8192
8579
  await writeFile6(path2, "", "utf8");
8193
8580
  }
8194
8581
  release3 = await lockfile.lock(path2, { retries: 5, retryWait: 50 });
8195
8582
  let prevHash = null;
8196
8583
  let seq = 1;
8197
- if (existsSync12(path2)) {
8198
- const raw = (await readFile12(path2, "utf8")).trimEnd();
8584
+ if (existsSync13(path2)) {
8585
+ const raw = (await readFile13(path2, "utf8")).trimEnd();
8199
8586
  if (raw.length > 0) {
8200
8587
  const lines = raw.split("\n");
8201
8588
  const lastLine = lines[lines.length - 1];
@@ -8234,9 +8621,9 @@ var init_Storage = __esm({
8234
8621
  */
8235
8622
  async loadHistoryEntries() {
8236
8623
  await this._ready;
8237
- const path2 = join16(this.baseDir, "history", "shell_history.jsonl");
8238
- if (!existsSync12(path2)) return [];
8239
- const raw = await readFile12(path2, "utf8");
8624
+ const path2 = join17(this.baseDir, "history", "shell_history.jsonl");
8625
+ if (!existsSync13(path2)) return [];
8626
+ const raw = await readFile13(path2, "utf8");
8240
8627
  const entries = [];
8241
8628
  for (const line of raw.split("\n")) {
8242
8629
  if (!line.trim()) continue;
@@ -8284,20 +8671,20 @@ var init_Storage = __esm({
8284
8671
  */
8285
8672
  async saveHistory(history) {
8286
8673
  await this._ready;
8287
- const path2 = join16(this.baseDir, "history", "shell_history.json");
8674
+ const path2 = join17(this.baseDir, "history", "shell_history.json");
8288
8675
  let release3;
8289
8676
  try {
8290
- if (!existsSync12(path2)) writeFileSync(path2, "[]", "utf8");
8677
+ if (!existsSync13(path2)) writeFileSync2(path2, "[]", "utf8");
8291
8678
  release3 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
8292
8679
  await this.historyMutex.run(async () => {
8293
8680
  const tmpPath = `${path2}.tmp`;
8294
8681
  const content = JSON.stringify(history, null, 2);
8295
8682
  await writeFile6(tmpPath, content, "utf8");
8296
8683
  try {
8297
- renameSync(tmpPath, path2);
8684
+ renameSync2(tmpPath, path2);
8298
8685
  } catch (e) {
8299
8686
  try {
8300
- if (existsSync12(tmpPath)) {
8687
+ if (existsSync13(tmpPath)) {
8301
8688
  const fs3 = await import("fs/promises");
8302
8689
  await fs3.unlink(tmpPath);
8303
8690
  }
@@ -8325,27 +8712,27 @@ var init_Storage = __esm({
8325
8712
  */
8326
8713
  async loadHistory() {
8327
8714
  await this._ready;
8328
- const path2 = join16(this.baseDir, "history", "shell_history.json");
8329
- if (!existsSync12(path2)) return [];
8715
+ const path2 = join17(this.baseDir, "history", "shell_history.json");
8716
+ if (!existsSync13(path2)) return [];
8330
8717
  let release3;
8331
8718
  try {
8332
8719
  release3 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
8333
8720
  return this.historyMutex.run(async () => {
8334
- if (existsSync12(path2)) {
8335
- const text = await readFile12(path2, "utf8");
8721
+ if (existsSync13(path2)) {
8722
+ const text = await readFile13(path2, "utf8");
8336
8723
  try {
8337
8724
  return JSON.parse(text);
8338
8725
  } catch (parseErr) {
8339
8726
  const filename = path2.split("/").pop() || "shell_history.json";
8340
8727
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8341
8728
  const suffix = randomBytes7(4).toString("hex");
8342
- const corruptBackupPath = join16(
8729
+ const corruptBackupPath = join17(
8343
8730
  this.baseDir,
8344
8731
  "history",
8345
8732
  `${filename}.corrupt.${stamp}-${suffix}.bak`
8346
8733
  );
8347
8734
  try {
8348
- renameSync(path2, corruptBackupPath);
8735
+ renameSync2(path2, corruptBackupPath);
8349
8736
  console.warn(`History file was corrupt; backed up to ${corruptBackupPath}`);
8350
8737
  } catch (backupErr) {
8351
8738
  console.error(`Failed to backup corrupt history file: ${backupErr}`);
@@ -8370,20 +8757,20 @@ var init_Storage = __esm({
8370
8757
  * R20-CLI-2: Typed with PermissionState from Sandbox.ts.
8371
8758
  */
8372
8759
  async savePermissions(permissions) {
8373
- const path2 = join16(this.baseDir, "vault", "permissions.json");
8760
+ const path2 = join17(this.baseDir, "vault", "permissions.json");
8374
8761
  let release3;
8375
8762
  try {
8376
- if (!existsSync12(path2)) writeFileSync(path2, "{}", "utf8");
8763
+ if (!existsSync13(path2)) writeFileSync2(path2, "{}", "utf8");
8377
8764
  release3 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
8378
8765
  await this.permissionsMutex.run(async () => {
8379
8766
  const tmpPath = `${path2}.tmp`;
8380
8767
  const content = JSON.stringify(permissions, null, 2);
8381
8768
  await writeFile6(tmpPath, content, "utf8");
8382
8769
  try {
8383
- renameSync(tmpPath, path2);
8770
+ renameSync2(tmpPath, path2);
8384
8771
  } catch (e) {
8385
8772
  try {
8386
- if (existsSync12(tmpPath)) {
8773
+ if (existsSync13(tmpPath)) {
8387
8774
  const fs3 = await import("fs/promises");
8388
8775
  await fs3.unlink(tmpPath);
8389
8776
  }
@@ -8407,27 +8794,27 @@ var init_Storage = __esm({
8407
8794
  * R20-CLI-2: Typed with PermissionState from Sandbox.ts.
8408
8795
  */
8409
8796
  async loadPermissions() {
8410
- const path2 = join16(this.baseDir, "vault", "permissions.json");
8411
- if (!existsSync12(path2)) return { trustedCommands: [], trustedPaths: [] };
8797
+ const path2 = join17(this.baseDir, "vault", "permissions.json");
8798
+ if (!existsSync13(path2)) return { trustedCommands: [], trustedPaths: [] };
8412
8799
  let release3;
8413
8800
  try {
8414
8801
  release3 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
8415
8802
  return this.permissionsMutex.run(async () => {
8416
- if (existsSync12(path2)) {
8417
- const text = await readFile12(path2, "utf8");
8803
+ if (existsSync13(path2)) {
8804
+ const text = await readFile13(path2, "utf8");
8418
8805
  try {
8419
8806
  return JSON.parse(text);
8420
8807
  } catch (parseErr) {
8421
8808
  const filename = path2.split("/").pop() || "permissions.json";
8422
8809
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8423
8810
  const suffix = randomBytes7(4).toString("hex");
8424
- const corruptBackupPath = join16(
8811
+ const corruptBackupPath = join17(
8425
8812
  this.baseDir,
8426
8813
  "vault",
8427
8814
  `${filename}.corrupt.${stamp}-${suffix}.bak`
8428
8815
  );
8429
8816
  try {
8430
- renameSync(path2, corruptBackupPath);
8817
+ renameSync2(path2, corruptBackupPath);
8431
8818
  console.warn(`Permissions file was corrupt; backed up to ${corruptBackupPath}`);
8432
8819
  } catch (backupErr) {
8433
8820
  console.error(`Failed to backup corrupt permissions file: ${backupErr}`);
@@ -8458,7 +8845,7 @@ var init_Storage = __esm({
8458
8845
  const filename = filePath.split("/").pop() || "file";
8459
8846
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8460
8847
  const suffix = randomBytes7(4).toString("hex");
8461
- const backupPath = join16(this.baseDir, "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
8848
+ const backupPath = join17(this.baseDir, "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
8462
8849
  await writeFile6(backupPath, content, "utf8");
8463
8850
  if (process.platform !== "win32") {
8464
8851
  chmodSync(backupPath, 384);
@@ -8470,11 +8857,11 @@ var init_Storage = __esm({
8470
8857
  });
8471
8858
 
8472
8859
  // ../core/src/Settings.ts
8473
- import { homedir as homedir10 } from "os";
8474
- import { join as join17 } from "path";
8475
- import { existsSync as existsSync13 } from "fs";
8860
+ import { homedir as homedir11 } from "os";
8861
+ import { join as join18 } from "path";
8862
+ import { existsSync as existsSync14 } from "fs";
8476
8863
  import * as fs from "fs";
8477
- import { readFile as readFile13 } from "fs/promises";
8864
+ import { readFile as readFile14 } from "fs/promises";
8478
8865
  import { randomBytes as randomBytes8 } from "crypto";
8479
8866
  function ensureConfigDir(p) {
8480
8867
  try {
@@ -8495,8 +8882,8 @@ function ensureConfigDir(p) {
8495
8882
  }
8496
8883
  async function readJson(path2) {
8497
8884
  try {
8498
- if (!existsSync13(path2)) return null;
8499
- const text = await readFile13(path2, "utf8");
8885
+ if (!existsSync14(path2)) return null;
8886
+ const text = await readFile14(path2, "utf8");
8500
8887
  if (!text.trim()) return null;
8501
8888
  return JSON.parse(text);
8502
8889
  } catch {
@@ -8550,8 +8937,8 @@ function mergeSettings(base, override) {
8550
8937
  }
8551
8938
  async function loadSettings(cwd = process.cwd(), env = process.env, warn) {
8552
8939
  const sources = [];
8553
- const userPath = join17(homedir10() || ".", ".msapling", "settings.json");
8554
- const projectPath = join17(cwd, ".msapling", "settings.json");
8940
+ const userPath = join18(homedir11() || ".", ".msapling", "settings.json");
8941
+ const projectPath = join18(cwd, ".msapling", "settings.json");
8555
8942
  const [user, project] = await Promise.all([readJson(userPath), readJson(projectPath)]);
8556
8943
  if (user) sources.push(userPath);
8557
8944
  if (project) sources.push(projectPath);
@@ -9218,10 +9605,10 @@ var init_unlock = __esm({
9218
9605
  });
9219
9606
 
9220
9607
  // src/commands/doctor.ts
9221
- import { homedir as homedir11 } from "os";
9222
- import { join as join18 } from "path";
9223
- import { existsSync as existsSync14 } from "fs";
9224
- import { readFile as readFile14 } from "fs/promises";
9608
+ import { homedir as homedir12 } from "os";
9609
+ import { join as join19 } from "path";
9610
+ import { existsSync as existsSync15 } from "fs";
9611
+ import { readFile as readFile15 } from "fs/promises";
9225
9612
  async function checkApiHealth(client) {
9226
9613
  try {
9227
9614
  if (typeof client.request !== "function") {
@@ -9246,12 +9633,12 @@ async function checkAuthStatus(client) {
9246
9633
  }
9247
9634
  }
9248
9635
  async function checkSettingsFile() {
9249
- const settingsPath = join18(homedir11(), ".msapling", "settings.json");
9636
+ const settingsPath = join19(homedir12(), ".msapling", "settings.json");
9250
9637
  try {
9251
- if (!existsSync14(settingsPath)) {
9638
+ if (!existsSync15(settingsPath)) {
9252
9639
  return { ok: false, message: `Not found: ${settingsPath}` };
9253
9640
  }
9254
- const text = await readFile14(settingsPath, "utf8");
9641
+ const text = await readFile15(settingsPath, "utf8");
9255
9642
  const parsed = JSON.parse(text);
9256
9643
  return { ok: true, message: `Valid JSON (${settingsPath})`, settings: parsed };
9257
9644
  } catch (e) {
@@ -9935,8 +10322,8 @@ var init_memories = __esm({
9935
10322
  });
9936
10323
 
9937
10324
  // src/commands/mdrive.ts
9938
- import { readFile as readFile15, writeFile as writeFile7 } from "fs/promises";
9939
- import { existsSync as existsSync15 } from "fs";
10325
+ import { readFile as readFile16, writeFile as writeFile7 } from "fs/promises";
10326
+ import { existsSync as existsSync16 } from "fs";
9940
10327
  import { basename, resolve as resolve14 } from "path";
9941
10328
  function formatBytes(b) {
9942
10329
  if (!b) return "0";
@@ -9989,11 +10376,11 @@ var init_mdrive = __esm({
9989
10376
  return;
9990
10377
  }
9991
10378
  const absLocal = resolve14(local);
9992
- if (!existsSync15(absLocal)) {
10379
+ if (!existsSync16(absLocal)) {
9993
10380
  context.addMessage("error", `Local file not found: ${absLocal}`);
9994
10381
  return;
9995
10382
  }
9996
- const content = await readFile15(absLocal, "utf8");
10383
+ const content = await readFile16(absLocal, "utf8");
9997
10384
  const res = await context.client.mdriveWrite(remote, content);
9998
10385
  context.addMessage("system", `Uploaded ${absLocal} \u2192 mdrive:${remote} (${formatBytes(content.length)}, hash=${res?.hash?.slice(0, 12) ?? "?"})`);
9999
10386
  return;
@@ -10086,15 +10473,15 @@ var init_clear = __esm({
10086
10473
  });
10087
10474
 
10088
10475
  // src/commands/mode.ts
10089
- import { homedir as homedir12 } from "os";
10090
- import { join as join19 } from "path";
10091
- import { existsSync as existsSync16 } from "fs";
10092
- import { readFile as readFile16, writeFile as writeFile8, mkdir as mkdir7 } from "fs/promises";
10476
+ import { homedir as homedir13 } from "os";
10477
+ import { join as join20 } from "path";
10478
+ import { existsSync as existsSync17 } from "fs";
10479
+ import { readFile as readFile17, writeFile as writeFile8, mkdir as mkdir7 } from "fs/promises";
10093
10480
  async function persistApprovalMode(mode, ttlMs) {
10094
10481
  try {
10095
10482
  let existing = {};
10096
- if (existsSync16(SETTINGS_PATH)) {
10097
- const text = await readFile16(SETTINGS_PATH, "utf8");
10483
+ if (existsSync17(SETTINGS_PATH)) {
10484
+ const text = await readFile17(SETTINGS_PATH, "utf8");
10098
10485
  if (text.trim()) {
10099
10486
  existing = JSON.parse(text);
10100
10487
  }
@@ -10105,8 +10492,8 @@ async function persistApprovalMode(mode, ttlMs) {
10105
10492
  ...ttlMs && { ttlMs }
10106
10493
  };
10107
10494
  existing.approvalMode = entry;
10108
- const settingsDir = join19(homedir12(), ".msapling");
10109
- if (!existsSync16(settingsDir)) {
10495
+ const settingsDir = join20(homedir13(), ".msapling");
10496
+ if (!existsSync17(settingsDir)) {
10110
10497
  await mkdir7(settingsDir, { recursive: true });
10111
10498
  }
10112
10499
  await writeFile8(SETTINGS_PATH, JSON.stringify(existing, null, 2), "utf8");
@@ -10118,7 +10505,7 @@ var init_mode = __esm({
10118
10505
  "src/commands/mode.ts"() {
10119
10506
  "use strict";
10120
10507
  init_esm_shims();
10121
- SETTINGS_PATH = join19(homedir12(), ".msapling", "settings.json");
10508
+ SETTINGS_PATH = join20(homedir13(), ".msapling", "settings.json");
10122
10509
  modeCommand = {
10123
10510
  name: "mode",
10124
10511
  args: "[default|plan|acceptEdits|bypassPermissions] [...options]",
@@ -10520,8 +10907,8 @@ var init_compact = __esm({
10520
10907
  });
10521
10908
 
10522
10909
  // src/commands/init.ts
10523
- import { join as join20 } from "path";
10524
- import { existsSync as existsSync17 } from "fs";
10910
+ import { join as join21 } from "path";
10911
+ import { existsSync as existsSync18 } from "fs";
10525
10912
  import { writeFile as writeFile9 } from "fs/promises";
10526
10913
  var initCommand;
10527
10914
  var init_init = __esm({
@@ -10535,8 +10922,8 @@ var init_init = __esm({
10535
10922
  handler: async (args2, context) => {
10536
10923
  try {
10537
10924
  const cwd = process.cwd();
10538
- const path2 = join20(cwd, "MSAPLING.md");
10539
- if (existsSync17(path2)) {
10925
+ const path2 = join21(cwd, "MSAPLING.md");
10926
+ if (existsSync18(path2)) {
10540
10927
  context.addMessage("error", "MSAPLING.md already exists in current directory.");
10541
10928
  return;
10542
10929
  }
@@ -10562,8 +10949,8 @@ var init_init = __esm({
10562
10949
  });
10563
10950
 
10564
10951
  // src/commands/review.ts
10565
- import { existsSync as existsSync18 } from "fs";
10566
- import { readFile as readFile17 } from "fs/promises";
10952
+ import { existsSync as existsSync19 } from "fs";
10953
+ import { readFile as readFile18 } from "fs/promises";
10567
10954
  var reviewCommand;
10568
10955
  var init_review = __esm({
10569
10956
  "src/commands/review.ts"() {
@@ -10582,8 +10969,8 @@ var init_review = __esm({
10582
10969
  }
10583
10970
  let content = "";
10584
10971
  try {
10585
- if (existsSync18(target)) {
10586
- content = await readFile17(target, "utf8");
10972
+ if (existsSync19(target)) {
10973
+ content = await readFile18(target, "utf8");
10587
10974
  } else {
10588
10975
  content = `Review target: ${target}`;
10589
10976
  }
@@ -10676,15 +11063,15 @@ var init_swarm = __esm({
10676
11063
 
10677
11064
  // src/commands/recipe.ts
10678
11065
  import { parse as parseYaml } from "yaml";
10679
- import { existsSync as existsSync19 } from "fs";
10680
- import { readFile as readFile18 } from "fs/promises";
10681
- import { join as join21 } from "path";
11066
+ import { existsSync as existsSync20 } from "fs";
11067
+ import { readFile as readFile19 } from "fs/promises";
11068
+ import { join as join22 } from "path";
10682
11069
  function findRecipe(name, cwd) {
10683
11070
  for (const dir of RECIPE_DIRS) {
10684
11071
  for (const suffix of NAME_SUFFIXES) {
10685
11072
  for (const ext of FILE_EXTS) {
10686
- const p = join21(cwd, dir, `${name}${suffix}${ext}`);
10687
- if (existsSync19(p)) return p;
11073
+ const p = join22(cwd, dir, `${name}${suffix}${ext}`);
11074
+ if (existsSync20(p)) return p;
10688
11075
  }
10689
11076
  }
10690
11077
  }
@@ -10743,7 +11130,7 @@ var init_recipe = __esm({
10743
11130
  let text;
10744
11131
  let recipe;
10745
11132
  try {
10746
- text = await readFile18(path2, "utf8");
11133
+ text = await readFile19(path2, "utf8");
10747
11134
  recipe = parseYaml(text);
10748
11135
  } catch (e) {
10749
11136
  context.addMessage("error", `Failed to load ${path2}: ${e.message}`);
@@ -10797,13 +11184,13 @@ ${rendered}` : rendered;
10797
11184
  });
10798
11185
 
10799
11186
  // src/commands/skill.ts
10800
- import { existsSync as existsSync20, readdirSync as readdirSync2, statSync as statSync5 } from "fs";
10801
- import { readFile as readFile19 } from "fs/promises";
10802
- import { join as join22, resolve as resolve15 } from "path";
11187
+ import { existsSync as existsSync21, readdirSync as readdirSync2, statSync as statSync5 } from "fs";
11188
+ import { readFile as readFile20 } from "fs/promises";
11189
+ import { join as join23, resolve as resolve15 } from "path";
10803
11190
  function findSkillsRoot(cwd) {
10804
11191
  for (const candidate of SKILLS_DIRS) {
10805
11192
  const full = resolve15(cwd, candidate);
10806
- if (existsSync20(full) && statSync5(full).isDirectory()) return full;
11193
+ if (existsSync21(full) && statSync5(full).isDirectory()) return full;
10807
11194
  }
10808
11195
  return null;
10809
11196
  }
@@ -10816,7 +11203,7 @@ function listAllSkills(root) {
10816
11203
  return out;
10817
11204
  }
10818
11205
  for (const domain of domains) {
10819
- const dir = join22(root, domain);
11206
+ const dir = join23(root, domain);
10820
11207
  let s;
10821
11208
  try {
10822
11209
  s = statSync5(dir);
@@ -10832,7 +11219,7 @@ function listAllSkills(root) {
10832
11219
  }
10833
11220
  for (const f of files) {
10834
11221
  if (!f.endsWith(".md")) continue;
10835
- out.push({ domain, name: f.slice(0, -3), path: join22(dir, f) });
11222
+ out.push({ domain, name: f.slice(0, -3), path: join23(dir, f) });
10836
11223
  }
10837
11224
  }
10838
11225
  return out.sort(
@@ -10897,7 +11284,7 @@ var init_skill = __esm({
10897
11284
  }
10898
11285
  let body;
10899
11286
  try {
10900
- body = await readFile19(skill.path, "utf8");
11287
+ body = await readFile20(skill.path, "utf8");
10901
11288
  } catch (e) {
10902
11289
  context.addMessage("error", `Failed to load skill ${skill.path}: ${e.message}`);
10903
11290
  return;
@@ -10921,9 +11308,9 @@ ${prompt4}`;
10921
11308
  });
10922
11309
 
10923
11310
  // src/commands/benchmark.ts
10924
- import { homedir as homedir13 } from "os";
10925
- import { join as join23 } from "path";
10926
- import { mkdirSync as mkdirSync4 } from "fs";
11311
+ import { homedir as homedir14 } from "os";
11312
+ import { join as join24 } from "path";
11313
+ import { mkdirSync as mkdirSync5 } from "fs";
10927
11314
  import * as fs2 from "fs";
10928
11315
  function parseArgs(args2) {
10929
11316
  let models = null;
@@ -11042,10 +11429,10 @@ HW at start: ${hw.cores}-core ${hw.platform} | CPU ${hw.cpuPct}% | RAM ${hw.ramP
11042
11429
  `[HW at run time: CPU ${hwAtEnd.cpuPct}% / RAM ${hwAtEnd.ramPct}% | ${hw.ramGiB} GiB RAM, ${hw.cores} cores]`
11043
11430
  );
11044
11431
  try {
11045
- const dir = join23(homedir13(), ".msapling", "benchmarks");
11046
- mkdirSync4(dir, { recursive: true });
11432
+ const dir = join24(homedir14(), ".msapling", "benchmarks");
11433
+ mkdirSync5(dir, { recursive: true });
11047
11434
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
11048
- const file = join23(dir, `${ts}.json`);
11435
+ const file = join24(dir, `${ts}.json`);
11049
11436
  const run = {
11050
11437
  ts: (/* @__PURE__ */ new Date()).toISOString(),
11051
11438
  rounds,
@@ -11291,22 +11678,22 @@ var init_theme = __esm({
11291
11678
  });
11292
11679
 
11293
11680
  // src/commands/theme.ts
11294
- import { join as join24 } from "path";
11295
- import { homedir as homedir14 } from "os";
11296
- import { existsSync as existsSync21 } from "fs";
11297
- import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
11681
+ import { join as join25 } from "path";
11682
+ import { homedir as homedir15 } from "os";
11683
+ import { existsSync as existsSync22 } from "fs";
11684
+ import { readFile as readFile21, writeFile as writeFile10 } from "fs/promises";
11298
11685
  async function persistTheme(storage, themeName) {
11299
- const settingsPath = join24(homedir14(), ".msapling", "settings.json");
11686
+ const settingsPath = join25(homedir15(), ".msapling", "settings.json");
11300
11687
  let existing = {};
11301
11688
  try {
11302
- if (existsSync21(settingsPath)) {
11303
- const text = await readFile20(settingsPath, "utf8");
11689
+ if (existsSync22(settingsPath)) {
11690
+ const text = await readFile21(settingsPath, "utf8");
11304
11691
  if (text.trim()) existing = JSON.parse(text);
11305
11692
  }
11306
11693
  } catch {
11307
11694
  }
11308
11695
  existing["theme"] = themeName;
11309
- ensureConfigDir(join24(homedir14(), ".msapling"));
11696
+ ensureConfigDir(join25(homedir15(), ".msapling"));
11310
11697
  await writeFile10(settingsPath, JSON.stringify(existing, null, 2), "utf8");
11311
11698
  }
11312
11699
  var VALID_THEMES, themeCommand;
@@ -11378,7 +11765,7 @@ var init_version = __esm({
11378
11765
  description: "Show version information for CLI and core packages",
11379
11766
  category: "debug",
11380
11767
  handler: async (_args, context) => {
11381
- const cliVersion = true ? "2.3.6-beta.30" : "(dev)";
11768
+ const cliVersion = true ? "2.3.6-beta.31" : "(dev)";
11382
11769
  const coreVersion = true ? "2.3.2" : "(dev)";
11383
11770
  const runtime = process.version;
11384
11771
  context.addMessage("system", "MSapling Version Info");
@@ -11387,7 +11774,7 @@ var init_version = __esm({
11387
11774
  context.addMessage("system", row2("Core (@msapling/core)", coreVersion));
11388
11775
  context.addMessage("system", row2("Runtime (Node/Bun)", runtime));
11389
11776
  try {
11390
- const ts = "2026-05-29T20:13:59.523Z";
11777
+ const ts = "2026-05-30T19:14:27.313Z";
11391
11778
  if (ts && ts !== "__BUILD_TIMESTAMP__") {
11392
11779
  context.addMessage("system", row2("Build Timestamp", ts));
11393
11780
  }
@@ -11400,15 +11787,15 @@ var init_version = __esm({
11400
11787
  });
11401
11788
 
11402
11789
  // src/commands/feedback.ts
11403
- import { join as join25 } from "path";
11404
- import { existsSync as existsSync22 } from "fs";
11405
- import { readFile as readFile21 } from "fs/promises";
11790
+ import { join as join26 } from "path";
11791
+ import { existsSync as existsSync23 } from "fs";
11792
+ import { readFile as readFile22 } from "fs/promises";
11406
11793
  async function readCliVersion() {
11407
11794
  try {
11408
11795
  const baseDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
11409
- const pkgPath = join25(baseDir, "..", "..", "package.json");
11410
- if (!existsSync22(pkgPath)) return "unknown";
11411
- const text = await readFile21(pkgPath, "utf8");
11796
+ const pkgPath = join26(baseDir, "..", "..", "package.json");
11797
+ if (!existsSync23(pkgPath)) return "unknown";
11798
+ const text = await readFile22(pkgPath, "utf8");
11412
11799
  const json = JSON.parse(text);
11413
11800
  return json.version ?? "unknown";
11414
11801
  } catch {
@@ -11448,8 +11835,8 @@ var init_feedback = __esm({
11448
11835
  });
11449
11836
 
11450
11837
  // src/commands/export.ts
11451
- import { homedir as homedir15 } from "os";
11452
- import { join as join26 } from "path";
11838
+ import { homedir as homedir16 } from "os";
11839
+ import { join as join27 } from "path";
11453
11840
  import { writeFile as writeFile11, mkdir as mkdir8 } from "fs/promises";
11454
11841
  function formatTimestamp(date) {
11455
11842
  return date.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
@@ -11499,10 +11886,10 @@ var init_export = __esm({
11499
11886
  let outputPath;
11500
11887
  let content;
11501
11888
  if (arg === "" || arg === "json") {
11502
- outputPath = join26(homedir15(), `msapling-export-${timestamp}.json`);
11889
+ outputPath = join27(homedir16(), `msapling-export-${timestamp}.json`);
11503
11890
  content = buildJsonExport(history);
11504
11891
  } else if (arg === "markdown" || arg === "md") {
11505
- outputPath = join26(homedir15(), `msapling-export-${timestamp}.md`);
11892
+ outputPath = join27(homedir16(), `msapling-export-${timestamp}.md`);
11506
11893
  content = buildMarkdownExport(history);
11507
11894
  } else {
11508
11895
  outputPath = arg;
@@ -11514,7 +11901,7 @@ var init_export = __esm({
11514
11901
  }
11515
11902
  }
11516
11903
  try {
11517
- const dir = join26(outputPath, "..");
11904
+ const dir = join27(outputPath, "..");
11518
11905
  await mkdir8(dir, { recursive: true });
11519
11906
  await writeFile11(outputPath, content, "utf8");
11520
11907
  context.addMessage("system", `Exported to: ${outputPath}`);
@@ -11709,17 +12096,17 @@ var init_plan = __esm({
11709
12096
  });
11710
12097
 
11711
12098
  // src/commands/note.ts
11712
- import { homedir as homedir16 } from "os";
11713
- import { join as join27 } from "path";
11714
- import { existsSync as existsSync23 } from "fs";
11715
- import { readFile as readFile22, writeFile as writeFile12 } from "fs/promises";
12099
+ import { homedir as homedir17 } from "os";
12100
+ import { join as join28 } from "path";
12101
+ import { existsSync as existsSync24 } from "fs";
12102
+ import { readFile as readFile23, writeFile as writeFile12 } from "fs/promises";
11716
12103
  function getNotesFilePath() {
11717
- return join27(homedir16(), ".msapling", "notes.json");
12104
+ return join28(homedir17(), ".msapling", "notes.json");
11718
12105
  }
11719
12106
  async function readNotes(filePath = getNotesFilePath()) {
11720
12107
  try {
11721
- if (!existsSync23(filePath)) return [];
11722
- const raw = await readFile22(filePath, "utf8");
12108
+ if (!existsSync24(filePath)) return [];
12109
+ const raw = await readFile23(filePath, "utf8");
11723
12110
  const parsed = JSON.parse(raw);
11724
12111
  if (!Array.isArray(parsed)) return [];
11725
12112
  return parsed;
@@ -11728,7 +12115,7 @@ async function readNotes(filePath = getNotesFilePath()) {
11728
12115
  }
11729
12116
  }
11730
12117
  async function writeNotes(notes, filePath = getNotesFilePath()) {
11731
- const dir = join27(homedir16(), ".msapling");
12118
+ const dir = join28(homedir17(), ".msapling");
11732
12119
  ensureConfigDir(dir);
11733
12120
  await writeFile12(filePath, JSON.stringify(notes, null, 2), "utf8");
11734
12121
  }
@@ -11874,14 +12261,14 @@ var init_todo = __esm({
11874
12261
  });
11875
12262
 
11876
12263
  // src/commands/outputStyle.ts
11877
- import { homedir as homedir17 } from "os";
11878
- import { join as join28, basename as basename2, extname as extname3 } from "path";
11879
- import { existsSync as existsSync24, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync, writeFileSync as writeFileSync2 } from "fs";
12264
+ import { homedir as homedir18 } from "os";
12265
+ import { join as join29, basename as basename2, extname as extname3 } from "path";
12266
+ import { existsSync as existsSync25, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
11880
12267
  function stylesDir() {
11881
- return join28(homedir17(), ".msapling", "output-styles");
12268
+ return join29(homedir18(), ".msapling", "output-styles");
11882
12269
  }
11883
12270
  function activeFile() {
11884
- return join28(stylesDir(), ".active");
12271
+ return join29(stylesDir(), ".active");
11885
12272
  }
11886
12273
  function parseStyleFile(text) {
11887
12274
  const fm = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
@@ -11902,13 +12289,13 @@ function parseStyleFile(text) {
11902
12289
  }
11903
12290
  function listUserStyles() {
11904
12291
  const dir = stylesDir();
11905
- if (!existsSync24(dir)) return [];
12292
+ if (!existsSync25(dir)) return [];
11906
12293
  const out = [];
11907
12294
  for (const entry of readdirSync3(dir)) {
11908
12295
  if (extname3(entry).toLowerCase() !== ".md") continue;
11909
- const full = join28(dir, entry);
12296
+ const full = join29(dir, entry);
11910
12297
  try {
11911
- const text = readFileSync(full, "utf8");
12298
+ const text = readFileSync2(full, "utf8");
11912
12299
  const { description, body } = parseStyleFile(text);
11913
12300
  out.push({
11914
12301
  name: basename2(entry, ".md"),
@@ -11934,16 +12321,16 @@ function findStyle(name) {
11934
12321
  function getActiveStyleName() {
11935
12322
  try {
11936
12323
  const f = activeFile();
11937
- if (!existsSync24(f)) return "default";
11938
- return readFileSync(f, "utf8").trim() || "default";
12324
+ if (!existsSync25(f)) return "default";
12325
+ return readFileSync2(f, "utf8").trim() || "default";
11939
12326
  } catch {
11940
12327
  return "default";
11941
12328
  }
11942
12329
  }
11943
12330
  function setActiveStyleName(name) {
11944
12331
  const dir = stylesDir();
11945
- if (!existsSync24(dir)) mkdirSync5(dir, { recursive: true });
11946
- writeFileSync2(activeFile(), `${name}
12332
+ if (!existsSync25(dir)) mkdirSync6(dir, { recursive: true });
12333
+ writeFileSync3(activeFile(), `${name}
11947
12334
  `, "utf8");
11948
12335
  }
11949
12336
  function getActiveStyle() {
@@ -11955,14 +12342,14 @@ function createUserStyle(name, description, body) {
11955
12342
  throw new Error(`Invalid style name "${name}" \u2014 use letters, digits, _ and - only.`);
11956
12343
  }
11957
12344
  const dir = stylesDir();
11958
- if (!existsSync24(dir)) mkdirSync5(dir, { recursive: true });
11959
- const target = join28(dir, `${name}.md`);
12345
+ if (!existsSync25(dir)) mkdirSync6(dir, { recursive: true });
12346
+ const target = join29(dir, `${name}.md`);
11960
12347
  const frontmatter = `---
11961
12348
  description: ${description.replace(/\n/g, " ")}
11962
12349
  ---
11963
12350
 
11964
12351
  `;
11965
- writeFileSync2(target, frontmatter + body.trim() + "\n", "utf8");
12352
+ writeFileSync3(target, frontmatter + body.trim() + "\n", "utf8");
11966
12353
  return target;
11967
12354
  }
11968
12355
  var BUILTIN_STYLES, outputStyleCommand;
@@ -12470,515 +12857,236 @@ var init_mfa = __esm({
12470
12857
  }
12471
12858
  };
12472
12859
  }
12473
- });
12474
-
12475
- // src/commands/mcp.ts
12476
- var mcpCommand;
12477
- var init_mcp = __esm({
12478
- "src/commands/mcp.ts"() {
12479
- "use strict";
12480
- init_esm_shims();
12481
- mcpCommand = {
12482
- name: "mcp",
12483
- args: "[connect|invoke|tools|disconnect] [...args]",
12484
- description: "MCP client surface: connect, invoke, list tools, disconnect",
12485
- category: "config",
12486
- handler: async (args2, context) => {
12487
- const sub = (args2[0] ?? "tools").toLowerCase();
12488
- const rest = args2.slice(1);
12489
- try {
12490
- if (sub === "connect") {
12491
- const url = rest[0];
12492
- if (!url) {
12493
- context.addMessage("system", "Usage: /mcp connect <server-url>");
12494
- return;
12495
- }
12496
- const result = await context.client.mcpConnect(url);
12497
- context.addMessage("system", `MCP Connected: ${result.status ?? "ok"}`);
12498
- return;
12499
- }
12500
- if (sub === "tools") {
12501
- const tools = await context.client.mcpTools();
12502
- context.addMessage("system", "Available MCP Tools:");
12503
- if (Array.isArray(tools)) {
12504
- for (const tool of tools) {
12505
- context.addMessage("system", ` - ${tool.name}: ${tool.description ?? ""}`);
12506
- }
12507
- } else if (typeof tools === "object") {
12508
- context.addMessage("system", JSON.stringify(tools, null, 2));
12509
- }
12510
- return;
12511
- }
12512
- if (sub === "invoke") {
12513
- const method = rest[0];
12514
- if (!method) {
12515
- context.addMessage("system", "Usage: /mcp invoke <method> [args...]");
12516
- return;
12517
- }
12518
- const argsStr = rest.slice(1).join(" ");
12519
- let argsObj;
12520
- try {
12521
- argsObj = argsStr ? JSON.parse(argsStr) : void 0;
12522
- } catch {
12523
- argsObj = { raw: argsStr };
12524
- }
12525
- const result = await context.client.mcpInvoke(method, argsObj);
12526
- context.addMessage("system", `MCP Result: ${JSON.stringify(result)}`);
12527
- return;
12528
- }
12529
- if (sub === "disconnect") {
12530
- const result = await context.client.mcpDisconnect();
12531
- context.addMessage("system", `MCP Disconnected: ${result.status ?? "ok"}`);
12532
- return;
12533
- }
12534
- context.addMessage("error", `Unknown /mcp subcommand '${sub}'. Try: connect, tools, invoke, disconnect.`);
12535
- } catch (e) {
12536
- context.addMessage("error", `MCP: ${e.message}`);
12537
- }
12538
- }
12539
- };
12540
- }
12541
- });
12542
-
12543
- // src/commands/webhook.ts
12544
- var webhookCommand;
12545
- var init_webhook = __esm({
12546
- "src/commands/webhook.ts"() {
12547
- "use strict";
12548
- init_esm_shims();
12549
- webhookCommand = {
12550
- name: "webhook",
12551
- args: "[list|add|rm|deliveries|retry] [...args]",
12552
- description: "Manage webhooks: create, list, delete, and view delivery logs",
12553
- category: "project",
12554
- handler: async (args2, context) => {
12555
- const sub = (args2[0] ?? "list").toLowerCase();
12556
- const rest = args2.slice(1);
12557
- try {
12558
- if (sub === "list") {
12559
- const webhooks = await context.client.listWebhooks();
12560
- context.addMessage("system", "Webhooks:");
12561
- if (Array.isArray(webhooks) && webhooks.length > 0) {
12562
- for (const wh of webhooks) {
12563
- const id = wh.id ?? wh.webhook_id ?? "?";
12564
- const url = wh.url ?? "?";
12565
- const events = wh.events ? wh.events.join(", ") : "";
12566
- context.addMessage("system", ` - ${id}: ${url} (${events})`);
12567
- }
12568
- } else {
12569
- context.addMessage("system", " (no webhooks)");
12570
- }
12571
- return;
12572
- }
12573
- if (sub === "add") {
12574
- const url = rest[0];
12575
- const events = rest.slice(1);
12576
- if (!url || events.length === 0) {
12577
- context.addMessage("system", "Usage: /webhook add <url> <event1> [event2] ...");
12578
- return;
12579
- }
12580
- const result = await context.client.createWebhook(url, events);
12581
- const id = result.id ?? result.webhook_id ?? "?";
12582
- context.addMessage("system", `Webhook created: ${id} \u2192 ${url}`);
12583
- return;
12584
- }
12585
- if (sub === "rm" || sub === "delete") {
12586
- const id = rest[0];
12587
- if (!id) {
12588
- context.addMessage("system", "Usage: /webhook rm <id>");
12589
- return;
12590
- }
12591
- await context.client.deleteWebhook(id);
12592
- context.addMessage("system", `Deleted webhook: ${id}`);
12593
- return;
12594
- }
12595
- if (sub === "deliveries") {
12596
- const webhookId = rest[0] || void 0;
12597
- const deliveries = await context.client.listWebhookDeliveries(webhookId);
12598
- const title = webhookId ? `Deliveries for webhook ${webhookId}:` : "Recent deliveries:";
12599
- context.addMessage("system", title);
12600
- if (Array.isArray(deliveries) && deliveries.length > 0) {
12601
- for (const d of deliveries) {
12602
- const id = d.id ?? d.delivery_id ?? "?";
12603
- const status = d.status ?? "?";
12604
- const at = d.delivered_at ?? d.created_at ?? "?";
12605
- context.addMessage("system", ` - ${id}: ${status} (${at})`);
12606
- }
12607
- } else {
12608
- context.addMessage("system", " (no deliveries)");
12609
- }
12610
- return;
12611
- }
12612
- if (sub === "retry") {
12613
- const deliveryId = rest[0];
12614
- if (!deliveryId) {
12615
- context.addMessage("system", "Usage: /webhook retry <delivery-id>");
12616
- return;
12617
- }
12618
- const result = await context.client.retryWebhookDelivery(deliveryId);
12619
- context.addMessage("system", `Retrying delivery: ${deliveryId} (${result.status ?? "queued"})`);
12620
- return;
12621
- }
12622
- context.addMessage("error", `Unknown /webhook subcommand '${sub}'. Try: list, add, rm, deliveries, retry.`);
12623
- } catch (e) {
12624
- context.addMessage("error", `Webhook: ${e.message}`);
12625
- }
12626
- }
12627
- };
12628
- }
12629
- });
12630
-
12631
- // src/commands/sync.ts
12632
- var syncCommand;
12633
- var init_sync = __esm({
12634
- "src/commands/sync.ts"() {
12860
+ });
12861
+
12862
+ // src/commands/mcp.ts
12863
+ var mcpCommand;
12864
+ var init_mcp = __esm({
12865
+ "src/commands/mcp.ts"() {
12635
12866
  "use strict";
12636
12867
  init_esm_shims();
12637
- init_src();
12638
- syncCommand = {
12639
- name: "sync",
12640
- description: "Manage offline message sync (status|push)",
12641
- args: "[status|push]",
12868
+ mcpCommand = {
12869
+ name: "mcp",
12870
+ args: "[connect|invoke|tools|disconnect] [...args]",
12871
+ description: "MCP client surface: connect, invoke, list tools, disconnect",
12642
12872
  category: "config",
12643
12873
  handler: async (args2, context) => {
12644
- const subcommand = (args2[0] || "status").toLowerCase();
12645
- const journal = getJournal();
12646
- switch (subcommand) {
12647
- case "status": {
12648
- const { pending, total } = await journal.journalCount();
12649
- context.addMessage("assistant", [
12650
- `Offline messages journal:`,
12651
- ` Total: ${total}`,
12652
- ` Pending sync: ${pending}`,
12653
- ` Synced: ${total - pending}`,
12654
- ...pending > 0 ? [``, `Run /sync push to sync pending messages to the backend.`] : []
12655
- ].join("\n"));
12656
- break;
12657
- }
12658
- case "push": {
12659
- const pending = await journal.journalListPending();
12660
- if (pending.length === 0) {
12661
- context.addMessage("assistant", "No pending messages to sync.");
12662
- break;
12874
+ const sub = (args2[0] ?? "tools").toLowerCase();
12875
+ const rest = args2.slice(1);
12876
+ try {
12877
+ if (sub === "connect") {
12878
+ const url = rest[0];
12879
+ if (!url) {
12880
+ context.addMessage("system", "Usage: /mcp connect <server-url>");
12881
+ return;
12663
12882
  }
12664
- context.addMessage("assistant", `Syncing ${pending.length} message(s)...`);
12665
- const byChat = /* @__PURE__ */ new Map();
12666
- for (const entry of pending) {
12667
- if (!byChat.has(entry.chat_id)) {
12668
- byChat.set(entry.chat_id, []);
12883
+ const result = await context.client.mcpConnect(url);
12884
+ context.addMessage("system", `MCP Connected: ${result.status ?? "ok"}`);
12885
+ return;
12886
+ }
12887
+ if (sub === "tools") {
12888
+ const tools = await context.client.mcpTools();
12889
+ context.addMessage("system", "Available MCP Tools:");
12890
+ if (Array.isArray(tools)) {
12891
+ for (const tool of tools) {
12892
+ context.addMessage("system", ` - ${tool.name}: ${tool.description ?? ""}`);
12669
12893
  }
12670
- byChat.get(entry.chat_id).push(entry);
12894
+ } else if (typeof tools === "object") {
12895
+ context.addMessage("system", JSON.stringify(tools, null, 2));
12671
12896
  }
12672
- let synced = 0;
12673
- let failed = 0;
12674
- for (const [chatId, entries] of byChat.entries()) {
12675
- for (let i = 0; i < entries.length; i += 50) {
12676
- const batch = entries.slice(i, i + 50);
12677
- try {
12678
- await context.client.importChat({
12679
- chat_id: chatId,
12680
- messages: batch.map((e) => ({
12681
- role: e.role,
12682
- content: e.content,
12683
- model: e.model
12684
- }))
12685
- });
12686
- await journal.journalMarkSynced(batch.map((e) => e.id));
12687
- synced += batch.length;
12688
- } catch (e) {
12689
- context.addMessage(
12690
- "error",
12691
- `Error syncing batch from chat ${chatId}: ${String(e).slice(0, 100)}`
12692
- );
12693
- failed += batch.length;
12694
- }
12695
- }
12897
+ return;
12898
+ }
12899
+ if (sub === "invoke") {
12900
+ const method = rest[0];
12901
+ if (!method) {
12902
+ context.addMessage("system", "Usage: /mcp invoke <method> [args...]");
12903
+ return;
12696
12904
  }
12697
- context.addMessage("assistant", `Sync complete: ${synced} synced, ${failed} failed`);
12698
- break;
12905
+ const argsStr = rest.slice(1).join(" ");
12906
+ let argsObj;
12907
+ try {
12908
+ argsObj = argsStr ? JSON.parse(argsStr) : void 0;
12909
+ } catch {
12910
+ argsObj = { raw: argsStr };
12911
+ }
12912
+ const result = await context.client.mcpInvoke(method, argsObj);
12913
+ context.addMessage("system", `MCP Result: ${JSON.stringify(result)}`);
12914
+ return;
12699
12915
  }
12700
- default:
12701
- context.addMessage("error", `Unknown sync subcommand: ${subcommand}
12702
- Usage: /sync [status|push]`);
12916
+ if (sub === "disconnect") {
12917
+ const result = await context.client.mcpDisconnect();
12918
+ context.addMessage("system", `MCP Disconnected: ${result.status ?? "ok"}`);
12919
+ return;
12920
+ }
12921
+ context.addMessage("error", `Unknown /mcp subcommand '${sub}'. Try: connect, tools, invoke, disconnect.`);
12922
+ } catch (e) {
12923
+ context.addMessage("error", `MCP: ${e.message}`);
12703
12924
  }
12704
12925
  }
12705
12926
  };
12706
12927
  }
12707
12928
  });
12708
12929
 
12709
- // ../core/src/diagnostics/specs.ts
12710
- import { cpus as cpus2, totalmem as totalmem2, freemem as freemem2, platform as platform3, arch as arch2 } from "os";
12711
- import { execSync } from "child_process";
12712
- async function collectSpecs() {
12713
- const cpuList = cpus2();
12714
- const totalMemBytes = totalmem2();
12715
- const freeMemBytes = freemem2();
12716
- const specs = {
12717
- cpu: {
12718
- cores: cpuList.length,
12719
- model: cpuList[0]?.model ?? "Unknown",
12720
- speed: cpuList[0]?.speed ?? 0
12721
- // MHz -> convert below
12722
- },
12723
- memory: {
12724
- totalGB: Math.round(totalMemBytes / 1024 ** 3 * 10) / 10,
12725
- freeGB: Math.round(freeMemBytes / 1024 ** 3 * 10) / 10
12726
- },
12727
- gpu: [],
12728
- disk: {
12729
- freeGB: 0,
12730
- totalGB: 0
12731
- },
12732
- platform: platform3(),
12733
- arch: arch2(),
12734
- nodeVersion: process.version.slice(1)
12735
- // Remove 'v' prefix
12736
- };
12737
- specs.cpu.speed = Math.round(specs.cpu.speed / 1e3 * 10) / 10;
12738
- try {
12739
- const { statfs } = await import("fs/promises");
12740
- const homeDir = process.env.HOME || process.env.USERPROFILE || "/";
12741
- const stat5 = await statfs(homeDir);
12742
- specs.disk.freeGB = Math.round(stat5.bavail * stat5.bsize / 1024 ** 3 * 10) / 10;
12743
- specs.disk.totalGB = Math.round(stat5.blocks * stat5.bsize / 1024 ** 3 * 10) / 10;
12744
- } catch {
12745
- specs.disk.freeGB = -1;
12746
- specs.disk.totalGB = -1;
12747
- }
12748
- specs.gpu = await detectGpu();
12749
- return specs;
12750
- }
12751
- async function detectGpu() {
12752
- const currentPlatform = platform3();
12753
- if (currentPlatform === "win32") {
12754
- return detectGpuWindows();
12755
- } else if (currentPlatform === "darwin") {
12756
- return detectGpuMacOS();
12757
- } else {
12758
- return detectGpuLinux();
12759
- }
12760
- }
12761
- function detectGpuWindows() {
12762
- try {
12763
- const output = execSync(
12764
- 'powershell -Command "Get-CimInstance Win32_VideoController | Select-Object Name, AdapterRAM"',
12765
- { timeout: 2e3, encoding: "utf-8" }
12766
- );
12767
- const gpus = [];
12768
- const lines = output.split("\n");
12769
- for (const line of lines) {
12770
- const parts = line.trim().split(/\s{2,}/);
12771
- if (parts[0] && parts[0] !== "Name") {
12772
- const memBytes = parseInt(parts[1] || "0");
12773
- gpus.push({
12774
- name: parts[0],
12775
- memoryGB: memBytes > 0 ? Math.round(memBytes / 1024 ** 3 * 10) / 10 : void 0
12776
- });
12777
- }
12778
- }
12779
- return gpus;
12780
- } catch {
12781
- return [];
12782
- }
12783
- }
12784
- function detectGpuMacOS() {
12785
- try {
12786
- const output = execSync("system_profiler SPDisplaysDataType -json", {
12787
- timeout: 2e3,
12788
- encoding: "utf-8"
12789
- });
12790
- const data = JSON.parse(output);
12791
- const gpus = [];
12792
- const displays = data.SPDisplaysDataType || [];
12793
- for (const display of displays) {
12794
- const chips = display["sppci_model_name"];
12795
- if (chips) {
12796
- gpus.push({ name: chips });
12797
- }
12798
- }
12799
- return gpus;
12800
- } catch {
12801
- return [];
12802
- }
12803
- }
12804
- function detectGpuLinux() {
12805
- const gpus = [];
12806
- try {
12807
- const output = execSync(
12808
- "nvidia-smi --query-gpu=name,memory.total --format=csv,noheader",
12809
- { timeout: 2e3, encoding: "utf-8" }
12810
- );
12811
- const lines = output.trim().split("\n");
12812
- for (const line of lines) {
12813
- const [name, mem] = line.split(",");
12814
- const memGB = mem ? Math.round(parseInt(mem) / 1024 * 10) / 10 : void 0;
12815
- gpus.push({ name: name.trim(), memoryGB: memGB });
12816
- }
12817
- return gpus;
12818
- } catch {
12819
- }
12820
- try {
12821
- const output = execSync("rocm-smi --showproductname", {
12822
- timeout: 2e3,
12823
- encoding: "utf-8"
12824
- });
12825
- const lines = output.trim().split("\n");
12826
- for (const line of lines) {
12827
- if (line.includes("GPU")) {
12828
- const match = line.match(/:\s*(.+)/);
12829
- if (match) gpus.push({ name: match[1].trim() });
12830
- }
12831
- }
12832
- return gpus;
12833
- } catch {
12834
- }
12835
- try {
12836
- const output = execSync("lspci | grep -i vga", {
12837
- timeout: 2e3,
12838
- encoding: "utf-8"
12839
- });
12840
- const lines = output.trim().split("\n");
12841
- for (const line of lines) {
12842
- const match = line.match(/:\s*(.+)/);
12843
- if (match) gpus.push({ name: match[1].trim() });
12844
- }
12845
- return gpus;
12846
- } catch {
12847
- return [];
12848
- }
12849
- }
12850
- var init_specs = __esm({
12851
- "../core/src/diagnostics/specs.ts"() {
12930
+ // src/commands/webhook.ts
12931
+ var webhookCommand;
12932
+ var init_webhook = __esm({
12933
+ "src/commands/webhook.ts"() {
12852
12934
  "use strict";
12853
12935
  init_esm_shims();
12936
+ webhookCommand = {
12937
+ name: "webhook",
12938
+ args: "[list|add|rm|deliveries|retry] [...args]",
12939
+ description: "Manage webhooks: create, list, delete, and view delivery logs",
12940
+ category: "project",
12941
+ handler: async (args2, context) => {
12942
+ const sub = (args2[0] ?? "list").toLowerCase();
12943
+ const rest = args2.slice(1);
12944
+ try {
12945
+ if (sub === "list") {
12946
+ const webhooks = await context.client.listWebhooks();
12947
+ context.addMessage("system", "Webhooks:");
12948
+ if (Array.isArray(webhooks) && webhooks.length > 0) {
12949
+ for (const wh of webhooks) {
12950
+ const id = wh.id ?? wh.webhook_id ?? "?";
12951
+ const url = wh.url ?? "?";
12952
+ const events = wh.events ? wh.events.join(", ") : "";
12953
+ context.addMessage("system", ` - ${id}: ${url} (${events})`);
12954
+ }
12955
+ } else {
12956
+ context.addMessage("system", " (no webhooks)");
12957
+ }
12958
+ return;
12959
+ }
12960
+ if (sub === "add") {
12961
+ const url = rest[0];
12962
+ const events = rest.slice(1);
12963
+ if (!url || events.length === 0) {
12964
+ context.addMessage("system", "Usage: /webhook add <url> <event1> [event2] ...");
12965
+ return;
12966
+ }
12967
+ const result = await context.client.createWebhook(url, events);
12968
+ const id = result.id ?? result.webhook_id ?? "?";
12969
+ context.addMessage("system", `Webhook created: ${id} \u2192 ${url}`);
12970
+ return;
12971
+ }
12972
+ if (sub === "rm" || sub === "delete") {
12973
+ const id = rest[0];
12974
+ if (!id) {
12975
+ context.addMessage("system", "Usage: /webhook rm <id>");
12976
+ return;
12977
+ }
12978
+ await context.client.deleteWebhook(id);
12979
+ context.addMessage("system", `Deleted webhook: ${id}`);
12980
+ return;
12981
+ }
12982
+ if (sub === "deliveries") {
12983
+ const webhookId = rest[0] || void 0;
12984
+ const deliveries = await context.client.listWebhookDeliveries(webhookId);
12985
+ const title = webhookId ? `Deliveries for webhook ${webhookId}:` : "Recent deliveries:";
12986
+ context.addMessage("system", title);
12987
+ if (Array.isArray(deliveries) && deliveries.length > 0) {
12988
+ for (const d of deliveries) {
12989
+ const id = d.id ?? d.delivery_id ?? "?";
12990
+ const status = d.status ?? "?";
12991
+ const at = d.delivered_at ?? d.created_at ?? "?";
12992
+ context.addMessage("system", ` - ${id}: ${status} (${at})`);
12993
+ }
12994
+ } else {
12995
+ context.addMessage("system", " (no deliveries)");
12996
+ }
12997
+ return;
12998
+ }
12999
+ if (sub === "retry") {
13000
+ const deliveryId = rest[0];
13001
+ if (!deliveryId) {
13002
+ context.addMessage("system", "Usage: /webhook retry <delivery-id>");
13003
+ return;
13004
+ }
13005
+ const result = await context.client.retryWebhookDelivery(deliveryId);
13006
+ context.addMessage("system", `Retrying delivery: ${deliveryId} (${result.status ?? "queued"})`);
13007
+ return;
13008
+ }
13009
+ context.addMessage("error", `Unknown /webhook subcommand '${sub}'. Try: list, add, rm, deliveries, retry.`);
13010
+ } catch (e) {
13011
+ context.addMessage("error", `Webhook: ${e.message}`);
13012
+ }
13013
+ }
13014
+ };
12854
13015
  }
12855
13016
  });
12856
13017
 
12857
- // ../core/src/governor/ResourceGovernor.ts
12858
- import { freemem as freemem3 } from "os";
12859
- import { homedir as homedir18 } from "os";
12860
- import { readFile as readFile23 } from "fs/promises";
12861
- import { join as join29 } from "path";
12862
- function determineTier(specs) {
12863
- const memGB = specs.memory.totalGB;
12864
- const cores = specs.cpu.cores;
12865
- if (memGB < 8 || cores < 4) return "T1";
12866
- if (memGB < 16 || cores < 8) return "T2";
12867
- if (memGB < 32 || cores < 16) return "T3";
12868
- return "T4";
12869
- }
12870
- function recommendLimits(specs) {
12871
- const tier = determineTier(specs);
12872
- return tierTable[tier];
12873
- }
12874
- async function readConfigOverrides() {
12875
- try {
12876
- const configPath = join29(homedir18(), ".msapling", "config.json");
12877
- const content = await readFile23(configPath, "utf-8");
12878
- const config = JSON.parse(content);
12879
- return config.limits ?? null;
12880
- } catch {
12881
- return null;
12882
- }
12883
- }
12884
- async function createResourceGovernor(specs) {
12885
- const recommended = recommendLimits(specs);
12886
- const overrides = await readConfigOverrides();
12887
- return new ResourceGovernor(recommended, overrides ?? void 0);
12888
- }
12889
- var tierTable, ResourceGovernor;
12890
- var init_ResourceGovernor = __esm({
12891
- "../core/src/governor/ResourceGovernor.ts"() {
13018
+ // src/commands/sync.ts
13019
+ var syncCommand;
13020
+ var init_sync = __esm({
13021
+ "src/commands/sync.ts"() {
12892
13022
  "use strict";
12893
13023
  init_esm_shims();
12894
- tierTable = {
12895
- T1: { maxAgents: 1, maxParallelTools: 1, maxFileWatchers: 50, localLlmTier: "3B" },
12896
- T2: { maxAgents: 2, maxParallelTools: 4, maxFileWatchers: 200, localLlmTier: "7B" },
12897
- T3: { maxAgents: 4, maxParallelTools: 8, maxFileWatchers: 500, localLlmTier: "13B" },
12898
- T4: { maxAgents: 8, maxParallelTools: 16, maxFileWatchers: 2e3, localLlmTier: "32B+" }
12899
- };
12900
- ResourceGovernor = class {
12901
- activeAgents = 0;
12902
- maxAgents;
12903
- activeTool = 0;
12904
- maxParallelTools;
12905
- maxFileWatchers;
12906
- memoryWarningShown = false;
12907
- memoryCheckInterval = null;
12908
- minFreeMemGB = 1.5;
12909
- constructor(limits, overrides) {
12910
- this.maxAgents = overrides?.maxAgents ?? limits.maxAgents;
12911
- this.maxParallelTools = overrides?.maxParallelTools ?? limits.maxParallelTools;
12912
- this.maxFileWatchers = overrides?.maxFileWatchers ?? limits.maxFileWatchers;
12913
- this.startMemoryMonitor();
12914
- }
12915
- /**
12916
- * Monitor free memory every 5s; block new acquires if below threshold
12917
- */
12918
- startMemoryMonitor() {
12919
- this.memoryCheckInterval = setInterval(() => {
12920
- const freeMemBytes = freemem3();
12921
- const freeMemGB = freeMemBytes / 1024 ** 3;
12922
- if (freeMemGB < this.minFreeMemGB && !this.memoryWarningShown) {
12923
- console.warn(
12924
- `[ResourceGovernor] Low memory: ${freeMemGB.toFixed(2)}GB free (threshold: ${this.minFreeMemGB}GB)`
12925
- );
12926
- this.memoryWarningShown = true;
12927
- } else if (freeMemGB >= this.minFreeMemGB) {
12928
- this.memoryWarningShown = false;
13024
+ init_src();
13025
+ syncCommand = {
13026
+ name: "sync",
13027
+ description: "Manage offline message sync (status|push)",
13028
+ args: "[status|push]",
13029
+ category: "config",
13030
+ handler: async (args2, context) => {
13031
+ const subcommand = (args2[0] || "status").toLowerCase();
13032
+ const journal = getJournal();
13033
+ switch (subcommand) {
13034
+ case "status": {
13035
+ const { pending, total } = await journal.journalCount();
13036
+ context.addMessage("assistant", [
13037
+ `Offline messages journal:`,
13038
+ ` Total: ${total}`,
13039
+ ` Pending sync: ${pending}`,
13040
+ ` Synced: ${total - pending}`,
13041
+ ...pending > 0 ? [``, `Run /sync push to sync pending messages to the backend.`] : []
13042
+ ].join("\n"));
13043
+ break;
12929
13044
  }
12930
- }, 5e3);
12931
- }
12932
- /**
12933
- * Attempt to acquire an agent slot
12934
- */
12935
- acquireAgent() {
12936
- const freeMemGB = freemem3() / 1024 ** 3;
12937
- if (freeMemGB < this.minFreeMemGB) return false;
12938
- if (this.activeAgents >= this.maxAgents) return false;
12939
- this.activeAgents++;
12940
- return true;
12941
- }
12942
- /**
12943
- * Release an agent slot
12944
- */
12945
- releaseAgent() {
12946
- this.activeAgents = Math.max(0, this.activeAgents - 1);
12947
- }
12948
- /**
12949
- * Attempt to acquire a tool execution slot
12950
- */
12951
- acquireTool() {
12952
- const freeMemGB = freemem3() / 1024 ** 3;
12953
- if (freeMemGB < this.minFreeMemGB) return false;
12954
- if (this.activeTool >= this.maxParallelTools) return false;
12955
- this.activeTool++;
12956
- return true;
12957
- }
12958
- /**
12959
- * Release a tool execution slot
12960
- */
12961
- releaseTool() {
12962
- this.activeTool = Math.max(0, this.activeTool - 1);
12963
- }
12964
- /**
12965
- * Get current active counts
12966
- */
12967
- getStatus() {
12968
- return {
12969
- activeAgents: this.activeAgents,
12970
- activeTool: this.activeTool,
12971
- maxAgents: this.maxAgents,
12972
- maxParallelTools: this.maxParallelTools
12973
- };
12974
- }
12975
- /**
12976
- * Clean up monitor interval
12977
- */
12978
- destroy() {
12979
- if (this.memoryCheckInterval) {
12980
- clearInterval(this.memoryCheckInterval);
12981
- this.memoryCheckInterval = null;
13045
+ case "push": {
13046
+ const pending = await journal.journalListPending();
13047
+ if (pending.length === 0) {
13048
+ context.addMessage("assistant", "No pending messages to sync.");
13049
+ break;
13050
+ }
13051
+ context.addMessage("assistant", `Syncing ${pending.length} message(s)...`);
13052
+ const byChat = /* @__PURE__ */ new Map();
13053
+ for (const entry of pending) {
13054
+ if (!byChat.has(entry.chat_id)) {
13055
+ byChat.set(entry.chat_id, []);
13056
+ }
13057
+ byChat.get(entry.chat_id).push(entry);
13058
+ }
13059
+ let synced = 0;
13060
+ let failed = 0;
13061
+ for (const [chatId, entries] of byChat.entries()) {
13062
+ for (let i = 0; i < entries.length; i += 50) {
13063
+ const batch = entries.slice(i, i + 50);
13064
+ try {
13065
+ await context.client.importChat({
13066
+ chat_id: chatId,
13067
+ messages: batch.map((e) => ({
13068
+ role: e.role,
13069
+ content: e.content,
13070
+ model: e.model
13071
+ }))
13072
+ });
13073
+ await journal.journalMarkSynced(batch.map((e) => e.id));
13074
+ synced += batch.length;
13075
+ } catch (e) {
13076
+ context.addMessage(
13077
+ "error",
13078
+ `Error syncing batch from chat ${chatId}: ${String(e).slice(0, 100)}`
13079
+ );
13080
+ failed += batch.length;
13081
+ }
13082
+ }
13083
+ }
13084
+ context.addMessage("assistant", `Sync complete: ${synced} synced, ${failed} failed`);
13085
+ break;
13086
+ }
13087
+ default:
13088
+ context.addMessage("error", `Unknown sync subcommand: ${subcommand}
13089
+ Usage: /sync [status|push]`);
12982
13090
  }
12983
13091
  }
12984
13092
  };
@@ -12992,9 +13100,8 @@ async function handler(args2, ctx) {
12992
13100
  const specs = await collectSpecs();
12993
13101
  const recommended = recommendLimits(specs);
12994
13102
  const tier = determineTier(specs);
12995
- const governor = await createResourceGovernor(specs);
13103
+ const governor = await getGlobalGovernor();
12996
13104
  const status = governor.getStatus();
12997
- governor.destroy();
12998
13105
  if (jsonMode) {
12999
13106
  const output = {
13000
13107
  specs,
@@ -13250,7 +13357,7 @@ var exec_exports = {};
13250
13357
  __export(exec_exports, {
13251
13358
  runExec: () => runExec
13252
13359
  });
13253
- import { existsSync as existsSync26 } from "fs";
13360
+ import { existsSync as existsSync27 } from "fs";
13254
13361
  import { readFile as readFile25 } from "fs/promises";
13255
13362
  import { homedir as homedir19 } from "os";
13256
13363
  import { join as join30 } from "path";
@@ -13258,7 +13365,7 @@ async function loadPersistedSettings() {
13258
13365
  const out = { mode: "default", theme: null };
13259
13366
  try {
13260
13367
  const p = join30(homedir19(), ".msapling", "settings.json");
13261
- if (!existsSync26(p)) return out;
13368
+ if (!existsSync27(p)) return out;
13262
13369
  const raw = JSON.parse(await readFile25(p, "utf8"));
13263
13370
  const parsed = parseApprovalMode(raw, Date.now());
13264
13371
  if (parsed.kind === "ok") out.mode = parsed.mode;
@@ -14136,7 +14243,7 @@ __export(doctor_exports, {
14136
14243
  });
14137
14244
  import { homedir as homedir20, platform as platform4 } from "os";
14138
14245
  import { join as join31 } from "path";
14139
- import { existsSync as existsSync27, statSync as statSync6, accessSync } from "fs";
14246
+ import { existsSync as existsSync28, statSync as statSync6, accessSync } from "fs";
14140
14247
  import { readdir as readdir3 } from "fs/promises";
14141
14248
  import { exec } from "child_process";
14142
14249
  import { promisify } from "util";
@@ -14160,7 +14267,7 @@ async function checkNodeVersion() {
14160
14267
  }
14161
14268
  async function checkConfigDir() {
14162
14269
  const configDir = join31(homedir20(), ".msapling");
14163
- if (!existsSync27(configDir)) {
14270
+ if (!existsSync28(configDir)) {
14164
14271
  return {
14165
14272
  name: "Config directory",
14166
14273
  status: "WARN",
@@ -14226,7 +14333,7 @@ async function checkPathConflicts() {
14226
14333
  const paths = pathEnv.split(platform4() === "win32" ? ";" : ":");
14227
14334
  const conflicts = [];
14228
14335
  for (const dir of paths) {
14229
- if (!dir || !existsSync27(dir)) continue;
14336
+ if (!dir || !existsSync28(dir)) continue;
14230
14337
  try {
14231
14338
  const files = await readdir3(dir);
14232
14339
  for (const file of files) {
@@ -15996,7 +16103,7 @@ __export(server_exports, {
15996
16103
  runStdio: () => runStdio,
15997
16104
  runStdioWithRegistry: () => runStdioWithRegistry
15998
16105
  });
15999
- import { readdirSync as readdirSync4, readFileSync as readFileSync2, statSync as statSync7 } from "fs";
16106
+ import { readdirSync as readdirSync4, readFileSync as readFileSync3, statSync as statSync7 } from "fs";
16000
16107
  import { join as join32, relative as relative14, resolve as resolve18 } from "path";
16001
16108
  function asResult2(text, isError = false) {
16002
16109
  return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
@@ -16041,7 +16148,7 @@ function readFilesAsContext(root, files, maxKB) {
16041
16148
  for (const f of files) {
16042
16149
  let body;
16043
16150
  try {
16044
- body = readFileSync2(f, "utf8");
16151
+ body = readFileSync3(f, "utf8");
16045
16152
  } catch {
16046
16153
  continue;
16047
16154
  }
@@ -16783,7 +16890,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
16783
16890
  var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
16784
16891
  /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
16785
16892
  "\u25CF MSapling CLI v",
16786
- "2.3.6-beta.30"
16893
+ "2.3.6-beta.31"
16787
16894
  ] }),
16788
16895
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
16789
16896
  ] });
@@ -17306,9 +17413,9 @@ ${prompt4}` : prompt4;
17306
17413
  for (const mention of fileMentions) {
17307
17414
  const filePath = mention.slice(1);
17308
17415
  try {
17309
- const { existsSync: existsSync28 } = await import("fs");
17416
+ const { existsSync: existsSync29 } = await import("fs");
17310
17417
  const { readFile: readFile26 } = await import("fs/promises");
17311
- if (existsSync28(filePath)) {
17418
+ if (existsSync29(filePath)) {
17312
17419
  const content = await readFile26(filePath, "utf8");
17313
17420
  const MAX_LEN = 32768;
17314
17421
  const truncated = content.length > MAX_LEN ? content.slice(0, MAX_LEN) + "\n...[TRUNCATED]" : content;
@@ -17362,7 +17469,7 @@ init_esm_shims();
17362
17469
  init_src3();
17363
17470
  init_parseApprovalMode();
17364
17471
  import { readFile as readFile24 } from "fs/promises";
17365
- import { existsSync as existsSync25 } from "fs";
17472
+ import { existsSync as existsSync26 } from "fs";
17366
17473
  async function initSession(ctx) {
17367
17474
  try {
17368
17475
  const { settings } = await loadSettings(
@@ -17379,7 +17486,7 @@ async function initSession(ctx) {
17379
17486
  const { homedir: homedir21 } = await import("os");
17380
17487
  const { join: join34 } = await import("path");
17381
17488
  const userSettingsPath = join34(homedir21(), ".msapling", "settings.json");
17382
- if (existsSync25(userSettingsPath)) {
17489
+ if (existsSync26(userSettingsPath)) {
17383
17490
  const userText = await readFile24(userSettingsPath, "utf8");
17384
17491
  let parsed;
17385
17492
  try {
@@ -17683,14 +17790,14 @@ var App = ({ compact: compact2 = false }) => {
17683
17790
 
17684
17791
  // src/runtime/bootstrap.ts
17685
17792
  init_esm_shims();
17686
- import { readFileSync as readFileSync3 } from "fs";
17793
+ import { readFileSync as readFileSync4 } from "fs";
17687
17794
  import { fileURLToPath as fileURLToPath2 } from "url";
17688
- import { dirname as dirname3, join as join33 } from "path";
17795
+ import { dirname as dirname4, join as join33 } from "path";
17689
17796
  function readCliVersion2() {
17690
- const here = dirname3(fileURLToPath2(import.meta.url));
17797
+ const here = dirname4(fileURLToPath2(import.meta.url));
17691
17798
  for (const rel of ["../package.json", "../../package.json"]) {
17692
17799
  try {
17693
- const pkg = JSON.parse(readFileSync3(join33(here, rel), "utf8"));
17800
+ const pkg = JSON.parse(readFileSync4(join33(here, rel), "utf8"));
17694
17801
  if (pkg.name && pkg.version) {
17695
17802
  return { name: pkg.name, version: pkg.version };
17696
17803
  }