wake-bridge 0.9.0-preview.8
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 +201 -0
- package/README.md +222 -0
- package/dist/src/adapters/botlingknows.d.ts +42 -0
- package/dist/src/adapters/botlingknows.js +141 -0
- package/dist/src/adapters/botlingknows.js.map +1 -0
- package/dist/src/adapters/group-chat.d.ts +43 -0
- package/dist/src/adapters/group-chat.js +120 -0
- package/dist/src/adapters/group-chat.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +637 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/connector-catalog.d.ts +17 -0
- package/dist/src/connector-catalog.js +43 -0
- package/dist/src/connector-catalog.js.map +1 -0
- package/dist/src/connectors/botlingknows-mcp.d.ts +15 -0
- package/dist/src/connectors/botlingknows-mcp.js +130 -0
- package/dist/src/connectors/botlingknows-mcp.js.map +1 -0
- package/dist/src/connectors/group-chat-http.d.ts +17 -0
- package/dist/src/connectors/group-chat-http.js +226 -0
- package/dist/src/connectors/group-chat-http.js.map +1 -0
- package/dist/src/connectors/mcp-http-client.d.ts +11 -0
- package/dist/src/connectors/mcp-http-client.js +108 -0
- package/dist/src/connectors/mcp-http-client.js.map +1 -0
- package/dist/src/core.d.ts +170 -0
- package/dist/src/core.js +1720 -0
- package/dist/src/core.js.map +1 -0
- package/dist/src/daemon.d.ts +31 -0
- package/dist/src/daemon.js +605 -0
- package/dist/src/daemon.js.map +1 -0
- package/dist/src/db.d.ts +31 -0
- package/dist/src/db.js +513 -0
- package/dist/src/db.js.map +1 -0
- package/dist/src/event-sdk.d.ts +43 -0
- package/dist/src/event-sdk.js +152 -0
- package/dist/src/event-sdk.js.map +1 -0
- package/dist/src/host-adapter-registry.d.ts +21 -0
- package/dist/src/host-adapter-registry.js +59 -0
- package/dist/src/host-adapter-registry.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/instance-config.d.ts +46 -0
- package/dist/src/instance-config.js +176 -0
- package/dist/src/instance-config.js.map +1 -0
- package/dist/src/mcp.d.ts +51 -0
- package/dist/src/mcp.js +728 -0
- package/dist/src/mcp.js.map +1 -0
- package/dist/src/operator-control.d.ts +82 -0
- package/dist/src/operator-control.js +220 -0
- package/dist/src/operator-control.js.map +1 -0
- package/dist/src/out-of-process-host.d.ts +37 -0
- package/dist/src/out-of-process-host.js +196 -0
- package/dist/src/out-of-process-host.js.map +1 -0
- package/dist/src/policy-control.d.ts +3 -0
- package/dist/src/policy-control.js +120 -0
- package/dist/src/policy-control.js.map +1 -0
- package/dist/src/release-lifecycle.d.ts +98 -0
- package/dist/src/release-lifecycle.js +378 -0
- package/dist/src/release-lifecycle.js.map +1 -0
- package/dist/src/sdk-error.d.ts +6 -0
- package/dist/src/sdk-error.js +12 -0
- package/dist/src/sdk-error.js.map +1 -0
- package/dist/src/source-adapter.d.ts +32 -0
- package/dist/src/source-adapter.js +164 -0
- package/dist/src/source-adapter.js.map +1 -0
- package/dist/src/source-connector.d.ts +50 -0
- package/dist/src/source-connector.js +358 -0
- package/dist/src/source-connector.js.map +1 -0
- package/dist/src/source-control-client.d.ts +38 -0
- package/dist/src/source-control-client.js +61 -0
- package/dist/src/source-control-client.js.map +1 -0
- package/dist/src/source-ingress.d.ts +16 -0
- package/dist/src/source-ingress.js +47 -0
- package/dist/src/source-ingress.js.map +1 -0
- package/dist/src/source-sdk.d.ts +6 -0
- package/dist/src/source-sdk.js +53 -0
- package/dist/src/source-sdk.js.map +1 -0
- package/dist/src/transport-sdk.d.ts +102 -0
- package/dist/src/transport-sdk.js +222 -0
- package/dist/src/transport-sdk.js.map +1 -0
- package/dist/src/transport.d.ts +23 -0
- package/dist/src/transport.js +71 -0
- package/dist/src/transport.js.map +1 -0
- package/dist/src/types.d.ts +522 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/util.d.ts +26 -0
- package/dist/src/util.js +136 -0
- package/dist/src/util.js.map +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +2 -0
- package/dist/src/version.js.map +1 -0
- package/docs/adapters/botlingknows.md +40 -0
- package/docs/adapters/gmail-planned.md +16 -0
- package/docs/adapters/group-chat.md +126 -0
- package/docs/operations/connector-catalog.md +40 -0
- package/docs/operations/operator-control.md +83 -0
- package/docs/operations/out-of-process-host-adapter.md +150 -0
- package/docs/operations/policies.md +106 -0
- package/docs/operations/public-sdk.md +87 -0
- package/docs/operations/release-lifecycle.md +154 -0
- package/docs/operations/source-connectors.md +163 -0
- package/docs/operations/source-ingress.md +108 -0
- package/docs/operations/templates/botlingknows-connector.env.example +5 -0
- package/docs/operations/templates/sources.json.example +15 -0
- package/docs/quickstart.md +143 -0
- package/docs/releases/0.9.0-preview.8.md +44 -0
- package/docs/verification.md +19 -0
- package/examples/policies/botlingknows-conservative.json +44 -0
- package/examples/reference-host-adapter/README.md +41 -0
- package/examples/reference-host-adapter/adapter.mjs +173 -0
- package/examples/reference-host-adapter/host-adapters.json.example +26 -0
- package/package.json +94 -0
- package/schemas/event-v1.schema.json +26 -0
- package/schemas/policy-v1.schema.json +66 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# First Wake Quickstart
|
|
2
|
+
|
|
3
|
+
状态:`wake-bridge@0.9.0-preview.8` current onboarding。
|
|
4
|
+
|
|
5
|
+
这条路径从全新安装走到第一条可审计 wake。它面向已经拥有 agent backend、runner、bridge service 或自制 frontend,并能实现精确 session 注入的用户。
|
|
6
|
+
|
|
7
|
+
## 开始前先确认
|
|
8
|
+
|
|
9
|
+
你需要两个彼此独立的接入面:
|
|
10
|
+
|
|
11
|
+
1. **Host Adapter**:知道要唤醒的精确 session,并能启动、恢复或排队这个 session。Wake Bridge 不内置具体 agent 产品的唤醒方法。
|
|
12
|
+
2. **Source Connector**:可选。它从论坛、邮件或群聊等来源读取事实;上游 credential 只由 Connector 保管。
|
|
13
|
+
|
|
14
|
+
如果还没有 Host Adapter,可以先运行仓库附带的 [Reference Host Adapter](../examples/reference-host-adapter/README.md) 验证协议 wiring;其默认 stdout sink 不会真的启动 agent。
|
|
15
|
+
|
|
16
|
+
## 1. 安装并创建 Agent Space
|
|
17
|
+
|
|
18
|
+
当前 preview 支持 macOS arm64/x64、Node.js 20+ 和 SQLite CLI 3.33+:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install --global wake-bridge@preview
|
|
22
|
+
wakebridge release-preflight
|
|
23
|
+
|
|
24
|
+
wakebridge init \
|
|
25
|
+
--data-dir "/absolute/private/path/wakebridge/my-agent" \
|
|
26
|
+
--instance-id my-agent \
|
|
27
|
+
--owner-id my-agent \
|
|
28
|
+
--timezone Asia/Shanghai
|
|
29
|
+
|
|
30
|
+
wakebridge release-preflight \
|
|
31
|
+
--config "/absolute/private/path/wakebridge/my-agent/wakebridge.config.json"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`init` 创建 mode-600 config、owner credential 与 SQLite DB,不在 stdout 打印 token,也不会覆盖已有实例。一个 preview instance 只服务一个 Agent Space。
|
|
35
|
+
|
|
36
|
+
## 2. 接入并启动 Host Adapter
|
|
37
|
+
|
|
38
|
+
为你的 adapter 准备一个 [Host Adapter manifest](operations/out-of-process-host-adapter.md#1-operator-配置),其中:
|
|
39
|
+
|
|
40
|
+
- `adapter_kind` 必须与 adapter 创建 `HostSessionClient` 时完全一致;
|
|
41
|
+
- `attention_channels` 必须包含随后事件与预约使用的 channel;
|
|
42
|
+
- bootstrap token、每个 adapter process 的 route token、owner token 必须互不相同;
|
|
43
|
+
- capability 和 `receipt_upper_bound` 只写真实验证过的能力。
|
|
44
|
+
|
|
45
|
+
在含 bootstrap token 的受限环境中启动 daemon:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
wakebridge doctor \
|
|
49
|
+
--config "/absolute/private/path/wakebridge/my-agent/wakebridge.config.json" \
|
|
50
|
+
--host-adapters "/absolute/private/path/host-adapters.json"
|
|
51
|
+
|
|
52
|
+
wakebridge daemon \
|
|
53
|
+
--config "/absolute/private/path/wakebridge/my-agent/wakebridge.config.json" \
|
|
54
|
+
--host 127.0.0.1 --port 4311 \
|
|
55
|
+
--host-adapters "/absolute/private/path/host-adapters.json"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
然后由 Host Adapter 使用 `wake-bridge/transport` 的 `HostSessionClient.open()` 登记:
|
|
59
|
+
|
|
60
|
+
- 精确、opaque 的 `session_ref`;
|
|
61
|
+
- 与事件相同的 `attention_channel`,下例使用 `life`;
|
|
62
|
+
- credential-free loopback `route_origin`;
|
|
63
|
+
- 独立的 per-process `route_token`。
|
|
64
|
+
|
|
65
|
+
正常运行时 adapter 续租同一个 lease;重建 session 或显式 takeover 才增加 generation;关闭时调用 `close()`。完整代码见 [out-of-process contract](operations/out-of-process-host-adapter.md) 与 [中立可运行示例](../examples/reference-host-adapter/README.md)。
|
|
66
|
+
|
|
67
|
+
## 3. 发送第一条受控 wake
|
|
68
|
+
|
|
69
|
+
使用不含敏感正文的 resource reference,并让 channel 与 Host Adapter 登记值完全一致:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
wakebridge emit \
|
|
73
|
+
--config "/absolute/private/path/wakebridge/my-agent/wakebridge.config.json" \
|
|
74
|
+
--source manual \
|
|
75
|
+
--type quickstart.test \
|
|
76
|
+
--dedupe-key quickstart-first-wake-1 \
|
|
77
|
+
--attention-channel-hint life \
|
|
78
|
+
--resource "quickstart://first-wake"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
正在运行的 daemon 会完成 tick 与 dispatch。检查结果:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
wakebridge status \
|
|
85
|
+
--config "/absolute/private/path/wakebridge/my-agent/wakebridge.config.json"
|
|
86
|
+
|
|
87
|
+
wakebridge inspect \
|
|
88
|
+
--config "/absolute/private/path/wakebridge/my-agent/wakebridge.config.json"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
成功证据分层如下:
|
|
92
|
+
|
|
93
|
+
- 没有 live binding 时,batch 保持 `waiting_for_endpoint`;这不是事件丢失;
|
|
94
|
+
- adapter 返回 HTTP 202 后,Core 最多记录 `transport_accepted`;
|
|
95
|
+
- adapter 自己必须证明 wake 已进入精确 session;
|
|
96
|
+
- agent 醒来后从 envelope 的 resource 读取权威来源,并通过 Wake Bridge MCP `attention_ack` 与 `attention_consume` 等工具提供 seen/consumed 证据。
|
|
97
|
+
|
|
98
|
+
如果 batch 一直等待,先检查事件、Host Adapter manifest 与 session registration 的 `attention_channel` 是否一致;Wake Bridge 不会猜测或跨 channel 错投。
|
|
99
|
+
|
|
100
|
+
## 4. 让 agent 自己使用 Wake Bridge
|
|
101
|
+
|
|
102
|
+
把下面的 stdio command 按你的 harness 格式配置为 MCP server:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
wakebridge mcp --config /absolute/private/path/wakebridge/my-agent/wakebridge.config.json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
运行 daemon 时,再通过 `WAKEBRIDGE_DAEMON_URL=http://127.0.0.1:4311` 让 MCP 的 Source control 工具访问同一个 authenticated daemon。不要把 owner token、Host Adapter token 或 provider credential 放进 prompt。
|
|
109
|
+
|
|
110
|
+
agent 可以用 `attention_schedule` 为未来的自己建立 durable self-commitment。`eligible_after` 必须是带 `Z` 或数字 offset 的绝对 RFC3339 时间,`resource` 应指向未来醒来后可重新读取的权威位置;重复提交应复用稳定的 `idempotency_key`。
|
|
111
|
+
|
|
112
|
+
可信 Host Adapter 观察到绑定 session 中真实用户消息被接受时,可以调用 `renewPresence()`。默认 policy 会在 presence lease 有效期内延后普通后台 claim,租约到期后自动重新进入调度;进程存活、窗口 focus、模型输出或 Wake 自己都不能冒充用户在场。
|
|
113
|
+
|
|
114
|
+
## 5. 可选:启用 Source Connector
|
|
115
|
+
|
|
116
|
+
先查看 catalog:
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
wakebridge connector-catalog
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
所有 Connector 默认 `disabled`。以小机知道为例,完整顺序固定为:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
configure → verify identity → bootstrap from-now → real UAT → enable
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
由 operator 启动独立 Connector,并让 daemon 使用 `--connectors /absolute/path/sources.json`。随后 owning agent 依次调用:
|
|
129
|
+
|
|
130
|
+
1. `attention_source_status`
|
|
131
|
+
2. `attention_source_verify("botlingknows")`
|
|
132
|
+
3. `attention_source_bootstrap(...exact verified subject/binding...)`
|
|
133
|
+
4. 用一条无敏感通知完成真实 wake UAT
|
|
134
|
+
5. `attention_source_enable("botlingknows")`
|
|
135
|
+
|
|
136
|
+
不要跳过 from-now bootstrap,不要手改 SQLite,也不要把上游 MCP URL 或 credential 传给 Wake Bridge 工具。详见 [Connector catalog](operations/connector-catalog.md)、[Source Connector contract](operations/source-connectors.md) 与 [小机知道 Connector](adapters/botlingknows.md)。
|
|
137
|
+
|
|
138
|
+
## 6. 停止与保留数据
|
|
139
|
+
|
|
140
|
+
Host Adapter 先关闭当前 session lease,再停止 daemon。若安装了 LaunchAgent,按 [release lifecycle](operations/release-lifecycle.md) 返回的精确 unload/uninstall 命令操作。
|
|
141
|
+
|
|
142
|
+
卸载 package 或 service 不删除 config、SQLite、backup 和日志。数据删除是另一个需要显式备份与确认的动作。
|
|
143
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# wake-bridge 0.9.0-preview.8
|
|
2
|
+
|
|
3
|
+
候选日期:2026-09-07。公开发布日期:2026-09-09。状态:npm `preview` + GitHub pre-release。
|
|
4
|
+
|
|
5
|
+
本版本重新收敛公开产品边界:Wake Bridge 支持版本化 Host Adapter Contract,不直接声明支持某个 agent CLI、Desktop
|
|
6
|
+
或 runtime。具体接入方负责精确 session 的启动、恢复、注入、presence/activity observation、receipt 上界与真实时延 UAT。
|
|
7
|
+
当前公共 adapter HTTP 202 仍最多产生 `transport_accepted` receipt;更高层 turn start 或 agent consumption 必须来自
|
|
8
|
+
独立、可验证的宿主/agent evidence。
|
|
9
|
+
|
|
10
|
+
## 公共宿主边界
|
|
11
|
+
|
|
12
|
+
- README、架构、协议、project background、doctor 与 release runbook 不把具体 agent 产品列入支持矩阵;
|
|
13
|
+
- `doctor` 只报告公共 out-of-process contract 与实际配置的 adapter,不探测或宣传产品 host;
|
|
14
|
+
- 具体产品实现、fixture 和 UAT 不进入公共仓库或 tarball;
|
|
15
|
+
- 新增中立 `examples/reference-host-adapter`,只依赖 `wake-bridge/transport`,演示 session open/renew/close、固定
|
|
16
|
+
envelope、attempt idempotency 与可信 presence/activity edge;示例 stdout sink 不冒充真实 agent wake。
|
|
17
|
+
|
|
18
|
+
## Source Connector catalog
|
|
19
|
+
|
|
20
|
+
- 新增 `wakebridge connector-catalog` 与公开 `connectorCatalog()`;所有条目默认 disabled;
|
|
21
|
+
- 小机知道为 bundled `official_optional / available`,仍须 configure → verify → from-now bootstrap → UAT → enable;
|
|
22
|
+
- Gmail 只标记为 `planned / not enableable`,并附公开进入条件;
|
|
23
|
+
- Group Chat 保留为 reference/conformance fixture;当前 catalog 不包含未沟通、未验收的第三方来源;
|
|
24
|
+
- 新增可选的小机知道保守 policy recipe;Connector 自身不携带默认唤醒决定。
|
|
25
|
+
|
|
26
|
+
## 验证口径
|
|
27
|
+
|
|
28
|
+
- public SDK、Host Adapter lifecycle/generation/receipt、Source Connector cursor/identity、CLI 与 release package 黑盒继续回归;
|
|
29
|
+
- tarball 必须包含中立示例、catalog 文档与 Gmail planned status,且不包含具体产品 Host Adapter;
|
|
30
|
+
- 全量测试固定 single fork,避免长 SQLite/loopback conformance 在逐文件 worker 轮换时触发 Vitest 汇报 RPC timeout;
|
|
31
|
+
- `host_accepted` 仍不等于 `agent_started`、`agent_seen` 或 `agent_consumed`;
|
|
32
|
+
- npm 默认 `latest` channel 不会被此 pre-release 改动;安装时必须显式使用 `wake-bridge@preview`。
|
|
33
|
+
|
|
34
|
+
公共候选验收结果:22 个 test files / 104 tests 全部通过;公开 Markdown 相对链接检查通过。
|
|
35
|
+
|
|
36
|
+
## 发布验收补充(2026-09-08)
|
|
37
|
+
|
|
38
|
+
- clean tarball 在仓库外完成 install、init、external Host Adapter、presence、restart/takeover fencing、self-commitment 与 close/no-endpoint 演练;
|
|
39
|
+
- 一个已有仓库外 Host Adapter consumer 仅替换为 preview.8 tarball 后,21 项 conformance 全部通过;
|
|
40
|
+
- 小机知道 Connector 的公开实现覆盖 identity verify、from-now bootstrap 与 disabled-by-default gate;
|
|
41
|
+
- 新增 [First Wake Quickstart](../quickstart.md) 与 GitHub 文档导航;
|
|
42
|
+
- 具体产品 Host Adapter 历史实现和 CLI 入口已从发布 tarball 移除,使 artifact 与 `product_hosts_bundled=false` 一致。
|
|
43
|
+
|
|
44
|
+
公开证据边界见 [verification](../verification.md)。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# 公开验证口径
|
|
2
|
+
|
|
3
|
+
状态:`wake-bridge@0.9.0-preview.8` release candidate。
|
|
4
|
+
|
|
5
|
+
公共仓库用以下证据验证自身,不用具体宿主产品的现场结果代替通用契约:
|
|
6
|
+
|
|
7
|
+
- Core durability:Event、Claim、Batch、outbox、retry 与 receipt 在 SQLite 中具有可恢复状态;
|
|
8
|
+
- policy:immediate、scheduled、suppress、quiet hours、presence、inactivity watch 与 self-commitment;
|
|
9
|
+
- Host Adapter contract:open、renew、close、takeover generation fencing、route credential separation 与 receipt upper bound;
|
|
10
|
+
- Source Connector contract:manifest identity、from-now bootstrap、cursor CAS、dedupe、retry 与 `needs_attention`;
|
|
11
|
+
- package black box:从 tarball 在仓库外安装后完成 import、init、daemon、emit、inspect、external Host Adapter、backup 与 service profile;
|
|
12
|
+
- reference adapter:只依赖公开 `wake-bridge/transport` subpath,并通过独立 conformance tests。
|
|
13
|
+
|
|
14
|
+
一次 HTTP 202 只证明 adapter transport 接受了请求。它不自动证明 agent session 已启动、agent 已看到消息或事项已处理。
|
|
15
|
+
更高层 receipt 必须来自宿主或 agent 能实际观察到的证据。
|
|
16
|
+
|
|
17
|
+
具体 Host Adapter 和 Source Connector 维护方仍须针对自己的版本完成真实登录、identity、restart、offline catch-up、latency、
|
|
18
|
+
presence/activity 与 failure-semantics UAT。通过公共 contract 代表 compatibility,不代表 Wake Bridge 项目替第三方宿主提供运行保证。
|
|
19
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"policies": [
|
|
4
|
+
{
|
|
5
|
+
"id": "botlingknows-mention",
|
|
6
|
+
"version": 1,
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"order": 100,
|
|
9
|
+
"match": { "source": "botlingknows", "type": "mention" },
|
|
10
|
+
"delivery": {
|
|
11
|
+
"mode": "immediate",
|
|
12
|
+
"quiet_hours_policy": "defer",
|
|
13
|
+
"foreground_presence_policy": "defer"
|
|
14
|
+
},
|
|
15
|
+
"batch": { "coalesce_by": "source", "max_events": 20, "window_ms": 30000 },
|
|
16
|
+
"target": { "attention_channel": "life" },
|
|
17
|
+
"reason_code": "botlingknows_mention"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "botlingknows-other",
|
|
21
|
+
"version": 1,
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"order": 50,
|
|
24
|
+
"match": { "source": "botlingknows" },
|
|
25
|
+
"delivery": {
|
|
26
|
+
"mode": "scheduled",
|
|
27
|
+
"scheduled_local_time": "09:00",
|
|
28
|
+
"quiet_hours_policy": "defer",
|
|
29
|
+
"foreground_presence_policy": "defer"
|
|
30
|
+
},
|
|
31
|
+
"batch": { "coalesce_by": "source", "max_events": 50, "window_ms": 60000 },
|
|
32
|
+
"target": { "attention_channel": "life" },
|
|
33
|
+
"reason_code": "botlingknows_daily"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "default",
|
|
37
|
+
"version": 2,
|
|
38
|
+
"enabled": true,
|
|
39
|
+
"order": -1000000,
|
|
40
|
+
"match": {},
|
|
41
|
+
"delivery": { "mode": "suppress" }
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Reference Host Adapter
|
|
2
|
+
|
|
3
|
+
这是一个中立的 out-of-process Host Adapter v1 示例,不对应、也不声称支持任何具体 agent 产品。
|
|
4
|
+
|
|
5
|
+
它演示:
|
|
6
|
+
|
|
7
|
+
- 以 scoped host credential 登记一个精确 session 与 attention channel;
|
|
8
|
+
- 只在本机 loopback 接收固定 Wake envelope;
|
|
9
|
+
- 对同一个 `attempt_id` 幂等接受;
|
|
10
|
+
- 定期 renew endpoint lease,退出时 close;
|
|
11
|
+
- 由 host 在观察到真实用户/会话 edge 时显式上报 presence 与 activity。
|
|
12
|
+
|
|
13
|
+
唯一需要接入方替换的是 `acceptWake`。这个回调必须把 envelope 交给 `session_ref` 指定的精确 session/task,并且只在
|
|
14
|
+
宿主已经接受该 delivery 后 resolve。它如何启动或恢复 session、怎样在 busy 时排队、能否 cold push,以及多久真正
|
|
15
|
+
启动 agent,全部属于该 Host Adapter 的能力与 UAT,不由 Wake Bridge Core 推断。
|
|
16
|
+
|
|
17
|
+
## 运行示例
|
|
18
|
+
|
|
19
|
+
先把 [host-adapters.json.example](host-adapters.json.example) 复制到私有配置目录,替换
|
|
20
|
+
`tested_host_versions` 和 capability 声明,并让 daemon 使用 `--host-adapters` 加载它。Host bootstrap token 与 delivery
|
|
21
|
+
route token 必须是两个不同的、至少 32 字符的随机值;不要写进 JSON、shell history 或仓库。
|
|
22
|
+
|
|
23
|
+
构建 package 后启动示例:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
export WAKEBRIDGE_DAEMON_ORIGIN=http://127.0.0.1:4311
|
|
27
|
+
export WAKEBRIDGE_HOST_TOKEN='<host bootstrap token>'
|
|
28
|
+
export WAKEBRIDGE_ROUTE_TOKEN='<different route token>'
|
|
29
|
+
export WAKEBRIDGE_SESSION_REF='<exact host session id>'
|
|
30
|
+
export WAKEBRIDGE_ATTENTION_CHANNEL=default
|
|
31
|
+
node examples/reference-host-adapter/adapter.mjs
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
仓库自带的 `acceptWake` 只把 envelope 写到 stdout,因此只证明协议 wiring,不会启动 agent。接入真实宿主时:
|
|
35
|
+
|
|
36
|
+
1. 用该宿主的公开、精确 session API 替换 stdout callback;
|
|
37
|
+
2. 按真实行为填写 capability 与最强 receipt upper bound,不把 HTTP 202 写成 agent seen;
|
|
38
|
+
3. 验收 idle、busy、restart、重复 delivery、session close、generation takeover、presence TTL 与 activity watch;
|
|
39
|
+
4. 只有在这些 UAT 通过后,才由 adapter 维护者声明该 host/version 的支持等级。
|
|
40
|
+
|
|
41
|
+
完整协议与安全边界见 [out-of-process Host Adapter runbook](../../docs/operations/out-of-process-host-adapter.md)。
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import {
|
|
4
|
+
HostSessionClient,
|
|
5
|
+
WakeBridgeSdkError,
|
|
6
|
+
validateLocalHostDeliveryRequest,
|
|
7
|
+
} from "wake-bridge/transport";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_RENEW_INTERVAL_MS = 60_000;
|
|
10
|
+
const MAX_ACCEPTED_ATTEMPTS = 1_000;
|
|
11
|
+
|
|
12
|
+
function requireSecret(value, name) {
|
|
13
|
+
if (typeof value !== "string" || value.length < 32) {
|
|
14
|
+
throw new Error(`${name} must contain at least 32 characters`);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function readJson(request) {
|
|
20
|
+
const chunks = [];
|
|
21
|
+
let size = 0;
|
|
22
|
+
for await (const chunk of request) {
|
|
23
|
+
size += chunk.length;
|
|
24
|
+
if (size > 256 * 1024) throw new WakeBridgeSdkError("request body is too large", "invalid_host_delivery", 413);
|
|
25
|
+
chunks.push(chunk);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
29
|
+
} catch {
|
|
30
|
+
throw new WakeBridgeSdkError("request body is not valid JSON", "invalid_host_delivery", 400);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Minimal out-of-process Host Adapter lifecycle.
|
|
36
|
+
*
|
|
37
|
+
* `acceptWake` is the only host-specific seam. It must enqueue the fixed Wake
|
|
38
|
+
* envelope into the exact session named by `sessionRef`, and resolve only once
|
|
39
|
+
* that host has accepted ownership of the delivery.
|
|
40
|
+
*/
|
|
41
|
+
export class ReferenceHostAdapter {
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.daemonOrigin = options.daemonOrigin;
|
|
44
|
+
this.hostToken = requireSecret(options.hostToken, "hostToken");
|
|
45
|
+
this.routeToken = requireSecret(options.routeToken, "routeToken");
|
|
46
|
+
if (this.hostToken === this.routeToken) throw new Error("hostToken and routeToken must be distinct");
|
|
47
|
+
this.sessionRef = options.sessionRef;
|
|
48
|
+
this.attentionChannel = options.attentionChannel;
|
|
49
|
+
this.acceptWake = options.acceptWake;
|
|
50
|
+
this.port = options.port ?? 0;
|
|
51
|
+
this.renewIntervalMs = options.renewIntervalMs ?? DEFAULT_RENEW_INTERVAL_MS;
|
|
52
|
+
this.acceptedAttempts = new Set();
|
|
53
|
+
this.client = new HostSessionClient({
|
|
54
|
+
base_url: this.daemonOrigin,
|
|
55
|
+
host_token: this.hostToken,
|
|
56
|
+
adapter_kind: "reference_host",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async start() {
|
|
61
|
+
this.server = createServer((request, response) => {
|
|
62
|
+
void this.handleRequest(request, response);
|
|
63
|
+
});
|
|
64
|
+
await new Promise((resolve, reject) => {
|
|
65
|
+
this.server.once("error", reject);
|
|
66
|
+
this.server.listen(this.port, "127.0.0.1", resolve);
|
|
67
|
+
});
|
|
68
|
+
const address = this.server.address();
|
|
69
|
+
if (!address || typeof address === "string") throw new Error("reference adapter did not obtain a TCP address");
|
|
70
|
+
this.routeOrigin = `http://127.0.0.1:${address.port}`;
|
|
71
|
+
try {
|
|
72
|
+
this.registration = await this.client.open({
|
|
73
|
+
session_ref: this.sessionRef,
|
|
74
|
+
attention_channel: this.attentionChannel,
|
|
75
|
+
route_origin: this.routeOrigin,
|
|
76
|
+
route_token: this.routeToken,
|
|
77
|
+
});
|
|
78
|
+
this.lease = this.client.lease(this.registration);
|
|
79
|
+
this.renewTimer = setInterval(() => {
|
|
80
|
+
void this.client.renew(this.lease).catch((error) => {
|
|
81
|
+
process.stderr.write(`[reference-host-adapter] lease renew failed: ${String(error)}\n`);
|
|
82
|
+
});
|
|
83
|
+
}, this.renewIntervalMs);
|
|
84
|
+
this.renewTimer.unref();
|
|
85
|
+
return { route_origin: this.routeOrigin, registration: this.registration };
|
|
86
|
+
} catch (error) {
|
|
87
|
+
await new Promise((resolve) => this.server.close(resolve));
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async noteUserPresence(observationId) {
|
|
93
|
+
if (!this.lease) throw new Error("adapter is not started");
|
|
94
|
+
return this.client.renewPresence(this.lease, {
|
|
95
|
+
observed_by: "reference_host",
|
|
96
|
+
observation: observationId,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async noteSessionActivity(observationId, kind = "user_input") {
|
|
101
|
+
if (!this.lease) throw new Error("adapter is not started");
|
|
102
|
+
return this.client.observeActivity(this.lease, { observation_id: observationId, kind });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async close() {
|
|
106
|
+
if (this.renewTimer) clearInterval(this.renewTimer);
|
|
107
|
+
if (this.lease) {
|
|
108
|
+
try { await this.client.close(this.lease); } catch { /* lease expiry is a safe close fallback */ }
|
|
109
|
+
}
|
|
110
|
+
if (this.server?.listening) await new Promise((resolve) => this.server.close(resolve));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async handleRequest(request, response) {
|
|
114
|
+
try {
|
|
115
|
+
if (request.method !== "POST" || request.url !== "/v1/wakes") {
|
|
116
|
+
response.writeHead(404).end();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (request.headers.authorization !== `Bearer ${this.routeToken}`) {
|
|
120
|
+
response.writeHead(401).end();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const delivery = validateLocalHostDeliveryRequest(await readJson(request));
|
|
124
|
+
if (request.headers["x-wakebridge-delivery-nonce"] !== delivery.delivery_nonce) {
|
|
125
|
+
response.writeHead(400).end();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!this.acceptedAttempts.has(delivery.attempt_id)) {
|
|
129
|
+
await this.acceptWake({
|
|
130
|
+
session_ref: this.sessionRef,
|
|
131
|
+
wake: delivery.wake,
|
|
132
|
+
attempt_id: delivery.attempt_id,
|
|
133
|
+
delivery_nonce: delivery.delivery_nonce,
|
|
134
|
+
});
|
|
135
|
+
this.acceptedAttempts.add(delivery.attempt_id);
|
|
136
|
+
if (this.acceptedAttempts.size > MAX_ACCEPTED_ATTEMPTS) {
|
|
137
|
+
this.acceptedAttempts.delete(this.acceptedAttempts.values().next().value);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
response.writeHead(202).end();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const status = error instanceof WakeBridgeSdkError ? error.status : 503;
|
|
143
|
+
response.writeHead(status).end();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function main() {
|
|
149
|
+
const adapter = new ReferenceHostAdapter({
|
|
150
|
+
daemonOrigin: process.env.WAKEBRIDGE_DAEMON_ORIGIN ?? "http://127.0.0.1:4311",
|
|
151
|
+
hostToken: process.env.WAKEBRIDGE_HOST_TOKEN,
|
|
152
|
+
routeToken: process.env.WAKEBRIDGE_ROUTE_TOKEN,
|
|
153
|
+
sessionRef: process.env.WAKEBRIDGE_SESSION_REF ?? "reference-session",
|
|
154
|
+
attentionChannel: process.env.WAKEBRIDGE_ATTENTION_CHANNEL ?? "default",
|
|
155
|
+
port: Number(process.env.WAKEBRIDGE_REFERENCE_HOST_PORT ?? "4393"),
|
|
156
|
+
acceptWake: async (delivery) => {
|
|
157
|
+
// Replace this callback with the host's exact-session queue/injection API.
|
|
158
|
+
process.stdout.write(`${JSON.stringify({ accepted_by_demo_sink: true, ...delivery })}\n`);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const started = await adapter.start();
|
|
162
|
+
process.stderr.write(`[reference-host-adapter] listening at ${started.route_origin}\n`);
|
|
163
|
+
const shutdown = () => { void adapter.close().finally(() => process.exit(0)); };
|
|
164
|
+
process.once("SIGINT", shutdown);
|
|
165
|
+
process.once("SIGTERM", shutdown);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
169
|
+
main().catch((error) => {
|
|
170
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
171
|
+
process.exitCode = 1;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"adapters": [
|
|
4
|
+
{
|
|
5
|
+
"id": "reference-host-local",
|
|
6
|
+
"adapter_kind": "reference_host",
|
|
7
|
+
"adapter_version": "1.0.0",
|
|
8
|
+
"host_kind": "reference_host",
|
|
9
|
+
"tested_host_versions": ["replace-with-your-host/version"],
|
|
10
|
+
"token_env": "WAKEBRIDGE_HOST_TOKEN",
|
|
11
|
+
"attention_channels": ["default"],
|
|
12
|
+
"capabilities": {
|
|
13
|
+
"exact_live_route": true,
|
|
14
|
+
"cold_push": false,
|
|
15
|
+
"warm_resume": true,
|
|
16
|
+
"requires_live_binding": true,
|
|
17
|
+
"queue_when_busy": true,
|
|
18
|
+
"steer_when_busy": false,
|
|
19
|
+
"foreground_presence_observable": true,
|
|
20
|
+
"session_activity_observable": true,
|
|
21
|
+
"waiter_turn_settle_compatible": false
|
|
22
|
+
},
|
|
23
|
+
"receipt_upper_bound": "host_accepted"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wake-bridge",
|
|
3
|
+
"version": "0.9.0-preview.8",
|
|
4
|
+
"description": "Local-first durable attention scheduler and wake bridge for agent harnesses",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/reneyuxi0402/wake-bridge.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/reneyuxi0402/wake-bridge/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/reneyuxi0402/wake-bridge#readme",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/src/index.js",
|
|
16
|
+
"types": "./dist/src/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/src/index.d.ts",
|
|
20
|
+
"import": "./dist/src/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./event": {
|
|
23
|
+
"types": "./dist/src/event-sdk.d.ts",
|
|
24
|
+
"import": "./dist/src/event-sdk.js"
|
|
25
|
+
},
|
|
26
|
+
"./source": {
|
|
27
|
+
"types": "./dist/src/source-sdk.d.ts",
|
|
28
|
+
"import": "./dist/src/source-sdk.js"
|
|
29
|
+
},
|
|
30
|
+
"./transport": {
|
|
31
|
+
"types": "./dist/src/transport-sdk.d.ts",
|
|
32
|
+
"import": "./dist/src/transport-sdk.js"
|
|
33
|
+
},
|
|
34
|
+
"./source/group-chat": {
|
|
35
|
+
"types": "./dist/src/adapters/group-chat.d.ts",
|
|
36
|
+
"import": "./dist/src/adapters/group-chat.js"
|
|
37
|
+
},
|
|
38
|
+
"./schema/event-v1": "./schemas/event-v1.schema.json",
|
|
39
|
+
"./schema/policy-v1": "./schemas/policy-v1.schema.json",
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist/src",
|
|
44
|
+
"schemas",
|
|
45
|
+
"examples/reference-host-adapter",
|
|
46
|
+
"examples/policies/botlingknows-conservative.json",
|
|
47
|
+
"docs/quickstart.md",
|
|
48
|
+
"docs/verification.md",
|
|
49
|
+
"docs/operations/release-lifecycle.md",
|
|
50
|
+
"docs/operations/policies.md",
|
|
51
|
+
"docs/operations/source-ingress.md",
|
|
52
|
+
"docs/operations/public-sdk.md",
|
|
53
|
+
"docs/operations/operator-control.md",
|
|
54
|
+
"docs/operations/out-of-process-host-adapter.md",
|
|
55
|
+
"docs/operations/connector-catalog.md",
|
|
56
|
+
"docs/operations/source-connectors.md",
|
|
57
|
+
"docs/adapters/group-chat.md",
|
|
58
|
+
"docs/adapters/botlingknows.md",
|
|
59
|
+
"docs/adapters/gmail-planned.md",
|
|
60
|
+
"docs/operations/templates/sources.json.example",
|
|
61
|
+
"docs/operations/templates/botlingknows-connector.env.example",
|
|
62
|
+
"docs/releases/0.9.0-preview.8.md"
|
|
63
|
+
],
|
|
64
|
+
"bin": {
|
|
65
|
+
"wakebridge": "dist/src/cli.js"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=20.0.0"
|
|
69
|
+
},
|
|
70
|
+
"os": ["darwin"],
|
|
71
|
+
"cpu": ["arm64", "x64"],
|
|
72
|
+
"keywords": ["agent", "attention", "scheduler", "host-adapter", "source-connector", "wake"],
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public",
|
|
75
|
+
"tag": "preview"
|
|
76
|
+
},
|
|
77
|
+
"sideEffects": false,
|
|
78
|
+
"scripts": {
|
|
79
|
+
"prebuild": "node scripts/clean-dist.mjs",
|
|
80
|
+
"build": "tsc",
|
|
81
|
+
"prepack": "npm run build",
|
|
82
|
+
"typecheck": "tsc --noEmit",
|
|
83
|
+
"pretest": "npm run build",
|
|
84
|
+
"test": "vitest run --testTimeout=30000 --pool=forks --poolOptions.forks.singleFork",
|
|
85
|
+
"dev": "tsx src/cli.ts"
|
|
86
|
+
},
|
|
87
|
+
"dependencies": {},
|
|
88
|
+
"devDependencies": {
|
|
89
|
+
"@types/node": "^22.15.0",
|
|
90
|
+
"tsx": "^4.19.4",
|
|
91
|
+
"typescript": "^5.8.3",
|
|
92
|
+
"vitest": "^3.2.4"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/reneyuxi0402/wake-bridge/schemas/event-v1.schema.json",
|
|
4
|
+
"title": "Wake Bridge event contract v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "type", "dedupe_key", "resource"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": 1 },
|
|
10
|
+
"type": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
11
|
+
"occurred_at": { "type": "string", "format": "date-time" },
|
|
12
|
+
"dedupe_key": { "type": "string", "minLength": 1, "maxLength": 500 },
|
|
13
|
+
"coalesce_key": { "type": ["string", "null"] },
|
|
14
|
+
"priority_hint": { "type": ["string", "null"] },
|
|
15
|
+
"attention_channel_hint": { "type": ["string", "null"] },
|
|
16
|
+
"actor_ref": { "type": ["string", "null"] },
|
|
17
|
+
"resource": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"required": ["uri"],
|
|
20
|
+
"properties": { "uri": { "type": "string", "minLength": 1, "maxLength": 2048 } }
|
|
21
|
+
},
|
|
22
|
+
"metadata": { "type": "object" },
|
|
23
|
+
"payload_preview": { "type": ["string", "null"], "maxLength": 500 },
|
|
24
|
+
"idempotency_key": { "type": ["string", "null"] }
|
|
25
|
+
}
|
|
26
|
+
}
|