terminal-commands 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/stdio.js CHANGED
@@ -410,11 +410,11 @@ var require_codegen = __commonJS({
410
410
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
411
411
  return `${varKind} ${this.name}${rhs};` + _n;
412
412
  }
413
- optimizeNames(names, constants2) {
413
+ optimizeNames(names, constants) {
414
414
  if (!names[this.name.str])
415
415
  return;
416
416
  if (this.rhs)
417
- this.rhs = optimizeExpr(this.rhs, names, constants2);
417
+ this.rhs = optimizeExpr(this.rhs, names, constants);
418
418
  return this;
419
419
  }
420
420
  get names() {
@@ -431,10 +431,10 @@ var require_codegen = __commonJS({
431
431
  render({ _n }) {
432
432
  return `${this.lhs} = ${this.rhs};` + _n;
433
433
  }
434
- optimizeNames(names, constants2) {
434
+ optimizeNames(names, constants) {
435
435
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
436
436
  return;
437
- this.rhs = optimizeExpr(this.rhs, names, constants2);
437
+ this.rhs = optimizeExpr(this.rhs, names, constants);
438
438
  return this;
439
439
  }
440
440
  get names() {
@@ -495,8 +495,8 @@ var require_codegen = __commonJS({
495
495
  optimizeNodes() {
496
496
  return `${this.code}` ? this : void 0;
497
497
  }
498
- optimizeNames(names, constants2) {
499
- this.code = optimizeExpr(this.code, names, constants2);
498
+ optimizeNames(names, constants) {
499
+ this.code = optimizeExpr(this.code, names, constants);
500
500
  return this;
501
501
  }
502
502
  get names() {
@@ -525,12 +525,12 @@ var require_codegen = __commonJS({
525
525
  }
526
526
  return nodes.length > 0 ? this : void 0;
527
527
  }
528
- optimizeNames(names, constants2) {
528
+ optimizeNames(names, constants) {
529
529
  const { nodes } = this;
530
530
  let i = nodes.length;
531
531
  while (i--) {
532
532
  const n = nodes[i];
533
- if (n.optimizeNames(names, constants2))
533
+ if (n.optimizeNames(names, constants))
534
534
  continue;
535
535
  subtractNames(names, n.names);
536
536
  nodes.splice(i, 1);
@@ -583,12 +583,12 @@ var require_codegen = __commonJS({
583
583
  return void 0;
584
584
  return this;
585
585
  }
586
- optimizeNames(names, constants2) {
586
+ optimizeNames(names, constants) {
587
587
  var _a3;
588
- this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants2);
589
- if (!(super.optimizeNames(names, constants2) || this.else))
588
+ this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants);
589
+ if (!(super.optimizeNames(names, constants) || this.else))
590
590
  return;
591
- this.condition = optimizeExpr(this.condition, names, constants2);
591
+ this.condition = optimizeExpr(this.condition, names, constants);
592
592
  return this;
593
593
  }
594
594
  get names() {
@@ -611,10 +611,10 @@ var require_codegen = __commonJS({
611
611
  render(opts) {
612
612
  return `for(${this.iteration})` + super.render(opts);
613
613
  }
614
- optimizeNames(names, constants2) {
615
- if (!super.optimizeNames(names, constants2))
614
+ optimizeNames(names, constants) {
615
+ if (!super.optimizeNames(names, constants))
616
616
  return;
617
- this.iteration = optimizeExpr(this.iteration, names, constants2);
617
+ this.iteration = optimizeExpr(this.iteration, names, constants);
618
618
  return this;
619
619
  }
620
620
  get names() {
@@ -650,10 +650,10 @@ var require_codegen = __commonJS({
650
650
  render(opts) {
651
651
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
652
652
  }
653
- optimizeNames(names, constants2) {
654
- if (!super.optimizeNames(names, constants2))
653
+ optimizeNames(names, constants) {
654
+ if (!super.optimizeNames(names, constants))
655
655
  return;
656
- this.iterable = optimizeExpr(this.iterable, names, constants2);
656
+ this.iterable = optimizeExpr(this.iterable, names, constants);
657
657
  return this;
658
658
  }
659
659
  get names() {
@@ -695,11 +695,11 @@ var require_codegen = __commonJS({
695
695
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
696
696
  return this;
697
697
  }
698
- optimizeNames(names, constants2) {
698
+ optimizeNames(names, constants) {
699
699
  var _a3, _b;
700
- super.optimizeNames(names, constants2);
701
- (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants2);
702
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants2);
700
+ super.optimizeNames(names, constants);
701
+ (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants);
702
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants);
703
703
  return this;
704
704
  }
705
705
  get names() {
@@ -1000,7 +1000,7 @@ var require_codegen = __commonJS({
1000
1000
  function addExprNames(names, from) {
1001
1001
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
1002
1002
  }
1003
- function optimizeExpr(expr, names, constants2) {
1003
+ function optimizeExpr(expr, names, constants) {
1004
1004
  if (expr instanceof code_1.Name)
1005
1005
  return replaceName(expr);
1006
1006
  if (!canOptimize(expr))
@@ -1015,14 +1015,14 @@ var require_codegen = __commonJS({
1015
1015
  return items;
1016
1016
  }, []));
1017
1017
  function replaceName(n) {
1018
- const c = constants2[n.str];
1018
+ const c = constants[n.str];
1019
1019
  if (c === void 0 || names[n.str] !== 1)
1020
1020
  return n;
1021
1021
  delete names[n.str];
1022
1022
  return c;
1023
1023
  }
1024
1024
  function canOptimize(e) {
1025
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
1025
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== void 0);
1026
1026
  }
1027
1027
  }
1028
1028
  function subtractNames(names, from) {
@@ -3262,8 +3262,8 @@ var require_utils = __commonJS({
3262
3262
  }
3263
3263
  return ind;
3264
3264
  }
3265
- function removeDotSegments(path2) {
3266
- let input2 = path2;
3265
+ function removeDotSegments(path4) {
3266
+ let input2 = path4;
3267
3267
  const output2 = [];
3268
3268
  let nextSlash = -1;
3269
3269
  let len = 0;
@@ -3672,8 +3672,8 @@ var require_schemes = __commonJS({
3672
3672
  }
3673
3673
  if (wsComponent.resourceName) {
3674
3674
  const queryIndex = wsComponent.resourceName.indexOf("?");
3675
- const path2 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3676
- wsComponent.path = path2 && path2 !== "/" ? path2 : void 0;
3675
+ const path4 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3676
+ wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
3677
3677
  wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
3678
3678
  wsComponent.resourceName = void 0;
3679
3679
  }
@@ -4148,13 +4148,14 @@ var require_fast_uri = __commonJS({
4148
4148
  if (!malformedIPLiteral) {
4149
4149
  malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
4150
4150
  }
4151
- if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4152
- if (uri.indexOf("%") !== -1) {
4153
- if (parsed.host !== void 0 && !malformedIPLiteral) {
4154
- const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4155
- parsed.host = reescapeHostDelimiters(host, isIP);
4156
- }
4151
+ if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
4152
+ let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4153
+ if (!isIP) {
4154
+ host = normalizePercentEncoding(host.toLowerCase());
4157
4155
  }
4156
+ parsed.host = reescapeHostDelimiters(host, isIP);
4157
+ }
4158
+ if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4158
4159
  if (parsed.path) {
4159
4160
  parsed.path = normalizePathEncoding(parsed.path);
4160
4161
  }
@@ -8017,10 +8018,10 @@ function mergeDefs(...defs) {
8017
8018
  function cloneDef(schema) {
8018
8019
  return mergeDefs(schema._zod.def);
8019
8020
  }
8020
- function getElementAtPath(obj, path2) {
8021
- if (!path2)
8021
+ function getElementAtPath(obj, path4) {
8022
+ if (!path4)
8022
8023
  return obj;
8023
- return path2.reduce((acc, key) => acc?.[key], obj);
8024
+ return path4.reduce((acc, key) => acc?.[key], obj);
8024
8025
  }
8025
8026
  function promiseAllObject(promisesObj) {
8026
8027
  const keys = Object.keys(promisesObj);
@@ -8360,11 +8361,11 @@ function explicitlyAborted(x, startIndex = 0) {
8360
8361
  }
8361
8362
  return false;
8362
8363
  }
8363
- function prefixIssues(path2, issues) {
8364
+ function prefixIssues(path4, issues) {
8364
8365
  return issues.map((iss) => {
8365
8366
  var _a3;
8366
8367
  (_a3 = iss).path ?? (_a3.path = []);
8367
- iss.path.unshift(path2);
8368
+ iss.path.unshift(path4);
8368
8369
  return iss;
8369
8370
  });
8370
8371
  }
@@ -8814,16 +8815,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
8814
8815
  }
8815
8816
  function formatError(error62, mapper = (issue2) => issue2.message) {
8816
8817
  const fieldErrors = { _errors: [] };
8817
- const processError = (error63, path2 = []) => {
8818
+ const processError = (error63, path4 = []) => {
8818
8819
  for (const issue2 of error63.issues) {
8819
8820
  if (issue2.code === "invalid_union" && issue2.errors.length) {
8820
- issue2.errors.map((issues) => processError({ issues }, [...path2, ...issue2.path]));
8821
+ issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
8821
8822
  } else if (issue2.code === "invalid_key") {
8822
- processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
8823
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
8823
8824
  } else if (issue2.code === "invalid_element") {
8824
- processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
8825
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
8825
8826
  } else {
8826
- const fullpath = [...path2, ...issue2.path];
8827
+ const fullpath = [...path4, ...issue2.path];
8827
8828
  if (fullpath.length === 0) {
8828
8829
  fieldErrors._errors.push(mapper(issue2));
8829
8830
  } else {
@@ -8862,17 +8863,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
8862
8863
  }
8863
8864
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
8864
8865
  const result = { errors: [] };
8865
- const processError = (error63, path2 = []) => {
8866
+ const processError = (error63, path4 = []) => {
8866
8867
  var _a3;
8867
8868
  for (const issue2 of error63.issues) {
8868
8869
  if (issue2.code === "invalid_union" && issue2.errors.length) {
8869
- issue2.errors.map((issues) => processError({ issues }, [...path2, ...issue2.path]));
8870
+ issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
8870
8871
  } else if (issue2.code === "invalid_key") {
8871
- processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
8872
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
8872
8873
  } else if (issue2.code === "invalid_element") {
8873
- processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
8874
+ processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
8874
8875
  } else {
8875
- const fullpath = [...path2, ...issue2.path];
8876
+ const fullpath = [...path4, ...issue2.path];
8876
8877
  if (fullpath.length === 0) {
8877
8878
  result.errors.push(mapper(issue2));
8878
8879
  continue;
@@ -8911,8 +8912,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
8911
8912
  }
8912
8913
  function toDotPath(_path) {
8913
8914
  const segs = [];
8914
- const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8915
- for (const seg of path2) {
8915
+ const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8916
+ for (const seg of path4) {
8916
8917
  if (typeof seg === "number")
8917
8918
  segs.push(`[${seg}]`);
8918
8919
  else if (typeof seg === "symbol")
@@ -26014,13 +26015,13 @@ function resolveRef(ref, ctx) {
26014
26015
  if (!ref.startsWith("#")) {
26015
26016
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
26016
26017
  }
26017
- const path2 = ref.slice(1).split("/").filter(Boolean);
26018
- if (path2.length === 0) {
26018
+ const path4 = ref.slice(1).split("/").filter(Boolean);
26019
+ if (path4.length === 0) {
26019
26020
  return ctx.rootSchema;
26020
26021
  }
26021
26022
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
26022
- if (path2[0] === defsKey) {
26023
- const key = path2[1] === void 0 ? void 0 : decodeJSONPointerSegment(path2[1]);
26023
+ if (path4[0] === defsKey) {
26024
+ const key = path4[1] === void 0 ? void 0 : decodeJSONPointerSegment(path4[1]);
26024
26025
  if (!key || !ctx.defs[key]) {
26025
26026
  throw new Error(`Reference not found: ${ref}`);
26026
26027
  }
@@ -28504,9 +28505,6 @@ var StdioServerTransport = class {
28504
28505
  }
28505
28506
  };
28506
28507
 
28507
- // src/server.ts
28508
- import { readdir, writeFile } from "node:fs/promises";
28509
-
28510
28508
  // node_modules/zod/v3/helpers/util.js
28511
28509
  var util;
28512
28510
  (function(util2) {
@@ -28881,8 +28879,8 @@ function getErrorMap2() {
28881
28879
 
28882
28880
  // node_modules/zod/v3/helpers/parseUtil.js
28883
28881
  var makeIssue = (params) => {
28884
- const { data, path: path2, errorMaps, issueData } = params;
28885
- const fullPath = [...path2, ...issueData.path || []];
28882
+ const { data, path: path4, errorMaps, issueData } = params;
28883
+ const fullPath = [...path4, ...issueData.path || []];
28886
28884
  const fullIssue = {
28887
28885
  ...issueData,
28888
28886
  path: fullPath
@@ -28997,11 +28995,11 @@ var errorUtil;
28997
28995
 
28998
28996
  // node_modules/zod/v3/types.js
28999
28997
  var ParseInputLazyPath = class {
29000
- constructor(parent, value, path2, key) {
28998
+ constructor(parent, value, path4, key) {
29001
28999
  this._cachedPath = [];
29002
29000
  this.parent = parent;
29003
29001
  this.data = value;
29004
- this._path = path2;
29002
+ this._path = path4;
29005
29003
  this._key = key;
29006
29004
  }
29007
29005
  get path() {
@@ -32552,11 +32550,11 @@ function normalizeObjectSchema(schema) {
32552
32550
  }
32553
32551
  return void 0;
32554
32552
  }
32555
- function getDotPath(path2) {
32556
- if (path2.length === 0) {
32553
+ function getDotPath(path4) {
32554
+ if (path4.length === 0) {
32557
32555
  return "object root";
32558
32556
  }
32559
- return path2.reduce((acc, seg, index) => {
32557
+ return path4.reduce((acc, seg, index) => {
32560
32558
  if (index === 0) {
32561
32559
  return String(seg);
32562
32560
  }
@@ -34000,6 +33998,9 @@ var Protocol = class {
34000
33998
  this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
34001
33999
  const handleTaskResult = async () => {
34002
34000
  const taskId = request.params.taskId;
34001
+ if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
34002
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
34003
+ }
34003
34004
  if (this._taskMessageQueue) {
34004
34005
  let queuedMessage;
34005
34006
  while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
@@ -34030,12 +34031,12 @@ var Protocol = class {
34030
34031
  throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
34031
34032
  }
34032
34033
  if (!isTerminal(task.status)) {
34033
- await this._waitForTaskUpdate(taskId, extra.signal);
34034
+ await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
34034
34035
  return await handleTaskResult();
34035
34036
  }
34036
34037
  if (isTerminal(task.status)) {
34037
34038
  const result = await this._taskStore.getTaskResult(taskId, extra.sessionId);
34038
- this._clearTaskQueue(taskId);
34039
+ this._clearTaskQueue(taskId, extra.sessionId);
34039
34040
  return {
34040
34041
  ...result,
34041
34042
  _meta: {
@@ -34072,7 +34073,7 @@ var Protocol = class {
34072
34073
  throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
34073
34074
  }
34074
34075
  await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
34075
- this._clearTaskQueue(request.params.taskId);
34076
+ this._clearTaskQueue(request.params.taskId, extra.sessionId);
34076
34077
  const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
34077
34078
  if (!cancelledTask) {
34078
34079
  throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
@@ -34200,6 +34201,19 @@ var Protocol = class {
34200
34201
  const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
34201
34202
  const capturedTransport = this._transport;
34202
34203
  const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
34204
+ const sessionId = capturedTransport?.sessionId;
34205
+ const store = this._taskStore;
34206
+ let relatedTaskFound = true;
34207
+ let relatedTaskLookup;
34208
+ if (relatedTaskId && store && this._taskMessageQueue && sessionId !== void 0) {
34209
+ relatedTaskFound = false;
34210
+ relatedTaskLookup = (async () => {
34211
+ if (!await store.getTask(relatedTaskId, sessionId)) {
34212
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
34213
+ }
34214
+ relatedTaskFound = true;
34215
+ })();
34216
+ }
34203
34217
  if (handler === void 0) {
34204
34218
  const errorResponse = {
34205
34219
  jsonrpc: "2.0",
@@ -34209,7 +34223,10 @@ var Protocol = class {
34209
34223
  message: "Method not found"
34210
34224
  }
34211
34225
  };
34212
- if (relatedTaskId && this._taskMessageQueue) {
34226
+ if (relatedTaskId && relatedTaskLookup) {
34227
+ const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
34228
+ relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error62) => this._onerror(new Error(`Failed to send an error response: ${error62}`)));
34229
+ } else if (relatedTaskId && this._taskMessageQueue) {
34213
34230
  this._enqueueTaskMessage(relatedTaskId, {
34214
34231
  type: "error",
34215
34232
  message: errorResponse,
@@ -34260,7 +34277,10 @@ var Protocol = class {
34260
34277
  closeSSEStream: extra?.closeSSEStream,
34261
34278
  closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
34262
34279
  };
34263
- Promise.resolve().then(() => {
34280
+ (relatedTaskLookup ?? Promise.resolve()).then(() => {
34281
+ if (relatedTaskLookup && abortController.signal.aborted) {
34282
+ throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
34283
+ }
34264
34284
  if (taskCreationParams) {
34265
34285
  this.assertTaskHandlerCapability(request.method);
34266
34286
  }
@@ -34295,7 +34315,7 @@ var Protocol = class {
34295
34315
  ...error62["data"] !== void 0 && { data: error62["data"] }
34296
34316
  }
34297
34317
  };
34298
- if (relatedTaskId && this._taskMessageQueue) {
34318
+ if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
34299
34319
  await this._enqueueTaskMessage(relatedTaskId, {
34300
34320
  type: "error",
34301
34321
  message: errorResponse,
@@ -34775,7 +34795,7 @@ var Protocol = class {
34775
34795
  throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
34776
34796
  }
34777
34797
  const maxQueueSize = this._options?.maxTaskQueueSize;
34778
- await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
34798
+ await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
34779
34799
  }
34780
34800
  /**
34781
34801
  * Clears the message queue for a task and rejects any pending request resolvers.
@@ -34804,12 +34824,13 @@ var Protocol = class {
34804
34824
  * Uses polling to check for updates at the task's configured poll interval.
34805
34825
  * @param taskId The task ID to wait for
34806
34826
  * @param signal Abort signal to cancel the wait
34827
+ * @param sessionId Session of the request that waits, passed to the task store
34807
34828
  * @returns Promise that resolves when an update occurs or rejects if aborted
34808
34829
  */
34809
- async _waitForTaskUpdate(taskId, signal) {
34830
+ async _waitForTaskUpdate(taskId, signal, sessionId) {
34810
34831
  let interval = this._options?.defaultTaskPollInterval ?? 1e3;
34811
34832
  try {
34812
- const task = await this._taskStore?.getTask(taskId);
34833
+ const task = await this._taskStore?.getTask(taskId, sessionId);
34813
34834
  if (task?.pollInterval) {
34814
34835
  interval = task.pollInterval;
34815
34836
  }
@@ -35688,6 +35709,42 @@ var ExperimentalMcpServerTasks = class {
35688
35709
  };
35689
35710
 
35690
35711
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
35712
+ function toolInputElementCount(value, max) {
35713
+ let count = 0;
35714
+ const stack = [value];
35715
+ while (stack.length > 0) {
35716
+ const node2 = stack.pop();
35717
+ if (node2 === null || typeof node2 !== "object")
35718
+ continue;
35719
+ if (Array.isArray(node2)) {
35720
+ for (const child of node2) {
35721
+ if (++count > max)
35722
+ return count;
35723
+ if (child !== null && typeof child === "object")
35724
+ stack.push(child);
35725
+ }
35726
+ } else {
35727
+ for (const key in node2) {
35728
+ if (!Object.prototype.hasOwnProperty.call(node2, key))
35729
+ continue;
35730
+ if (++count > max)
35731
+ return count;
35732
+ const child = node2[key];
35733
+ if (child !== null && typeof child === "object")
35734
+ stack.push(child);
35735
+ }
35736
+ }
35737
+ }
35738
+ return count;
35739
+ }
35740
+ function resolveMaxToolInputElements(value) {
35741
+ if (value === void 0 || value === Infinity)
35742
+ return void 0;
35743
+ if (typeof value !== "number" || Number.isNaN(value) || value < 1) {
35744
+ throw new RangeError(`maxToolInputElements must be a number of at least 1, or Infinity, got ${String(value)}`);
35745
+ }
35746
+ return value;
35747
+ }
35691
35748
  var McpServer = class {
35692
35749
  constructor(serverInfo, options) {
35693
35750
  this._registeredResources = {};
@@ -35699,6 +35756,7 @@ var McpServer = class {
35699
35756
  this._resourceHandlersInitialized = false;
35700
35757
  this._promptHandlersInitialized = false;
35701
35758
  this.server = new Server(serverInfo, options);
35759
+ this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
35702
35760
  }
35703
35761
  /**
35704
35762
  * Access experimental features.
@@ -35829,12 +35887,15 @@ var McpServer = class {
35829
35887
  * Validates tool input arguments against the tool's input schema.
35830
35888
  */
35831
35889
  async validateToolInput(tool, args, toolName) {
35890
+ if (this._maxToolInputElements !== void 0 && toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements) {
35891
+ throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`);
35892
+ }
35832
35893
  if (!tool.inputSchema) {
35833
35894
  return void 0;
35834
35895
  }
35835
35896
  const inputObj = normalizeObjectSchema(tool.inputSchema);
35836
35897
  const schemaToParse = inputObj ?? tool.inputSchema;
35837
- const parseResult = await safeParseAsync3(schemaToParse, args);
35898
+ const parseResult = await safeParseAsync3(schemaToParse, args ?? {});
35838
35899
  if (!parseResult.success) {
35839
35900
  const error62 = "error" in parseResult ? parseResult.error : "Unknown error";
35840
35901
  const errorMessage = getParseErrorMessage(error62);
@@ -36072,7 +36133,7 @@ var McpServer = class {
36072
36133
  }
36073
36134
  if (prompt.argsSchema) {
36074
36135
  const argsObj = normalizeObjectSchema(prompt.argsSchema);
36075
- const parseResult = await safeParseAsync3(argsObj, request.params.arguments);
36136
+ const parseResult = await safeParseAsync3(argsObj, request.params.arguments ?? {});
36076
36137
  if (!parseResult.success) {
36077
36138
  const error62 = "error" in parseResult ? parseResult.error : "Unknown error";
36078
36139
  const errorMessage = getParseErrorMessage(error62);
@@ -36479,35 +36540,28 @@ var EMPTY_COMPLETION_RESULT = {
36479
36540
  }
36480
36541
  };
36481
36542
 
36482
- // src/file-reader.ts
36483
- import { open as open2 } from "node:fs/promises";
36484
- async function readBoundedTextFile(filename, maxCharacters) {
36485
- const handle = await open2(filename, "r");
36486
- try {
36487
- const stats = await handle.stat();
36488
- const buffer = Buffer.allocUnsafe(maxCharacters * 4 + 4);
36489
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
36490
- const hasUnreadBytes = bytesRead < stats.size;
36491
- const decoded = new TextDecoder().decode(buffer.subarray(0, bytesRead), {
36492
- stream: hasUnreadBytes
36493
- });
36494
- return {
36495
- contents: decoded.slice(0, maxCharacters),
36496
- truncated: hasUnreadBytes || decoded.length > maxCharacters
36497
- };
36498
- } finally {
36499
- await handle.close();
36500
- }
36501
- }
36502
-
36503
36543
  // src/process-manager.ts
36504
36544
  import { randomUUID } from "node:crypto";
36545
+ import {
36546
+ closeSync,
36547
+ fsyncSync,
36548
+ mkdirSync,
36549
+ openSync,
36550
+ readFileSync,
36551
+ renameSync,
36552
+ rmSync,
36553
+ writeFileSync
36554
+ } from "node:fs";
36555
+ import path3 from "node:path";
36556
+ import { spawn as spawn2 } from "node:child_process";
36557
+
36558
+ // src/rooted-filesystem.ts
36505
36559
  import { spawn } from "node:child_process";
36560
+ import { realpathSync, statSync } from "node:fs";
36561
+ import path2 from "node:path";
36562
+ import { fileURLToPath } from "node:url";
36506
36563
 
36507
36564
  // src/security.ts
36508
- import { constants } from "node:fs";
36509
- import { access, lstat, realpath } from "node:fs/promises";
36510
- import os from "node:os";
36511
36565
  import path from "node:path";
36512
36566
  var DEFAULT_ENVIRONMENT_KEYS = [
36513
36567
  "LANG",
@@ -36540,34 +36594,7 @@ function configuredRoots(raw = process.env.MACHINE_TERMINAL_ROOTS) {
36540
36594
  }
36541
36595
  function isWithinRoot(candidate, root) {
36542
36596
  const relative = path.relative(root, candidate);
36543
- return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== "..";
36544
- }
36545
- async function resolveReadablePath(requestedPath, roots2, cwd = roots2[0] ?? os.homedir()) {
36546
- const resolvedRoots = await Promise.all(roots2.map((root) => realpath(root)));
36547
- const candidate = await realpath(path.resolve(cwd, requestedPath));
36548
- if (!resolvedRoots.some((root) => isWithinRoot(candidate, root))) {
36549
- throw new Error("Path is outside MACHINE_TERMINAL_ROOTS.");
36550
- }
36551
- await access(candidate, constants.R_OK);
36552
- return candidate;
36553
- }
36554
- async function resolveWritablePath(requestedPath, roots2, cwd = roots2[0] ?? os.homedir()) {
36555
- const absolute = path.resolve(cwd, requestedPath);
36556
- const resolvedParent = await realpath(path.dirname(absolute));
36557
- const resolvedRoots = await Promise.all(roots2.map((root) => realpath(root)));
36558
- const candidate = path.join(resolvedParent, path.basename(absolute));
36559
- if (!resolvedRoots.some((root) => isWithinRoot(candidate, root))) {
36560
- throw new Error("Path is outside MACHINE_TERMINAL_ROOTS.");
36561
- }
36562
- try {
36563
- const stats = await lstat(candidate);
36564
- if (stats.isSymbolicLink()) {
36565
- throw new Error("Writing through symbolic links is not allowed.");
36566
- }
36567
- } catch (error62) {
36568
- if (error62.code !== "ENOENT") throw error62;
36569
- }
36570
- return candidate;
36597
+ return relative === "" || !path.isAbsolute(relative) && !relative.startsWith(`..${path.sep}`) && relative !== "..";
36571
36598
  }
36572
36599
  function safeEnvironment(source = process.env) {
36573
36600
  return Object.fromEntries(
@@ -36592,7 +36619,200 @@ function assertProgramAllowed(program) {
36592
36619
  }
36593
36620
  }
36594
36621
 
36622
+ // src/rooted-filesystem.ts
36623
+ var filesystemHelperPath = fileURLToPath(
36624
+ new URL(
36625
+ `../dist/native/${process.platform}-${process.arch}/fs-helper${process.platform === "win32" ? ".exe" : ""}`,
36626
+ import.meta.url
36627
+ )
36628
+ );
36629
+ var RootedFilesystem = class {
36630
+ roots;
36631
+ #specs;
36632
+ #aliases;
36633
+ #child;
36634
+ #pending = /* @__PURE__ */ new Map();
36635
+ #nextID = 0;
36636
+ #buffer = "";
36637
+ #failure;
36638
+ #initialized = false;
36639
+ ready;
36640
+ constructor(roots2) {
36641
+ if (!roots2.length || roots2.length > 128)
36642
+ throw new Error("ROOTED_FILESYSTEM_ROOT_LIMIT");
36643
+ this.#aliases = roots2.map((root) => path2.resolve(root));
36644
+ this.#specs = roots2.map((root) => {
36645
+ const name = realpathSync(root);
36646
+ const info = statSync(name, { bigint: true });
36647
+ if (!info.isDirectory())
36648
+ throw new Error("ROOTED_FILESYSTEM_EXPECTED_DIRECTORY");
36649
+ return { path: name, dev: info.dev.toString(), ino: info.ino.toString() };
36650
+ });
36651
+ this.roots = this.#specs.map((root) => root.path);
36652
+ this.#child = spawn(filesystemHelperPath, [], {
36653
+ stdio: ["pipe", "pipe", "pipe"],
36654
+ windowsHide: true,
36655
+ env: safeEnvironment()
36656
+ });
36657
+ this.#child.stderr.resume();
36658
+ this.#child.on(
36659
+ "error",
36660
+ () => this.#fail(new Error("ROOTED_FILESYSTEM_HELPER_UNAVAILABLE"))
36661
+ );
36662
+ this.#child.on(
36663
+ "close",
36664
+ () => this.#fail(new Error("ROOTED_FILESYSTEM_HELPER_CLOSED"))
36665
+ );
36666
+ this.#child.stdin.on(
36667
+ "error",
36668
+ () => this.#fail(new Error("ROOTED_FILESYSTEM_HELPER_CLOSED"))
36669
+ );
36670
+ this.#child.stdout.on(
36671
+ "error",
36672
+ () => this.#fail(new Error("ROOTED_FILESYSTEM_HELPER_CLOSED"))
36673
+ );
36674
+ this.#child.stderr.on(
36675
+ "error",
36676
+ () => this.#fail(new Error("ROOTED_FILESYSTEM_HELPER_CLOSED"))
36677
+ );
36678
+ this.#child.stdout.setEncoding("utf8");
36679
+ this.#child.stdout.on("data", (data) => {
36680
+ this.#buffer += data;
36681
+ if (Buffer.byteLength(this.#buffer) > 4 * 1024 * 1024) {
36682
+ this.#fail(new Error("ROOTED_FILESYSTEM_REPLY_LIMIT"));
36683
+ return;
36684
+ }
36685
+ let newline;
36686
+ while ((newline = this.#buffer.indexOf("\n")) !== -1) {
36687
+ const line = this.#buffer.slice(0, newline);
36688
+ this.#buffer = this.#buffer.slice(newline + 1);
36689
+ try {
36690
+ const reply = JSON.parse(line);
36691
+ const waiting = this.#pending.get(reply.id);
36692
+ if (!waiting) continue;
36693
+ this.#pending.delete(reply.id);
36694
+ clearTimeout(waiting.timer);
36695
+ if (reply.error) waiting.reject(new Error(reply.error));
36696
+ else waiting.resolve(reply);
36697
+ } catch {
36698
+ this.#fail(new Error("ROOTED_FILESYSTEM_INVALID_REPLY"));
36699
+ return;
36700
+ }
36701
+ }
36702
+ this.#referenceWork();
36703
+ });
36704
+ this.ready = this.#request("init", { roots: this.#specs }).then(() => {
36705
+ this.#initialized = true;
36706
+ });
36707
+ void this.ready.catch(() => void 0);
36708
+ }
36709
+ #referenceWork() {
36710
+ const method = this.#pending.size ? "ref" : "unref";
36711
+ this.#child[method]();
36712
+ for (const stream of [
36713
+ this.#child.stdin,
36714
+ this.#child.stdout,
36715
+ this.#child.stderr
36716
+ ])
36717
+ stream[method]();
36718
+ }
36719
+ #fail(error62) {
36720
+ this.#failure ??= error62;
36721
+ for (const waiting of this.#pending.values()) {
36722
+ clearTimeout(waiting.timer);
36723
+ waiting.reject(this.#failure);
36724
+ }
36725
+ this.#pending.clear();
36726
+ this.#child.kill();
36727
+ this.#referenceWork();
36728
+ }
36729
+ #request(operation, values) {
36730
+ if (this.#failure) return Promise.reject(this.#failure);
36731
+ if (this.#pending.size >= 16)
36732
+ return Promise.reject(new Error("ROOTED_FILESYSTEM_PENDING_LIMIT"));
36733
+ const id = ++this.#nextID;
36734
+ const message = JSON.stringify({ id, operation, ...values }) + "\n";
36735
+ if (Buffer.byteLength(message) >= 4 * 1024 * 1024)
36736
+ return Promise.reject(new Error("ROOTED_FILESYSTEM_INPUT_LIMIT"));
36737
+ return new Promise((resolve, reject) => {
36738
+ const timer = setTimeout(
36739
+ () => this.#fail(new Error("ROOTED_FILESYSTEM_TIMEOUT")),
36740
+ 1e4
36741
+ );
36742
+ this.#pending.set(id, { resolve, reject, timer });
36743
+ this.#referenceWork();
36744
+ this.#child.stdin.write(message);
36745
+ });
36746
+ }
36747
+ resolve(requestedPath) {
36748
+ const candidate = path2.resolve(this.#aliases[0], requestedPath);
36749
+ const index = this.roots.findIndex(
36750
+ (root, i) => isWithinRoot(candidate, root) || isWithinRoot(candidate, this.#aliases[i])
36751
+ );
36752
+ if (index === -1)
36753
+ throw new Error("Path is outside MACHINE_TERMINAL_ROOTS.");
36754
+ const base = isWithinRoot(candidate, this.roots[index]) ? this.roots[index] : this.#aliases[index];
36755
+ const relative = path2.relative(base, candidate) || ".";
36756
+ if (relative.length > 4096) throw new Error("ROOTED_FILESYSTEM_PATH_LIMIT");
36757
+ return {
36758
+ root: index,
36759
+ path: relative,
36760
+ displayPath: path2.join(this.roots[index], relative)
36761
+ };
36762
+ }
36763
+ async read(target, maxCharacters) {
36764
+ await this.ready;
36765
+ const reply = await this.#request("read", { ...target, maxCharacters });
36766
+ const decoded = new TextDecoder().decode(
36767
+ Buffer.from(reply.bytes ?? "", "base64"),
36768
+ { stream: reply.hasUnread }
36769
+ );
36770
+ return {
36771
+ contents: decoded.slice(0, maxCharacters),
36772
+ truncated: reply.hasUnread || decoded.length > maxCharacters
36773
+ };
36774
+ }
36775
+ async write(target, contents, overwrite) {
36776
+ await this.ready;
36777
+ return (await this.#request("write", { ...target, contents, overwrite })).bytesWritten;
36778
+ }
36779
+ async list(target) {
36780
+ await this.ready;
36781
+ const reply = await this.#request("list", { ...target });
36782
+ return { entries: reply.entries ?? [], truncated: reply.truncated };
36783
+ }
36784
+ async execution(requestedPath, program, args) {
36785
+ await this.ready;
36786
+ const target = this.resolve(requestedPath);
36787
+ return {
36788
+ roots: [this.#specs[target.root]],
36789
+ path: target.path,
36790
+ program,
36791
+ args
36792
+ };
36793
+ }
36794
+ async close() {
36795
+ if (this.#child.exitCode !== null || this.#child.signalCode !== null)
36796
+ return;
36797
+ const exited = new Promise(
36798
+ (resolve) => this.#child.once("close", () => resolve())
36799
+ );
36800
+ this.#fail(new Error("ROOTED_FILESYSTEM_CLOSED"));
36801
+ await exited;
36802
+ }
36803
+ get available() {
36804
+ return this.#initialized && !this.#failure;
36805
+ }
36806
+ };
36807
+
36595
36808
  // src/process-manager.ts
36809
+ var MAX_EVENT_CHARACTERS = 4096;
36810
+ var MAX_RETAINED_EVENTS = 128;
36811
+ var MAX_RETAINED_EVENT_CHARACTERS = 64e3;
36812
+ var LOCAL_SESSION_RETENTION_MS = 60 * 6e4;
36813
+ var MAX_TASK_TOMBSTONES = 2e4;
36814
+ var TASK_RECEIPT_RETENTION_MS = 30 * 24 * 60 * 6e4;
36815
+ var TERMINATION_GRACE_MS = 1500;
36596
36816
  function appendBounded(current, value, limit) {
36597
36817
  if (current.length >= limit) return current;
36598
36818
  return `${current}${value.toString("utf8")}`.slice(0, limit);
@@ -36600,6 +36820,7 @@ function appendBounded(current, value, limit) {
36600
36820
  function snapshot(session) {
36601
36821
  return {
36602
36822
  sessionId: session.sessionId,
36823
+ ...session.taskId ? { taskId: session.taskId } : {},
36603
36824
  program: session.program,
36604
36825
  args: session.args,
36605
36826
  cwd: session.cwd,
@@ -36613,17 +36834,312 @@ function snapshot(session) {
36613
36834
  };
36614
36835
  }
36615
36836
  var ProcessManager = class {
36837
+ constructor(limits = {
36838
+ maxRunning: 32,
36839
+ maxRetained: 512
36840
+ }, receiptFile = process.env.MACHINE_TERMINAL_TASK_RECEIPTS_FILE) {
36841
+ this.limits = limits;
36842
+ this.receiptFile = receiptFile;
36843
+ if (!Number.isInteger(limits.maxRunning) || limits.maxRunning < 1 || !Number.isInteger(limits.maxRetained) || limits.maxRetained < limits.maxRunning) {
36844
+ throw new Error("INVALID_PROCESS_LIMITS: Invalid local process limits.");
36845
+ }
36846
+ this.#loadReceipts();
36847
+ }
36848
+ limits;
36849
+ receiptFile;
36616
36850
  #sessions = /* @__PURE__ */ new Map();
36851
+ #taskSessions = /* @__PURE__ */ new Map();
36852
+ #taskReceipts = /* @__PURE__ */ new Map();
36853
+ #loadReceipts() {
36854
+ if (!this.receiptFile) return;
36855
+ let raw;
36856
+ try {
36857
+ raw = readFileSync(this.receiptFile, "utf8");
36858
+ } catch (error62) {
36859
+ if (error62.code === "ENOENT") return;
36860
+ throw error62;
36861
+ }
36862
+ const parsed = JSON.parse(raw);
36863
+ if (parsed.version !== 1 || !Array.isArray(parsed.receipts)) {
36864
+ throw new Error(
36865
+ "INVALID_TASK_RECEIPTS: Refusing to drop existing replay receipts."
36866
+ );
36867
+ }
36868
+ for (const receipt of parsed.receipts) {
36869
+ if (typeof receipt.taskId !== "string" || !Number.isSafeInteger(receipt.createdAt)) {
36870
+ throw new Error("INVALID_TASK_RECEIPTS: Corrupt task-receipt record.");
36871
+ }
36872
+ this.#taskReceipts.set(receipt.taskId, receipt.createdAt);
36873
+ }
36874
+ this.#pruneReceipts();
36875
+ }
36876
+ #persistReceipts() {
36877
+ if (!this.receiptFile) return;
36878
+ mkdirSync(path3.dirname(this.receiptFile), { recursive: true, mode: 448 });
36879
+ const temporary = this.receiptFile + "." + process.pid + "." + randomUUID() + ".tmp";
36880
+ try {
36881
+ writeFileSync(
36882
+ temporary,
36883
+ JSON.stringify({
36884
+ version: 1,
36885
+ receipts: [...this.#taskReceipts].map(([taskId, createdAt]) => ({
36886
+ taskId,
36887
+ createdAt
36888
+ }))
36889
+ }),
36890
+ { mode: 384, flag: "wx", flush: true }
36891
+ );
36892
+ renameSync(temporary, this.receiptFile);
36893
+ if (process.platform !== "win32") {
36894
+ const directory = openSync(path3.dirname(this.receiptFile), "r");
36895
+ try {
36896
+ fsyncSync(directory);
36897
+ } finally {
36898
+ closeSync(directory);
36899
+ }
36900
+ }
36901
+ } finally {
36902
+ rmSync(temporary, { force: true });
36903
+ }
36904
+ }
36905
+ #pruneReceipts() {
36906
+ const cutoff = Date.now() - TASK_RECEIPT_RETENTION_MS;
36907
+ let dirty = false;
36908
+ for (const [taskId, createdAt] of this.#taskReceipts) {
36909
+ if (createdAt < cutoff && !this.#taskSessions.has(taskId)) {
36910
+ this.#taskReceipts.delete(taskId);
36911
+ dirty = true;
36912
+ }
36913
+ }
36914
+ if (dirty) this.#persistReceipts();
36915
+ }
36916
+ #recordTaskReceipt(taskId) {
36917
+ this.#taskReceipts.set(taskId, Date.now());
36918
+ try {
36919
+ this.#persistReceipts();
36920
+ } catch (error62) {
36921
+ this.#taskReceipts.delete(taskId);
36922
+ throw error62;
36923
+ }
36924
+ }
36925
+ #signalProcessTree(session, signal) {
36926
+ const pid = session.child.pid;
36927
+ if (!pid || pid < 1) return;
36928
+ if (process.platform === "win32") {
36929
+ const killer = spawn2("taskkill.exe", ["/PID", String(pid), "/T", "/F"], {
36930
+ windowsHide: true,
36931
+ stdio: "ignore"
36932
+ });
36933
+ killer.on("error", () => session.child.kill("SIGKILL"));
36934
+ killer.on("close", (code) => {
36935
+ if (code !== 0 && session.running) session.child.kill("SIGKILL");
36936
+ });
36937
+ return;
36938
+ }
36939
+ try {
36940
+ process.kill(-pid, signal);
36941
+ return;
36942
+ } catch (error62) {
36943
+ if (error62.code === "ESRCH") return;
36944
+ }
36945
+ session.child.kill(signal);
36946
+ }
36947
+ #unixProcessGroupExists(session) {
36948
+ if (process.platform === "win32") return false;
36949
+ const pid = session.child.pid;
36950
+ if (!pid || pid < 1) return false;
36951
+ try {
36952
+ process.kill(-pid, 0);
36953
+ return true;
36954
+ } catch (error62) {
36955
+ return error62.code !== "ESRCH";
36956
+ }
36957
+ }
36958
+ #finishSession(session, exitCode, signal) {
36959
+ if (session.finishedRecorded) return;
36960
+ session.finishedRecorded = true;
36961
+ session.running = false;
36962
+ session.finishedAt = Date.now();
36963
+ session.exitCode = exitCode;
36964
+ session.signal = signal;
36965
+ if (session.timeout) clearTimeout(session.timeout);
36966
+ if (session.forceKillTimer) {
36967
+ clearTimeout(session.forceKillTimer);
36968
+ session.forceKillTimer = void 0;
36969
+ }
36970
+ this.#record(session, "finished", {
36971
+ exitCode,
36972
+ signal,
36973
+ timedOut: session.timedOut
36974
+ });
36975
+ }
36976
+ #requestTermination(session) {
36977
+ if (!session.running) return;
36978
+ session.terminationRequested = true;
36979
+ this.#signalProcessTree(session, "SIGTERM");
36980
+ if (!session.forceKillTimer) {
36981
+ session.forceKillTimer = setTimeout(() => {
36982
+ session.forceKillTimer = void 0;
36983
+ if (process.platform !== "win32" || session.running) {
36984
+ this.#signalProcessTree(session, "SIGKILL");
36985
+ }
36986
+ const pending = session.pendingTerminationClose;
36987
+ if (pending) {
36988
+ session.pendingTerminationClose = void 0;
36989
+ this.#finishSession(session, pending.exitCode, pending.signal);
36990
+ }
36991
+ }, TERMINATION_GRACE_MS);
36992
+ }
36993
+ }
36994
+ #pruneFinished() {
36995
+ const cutoff = Date.now() - LOCAL_SESSION_RETENTION_MS;
36996
+ for (const [sessionId, session] of this.#sessions) {
36997
+ if (session.finishedAt !== void 0 && session.finishedAt < cutoff && session.watchers.size === 0) {
36998
+ this.#sessions.delete(sessionId);
36999
+ if (session.taskId) this.#taskSessions.delete(session.taskId);
37000
+ }
37001
+ }
37002
+ }
37003
+ #record(session, kind, details = {}) {
37004
+ const event = {
37005
+ sessionId: session.sessionId,
37006
+ ...session.taskId ? { taskId: session.taskId } : {},
37007
+ sequence: ++session.nextSequence,
37008
+ kind,
37009
+ ...details
37010
+ };
37011
+ session.events.push(event);
37012
+ session.retainedEventCharacters += event.data?.length ?? 0;
37013
+ while (session.events.length > MAX_RETAINED_EVENTS || session.retainedEventCharacters > MAX_RETAINED_EVENT_CHARACTERS) {
37014
+ const removed = session.events.shift();
37015
+ session.retainedEventCharacters -= removed.data?.length ?? 0;
37016
+ }
37017
+ for (const wake of session.watchers) wake();
37018
+ }
37019
+ #output(session, stream, chunk) {
37020
+ const before = session[stream].length;
37021
+ const next = appendBounded(
37022
+ session[stream],
37023
+ chunk,
37024
+ session.maxOutputCharacters
37025
+ );
37026
+ session[stream] = next;
37027
+ const appended = next.slice(before);
37028
+ for (let i = 0; i < appended.length; i += MAX_EVENT_CHARACTERS) {
37029
+ this.#record(session, stream, {
37030
+ offset: before + i,
37031
+ data: appended.slice(i, i + MAX_EVENT_CHARACTERS)
37032
+ });
37033
+ }
37034
+ }
37035
+ async watch(sessionId, afterSequence, timeoutMs = 15e3, maxEvents = 32) {
37036
+ this.#pruneFinished();
37037
+ const session = this.#sessions.get(sessionId);
37038
+ if (!session)
37039
+ throw new Error("The requested process session was not found.");
37040
+ if (!Number.isSafeInteger(afterSequence) || afterSequence < 0) {
37041
+ throw new Error("afterSequence must be a non-negative integer.");
37042
+ }
37043
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 2e4) {
37044
+ throw new Error("timeoutMs must be between 100 and 20000.");
37045
+ }
37046
+ if (!Number.isInteger(maxEvents) || maxEvents < 1 || maxEvents > 32) {
37047
+ throw new Error("maxEvents must be between 1 and 32.");
37048
+ }
37049
+ const collect = () => {
37050
+ const first = session.events[0]?.sequence ?? session.nextSequence + 1;
37051
+ const missedEvents = afterSequence + 1 < first;
37052
+ const events = session.events.filter((event) => event.sequence > afterSequence).slice(0, maxEvents);
37053
+ return {
37054
+ sessionId,
37055
+ events,
37056
+ nextSequence: events.at(-1)?.sequence ?? Math.max(afterSequence, session.nextSequence),
37057
+ missedEvents,
37058
+ finished: !session.running
37059
+ };
37060
+ };
37061
+ const initial = collect();
37062
+ if (initial.events.length || initial.finished || initial.missedEvents) {
37063
+ return initial;
37064
+ }
37065
+ return new Promise((resolve) => {
37066
+ let timer;
37067
+ const finish = () => {
37068
+ clearTimeout(timer);
37069
+ session.watchers.delete(finish);
37070
+ resolve(collect());
37071
+ };
37072
+ session.watchers.add(finish);
37073
+ timer = setTimeout(finish, timeoutMs);
37074
+ if (session.nextSequence > afterSequence || !session.running) finish();
37075
+ });
37076
+ }
36617
37077
  start(options) {
37078
+ this.#pruneReceipts();
37079
+ if (options.taskId) {
37080
+ const existingSession = this.#taskSessions.get(options.taskId);
37081
+ if (existingSession) {
37082
+ const existing = this.#sessions.get(existingSession);
37083
+ if (!existing) throw new Error("PROCESS_TASK_SESSION_LOST");
37084
+ if (existing.program !== options.program || existing.cwd !== options.cwd || existing.args.join("\0") !== options.args.join("\0")) {
37085
+ throw new Error(
37086
+ "PROCESS_TASK_CONFLICT: Task ID already started with different arguments."
37087
+ );
37088
+ }
37089
+ const result2 = snapshot(existing);
37090
+ return {
37091
+ ...result2,
37092
+ nextStdoutOffset: result2.stdout.length,
37093
+ nextStderrOffset: result2.stderr.length
37094
+ };
37095
+ }
37096
+ if (this.#taskReceipts.has(options.taskId)) {
37097
+ throw new Error(
37098
+ "PROCESS_TASK_SESSION_LOST: Task already ran; will not execute again."
37099
+ );
37100
+ }
37101
+ }
37102
+ this.#pruneFinished();
37103
+ if (this.#sessions.size >= this.limits.maxRetained) {
37104
+ throw new Error("PROCESS_LIMIT_REACHED: Local process history is full.");
37105
+ }
37106
+ const running = [...this.#sessions.values()].filter(
37107
+ (session2) => session2.running
37108
+ ).length;
37109
+ if (running >= this.limits.maxRunning) {
37110
+ throw new Error(
37111
+ "PROCESS_LIMIT_REACHED: Too many concurrent local processes."
37112
+ );
37113
+ }
37114
+ if (options.taskId && this.#taskReceipts.size >= MAX_TASK_TOMBSTONES) {
37115
+ throw new Error(
37116
+ "PROCESS_LIMIT_REACHED: Local task receipt capacity reached."
37117
+ );
37118
+ }
36618
37119
  assertProgramAllowed(options.program);
36619
- const child = spawn(options.program, options.args, {
36620
- cwd: options.cwd,
36621
- env: safeEnvironment(),
36622
- stdio: ["ignore", "pipe", "pipe"],
36623
- windowsHide: true
36624
- });
37120
+ if (options.rootedExecution && (options.rootedExecution.program !== options.program || options.rootedExecution.args.join("\0") !== options.args.join("\0")))
37121
+ throw new Error("ROOTED_EXECUTION_MISMATCH");
37122
+ const rootedPayload = options.rootedExecution ? JSON.stringify(options.rootedExecution) : void 0;
37123
+ if (rootedPayload && Buffer.byteLength(rootedPayload) >= 4 * 1024 * 1024)
37124
+ throw new Error("ROOTED_EXECUTION_INPUT_LIMIT");
37125
+ if (options.taskId) this.#recordTaskReceipt(options.taskId);
37126
+ const child = spawn2(
37127
+ options.rootedExecution ? filesystemHelperPath : options.program,
37128
+ options.rootedExecution ? ["--exec"] : options.args,
37129
+ {
37130
+ cwd: options.rootedExecution ? void 0 : options.cwd,
37131
+ env: safeEnvironment(),
37132
+ stdio: ["pipe", "pipe", "pipe"],
37133
+ windowsHide: true,
37134
+ // A separate Unix process group lets us terminate descendants too.
37135
+ detached: process.platform !== "win32"
37136
+ }
37137
+ );
37138
+ child.stdin.on("error", () => void 0);
37139
+ child.stdin.end(rootedPayload);
36625
37140
  const session = {
36626
37141
  sessionId: randomUUID(),
37142
+ ...options.taskId ? { taskId: options.taskId } : {},
36627
37143
  program: options.program,
36628
37144
  args: [...options.args],
36629
37145
  cwd: options.cwd,
@@ -36634,22 +37150,21 @@ var ProcessManager = class {
36634
37150
  running: true,
36635
37151
  exitCode: null,
36636
37152
  signal: null,
36637
- timedOut: false
37153
+ timedOut: false,
37154
+ nextSequence: 0,
37155
+ events: [],
37156
+ retainedEventCharacters: 0,
37157
+ watchers: /* @__PURE__ */ new Set()
36638
37158
  };
36639
37159
  this.#sessions.set(session.sessionId, session);
37160
+ if (session.taskId)
37161
+ this.#taskSessions.set(session.taskId, session.sessionId);
37162
+ this.#record(session, "started");
36640
37163
  child.stdout.on("data", (chunk) => {
36641
- session.stdout = appendBounded(
36642
- session.stdout,
36643
- chunk,
36644
- session.maxOutputCharacters
36645
- );
37164
+ this.#output(session, "stdout", chunk);
36646
37165
  });
36647
37166
  child.stderr.on("data", (chunk) => {
36648
- session.stderr = appendBounded(
36649
- session.stderr,
36650
- chunk,
36651
- session.maxOutputCharacters
36652
- );
37167
+ this.#output(session, "stderr", chunk);
36653
37168
  });
36654
37169
  child.once("error", (error62) => {
36655
37170
  session.stderr = appendBounded(
@@ -36663,15 +37178,17 @@ var ProcessManager = class {
36663
37178
  if (session.timeout) clearTimeout(session.timeout);
36664
37179
  });
36665
37180
  child.once("close", (exitCode, signal) => {
36666
- session.running = false;
36667
- session.exitCode = exitCode;
36668
- session.signal = signal;
36669
- if (session.timeout) clearTimeout(session.timeout);
37181
+ if (session.terminationRequested && session.forceKillTimer && this.#unixProcessGroupExists(session)) {
37182
+ session.pendingTerminationClose = { exitCode, signal };
37183
+ if (session.timeout) clearTimeout(session.timeout);
37184
+ return;
37185
+ }
37186
+ this.#finishSession(session, exitCode, signal);
36670
37187
  });
36671
37188
  session.timeout = setTimeout(() => {
36672
37189
  if (!session.running) return;
36673
37190
  session.timedOut = true;
36674
- child.kill("SIGTERM");
37191
+ this.#requestTermination(session);
36675
37192
  }, options.timeoutMs);
36676
37193
  const result = snapshot(session);
36677
37194
  return {
@@ -36681,6 +37198,7 @@ var ProcessManager = class {
36681
37198
  };
36682
37199
  }
36683
37200
  read(sessionId, stdoutOffset, stderrOffset, maxOutputCharacters) {
37201
+ this.#pruneFinished();
36684
37202
  const session = this.#sessions.get(sessionId);
36685
37203
  if (!session)
36686
37204
  throw new Error("The requested process session was not found.");
@@ -36709,36 +37227,60 @@ var ProcessManager = class {
36709
37227
  };
36710
37228
  }
36711
37229
  terminate(sessionId) {
37230
+ this.#pruneFinished();
36712
37231
  const session = this.#sessions.get(sessionId);
36713
37232
  if (!session)
36714
37233
  throw new Error("The requested process session was not found.");
36715
- if (session.running) session.child.kill("SIGTERM");
37234
+ this.#requestTermination(session);
36716
37235
  return snapshot(session);
36717
37236
  }
36718
37237
  list() {
37238
+ this.#pruneFinished();
36719
37239
  return [...this.#sessions.values()].map(snapshot);
36720
37240
  }
36721
37241
  close() {
36722
37242
  for (const session of this.#sessions.values()) {
36723
37243
  if (session.timeout) clearTimeout(session.timeout);
36724
- if (session.running) session.child.kill("SIGTERM");
37244
+ this.#requestTermination(session);
37245
+ }
37246
+ for (const session of this.#sessions.values()) {
37247
+ for (const wake of session.watchers) wake();
36725
37248
  }
36726
37249
  this.#sessions.clear();
37250
+ this.#taskSessions.clear();
36727
37251
  }
36728
37252
  };
36729
37253
 
36730
37254
  // src/server.ts
36731
37255
  var roots = configuredRoots();
36732
37256
  var processManager = new ProcessManager();
37257
+ var sharedFilesystem;
37258
+ function defaultFilesystem() {
37259
+ return sharedFilesystem ??= new RootedFilesystem(roots);
37260
+ }
37261
+ async function initializeFilesystemBoundary() {
37262
+ await defaultFilesystem().ready;
37263
+ }
37264
+ async function closeFilesystemBoundary() {
37265
+ await sharedFilesystem?.close();
37266
+ }
36733
37267
  function textResult(value) {
36734
37268
  return {
36735
37269
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
36736
37270
  structuredContent: value
36737
37271
  };
36738
37272
  }
36739
- function createServer() {
37273
+ function createServer(options = {}) {
37274
+ const filesystem = options.filesystem ?? defaultFilesystem();
37275
+ const requireAuthorizedAgent = () => {
37276
+ if (!options.allowDestructive) {
37277
+ throw new Error(
37278
+ "LOCAL_APPROVAL_REQUIRED: Connect through the approved Terminal Commands agent."
37279
+ );
37280
+ }
37281
+ };
36740
37282
  const server2 = new McpServer(
36741
- { name: "terminal-commands", version: "0.1.0" },
37283
+ { name: "terminal-commands", version: "0.2.0" },
36742
37284
  {
36743
37285
  instructions: "This server controls the user's machine. Prefer dedicated file tools over commands. Every command and write can modify or expose local data and must receive user approval. Never request secrets or bypass configured roots and safety controls."
36744
37286
  }
@@ -36791,15 +37333,12 @@ function createServer() {
36791
37333
  }
36792
37334
  },
36793
37335
  async ({ path: requestedPath }) => {
36794
- const directory = await resolveReadablePath(requestedPath, roots);
36795
- const entries = await readdir(directory, { withFileTypes: true });
37336
+ const directory = filesystem.resolve(requestedPath);
37337
+ const { entries, truncated } = await filesystem.list(directory);
36796
37338
  return textResult({
36797
- path: directory,
36798
- entries: entries.slice(0, 1e3).map((entry) => ({
36799
- name: entry.name,
36800
- type: entry.isDirectory() ? "directory" : entry.isFile() ? "file" : "other"
36801
- })),
36802
- truncated: entries.length > 1e3
37339
+ path: directory.displayPath,
37340
+ entries,
37341
+ truncated
36803
37342
  });
36804
37343
  }
36805
37344
  );
@@ -36824,13 +37363,13 @@ function createServer() {
36824
37363
  }
36825
37364
  },
36826
37365
  async ({ path: requestedPath, maxCharacters }) => {
36827
- const filename = await resolveReadablePath(requestedPath, roots);
36828
- const { contents, truncated } = await readBoundedTextFile(
37366
+ const filename = filesystem.resolve(requestedPath);
37367
+ const { contents, truncated } = await filesystem.read(
36829
37368
  filename,
36830
37369
  maxCharacters
36831
37370
  );
36832
37371
  return textResult({
36833
- path: filename,
37372
+ path: filename.displayPath,
36834
37373
  contents,
36835
37374
  truncated
36836
37375
  });
@@ -36857,14 +37396,16 @@ function createServer() {
36857
37396
  }
36858
37397
  },
36859
37398
  async ({ path: requestedPath, contents, overwrite }) => {
36860
- const filename = await resolveWritablePath(requestedPath, roots);
36861
- await writeFile(filename, contents, {
36862
- encoding: "utf8",
36863
- flag: overwrite ? "w" : "wx"
36864
- });
37399
+ requireAuthorizedAgent();
37400
+ const filename = filesystem.resolve(requestedPath);
37401
+ const bytesWritten = await filesystem.write(
37402
+ filename,
37403
+ contents,
37404
+ overwrite
37405
+ );
36865
37406
  return textResult({
36866
- path: filename,
36867
- bytesWritten: Buffer.byteLength(contents)
37407
+ path: filename.displayPath,
37408
+ bytesWritten
36868
37409
  });
36869
37410
  }
36870
37411
  );
@@ -36874,6 +37415,7 @@ function createServer() {
36874
37415
  title: "Start a process",
36875
37416
  description: "Start one approved executable without a shell on this machine and return a session ID for reading its output.",
36876
37417
  inputSchema: {
37418
+ taskId: external_exports.string().uuid().optional(),
36877
37419
  program: external_exports.string().min(1),
36878
37420
  args: external_exports.array(external_exports.string()).max(100).default([]),
36879
37421
  cwd: external_exports.string().min(1).optional(),
@@ -36882,6 +37424,7 @@ function createServer() {
36882
37424
  },
36883
37425
  outputSchema: {
36884
37426
  sessionId: external_exports.string(),
37427
+ taskId: external_exports.string().uuid().optional(),
36885
37428
  program: external_exports.string(),
36886
37429
  args: external_exports.array(external_exports.string()),
36887
37430
  cwd: external_exports.string(),
@@ -36901,16 +37444,21 @@ function createServer() {
36901
37444
  openWorldHint: true
36902
37445
  }
36903
37446
  },
36904
- async ({ program, args, cwd, timeoutMs, maxOutputCharacters }) => {
36905
- const workingDirectory = await resolveReadablePath(
37447
+ async ({ taskId, program, args, cwd, timeoutMs, maxOutputCharacters }) => {
37448
+ requireAuthorizedAgent();
37449
+ const workingDirectory = filesystem.resolve(cwd ?? roots[0]);
37450
+ const rootedExecution = await filesystem.execution(
36906
37451
  cwd ?? roots[0],
36907
- roots
37452
+ program,
37453
+ args
36908
37454
  );
36909
37455
  return textResult(
36910
37456
  processManager.start({
37457
+ taskId,
36911
37458
  program,
36912
37459
  args,
36913
- cwd: workingDirectory,
37460
+ cwd: workingDirectory.displayPath,
37461
+ rootedExecution,
36914
37462
  timeoutMs,
36915
37463
  maxOutputCharacters
36916
37464
  })
@@ -36930,6 +37478,7 @@ function createServer() {
36930
37478
  },
36931
37479
  outputSchema: {
36932
37480
  sessionId: external_exports.string(),
37481
+ taskId: external_exports.string().uuid().optional(),
36933
37482
  program: external_exports.string(),
36934
37483
  args: external_exports.array(external_exports.string()),
36935
37484
  cwd: external_exports.string(),
@@ -36958,6 +37507,51 @@ function createServer() {
36958
37507
  )
36959
37508
  )
36960
37509
  );
37510
+ server2.registerTool(
37511
+ "watch_process_events",
37512
+ {
37513
+ title: "Watch local process events",
37514
+ description: "Wait for sequenced, bounded process events after a cursor. Used internally by the companion to push progress over WebSocket.",
37515
+ inputSchema: {
37516
+ sessionId: external_exports.string().uuid(),
37517
+ afterSequence: external_exports.number().int().nonnegative().default(0),
37518
+ timeoutMs: external_exports.number().int().min(100).max(2e4).default(1e4),
37519
+ maxEvents: external_exports.number().int().min(1).max(32).default(32)
37520
+ },
37521
+ outputSchema: {
37522
+ sessionId: external_exports.string().uuid(),
37523
+ events: external_exports.array(
37524
+ external_exports.object({
37525
+ sessionId: external_exports.string().uuid(),
37526
+ taskId: external_exports.string().uuid().optional(),
37527
+ sequence: external_exports.number().int().positive(),
37528
+ kind: external_exports.enum(["started", "stdout", "stderr", "finished"]),
37529
+ offset: external_exports.number().int().nonnegative().optional(),
37530
+ data: external_exports.string().max(4096).optional(),
37531
+ exitCode: external_exports.number().int().nullable().optional(),
37532
+ signal: external_exports.string().nullable().optional(),
37533
+ timedOut: external_exports.boolean().optional()
37534
+ })
37535
+ ).max(32),
37536
+ nextSequence: external_exports.number().int().nonnegative(),
37537
+ missedEvents: external_exports.boolean(),
37538
+ finished: external_exports.boolean()
37539
+ },
37540
+ annotations: {
37541
+ readOnlyHint: true,
37542
+ destructiveHint: false,
37543
+ openWorldHint: false
37544
+ }
37545
+ },
37546
+ async ({ sessionId, afterSequence, timeoutMs, maxEvents }) => textResult(
37547
+ await processManager.watch(
37548
+ sessionId,
37549
+ afterSequence,
37550
+ timeoutMs,
37551
+ maxEvents
37552
+ )
37553
+ )
37554
+ );
36961
37555
  server2.registerTool(
36962
37556
  "terminate_process",
36963
37557
  {
@@ -36966,6 +37560,7 @@ function createServer() {
36966
37560
  inputSchema: { sessionId: external_exports.string().uuid() },
36967
37561
  outputSchema: {
36968
37562
  sessionId: external_exports.string(),
37563
+ taskId: external_exports.string().uuid().optional(),
36969
37564
  program: external_exports.string(),
36970
37565
  args: external_exports.array(external_exports.string()),
36971
37566
  cwd: external_exports.string(),
@@ -36983,7 +37578,10 @@ function createServer() {
36983
37578
  openWorldHint: false
36984
37579
  }
36985
37580
  },
36986
- async ({ sessionId }) => textResult(processManager.terminate(sessionId))
37581
+ async ({ sessionId }) => {
37582
+ requireAuthorizedAgent();
37583
+ return textResult(processManager.terminate(sessionId));
37584
+ }
36987
37585
  );
36988
37586
  server2.registerTool(
36989
37587
  "list_processes",
@@ -36995,6 +37593,7 @@ function createServer() {
36995
37593
  processes: external_exports.array(
36996
37594
  external_exports.object({
36997
37595
  sessionId: external_exports.string(),
37596
+ taskId: external_exports.string().uuid().optional(),
36998
37597
  program: external_exports.string(),
36999
37598
  args: external_exports.array(external_exports.string()),
37000
37599
  cwd: external_exports.string(),
@@ -37002,6 +37601,8 @@ function createServer() {
37002
37601
  running: external_exports.boolean(),
37003
37602
  exitCode: external_exports.number().int().nullable(),
37004
37603
  signal: external_exports.string().nullable(),
37604
+ stdout: external_exports.string(),
37605
+ stderr: external_exports.string(),
37005
37606
  timedOut: external_exports.boolean()
37006
37607
  })
37007
37608
  )
@@ -37018,12 +37619,16 @@ function createServer() {
37018
37619
  }
37019
37620
 
37020
37621
  // src/stdio.ts
37021
- var server = createServer();
37622
+ var server = createServer({
37623
+ allowDestructive: process.env.MACHINE_TERMINAL_TRUST_STDIO === "1"
37624
+ });
37022
37625
  var transport = new StdioServerTransport();
37626
+ await initializeFilesystemBoundary();
37023
37627
  await server.connect(transport);
37024
37628
  async function shutdown() {
37025
37629
  processManager.close();
37026
37630
  await server.close();
37631
+ await closeFilesystemBoundary();
37027
37632
  process.exit(0);
37028
37633
  }
37029
37634
  process.on("SIGINT", () => void shutdown());