glasspane-harness 0.6.2 → 0.6.4
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 +216 -0
- package/README.zh-CN.md +173 -0
- package/bin/glasspane-harness.exe +10 -0
- package/package.json +50 -1
- package/postinstall.mjs +191 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 opencode
|
|
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,216 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<code>glasspane-harness</code>
|
|
3
|
+
</h1>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="README.md"><strong>English</strong></a> ·
|
|
7
|
+
<a href="README.zh-CN.md"><strong>简体中文</strong></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://www.npmjs.com/package/glasspane-harness"><img src="https://img.shields.io/npm/dt/glasspane-harness?label=Downloads&style=for-the-badge&color=2ea44f&logo=npm&logoColor=white" alt="npm downloads"></a>
|
|
12
|
+
<img src="https://img.shields.io/dynamic/json?url=https%3A%2F%2Fregistry.npmjs.org%2Fglasspane-harness%2Flatest&query=version&label=version&color=brightgreen" alt="version">
|
|
13
|
+
<a href="https://github.com/jingzhao-l/glasspane-harness/actions/workflows/ci.yml"><img src="https://github.com/jingzhao-l/glasspane-harness/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
14
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License"></a>
|
|
15
|
+
<img src="https://img.shields.io/badge/macOS-13%2B-blue?logo=apple&logoColor=white" alt="macOS 13+">
|
|
16
|
+
<a href="https://github.com/jingzhao-l/glasspane-harness"><img src="https://img.shields.io/github/stars/jingzhao-l/glasspane-harness?style=social&label=Star" alt="Stars"></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="assets/banner.svg" alt="GlassPane Harness — evidence-honest GUI verification for macOS apps" width="720">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
**The problem.** An AI agent driving a real macOS app cannot honestly say whether the UI
|
|
26
|
+
did what it asked. It sees its own tool output, guesses from prose, and reports success.
|
|
27
|
+
Screenshots don't help: they show *a* moment, not a *change*, and they cannot say whether
|
|
28
|
+
the change was caused by the click.
|
|
29
|
+
|
|
30
|
+
**GlassPane Harness** is a coding agent (an [opencode](https://github.com/anomalyco/opencode) upstream fork
|
|
31
|
+
fork) wired to a Swift verification engine through a native `gp_*` tool surface. Every
|
|
32
|
+
action is measured — accessibility-tree diff, pixel diff, crash and responsiveness
|
|
33
|
+
signals — and the engine reports **its own** attribution (`strong` / `soft` / `none`,
|
|
34
|
+
circuit-breaker level, pass / fail / inconclusive). The harness transcribes; it never
|
|
35
|
+
invents a verdict. Verified calls are appended to a hash-chained decision log, and a
|
|
36
|
+
compaction hook re-injects the session's evidence anchors so summarising can never quietly
|
|
37
|
+
drop the audit chain.
|
|
38
|
+
|
|
39
|
+
The engine is [GlassPane](https://github.com/jingzhao-l/GlassPane). The harness is the
|
|
40
|
+
agent side of it.
|
|
41
|
+
|
|
42
|
+
## This is a complete coding agent, not a stripped one
|
|
43
|
+
|
|
44
|
+
The verification wiring is the point of this fork, but it is **additive**. Everything
|
|
45
|
+
upstream opencode does, this harness still does — same agents, same tools, same models, same
|
|
46
|
+
permissions, same LSP and MCP support, same TUI and web app, same SDK and plugin API.
|
|
47
|
+
Nothing was cut to make room for the kernel.
|
|
48
|
+
|
|
49
|
+
That is a measured claim, not a promise. The fork tracks upstream
|
|
50
|
+
[anomalyco/opencode](https://github.com/anomalyco/opencode) at a pinned tag, and
|
|
51
|
+
`harness/tools/fork-diff.mjs` compares every file in this tree against that tag on every
|
|
52
|
+
change:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
fork-diff vs v1.18.32: 6527/6687 byte-identical, 51 edited, 55 added, 54 deleted
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Roughly 98% of the upstream tree is byte-for-byte upstream. The edits are concentrated in
|
|
59
|
+
config/paths/branding, the `gp_*` tool surface, the kernel binding, the compaction hook,
|
|
60
|
+
and the packaging scripts. The deleted files are upstream's own publish plumbing for
|
|
61
|
+
channels this product does not use (ghcr images, AUR, a Homebrew tap, a hosted console
|
|
62
|
+
account) — plus, in 0.4.0, the first-party account surface described below.
|
|
63
|
+
|
|
64
|
+
What "additive" means in practice: if upstream ships a feature in a later release, this
|
|
65
|
+
fork can take it, because the feature is still here to take.
|
|
66
|
+
|
|
67
|
+
### One exception, and it is deliberate: no account, bring your own key
|
|
68
|
+
|
|
69
|
+
Upstream lets you `auth login` to an opencode account, which unlocks opencode's own
|
|
70
|
+
model gateway (a first-party provider in the catalog) and remote session sharing. This
|
|
71
|
+
product has no such account, so that surface is removed rather than shipped broken:
|
|
72
|
+
|
|
73
|
+
- upstream's `opencode` provider entry is filtered out of the model catalog
|
|
74
|
+
- `auth login` / `logout` / `switch`, the `/org` command and the org switcher are gone
|
|
75
|
+
- the `/experimental/console*` endpoints and the console-managed-provider state are gone
|
|
76
|
+
- session sharing to opencode's upstream cloud is gone (`share` / `autoshare` config keys too)
|
|
77
|
+
|
|
78
|
+
**Everything else in the catalog is untouched.** Anthropic, OpenAI, Google, GitHub
|
|
79
|
+
Copilot, Amazon Bedrock, Azure, OpenRouter and the rest are all still there — hundreds of
|
|
80
|
+
models — and each one is unlocked by **your own API key**, supplied by environment
|
|
81
|
+
variable, by the config file with an `{env:NAME}` template, or by the TUI's `/connect`
|
|
82
|
+
dialog. There is nothing to sign up for and nothing to log into. See
|
|
83
|
+
[`docs/models-and-keys.md`](docs/models-and-keys.md).
|
|
84
|
+
|
|
85
|
+
## Product surfaces
|
|
86
|
+
|
|
87
|
+
| Surface | What you get | How it ships |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| **CLI + TUI** | The agent in your terminal: interactive TUI, `run` for one-shot tasks, `serve` for headless clients | the `glasspane-harness` / `gp-harness` binaries, npm + one-click installer |
|
|
90
|
+
| **Web app** (embedded) | Browser UI served by the binary itself — no second process, no separate deploy | embedded in the binary (`--skip-embed-web-ui` for a CLI-only build) |
|
|
91
|
+
| **Docs** | This repository's `docs/` — install, tools, evidence, migration, troubleshooting | in-repo markdown (canonical; versioned with the code) |
|
|
92
|
+
| **SDKs** | TypeScript SDK and the plugin SDK for building your own `gp_*`-style tools | workspace packages, shipped with the tree |
|
|
93
|
+
|
|
94
|
+
Not shipped, on purpose: the upstream hosted console / enterprise stack (a private harness
|
|
95
|
+
has no use for an organisation-and-quota back office), the Electron desktop app (a second
|
|
96
|
+
batch — it needs signing and notarisation accounts), and the upstream docs *site*
|
|
97
|
+
(upstream's Astro/Starlight tree) is **gone from this repository**; the documentation is
|
|
98
|
+
`docs/`, and the console/enterprise/stats trees went with it. They were never part of
|
|
99
|
+
this product, and keeping them meant keeping someone else's product surface in our repo.
|
|
100
|
+
|
|
101
|
+
## Install
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# npm (recommended)
|
|
105
|
+
npm install -g glasspane-harness
|
|
106
|
+
|
|
107
|
+
# or bun
|
|
108
|
+
bun add -g glasspane-harness
|
|
109
|
+
|
|
110
|
+
# or one-click (npm first, GitHub release asset as fallback)
|
|
111
|
+
curl -fsSL https://raw.githubusercontent.com/jingzhao-l/glasspane-harness/main/scripts/install.sh | bash
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Two commands are installed: **`glasspane-harness`** and the short alias **`gp-harness`**.
|
|
115
|
+
|
|
116
|
+
**macOS only.** The engine, the permission model and the evidence pipeline exist only on
|
|
117
|
+
macOS, so the npm distribution is macOS-only too: the package declares `os: darwin` and
|
|
118
|
+
npm refuses the install elsewhere instead of handing you a binary that cannot run. There
|
|
119
|
+
is no Linux or Windows build, by decision, not by omission.
|
|
120
|
+
|
|
121
|
+
Prerequisites: macOS 13+ (Apple silicon or Intel), and — for the `gp_*` tools — the **GlassPane** background
|
|
122
|
+
service running with Accessibility granted ([install GlassPane](https://github.com/jingzhao-l/GlassPane);
|
|
123
|
+
the installer prints the exact next steps). Without the engine every `gp_*` call answers
|
|
124
|
+
`GP_E_ENGINE_UNREACHABLE` with a remedy — that is the engine's word, not the installer's.
|
|
125
|
+
|
|
126
|
+
If npm's global prefix is not writable, use
|
|
127
|
+
`npm install -g --prefix "$HOME/.local" glasspane-harness` and put `~/.local/bin` on PATH;
|
|
128
|
+
the installer detects that failure and prints this remedy itself.
|
|
129
|
+
|
|
130
|
+
## Quick start
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
glasspane-harness # interactive TUI (the embedded web UI is served too)
|
|
134
|
+
glasspane-harness run "attach to Notes, type a heading, then tell me whether the UI proved it"
|
|
135
|
+
glasspane-harness serve --port 4096 # headless server; the web app is served from it
|
|
136
|
+
glasspane-harness --help
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The first thing to ask any session for is `gp_probe_status`: it reports the engine
|
|
140
|
+
version, advertised capabilities and permission state — and every remedy the tool surface
|
|
141
|
+
emits points back at it.
|
|
142
|
+
|
|
143
|
+
## The `gp_*` tool surface
|
|
144
|
+
|
|
145
|
+
| Tool | What it does | Permission |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `gp_probe_status` | engine self-report: version, capabilities, accessibility / input-monitoring / screen-recording / developer-tools state | prompts (diagnostic entry point) |
|
|
148
|
+
| `gp_attach` | attach to a running app by bundle id or pid | prompts |
|
|
149
|
+
| `gp_observe` | snapshot the attached window's accessibility tree (digest, node count) | prompts |
|
|
150
|
+
| `gp_act` | perform one accessibility action and return what the engine observed changing (op id ties it to an evidence pack) | prompts (moves a real app) |
|
|
151
|
+
| `gp_diagnose` | classify an operation from its pack (contaminated / out-of-band / callback / pre-existing / no-change) | prompts |
|
|
152
|
+
| `gp_last_evidence` | fetch the evidence pack the engine archived for an operation | prompts |
|
|
153
|
+
|
|
154
|
+
Whether a method exists is read from the running engine (`hello.capabilities`) — the list
|
|
155
|
+
is never hard-coded here, because it grows upstream of this file. Full reference:
|
|
156
|
+
[`docs/tools.md`](docs/tools.md).
|
|
157
|
+
|
|
158
|
+
## The evidence discipline
|
|
159
|
+
|
|
160
|
+
1. **Judgement stays in Swift.** Attribution, diagnosis class, circuit-breaker level and
|
|
161
|
+
the pass / fail / inconclusive outcome are computed by the GlassPane engine. The
|
|
162
|
+
harness transcribes; it never derives. A CI ratchet fails when a tool surface grows a
|
|
163
|
+
threshold, a pixel verdict or a pass/fail synthesis.
|
|
164
|
+
2. **What the model reads is what the engine said.** Error codes and agent-executable
|
|
165
|
+
remedies go into the tool's `output` string, not only into metadata.
|
|
166
|
+
3. **The audit chain is a product feature.** Verified calls are appended to a hash-chained
|
|
167
|
+
decision log (op id ↔ entry hash); the fixed points re-compute the chain with two
|
|
168
|
+
independent hashers, so the ledger can be verified without trusting the writer.
|
|
169
|
+
4. **Compaction may not erase evidence.** The compaction hook injects the session's
|
|
170
|
+
evidence anchors into the summarisation prompt; absence is stated as arithmetic
|
|
171
|
+
(`gp_* calls: N · decisions recorded: M`), never assumed.
|
|
172
|
+
|
|
173
|
+
Details: [`docs/evidence.md`](docs/evidence.md).
|
|
174
|
+
|
|
175
|
+
## Documentation
|
|
176
|
+
|
|
177
|
+
- [`docs/install.md`](docs/install.md) — install paths, permissions, verification
|
|
178
|
+
- [`docs/models-and-keys.md`](docs/models-and-keys.md) — the model catalog and how to supply
|
|
179
|
+
your own API keys
|
|
180
|
+
- [`docs/tools.md`](docs/tools.md) — the `gp_*` surface, argument by argument
|
|
181
|
+
- [`docs/evidence.md`](docs/evidence.md) — attribution, diagnosis, the decision log
|
|
182
|
+
- [`docs/migrate-from-opencode.md`](docs/migrate-from-opencode.md) — what the fork changed versus upstream
|
|
183
|
+
customisation changed, and what deliberately stayed
|
|
184
|
+
- [`docs/release.md`](docs/release.md) — cutting a release, and the npm trusted-publisher
|
|
185
|
+
setup
|
|
186
|
+
- [`docs/kernel-integration.md`](docs/kernel-integration.md) — the shared kernel, why it is
|
|
187
|
+
not on npm, and how much of the two-way integration exists today
|
|
188
|
+
- [`docs/troubleshooting.md`](docs/troubleshooting.md) — engine unreachable, permission
|
|
189
|
+
denials, the `GP_E_*` codes
|
|
190
|
+
- [`FORK.md`](FORK.md) / [`SYNCLOG.md`](SYNCLOG.md) — the fork's coordinates, discipline
|
|
191
|
+
and per-batch measurements; [`NOTICE`](NOTICE) — attribution;
|
|
192
|
+
[`CHANGELOG.md`](CHANGELOG.md) — what shipped in each version
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
bun install # workspace (on TLS-intercepting networks bun needs
|
|
198
|
+
# NODE_EXTRA_CA_CERTS — see FORK.md)
|
|
199
|
+
bun run typecheck # per package
|
|
200
|
+
bun test --timeout 30000 # fixed-point tests (from packages/opencode)
|
|
201
|
+
bun run build --single # product build: CLI + TUI + embedded web app
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The repo-side gates live in the GlassPane repository under `harness/` (hook-liveness,
|
|
205
|
+
fork-diff, kernel-vendor, tool-surface, surface-semantics, product-surface,
|
|
206
|
+
brand-surface); their rationale, negative controls and known boundaries are documented in
|
|
207
|
+
`harness/README.md`. Every customisation commit carries the `[gp]` prefix.
|
|
208
|
+
|
|
209
|
+
## License & attribution
|
|
210
|
+
|
|
211
|
+
MIT, like upstream. `LICENSE` is upstream's, unchanged; [`NOTICE`](NOTICE) records what
|
|
212
|
+
this fork changed and where the code came from. Upstream:
|
|
213
|
+
[anomalyco/opencode](https://github.com/anomalyco/opencode) `v1.18.32` © SST — without
|
|
214
|
+
them this product does not exist, and `FORK.md` exists so that claim stays true for
|
|
215
|
+
every future sync.
|
|
216
|
+
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<code>glasspane-harness</code>
|
|
3
|
+
</h1>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="README.zh-CN.md"><strong>简体中文</strong></a> ·
|
|
7
|
+
<a href="README.md"><strong>English</strong></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://www.npmjs.com/package/glasspane-harness"><img src="https://img.shields.io/npm/dt/glasspane-harness?label=Downloads&style=for-the-badge&color=2ea44f&logo=npm&logoColor=white" alt="npm downloads"></a>
|
|
12
|
+
<img src="https://img.shields.io/dynamic/json?url=https%3A%2F%2Fregistry.npmjs.org%2Fglasspane-harness%2Flatest&query=version&label=version&color=brightgreen" alt="version">
|
|
13
|
+
<a href="https://github.com/jingzhao-l/glasspane-harness/actions/workflows/ci.yml"><img src="https://github.com/jingzhao-l/glasspane-harness/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
14
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License"></a>
|
|
15
|
+
<img src="https://img.shields.io/badge/macOS-13%2B-blue?logo=apple&logoColor=white" alt="macOS 13+">
|
|
16
|
+
<a href="https://github.com/jingzhao-l/glasspane-harness"><img src="https://img.shields.io/github/stars/jingzhao-l/glasspane-harness?style=social&label=Star" alt="Stars"></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="assets/banner.svg" alt="GlassPane Harness — 证据诚实的 macOS 应用 GUI 验证 harness" width="720">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
**问题。** 一个驱动真实 macOS 应用的 AI agent,没法诚实地说清界面到底有没有按它要求的动。
|
|
26
|
+
它只看得见自己的工具输出,于是靠散文猜测、报成功。截图也不解决问题:截图给的是**某个瞬间**,
|
|
27
|
+
不是**某次变化**,更说不清这次变化是不是那次点击造成的。
|
|
28
|
+
|
|
29
|
+
**GlassPane Harness** 是一个编码 agent([opencode](https://github.com/anomalyco/opencode)
|
|
30
|
+
的 fork),通过原生 `gp_*` 工具面接到 Swift 验证引擎上。每次动作都被测量——辅助功能树 diff、
|
|
31
|
+
像素 diff、崩溃与响应性信号——然后由引擎给出**它自己的**归因(`strong` / `soft` / `none`、
|
|
32
|
+
熔断级、pass / fail / inconclusive)。harness 只转录,不自己下结论。成包的验证调用进哈希链
|
|
33
|
+
决策日志;压缩钩子会把会话的证据锚点重新注入压缩提示词,摘要再长也丢不掉审计链。
|
|
34
|
+
|
|
35
|
+
引擎是 [GlassPane](https://github.com/jingzhao-l/GlassPane),harness 是它的 agent 侧。
|
|
36
|
+
|
|
37
|
+
## 这是一个完整的编程 harness,不是被削过头的 fork
|
|
38
|
+
|
|
39
|
+
验证接线是这次分叉的落点,但它是**加法**:上游 opencode 会做的每一件事,这个 harness 都会做——
|
|
40
|
+
同样的 agent、工具、模型、权限,同样的 LSP 与 MCP 支持,同样的 TUI 与 web 应用,同样的
|
|
41
|
+
SDK 与 plugin API。为了塞下内核,没有任何功能被砍掉。
|
|
42
|
+
|
|
43
|
+
这句话是**量出来的**,不是承诺。分叉锚定上游
|
|
44
|
+
[anomalyco/opencode](https://github.com/anomalyco/opencode) 的某个 tag,
|
|
45
|
+
`harness/tools/fork-diff.mjs` 每次改动都会把本树与那个 tag 逐文件对比:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
fork-diff vs v1.18.32: 6527/6687 byte-identical, 51 edited, 55 added, 54 deleted
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
上游树约 98% 逐字节保持上游原样。改动集中在 config/paths/品牌、`gp_*` 工具面、内核绑定、
|
|
52
|
+
压缩钩子和打包脚本;删掉的是上游**自己的**发布管道(ghcr 镜像、AUR、Homebrew tap、
|
|
53
|
+
托管 console 账号)——外加 0.4.0 里下面说的第一方账号面。
|
|
54
|
+
|
|
55
|
+
"加法"在实践中的含义:上游后续版本发布的功能,这个 fork 接得住——因为功能还在那里。
|
|
56
|
+
|
|
57
|
+
### 唯一的例外,而且是刻意的:没有账号,自带 API key
|
|
58
|
+
|
|
59
|
+
上游可以 `auth login` 登录 opencode 账号,用来解锁 opencode 自家的模型网关(目录里的第一方
|
|
60
|
+
provider)和云端会话分享。本产品没有这个账号,所以这片面是**删掉**而不是留一个跑不通的壳:
|
|
61
|
+
|
|
62
|
+
- 上游目录里的 `opencode` provider 条目被过滤掉
|
|
63
|
+
- `auth login` / `logout` / `switch`、`/org` 命令、组织切换器全部移除
|
|
64
|
+
- `/experimental/console*` 端点与 console 托管 provider 状态全部移除
|
|
65
|
+
- 分享会话到上游 opencode 云端的能力移除(`share` / `autoshare` 配置键一并移除)
|
|
66
|
+
|
|
67
|
+
**目录里其他一切都原封不动。** Anthropic、OpenAI、Google、GitHub Copilot、Amazon Bedrock、
|
|
68
|
+
Azure、OpenRouter 等等全在——几百个模型——每一个都用**你自己的 API key** 解锁:环境变量、
|
|
69
|
+
配置文件里的 `{env:NAME}` 模板,或 TUI 的 `/connect` 对话框。不用注册、不用登录。见
|
|
70
|
+
[`docs/models-and-keys.md`](docs/models-and-keys.md)。
|
|
71
|
+
|
|
72
|
+
## 产品面
|
|
73
|
+
|
|
74
|
+
| 产品面 | 你拿到什么 | 怎么发 |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| **CLI + TUI** | 终端里的 agent:交互式 TUI、`run` 一次性任务、`serve` 无头服务 | `glasspane-harness` / `gp-harness` 二进制,npm + 一键安装器 |
|
|
77
|
+
| **Web 应用**(内嵌) | 二进制自己服务的浏览器界面——没有第二个进程、不单独部署 | 嵌进二进制(`--skip-embed-web-ui` 出纯 CLI 构建) |
|
|
78
|
+
| **文档** | 本仓 `docs/`——安装、工具面、证据、迁移、排障 | 仓内 markdown(canonical,与代码同版本) |
|
|
79
|
+
| **SDK** | TypeScript SDK 与 plugin SDK,用来写自己的 `gp_*` 式工具 | 工作区包,随树发布 |
|
|
80
|
+
|
|
81
|
+
**刻意不发**:上游的托管 console / enterprise 栈(私有 harness 不需要"组织与额度后台"这种形态)、
|
|
82
|
+
上游的**文档站**(`packages/web`)——这两片树已**从本仓删除**;本产品的 canonical 文档是 `docs/`。
|
|
83
|
+
它们从来不属于这个产品,把别人的产品面留在自己仓里只会让私有化变成自欺。Electron 桌面 app 保留在
|
|
84
|
+
树里(产品面,第二批——需要签名与公证账号)。
|
|
85
|
+
|
|
86
|
+
## 安装
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install -g glasspane-harness # 推荐
|
|
90
|
+
bun add -g glasspane-harness
|
|
91
|
+
curl -fsSL https://raw.githubusercontent.com/jingzhao-l/glasspane-harness/main/scripts/install.sh | bash
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
会装出两个命令:**`glasspane-harness`** 与短别名 **`gp-harness`**。
|
|
95
|
+
|
|
96
|
+
**只支持 macOS。** 引擎、权限模型、证据流水线都只存在于 macOS,npm 分发随之收成
|
|
97
|
+
macOS-only:包声明 `os: darwin`,非 macOS 上 npm 直接拒绝安装,而不是给你一个跑不了的
|
|
98
|
+
二进制。**没有 Linux / Windows 构建,这是决定,不是遗漏。**
|
|
99
|
+
|
|
100
|
+
前提:macOS 13+(Apple silicon 或 Intel);要用 `gp_*` 工具面还需要 **GlassPane** 后台服务在跑且已授予辅助功能权限
|
|
101
|
+
([先装 GlassPane](https://github.com/jingzhao-l/GlassPane),安装器会把下一步说清楚)。没有引擎时,
|
|
102
|
+
每个 `gp_*` 调用只会回 `GP_E_ENGINE_UNREACHABLE` 加一条 remedy——那是引擎的话,不是安装器的。
|
|
103
|
+
|
|
104
|
+
若 npm 的全局前缀不可写,用 `npm install -g --prefix "$HOME/.local" glasspane-harness` 并把
|
|
105
|
+
`~/.local/bin` 放进 PATH;安装器会自己检测到这种失败并把这条补救打出来。
|
|
106
|
+
|
|
107
|
+
## 上手
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
glasspane-harness # 交互式 TUI(内嵌 web 应用也会被服务出来)
|
|
111
|
+
glasspane-harness run "attach 到备忘录,打一个标题,然后告诉我 UI 有没有给出证据"
|
|
112
|
+
glasspane-harness serve --port 4096 # 无头服务;web 应用由它提供
|
|
113
|
+
glasspane-harness --help
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
任何会话里第一件该问的事是 `gp_probe_status`:它报引擎版本、宣称的能力与权限状态——
|
|
117
|
+
工具面里每条 remedy 都指回它。
|
|
118
|
+
|
|
119
|
+
## `gp_*` 工具面
|
|
120
|
+
|
|
121
|
+
| 工具 | 作用 | 权限 |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `gp_probe_status` | 引擎自报:版本、能力、辅助功能/输入监听/屏幕录制/开发者工具状态 | 询问(诊断入口) |
|
|
124
|
+
| `gp_attach` | 按 bundle id 或 pid 附着到运行中的应用 | 询问 |
|
|
125
|
+
| `gp_observe` | 快照附着窗口的辅助功能树(digest、节点数) | 询问 |
|
|
126
|
+
| `gp_act` | 执行一次辅助功能动作,返回引擎观察到的东西(op id 关联证据包) | 询问(会动真实应用) |
|
|
127
|
+
| `gp_diagnose` | 按证据包给操作定性(污染 / 带外 / 回调 / 既有问题 / 无变化) | 询问 |
|
|
128
|
+
| `gp_last_evidence` | 取回引擎为某次操作归档的证据包 | 询问 |
|
|
129
|
+
|
|
130
|
+
方法是否存在**从运行中的引擎读**(`hello.capabilities`),本文件不写死清单——那份清单在本文件
|
|
131
|
+
之后还会长。完整参考:[`docs/tools.md`](docs/tools.md)。
|
|
132
|
+
|
|
133
|
+
## 证据纪律
|
|
134
|
+
|
|
135
|
+
1. **判定留在 Swift**:归因、诊断类、熔断级、pass / fail / inconclusive 都由 GlassPane 引擎算;
|
|
136
|
+
harness 只转录、不推导。工具面长出阈值、像素裁决或 pass/fail 合成,CI 棘轮即红。
|
|
137
|
+
2. **模型看到的必须是引擎说过的**:错误码与可执行 remedy 进工具的 `output` 文本,不只塞 metadata。
|
|
138
|
+
3. **审计链是产品功能**:成包调用进哈希链决策日志(op id ↔ entry hash);固定点用两个独立哈希
|
|
139
|
+
实现复算链条,账本不必信任写它的那段代码也能验。
|
|
140
|
+
4. **压缩不许抹掉证据**:压缩钩子把证据锚点注入摘要提示词;缺席以算术呈现
|
|
141
|
+
(`gp_* calls: N · decisions recorded: M`),不许靠默认。
|
|
142
|
+
|
|
143
|
+
细节:[`docs/evidence.md`](docs/evidence.md)。
|
|
144
|
+
|
|
145
|
+
## 文档
|
|
146
|
+
|
|
147
|
+
- [`docs/install.md`](docs/install.md) — 安装通道、权限、自检
|
|
148
|
+
- [`docs/tools.md`](docs/tools.md) — 逐个参数的 `gp_*` 参考
|
|
149
|
+
- [`docs/evidence.md`](docs/evidence.md) — 归因、诊断、决策日志
|
|
150
|
+
- [`docs/migrate-from-opencode.md`](docs/migrate-from-opencode.md) — 相对上游改了什么、刻意留了什么
|
|
151
|
+
- [`docs/troubleshooting.md`](docs/troubleshooting.md) — 引擎不可达、权限拒绝、`GP_E_*` 码表
|
|
152
|
+
- [`FORK.md`](FORK.md) / [`SYNCLOG.md`](SYNCLOG.md) — 分叉坐标、纪律、每批实测数字;
|
|
153
|
+
[`NOTICE`](NOTICE) — 署名;[`CHANGELOG.md`](CHANGELOG.md) — 每个版本发了什么
|
|
154
|
+
|
|
155
|
+
## 开发
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
bun install # 工作区(被 TLS 拦截的网络上 bun 需要 NODE_EXTRA_CA_CERTS,见 FORK.md)
|
|
159
|
+
bun run typecheck
|
|
160
|
+
bun test --timeout 30000 # 固定点测试(在 packages/opencode 里跑)
|
|
161
|
+
bun run build --single # 产品构建:CLI + TUI + 内嵌 web 应用
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
仓侧的闸住在 GlassPane 仓的 `harness/`(hook-liveness、fork-diff、kernel-vendor、
|
|
165
|
+
tool-surface、surface-semantics、product-surface、brand-surface),理由、反向控制与已知边界
|
|
166
|
+
写在 `harness/README.md`。我们每个定制提交都带 `[gp]` 前缀。
|
|
167
|
+
|
|
168
|
+
## 许可与署名
|
|
169
|
+
|
|
170
|
+
MIT,与上游一致。`LICENSE` 是上游原文未改,[`NOTICE`](NOTICE) 记录本 fork 改了什么、代码来自哪里。
|
|
171
|
+
上游:[anomalyco/opencode](https://github.com/anomalyco/opencode) `v1.18.32` © SST——没有他们就没有
|
|
172
|
+
这个产品,而 `FORK.md` 的存在是为了让这句话在未来的每次同步后仍然成立。
|
|
173
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
echo "Error: glasspane-harness's postinstall script was not run." >&2
|
|
2
|
+
echo "" >&2
|
|
3
|
+
echo "This occurs when using --ignore-scripts during installation, or when using a" >&2
|
|
4
|
+
echo "package manager like pnpm that does not run postinstall scripts by default." >&2
|
|
5
|
+
echo "" >&2
|
|
6
|
+
echo "To fix this, run the postinstall script manually:" >&2
|
|
7
|
+
echo " cd node_modules/glasspane-harness && node postinstall.mjs" >&2
|
|
8
|
+
echo "" >&2
|
|
9
|
+
echo "Or reinstall glasspane-harness without the --ignore-scripts flag." >&2
|
|
10
|
+
exit 1
|
package/package.json
CHANGED
|
@@ -1 +1,50 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "glasspane-harness",
|
|
3
|
+
"description": "GlassPane Harness — evidence-honest GUI verification harness for macOS apps (opencode fork with GlassPane's native gp_* tool surface)",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"glasspane",
|
|
6
|
+
"harness",
|
|
7
|
+
"gui-testing",
|
|
8
|
+
"macos",
|
|
9
|
+
"accessibility",
|
|
10
|
+
"evidence",
|
|
11
|
+
"agent"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/jingzhao-l/glasspane-harness.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/jingzhao-l/glasspane-harness#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/jingzhao-l/glasspane-harness/issues"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"glasspane-harness": "./bin/glasspane-harness.exe",
|
|
23
|
+
"gp-harness": "./bin/glasspane-harness.exe"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"postinstall": "node ./postinstall.mjs"
|
|
27
|
+
},
|
|
28
|
+
"version": "0.6.4",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"os": [
|
|
31
|
+
"darwin"
|
|
32
|
+
],
|
|
33
|
+
"cpu": [
|
|
34
|
+
"arm64",
|
|
35
|
+
"x64"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"funding": "https://github.com/sponsors/jingzhao-l",
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"tag": "latest"
|
|
44
|
+
},
|
|
45
|
+
"sideEffects": false,
|
|
46
|
+
"optionalDependencies": {
|
|
47
|
+
"glasspane-harness-darwin-arm64": "0.6.4",
|
|
48
|
+
"glasspane-harness-darwin-x64": "0.6.4"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import childProcess from "child_process"
|
|
4
|
+
import fs from "fs"
|
|
5
|
+
import os from "os"
|
|
6
|
+
import path from "path"
|
|
7
|
+
import { createRequire } from "module"
|
|
8
|
+
import { fileURLToPath } from "url"
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const require = createRequire(import.meta.url)
|
|
12
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
|
|
13
|
+
|
|
14
|
+
const platformMap = {
|
|
15
|
+
darwin: "darwin",
|
|
16
|
+
linux: "linux",
|
|
17
|
+
win32: "windows",
|
|
18
|
+
}
|
|
19
|
+
const archMap = {
|
|
20
|
+
x64: "x64",
|
|
21
|
+
arm64: "arm64",
|
|
22
|
+
arm: "arm",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const platform = platformMap[os.platform()] ?? os.platform()
|
|
26
|
+
const arch = archMap[os.arch()] ?? os.arch()
|
|
27
|
+
// Product identity lives in product.json at the repo root; product-surface.mjs
|
|
28
|
+
// keeps these constants in step with it (this file ships inside the npm wrapper).
|
|
29
|
+
const base = `glasspane-harness-${platform}-${arch}`
|
|
30
|
+
const sourceBinary = platform === "windows" ? "glasspane-harness.exe" : "glasspane-harness"
|
|
31
|
+
const targetBinary = path.join(__dirname, "bin", "glasspane-harness.exe")
|
|
32
|
+
|
|
33
|
+
function supportsAvx2() {
|
|
34
|
+
if (arch !== "x64") return false
|
|
35
|
+
|
|
36
|
+
if (platform === "linux") {
|
|
37
|
+
try {
|
|
38
|
+
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
39
|
+
} catch {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (platform === "darwin") {
|
|
45
|
+
try {
|
|
46
|
+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
47
|
+
encoding: "utf8",
|
|
48
|
+
timeout: 1500,
|
|
49
|
+
})
|
|
50
|
+
if (result.status !== 0) return false
|
|
51
|
+
return (result.stdout || "").trim() === "1"
|
|
52
|
+
} catch {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (platform === "windows") {
|
|
58
|
+
const command =
|
|
59
|
+
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
|
|
60
|
+
|
|
61
|
+
for (const executable of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
|
|
62
|
+
try {
|
|
63
|
+
const result = childProcess.spawnSync(executable, ["-NoProfile", "-NonInteractive", "-Command", command], {
|
|
64
|
+
encoding: "utf8",
|
|
65
|
+
timeout: 3000,
|
|
66
|
+
windowsHide: true,
|
|
67
|
+
})
|
|
68
|
+
if (result.status !== 0) continue
|
|
69
|
+
const output = (result.stdout || "").trim().toLowerCase()
|
|
70
|
+
if (output === "true" || output === "1") return true
|
|
71
|
+
if (output === "false" || output === "0") return false
|
|
72
|
+
} catch {
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isMusl() {
|
|
82
|
+
if (platform !== "linux") return false
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
86
|
+
} catch {
|
|
87
|
+
// Ignore filesystem probes that are blocked by the host.
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
92
|
+
return `${result.stdout || ""}${result.stderr || ""}`.toLowerCase().includes("musl")
|
|
93
|
+
} catch {
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function packageNames() {
|
|
99
|
+
const baseline = arch === "x64" && !supportsAvx2()
|
|
100
|
+
|
|
101
|
+
if (platform === "linux") {
|
|
102
|
+
if (isMusl()) {
|
|
103
|
+
if (arch === "x64")
|
|
104
|
+
return baseline
|
|
105
|
+
? [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
106
|
+
: [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
107
|
+
return [`${base}-musl`, base]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (arch === "x64")
|
|
111
|
+
return baseline
|
|
112
|
+
? [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
|
113
|
+
: [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
|
114
|
+
return [base, `${base}-musl`]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (arch === "x64") return baseline ? [`${base}-baseline`, base] : [base, `${base}-baseline`]
|
|
118
|
+
return [base]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resolveBinary(name) {
|
|
122
|
+
const packageJsonPath = require.resolve(`${name}/package.json`)
|
|
123
|
+
const binaryPath = path.join(path.dirname(packageJsonPath), "bin", sourceBinary)
|
|
124
|
+
if (!fs.existsSync(binaryPath)) throw new Error(`Binary not found at ${binaryPath}`)
|
|
125
|
+
return binaryPath
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function installPackage(name) {
|
|
129
|
+
const version = packageJson.optionalDependencies?.[name]
|
|
130
|
+
if (!version) return
|
|
131
|
+
|
|
132
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "glasspane-harness-install-"))
|
|
133
|
+
try {
|
|
134
|
+
const result = childProcess.spawnSync(
|
|
135
|
+
"npm",
|
|
136
|
+
["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", temp, `${name}@${version}`],
|
|
137
|
+
{ stdio: "inherit", windowsHide: true },
|
|
138
|
+
)
|
|
139
|
+
if (result.status !== 0) return
|
|
140
|
+
const packageDir = path.join(temp, "node_modules", name)
|
|
141
|
+
copyBinary(path.join(packageDir, "bin", sourceBinary), targetBinary)
|
|
142
|
+
return true
|
|
143
|
+
} finally {
|
|
144
|
+
fs.rmSync(temp, { recursive: true, force: true })
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function copyBinary(source, target) {
|
|
149
|
+
if (!fs.existsSync(source)) throw new Error(`Binary not found at ${source}`)
|
|
150
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
151
|
+
if (fs.existsSync(target)) fs.unlinkSync(target)
|
|
152
|
+
try {
|
|
153
|
+
fs.linkSync(source, target)
|
|
154
|
+
} catch {
|
|
155
|
+
fs.copyFileSync(source, target)
|
|
156
|
+
}
|
|
157
|
+
fs.chmodSync(target, 0o755)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function verifyBinary() {
|
|
161
|
+
const result = childProcess.spawnSync(targetBinary, ["--version"], {
|
|
162
|
+
encoding: "utf8",
|
|
163
|
+
stdio: "ignore",
|
|
164
|
+
windowsHide: true,
|
|
165
|
+
})
|
|
166
|
+
return result.status === 0
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function main() {
|
|
170
|
+
for (const name of packageNames()) {
|
|
171
|
+
try {
|
|
172
|
+
copyBinary(resolveBinary(name), targetBinary)
|
|
173
|
+
if (verifyBinary()) return
|
|
174
|
+
} catch {
|
|
175
|
+
if (installPackage(name) && verifyBinary()) return
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
throw new Error(
|
|
180
|
+
`It seems your package manager failed to install the right glasspane-harness CLI package. Try manually installing ${packageNames()
|
|
181
|
+
.map((name) => JSON.stringify(name))
|
|
182
|
+
.join(" or ")}.`,
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
main()
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error(error.message)
|
|
190
|
+
process.exit(1)
|
|
191
|
+
}
|