glasspane-mcp 1.19.0 → 1.20.1

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/README.md CHANGED
@@ -11,7 +11,9 @@ package alone does not give you a working verification engine.
11
11
  只装本包不会得到可用的验证引擎——先装引擎:
12
12
 
13
13
  ```bash
14
- curl -fsSL https://raw.githubusercontent.com/jingzhao-l/GlassPane/main/install.sh | sh
14
+ # Fetch install.sh, then run it — two steps on purpose: piped into `sh`, a curl that fails
15
+ # leaves stdin empty and the pipeline exits 0 while nothing was installed.
16
+ curl -fsSL https://raw.githubusercontent.com/jingzhao-l/GlassPane/main/install.sh -o /tmp/gp-install.sh && sh /tmp/gp-install.sh
15
17
  # 或:npx glasspane-install
16
18
  ```
17
19
 
@@ -0,0 +1,64 @@
1
+ /**
2
+ * dimension.ts — 面 A's declared review dimension, shape-checked by the kernel (场景② 的对称半边).
3
+ *
4
+ * WHY THIS FILE EXISTS. The fork surface (面 B) has carried this argument since the run-plan
5
+ * work: an agent says which dimension a call was serving, the shell refuses a malformed id
6
+ * before the daemon sees anything, strips the annotation from the engine frame, and echoes the
7
+ * declaration back as its own content item. This shell is the other doorway to the same engine,
8
+ * and until now it had no such slot — so a run driven through `glasspane-mcp` could not say what
9
+ * a call was for, and the compaction account could only ever read coverage from the fork's own
10
+ * calls. That asymmetry is the whole reason the dimension counter looked healthy in one harness
11
+ * and empty in the other.
12
+ *
13
+ * WHAT THIS IS NOT. The shape comes from `iterate-kernel/dimension-context`
14
+ * (`DIMENSION_ID_PATTERN` / `DIMENSION_ID_MAX_LENGTH`) — the same constants the run plan is
15
+ * validated against — so a plan id and a declaration cannot be spelled two ways. Nothing here
16
+ * knows what a dimension *means*: it refuses malformed tokens and never checks a name against a
17
+ * list, because the list belongs to whoever configured the run.
18
+ *
19
+ * WHY A MALFORMED ID IS REFUSED AND AN ABSENT ONE IS NOT. `dimensionContext` counts a planned
20
+ * dimension as verified only when a recorded decision carries that id. A declaration that reaches
21
+ * the ledger malformed is therefore the lying case — it would be counted as an *unplanned*
22
+ * dimension and silently rename what the session claims it covered. An honest but unread
23
+ * declaration is merely blind, so absence is legitimate and means "this call served no declared
24
+ * dimension"; compaction then reports that dimension as unverified rather than inventing one.
25
+ *
26
+ * Only `gp_last_evidence`'s reply carries an evidence pack, and a pack is what a decision is
27
+ * recorded from (`Dispatcher.swift:156`), so a dimension declared on `gp_attach`, `gp_observe`,
28
+ * `gp_act` or `gp_diagnose` records nothing and its dimension stays reported unverified. It is
29
+ * still accepted and still echoed on those calls, for the reason in the paragraph above: a
30
+ * refused-but-unreadable declaration is a lie, an echoed-but-unrecorded one is a fact the model
31
+ * can see. The echo is a separate content item so `content[0]` stays the daemon's frame byte for
32
+ * byte — the same rule the provider attribution line follows, and the reason the Python relay can
33
+ * classify per item instead of reading a joined string.
34
+ */
35
+ import { type ToolErrorText } from "./errors.js";
36
+ /**
37
+ * The advertised half of the kernel's rule, for the JSON Schemas in `tools.ts`.
38
+ * `test/dimension.test.mjs` compares it against the published
39
+ * `kernel/schemas/*.schema.json` and against 面 B's own copy, so no side can be tightened or
40
+ * loosened alone.
41
+ */
42
+ export declare const DIMENSION_ID_LIMITS: {
43
+ readonly pattern: RegExp;
44
+ readonly maxLength: 64;
45
+ };
46
+ export declare function validateDimension(value: unknown): {
47
+ ok: true;
48
+ dimension?: string;
49
+ } | {
50
+ ok: false;
51
+ error: ToolErrorText;
52
+ };
53
+ /**
54
+ * Drop the shell-side annotation from what the daemon is asked to do. The engine has no
55
+ * `dimension` parameter and never learns the word; exported so the fixed-point test can name the
56
+ * invariant "the engine never sees our annotation" rather than infer it from a passing call.
57
+ */
58
+ export declare function engineBody(params: Record<string, unknown>): Record<string, unknown>;
59
+ /**
60
+ * The echo line — one content item, its own prefix, and it claims only what this shell knows.
61
+ * `none` is never printed: an undeclared call keeps the reply byte-identical.
62
+ */
63
+ export declare function dimensionAttributionLine(dimension: string): string;
64
+ //# sourceMappingURL=dimension.d.ts.map
@@ -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.19.0";
29
+ readonly version: "1.20.1";
30
30
  };
31
31
  export declare const PARSE_ERROR = -32700;
32
32
  export declare const INVALID_REQUEST = -32600;
@@ -7,6 +7,14 @@ import { LineIo } from "./io.js";
7
7
  * restore command: it detects a boot-out'd launchd job, bootstraps it, and
8
8
  * verifies the daemon's hello self-report — the only remaining human action
9
9
  * (TCC checkbox) stays honest. Falls back to generic wording otherwise.
10
+ *
11
+ * The ", then retry" tail belongs here and only here: every caller of this
12
+ * function is an answer for a request that did *not* go out (the write failed,
13
+ * {@link EngineJsonRpcClient.call}'s catch, or no request was in flight at all),
14
+ * so nothing can be performed twice by following it. A request that was already
15
+ * on the wire when the transport died gets {@link transportDeathRemedy}, which
16
+ * keeps the command and drops the tail for the methods whose re-issue is the
17
+ * harm.
10
18
  */
11
19
  export declare function daemonUnreachableRemedy(): string;
12
20
  /** Env override for the daemon socket path (default below). */
@@ -505,6 +513,19 @@ export declare class EngineJsonRpcClient {
505
513
  * uses it (R9-高1) — and fail that one call only.
506
514
  */
507
515
  private failOversizedReply;
516
+ /**
517
+ * The daemon's error frame, mapped into this shell's error.
518
+ *
519
+ * An error frame is a three-field shape — code, message, remedy — and each
520
+ * field has to actually say something for the answer to be usable by an agent.
521
+ * `??` only catches null/undefined, so a frame carrying `code: ""` used to be
522
+ * forwarded verbatim as an error with no name and no cause, while the remedy arm
523
+ * below already treats `""` as absent: one rule for three fields on the same
524
+ * frame. A blank is now read as "not stated" for every one of them, and a
525
+ * non-blank value — including one that merely looks odd — passes through
526
+ * untouched, because this function forwards the daemon's own words and has no
527
+ * authority to rewrite them.
528
+ */
508
529
  private engineError;
509
530
  /**
510
531
  * Oldest request this client still tracks, settled ones included: that is the
@@ -549,6 +570,13 @@ export declare class EngineJsonRpcClient {
549
570
  * "this shell stopped tracking it" from "it never happened" — the difference
550
571
  * that decides whether it re-performs an `act` on the user's screen (R9-中1).
551
572
  * The reverse mutation is to delete this note and keep the silent clear.
573
+ *
574
+ * The message is one sentence for every request — it states what happened to
575
+ * the transport, which is the same fact for all of them — while the remedy is
576
+ * written per method (`remedyFor`): what the caller may do next depends on
577
+ * whether re-issuing that particular request would perform it a second time,
578
+ * and one remedy for the whole batch is how "…, then retry" reached an
579
+ * outstanding `act` at all. See {@link transportDeathRemedy}.
552
580
  */
553
581
  private teardown;
554
582
  private report;
package/dist/errors.d.ts CHANGED
@@ -70,6 +70,52 @@ export declare const GP_E_UNKNOWN = "GP_E_UNKNOWN";
70
70
  * `projects.json` for the second case sends it to a file that is fine.
71
71
  */
72
72
  export declare const GP_E_NO_USER_RECORD = "GP_E_NO_USER_RECORD";
73
+ /**
74
+ * Shell-only: a state root **the daemon reported about itself** (`hello`'s
75
+ * `stateRoot`) names a path this process will neither create nor re-permission —
76
+ * the filesystem root, the account's home directory itself, or a value that is not
77
+ * an absolute path at all.
78
+ *
79
+ * The daemon refuses the same shapes for its own `--state-dir` (`StateRoot.classify`
80
+ * → `StateRoot.notADedicatedRoot`) and then `tightenPermissions` that root to 0700,
81
+ * so the argument parse is the only thing standing between `--state-dir ~` and a
82
+ * locked home directory. It has no code for the refusal because the refusal is an
83
+ * exit message, not an error frame — which is why this shell cannot reuse one
84
+ * either: the root arrives over a socket with **no peer authentication** (SECURITY.md:
85
+ * same uid = full authority), so a value in a reply frame can name `/Users/<you>` and
86
+ * this shell's own `mkdirSync(…, 0700)` + `chmod` would obey it. `test/remedy-surface.test.mjs`
87
+ * checks this stays absent from the daemon's enum, so the split has to be re-read if
88
+ * the daemon ever does declare it.
89
+ *
90
+ * 它**不**列进下面那份 `SHELL_REQUEST_REFUSAL_CODES`:这一码说的不是"你给的参数不对",
91
+ * 而是"服务报了一个我们不能照做的根",改参数重试不会有用;HTTP 那一侧因此按
92
+ * shell-only 的规矩答 503(与 `GP_E_REGISTRY_DRIFT` 同一族:"什么都没写,去看服务"),
93
+ * 而不是 4xx。
94
+ */
95
+ export declare const GP_E_STATE_ROOT_REFUSED = "GP_E_STATE_ROOT_REFUSED";
96
+ /**
97
+ * Shell-only: a declared review dimension is malformed. The daemon has no dimension
98
+ * vocabulary at all — a dimension is the harness's annotation on a call, never an engine
99
+ * parameter — so this code can only ever come from the two surfaces that accept the argument,
100
+ * and `test/remedy-surface.test.mjs` checks it is genuinely absent from the daemon's enum.
101
+ * 面 B (`tool/glasspane/kernel.ts:validateDimension`) spells the same code for the same refusal:
102
+ * an agent that drives the app through either doorway must see one vocabulary, and
103
+ * `mcp-shell/test/dimension.test.mjs` is the leg that compares the two surfaces' tool lists so a
104
+ * slot added on one side reddens on the other.
105
+ */
106
+ export declare const GP_E_DIMENSION = "GP_E_DIMENSION";
107
+ /**
108
+ * Codes this shell authors **about the caller's request**, as opposed to about its own failure.
109
+ *
110
+ * They are absent from the daemon's enum for a reason that has nothing to do with blame: the
111
+ * daemon has no `dimension` concept to declare, because the argument is stripped before the frame
112
+ * is built. So "the daemon does not declare it" cannot be read as "this gateway did not finish the
113
+ * request", and `test/http-gateway.test.mjs` therefore partitions shell-only codes with this list
114
+ * instead of with enum membership — the infrastructure codes (`GP_E_ENGINE_TIMEOUT`,
115
+ * `GP_E_ENGINE_BACKLOG`, …) still may never be answered as a 4xx, while a code listed here must be
116
+ * *proven* to come from argument validation, by the HTTP test that drives the real route.
117
+ */
118
+ export declare const SHELL_REQUEST_REFUSAL_CODES: readonly string[];
73
119
  export interface ToolErrorText {
74
120
  code: string;
75
121
  message: string;