dsh-grok-provider 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/CHANGELOG.md +15 -0
- package/CONTRIBUTING.md +51 -0
- package/LICENSE +21 -0
- package/README.en.md +200 -0
- package/README.md +200 -0
- package/SECURITY.md +35 -0
- package/dist/client/client.js +215 -0
- package/dist/host/index.mjs +147 -0
- package/dist/internal/account-dashboard.mjs +67 -0
- package/dist/internal/auth-controller.mjs +184 -0
- package/dist/internal/auth-registry.mjs +64 -0
- package/dist/internal/auth-rpc.mjs +118 -0
- package/dist/internal/billing-summary.mjs +105 -0
- package/dist/internal/credential-source.mjs +168 -0
- package/dist/internal/grok-adapter.mjs +160 -0
- package/dist/internal/grok-command-handler.mjs +62 -0
- package/dist/internal/grok-command.mjs +17 -0
- package/dist/internal/grok-transport.mjs +279 -0
- package/dist/internal/model-catalog.mjs +142 -0
- package/dist/internal/official-auth-driver.mjs +40 -0
- package/dist/internal/official-cli-auth.mjs +231 -0
- package/dist/internal/official-cli-verifier.mjs +62 -0
- package/dist/internal/official-credential-loader.mjs +64 -0
- package/dist/internal/provider-runtime.mjs +43 -0
- package/dist/internal/responses-codec.mjs +357 -0
- package/dist/internal/responses-request.mjs +246 -0
- package/dist/internal/responses-sse.mjs +123 -0
- package/docs/01-product-requirements.md +128 -0
- package/docs/02-architecture-options.md +180 -0
- package/docs/03-security-threat-model.md +243 -0
- package/docs/04-harness-contract.md +325 -0
- package/docs/05-test-plan.md +224 -0
- package/docs/06-release-plan.md +182 -0
- package/docs/07-decision-gate.md +76 -0
- package/docs/08-upstream-cli-1.0.5-evidence.md +98 -0
- package/docs/09-implementation-status.md +37 -0
- package/docs/README.md +60 -0
- package/docs/adr/0001-auth-and-transport-route.md +86 -0
- package/docs/adr/0002-v0.1-scope.md +46 -0
- package/docs/adr/0003-dual-authentication.md +77 -0
- package/docs/adr/0004-dynamic-model-catalog.md +34 -0
- package/docs/adr/0005-official-cli-only-authentication.md +36 -0
- package/docs/adr/0006-account-dashboard.md +84 -0
- package/grok-provider.patch.yml +3 -0
- package/package.json +92 -0
- package/types/index.d.ts +9 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# 安全与威胁模型
|
|
2
|
+
|
|
3
|
+
## 1. 结论
|
|
4
|
+
|
|
5
|
+
`0.1.0` 的安全边界建立在两条原则上:
|
|
6
|
+
|
|
7
|
+
- 登录由 Harness 发起,但认证协议由 xAI 官方 Grok Build CLI 完成。
|
|
8
|
+
- 本插件取得的推理 Bearer token 只在 Host 的凭据读取器和固定 origin transport 之间短暂流动。官方 CLI 的登录网络、代理、托管配置同步与遥测是独立上游边界,不受该 transport 约束。
|
|
9
|
+
|
|
10
|
+
只有本文所有 P0 门禁和 [测试计划](./05-test-plan.md) 的安全用例通过,才允许发布。
|
|
11
|
+
|
|
12
|
+
## 2. 信任边界
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
Web renderer / TUI
|
|
16
|
+
│ 闭合 RPC 或 /grok 命令;无 token、URL、path、env
|
|
17
|
+
▼
|
|
18
|
+
Host AuthCoordinator
|
|
19
|
+
├─ OfficialGrokLoginBridge
|
|
20
|
+
│ └─ path/version-constrained ~/.grok/bin/grok[.exe] login --oauth
|
|
21
|
+
│ ├─ 标准配置:系统浏览器与 xAI OAuth
|
|
22
|
+
│ ├─ 有效配置也可能选择 external helper / devbox / 企业 OIDC
|
|
23
|
+
│ ├─ CLI 自己的代理、托管配置同步与遥测
|
|
24
|
+
│ └─ ~/.grok/auth.json
|
|
25
|
+
├─ OfficialSessionCredentialSource
|
|
26
|
+
│ └─ 只读 ~/.grok/auth.json
|
|
27
|
+
└─ PinnedGrokTransport
|
|
28
|
+
└─ cli-chat-proxy.grok.com(models / responses / billing?format=credits)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
受保护资产:
|
|
32
|
+
|
|
33
|
+
- access token、refresh token、授权码、state 和 PKCE verifier。
|
|
34
|
+
- 用户提示词、工具定义、工具参数与工具结果。
|
|
35
|
+
- 账号身份、订阅状态和配额。
|
|
36
|
+
- Host 进程、用户文件和 xAI 请求额度。
|
|
37
|
+
- npm 发布身份与供应链完整性。
|
|
38
|
+
|
|
39
|
+
## 3. 威胁与控制
|
|
40
|
+
|
|
41
|
+
| 威胁 | 影响 | 控制 | 等级 |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| renderer 把任意命令交给 Host | 本机任意代码执行 | RPC/命令只接受闭合 action;路径和参数完全由 Host 生成 | P0 |
|
|
44
|
+
| PATH 或 workspace 劫持 `grok` | 执行恶意二进制 | 不从 cwd 搜索;优先官方 `~/.grok/bin`;realpath、owner、普通文件与目录包含关系校验 | P0 |
|
|
45
|
+
| 插件参数被 shell 解释 | 任意命令执行 | 只用 Harness `ctx.subprocess`、绝对 argv、固定命令表;插件不显式启动 shell | P0 |
|
|
46
|
+
| CLI 有效配置运行 external auth command | 以当前用户权限执行 `sh -c`/`cmd /C` | 官方 CLI 与 user/system/MDM 配置是显式信任边界;已知环境型覆盖在 spawn 前拒绝;磁盘/MDM 配置不能可靠预判;登录后只接受与绑定生产 OIDC schema 相符的候选;README 披露不能保证端到端无 shell | P0 |
|
|
47
|
+
| 登录进程继承 DSH secrets | 凭据横向泄漏 | 构造最小环境;移除 npm token、DSH credential、API keys 和无关 secret | P0 |
|
|
48
|
+
| 登录 stdout/stderr 含敏感字段 | token 或 OAuth 事务泄漏到 UI/日志 | 不透传原始输出;每流 64 KiB;仅映射闭合状态与稳定错误码 | P0 |
|
|
49
|
+
| 登录进程悬挂或并发 | listener/进程泄漏、凭据竞争 | single-flight、5 分钟超时、AbortSignal、取消、卸载清理 | P0 |
|
|
50
|
+
| 恶意/损坏 auth.json | 内存耗尽、解析攻击、任意文件读取 | 路径不来自 UI;64 KiB;普通文件;拒绝 symlink/reparse;严格 schema | P0 |
|
|
51
|
+
| external/企业/歧义凭据误送固定 xAI Proxy | token 泄漏或账号混淆 | 唯一候选;闭合 auth mode/issuer/scope/client/expiry 关系;schema 不符失败关闭;承认 metadata 未签名 | P0 |
|
|
52
|
+
| token 被发往自定义或重定向 origin | 账号接管 | endpoint ID;固定 HTTPS origin/path;`redirect: "error"` | P0 |
|
|
53
|
+
| Authorization 被继承到远端 URL | 账号接管 | `0.1.0` 无图片下载和任意 URL 请求;transport 不接受 URL | P0 |
|
|
54
|
+
| SSE/压缩响应无限增长 | 内存、CPU、磁盘 DoS | 字节、行、事件、总时长和 idle timeout 双重上限 | P0 |
|
|
55
|
+
| 原始远端错误返回 UI | token、账号或内部信息泄漏 | 只返回插件稳定错误码和安全文案;Harness RPC correlation 留在 carrier 内部 | P0 |
|
|
56
|
+
| billing 响应或 credential metadata 越界进入 renderer | 身份、订阅或凭据泄漏 | Host 严格抽取百分比、周期与模型 capability;拒绝/忽略 identity、balance、history、headers、URL 和原始响应 | P0 |
|
|
57
|
+
| token 到期被误标为额度重置 | 错误产品决策、误导用户 | 重置时间只接受 billing period end;credential `expires_at` 不进入 dashboard DTO | P0 |
|
|
58
|
+
| 凭据轮换期间读到半写文件 | 认证失败或旧 token 重放 | 只接受完整 JSON;短退避重读;不写回;已发送请求不自动重放 | P1 |
|
|
59
|
+
| CLI 更新改变协议或文件格式 | 静默错误 | 登录前后重查 realpath/identity/version;只允许发布时冻结的有限精确版本集合,未知更高/更低版本都失败;CLI 自身更新行为属于 vendor boundary | P1 |
|
|
60
|
+
| 未授权复用官方/第三方 OAuth Client ID | 客户端冒充、封禁或条款违约 | 包中不存在独立 OAuth client;只调用官方 CLI 的公开登录命令 | P0 |
|
|
61
|
+
| 恶意 device-flow URI/code | 用户被引向钓鱼站或 token 被劫持 | discovery 与 device/token/revoke endpoint 全部固定;verification URI 只允许 `https://auth.x.ai` 精确路径;device_code 永不进入 renderer | P0 |
|
|
62
|
+
| 凭据 generation/账号目录混用 | 把请求发到错误账号 | 每次 prepare 冻结 auth generation;catalog、lease、logout、401 都绑定同一 generation;禁止 silent fallback | P0 |
|
|
63
|
+
| npm 依赖安装脚本 | 供应链代码执行 | 目标零 runtime dependencies;全图禁止 lifecycle scripts | P0 |
|
|
64
|
+
|
|
65
|
+
## 4. 官方 CLI 进程边界
|
|
66
|
+
|
|
67
|
+
### 可执行文件发现
|
|
68
|
+
|
|
69
|
+
默认候选:
|
|
70
|
+
|
|
71
|
+
- macOS:`${HOME}/.grok/bin/grok`
|
|
72
|
+
- Windows:`${USERPROFILE}\\.grok\\bin\\grok.exe`
|
|
73
|
+
|
|
74
|
+
首版只使用 Host 启动时冻结的绝对 `GROK_HOME`(默认 `${HOME}/.grok`)下 `bin/grok[.exe]`。不支持 `GROK_BIN_DIR`、PATH、workspace 或 UI 指定路径。使用 npm trampoline 的用户必须先在 Terminal/PowerShell 从官方安装入口完成 bootstrap 并执行一次 `grok --version`;插件不下载、安装或更新 CLI。
|
|
75
|
+
|
|
76
|
+
macOS 官方默认路径可能是 symlink;验证时允许 symlink,但 `realpath` 必须仍位于规范化的 `GROK_HOME` 根内,并指向当前用户拥有的普通可执行文件。Windows 候选必须是普通文件并拒绝 reparse point。路径、owner 和版本检查只能约束候选形状,不能证明 publisher;“用户从 xAI 官方渠道安装”是信任假设,除非 spike 证明可稳定验证签名/notarization/官方哈希。
|
|
77
|
+
|
|
78
|
+
### 固定命令表
|
|
79
|
+
|
|
80
|
+
| 用户动作 | 可执行参数 | 说明 |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| 检查版本 | `--version` | 10 秒、16 KiB 输出上限 |
|
|
83
|
+
| 浏览器登录 | `login --oauth` | 标准配置下官方 CLI 打开浏览器并处理 callback |
|
|
84
|
+
| 退出 | `logout` | 官方 CLI 删除自己的凭据 |
|
|
85
|
+
|
|
86
|
+
`cancel` 不是一个 CLI 参数,只会中止当前 Host 拥有的登录子进程。
|
|
87
|
+
|
|
88
|
+
禁止:
|
|
89
|
+
|
|
90
|
+
- 插件通过 argv 显式启动任何 shell。
|
|
91
|
+
- 固定 `["login", "--oauth"]` 之外的 flags、prompt、cwd 或环境变量。
|
|
92
|
+
- 把授权 URL 从 stdout/stderr 提取后交给通用 opener。
|
|
93
|
+
- 自动下载或更新 Grok CLI。
|
|
94
|
+
- 在登录流程中启动 Grok agent、工具或 workspace 会话。
|
|
95
|
+
|
|
96
|
+
`--oauth` 只固定 loopback transport,不绕过 external auth、devbox、企业 OIDC、system managed config 或 macOS MDM。插件对已知环境覆盖设置 tombstone,但不能靠环境清理覆盖磁盘/MDM 配置。
|
|
97
|
+
|
|
98
|
+
子进程环境必须从闭合 allowlist 构造,而不是保留任意用户 PATH:
|
|
99
|
+
|
|
100
|
+
- macOS PATH 固定为系统目录 `/usr/bin:/bin:/usr/sbin:/sbin`;Windows PATH/PATHEXT/COMSPEC 固定为从已验证 `SystemRoot` 派生的系统目录与扩展集合。
|
|
101
|
+
- 只保留必要 OS home、`SystemRoot`/`WINDIR`、临时目录、locale,以及明确披露的 proxy/CA 变量;proxy/CA 属于用户管理的网络信任边界。
|
|
102
|
+
- 明确移除 `XAI_API_KEY`、`GROK_AUTH_PROVIDER_*`、`GROK_OIDC_*`、`GROK_OAUTH2_*`、`GROK_LOCAL_AUTH`、endpoint/model override、`BROWSER`、`RUST_LOG`、`GROK_LOG_FILE`、`NODE_OPTIONS`、`SSLKEYLOGFILE`、`DYLD_*`、`LD_*`、npm/DSH/API secrets和其他进程行为变量。
|
|
103
|
+
- 精确 allowlist/tombstone 列表在协议 spike 按固定 CLI 版本与两平台冻结;workspace/PATH canary 不得被登录链执行。
|
|
104
|
+
|
|
105
|
+
官方 loopback 登录可能先清除旧 credential,取消/失败也可能使共享会话失效;成功后 CLI 还可能同步 managed config 或发送其自身已启用的遥测。插件无法撤销这些官方副作用。登录前 UI 必须提示,`logout` 必须经前台用户确认,且会影响所有共享同一 `GROK_HOME` 的应用。
|
|
106
|
+
|
|
107
|
+
“插件不自动更新”只约束本插件;官方 CLI 自身的更新检查/替换仍属于 vendor boundary。登录前后必须重新解析 executable identity 与版本,若发生变化或落出已测集合,凭据状态失败关闭并要求重新验证兼容性。
|
|
108
|
+
|
|
109
|
+
### 状态机
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
idle ──login──> starting ──spawned──> running
|
|
113
|
+
▲ │ │
|
|
114
|
+
│ ├─spawn error────────>failed
|
|
115
|
+
│ └─cancel─────────────>cancelled
|
|
116
|
+
│ │
|
|
117
|
+
└──────── success + valid auth <──────┤
|
|
118
|
+
├─timeout──>failed
|
|
119
|
+
└─exit/auth invalid──>failed
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
登录尝试与当前凭据是两个正交状态。CLI 以成功、非零、取消或超时结算后,都必须先 `waitForExit()`、失效旧 cache 并重新检查 auth 文件;例如“尝试 cancelled,但凭据已在取消前写入并有效”必须如实表示,不能静默启用,也不能谎报未登录。
|
|
123
|
+
|
|
124
|
+
最小公开状态形状:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
type PublicAuthStatus = {
|
|
128
|
+
credential: "missing" | "valid" | "expiring" | "expired" | "unsupported"
|
|
129
|
+
login:
|
|
130
|
+
| { state: "idle" }
|
|
131
|
+
| { state: "starting" }
|
|
132
|
+
| { state: "running"; sessionId: string }
|
|
133
|
+
| { state: "settled"; outcome: "succeeded" | "failed" | "cancelled" | "timed-out" }
|
|
134
|
+
expiryBucket?: "under-5m" | "under-1h" | "later"
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
认证 DTO 不声称能可靠知道浏览器是否打开,也不包含 process ID、binary path、OAuth URL、stdout/stderr、token、auth 文件内容、email、user ID、team/org、subscription 或 fingerprint。stdin 为 ignored,因此插件不支持手工粘贴 code fallback。
|
|
139
|
+
|
|
140
|
+
## 5. 凭据文件
|
|
141
|
+
|
|
142
|
+
- 插件只读 Grok CLI 凭据,不创建第二份落盘副本。
|
|
143
|
+
- 官方 CLI 令牌不写入 settings、DSH credentials、workspace、临时文件或诊断包;插件不创建第二份 token grant。
|
|
144
|
+
- `1.0.5` 的真实 `auth.json` 同时包含 refresh token。Host 对文件的有界读取会短暂接触包含它的原始字节;实现不得缓存、使用、记录或写回 refresh token,解析后只保留闭合校验元数据与短期 access-token lease。该约束缩短暴露窗口但不构成进程级隔离。
|
|
145
|
+
- 文件路径只由 OS home、官方 `GROK_HOME` 约定和 Host 环境解析;UI 不可选择路径。
|
|
146
|
+
- 读取前后检查文件元数据,降低替换竞态;解析失败时不保留部分值。
|
|
147
|
+
- 生产 OIDC schema 候选筛选必须版本化:顶层对象有界且只有一个候选;map key 等于规范化 issuer 与 client ID 组合;`auth_mode === "oidc"`;issuer 精确等于该固定 CLI 版本的 xAI 生产 issuer;scope、client ID、access token 和 `expires_at` 关系闭合。拒绝 external、api_key、web_login、legacy scope、企业 issuer、多候选和未知关键模式。最终字段和值必须在协议 spike 绑定到精确 CLI tag/commit,不能长期依赖 mutable `main`。
|
|
148
|
+
- 上述 metadata 未签名,官方源码也只把 auth mode/issuer 当 provenance/debug hint;它不是密码学 trust assertion。插件在信任官方 CLI 与当前用户凭据目录的前提下做本地失败关闭,真实 bearer 最终由固定 xAI Proxy 服务端验证。
|
|
149
|
+
- access token 只在 Host 内存使用;缓存采用短生命周期并可显式清空。Host 有界读取完整 JSON 时原始 buffer/string 可能瞬时包含 refresh token;解析器不提取、不缓存、不使用、不返回、不记录、不写回该字段,且 JavaScript 内存不承诺可靠清零。
|
|
150
|
+
- 解析 `expires_at` 并使用固定 skew 在发送前拒绝过期/将过期凭据。只有 issuer、client ID、scope 与 schema 全部匹配而时间失效时,才 single-flight 启动固定 `grok models`;OAuth refresh 状态机和文件写回仍由官方 CLI 完成。刷新后必须重新读取并完整校验,只重试一次。
|
|
151
|
+
- 401 立即使 lease 失效并返回 LLM `AUTH`,不自动重放已发出的 POST;下一次明确用户动作才重新读取文件或发起登录。
|
|
152
|
+
- `/grok logout` 或 Web 退出通过官方 `grok logout` 完成,插件不直接 unlink 文件。
|
|
153
|
+
- logout 先推进 auth generation 并中止所有插件推理;完成后清缓存。旧 generation 的完成回调不得重新填入凭据。
|
|
154
|
+
|
|
155
|
+
## 6. 固定网络策略
|
|
156
|
+
|
|
157
|
+
本插件拥有并注入凭据的生产 endpoint 闭合集合:
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
GET https://cli-chat-proxy.grok.com/v1/models
|
|
161
|
+
POST https://cli-chat-proxy.grok.com/v1/responses
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`chat-completions` 与 `messages` 只有在未来真实目录出现相应 backend、对应 codec 和真机门禁通过后才能进入发布集合;当前不得为“看起来兼容”而启用。
|
|
165
|
+
|
|
166
|
+
要求:
|
|
167
|
+
|
|
168
|
+
- `redirect: "error"`,301/302/303/307/308 全部失败。
|
|
169
|
+
- Authorization 只能由 `PinnedGrokTransport` 注入。
|
|
170
|
+
- 不接受 base URL、proxy URL、模型返回 URL或 discovery URL。
|
|
171
|
+
- 不把 token 放进 query string。
|
|
172
|
+
- 每个请求携带 Harness `attributionHeaders()`、`X-XAI-Token-Auth: xai-grok-cli`、发布绑定的 `x-grok-client-version` 与本项目真实 `x-grok-client-identifier`/package identity;不得伪造 `grok-shell`。缺失版本会触发 426,版本变更必须重跑协议门禁。
|
|
173
|
+
- token、headers、完整 URL query、prompt 和原始远端 body 都不进日志。
|
|
174
|
+
- 本限制不覆盖官方 CLI 子进程的 OAuth、企业 IdP、managed-config、代理或遥测网络;这些由官方 CLI 及其有效配置负责并在隐私说明中单独披露。
|
|
175
|
+
|
|
176
|
+
请求在 `JSON.stringify` 前也必须受限:消息数、单条/总 UTF-8 字节、工具数量、单个/总 schema 字节、schema 深度与 tool-result 大小均有闭合上限。响应必须验证允许的 `Content-Type`。Provider 只产生 Harness tool-call chunks,不自行执行工具或写文件;未在本次请求声明的工具名、厂商 server-tool/search/image 事件全部拒绝。
|
|
177
|
+
|
|
178
|
+
初始上限:
|
|
179
|
+
|
|
180
|
+
- auth.json:64 KiB。
|
|
181
|
+
- 消息:最多 512 条;单条 2 MiB;序列化前累计 UTF-8 8 MiB。
|
|
182
|
+
- 工具:最多 128 个;单个 schema 256 KiB;全部 schema 2 MiB;结构深度 64。
|
|
183
|
+
- 单个 tool result:2 MiB。
|
|
184
|
+
- 普通 JSON/错误 body:64 KiB。
|
|
185
|
+
- SSE 单行:256 KiB。
|
|
186
|
+
- SSE 单事件:1 MiB。
|
|
187
|
+
- SSE event:最多 100,000;comment/heartbeat 合计最多 100,000。
|
|
188
|
+
- 单次流累计协议字节:32 MiB。
|
|
189
|
+
- block 与 tool call:分别最多 4,096;单个 tool arguments 1 MiB、累计 8 MiB。
|
|
190
|
+
- 响应 JSON/schema 结构深度:64。
|
|
191
|
+
- 首字节超时:30 秒。
|
|
192
|
+
- idle timeout:120 秒。
|
|
193
|
+
- 绝对时长:30 分钟。
|
|
194
|
+
|
|
195
|
+
同时检查声明长度和实际解压后流字节,覆盖 chunked、伪造 Content-Length、gzip/brotli 炸弹与无限事件。
|
|
196
|
+
|
|
197
|
+
## 7. RPC 与命令
|
|
198
|
+
|
|
199
|
+
Web RPC 只允许:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
type AuthRpc = {
|
|
203
|
+
status(input: {}): Promise<AuthOutcome>
|
|
204
|
+
login(input: {}): Promise<AuthOutcome>
|
|
205
|
+
cancel(input: { sessionId: string }): Promise<AuthOutcome>
|
|
206
|
+
logout(input: { phase: "begin" } | { phase: "confirm"; confirmationId: string }): Promise<AuthOutcome>
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
每个 unary handler 实际再包成 Harness `RpcResult<AuthOutcome>`;业务失败留在成功 value union,只有 rc.2 允许的框架错误进入 error 分支。
|
|
211
|
+
|
|
212
|
+
- authority 使用 `loopback`,依赖 rc.2 Connection 对 Host/Origin/Sec-Fetch 混淆的 trust fence;它不提供网络层认证,handler 也拿不到 sender、frame 或浏览器 user-gesture 证明。
|
|
213
|
+
- Web 的前台按钮与 logout 二次确认属于防误操作 UX,不是 Host 授权边界。能以当前用户身份直接访问本机 Harness RPC 的恶意进程已能直接运行 `grok logout`/读取用户凭据,属于同一用户权限残余风险。
|
|
214
|
+
- handler 对 endpoint/payload 使用严格 schema;拒绝未知字段。`sessionId`/短期 confirmation ID 只降低误操作与陈旧 UI 重放,不能抵御同用户本地进程。
|
|
215
|
+
- `sessionId` 是随机的不透明 ID,不是 OAuth state 或 process ID。
|
|
216
|
+
- renderer 不能获得 token、URL、文件路径或任意命令能力。
|
|
217
|
+
|
|
218
|
+
TUI 通过 Harness 的 human-command registry 注册一个全局 `/grok`,只接受 `status|login|cancel|logout` 的闭合语法。命令直接在 Host 执行,不发送给模型;取消使用 invocation 自带的 `AbortSignal`。
|
|
219
|
+
|
|
220
|
+
## 8. 搜索与图片
|
|
221
|
+
|
|
222
|
+
`0.1.0` 不注册 Web/X Search、图片生成、图片输入或下载工具。因此:
|
|
223
|
+
|
|
224
|
+
- 请求体中不存在厂商侧搜索工具。
|
|
225
|
+
- 不存在远端图片 URL 下载逻辑。
|
|
226
|
+
- 不存在模型可控文件路径写入。
|
|
227
|
+
- 不存在把 Bearer token 带到第二个 origin 的功能路径。
|
|
228
|
+
|
|
229
|
+
未来新增时必须另写 ADR 和威胁模型。
|
|
230
|
+
|
|
231
|
+
## 9. 残余风险
|
|
232
|
+
|
|
233
|
+
本项目不承诺防御:
|
|
234
|
+
|
|
235
|
+
- 已取得当前 OS 用户权限、能替换 `~/.grok` 内容的恶意软件。
|
|
236
|
+
- 被攻陷的官方 Grok CLI、系统浏览器或 xAI 服务。
|
|
237
|
+
- 官方 CLI 或其有效 user/system/MDM 配置运行 external helper、企业 OIDC、managed-config sync、代理或遥测的行为;本插件不能给出端到端无 shell或单一网络 origin 保证。
|
|
238
|
+
- xAI 官方凭据文件本身的 0600/Windows ACL 模型;官方文档建议配合 FileVault 或 BitLocker。
|
|
239
|
+
- 同一用户身份下恶意进程读取官方 token。
|
|
240
|
+
- xAI 服务契约、模型行为、订阅配额或服务条款变化。
|
|
241
|
+
- 当前第一方 token 可能包含 conversation/workspace read-write 等较宽 scope;泄漏影响不只一次聊天。精确 scopes 按发布绑定的 CLI 版本披露。
|
|
242
|
+
|
|
243
|
+
这些风险必须在 README 和发布说明中披露。
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# DeepSeek Harness `0.1.1-rc.2` 接口契约
|
|
2
|
+
|
|
3
|
+
## 1. 目标
|
|
4
|
+
|
|
5
|
+
插件只使用 Harness 的公开 npm 接口,不读取私有对象、不修改 Harness 源码,也不依赖当前 YukiRyou DeepSeek 工作区的内部实现。
|
|
6
|
+
|
|
7
|
+
兼容基线:
|
|
8
|
+
|
|
9
|
+
- DeepSeek Harness:`0.1.1-rc.2`
|
|
10
|
+
- Harness 内置 Node:`24.19.0`
|
|
11
|
+
- 首版发布平台:`darwin-arm64`、`win32-x64`
|
|
12
|
+
|
|
13
|
+
## 2. Host peer packages
|
|
14
|
+
|
|
15
|
+
首版预计需要以下版本。脚手架前必须按真实静态 import 图分为 required 与 optional peer:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"@deepseek-ai/cordis": "4.0.1",
|
|
20
|
+
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
21
|
+
"@deepseek-ai/dsh-subprocess": "0.1.1-rc.2",
|
|
22
|
+
"@deepseek-ai/dsh-settings": "0.1.1-rc.2",
|
|
23
|
+
"@deepseek-ai/dsh-commands": "0.1.1-rc.2",
|
|
24
|
+
"@deepseek-ai/dsh-client-connection": "0.1.1-rc.2",
|
|
25
|
+
"@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
|
|
26
|
+
"@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
|
|
28
|
+
"@deepseek-ai/schemastery": "3.18.1"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
初步分组:
|
|
33
|
+
|
|
34
|
+
- required:`@deepseek-ai/cordis`、`@deepseek-ai/dsh-llm`、`@deepseek-ai/schemastery`。
|
|
35
|
+
- profile-specific optional:subprocess、commands、connection 和 client UI packages。目标桌面 Runtime 必须实际挂载 subprocess service 才能提供官方 CLI 登录;缺失时模型 Provider 仍可读取已有有效官方凭据,但登录/注销动作不可用。
|
|
36
|
+
|
|
37
|
+
全部放入 `peerDependencies`,optional 项同时声明 `peerDependenciesMeta.<name>.optional: true`。可选 peer 不得被 Host 入口无条件静态 import;需要通过独立 export、条件加载或已证明的 Runtime external 方式隔离。Web、TUI、headless 三种缺失可选 service/peer 的测试必须通过。
|
|
38
|
+
|
|
39
|
+
这些包由 Harness Runtime 满足,不进入插件普通 dependency 图。版本在脚手架阶段以实际 rc.2 manifest 再核对;不自动放宽到未经测试的 Harness 版本。
|
|
40
|
+
|
|
41
|
+
目标是零普通 runtime dependencies。登录进程只经过 Runtime 提供的 `ctx.subprocess`;HTTP、流解析、crypto、path 和用户 home 凭据读取使用 Node 24 内建能力。插件不依赖或打包 `@deepseek-ai/dsh-subprocess-local`。
|
|
42
|
+
|
|
43
|
+
## 3. Cordis 入口
|
|
44
|
+
|
|
45
|
+
宿主入口形状:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
export const name = "llm-grok"
|
|
49
|
+
export const inject = ["llm"]
|
|
50
|
+
export const Config = /* Schemastery */
|
|
51
|
+
|
|
52
|
+
export function apply(ctx, config) {
|
|
53
|
+
// registration only after services validate
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
可选服务不能加入强制 `inject`:
|
|
58
|
+
|
|
59
|
+
- settings 使用可选安装 helper。
|
|
60
|
+
- commands 使用 `ctx.inject(["commands"], ...)`。
|
|
61
|
+
- Web RPC 使用 `ctx.inject(["connection"], ...)`。
|
|
62
|
+
- CLI login/logout 使用 `ctx.inject(["subprocess"], (subprocessCtx) => ...)`;内部只能调用该 child context 的 `subprocessCtx.subprocess`。
|
|
63
|
+
- 无 Web、无 TUI、无 settings 的 headless profile 仍应安全启动 LLM Provider。
|
|
64
|
+
|
|
65
|
+
所有 registration、listener、timer和 inflight promise 都必须由 Cordis effect disposer 管理。登录 capability 安装在 `ctx.inject(["subprocess"], ...)` 的 child fiber;service 被替换或卸载时,先中止登录,再 `waitForExit()`,随后等待 in-flight settle。
|
|
66
|
+
|
|
67
|
+
## 4. LLM adapter
|
|
68
|
+
|
|
69
|
+
实现 `LlmAdapter` 的公开成员:
|
|
70
|
+
|
|
71
|
+
- `providerInfo`
|
|
72
|
+
- `providerRetryPolicy`
|
|
73
|
+
- `listModels`
|
|
74
|
+
- `resolveModel`
|
|
75
|
+
- `prepareCall`
|
|
76
|
+
- `stream`
|
|
77
|
+
|
|
78
|
+
注册:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
ctx.llm.registerAdapter(["grok"], adapter)
|
|
82
|
+
|
|
83
|
+
ctx.llm.registerConfigurableProviders([{
|
|
84
|
+
provider: "grok",
|
|
85
|
+
displayName: "Grok Build",
|
|
86
|
+
settingsNs: "llm-grok",
|
|
87
|
+
settingsPath: [],
|
|
88
|
+
}])
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`listModels()` 是异步动态目录:使用所选 auth generation 请求固定 `/v1/models`,返回该账号当前全部可见模型。目录是 discovery surface,不是路由白名单;`resolveModel()` 对未缓存的显式模型 ID 可做一次有界刷新,但不得根据 ID 名称猜 capability。
|
|
92
|
+
|
|
93
|
+
每个 provider HTTP 请求必须包含 `attributionHeaders()`。
|
|
94
|
+
|
|
95
|
+
### `prepareCall` generation
|
|
96
|
+
|
|
97
|
+
`prepareCall()` 返回公开 `PreparedAdapterCall` 的精确形状只有 `{ model: LlmResolvedModelInfo, stream }`。adapter 在其 `stream` 闭包内冻结私有 generation,例如:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
type AdapterGeneration = {
|
|
101
|
+
resolvedModel: LlmResolvedModelInfo
|
|
102
|
+
transportContractVersion: 1
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
retry policy 由 LLM Runtime 与 adapter registration generation 一起绑定,不伪装成 `PreparedAdapterCall` 字段。access token 不写入可序列化 generation;stream 真正发请求前从私有 `HostCredentialSource` 取得一次 lease,并绑定到该次尝试。设置热更新不能改变已经 prepare 的模型和协议;logout/auth generation 可以使尚未发送或在途 lease 失效。直接调用 `stream()` 时,也必须在返回惰性 iterable 前同步捕获 adapter generation。
|
|
107
|
+
|
|
108
|
+
### 流顺序
|
|
109
|
+
|
|
110
|
+
本插件对成功响应采用比 rc.2 最低要求更严格的序列:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
block-start
|
|
114
|
+
text-delta | reasoning-delta | tool-call-delta ...
|
|
115
|
+
block-end
|
|
116
|
+
usage
|
|
117
|
+
finish
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- 每个 block chunk 的 `index` 都是非负 safe integer;多个 index 可以交错。同一 index 不可重复 start;delta 只能指向相同 index、相同类型的 open block。
|
|
121
|
+
- `block-start` 携带 `blockType`;`tool-call-delta` 携带稳定 `id`、可选 `name` 与原始 JSON `argumentsDelta`;`block-end.block` 是该 index 的完整组装 block。
|
|
122
|
+
- 成功 finish(`stop|tool-calls|max-tokens`)前所有 block 必须闭合,`usage` 必须且至多出现一次并紧邻 terminal finish。rc.2 本身允许 usage 缺失,但本插件把缺失视为协议失败。
|
|
123
|
+
- `TokenUsage.inputTokens`、`cacheReadTokens`、`cacheWriteTokens` 可同时存在,但各自计数集合不重叠;billed input 是三者之和。
|
|
124
|
+
- 必须有且只有一个 terminal `finish`,之后不得再有 chunk。只有 `error`/`aborted` finish 可按 rc.2 容许未闭合 block,并必须携带 `failure`。
|
|
125
|
+
- provider stop reason 映射为对象 `{kind:"stop"}`、`{kind:"tool-calls"}`、`{kind:"max-tokens"}`,异常/取消映射 `{kind:"error"|"aborted", failure}`。
|
|
126
|
+
- 空成功响应映射为 `EMPTY_RESPONSE`。
|
|
127
|
+
- 截断 SSE、未知事件、重复 finish 和无闭合 tool call 必须失败。
|
|
128
|
+
- AbortSignal 贯穿 fetch、reader、codec 和 adapter iterator。
|
|
129
|
+
- 首版遇到任何 image content block,都必须在发出 HTTP 前以 `UNSUPPORTED_CONTENT` 失败;Provider 不因此依赖 attachment service。
|
|
130
|
+
- 认证缺失或被拒使用 Harness 已识别的 LLM code `AUTH`,不用自造 `AUTH_REQUIRED`。
|
|
131
|
+
|
|
132
|
+
## 5. TUI `/grok` 命令
|
|
133
|
+
|
|
134
|
+
使用 `@deepseek-ai/dsh-commands` 的公开 human-command registry:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
ctx.commands.register({
|
|
138
|
+
name: "grok",
|
|
139
|
+
description: "Manage Grok Build authentication",
|
|
140
|
+
input: { hint: "status|login|cancel|logout" },
|
|
141
|
+
recordInput: false,
|
|
142
|
+
handler: async ({ rawInput, signal }) => {
|
|
143
|
+
// parse closed grammar; call shared Host AuthCoordinator
|
|
144
|
+
return { kind: "success", text: "<redacted-safe-status>" }
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
约束:
|
|
150
|
+
|
|
151
|
+
- 命令由交互 UI 直接执行,绝不发送给模型。
|
|
152
|
+
- 只接受闭合语法:`status`、`login`、`cancel`、`logout`;额外参数返回 usage。
|
|
153
|
+
- `recordInput: false` 只是不把 `rawInput` 复制进 `command/run.args`;命令名、run/done 生命周期与返回 text 仍会持久化,因此返回文本必须脱敏。
|
|
154
|
+
- parser 必须保留并测试命令名后的原始分隔空白,再按闭合 grammar 决定成功或返回 `{ kind: "error", text: usage }`。
|
|
155
|
+
- `signal` 取消 `login` 等待;不会把授权 URL、token 或原始 CLI 输出写入命令结果。
|
|
156
|
+
- TUI 先调用共享 controller 的 `beginLogin(mode)`,再用 invocation signal 等待该 session;等待被取消时,由本次命令拥有的 session 执行 `cancel()`。Web 只启动并轮询 `status`。每个 auth mode 同时最多一个事务,selection generation 防止两种 token 混用。
|
|
157
|
+
- `/grok logout` 第一次只建立短期 confirmation;在 TTL 内第二次输入同一闭合命令才执行,不新增可携带任意参数的 grammar。
|
|
158
|
+
|
|
159
|
+
## 6. Web 设置与 RPC
|
|
160
|
+
|
|
161
|
+
设置 namespace:`llm-grok`。
|
|
162
|
+
|
|
163
|
+
设置页只存非敏感 UI/功能配置,不存 token、auth 文件路径、binary path 或 base URL。
|
|
164
|
+
|
|
165
|
+
Host channel 示例:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
ctx.connection.rpc.handle(
|
|
169
|
+
"/grok-auth",
|
|
170
|
+
handler,
|
|
171
|
+
{ authority: "loopback" },
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
闭合方法:
|
|
176
|
+
|
|
177
|
+
- `status`
|
|
178
|
+
- `dashboard`
|
|
179
|
+
- `login`
|
|
180
|
+
- `cancel`
|
|
181
|
+
- `logout`
|
|
182
|
+
|
|
183
|
+
`status`、`login`、`logout` 只接受空对象;`cancel` 只接受当前公开状态中的 `sessionId`。不存在 `authMode`、模式选择、OAuth URL、device code、access/refresh token、identity 或 token endpoint 字段。
|
|
184
|
+
|
|
185
|
+
`dashboard` 也只接受空对象,并且只在 `status.available === true` 时由页面调用。它返回脱敏模型 capability 和额度摘要;不得返回 credential metadata、用户身份、上游原文、headers 或 endpoint。额度重置时间只能来自 billing period end,不得使用 OAuth credential expiry。
|
|
186
|
+
|
|
187
|
+
业务状态不能冒充 rc.2 的 `RpcErrorCode`。成功分支承载闭合 outcome:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
type AuthOutcome =
|
|
191
|
+
| { kind: "status"; status: PublicAuthStatus }
|
|
192
|
+
| { kind: "login-started"; status: PublicAuthStatus; sessionId: string }
|
|
193
|
+
| { kind: "logout-confirmation-required"; confirmationId: string; expiresAt: string }
|
|
194
|
+
| { kind: "busy"; status: PublicAuthStatus; diagnosticId: string }
|
|
195
|
+
| { kind: "cli-not-found"; diagnosticId: string }
|
|
196
|
+
| { kind: "cli-unsupported"; diagnosticId: string }
|
|
197
|
+
| { kind: "unsupported-auth-config"; diagnosticId: string }
|
|
198
|
+
| { kind: "auth-required"; diagnosticId: string }
|
|
199
|
+
|
|
200
|
+
return { ok: true, value: outcome }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
RPC error 分支只使用 rc.2 已有且语义匹配的码:schema 失败为 `bad-request` 并填 `details.issues`,调用 signal 中止为 `cancelled`,未分类异常为 `internal`。不得借用 `agent-busy`。Connection 的 correlation ID 留在 carrier 内部,handler/业务 DTO 不把它当可访问字段;`diagnosticId` 是本插件另行生成的随机安全标识。
|
|
204
|
+
|
|
205
|
+
handler 必须全程 non-throwing:依赖调用、业务逻辑、DTO 校验与 JSON 可序列化检查全部包在边界内;任何未知 throw 都折叠为固定脱敏的 `internal` `RpcResult`。若 throw 逃到 rc.2 carrier,它会变成可能含 `String(error)` 的 HTTP 500,因此属于 P0 实现错误。
|
|
206
|
+
|
|
207
|
+
## 7. 客户端 bundle
|
|
208
|
+
|
|
209
|
+
导出 `./client`,构建为 Harness lazy-CJS wrapper:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
window.__ModuleLoader__.load({
|
|
213
|
+
id: "<package-json-name>",
|
|
214
|
+
factory: (require) => {
|
|
215
|
+
var module = { exports: {} }
|
|
216
|
+
var exports = module.exports
|
|
217
|
+
// compiled client
|
|
218
|
+
return module.exports
|
|
219
|
+
},
|
|
220
|
+
})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
bundle ID 必须由最终 `package.json.name` 在构建时注入,不能在包名决策前硬编码;Host/client/patch 三处必须使用同一个精确 ID。
|
|
224
|
+
|
|
225
|
+
客户端依赖图至少包含:
|
|
226
|
+
|
|
227
|
+
- `@deepseek-ai/dsh-client-runtime`
|
|
228
|
+
- `@deepseek-ai/dsh-client-ui-settings`
|
|
229
|
+
- `@deepseek-ai/dsh-client-locale`
|
|
230
|
+
- `@deepseek-ai/dsh-client-connection`
|
|
231
|
+
|
|
232
|
+
最终 `package.json` 至少声明:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"exports": {
|
|
237
|
+
".": {
|
|
238
|
+
"types": "./types/index.d.ts",
|
|
239
|
+
"default": "./dist/host/index.js"
|
|
240
|
+
},
|
|
241
|
+
"./client": {
|
|
242
|
+
"types": "./dist/client/index.d.ts",
|
|
243
|
+
"default": "./dist/client/client.js"
|
|
244
|
+
},
|
|
245
|
+
"./package.json": "./package.json"
|
|
246
|
+
},
|
|
247
|
+
"dsh": {
|
|
248
|
+
"bundle": {
|
|
249
|
+
"patch": "grok-provider.patch.yml"
|
|
250
|
+
},
|
|
251
|
+
"client": {
|
|
252
|
+
"platform": "web",
|
|
253
|
+
"inject": [
|
|
254
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
255
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
256
|
+
"@deepseek-ai/dsh-client-locale",
|
|
257
|
+
"@deepseek-ai/dsh-client-connection"
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
rc.2 的 Host 端 client-module scanner 会执行 `require.resolve("<package>/package.json")` 读取 `dsh.client` 声明,因此 `./package.json` 是 Web bundle 可发现性的必要公开元数据入口;缺失时 Host 插件仍可挂载,但浏览器启动图不会包含该包。
|
|
265
|
+
|
|
266
|
+
client bundle 导出 Cordis `inject` 与 `apply`,所需 services 至少为 `slots`、`locale`、`connection`。设置页通过 `ctx.slots.inject("settings.section", () => ctx.slots.register(...))` 注册。
|
|
267
|
+
|
|
268
|
+
三类依赖必须分开:`dsh.client.inject` 只列提供所需 Cordis service 的 client plugin;bundle 动态 `require()` 且不是平台 seed 的 package 列入 `dsh.client.external`;React、UI primitives 等 Runtime 平台 seed 不进入 inject。Host/client 的真实静态和动态 import 图决定 peer 与 external,不能把所有 bundle require 一律塞进 inject。
|
|
269
|
+
|
|
270
|
+
客户端编译必须证明不能 import Host credential、transport、fs、path、child_process 或 auth JSON parser 模块。
|
|
271
|
+
|
|
272
|
+
## 8. 认证协调器
|
|
273
|
+
|
|
274
|
+
公共认证控制与私有凭据源必须在类型边界上拆开,避免 RPC/TUI 即使误用也拿到 token 方法:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
interface PublicAuthController {
|
|
278
|
+
status(): Promise<PublicAuthStatus>
|
|
279
|
+
beginLogin(signal: AbortSignal): Promise<{ sessionId: string; status: PublicAuthStatus }>
|
|
280
|
+
waitForLogin(sessionId: string, signal: AbortSignal): Promise<PublicAuthStatus>
|
|
281
|
+
cancel(sessionId: string, signal: AbortSignal): Promise<PublicAuthStatus>
|
|
282
|
+
beginLogoutConfirmation(): Promise<{ confirmationId: string; expiresAt: string }>
|
|
283
|
+
confirmLogout(confirmationId: string, signal: AbortSignal): Promise<PublicAuthStatus>
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
interface HostCredentialSource {
|
|
287
|
+
acquire(signal: AbortSignal): Promise<HostTokenLease>
|
|
288
|
+
invalidateRejected(fingerprint: string): void
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Web RPC 和 TUI 只获得同一个 `PublicAuthController`;`PinnedGrokTransport` 私有持有 `HostCredentialSource`。二者内部共享一个认证状态核,但没有可互相转换的公开对象。`HostTokenLease` 只能在 Host transport 消费,不可序列化,也不能被 RPC handler 返回。token fingerprint 只使用不可逆、进程内加盐的短摘要,用于缓存失效和测试,不写日志。
|
|
293
|
+
|
|
294
|
+
`beginLogin()` 只有在受管 CLI 已成功 spawn 后才返回 session;进程随后独立结算并更新共享状态。Web 用 `status` 观察,TUI 用 `waitForLogin` 等待。`cancel` 必须匹配当前 sessionId。logout confirmation 单次使用、短 TTL、绑定当前 auth generation;它是防误操作/陈旧 UI 机制,不是网络认证。
|
|
295
|
+
|
|
296
|
+
登录事务由 controller 自己的 `AbortController` 所有。RPC request signal 只控制“spawn 并发布 session 之前”的启动阶段;`beginLogin()` 返回后不能再把已结算的 unary request signal 当事务 owner。后台事务只能由匹配的 `cancel(sessionId)`、5 分钟 deadline、subprocess service teardown 或插件 dispose 中止。`PublicAuthStatus` 的 running 分支公开当前不透明 sessionId,因此 `/grok cancel` 可先读 status 后取消,无需用户输入 ID。
|
|
297
|
+
|
|
298
|
+
## 9. 官方 CLI 边界
|
|
299
|
+
|
|
300
|
+
Harness rc.2 不提供 HTTPS URL opener,也不需要插件自建 opener:插件通过受管进程 seam 启动官方 `grok login --oauth`,标准配置下由 CLI 自己跨平台打开浏览器。
|
|
301
|
+
|
|
302
|
+
只使用 `ctx.subprocess.resolveExecutable()` / `ctx.subprocess.spawn()`:
|
|
303
|
+
|
|
304
|
+
- 经过本插件路径/owner/realpath 检查的绝对 executable path,再交给 seam 验证。
|
|
305
|
+
- 固定完整 argv;seam 不做 shell 解释,插件自身不启动 shell。
|
|
306
|
+
- stdin ignored;stdout/stderr 使用 raw pipe,自行按 UTF-8 原始字节计数,任一超过 64 KiB 就 abort;始终 drain 且不透传原文。collect 模式的截断本身不会终止进程,因此不用于该门禁。
|
|
307
|
+
- 受控 cwd 与过滤环境。
|
|
308
|
+
- single-flight、5 分钟 deadline、AbortSignal、`terminate()`、有界 `waitForExit(teardownSignal)` 与 async dispose cleanup。`waitForExit()` 返回 `false` 时记录脱敏 cleanup failure、让 disposer 有界结算,并把登录 capability 隔离为“需 Host 重启或 subprocess service replacement”;不得自动重新启用或永久卡住卸载。
|
|
309
|
+
- 只承诺 seam 可观察到的受管进程树;官方 CLI 根据可信配置启动并主动脱离该树的后代属于 vendor trust boundary。
|
|
310
|
+
|
|
311
|
+
不引入 npm `open`,不调用 `host.openPath`,也不把 HTTPS URL 当文件路径处理。
|
|
312
|
+
|
|
313
|
+
`--oauth` 只固定 loopback transport,不保证绕过 external provider/企业 OIDC,也不保证官方 CLI 内部不用 shell。公开 `SubprocessSpawnSpec` 没有 `windowsHide`;“Windows 不出现额外 console 闪窗”必须在 Gate 1 真实设备验证,失败则阻断发布并向 Harness seam 请求能力,不能绕回 `node:child_process`。
|
|
314
|
+
|
|
315
|
+
## 10. HMR 与卸载
|
|
316
|
+
|
|
317
|
+
卸载或热替换后必须:
|
|
318
|
+
|
|
319
|
+
- 注销 adapter 和 configurable provider。
|
|
320
|
+
- 注销 settings section、RPC handler 和 `/grok` command。
|
|
321
|
+
- 中止所有 fetch 和登录进程。
|
|
322
|
+
- 清空 token 内存缓存。
|
|
323
|
+
- 清理 timer、reader 与 event listener。
|
|
324
|
+
- 等待受控 inflight promise settle,不留下未处理 rejection。
|
|
325
|
+
- 覆盖 Host activate → unload → activate 后只有一个 adapter/RPC/command;client bundle revision 更新后旧 slot、listener、store/controller 全部释放;subprocess service 替换时 child fiber 执行进程终止与有界等待后才卸载,并覆盖 `waitForExit()` 返回 `false` 的路径。
|