hongjr03
d36b00bbec
feat: make agent roles and skills dynamic
2026-07-11 12:55:05 +08:00
hongjr03
17c0536958
fix: enable only curated agent skills
2026-07-11 12:25:52 +08:00
hongjr03
96e120e02c
feat: add curated curriculum agent skills
2026-07-11 12:22:01 +08:00
hongjr03
7fcb57013e
fix: restore bounded agent sandbox execution
2026-07-11 02:34:56 +08:00
hongjr03
848f913597
feat: secure organization provider credentials
2026-07-10 22:04:43 +08:00
hongjr03
f07f280b8f
fix: prove Linux Agent sandbox boundary
2026-07-10 17:59:56 +08:00
hongjr03
f4968d6657
fix: confine Agent and MCP data access
2026-07-10 16:38:08 +08:00
hongjr03
5d315fff21
fix: enforce role tool whitelist
2026-07-09 20:48:03 +08:00
hongjr03
716101eed0
feat: add agent cost reporting
2026-07-09 20:29:56 +08:00
hongjr03
d01ea0e1cb
feat: interrupt running agent via card button with confirm dialog
...
Add a ⛔ 中断 button to live streaming cards; Feishu's native confirm
dialog is the confirmation step, so no extra card round-trip is needed.
- builder.ts: render interrupt action (danger button + confirm) while the
run is live; new `interrupted` flag renders a 已中断 footer instead of
完成/失败 on the complete card.
- streaming-card.ts: finish(text, { interrupted }) threads the flag into
the final patch; overflow cards suppress the button.
- runner.ts: RunRequest gains abortController, passed to the SDK query;
abort surfaces as RunStatus "interrupted" (no error) in the catch.
- trigger.ts: activeRuns registry maps runId → AbortController; onCardAction
resolves the interrupt button, authorizes agent.cancel, aborts the run.
Interrupted runs persist as CANCELED (spec RunState.canceled, terminal →
lock released per ADR-0002).
- authorizer.ts: agent.cancel now consults PermissionSettings.agentCancel
(MANAGE_ONLY/DISABLED) — previously only agentTrigger was honored, but
spec/PermissionGrant deliberately splits these knobs ("谁能触发" ≠
"谁能取消"). Default role remains MANAGE (Capability.normalCancel).
Tests: runner abort unit test, trigger interrupt + denied integration
tests, three agent.cancel authorization tests (manage allowed, edit denied,
DISABLED policy denies even manage). 26 files / 175 tests pass.
2026-07-09 18:59:31 +08:00
hongjr03
d6724e5a83
feat: streaming agent card with tool-use trace, reasoning panel, and tool results
...
Replace text-only PatchableTextStream with a unified StreamingAgentCard that
renders the full agent run lifecycle in a single Feishu interactive card:
- Tool-use panel: collapsible, shows each tool call with status (running/
success/error), input summary, and result/error in code blocks
- Reasoning panel: collapsible, streams thinking text inline then collapses
on completion
- Answer text: rendered via native Feishu markdown component (no post-row
round-trip)
Runner now captures previously-discarded SDK events:
- thinking_delta → reasoning stream
- tool_use id + input on content_block_start / assistant message
- tool_result from user messages (was entirely unhandled)
New files:
- card/trace-store.ts: per-run in-memory tool-use trace with secret redaction
- card/builder.ts: Feishu card JSON builder (tool panel + reasoning + text)
- card/streaming-card.ts: StreamingAgentCard controller (400ms throttled)
client.ts: add sendCard/patchCard raw JSON primitives
2026-07-09 17:18:40 +08:00
hongjr03
3f486232a8
feat(hub): 抽出运行时配置
2026-07-08 15:25:49 +08:00
hongjr03
ead5cf04d6
fix(hub): 稳定 Feishu trigger 集成测试
2026-07-08 15:13:50 +08:00
hongjr03
ecbc2c8666
fix(hub): 对齐 ADR-0018 agent 执行面边界
...
runner.ts: 启用 SDK 内置沙箱(bubblewrap/seatbelt),写入限制在 workspace,
denyRead 敏感路径,failIfUnavailable 硬拒非沙箱运行。bypassPermissions 保留
(headless 无交互),沙箱是硬边界而非权限提示层。
install_service.sh: 默认 service user 从 root 改为 cph-hub;env 模板修正
OPENROUTER_API_KEY → ANTHROPIC_AUTH_TOKEN/ANTHROPIC_BASE_URL/ANTHROPIC_API_KEY
(与 server.ts 实际读取一致);补 __BASE_DIR__ sed 替换。
cph-hub.service: AssertPathExists=/usr/bin/bwrap 强制沙箱依赖;NoNewPrivileges。
不加 ProtectSystem/ProtectHome(会破坏 cph render-cache 与 bwrap user-namespace)。
trigger.ts: 权限闸门去掉 if (senderOpenId !== '') 短路——缺 open_id 一律
fail-closed 拒绝,不再静默跳过;role gate 同步去掉冗余守卫(已由上游保证)。
顺手把 JSON.parse(msg.content) 的 inline cast 换成 Zod schema parse
(FileMessageContentSchema / TextMessageContentSchema)。
ADR-0018: 状态改为 Accepted+implemented,机制段写定 SDK 沙箱,网络决定为开放,
Open Questions 更新。
2026-07-08 13:11:14 +08:00
hongjr03
c73b41e1da
fix(hub): resume Claude SDK sessions
2026-07-08 11:48:20 +08:00
hongjr03
4bd5b03bb1
refactor(hub): Vercel AI SDK → Claude Code SDK
...
用 @anthropic-ai/claude-agent-sdk 的 query() 替换 Vercel AI SDK 的
streamText。SDK 自带 Read/Write/Bash/Glob/Grep 工具——read_file/
write_file/list_files/cph_check/cph_build 全部不需要了,Bash 跑
cph 直接。
UX 同时改成 workbuddy 风格:
- 收到 @bot → 表情回复(👍 )确认收到,不发'已开始处理'
- agent 回复作为文本流式发送(text-as-card patch,markdown 渲染)
- 超长自动续发新消息,不截断
- 完成/出错不再额外发状态消息
- 去掉卡片(model选择器/取消按钮)
ADR-0017 更新:放弃 provider-agnostic,Claude Code SDK 是 Anthropic
专有。换来:compaction、工具审批、partial message 流式、成熟 agent
loop。
tsc rc=0。旧文件(workspace.ts/cph.ts/provider.ts/tools.ts)暂留,
下个 commit 清理。
2026-07-08 01:36:59 +08:00
hongjr03
e7924f78d5
feat(hub): 结构化运行历史(A-3) + lock 心跳(A-5)
...
A-3 结构化历史:
- 新增 AgentMessage 表(每条消息的 queryable 投影,transcript 文件仍是 agent 读回记忆)
- 新增 AgentFileChange 表(run 期间文件变更 before/after hash + operation)
- runner.ts: generateText 后 persistAgentMessages 落库 result.responseMessages
- workspace.ts writeFileTool: 写前算 beforeHash(SHA-256),写后算 afterHash,通过 ctx.onFileChange sink 记录
- runner.ts flushFileChanges: 收集 sink 的变更批量写入 AgentFileChange
- ToolContext 加 onFileChange sink 字段
- trigger.ts 接线 runId/sessionId/prisma 到 RunRequest
A-5 lock 心跳:
- ProjectAgentLock 加 heartbeatAt 列
- runner.ts onStepFinish 回调:每步 model step 后更新 heartbeatAt(吞错,lock 可能被 force-release)
60 tests 全过(tsc 干净)。
2026-07-07 22:20:30 +08:00
hongjr03
a2c8fa8eaf
refactor(hub): 手搓 agent loop 换成 Vercel AI SDK
...
将 provider/runner/tools 三件手搓轮子替换为 AI SDK v7 的 generateText + tool:
- 删 openrouter-provider.ts(翻译层消失)、provider.ts 自定义消息类型
- runner.ts 循环体改为 generateText({stopWhen: stepCountIs}),保留 RunRequest/RunResult 公共签名
- tools.ts ToolRegistry 改为 ToolFactory + build(ctx, names?) 按 role 白名单构建 tools record
- workspace/cph/feishu 工具改写为 tool() 定义,execute 闭包捕获 per-run ToolContext
- transcript.ts 改为 ModelMessage[] JSONL(0.0.0 cutover,无迁移)
- server.ts/trigger.ts 接 modelFactory + toolWhitelist
- 测试:helpers 写 MockLanguageModel implements LanguageModelV4;5 个测试改写 + 新增 runner.test.ts
- 移除未使用的 openai 依赖
- ADR-0017 Consequences 同步:loop 委托给 AI SDK,provider seam 是 LanguageModel
tsc 干净,54 tests 全过。
2026-07-07 20:07:02 +08:00
hongjr03
afaf5bee09
feat(hub): RoleEntry 完整 bundle + per-run tool 白名单 + per-role 触发权限
...
RoleEntry 扩成 (model, systemPrompt, tools) bundle,id 兼任 slash command 名。
ToolRegistry.subset() 支持按白名单构造 per-run 视图,模型永远看不到 role 外的工具。
trigger 解析 /<role> 命令,查 RoleEntry 组装 systemPrompt + 子集 registry 传给 runner。
新增 RoleTriggerGrant 表 + canTriggerRole gate,与 ADR-0004 canTriggerAgent 串联:
先问'能不能触发 agent',再问'能触发哪个 role'。未配置 role 放行(back-compat)。
- models.ts: RoleEntry 加 systemPrompt + tools 字段
- tools.ts: ToolRegistry.subset(names) 返回共享 handler 的子集视图
- trigger.ts: extractRole 解析 slash; 传 systemPrompt + runTools; catch 链容错 P2025
- runner.ts: RunRequest.systemPrompt 改 string | undefined (exactOptionalPropertyTypes)
- schema.prisma + migration: RoleTriggerGrant(projectId, roleId, principal, revokedAt)
- permission.ts: canTriggerRole gate (有 grant 记录即白名单模式,含 revoked)
- server.ts: draft/review 两个 role 加 systemPrompt + tools 白名单
- 测试: role-permission.test.ts (5) + trigger.test.ts (+3), 53 全绿
2026-07-07 18:47:51 +08:00
hongjr03
0fe6cabc68
feat(hub): session transcript 连续性 + slash commands (/new /resume /reset)
...
修复每次 @bot 都是失忆状态的问题。
- transcript.ts: JSONL 文件存 session 对话消息(workspace/.cph/sessions/
<id>.jsonl),append-only;和 Claude Code 一致;ADR-0003 说的'不存全
历史'是群聊历史,session 对话是 Hub 自己产生的,不冲突
- runner.ts: RunRequest 加 transcriptPath;run 开始时读历史拼进初始
messages(system prompt 只在首条);run 结束时 append 本轮新消息
(loadedCount 区分历史 vs 新增,不重复写);best-effort(写失败不丢
run 结果)
- trigger.ts: slash commands 在 lock 检查之前(/new /resume /reset
不创建 run 不需要锁);transcriptPath 传给 runner
/new = 归档当前 session,下次 @bot 自动建新的
/resume = 恢复最近归档的 session
/reset = 同 /new
未知 /cmd 透传给 agent 当普通 prompt
- unit: transcript.test.ts 读写回环/append 累积/损坏行恢复/路径(6)
- integration: trigger.test.ts 加 /new /resume /reset /unknown(4)
修了 slash command 被 lock 挡住的问题(提到 lock 之前)。
vitest run 45/45 通过;tsc rc=0。
2026-07-07 17:50:05 +08:00
hongjr03
a08c33205b
refactor(hub): role 改为数据条目,不再是写死的闭集枚举
...
ADR-0017 说 role-based routing 是 product/admin config,不是 spec invariant。
之前 RunRole = "draft"|"review"|"triage" 闭集违反这条——role 钉死在代码里,
教师/admin 无法自定义。改为:
- RoleEntry(id+label+defaultModel)是数据,registry 持有 role 集合
- roleId 退化为 string,新 role 是配置改动不是代码改动
- runner 不再带 role 字段(model 已由 caller resolve 好传入,role 透传无意义)
- 未知 role 优雅降级到首个启用 model,不抛
tsc --noEmit rc=0;role-as-data + ADR-0003 不变式 smoke 通过。
2026-07-06 22:41:39 +08:00
hongjr03
3bca137b48
feat(hub): provider-agnostic agent seam 骨架(ADR-0001/0003/0017)
...
hub/ TS 新部件,平级于 spec/。本仓从零 TS 重写(非迁移旧服务),栈:
Fastify+Prisma 待补;本提交落 provider-agnostic agent runtime 骨架:
- provider.ts: AgentProvider seam,OpenAI-compatible messages+tools+tool_calls
形状(语言通解,非 OpenAI 承诺);@Claude 仅为触发品牌(ADR-0017)
- openrouter-provider.ts: openai SDK 指向 OpenRouter 的具体适配器,仅做
Message↔SDK 翻译;agent loop 不 import SDK,换 provider 不改 loop
- runner.ts: 自有 agent loop(dispatch tools/循环到 stop/budget),per-run
model(ADR-0017 role-based routing,run 带其 model)
- tools.ts: 窄工具面 + ADR-0003 McpReadRequest.Authorized 落代码:
feishu_read_context 拒绝非本项目绑定 chat 的读取(实测错 chat 抛
UnauthorizedChatRead,对 chat 放行)
- models.ts: ModelRegistry seam(role→default 映射策略 OPEN)
- server.ts: 接线入口(stub Feishu read,无活凭证即可编译)
tsc --noEmit rc=0;ADR-0003 不变式 smoke 通过。
2026-07-06 22:38:04 +08:00