glasspane-mcp 0.1.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/LICENSE +21 -0
- package/README.md +24 -0
- package/dist/audit-session.d.ts +22 -0
- package/dist/audit-session.d.ts.map +1 -0
- package/dist/audit-session.js +64 -0
- package/dist/audit-session.js.map +1 -0
- package/dist/canonical.d.ts +7 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +23 -0
- package/dist/canonical.js.map +1 -0
- package/dist/dispatch.d.ts +47 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +122 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/engine-client.d.ts +54 -0
- package/dist/engine-client.d.ts.map +1 -0
- package/dist/engine-client.js +135 -0
- package/dist/engine-client.js.map +1 -0
- package/dist/errors.d.ts +23 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +22 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence-report.d.ts +30 -0
- package/dist/evidence-report.d.ts.map +1 -0
- package/dist/evidence-report.js +161 -0
- package/dist/evidence-report.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1476 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +51 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +74 -0
- package/dist/io.js.map +1 -0
- package/dist/project-registry.d.ts +76 -0
- package/dist/project-registry.d.ts.map +1 -0
- package/dist/project-registry.js +144 -0
- package/dist/project-registry.js.map +1 -0
- package/dist/tools.d.ts +255 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +556 -0
- package/dist/tools.js.map +1 -0
- package/package.json +51 -0
- package/schemas/decision-log-entry.schema.json +45 -0
- package/schemas/evidence-pack.schema.json +359 -0
- package/schemas/recipe-config.schema.json +36 -0
- package/schemas/schemas/decision-log-entry.schema.json +45 -0
- package/schemas/schemas/evidence-pack.schema.json +359 -0
- package/schemas/schemas/recipe-config.schema.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jingzhao-l
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# glasspane-mcp
|
|
2
|
+
|
|
3
|
+
GlassPane 的 MCP 服务器(stdio):把 AI 客户端的 `gp_*` 工具调用转发给本机
|
|
4
|
+
GlassPane 后台服务,并把每一次界面操作的证据带回给你。
|
|
5
|
+
|
|
6
|
+
## 用法
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm i -g glasspane-mcp
|
|
10
|
+
glasspane-mcp # 默认连接 ~/.glasspane/engine.sock
|
|
11
|
+
GLASSPANE_ENGINE_SOCK=/path/to/engine.sock glasspane-mcp
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
MCP 客户端配置:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{ "mcpServers": { "glasspane": { "command": "glasspane-mcp" } } }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
后台服务未运行或权限未就绪时,工具调用会返回带补救步骤的结构化错误——
|
|
21
|
+
按错误里的命令执行即可,无需查阅其它文档。
|
|
22
|
+
|
|
23
|
+
工具清单与语义以 `gp_*` 运行时 `tools/list` 返回为准。
|
|
24
|
+
License: MIT.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory session trail of operationIds seen by this MCP server process.
|
|
3
|
+
* The daemon's method table is frozen (P1 spec v1.3 §10.3), so `gp_recent_reports`
|
|
4
|
+
* cannot ask the daemon to enumerate its history — this shell tracks the
|
|
5
|
+
* operationIds returned by act/assert/diagnose/restore/last_evidence itself,
|
|
6
|
+
* mirroring the daemon's bounded-memory semantics.
|
|
7
|
+
*
|
|
8
|
+
* Lifecycle: cleared on every successful `gp_attach` (the daemon invalidates
|
|
9
|
+
* its evidence history when the attached app changes; re-attach is idempotent
|
|
10
|
+
* and simply restarts the trail). Does not persist across process restarts.
|
|
11
|
+
*/
|
|
12
|
+
export declare const AUDIT_HISTORY_LIMIT = 20;
|
|
13
|
+
export declare class EvidenceAuditSession {
|
|
14
|
+
private ids;
|
|
15
|
+
/** Record every operationId/evidenceId found in an engine result frame. */
|
|
16
|
+
record(result: unknown): void;
|
|
17
|
+
/** Clear the trail (called after a successful attach). */
|
|
18
|
+
reset(): void;
|
|
19
|
+
/** The most recent `limit` recorded operationIds, oldest first. */
|
|
20
|
+
recentIds(limit: number): string[];
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=audit-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-session.d.ts","sourceRoot":"","sources":["../src/audit-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,GAAG,CAAgB;IAE3B,2EAA2E;IAC3E,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAW7B,0DAA0D;IAC1D,KAAK,IAAI,IAAI;IAIb,mEAAmE;IACnE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAGnC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory session trail of operationIds seen by this MCP server process.
|
|
3
|
+
* The daemon's method table is frozen (P1 spec v1.3 §10.3), so `gp_recent_reports`
|
|
4
|
+
* cannot ask the daemon to enumerate its history — this shell tracks the
|
|
5
|
+
* operationIds returned by act/assert/diagnose/restore/last_evidence itself,
|
|
6
|
+
* mirroring the daemon's bounded-memory semantics.
|
|
7
|
+
*
|
|
8
|
+
* Lifecycle: cleared on every successful `gp_attach` (the daemon invalidates
|
|
9
|
+
* its evidence history when the attached app changes; re-attach is idempotent
|
|
10
|
+
* and simply restarts the trail). Does not persist across process restarts.
|
|
11
|
+
*/
|
|
12
|
+
export const AUDIT_HISTORY_LIMIT = 20;
|
|
13
|
+
export class EvidenceAuditSession {
|
|
14
|
+
ids = [];
|
|
15
|
+
/** Record every operationId/evidenceId found in an engine result frame. */
|
|
16
|
+
record(result) {
|
|
17
|
+
for (const id of collectOperationIds(result)) {
|
|
18
|
+
if (!this.ids.includes(id)) {
|
|
19
|
+
this.ids.push(id);
|
|
20
|
+
if (this.ids.length > AUDIT_HISTORY_LIMIT) {
|
|
21
|
+
this.ids.splice(0, this.ids.length - AUDIT_HISTORY_LIMIT);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Clear the trail (called after a successful attach). */
|
|
27
|
+
reset() {
|
|
28
|
+
this.ids = [];
|
|
29
|
+
}
|
|
30
|
+
/** The most recent `limit` recorded operationIds, oldest first. */
|
|
31
|
+
recentIds(limit) {
|
|
32
|
+
return this.ids.slice(-limit);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Bounded recursive walk (depth ≤ 3) that collects string-valued
|
|
37
|
+
* `operationId` / `evidenceId` fields — from act/assert/diagnose/restore
|
|
38
|
+
* results and from nested `evidencePack` frames. Depth is capped so an
|
|
39
|
+
* adversarial frame cannot force an unbounded descent.
|
|
40
|
+
*/
|
|
41
|
+
function collectOperationIds(value, depth = 0) {
|
|
42
|
+
const out = [];
|
|
43
|
+
if (depth > 3 || value === null || typeof value !== "object") {
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
for (const item of value) {
|
|
48
|
+
out.push(...collectOperationIds(item, depth + 1));
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
const record = value;
|
|
53
|
+
for (const key of Object.keys(record)) {
|
|
54
|
+
const entry = record[key];
|
|
55
|
+
if ((key === "operationId" || key === "evidenceId") && typeof entry === "string") {
|
|
56
|
+
out.push(entry);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
out.push(...collectOperationIds(entry, depth + 1));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=audit-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-session.js","sourceRoot":"","sources":["../src/audit-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC,MAAM,OAAO,oBAAoB;IACvB,GAAG,GAAa,EAAE,CAAC;IAE3B,2EAA2E;IAC3E,MAAM,CAAC,MAAe;QACpB,KAAK,MAAM,EAAE,IAAI,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAClB,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,mBAAmB,EAAE,CAAC;oBAC1C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,mBAAmB,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,KAAK;QACH,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;IAChB,CAAC;IAED,mEAAmE;IACnE,SAAS,CAAC,KAAa;QACrB,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;CACF;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,KAAc,EAAE,KAAK,GAAG,CAAC;IACpD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC7D,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,GAAG,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,YAAY,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACjF,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON serialization: object keys sorted, no insignificant
|
|
3
|
+
* whitespace. Mirrors the cross-language convention used by the kernel C35
|
|
4
|
+
* roundtrips so engine results and evidence packs stay byte-stable.
|
|
5
|
+
*/
|
|
6
|
+
export declare function canonicalJson(value: unknown): string;
|
|
7
|
+
//# sourceMappingURL=canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON serialization: object keys sorted, no insignificant
|
|
3
|
+
* whitespace. Mirrors the cross-language convention used by the kernel C35
|
|
4
|
+
* roundtrips so engine results and evidence packs stay byte-stable.
|
|
5
|
+
*/
|
|
6
|
+
export function canonicalJson(value) {
|
|
7
|
+
return JSON.stringify(sortKeys(value));
|
|
8
|
+
}
|
|
9
|
+
function sortKeys(value) {
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return value.map((entry) => sortKeys(entry));
|
|
12
|
+
}
|
|
13
|
+
if (value !== null && typeof value === "object") {
|
|
14
|
+
const record = value;
|
|
15
|
+
const sorted = {};
|
|
16
|
+
for (const key of Object.keys(record).sort()) {
|
|
17
|
+
sorted[key] = sortKeys(record[key]);
|
|
18
|
+
}
|
|
19
|
+
return sorted;
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,KAAgC,CAAC;QAChD,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EngineJsonRpcClient } from "./engine-client.js";
|
|
2
|
+
import { ToolSpec } from "./tools.js";
|
|
3
|
+
import { EvidenceAuditSession } from "./audit-session.js";
|
|
4
|
+
export declare const JSONRPC: "2.0";
|
|
5
|
+
/** MCP protocol version echoed on initialize (spec §6.1). */
|
|
6
|
+
export declare const MCP_PROTOCOL_VERSION: "2025-06-18";
|
|
7
|
+
export declare const SERVER_INFO: {
|
|
8
|
+
readonly name: "glasspane-mcp";
|
|
9
|
+
readonly version: "0.1.0";
|
|
10
|
+
};
|
|
11
|
+
export declare const PARSE_ERROR = -32700;
|
|
12
|
+
export declare const INVALID_REQUEST = -32600;
|
|
13
|
+
export declare const METHOD_NOT_FOUND = -32601;
|
|
14
|
+
export declare const INVALID_PARAMS = -32602;
|
|
15
|
+
export declare const INTERNAL_ERROR = -32603;
|
|
16
|
+
export interface RpcErrorBody {
|
|
17
|
+
code: number;
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
export interface RpcResponse {
|
|
21
|
+
jsonrpc: typeof JSONRPC;
|
|
22
|
+
id: number | string | null;
|
|
23
|
+
result?: unknown;
|
|
24
|
+
error?: RpcErrorBody;
|
|
25
|
+
}
|
|
26
|
+
export interface McpServerDeps {
|
|
27
|
+
engine: EngineJsonRpcClient;
|
|
28
|
+
specs?: readonly ToolSpec[];
|
|
29
|
+
/** Overridable audit session (per-server operationId trail, spec v1.3 §10.3). */
|
|
30
|
+
session?: EvidenceAuditSession;
|
|
31
|
+
}
|
|
32
|
+
export declare class McpServer {
|
|
33
|
+
private readonly deps;
|
|
34
|
+
private readonly specs;
|
|
35
|
+
private readonly session;
|
|
36
|
+
constructor(deps: McpServerDeps);
|
|
37
|
+
/** Handle a single newline-delimited frame; returns a response or null. */
|
|
38
|
+
handleLine(line: string): Promise<RpcResponse | null>;
|
|
39
|
+
private handleNotification;
|
|
40
|
+
private dispatch;
|
|
41
|
+
private callTool;
|
|
42
|
+
private asParams;
|
|
43
|
+
private isRequestBody;
|
|
44
|
+
private result;
|
|
45
|
+
private error;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAA2B,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAM1D,eAAO,MAAM,OAAO,EAAG,KAAc,CAAC;AACtC,6DAA6D;AAC7D,eAAO,MAAM,oBAAoB,EAAG,YAAqB,CAAC;AAC1D,eAAO,MAAM,WAAW;;;CAAuD,CAAC;AAEhF,eAAO,MAAM,WAAW,SAAS,CAAC;AAClC,eAAO,MAAM,eAAe,SAAS,CAAC;AACtC,eAAO,MAAM,gBAAgB,SAAS,CAAC;AACvC,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,cAAc,SAAS,CAAC;AAErC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAaD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC5B,iFAAiF;IACjF,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,qBAAa,SAAS;IAIR,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;gBAElB,IAAI,EAAE,aAAa;IAKhD,2EAA2E;IACrE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;YAkC7C,kBAAkB;YAIlB,QAAQ;YA6BR,QAAQ;IAkBtB,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,KAAK;CAGd"}
|
package/dist/dispatch.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { executeTool, TOOL_SPECS } from "./tools.js";
|
|
2
|
+
import { EvidenceAuditSession } from "./audit-session.js";
|
|
3
|
+
/* ------------------------------------------------------------------ *
|
|
4
|
+
* JSON-RPC 2.0 message shapes (spec §6.1).
|
|
5
|
+
* ------------------------------------------------------------------ */
|
|
6
|
+
export const JSONRPC = "2.0";
|
|
7
|
+
/** MCP protocol version echoed on initialize (spec §6.1). */
|
|
8
|
+
export const MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
9
|
+
export const SERVER_INFO = { name: "glasspane-mcp", version: "0.1.0" };
|
|
10
|
+
export const PARSE_ERROR = -32700;
|
|
11
|
+
export const INVALID_REQUEST = -32600;
|
|
12
|
+
export const METHOD_NOT_FOUND = -32601;
|
|
13
|
+
export const INVALID_PARAMS = -32602;
|
|
14
|
+
export const INTERNAL_ERROR = -32603;
|
|
15
|
+
export class McpServer {
|
|
16
|
+
deps;
|
|
17
|
+
specs;
|
|
18
|
+
session;
|
|
19
|
+
constructor(deps) {
|
|
20
|
+
this.deps = deps;
|
|
21
|
+
this.specs = deps.specs ?? TOOL_SPECS;
|
|
22
|
+
this.session = deps.session ?? new EvidenceAuditSession();
|
|
23
|
+
}
|
|
24
|
+
/** Handle a single newline-delimited frame; returns a response or null. */
|
|
25
|
+
async handleLine(line) {
|
|
26
|
+
if (line.trim() === "") {
|
|
27
|
+
return null; // blank frame — no response per protocol §6.1
|
|
28
|
+
}
|
|
29
|
+
let parsed;
|
|
30
|
+
try {
|
|
31
|
+
parsed = JSON.parse(line);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return this.error(PARSE_ERROR, "Parse error", null);
|
|
35
|
+
}
|
|
36
|
+
if (!this.isRequestBody(parsed)) {
|
|
37
|
+
return this.error(INVALID_REQUEST, "Invalid Request", null);
|
|
38
|
+
}
|
|
39
|
+
const request = parsed;
|
|
40
|
+
if (request.id === undefined) {
|
|
41
|
+
// Notification: process but never answer. Unknown notifications are
|
|
42
|
+
// ignored rather than rejected (spec §6.1).
|
|
43
|
+
await this.handleNotification(request);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (typeof request.id !== "number" && typeof request.id !== "string") {
|
|
47
|
+
return this.error(INVALID_REQUEST, "Invalid Request", null);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return await this.dispatch(request, request.id);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return this.error(INTERNAL_ERROR, `Internal error: ${String(error)}`, request.id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async handleNotification(request) {
|
|
57
|
+
// no-op: only `notifications/initialized` is expected at P0; nothing to do.
|
|
58
|
+
}
|
|
59
|
+
async dispatch(request, id) {
|
|
60
|
+
if (typeof request.method !== "string") {
|
|
61
|
+
return this.error(INVALID_REQUEST, "Invalid Request", id);
|
|
62
|
+
}
|
|
63
|
+
switch (request.method) {
|
|
64
|
+
case "initialize":
|
|
65
|
+
return this.result(id, {
|
|
66
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
67
|
+
capabilities: { tools: { listChanged: false } },
|
|
68
|
+
serverInfo: SERVER_INFO,
|
|
69
|
+
});
|
|
70
|
+
case "ping":
|
|
71
|
+
return this.result(id, {});
|
|
72
|
+
case "tools/list":
|
|
73
|
+
return this.result(id, {
|
|
74
|
+
tools: this.specs.map((spec) => ({
|
|
75
|
+
name: spec.name,
|
|
76
|
+
description: spec.description,
|
|
77
|
+
inputSchema: spec.inputSchema,
|
|
78
|
+
})),
|
|
79
|
+
});
|
|
80
|
+
case "tools/call":
|
|
81
|
+
return this.callTool(id, request.params);
|
|
82
|
+
default:
|
|
83
|
+
return this.error(METHOD_NOT_FOUND, `Method not found: ${request.method}`, id);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async callTool(id, rawParams) {
|
|
87
|
+
const params = this.asParams(rawParams);
|
|
88
|
+
if (!params) {
|
|
89
|
+
return this.error(INVALID_PARAMS, "tools/call requires an object params", id);
|
|
90
|
+
}
|
|
91
|
+
const name = params.name;
|
|
92
|
+
if (typeof name !== "string") {
|
|
93
|
+
return this.error(INVALID_PARAMS, "tools/call requires a string name", id);
|
|
94
|
+
}
|
|
95
|
+
const spec = this.specs.find((candidate) => candidate.name === name);
|
|
96
|
+
if (!spec) {
|
|
97
|
+
return this.error(INVALID_PARAMS, `Unknown tool: ${name}`, id);
|
|
98
|
+
}
|
|
99
|
+
const outcome = await executeTool(spec, params.arguments, this.deps.engine, this.session);
|
|
100
|
+
return this.result(id, { content: outcome.content, isError: outcome.isError });
|
|
101
|
+
}
|
|
102
|
+
asParams(rawParams) {
|
|
103
|
+
if (typeof rawParams !== "object" || rawParams === null || Array.isArray(rawParams)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return rawParams;
|
|
107
|
+
}
|
|
108
|
+
isRequestBody(value) {
|
|
109
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
const candidate = value;
|
|
113
|
+
return candidate.jsonrpc === JSONRPC;
|
|
114
|
+
}
|
|
115
|
+
result(id, result) {
|
|
116
|
+
return { jsonrpc: JSONRPC, id, result };
|
|
117
|
+
}
|
|
118
|
+
error(code, message, id) {
|
|
119
|
+
return { jsonrpc: JSONRPC, id, error: { code, message } };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { LineIo } from "./io.js";
|
|
2
|
+
/**
|
|
3
|
+
* Agent-actionable remedy for an unreachable daemon (P6 §11 audit items ④/⑥:
|
|
4
|
+
* a remedy must be an executable command, not a prose hint). When the repo
|
|
5
|
+
* installer is present next to this build, hand over the machine-verified
|
|
6
|
+
* restore command: it detects a boot-out'd launchd job, bootstraps it, and
|
|
7
|
+
* verifies the daemon's hello self-report — the only remaining human action
|
|
8
|
+
* (TCC checkbox) stays honest. Falls back to generic wording otherwise.
|
|
9
|
+
*/
|
|
10
|
+
export declare function daemonUnreachableRemedy(): string;
|
|
11
|
+
/** Env override for the daemon socket path (default below). */
|
|
12
|
+
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). */
|
|
16
|
+
export declare function defaultSocketPath(): string;
|
|
17
|
+
/** Error frame returned by the daemon (spec §3.2.1) or raised by the shell. */
|
|
18
|
+
export interface EngineErrorBody {
|
|
19
|
+
code: string;
|
|
20
|
+
message: string;
|
|
21
|
+
remedy: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class EngineCallError extends Error {
|
|
24
|
+
readonly code: string;
|
|
25
|
+
readonly message: string;
|
|
26
|
+
readonly remedy: string;
|
|
27
|
+
constructor(code: string, message: string, remedy: string);
|
|
28
|
+
toBody(): EngineErrorBody;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Request/response association over a line transport. Kept independent of
|
|
32
|
+
* the transport so tests inject a fake LineIo (spec §7.1 "fake engine
|
|
33
|
+
* transport").
|
|
34
|
+
*/
|
|
35
|
+
export declare class EngineJsonRpcClient {
|
|
36
|
+
private readonly io;
|
|
37
|
+
private readonly timeoutMs;
|
|
38
|
+
private nextId;
|
|
39
|
+
private readonly pending;
|
|
40
|
+
constructor(io: LineIo, timeoutMs?: number);
|
|
41
|
+
/** Send a request and await its matching response frame. */
|
|
42
|
+
call(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
43
|
+
close(): void;
|
|
44
|
+
private handleMessage;
|
|
45
|
+
private failAll;
|
|
46
|
+
}
|
|
47
|
+
/** Production client bound to a Unix socket path (lazy connect on first call). */
|
|
48
|
+
export declare function unixSocketEngineClient(socketPath: string): EngineJsonRpcClient;
|
|
49
|
+
/**
|
|
50
|
+
* Build a client over an existing LineIo (used by tests with a fake
|
|
51
|
+
* transport and by non-socket connectivity).
|
|
52
|
+
*/
|
|
53
|
+
export declare function engineClientOver(io: LineIo, timeoutMs?: number): EngineJsonRpcClient;
|
|
54
|
+
//# sourceMappingURL=engine-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-client.d.ts","sourceRoot":"","sources":["../src/engine-client.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAgB,MAAM,SAAS,CAAC;AAG/C;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAOhD;AAED,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AACzD,kDAAkD;AAClD,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC,sDAAsD;AACtD,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,SAAkB,OAAO,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAQzD,MAAM,IAAI,eAAe;CAG1B;AAaD;;;;GAIG;AACH,qBAAa,mBAAmB;IAK5B,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAL5B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;gBAGnC,EAAE,EAAE,MAAM,EACV,SAAS,GAAE,MAA0B;IAexD,4DAA4D;IAC5D,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCxE,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,OAAO;CAMhB;AAED,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB,CAI9E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAEpF"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { StreamLineIo } from "./io.js";
|
|
7
|
+
import { GP_E_ENGINE_UNREACHABLE } from "./errors.js";
|
|
8
|
+
/**
|
|
9
|
+
* Agent-actionable remedy for an unreachable daemon (P6 §11 audit items ④/⑥:
|
|
10
|
+
* a remedy must be an executable command, not a prose hint). When the repo
|
|
11
|
+
* installer is present next to this build, hand over the machine-verified
|
|
12
|
+
* restore command: it detects a boot-out'd launchd job, bootstraps it, and
|
|
13
|
+
* verifies the daemon's hello self-report — the only remaining human action
|
|
14
|
+
* (TCC checkbox) stays honest. Falls back to generic wording otherwise.
|
|
15
|
+
*/
|
|
16
|
+
export function daemonUnreachableRemedy() {
|
|
17
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const installerCli = path.resolve(here, "..", "..", "installer", "cli.js");
|
|
19
|
+
if (fs.existsSync(installerCli)) {
|
|
20
|
+
return `run "node ${installerCli} --restore-launchd" (auto-restores and verifies the launchd-managed daemon), then retry`;
|
|
21
|
+
}
|
|
22
|
+
return "ensure the glasspane daemon is running (launchd job com.glasspane.daemon; in a repo checkout run `node installer/cli.js --restore-launchd`), then retry";
|
|
23
|
+
}
|
|
24
|
+
/** Env override for the daemon socket path (default below). */
|
|
25
|
+
export const ENGINE_SOCKET_ENV = "GLASSPANE_ENGINE_SOCK";
|
|
26
|
+
/** Connection/response timeout, per spec §6.2. */
|
|
27
|
+
export const ENGINE_TIMEOUT_MS = 10_000;
|
|
28
|
+
/** The daemon listens here by default (spec §3.1). */
|
|
29
|
+
export function defaultSocketPath() {
|
|
30
|
+
return process.env[ENGINE_SOCKET_ENV] ?? path.join(os.homedir(), ".glasspane", "engine.sock");
|
|
31
|
+
}
|
|
32
|
+
export class EngineCallError extends Error {
|
|
33
|
+
code;
|
|
34
|
+
message;
|
|
35
|
+
remedy;
|
|
36
|
+
constructor(code, message, remedy) {
|
|
37
|
+
super(`${code}: ${message}`);
|
|
38
|
+
this.name = "EngineCallError";
|
|
39
|
+
this.code = code;
|
|
40
|
+
this.message = message;
|
|
41
|
+
this.remedy = remedy;
|
|
42
|
+
}
|
|
43
|
+
toBody() {
|
|
44
|
+
return { code: this.code, message: this.message, remedy: this.remedy };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Request/response association over a line transport. Kept independent of
|
|
49
|
+
* the transport so tests inject a fake LineIo (spec §7.1 "fake engine
|
|
50
|
+
* transport").
|
|
51
|
+
*/
|
|
52
|
+
export class EngineJsonRpcClient {
|
|
53
|
+
io;
|
|
54
|
+
timeoutMs;
|
|
55
|
+
nextId = 0;
|
|
56
|
+
pending = new Map();
|
|
57
|
+
constructor(io, timeoutMs = ENGINE_TIMEOUT_MS) {
|
|
58
|
+
this.io = io;
|
|
59
|
+
this.timeoutMs = timeoutMs;
|
|
60
|
+
this.io.onMessage((line) => this.handleMessage(line));
|
|
61
|
+
this.io.onError((error) => this.failAll(new EngineCallError(GP_E_ENGINE_UNREACHABLE, `engine transport error: ${error.message}`, daemonUnreachableRemedy())));
|
|
62
|
+
this.io.onClose(() => this.failAll(new EngineCallError(GP_E_ENGINE_UNREACHABLE, "engine transport closed", daemonUnreachableRemedy())));
|
|
63
|
+
}
|
|
64
|
+
/** Send a request and await its matching response frame. */
|
|
65
|
+
call(method, params) {
|
|
66
|
+
const id = this.nextId++;
|
|
67
|
+
const frame = { id, method, ...(params === undefined ? {} : { params }) };
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
this.pending.delete(id);
|
|
71
|
+
reject(new EngineCallError(GP_E_ENGINE_UNREACHABLE, `engine request timed out after ${this.timeoutMs}ms (method ${method})`, daemonUnreachableRemedy()));
|
|
72
|
+
}, this.timeoutMs);
|
|
73
|
+
// Deliberately NOT `timer.unref()`. An unreferenced timer does not keep
|
|
74
|
+
// the event loop alive, so when a pending call is the only outstanding
|
|
75
|
+
// work Node exits and the timeout is never delivered — the caller waits
|
|
76
|
+
// on a promise that is settled by process death, not by a diagnosis.
|
|
77
|
+
// The timeout is this client's only guarantee that every request settles,
|
|
78
|
+
// so it must be a ref'd handle. It can never hold the loop past
|
|
79
|
+
// `timeoutMs`: it either fires (and settles the call) or is cleared by
|
|
80
|
+
// the response / transport-close path below.
|
|
81
|
+
this.pending.set(id, {
|
|
82
|
+
resolve,
|
|
83
|
+
reject: (reason) => {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
reject(reason);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
this.io.writeLine(JSON.stringify(frame));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
close() {
|
|
92
|
+
this.io.close();
|
|
93
|
+
}
|
|
94
|
+
handleMessage(line) {
|
|
95
|
+
let frame;
|
|
96
|
+
try {
|
|
97
|
+
frame = JSON.parse(line);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Non-JSON response is not attributable to a request id; drop.
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const entry = this.pending.get(frame.id);
|
|
104
|
+
if (!entry) {
|
|
105
|
+
return; // late/unsolicited frame — drop
|
|
106
|
+
}
|
|
107
|
+
this.pending.delete(frame.id);
|
|
108
|
+
if (frame.error) {
|
|
109
|
+
entry.reject(new EngineCallError(frame.error.code, frame.error.message, frame.error.remedy));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
entry.resolve(frame.result);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
failAll(reason) {
|
|
116
|
+
for (const entry of this.pending.values()) {
|
|
117
|
+
entry.reject(reason);
|
|
118
|
+
}
|
|
119
|
+
this.pending.clear();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Production client bound to a Unix socket path (lazy connect on first call). */
|
|
123
|
+
export function unixSocketEngineClient(socketPath) {
|
|
124
|
+
const socket = net.createConnection(socketPath);
|
|
125
|
+
const io = new StreamLineIo(socket, socket);
|
|
126
|
+
return new EngineJsonRpcClient(io);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Build a client over an existing LineIo (used by tests with a fake
|
|
130
|
+
* transport and by non-socket connectivity).
|
|
131
|
+
*/
|
|
132
|
+
export function engineClientOver(io, timeoutMs) {
|
|
133
|
+
return new EngineJsonRpcClient(io, timeoutMs);
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=engine-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-client.js","sourceRoot":"","sources":["../src/engine-client.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GP_E failure-mode codes surfaced to the agent (P0 spec §3.4 / §6.2).
|
|
3
|
+
* Engine-side codes are defined by the daemon; the shell only adds the
|
|
4
|
+
* codes that arise between the shell and the engine connection.
|
|
5
|
+
*/
|
|
6
|
+
export declare const GP_E_ENGINE_UNREACHABLE = "GP_E_ENGINE_UNREACHABLE";
|
|
7
|
+
export declare const GP_E_BAD_PARAMS = "GP_E_BAD_PARAMS";
|
|
8
|
+
export declare const GP_E_NO_EVIDENCE = "GP_E_NO_EVIDENCE";
|
|
9
|
+
export declare const GP_E_INTERNAL = "GP_E_INTERNAL";
|
|
10
|
+
export declare const GP_E_PROJECT_LIMIT = "GP_E_PROJECT_LIMIT";
|
|
11
|
+
export declare const GP_E_NOT_FOUND = "GP_E_NOT_FOUND";
|
|
12
|
+
export interface ToolErrorText {
|
|
13
|
+
code: string;
|
|
14
|
+
message: string;
|
|
15
|
+
remedy: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Wire text shape consumed by the agent (spec §6.2):
|
|
19
|
+
* `GP_E_XXX: message | remedy: <guidance>` — the prefix is greppable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatToolError(code: string, message: string, remedy: string): string;
|
|
22
|
+
export declare function formatToolErrorShape(error: ToolErrorText): string;
|
|
23
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;AACjE,eAAO,MAAM,eAAe,oBAAoB,CAAC;AACjD,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AACnD,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAC7C,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AACvD,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAErF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAEjE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GP_E failure-mode codes surfaced to the agent (P0 spec §3.4 / §6.2).
|
|
3
|
+
* Engine-side codes are defined by the daemon; the shell only adds the
|
|
4
|
+
* codes that arise between the shell and the engine connection.
|
|
5
|
+
*/
|
|
6
|
+
export const GP_E_ENGINE_UNREACHABLE = "GP_E_ENGINE_UNREACHABLE";
|
|
7
|
+
export const GP_E_BAD_PARAMS = "GP_E_BAD_PARAMS";
|
|
8
|
+
export const GP_E_NO_EVIDENCE = "GP_E_NO_EVIDENCE";
|
|
9
|
+
export const GP_E_INTERNAL = "GP_E_INTERNAL";
|
|
10
|
+
export const GP_E_PROJECT_LIMIT = "GP_E_PROJECT_LIMIT";
|
|
11
|
+
export const GP_E_NOT_FOUND = "GP_E_NOT_FOUND";
|
|
12
|
+
/**
|
|
13
|
+
* Wire text shape consumed by the agent (spec §6.2):
|
|
14
|
+
* `GP_E_XXX: message | remedy: <guidance>` — the prefix is greppable.
|
|
15
|
+
*/
|
|
16
|
+
export function formatToolError(code, message, remedy) {
|
|
17
|
+
return `${code}: ${message} | remedy: ${remedy}`;
|
|
18
|
+
}
|
|
19
|
+
export function formatToolErrorShape(error) {
|
|
20
|
+
return formatToolError(error.code, error.message, error.remedy);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAC;AACjE,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AACjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AACnD,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAC7C,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AACvD,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAQ/C;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,OAAe,EAAE,MAAc;IAC3E,OAAO,GAAG,IAAI,KAAK,OAAO,cAAc,MAAM,EAAE,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAoB;IACvD,OAAO,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;AAClE,CAAC"}
|