glasspane-mcp 1.2.0 → 1.3.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.
@@ -20,8 +20,23 @@ export interface TrailTurn {
20
20
  /** Hand the trail on: this turn is done with it, or never will be. */
21
21
  release(): void;
22
22
  }
23
+ /**
24
+ * Handed to {@link EvidenceAuditSession.recordLateArrival} when the request that
25
+ * produced a late reply never said which generation it belonged to. It is
26
+ * deliberately not `undefined` and not a number the real epochs can ever reach:
27
+ * an unattributed frame is *known* unattributed, and refusing it is the default.
28
+ */
29
+ export declare const UNKNOWN_GENERATION = -1;
30
+ /** Where a late arrival ended up: written, or refused because a re-attach got there first. */
31
+ export interface LateArrivalOutcome {
32
+ written: boolean;
33
+ /** The trail generation in force at the moment of the decision. */
34
+ generationAtWrite: number;
35
+ }
23
36
  export declare class EvidenceAuditSession {
24
37
  private ids;
38
+ private epoch;
39
+ private attachedAppKey;
25
40
  /**
26
41
  * Turns claimed but not released. This, not a "latest gate", is what a new
27
42
  * claim waits for: a turn that abandons the trail (its engine call failed
@@ -34,15 +49,68 @@ export declare class EvidenceAuditSession {
34
49
  /** Record every operationId/evidenceId found in an engine result frame. */
35
50
  record(result: unknown): void;
36
51
  /**
37
- * Clear the trail (called after a successful attach).
52
+ * The trail's generation number. Every {@link restart} bumps it.
53
+ *
54
+ * It exists so a reply that arrives *after* its caller was already answered
55
+ * can be judged against the trail it was written into: {@link recordLateArrival}
56
+ * takes the epoch the request was admitted under, and a mismatch means a
57
+ * re-attach happened in between. Without it, a late write lands in a *new*
58
+ * session's trail — an operationId from the previous app that
59
+ * `gp_recent_reports` then fetches and renders as if it belonged to this one
60
+ * (R8b-高1, measured: `['op_OLDAPP']` in the trail after attaching a second
61
+ * app).
62
+ */
63
+ get generation(): number;
64
+ /** The app the trail currently belongs to, or null before the first attach. */
65
+ get attachedApp(): string | null;
66
+ /**
67
+ * A successful attach: the trail restarts **only when the attached app
68
+ * changed**, because that is the daemon's own rule —
69
+ * `EngineCore.attach` clears `history` under
70
+ * `if attachedApp != app`, and its comment says re-attach to the same app is
71
+ * idempotent. Clearing on *every* attach (what this used to do) is strictly
72
+ * worse in two directions: an idempotent re-attach wiped operations that were
73
+ * really in the trail (including one recorded by a late reply, which put
74
+ * `gp_recent_reports` back to "run gp_act first"), and — because the clear
75
+ * happens whenever the reply lands — it could also drop the record of an act
76
+ * the daemon still has.
77
+ *
78
+ * `appKey` must be built from every stored property of Swift's `AttachedApp`
79
+ * (`pid`, `bundleId`, `appName`); a key that ignored `pid` would keep the
80
+ * trail across an app restart whose history the daemon did clear. The gate
81
+ * that pins this to the Swift struct is
82
+ * `test/attach-identity.test.mjs`.
38
83
  *
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.
84
+ * Returns whether the trail was restarted. A null `appKey` means the attach
85
+ * result did not identify the app at all, which is treated as a change: an
86
+ * unknown identity cannot be *shown* to be the same app, and a stale trail is
87
+ * the failure that misattributes evidence.
42
88
  */
43
- reset(): void;
89
+ restart(appKey: string | null): boolean;
44
90
  /** The most recent `limit` recorded operationIds, oldest first. */
45
91
  recentIds(limit: number): string[];
92
+ /**
93
+ * Record a frame that arrives with **no request of the caller still holding a
94
+ * turn**: a late engine reply (R8-高3, `EngineJsonRpcClient.onLateReply`). Its
95
+ * own turn was released the moment the timeout was reported to the agent, so
96
+ * there is no request-order position left to write at.
97
+ *
98
+ * It still goes through the turn machinery rather than a bare `record()`,
99
+ * because a direct write could land *between* two admitted calls and reorder
100
+ * the trail against the requests the agent made — the exact thing the turns
101
+ * exist to prevent. Claiming on arrival gives the only honest position left:
102
+ * behind whatever is still outstanding, and ordered among late arrivals by
103
+ * when they arrived.
104
+ *
105
+ * `admittedUnderGeneration` is compared **inside** that wait (see the body):
106
+ * the position and the epoch can only be judged at the moment the write
107
+ * actually happens. A reply whose request carried no generation cannot be
108
+ * placed either — so it is passed as {@link UNKNOWN_GENERATION} and the caller
109
+ * decides whether that is acceptable; this method refuses it by default, because
110
+ * "we do not know which attach this belongs to" is not a reason to write into
111
+ * somebody else's trail.
112
+ */
113
+ recordLateArrival(apply: (session: EvidenceAuditSession) => void, admittedUnderGeneration?: number): Promise<LateArrivalOutcome>;
46
114
  /**
47
115
  * Claim the next position in this session's trail request order, so that
48
116
  * overlapping calls mutate and read `ids` in the order they were admitted
@@ -55,4 +123,13 @@ export declare class EvidenceAuditSession {
55
123
  */
56
124
  claimTrailTurn(): TrailTurn;
57
125
  }
126
+ /**
127
+ * Bounded recursive walk (depth ≤ 3) that collects string-valued
128
+ * `operationId` / `evidenceId` fields — from act/assert/diagnose/restore
129
+ * results and from nested `evidencePack` frames. Depth is capped so an
130
+ * adversarial frame cannot force an unbounded descent.
131
+ */
132
+ /** The same bounded walk, as the shell's one way to ask "does this frame name an
133
+ * operation?" — the late-reply wiring needs it to say *what* it dropped. */
134
+ export declare function operationIds(value: unknown): string[];
58
135
  //# sourceMappingURL=audit-session.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.2.0";
29
+ readonly version: "1.3.0";
30
30
  };
31
31
  export declare const PARSE_ERROR = -32700;
32
32
  export declare const INVALID_REQUEST = -32600;
@@ -46,8 +46,23 @@ export interface RpcResponse {
46
46
  export interface McpServerDeps {
47
47
  engine: EngineJsonRpcClient;
48
48
  specs?: readonly ToolSpec[];
49
- /** Overridable audit session (per-server operationId trail, spec v1.3 §10.3). */
50
- session?: EvidenceAuditSession;
49
+ /**
50
+ * The per-server operationId trail (spec v1.3 §10.3). **Required**: it used to
51
+ * be optional with a `?? new EvidenceAuditSession()` default, which let a
52
+ * server be built with a trail nobody else holds a reference to — every tool
53
+ * result then looked correct while `gp_recent_reports` answered from an empty
54
+ * trail, and the late-reply sink was wired to a *different* session. The
55
+ * composition is `createTrackedMcpServer`'s job now, so the default had no
56
+ * legitimate user (R8b-低).
57
+ */
58
+ session: EvidenceAuditSession;
59
+ /**
60
+ * Sink for facts the protocol gives no answer to: a notification this shell
61
+ * does not act on. Without it `notifications/cancelled` — a client saying it
62
+ * has given up on a request — vanished with zero trace while the daemon
63
+ * request it refers to stayed in flight and kept acting on the user's screen.
64
+ */
65
+ report?: (note: string) => void;
51
66
  }
52
67
  export declare class McpServer {
53
68
  private readonly deps;
@@ -72,4 +87,24 @@ export declare class McpServer {
72
87
  private result;
73
88
  private error;
74
89
  }
90
+ /**
91
+ * Wire an engine client to a server over one shared audit session, including the
92
+ * late-reply sink (R8-高3).
93
+ *
94
+ * This is a function rather than four lines inside `main()` so that the test for
95
+ * "a reply that lands after the caller was answered still reaches the trail"
96
+ * drives **the** wiring an MCP client actually gets. Inlining it would leave the
97
+ * only copy of that decision in a file that runs `main()` on import, i.e. a fix
98
+ * on a path no test can execute.
99
+ *
100
+ * The session is created here and handed to `McpServer` rather than left to its
101
+ * default, because the engine client must reach the *same* instance: a late
102
+ * reply names an operation that really ran, and the only place an agent can get
103
+ * that back from is this trail (`gp_recent_reports`). A sink that recorded into
104
+ * a second session would look fixed and change nothing.
105
+ */
106
+ export declare function createTrackedMcpServer(engine: EngineJsonRpcClient, report: (note: string) => void): {
107
+ server: McpServer;
108
+ session: EvidenceAuditSession;
109
+ };
75
110
  //# sourceMappingURL=dispatch.d.ts.map
@@ -36,8 +36,26 @@ export declare const ENGINE_SOCKET_ENV = "GLASSPANE_ENGINE_SOCK";
36
36
  *
37
37
  * Deliberately not "fixed" by asking the daemon: before a connection exists
38
38
  * there is no daemon to ask, and `hello` is itself answered after connecting.
39
- * Matching the daemon's home lookup from Node would take an FFI call to
40
- * `NSHomeDirectory()`, which this shell does not have.
39
+ *
40
+ * R8-中9, and read this before "harmonising" it with `systemHome()`: matching the
41
+ * daemon's lookup from Node **is** available — `os.userInfo().homedir` reads the
42
+ * user record, the same source `NSHomeDirectory()` uses, no FFI needed, and
43
+ * `project-registry.ts`'s `systemHome()` does exactly that. This function
44
+ * deliberately does not. The two files sit on opposite sides of a safety
45
+ * question:
46
+ * - `projects.json` is *our* data: naming a file the daemon cannot load turns
47
+ * a write into a stray, so it must follow the daemon's rule.
48
+ * - the engine socket is *the user's screen*: a process that pointed `$HOME`
49
+ * somewhere private as its own isolation boundary currently fails to connect
50
+ * ("nothing is listening on the guessed path", an honest refusal). Resolve
51
+ * the home the daemon's way here and that same process silently drives the
52
+ * live daemon — clicks, state writes, the whole act surface — against a
53
+ * machine it asked not to be on. `$HOME`-redirect is not a supported way to
54
+ * isolate from a daemon (name the socket, or don't), but it must not be
55
+ * upgraded into a way to reach it either.
56
+ *
57
+ * `test/engine-client.test.mjs` pins both halves of that split, so changing
58
+ * either side is a deliberate edit against a red test rather than a cleanup.
41
59
  *
42
60
  * Single source all the same: the CLI argument parser in index.ts and the
43
61
  * reconnecting transport both resolve the path through this function, so an env
@@ -46,6 +64,22 @@ export declare const ENGINE_SOCKET_ENV = "GLASSPANE_ENGINE_SOCK";
46
64
  export declare function defaultSocketPath(): string;
47
65
  /** Fallback deadline for a method missing from the table below. */
48
66
  export declare const ENGINE_TIMEOUT_MS = 10000;
67
+ /**
68
+ * B-02: how long a *caller* may be kept waiting for any method, whatever the
69
+ * daemon's own worst case is.
70
+ *
71
+ * A real MCP client times a single request out at around 60 s, stops waiting,
72
+ * and the agent — holding no answer, no code and no remedy — re-issues the
73
+ * call. On `act` that re-issue performs the same click on the user's screen a
74
+ * second time, which is precisely the harm a deadline is meant to remove. So
75
+ * the ceiling sits below a client's patience: the shell answers at 50 s with
76
+ * `GP_E_ENGINE_TIMEOUT` and the no-replay remedy, and the request stays
77
+ * registered, so the reply that arrives later is still attributed and written
78
+ * to the log (`onDeadline` -> `reportLateReply`). Nothing is discarded by
79
+ * answering early, which is what makes the multi-minute deadlines above safe to
80
+ * cap: they describe the daemon, not the caller's wait.
81
+ */
82
+ export declare const CALLER_VISIBLE_CEILING_MS = 50000;
49
83
  /**
50
84
  * Per-method wall-clock deadlines. 10 s is the daemon's *internal* latency
51
85
  * flag (`EngineCore.performanceLatencyBudgetMs`), not its worst-case wall
@@ -60,9 +94,23 @@ export declare const ENGINE_TIMEOUT_MS = 10000;
60
94
  * method below the ceiling, and above it they are the daemon-side worst case
61
95
  * the timeout text quotes — the caller answered at the ceiling, while the
62
96
  * request stays registered so the real reply is attributed when it lands.
97
+ *
98
+ * `probe_status` is deliberately *not* "the cheap call, so give it the shortest
99
+ * window". The daemon answers one request at a time, so a probe is queued
100
+ * behind whatever the agent is standing over, and {@link slowEngineRemedy}
101
+ * tells the agent to send exactly this probe *while an `act` is outstanding*. A
102
+ * probe with a shorter window than the request in front of it expires against a
103
+ * busy daemon, and the agent then reads "the probe timed out too" as permission
104
+ * to restart — which cancels and rolls back an action mid-flight on the user's
105
+ * screen. It gets the full client-safe wait so the busy-but-will-finish case is
106
+ * answerable at all; the case where it is not (`act` up to 120 s against a 50 s
107
+ * ceiling — no deadline here can cover it, since the ceiling is the client's
108
+ * patience, not the daemon's) is what
109
+ * {@link livenessProbeDecision}'s `timed-out` entry exists to disarm.
110
+ * `test/probe-liveness.test.mjs` pins both halves: the armed timer, and the
111
+ * arithmetic invariant that no method can outlive the probe meant to diagnose it.
63
112
  */
64
113
  export declare const ENGINE_DEADLINES_MS: {
65
- readonly hello: 15000;
66
114
  readonly attach: 30000;
67
115
  readonly act: 120000;
68
116
  readonly observe: 45000;
@@ -70,31 +118,18 @@ export declare const ENGINE_DEADLINES_MS: {
70
118
  readonly diagnose: 30000;
71
119
  readonly last_evidence: 60000;
72
120
  readonly snapshot: 90000;
73
- readonly probe_status: 15000;
121
+ readonly probe_status: 50000;
122
+ readonly hello: 50000;
74
123
  readonly shutdown: 15000;
124
+ readonly audit_ui: 45000;
125
+ readonly capture_view: 30000;
75
126
  };
76
127
  /** Fixed part of an ffwd restore deadline (per-step cost is `act` above). */
77
128
  export declare const RESTORE_BASE_DEADLINE_MS = 30000;
78
- /**
79
- * B-02: how long a *caller* may be kept waiting for any method, whatever the
80
- * daemon's own worst case is.
81
- *
82
- * A real MCP client times a single request out at around 60 s, stops waiting,
83
- * and the agent — holding no answer, no code and no remedy — re-issues the
84
- * call. On `act` that re-issue performs the same click on the user's screen a
85
- * second time, which is precisely the harm a deadline is meant to remove. So
86
- * the ceiling sits below a client's patience: the shell answers at 50 s with
87
- * `GP_E_ENGINE_TIMEOUT` and the no-replay remedy, and the request stays
88
- * registered, so the reply that arrives later is still attributed and written
89
- * to the log (`onDeadline` -> `reportLateReply`). Nothing is discarded by
90
- * answering early, which is what makes the multi-minute deadlines above safe to
91
- * cap: they describe the daemon, not the caller's wait.
92
- */
93
- export declare const CALLER_VISIBLE_CEILING_MS = 50000;
94
- /** When the caller gets its answer for `method`: its deadline, capped. */
95
- export declare function callerDeadlineMs(method: string, params?: Record<string, unknown>, ceilingMs?: number): number;
96
129
  /** Deadline for one request; `restore` scales with its replayed step count. */
97
130
  export declare function engineDeadlineMs(method: string, params?: Record<string, unknown>): number;
131
+ /** When the caller gets its answer for `method`: its deadline, capped. */
132
+ export declare function callerDeadlineMs(method: string, params?: Record<string, unknown>, ceilingMs?: number): number;
98
133
  /** Engine protocol version this shell speaks (daemon `hello.protocolVersion`). */
99
134
  export declare const ENGINE_PROTOCOL_VERSION = "0";
100
135
  /** Daemon self-report the shell compares a fresh connection against. */
@@ -126,8 +161,64 @@ export declare class EngineCallError extends Error {
126
161
  constructor(code: string, message: string, remedy: string);
127
162
  toBody(): EngineErrorBody;
128
163
  }
129
- /** Remedy for a deadline overrun: wait and measure, never restart or replay. */
130
- export declare function slowEngineRemedy(method: string): string;
164
+ /**
165
+ * What an agent can learn from a `gp_probe_status` it sent while another call is
166
+ * outstanding, and — the part that was wrong before — what each answer
167
+ * authorises.
168
+ *
169
+ * R8-高1: the sentence this replaces told the agent to "restart only if
170
+ * gp_probe_status times out too". The daemon serves **one request at a time**,
171
+ * so a probe is queued behind the very request it is meant to diagnose: against
172
+ * a busy daemon "the probe timed out as well" is the *expected* answer, not
173
+ * evidence of death. Following it therefore meant running `--restore-launchd`
174
+ * against a daemon that is mid-`act` on the user's screen — and SIGTERM makes
175
+ * that daemon cancel and roll back the in-flight action (`main.swift`'s early
176
+ * shutdown path). A liveness check whose failure branch is reached exactly when
177
+ * the thing it checks is alive is not a check. Only a transport outcome can tell
178
+ * "gone" from "busy", so every outcome now states its own decision, and
179
+ * `test/probe-liveness.test.mjs` pins that exactly one of them may name the
180
+ * restore command.
181
+ */
182
+ export type LivenessProbeOutcome = "answered" | "engine-error" | "timed-out" | "unreachable";
183
+ /** Every outcome the four sentences below cover; the gate iterates this list. */
184
+ export declare const LIVENESS_PROBE_OUTCOMES: readonly LivenessProbeOutcome[];
185
+ /**
186
+ * The decision for one probe outcome. There is no `default` on purpose: adding
187
+ * an outcome to {@link LIVENESS_PROBE_OUTCOMES} without deciding what it
188
+ * authorises is a compile error rather than a silent hole.
189
+ *
190
+ * Only `unreachable` may name the restore command, because only it is raised
191
+ * when the socket itself is gone ({@link GP_E_ENGINE_UNREACHABLE}); `timed-out`
192
+ * is {@link GP_E_ENGINE_TIMEOUT}, which by construction means "no answer yet".
193
+ * The lookup is a function, not a frozen object, so the filesystem probe inside
194
+ * `daemonUnreachableRemedy()` stays out of module initialisation.
195
+ */
196
+ export declare function livenessProbeDecision(outcome: LivenessProbeOutcome): string;
197
+ /**
198
+ * Which way into this shell an answer is being delivered to. The two surfaces
199
+ * hold different state: only the MCP stdio server has an
200
+ * `EvidenceAuditSession`, so only it can promise that a late reply's
201
+ * operationId will come back through `gp_recent_reports`. Promising that to a
202
+ * `curl` caller of the HTTP gateway sends it to a tool that route does not even
203
+ * expose (`POST /v1/tools/recent_reports` is a 404 — `FORWARDABLE_TOOLS` skips
204
+ * every tool with its own `execute`), so each surface gets the sentence about
205
+ * the state it really has (R8b-高3).
206
+ */
207
+ export type ShellSurface = "mcp" | "http";
208
+ /** Where the answer to a timed-out request can be found, per surface. */
209
+ export declare function lateReplyRoute(surface: ShellSurface): string;
210
+ /**
211
+ * Remedy for a deadline overrun: wait and measure, never restart on a probe that
212
+ * merely went unanswered, and never replay a user-visible action.
213
+ */
214
+ export declare function slowEngineRemedy(method: string, surface?: ShellSurface): string;
215
+ /** One late engine reply, as handed to {@link EngineJsonRpcClient.onLateReply}. */
216
+ export interface LateReply {
217
+ method: string;
218
+ result: unknown;
219
+ /** Whatever `call()` was given; see {@link Pending.correlation}. */
220
+ correlation?: number;
221
+ }
131
222
  /**
132
223
  * Request/response association over a line transport. Kept independent of
133
224
  * the transport so tests inject a fake LineIo (spec §7.1 "fake engine
@@ -144,10 +235,13 @@ export declare class EngineJsonRpcClient {
144
235
  * registration.
145
236
  */
146
237
  private readonly callerCeilingMs;
238
+ /** Whom a timeout's remedy is being written for; see {@link ShellSurface}. */
239
+ private readonly surface;
147
240
  private nextId;
148
241
  private closed;
149
242
  private readonly pending;
150
243
  private noteHandler;
244
+ private lateReplyHandler;
151
245
  constructor(io: LineIo, timeoutMs?: number | undefined, identity?: EngineIdentityExpectation | null,
152
246
  /**
153
247
  * Ceiling on the caller's wait (B-02). Injectable because a test cannot
@@ -155,7 +249,9 @@ export declare class EngineJsonRpcClient {
155
249
  * caller with a longer patience gets a longer wait, never a longer
156
250
  * registration.
157
251
  */
158
- callerCeilingMs?: number);
252
+ callerCeilingMs?: number,
253
+ /** Whom a timeout's remedy is being written for; see {@link ShellSurface}. */
254
+ surface?: ShellSurface);
159
255
  /**
160
256
  * Sink for facts that cannot be attached to any request of the caller: a
161
257
  * late reply, a frame with an id this client never issued, a daemon that
@@ -163,8 +259,24 @@ export declare class EngineJsonRpcClient {
163
259
  * "not measured" turns into a confident wrong story.
164
260
  */
165
261
  onEngineNote(handler: (note: string) => void): void;
166
- /** Send a request and await its matching response frame. */
167
- call(method: string, params?: Record<string, unknown>): Promise<unknown>;
262
+ /**
263
+ * Sink for the *body* of a reply that lands after its caller was already
264
+ * answered (R8-高3). Logging it is not enough: the operationId in that frame
265
+ * is the only thing that puts the operation into this session's evidence
266
+ * trail, and without it `gp_recent_reports` answers
267
+ * `GP_E_NO_EVIDENCE ... run gp_act first` for an act that genuinely ran and
268
+ * genuinely mutated the user's screen — an agent reading that answer clicks
269
+ * again. The entry the reply is attributed to is only kept for
270
+ * `LATE_REPLY_RETENTION` requests, so this sink sees the tracked window and
271
+ * the eviction note says when something fell outside it.
272
+ */
273
+ onLateReply(handler: (reply: LateReply) => void): void;
274
+ /**
275
+ * Send a request and await its matching response frame. `correlation` is
276
+ * echoed to {@link onLateReply} if this request is answered after its caller
277
+ * has already been given a timeout; it is otherwise unused by this client.
278
+ */
279
+ call(method: string, params?: Record<string, unknown>, correlation?: number): Promise<unknown>;
168
280
  close(): void;
169
281
  /**
170
282
  * Timeout path: settle the caller with a *slow* diagnosis but keep the
@@ -219,6 +331,8 @@ export declare function unixSocketEngineClient(socketPath: string, options?: {
219
331
  identity?: EngineIdentityExpectation;
220
332
  open?: EngineTransportOpener;
221
333
  callerCeilingMs?: number;
334
+ /** Pass `"http"` from the gateway: its callers have no operation trail. */
335
+ surface?: ShellSurface;
222
336
  }): EngineJsonRpcClient;
223
337
  /**
224
338
  * Build a client over an existing LineIo (used by tests with a fake
package/dist/errors.d.ts CHANGED
@@ -11,15 +11,45 @@ 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
12
  /**
13
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.
14
+ * exist twice with different spellings (X-15).
15
+ *
16
+ * `GP_E_ENGINE_TIMEOUT` is shell-only: the daemon's `GPErrorCode` enum has no
17
+ * counterpart, because it describes a deadline *this process* chose to stop
18
+ * waiting on while the daemon may still be working. An agent must be able to
19
+ * tell that apart from a daemon error, and the daemon's vocabulary is the only
20
+ * place that could add it.
21
+ *
22
+ * `GP_E_PAYLOAD_TOO_LARGE` is **not** shell-only — the daemon declares it too
23
+ * (`GPErrorCode.payloadTooLarge`, raised by its dispatcher for an oversized
24
+ * request frame), and this shell uses the same code for an oversized frame on
25
+ * *its* side of the stdio/HTTP boundary. Sharing the code is deliberate: one
26
+ * meaning ("that frame was too big"), whichever side hit the limit. It is
27
+ * spelled here rather than imported because there is no shared source for the
28
+ * two languages, and `test/remedy-surface.test.mjs` is what keeps this
29
+ * paragraph true: it reads the daemon's enum and fails if any code labelled
30
+ * shell-only ever appears in it, or if a code this file exports stops being
31
+ * either shared or genuinely shell-only.
20
32
  */
21
33
  export declare const GP_E_ENGINE_TIMEOUT = "GP_E_ENGINE_TIMEOUT";
22
34
  export declare const GP_E_PAYLOAD_TOO_LARGE = "GP_E_PAYLOAD_TOO_LARGE";
35
+ /**
36
+ * Shell-only, and deliberately *not* a daemon code: this is what the shell
37
+ * answers when a daemon error frame arrives with no `code` at all. Naming it
38
+ * here (rather than inlining a string at the one call site, where it used to
39
+ * live) keeps it inside the same vocabulary an agent can look up, and
40
+ * `test/remedy-surface.test.mjs` checks it really is absent from the daemon's
41
+ * enum — if the daemon ever declares it, this fallback stops being a shell-side
42
+ * invention and the split has to be re-read.
43
+ */
44
+ export declare const GP_E_UNKNOWN = "GP_E_UNKNOWN";
45
+ /**
46
+ * Shell-only: this process could not resolve the user whose state directory is
47
+ * meant (`os.userInfo()` has no record). Distinct from `GP_E_INTERNAL` because
48
+ * the two have different remedies — one names a file to repair, the other names
49
+ * the account this shell is running under — and sending an agent to open
50
+ * `projects.json` for the second case sends it to a file that is fine.
51
+ */
52
+ export declare const GP_E_NO_USER_RECORD = "GP_E_NO_USER_RECORD";
23
53
  export interface ToolErrorText {
24
54
  code: string;
25
55
  message: string;