Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.git
.env
node_modules
dist
coverage
playwright-report
test-results
data
*.log
41 changes: 41 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
WAKEONCUE_HOST=127.0.0.1
WAKEONCUE_API_PORT=4310
WAKEONCUE_DATABASE_PATH=./data/wakeoncue.sqlite
WAKEONCUE_LOG_LEVEL=info
WAKEONCUE_WEBHOOK_CLOCK_SKEW_SECONDS=300
WAKEONCUE_WEBHOOK_SECRET=replace-with-a-local-development-secret
WAKEONCUE_OMI_WEBHOOK_TOKEN=replace-with-a-dedicated-omi-ingress-token
WAKEONCUE_OMI_SUBJECT=local-user
WAKEONCUE_TIMEZONE_OFFSET_MINUTES=480
WAKEONCUE_QUIET_START_HOUR=22
WAKEONCUE_QUIET_END_HOUR=7
WAKEONCUE_DAILY_WAKE_LIMIT=3
WAKEONCUE_DAILY_NOTIFICATION_LIMIT=5
WAKEONCUE_NOTIFICATION_DAILY_BUDGET=3
WAKEONCUE_NATIVE_NOTIFICATION_GRACE_MS=5000
WAKEONCUE_NOTIFICATION_ADAPTER=disabled
WAKEONCUE_NOTIFICATION_WEBHOOK_URL=http://127.0.0.1:4320/notifications
WAKEONCUE_NOTIFICATION_WEBHOOK_SECRET=replace-with-a-dedicated-notification-secret
WAKEONCUE_OUTCOME_VERIFICATION_SECRET=replace-with-a-dedicated-verifier-secret
WAKEONCUE_ENCRYPTION_KEY=replace-with-32-byte-base64-key
WAKEONCUE_PUBLIC_URL=http://127.0.0.1:4310
WAKEONCUE_CONSOLE_URL=http://127.0.0.1:4173
WAKEONCUE_RUNTIME_ADAPTER=disabled
WAKEONCUE_RUNTIME_CALLBACK_URL=http://127.0.0.1:4310/v1/runtime/callbacks/openclaw
WAKEONCUE_RUNTIME_CALLBACK_SECRET=replace-with-a-dedicated-runtime-callback-secret
WAKEONCUE_RUNTIME_CALLBACK_CLOCK_SKEW_SECONDS=300
WAKEONCUE_RUNTIME_PEP_SECRET=replace-with-a-dedicated-runtime-pep-secret
WAKEONCUE_RUNTIME_PEP_CLOCK_SKEW_SECONDS=60
WAKEONCUE_APPROVAL_ADMIN_TOKEN=replace-with-a-human-console-only-token
WAKEONCUE_APPROVAL_WAIT_MS=90000
WAKEONCUE_PERMIT_TTL_SECONDS=300
WAKEONCUE_RUNTIME_STALE_AFTER_MS=60000
WAKEONCUE_RUNTIME_CALLBACK_STALE_AFTER_MS=300000
WAKEONCUE_OPENCLAW_BASE_URL=http://127.0.0.1:18791
WAKEONCUE_OPENCLAW_HOOK_TOKEN=replace-with-a-local-openclaw-hook-token
WAKEONCUE_OPENCLAW_AGENT_ID=main
WAKEONCUE_OPENCLAW_MODEL=modelstudio/glm-5
WAKEONCUE_OPENCLAW_PLUGIN_VERIFIED=0
WAKEONCUE_OPENCLAW_ACTIVATION_TIMEOUT_MS=15000
WAKEONCUE_OPENCLAW_AGENT_TIMEOUT_SECONDS=120
WAKEONCUE_LIVE_WAKE_ENABLED=false
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI

on:
push:
branches: [main, "codex/**"]
pull_request:

permissions:
contents: read

jobs:
quality:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10.13.1
- uses: actions/setup-node@v4
with:
node-version: 26
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm format:check
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm test
- run: pnpm build
16 changes: 15 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
.DS_Store
node_modules/
dist/
coverage/
playwright-report/
test-results/
.vite/
.env
.env.*
!.env.example
data/*.sqlite
data/*.sqlite-*
data/backups/
*.log
*.tsbuildinfo
.idea/
.vscode/
.runtime/
node_modules/
dist/
coverage/
Expand All @@ -11,4 +26,3 @@ coverage/
*.db
*.db-shm
*.db-wal

1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
README.md
MVP_GOAL_PROMPT.md
docs/architecture.html
docs/architecture.md
docs/mvp.md
6 changes: 6 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 100
}
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM node:26-bookworm-slim AS base
ENV PNPM_HOME=/pnpm
ENV PATH=$PNPM_HOME:$PATH
RUN corepack enable
WORKDIR /app

FROM base AS dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN --mount=type=cache,id=wakeoncue-pnpm-store,target=/pnpm/store \
pnpm config set store-dir /pnpm/store \
&& pnpm install --frozen-lockfile

FROM dependencies AS build
COPY . .
RUN pnpm build

FROM base AS runtime
ENV NODE_ENV=production
COPY --from=build /app /app
EXPOSE 4310
CMD ["pnpm", "start:api"]
206 changes: 206 additions & 0 deletions MVP_GOAL_PROMPT.md

Large diffs are not rendered by default.

68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ WakeOnCue 不实现通用 Agent,也不代替 OpenClaw、Pi Agent 等运行时

- [系统架构](docs/architecture.md)
- [MVP 设计](docs/mvp.md)
- [可直接交给 Codex 长期执行的 MVP Goal Prompt](MVP_GOAL_PROMPT.md)
- [可交互 HTML 架构图](docs/architecture.html)

## MVP
Expand All @@ -44,7 +45,72 @@ WakeOnCue 不实现通用 Agent,也不代替 OpenClaw、Pi Agent 等运行时

## 项目状态

当前处于架构与 MVP 定义阶段。仓库暂为私有,尚未承诺稳定 API。
工程 MVP 正在按 `docs/implementation-status.md` 中的可运行 checkpoint 推进。仓库暂为私有,尚未承诺稳定 API;生产 Live Wake 默认关闭。

## 本地开发

当前工程基线为 Node.js 26 与 pnpm 10:

~~~bash
corepack enable
# 可选:需要覆盖默认本地配置或接真实 Adapter 时再复制
cp .env.example .env
pnpm install --frozen-lockfile
pnpm db:migrate
pnpm dev
~~~

默认地址:API `http://127.0.0.1:4310`,Console `http://127.0.0.1:4173`。健康检查:

~~~bash
curl --fail http://127.0.0.1:4310/health
curl --fail http://127.0.0.1:4310/ready
~~~

质量门:

~~~bash
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm build
~~~

### 真实 OpenClaw E2E

WakeOnCue 日常开发继续使用 Node 26。当前固定验证的 OpenClaw `2026.7.1-2` 不支持 Node 26,因此只给 OpenClaw 进程使用 `n` 安装在用户目录中的 Node 24,不修改全局 Node,也不要求 Docker:

~~~bash
N_PREFIX="$HOME/.local/n" n 24.19.0
mkdir -p .runtime/openclaw-cli
PATH="$HOME/.local/n/bin:$PATH" npm install \
--prefix .runtime/openclaw-cli \
--ignore-scripts --no-audit --no-fund \
openclaw@2026.7.1-2

export WAKEONCUE_OPENCLAW_BIN="$PWD/.runtime/openclaw-cli/node_modules/.bin/openclaw"
export WAKEONCUE_OPENCLAW_NODE_BIN_DIR="$HOME/.local/n/bin"
pnpm test:e2e:openclaw
~~~

E2E 会创建隔离的 `.runtime/openclaw-e2e` 状态,强制 Gateway 绑定 loopback,关闭渠道,并通过 OpenClaw 官方 CLI 把现有 `~/.openclaw` 中的 portable static auth profile 导入隔离的 SQLite auth store;中间 JSON 副本随后删除,密钥不会输出。也可用 `WAKEONCUE_OPENCLAW_SOURCE_STATE_DIR` 指向另一份来源状态。

这条验证使用版本化、脱敏的 Omi fixture,但启动的 OpenClaw、模型请求、plugin hook、Tool Attempt 和签名 callback 都是真实运行。它证明工程集成,不代表 Omi 设备线上数据或生产 7 天 canary;生产 Live Wake 仍默认关闭。

### Approval / Permit

OpenClaw 的 `before_tool_call` 会把精确 Tool Attempt 通过独立 HMAC 密钥提交给 WakeOnCue PDP。受约束只读工具可以直接放行;外发消息、邮件、文件及日历/任务/业务写操作会暂停在 PEP,等待 Console 的“批准一次”或“拒绝”;删除、支付、购买、设备控制和未知工具直接拒绝。

将 `WAKEONCUE_APPROVAL_ADMIN_TOKEN` 只提供给本地人类操作者,不要传给 OpenClaw 进程。Console 中输入的 token 只保存在当前页面的 `sessionStorage`。批准产生的短 TTL Permit 绑定 subject、Runtime、Task、Attempt、tool 和完整 canonical arguments digest;PEP 在真实执行前原子消费一次。收件人、附件或任一参数变化、Permit 过期及重复消费都会拒绝执行。

Compose 路径:

~~~bash
docker compose up --build
~~~

当前实际验证、证据与剩余项见 [工程 MVP 实现状态](docs/implementation-status.md)。

## 名称

Expand Down
6 changes: 6 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@wakeoncue/api",
"version": "0.1.0",
"private": true,
"type": "module"
}
15 changes: 15 additions & 0 deletions apps/api/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { buildServer } from "./server.ts";

const server = await buildServer();
const host = process.env["WAKEONCUE_HOST"] ?? "127.0.0.1";
const port = Number(process.env["WAKEONCUE_API_PORT"] ?? "4310");

const shutdown = async (signal: string): Promise<void> => {
server.log.info({ signal }, "graceful shutdown requested");
await server.close();
};

process.once("SIGINT", () => void shutdown("SIGINT"));
process.once("SIGTERM", () => void shutdown("SIGTERM"));

await server.listen({ host, port });
Loading
Loading