page-harness 0.1.11 → 0.1.13
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/AGENTS.md +43 -0
- package/README.md +4 -1
- package/README.zh-CN.md +4 -1
- package/dist/cli.js +28 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/page/overlay.js +15 -15
- package/docs/development.md +82 -0
- package/package.json +3 -1
package/AGENTS.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 给改这个仓库的代理
|
|
2
|
+
|
|
3
|
+
用户文档在 [README.md](./README.md) 和 [README.zh-CN.md](./README.zh-CN.md)。目录、命令和开发约定在 [docs/development.md](./docs/development.md)。这里只写改代码时容易踩错的约束。
|
|
4
|
+
|
|
5
|
+
## 先跑什么
|
|
6
|
+
|
|
7
|
+
Node 20.11+。根目录 `npm ci` 之后:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm run lint
|
|
11
|
+
npm run typecheck
|
|
12
|
+
npm test
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
改了 `dsh-plugin/` 还要在该目录跑 `npm run check` 和 `npm test`。改了页面上能看见的行为,再 `npm run build && npm run test:e2e`。`dist/` 被 gitignore,不要提交构建产物。
|
|
16
|
+
|
|
17
|
+
## 两个包,两条运行路径
|
|
18
|
+
|
|
19
|
+
- 根包 `page-harness` 是 CLI。它代理用户的页面,注入 overlay,并用本机的 agent CLI 改源码。
|
|
20
|
+
- `dsh-plugin/` 是 DeepSeek Harness 插件 `page-harness-dsh`。预览由 DSH 调 CLI,参数带 `--no-agent`。候选由 DSH 模型调用 `page_harness_propose`,不要在插件里启动 page-harness 自己的 agent,也不要把几版同时写进源码。
|
|
21
|
+
|
|
22
|
+
插件装进 DSH profile 后是文件副本,不是源码目录的符号链接。改 `dsh-plugin/lib/` 不会自动出现在正在跑的 DSH 里。
|
|
23
|
+
|
|
24
|
+
## 不要改的行为
|
|
25
|
+
|
|
26
|
+
- 预览端口保持独立。DSH 用自己的端口(常见 3080)。页面预览默认 `17994`,面板和 `/__ph/ws` 用下一个端口。不要把这两个端口反代进 3080,也不要加路径前缀。
|
|
27
|
+
- 回环或 HTTPS 上的面板 cookie 是 `SameSite=None; Secure; Partitioned`。公网 HTTP 保持 `SameSite=Strict`。无 cookie 的 WebSocket 必须继续 403。
|
|
28
|
+
- `POST /__ph/api/review/resolve` 只给本机 curl / 复制出的提示词。带 `Origin`、跨站 `Sec-Fetch-Site` 或面板 session cookie 的请求返回 403。面板改意见走 WebSocket,不走这个地址。
|
|
29
|
+
- DSH 里采用某一版时,输入框里的 `<page-element>` 不带内部 HTML。选择器、标签和文本放在属性上,具体改动写在后面的句子里。
|
|
30
|
+
|
|
31
|
+
## 插件客户端
|
|
32
|
+
|
|
33
|
+
`dsh-plugin/lib/client.js` 是一个没有 import 的浏览器脚本。和它对应的纯函数在 `propose.js`、`preview-bar.js`、`share-bar.js`。改其中一份时,另一份必须保持函数体一致;`dsh-plugin/test/` 会比对。`npm test` 不会执行 `client.js`,所以还要过 `npm run check`。
|
|
34
|
+
|
|
35
|
+
## 提交
|
|
36
|
+
|
|
37
|
+
功能 PR 不要改 `package.json` 的 `version`。提交说明用英文祈使句,说明为什么改。没有明确要求就不要 push,也不要开新的发布 tag。
|
|
38
|
+
|
|
39
|
+
## 发版
|
|
40
|
+
|
|
41
|
+
只发布根包 `page-harness`。tag 必须是 `v` 加 `package.json` 里的版本,例如 `v0.1.12`。不要给 `page-harness-dsh` 单独发版,也不要改它的版本号。
|
|
42
|
+
|
|
43
|
+
DSH 直接安装 npm 上的 `page-harness@<该版本>`。插件源码留在 `dsh-plugin/`,运行时用这个 CLI 包。以后每次发版都这样。
|
package/README.md
CHANGED
|
@@ -273,7 +273,7 @@ There is no login: it is a local developer tool and binds to `127.0.0.1` by defa
|
|
|
273
273
|
- **Two origins.** Your app (and any script on it) runs on the app port; the panel, API and agent endpoints are on the control port, so page scripts cannot read them.
|
|
274
274
|
- **Owner and reviewers.** Without `--share` only this machine can connect and everything behaves as one user. With `--share`, the owner session is only issued to loopback requests; other machines need the invite link, which sets signed (HMAC) reviewer cookies for the panel and the app port; without them both ports answer `403`. Reviewers are limited to the review actions on the WebSocket and to reading artifacts; everything else returns `403`. Name the owner in Settings → Team.
|
|
275
275
|
- **Session cookie.** The panel gets an `HttpOnly`, `SameSite=Strict` cookie scoped to `/__ph`; the cookie is stripped before requests are forwarded to your dev server.
|
|
276
|
-
- **API.** Reads require a same-origin `Sec-Fetch-Site`; writes require a matching `Origin` plus a custom `X-PH` header. The WebSocket checks the cookie and `Origin`. The bookmarklet is the exception: cross-origin review reads and writes need the per-run `X-PH-Token`, and the response reflects that page's `Origin` (never `*`). `GET /__ph/api/review` stays callable from curl without CORS headers.
|
|
276
|
+
- **API.** Reads require a same-origin `Sec-Fetch-Site`; writes require a matching `Origin` plus a custom `X-PH` header. The WebSocket checks the cookie and `Origin`. The bookmarklet is the exception: cross-origin review reads and writes need the per-run `X-PH-Token`, and the response reflects that page's `Origin` (never `*`). `GET /__ph/api/review` stays callable from curl without CORS headers. `POST /__ph/api/review/resolve` is the local agent's endpoint, so it takes no cookie and no token: it accepts only requests without an `Origin` (curl, the copied prompt), and refuses browsers, cross-site requests and session cookies with `403`.
|
|
277
277
|
- **MCP.** Every agent turn gets its own bearer token (passed through an environment variable or a private temp file, never on the command line) that is revoked when the turn ends. Browser requests to the MCP endpoint are rejected.
|
|
278
278
|
- **Host allow-list.** Requests with unexpected `Host` headers get `421` (DNS rebinding). Add names with `--allow-host` if you expose it on a LAN.
|
|
279
279
|
- **Framing.** The panel may only be framed by the app origin; the history page cannot be framed at all.
|
|
@@ -328,11 +328,14 @@ Delete `<project>/.page-harness/`.
|
|
|
328
328
|
| [Security model](#security-model) | What is locked down and how |
|
|
329
329
|
| [Data](#data) | What is stored in `.page-harness/` |
|
|
330
330
|
| [FAQ](#faq) | Common questions |
|
|
331
|
+
| [Development](./docs/development.md) | Repository map, commands, and conventions |
|
|
331
332
|
| [Homepage](https://cocofhu.github.io/page-harness-site/) | Product tour and install |
|
|
332
333
|
| [npm package](https://www.npmjs.com/package/page-harness) | Released versions |
|
|
333
334
|
|
|
334
335
|
## Development
|
|
335
336
|
|
|
337
|
+
Contributor guide: [docs/development.md](./docs/development.md). Agent notes: [AGENTS.md](./AGENTS.md).
|
|
338
|
+
|
|
336
339
|
```bash
|
|
337
340
|
npm install
|
|
338
341
|
npm run dev # rebuild client bundles and the CLI on change
|
package/README.zh-CN.md
CHANGED
|
@@ -273,7 +273,7 @@ GET http://127.0.0.1:17991/__ph/api/review?scope=all&path=%2F&page=1&pageSize=20
|
|
|
273
273
|
- **两个源。** 你的应用(以及其中任何脚本)在应用端口;面板、API、Agent 接口在控制端口,页面脚本读不到。
|
|
274
274
|
- **所有者与评审者。** 不加 `--share` 时只有本机能连,一切按单用户处理。加了 `--share` 后,所有者会话只发给本机回环请求;其他机器必须通过邀请链接拿到带 HMAC 签名的评审者 Cookie(面板和应用端口各一个),没有就两个端口都返回 `403`。评审者在 WebSocket 上只能做评审操作,API 只能读产物,其余一律 `403`。所有者名字在设置 → 团队里修改。
|
|
275
275
|
- **会话 Cookie。** 面板拿到 `HttpOnly`、`SameSite=Strict`、作用于 `/__ph` 的 Cookie;转发到你的开发服务器前会去掉它。
|
|
276
|
-
- **API。** 读请求要求同源的 `Sec-Fetch-Site`;写请求要求匹配的 `Origin` 加自定义 `X-PH` 头。WebSocket 校验 Cookie 和 `Origin`。书签是例外:跨源读写意见必须带本次运行的 `X-PH-Token`,响应会反射该页面的 `Origin`(不会是 `*`)。`GET /__ph/api/review` 仍给 curl 用,不带 CORS
|
|
276
|
+
- **API。** 读请求要求同源的 `Sec-Fetch-Site`;写请求要求匹配的 `Origin` 加自定义 `X-PH` 头。WebSocket 校验 Cookie 和 `Origin`。书签是例外:跨源读写意见必须带本次运行的 `X-PH-Token`,响应会反射该页面的 `Origin`(不会是 `*`)。`GET /__ph/api/review` 仍给 curl 用,不带 CORS 头。`POST /__ph/api/review/resolve` 是本机 Agent 的接口,既不带 Cookie 也不带令牌:只接受不带 `Origin` 的请求(curl、复制出的提示词),浏览器、跨站请求和会话 Cookie 一律返回 `403`。
|
|
277
277
|
- **MCP。** 每一轮 Agent 调用都发一个独立的 Bearer token(通过环境变量或私有临时文件传递,不出现在命令行),回合结束即作废。来自浏览器的 MCP 请求一律拒绝。
|
|
278
278
|
- **Host 白名单。** `Host` 头不符的请求返回 `421`(防 DNS rebinding)。在局域网使用时用 `--allow-host` 添加。
|
|
279
279
|
- **嵌入限制。** 面板只允许被应用源嵌入;聊天记录页不允许被任何页面嵌入。
|
|
@@ -328,11 +328,14 @@ team.json 邀请 token、签名密钥和评审者名字(权
|
|
|
328
328
|
| [安全模型](#安全模型) | 哪些地方锁住了,怎么锁的 |
|
|
329
329
|
| [数据](#数据) | `.page-harness/` 里存了什么 |
|
|
330
330
|
| [常见问题](#常见问题) | 大家常问的 |
|
|
331
|
+
| [开发说明](./docs/development.md) | 仓库地图、命令和开发约定 |
|
|
331
332
|
| [官网](https://cocofhu.github.io/page-harness-site/) | 产品演示与安装 |
|
|
332
333
|
| [npm 包](https://www.npmjs.com/package/page-harness) | 已发布的版本 |
|
|
333
334
|
|
|
334
335
|
## 开发
|
|
335
336
|
|
|
337
|
+
贡献说明见 [docs/development.md](./docs/development.md),改代码时的约束见 [AGENTS.md](./AGENTS.md)。
|
|
338
|
+
|
|
336
339
|
```bash
|
|
337
340
|
npm install
|
|
338
341
|
npm run dev # 监听并重建前端脚本与 CLI
|
package/dist/cli.js
CHANGED
|
@@ -2833,6 +2833,9 @@ function parseCookies(header) {
|
|
|
2833
2833
|
return out;
|
|
2834
2834
|
}
|
|
2835
2835
|
var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
|
2836
|
+
function isLoopbackBind(host) {
|
|
2837
|
+
return host === "localhost" || LOOPBACK.has(host);
|
|
2838
|
+
}
|
|
2836
2839
|
function isLoopback(req) {
|
|
2837
2840
|
return LOOPBACK.has(req.socket.remoteAddress ?? "");
|
|
2838
2841
|
}
|
|
@@ -2894,8 +2897,17 @@ var Auth = class {
|
|
|
2894
2897
|
get sharing() {
|
|
2895
2898
|
return !!this.opts.team?.enabled;
|
|
2896
2899
|
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Panel session. SameSite=Strict is discarded when DSH (127.0.0.1) frames the localhost
|
|
2902
|
+
* panel, so the WebSocket never gets a session and the overlay stays on "Lost connection".
|
|
2903
|
+
* Loopback and https use a partitioned cookie, which the browser keeps in that iframe.
|
|
2904
|
+
* Plain HTTP on a public host stays Strict: a Secure cookie would not be stored there.
|
|
2905
|
+
*/
|
|
2897
2906
|
setCookie(res, value) {
|
|
2898
|
-
|
|
2907
|
+
const persistent = value === this.uiSecret ? "" : "; Max-Age=31536000";
|
|
2908
|
+
const partitioned = this.scheme === "https" || isLoopbackBind(this.opts.host);
|
|
2909
|
+
const sameSite = partitioned ? "; SameSite=None; Secure; Partitioned" : `; SameSite=Strict${this.secureAttr}`;
|
|
2910
|
+
res.appendHeader("Set-Cookie", `${this.cookieName}=${value}; Path=/__ph; HttpOnly${sameSite}${persistent}`);
|
|
2899
2911
|
}
|
|
2900
2912
|
/**
|
|
2901
2913
|
* Called when a panel page is served. Keeps an existing session; otherwise local requests get
|
|
@@ -2981,6 +2993,18 @@ var Auth = class {
|
|
|
2981
2993
|
revokeMcpToken(token) {
|
|
2982
2994
|
this.mcpTokens.delete(token);
|
|
2983
2995
|
}
|
|
2996
|
+
/**
|
|
2997
|
+
* The local review API (`POST /__ph/api/review/resolve`) is the one write a coding agent makes with
|
|
2998
|
+
* plain curl, so it can carry neither the session cookie nor a token. Browsers must not reach it:
|
|
2999
|
+
* every browser POST carries `Origin`, and a page that could call this would mark the user's notes
|
|
3000
|
+
* resolved with no session, no token and no prompt.
|
|
3001
|
+
*/
|
|
3002
|
+
checkLocalAgent(req) {
|
|
3003
|
+
if (req.headers.origin) return "browsers may not call the local agent API";
|
|
3004
|
+
if (req.headers["sec-fetch-site"] === "cross-site") return "cross-site requests are not accepted here";
|
|
3005
|
+
if (req.headers.cookie && parseCookies(req.headers.cookie)[this.cookieName]) return "session cookies are not accepted here";
|
|
3006
|
+
return null;
|
|
3007
|
+
}
|
|
2984
3008
|
/** MCP endpoint: agent CLIs only. */
|
|
2985
3009
|
checkMcp(req) {
|
|
2986
3010
|
if (req.headers.origin) return { error: "browsers may not call the MCP endpoint" };
|
|
@@ -5026,7 +5050,7 @@ async function startServers(o) {
|
|
|
5026
5050
|
if (!o.auth.allowEmbed(req)) return noAccess(res, "Open this preview from the embedding page.<br>\u8BF7\u4ECE\u5D4C\u5165\u9884\u89C8\u7684\u9875\u9762\u6253\u5F00\u3002");
|
|
5027
5051
|
const url = new URL(req.url ?? "/", "http://x");
|
|
5028
5052
|
const p = url.pathname;
|
|
5029
|
-
if (req.method === "OPTIONS" && (p === "/__ph/overlay.js" || p.startsWith("/__ph/api/review"))) {
|
|
5053
|
+
if (req.method === "OPTIONS" && p !== "/__ph/api/review/resolve" && (p === "/__ph/overlay.js" || p.startsWith("/__ph/api/review"))) {
|
|
5030
5054
|
bookmarkletCors(req, res);
|
|
5031
5055
|
res.writeHead(204, { "Cache-Control": "no-store" });
|
|
5032
5056
|
return res.end();
|
|
@@ -5258,6 +5282,8 @@ async function serveReviewExtras(o, req, res, url) {
|
|
|
5258
5282
|
return true;
|
|
5259
5283
|
}
|
|
5260
5284
|
if (m === "POST" && p === "/__ph/api/review/resolve") {
|
|
5285
|
+
const err = o.auth.checkLocalAgent(req);
|
|
5286
|
+
if (err) return json(res, { ok: false, error: err }, 403), true;
|
|
5261
5287
|
await serveReviewResolve(o.gateway, req, res);
|
|
5262
5288
|
return true;
|
|
5263
5289
|
}
|