glasspane-mcp 1.7.0 → 1.8.2

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.
@@ -26,7 +26,7 @@ export declare const MCP_PROTOCOL_VERSION: "2025-06-18";
26
26
  export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
27
27
  export declare const SERVER_INFO: {
28
28
  readonly name: "glasspane-mcp";
29
- readonly version: "1.7.0";
29
+ readonly version: "1.8.2";
30
30
  };
31
31
  export declare const PARSE_ERROR = -32700;
32
32
  export declare const INVALID_REQUEST = -32600;
@@ -243,6 +243,15 @@ export declare function livenessProbeDecision(outcome: LivenessProbeOutcome): st
243
243
  export type ShellSurface = "mcp" | "http";
244
244
  /** Where the answer to a timed-out request can be found, per surface. */
245
245
  export declare function lateReplyRoute(surface: ShellSurface): string;
246
+ /**
247
+ * 背压闸的出路。两条禁令各由一条现场证据撑着:探针本身也要过一次 `call()`,会被
248
+ * 同一道测量原地拒掉,所以旧文本那句"先用 gp_probe_status 查一下"是一件此刻做不到
249
+ * 第二次的事;这里唯一测到的是**本进程**的字节水位,不是 daemon 的状态,而旧文本
250
+ * 却挂了 GP_E_ENGINE_UNREACHABLE 那条重启命令——`livenessProbeDecision` 把点名生命
251
+ * 周期命令的权力只留给 `unreachable`,换一个可能正跑着 act 的 service 会回滚用户屏
252
+ * 幕上的动作。"每次重试都只会把队列堆得更深"同样不成立:拒绝发生在 `writeLine` 之前。
253
+ */
254
+ export declare function writeBacklogRemedy(backlogBytes: number, limitBytes: number): string;
246
255
  /**
247
256
  * Remedy for a deadline overrun: wait and measure, never restart on a probe that
248
257
  * merely went unanswered, and never replay a request whose re-issue has a side
package/dist/errors.d.ts CHANGED
@@ -32,6 +32,20 @@ export declare const GP_E_NOT_FOUND = "GP_E_NOT_FOUND";
32
32
  */
33
33
  export declare const GP_E_ENGINE_TIMEOUT = "GP_E_ENGINE_TIMEOUT";
34
34
  export declare const GP_E_PAYLOAD_TOO_LARGE = "GP_E_PAYLOAD_TOO_LARGE";
35
+ /**
36
+ * Shell-only: this process refused to *write* a request, because the bytes it had
37
+ * already handed the daemon's socket were not being read
38
+ * (`ENGINE_WRITE_BACKLOG_BYTES` in `engine-client.ts`).
39
+ *
40
+ * It is deliberately not `GP_E_ENGINE_UNREACHABLE`, which is what this refusal
41
+ * used to answer with: that code's remedy orders a daemon restart, and a backlog
42
+ * measures a memory watermark in *this* process, not the service's state — the
43
+ * daemon may be mid-`act` on the user's screen, where a SIGTERM cancels and rolls
44
+ * the action back. The two answers also have different next moves, which is the
45
+ * real reason to split the code: the unread bytes may drain on their own, while a
46
+ * socket nobody is listening on never does.
47
+ */
48
+ export declare const GP_E_ENGINE_BACKLOG = "GP_E_ENGINE_BACKLOG";
35
49
  /**
36
50
  * Shell-only, and deliberately *not* a daemon code: this is what the shell
37
51
  * answers when a daemon error frame arrives with no `code` at all. Naming it
@@ -151,6 +151,7 @@ var GP_E_PROJECT_LIMIT = "GP_E_PROJECT_LIMIT";
151
151
  var GP_E_NOT_FOUND = "GP_E_NOT_FOUND";
152
152
  var GP_E_ENGINE_TIMEOUT = "GP_E_ENGINE_TIMEOUT";
153
153
  var GP_E_PAYLOAD_TOO_LARGE = "GP_E_PAYLOAD_TOO_LARGE";
154
+ var GP_E_ENGINE_BACKLOG = "GP_E_ENGINE_BACKLOG";
154
155
  var GP_E_UNKNOWN = "GP_E_UNKNOWN";
155
156
  var GP_E_NO_USER_RECORD = "GP_E_NO_USER_RECORD";
156
157
  function formatToolError(code, message, remedy) {
@@ -280,6 +281,9 @@ var NARROWING_AUTHORITY = {
280
281
  mcp: "the gp_* tool that took this call publishes its own parameters, and their floors, in the schema shown beside that tool \u2014 read it there, not here",
281
282
  http: "this gateway publishes no parameter listing of its own, so the floors a body was accepted against are in the request contract this call was copied from"
282
283
  };
284
+ function writeBacklogRemedy(backlogBytes, limitBytes) {
285
+ return `this shell wrote nothing: ${backlogBytes} bytes \u2014 over this gate's ${limitBytes} \u2014 are queued in THIS process for the engine socket, and the service accepted the connection and is not reading it. That is a fact about this shell's buffer, not a diagnosis of why: one long operation and a service wedged solid look identical from here, which is why this answer orders neither a restart nor a probe. Stop sending. Do not retry this call in a loop: every attempt is refused by this same measurement, and a refusal writes no bytes, so a loop moves only the clock, not the queue. What is already queued is the service's to read, so wait for the backlog to drain and send again after that \u2014 this gate is re-measured per call, so the first call accepted after the wait is the proof that it drained, and it is a first send rather than a second copy of an action, because nothing went out the first time. While waiting, read the daemon's own log: it is the only side that can say what it is stuck on, and this shell cannot see past its own buffer. For the service installed by this repo's installer that log is \`~/.glasspane/installer-daemon.log\`, the file its stderr is redirected into, in the same directory as the default socket; a daemon started by hand writes wherever its operator pointed its stderr.`;
286
+ }
283
287
  function clientClosingRemedy(surface) {
284
288
  const readRoute = surface === "mcp" ? "gp_recent_reports lists the operations this session recorded and gp_last_evidence {operationId} returns one in full" : "this gateway keeps no operation trail of its own, so the stderr line this reply is written to is the record";
285
289
  return `this shell's own engine client is ending, which measures nothing about the daemon: nothing here authorises touching the daemon's lifecycle, and no restart is authorised by this answer. What is unknown stays unknown: whether the daemon finished this request cannot be told from a client that stopped asking. The reply may still land, under the bound this shell already states \u2014 ${lateReplyRoute(surface)} \u2014 and this shutdown is one of the endings that bound names: the server log line written at this moment lists the requests this client stopped tracking. Read what happened rather than performing it a second time: ${readRoute}. Ask through a server process that still has a live engine client.`;
@@ -426,9 +430,9 @@ var EngineJsonRpcClient = class {
426
430
  if (backlog > ENGINE_WRITE_BACKLOG_BYTES) {
427
431
  this.pending.delete(id);
428
432
  this.reject(entry, new EngineCallError(
429
- GP_E_ENGINE_UNREACHABLE,
433
+ GP_E_ENGINE_BACKLOG,
430
434
  `not sending '${method}': ${backlog} bytes are already queued for the engine socket and it has not read them`,
431
- `the background service is accepting the connection but not reading it \u2014 it is wedged, not busy. Check it with gp_probe_status; if it stays like this, restart it with the command in \`glasspaned --help\` (or the panel's \u91CD\u542F\u540E\u53F0\u670D\u52A1 button). Do not retry this call in a loop: every retry only deepens the queue it is refusing to drain.`
435
+ writeBacklogRemedy(backlog, ENGINE_WRITE_BACKLOG_BYTES)
432
436
  ));
433
437
  return;
434
438
  }
@@ -3390,6 +3394,8 @@ function httpStatusForEngineCode(code) {
3390
3394
  return 409;
3391
3395
  case GP_E_ENGINE_UNREACHABLE:
3392
3396
  return 503;
3397
+ case GP_E_ENGINE_BACKLOG:
3398
+ return 503;
3393
3399
  case GP_E_ENGINE_TIMEOUT:
3394
3400
  return 504;
3395
3401
  case GP_E_PAYLOAD_TOO_LARGE:
@@ -151,6 +151,7 @@ var GP_E_PROJECT_LIMIT = "GP_E_PROJECT_LIMIT";
151
151
  var GP_E_NOT_FOUND = "GP_E_NOT_FOUND";
152
152
  var GP_E_ENGINE_TIMEOUT = "GP_E_ENGINE_TIMEOUT";
153
153
  var GP_E_PAYLOAD_TOO_LARGE = "GP_E_PAYLOAD_TOO_LARGE";
154
+ var GP_E_ENGINE_BACKLOG = "GP_E_ENGINE_BACKLOG";
154
155
  var GP_E_UNKNOWN = "GP_E_UNKNOWN";
155
156
  var GP_E_NO_USER_RECORD = "GP_E_NO_USER_RECORD";
156
157
  function formatToolError(code, message, remedy) {
@@ -276,6 +277,9 @@ var NARROWING_AUTHORITY = {
276
277
  mcp: "the gp_* tool that took this call publishes its own parameters, and their floors, in the schema shown beside that tool \u2014 read it there, not here",
277
278
  http: "this gateway publishes no parameter listing of its own, so the floors a body was accepted against are in the request contract this call was copied from"
278
279
  };
280
+ function writeBacklogRemedy(backlogBytes, limitBytes) {
281
+ return `this shell wrote nothing: ${backlogBytes} bytes \u2014 over this gate's ${limitBytes} \u2014 are queued in THIS process for the engine socket, and the service accepted the connection and is not reading it. That is a fact about this shell's buffer, not a diagnosis of why: one long operation and a service wedged solid look identical from here, which is why this answer orders neither a restart nor a probe. Stop sending. Do not retry this call in a loop: every attempt is refused by this same measurement, and a refusal writes no bytes, so a loop moves only the clock, not the queue. What is already queued is the service's to read, so wait for the backlog to drain and send again after that \u2014 this gate is re-measured per call, so the first call accepted after the wait is the proof that it drained, and it is a first send rather than a second copy of an action, because nothing went out the first time. While waiting, read the daemon's own log: it is the only side that can say what it is stuck on, and this shell cannot see past its own buffer. For the service installed by this repo's installer that log is \`~/.glasspane/installer-daemon.log\`, the file its stderr is redirected into, in the same directory as the default socket; a daemon started by hand writes wherever its operator pointed its stderr.`;
282
+ }
279
283
  function clientClosingRemedy(surface) {
280
284
  const readRoute = surface === "mcp" ? "gp_recent_reports lists the operations this session recorded and gp_last_evidence {operationId} returns one in full" : "this gateway keeps no operation trail of its own, so the stderr line this reply is written to is the record";
281
285
  return `this shell's own engine client is ending, which measures nothing about the daemon: nothing here authorises touching the daemon's lifecycle, and no restart is authorised by this answer. What is unknown stays unknown: whether the daemon finished this request cannot be told from a client that stopped asking. The reply may still land, under the bound this shell already states \u2014 ${lateReplyRoute(surface)} \u2014 and this shutdown is one of the endings that bound names: the server log line written at this moment lists the requests this client stopped tracking. Read what happened rather than performing it a second time: ${readRoute}. Ask through a server process that still has a live engine client.`;
@@ -422,9 +426,9 @@ var EngineJsonRpcClient = class {
422
426
  if (backlog > ENGINE_WRITE_BACKLOG_BYTES) {
423
427
  this.pending.delete(id);
424
428
  this.reject(entry, new EngineCallError(
425
- GP_E_ENGINE_UNREACHABLE,
429
+ GP_E_ENGINE_BACKLOG,
426
430
  `not sending '${method}': ${backlog} bytes are already queued for the engine socket and it has not read them`,
427
- `the background service is accepting the connection but not reading it \u2014 it is wedged, not busy. Check it with gp_probe_status; if it stays like this, restart it with the command in \`glasspaned --help\` (or the panel's \u91CD\u542F\u540E\u53F0\u670D\u52A1 button). Do not retry this call in a loop: every retry only deepens the queue it is refusing to drain.`
431
+ writeBacklogRemedy(backlog, ENGINE_WRITE_BACKLOG_BYTES)
428
432
  ));
429
433
  return;
430
434
  }
@@ -3380,6 +3384,8 @@ function httpStatusForEngineCode(code) {
3380
3384
  return 409;
3381
3385
  case GP_E_ENGINE_UNREACHABLE:
3382
3386
  return 503;
3387
+ case GP_E_ENGINE_BACKLOG:
3388
+ return 503;
3383
3389
  case GP_E_ENGINE_TIMEOUT:
3384
3390
  return 504;
3385
3391
  case GP_E_PAYLOAD_TOO_LARGE:
package/dist/index.js CHANGED
@@ -807,6 +807,7 @@ var GP_E_PROJECT_LIMIT = "GP_E_PROJECT_LIMIT";
807
807
  var GP_E_NOT_FOUND = "GP_E_NOT_FOUND";
808
808
  var GP_E_ENGINE_TIMEOUT = "GP_E_ENGINE_TIMEOUT";
809
809
  var GP_E_PAYLOAD_TOO_LARGE = "GP_E_PAYLOAD_TOO_LARGE";
810
+ var GP_E_ENGINE_BACKLOG = "GP_E_ENGINE_BACKLOG";
810
811
  var GP_E_UNKNOWN = "GP_E_UNKNOWN";
811
812
  var GP_E_NO_USER_RECORD = "GP_E_NO_USER_RECORD";
812
813
  function formatToolError(code, message, remedy) {
@@ -936,6 +937,9 @@ var NARROWING_AUTHORITY = {
936
937
  mcp: "the gp_* tool that took this call publishes its own parameters, and their floors, in the schema shown beside that tool \u2014 read it there, not here",
937
938
  http: "this gateway publishes no parameter listing of its own, so the floors a body was accepted against are in the request contract this call was copied from"
938
939
  };
940
+ function writeBacklogRemedy(backlogBytes, limitBytes) {
941
+ return `this shell wrote nothing: ${backlogBytes} bytes \u2014 over this gate's ${limitBytes} \u2014 are queued in THIS process for the engine socket, and the service accepted the connection and is not reading it. That is a fact about this shell's buffer, not a diagnosis of why: one long operation and a service wedged solid look identical from here, which is why this answer orders neither a restart nor a probe. Stop sending. Do not retry this call in a loop: every attempt is refused by this same measurement, and a refusal writes no bytes, so a loop moves only the clock, not the queue. What is already queued is the service's to read, so wait for the backlog to drain and send again after that \u2014 this gate is re-measured per call, so the first call accepted after the wait is the proof that it drained, and it is a first send rather than a second copy of an action, because nothing went out the first time. While waiting, read the daemon's own log: it is the only side that can say what it is stuck on, and this shell cannot see past its own buffer. For the service installed by this repo's installer that log is \`~/.glasspane/installer-daemon.log\`, the file its stderr is redirected into, in the same directory as the default socket; a daemon started by hand writes wherever its operator pointed its stderr.`;
942
+ }
939
943
  function clientClosingRemedy(surface) {
940
944
  const readRoute = surface === "mcp" ? "gp_recent_reports lists the operations this session recorded and gp_last_evidence {operationId} returns one in full" : "this gateway keeps no operation trail of its own, so the stderr line this reply is written to is the record";
941
945
  return `this shell's own engine client is ending, which measures nothing about the daemon: nothing here authorises touching the daemon's lifecycle, and no restart is authorised by this answer. What is unknown stays unknown: whether the daemon finished this request cannot be told from a client that stopped asking. The reply may still land, under the bound this shell already states \u2014 ${lateReplyRoute(surface)} \u2014 and this shutdown is one of the endings that bound names: the server log line written at this moment lists the requests this client stopped tracking. Read what happened rather than performing it a second time: ${readRoute}. Ask through a server process that still has a live engine client.`;
@@ -1082,9 +1086,9 @@ var EngineJsonRpcClient = class {
1082
1086
  if (backlog > ENGINE_WRITE_BACKLOG_BYTES) {
1083
1087
  this.pending.delete(id);
1084
1088
  this.reject(entry, new EngineCallError(
1085
- GP_E_ENGINE_UNREACHABLE,
1089
+ GP_E_ENGINE_BACKLOG,
1086
1090
  `not sending '${method}': ${backlog} bytes are already queued for the engine socket and it has not read them`,
1087
- `the background service is accepting the connection but not reading it \u2014 it is wedged, not busy. Check it with gp_probe_status; if it stays like this, restart it with the command in \`glasspaned --help\` (or the panel's \u91CD\u542F\u540E\u53F0\u670D\u52A1 button). Do not retry this call in a loop: every retry only deepens the queue it is refusing to drain.`
1091
+ writeBacklogRemedy(backlog, ENGINE_WRITE_BACKLOG_BYTES)
1088
1092
  ));
1089
1093
  return;
1090
1094
  }
@@ -3661,7 +3665,7 @@ var SUPPORTED_PROTOCOL_VERSIONS = [
3661
3665
  "2025-03-26",
3662
3666
  MCP_PROTOCOL_VERSION
3663
3667
  ];
3664
- var SERVER_INFO = { name: "glasspane-mcp", version: "1.7.0" };
3668
+ var SERVER_INFO = { name: "glasspane-mcp", version: "1.8.2" };
3665
3669
  var PARSE_ERROR = -32700;
3666
3670
  var INVALID_REQUEST = -32600;
3667
3671
  var METHOD_NOT_FOUND = -32601;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glasspane-mcp",
3
- "version": "1.7.0",
3
+ "version": "1.8.2",
4
4
  "description": "GlassPane MCP stdio shell: JSON-RPC 2.0 over stdio bridging an AI agent to the glasspane engine daemon",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",