glasspane-mcp 0.1.0 → 1.1.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.
Files changed (43) hide show
  1. package/README.md +20 -1
  2. package/dist/audit-session.d.ts +37 -1
  3. package/dist/dispatch.d.ts +30 -2
  4. package/dist/engine-client.d.ts +156 -8
  5. package/dist/errors.d.ts +11 -0
  6. package/dist/evidence-report.d.ts +26 -10
  7. package/dist/index.js +1327 -214
  8. package/dist/io.d.ts +94 -5
  9. package/dist/project-registry.d.ts +78 -34
  10. package/dist/tools.d.ts +24 -4
  11. package/package.json +2 -2
  12. package/dist/audit-session.d.ts.map +0 -1
  13. package/dist/audit-session.js +0 -64
  14. package/dist/audit-session.js.map +0 -1
  15. package/dist/canonical.d.ts.map +0 -1
  16. package/dist/canonical.js +0 -23
  17. package/dist/canonical.js.map +0 -1
  18. package/dist/dispatch.d.ts.map +0 -1
  19. package/dist/dispatch.js +0 -122
  20. package/dist/dispatch.js.map +0 -1
  21. package/dist/engine-client.d.ts.map +0 -1
  22. package/dist/engine-client.js +0 -135
  23. package/dist/engine-client.js.map +0 -1
  24. package/dist/errors.d.ts.map +0 -1
  25. package/dist/errors.js +0 -22
  26. package/dist/errors.js.map +0 -1
  27. package/dist/evidence-report.d.ts.map +0 -1
  28. package/dist/evidence-report.js +0 -161
  29. package/dist/evidence-report.js.map +0 -1
  30. package/dist/index.d.ts.map +0 -1
  31. package/dist/index.js.map +0 -1
  32. package/dist/io.d.ts.map +0 -1
  33. package/dist/io.js +0 -74
  34. package/dist/io.js.map +0 -1
  35. package/dist/project-registry.d.ts.map +0 -1
  36. package/dist/project-registry.js +0 -144
  37. package/dist/project-registry.js.map +0 -1
  38. package/dist/tools.d.ts.map +0 -1
  39. package/dist/tools.js +0 -556
  40. package/dist/tools.js.map +0 -1
  41. package/schemas/schemas/decision-log-entry.schema.json +0 -45
  42. package/schemas/schemas/evidence-pack.schema.json +0 -359
  43. package/schemas/schemas/recipe-config.schema.json +0 -36
package/README.md CHANGED
@@ -3,7 +3,19 @@
3
3
  GlassPane 的 MCP 服务器(stdio):把 AI 客户端的 `gp_*` 工具调用转发给本机
4
4
  GlassPane 后台服务,并把每一次界面操作的证据带回给你。
5
5
 
6
- ## 用法
6
+ This package is the **transport layer only**. It needs the GlassPane daemon
7
+ (`glasspaned`) running on this Mac, which the installer provides — installing this
8
+ package alone does not give you a working verification engine.
9
+
10
+ 本包只是转发层:它需要本机已在跑 GlassPane 后台服务,而该服务由安装器产出。
11
+ 只装本包不会得到可用的验证引擎——先装引擎:
12
+
13
+ ```bash
14
+ curl -fsSL https://raw.githubusercontent.com/jingzhao-l/GlassPane/main/install.sh | sh
15
+ # 或:npx glasspane-install
16
+ ```
17
+
18
+ ## 用法 / Usage
7
19
 
8
20
  ```bash
9
21
  npm i -g glasspane-mcp
@@ -21,4 +33,11 @@ MCP 客户端配置:
21
33
  按错误里的命令执行即可,无需查阅其它文档。
22
34
 
23
35
  工具清单与语义以 `gp_*` 运行时 `tools/list` 返回为准。
36
+
37
+ ## What ships in this package / 包里有什么
38
+
39
+ `dist/index.js` 是 esbuild 单文件产物:`@iterate/kernel`(schema 真源)在发布时
40
+ 已捆绑内联,包内不存在指向源码仓库的 `file:` 依赖;`schemas/` 是 kernel schema
41
+ 的发布副本,每次 `npm run bundle` 重新同步。
42
+
24
43
  License: MIT.
@@ -10,13 +10,49 @@
10
10
  * and simply restarts the trail). Does not persist across process restarts.
11
11
  */
12
12
  export declare const AUDIT_HISTORY_LIMIT = 20;
13
+ /**
14
+ * One position in a session's trail *request order*, handed out by
15
+ * {@link EvidenceAuditSession.claimTrailTurn}. `release` is idempotent.
16
+ */
17
+ export interface TrailTurn {
18
+ /** Wait until every turn claimed before this one has released. */
19
+ acquire(): Promise<void>;
20
+ /** Hand the trail on: this turn is done with it, or never will be. */
21
+ release(): void;
22
+ }
13
23
  export declare class EvidenceAuditSession {
14
24
  private ids;
25
+ /**
26
+ * Turns claimed but not released. This, not a "latest gate", is what a new
27
+ * claim waits for: a turn that abandons the trail (its engine call failed
28
+ * before any mutation) releases *out of order*, and a chain that only looked
29
+ * at the immediately preceding turn would let everything behind it run while
30
+ * an older, still-outstanding act had not written yet — the exact completion
31
+ * order the turn machinery exists to remove.
32
+ */
33
+ private openGates;
15
34
  /** Record every operationId/evidenceId found in an engine result frame. */
16
35
  record(result: unknown): void;
17
- /** Clear the trail (called after a successful attach). */
36
+ /**
37
+ * Clear the trail (called after a successful attach).
38
+ *
39
+ * Deliberately does *not* touch {@link openGates}: outstanding turns are not
40
+ * outstanding mutations, and forgetting them would let a later read overtake
41
+ * an act that is still going to write.
42
+ */
18
43
  reset(): void;
19
44
  /** The most recent `limit` recorded operationIds, oldest first. */
20
45
  recentIds(limit: number): string[];
46
+ /**
47
+ * Claim the next position in this session's trail request order, so that
48
+ * overlapping calls mutate and read `ids` in the order they were admitted
49
+ * rather than in the order the daemon answered.
50
+ *
51
+ * Synchronous on purpose — a claim made after an `await` would itself be
52
+ * ordered by completion. Callers hold a turn only across a `record()` /
53
+ * `reset()` / `recentIds()`, never across an engine round trip, so the queue
54
+ * can delay the moment a reply is built but never a request at the daemon.
55
+ */
56
+ claimTrailTurn(): TrailTurn;
21
57
  }
22
58
  //# sourceMappingURL=audit-session.d.ts.map
@@ -2,11 +2,31 @@ import { EngineJsonRpcClient } from "./engine-client.js";
2
2
  import { ToolSpec } from "./tools.js";
3
3
  import { EvidenceAuditSession } from "./audit-session.js";
4
4
  export declare const JSONRPC: "2.0";
5
- /** MCP protocol version echoed on initialize (spec §6.1). */
5
+ /**
6
+ * This shell's own MCP protocol version — the fallback answer on initialize
7
+ * when the client asks for a version we do not implement (spec §6.1).
8
+ */
6
9
  export declare const MCP_PROTOCOL_VERSION: "2025-06-18";
10
+ /**
11
+ * MCP revisions this shell genuinely interoperates with, i.e. the ones
12
+ * initialize echoes back. A version is listed only when everything the shell
13
+ * answers is part of that revision's own dialect: it implements
14
+ * `initialize`/`ping`/`tools/list`/`tools/call` over newline-delimited JSON-RPC
15
+ * 2.0 on stdio, advertises the `tools` capability alone with
16
+ * `listChanged: false`, sends no batch request and no notification it has not
17
+ * been asked for, and returns `content` text without `structuredContent`,
18
+ * `outputSchema`, sampling, roots, elicitation or streaming. All of that is
19
+ * inside the oldest revision below, so echoing it back promises nothing the
20
+ * shell cannot deliver (spec §6.1: 回显客户端版本,不识别时回落本常量).
21
+ *
22
+ * Anything newer or otherwise unknown falls back to {@link
23
+ * MCP_PROTOCOL_VERSION} — including a *newer* revision: this shell has not been
24
+ * shown against it, so it does not claim it.
25
+ */
26
+ export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
7
27
  export declare const SERVER_INFO: {
8
28
  readonly name: "glasspane-mcp";
9
- readonly version: "0.1.0";
29
+ readonly version: "1.1.1";
10
30
  };
11
31
  export declare const PARSE_ERROR = -32700;
12
32
  export declare const INVALID_REQUEST = -32600;
@@ -38,6 +58,14 @@ export declare class McpServer {
38
58
  handleLine(line: string): Promise<RpcResponse | null>;
39
59
  private handleNotification;
40
60
  private dispatch;
61
+ /**
62
+ * Spec §6.1: echo the client's protocolVersion when this server implements it
63
+ * (the shipped {@link SUPPORTED_PROTOCOL_VERSIONS}; see that list for what
64
+ * "implements" means here), otherwise fall back to MCP_PROTOCOL_VERSION. A
65
+ * missing or non-string version is just "not recognized" — it falls back too,
66
+ * and never throws.
67
+ */
68
+ private negotiateProtocolVersion;
41
69
  private callTool;
42
70
  private asParams;
43
71
  private isRequestBody;
@@ -1,3 +1,4 @@
1
+ import { Duplex } from "node:stream";
1
2
  import { LineIo } from "./io.js";
2
3
  /**
3
4
  * Agent-actionable remedy for an unreachable daemon (P6 §11 audit items ④/⑥:
@@ -10,10 +11,82 @@ import { LineIo } from "./io.js";
10
11
  export declare function daemonUnreachableRemedy(): string;
11
12
  /** Env override for the daemon socket path (default below). */
12
13
  export declare const ENGINE_SOCKET_ENV = "GLASSPANE_ENGINE_SOCK";
13
- /** Connection/response timeout, per spec §6.2. */
14
- export declare const ENGINE_TIMEOUT_MS = 10000;
15
- /** The daemon listens here by default (spec §3.1). */
14
+ /**
15
+ * The daemon listens here by default (spec §3.1). Single source: the CLI
16
+ * argument parser in index.ts and the reconnecting transport both resolve the
17
+ * path through this function, so an env override cannot be honoured on one side
18
+ * only.
19
+ */
16
20
  export declare function defaultSocketPath(): string;
21
+ /** Fallback deadline for a method missing from the table below. */
22
+ export declare const ENGINE_TIMEOUT_MS = 10000;
23
+ /**
24
+ * Per-method wall-clock deadlines. 10 s is the daemon's *internal* latency
25
+ * flag (`EngineCore.performanceLatencyBudgetMs`), not its worst-case wall
26
+ * time: a tree walk gets 10 s of its own (`AXChannel.treeTimeoutSeconds`), an
27
+ * act reads the tree twice around a 2 s ping, and an ffwd restore replays up
28
+ * to 64 steps (ParamValidation.stepsUpper) by calling act per step. A uniform
29
+ * 10 s deadline diagnoses a slow-but-healthy daemon as "unreachable, restart
30
+ * it" and then throws away the reply that lands later, so every value here is
31
+ * strictly larger than the daemon's own worst case for that method.
32
+ *
33
+ * What these numbers do after B-02: they are still the caller's wait for every
34
+ * method below the ceiling, and above it they are the daemon-side worst case
35
+ * the timeout text quotes — the caller answered at the ceiling, while the
36
+ * request stays registered so the real reply is attributed when it lands.
37
+ */
38
+ export declare const ENGINE_DEADLINES_MS: {
39
+ readonly hello: 15000;
40
+ readonly attach: 30000;
41
+ readonly act: 120000;
42
+ readonly observe: 45000;
43
+ readonly assert_element: 60000;
44
+ readonly diagnose: 30000;
45
+ readonly last_evidence: 60000;
46
+ readonly snapshot: 90000;
47
+ readonly probe_status: 15000;
48
+ readonly shutdown: 15000;
49
+ };
50
+ /** Fixed part of an ffwd restore deadline (per-step cost is `act` above). */
51
+ export declare const RESTORE_BASE_DEADLINE_MS = 30000;
52
+ /**
53
+ * B-02: how long a *caller* may be kept waiting for any method, whatever the
54
+ * daemon's own worst case is.
55
+ *
56
+ * A real MCP client times a single request out at around 60 s, stops waiting,
57
+ * and the agent — holding no answer, no code and no remedy — re-issues the
58
+ * call. On `act` that re-issue performs the same click on the user's screen a
59
+ * second time, which is precisely the harm a deadline is meant to remove. So
60
+ * the ceiling sits below a client's patience: the shell answers at 50 s with
61
+ * `GP_E_ENGINE_TIMEOUT` and the no-replay remedy, and the request stays
62
+ * registered, so the reply that arrives later is still attributed and written
63
+ * to the log (`onDeadline` -> `reportLateReply`). Nothing is discarded by
64
+ * answering early, which is what makes the multi-minute deadlines above safe to
65
+ * cap: they describe the daemon, not the caller's wait.
66
+ */
67
+ export declare const CALLER_VISIBLE_CEILING_MS = 50000;
68
+ /** When the caller gets its answer for `method`: its deadline, capped. */
69
+ export declare function callerDeadlineMs(method: string, params?: Record<string, unknown>, ceilingMs?: number): number;
70
+ /** Deadline for one request; `restore` scales with its replayed step count. */
71
+ export declare function engineDeadlineMs(method: string, params?: Record<string, unknown>): number;
72
+ /** Engine protocol version this shell speaks (daemon `hello.protocolVersion`). */
73
+ export declare const ENGINE_PROTOCOL_VERSION = "0";
74
+ /** Daemon self-report the shell compares a fresh connection against. */
75
+ export interface EngineIdentityExpectation {
76
+ /**
77
+ * Engine release this shell was paired with: the repo keeps one version line,
78
+ * so a difference means the MCP config points at another build. Handed over
79
+ * by the entry point rather than minted here, because a literal in this file
80
+ * would sit outside the version-site guard.
81
+ */
82
+ version?: string;
83
+ }
84
+ /**
85
+ * Shell-side failure codes live in `errors.ts` with the rest of the agent-facing
86
+ * vocabulary (X-15); this file used to declare them locally, which left
87
+ * `GP_E_ENGINE_TIMEOUT` spelled in two places with only a comment keeping them
88
+ * together.
89
+ */
17
90
  /** Error frame returned by the daemon (spec §3.2.1) or raised by the shell. */
18
91
  export interface EngineErrorBody {
19
92
  code: string;
@@ -27,6 +100,8 @@ export declare class EngineCallError extends Error {
27
100
  constructor(code: string, message: string, remedy: string);
28
101
  toBody(): EngineErrorBody;
29
102
  }
103
+ /** Remedy for a deadline overrun: wait and measure, never restart or replay. */
104
+ export declare function slowEngineRemedy(method: string): string;
30
105
  /**
31
106
  * Request/response association over a line transport. Kept independent of
32
107
  * the transport so tests inject a fake LineIo (spec §7.1 "fake engine
@@ -34,18 +109,91 @@ export declare class EngineCallError extends Error {
34
109
  */
35
110
  export declare class EngineJsonRpcClient {
36
111
  private readonly io;
37
- private readonly timeoutMs;
112
+ private readonly timeoutMs?;
113
+ private readonly identity;
114
+ /**
115
+ * Ceiling on the caller's wait (B-02). Injectable because a test cannot
116
+ * wait 50 s, and because the ceiling is a property of *who is asking* — a
117
+ * caller with a longer patience gets a longer wait, never a longer
118
+ * registration.
119
+ */
120
+ private readonly callerCeilingMs;
38
121
  private nextId;
122
+ private closed;
39
123
  private readonly pending;
40
- constructor(io: LineIo, timeoutMs?: number);
124
+ private noteHandler;
125
+ constructor(io: LineIo, timeoutMs?: number | undefined, identity?: EngineIdentityExpectation | null,
126
+ /**
127
+ * Ceiling on the caller's wait (B-02). Injectable because a test cannot
128
+ * wait 50 s, and because the ceiling is a property of *who is asking* — a
129
+ * caller with a longer patience gets a longer wait, never a longer
130
+ * registration.
131
+ */
132
+ callerCeilingMs?: number);
133
+ /**
134
+ * Sink for facts that cannot be attached to any request of the caller: a
135
+ * late reply, a frame with an id this client never issued, a daemon that
136
+ * fails its handshake. Without a sink these stay invisible, which is how
137
+ * "not measured" turns into a confident wrong story.
138
+ */
139
+ onEngineNote(handler: (note: string) => void): void;
41
140
  /** Send a request and await its matching response frame. */
42
141
  call(method: string, params?: Record<string, unknown>): Promise<unknown>;
43
142
  close(): void;
143
+ /**
144
+ * Timeout path: settle the caller with a *slow* diagnosis but keep the
145
+ * entry, so a reply landing afterwards is delivered instead of thrown away.
146
+ * Only the transport going away makes a request unrecoverable.
147
+ */
148
+ private onDeadline;
149
+ /**
150
+ * A settled-by-timeout entry is what makes a late reply attributable, so the
151
+ * retention is bounded oldest-first and the eviction is logged, not silent.
152
+ */
153
+ private retainForLateReply;
44
154
  private handleMessage;
45
- private failAll;
155
+ private settleFromFrame;
156
+ /**
157
+ * A frame over the cap: attribute it to the request it belonged to — from
158
+ * the retained head when the daemon echoed an id, else by arrival order —
159
+ * and fail that one call only.
160
+ */
161
+ private failOversizedReply;
162
+ private engineError;
163
+ /** Oldest request the caller is still waiting on (daemon FIFO order). */
164
+ private oldestOpen;
165
+ private reportLateReply;
166
+ private resolve;
167
+ private reject;
168
+ /**
169
+ * Single settle funnel: clears the deadline timer on every path and marks
170
+ * the entry settled. Callers delete the entry when the transport can no
171
+ * longer answer it, and keep it after a timeout for the late reply.
172
+ */
173
+ private complete;
174
+ /** Reject everything outstanding and drop every handle. */
175
+ private teardown;
176
+ private report;
177
+ /**
178
+ * R4-06: both sides self-report protocol/version and the repo keeps a single
179
+ * version line, yet nothing compared them at runtime — an old daemon under a
180
+ * new shell only surfaced as a mystery schema error later. Handshake once
181
+ * per (re)connection and report the measurement, including "unmeasured".
182
+ */
183
+ private handshake;
184
+ private verifyIdentity;
46
185
  }
47
- /** Production client bound to a Unix socket path (lazy connect on first call). */
48
- export declare function unixSocketEngineClient(socketPath: string): EngineJsonRpcClient;
186
+ /** How a transport stream is opened; injectable so tests need no socket. */
187
+ export type EngineTransportOpener = (socketPath: string) => Duplex;
188
+ /**
189
+ * Production client on a Unix socket: lazy connect, reconnect on demand once
190
+ * the daemon is back, re-handshake per connection.
191
+ */
192
+ export declare function unixSocketEngineClient(socketPath: string, options?: {
193
+ identity?: EngineIdentityExpectation;
194
+ open?: EngineTransportOpener;
195
+ callerCeilingMs?: number;
196
+ }): EngineJsonRpcClient;
49
197
  /**
50
198
  * Build a client over an existing LineIo (used by tests with a fake
51
199
  * transport and by non-socket connectivity).
package/dist/errors.d.ts CHANGED
@@ -9,6 +9,17 @@ export declare const GP_E_NO_EVIDENCE = "GP_E_NO_EVIDENCE";
9
9
  export declare const GP_E_INTERNAL = "GP_E_INTERNAL";
10
10
  export declare const GP_E_PROJECT_LIMIT = "GP_E_PROJECT_LIMIT";
11
11
  export declare const GP_E_NOT_FOUND = "GP_E_NOT_FOUND";
12
+ /**
13
+ * Codes this shell raises for itself, in one place so a string literal cannot
14
+ * exist twice with different spellings (X-15). They are **not** daemon codes:
15
+ * the daemon's `GPErrorCode` enum has no counterpart for either, because both
16
+ * describe a failure of the shell↔engine connection rather than of a request —
17
+ * a deadline this process chose to stop waiting on, and a frame too large to
18
+ * put on the wire. An agent must be able to tell those apart from a daemon
19
+ * error, and the daemon's own vocabulary is the only place that could add them.
20
+ */
21
+ export declare const GP_E_ENGINE_TIMEOUT = "GP_E_ENGINE_TIMEOUT";
22
+ export declare const GP_E_PAYLOAD_TOO_LARGE = "GP_E_PAYLOAD_TOO_LARGE";
12
23
  export interface ToolErrorText {
13
24
  code: string;
14
25
  message: string;
@@ -1,11 +1,26 @@
1
1
  import type { EvidencePack } from "@iterate/kernel";
2
+ /**
3
+ * What a report may be handed: a validated pack, or the same structure with
4
+ * `schemaVersion` widened so it can carry provenance text
5
+ * (`glasspane.evidence/0.1-draft (read as glasspane.evidence/0.1)`). Every
6
+ * `EvidencePack` is assignable here; only the renderer's own first line reads
7
+ * the widened field, so no consumer of the strict type sees this shape.
8
+ */
9
+ export type EvidencePackReportView = Omit<EvidencePack, "schemaVersion"> & {
10
+ schemaVersion: string;
11
+ };
2
12
  /**
3
13
  * Type-mirrored renderer of the engine's `EvidenceReportGenerator`
4
- * (engine/Sources/GlassPaneEngine/EvidenceReportGenerator.swift). Both
5
- * implementations share the same structural contract (same titles, sections,
6
- * summary lines, escaping and placeholder semantics) and are covered by the
7
- * same golden expectations (P1 spec v1.3 §12.1), so a change must be applied
8
- * to both files in the same commit to avoid drift.
14
+ * (engine/Sources/GlassPaneEngine/EvidenceReportGenerator.swift): same title,
15
+ * same summary-line order (attribution / breaker / assert / act / axEvent /
16
+ * handlerProbe / stateDiff / pixelDiff / responsiveness / crash), same
17
+ * escaping, same placeholder semantics (P1 spec v1.3 §10.2).
18
+ *
19
+ * The mirror is guarded by a shared golden, not by this comment:
20
+ * `kernel/fixtures/report.ok-01.md` / `.html` hold the byte-exact output for
21
+ * the pack both suites call `goldenPack()`. `test/evidence-report.test.mjs`
22
+ * compares this side, `EngineP1Batch4Tests` the engine side, so a one-sided
23
+ * change fails a golden (spec v1.3 §12 P1-D1, §13.5 同一 commit 双改).
9
24
  */
10
25
  export interface ReportSection {
11
26
  title: string;
@@ -15,16 +30,17 @@ export interface ReportSection {
15
30
  export declare const PLACEHOLDER = "\u2014";
16
31
  export declare const SECTION_TITLES: readonly ["PATH", "ANOMALY", "EVIDENCE", "NEXT"];
17
32
  /** `operationId · measure · level`, mirroring the engine's reportTitle. */
18
- export declare function reportTitle(pack: EvidencePack): string;
33
+ export declare function reportTitle(pack: EvidencePackReportView): string;
19
34
  /** Attribution / contamination / circuit-breaker / signal summary lines. */
20
- export declare function evidenceSummaryLines(pack: EvidencePack): string[];
35
+ export declare function evidenceSummaryLines(pack: EvidencePackReportView): string[];
21
36
  /** Compact Markdown view (terminal / dialog friendly). */
22
- export declare function renderMarkdown(pack: EvidencePack, diagnostics: string | undefined): string;
37
+ export declare function renderMarkdown(pack: EvidencePackReportView, diagnostics: string | undefined): string;
23
38
  /** Full HTML view with an operationId anchor; all values escaped. */
24
- export declare function renderHTML(pack: EvidencePack, diagnostics: string | undefined): string;
39
+ export declare function renderHTML(pack: EvidencePackReportView, diagnostics: string | undefined): string;
25
40
  /**
26
41
  * Escape HTML metacharacters in values that cross into a report (spec v1.3
27
- * §10.5, no injection). Shared so aggregation headers escape too.
42
+ * §13.4, the five-character escape set, no injection). Shared so aggregation
43
+ * headers escape too.
28
44
  */
29
45
  export declare function escapeHTML(value: string): string;
30
46
  //# sourceMappingURL=evidence-report.d.ts.map