Commit Graph

47 Commits

Author SHA1 Message Date
hongjr03 3bf643ff4d fix: guide Feishu users through onboarding 2026-07-11 13:52:19 +08:00
hongjr03 d36b00bbec feat: make agent roles and skills dynamic 2026-07-11 12:55:05 +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 035c264179 fix: keep agent sandbox sockets on short paths 2026-07-11 02:22:19 +08:00
hongjr03 38d9a9c6cb fix: bootstrap fresh alpha silo 2026-07-11 01:40:20 +08:00
hongjr03 9e954790dc feat: add deployable alpha silo 2026-07-11 00:25:45 +08:00
hongjr03 44557da499 feat: secure organization Feishu credentials 2026-07-10 23:13:11 +08:00
hongjr03 848f913597 feat: secure organization provider credentials 2026-07-10 22:04:43 +08:00
hongjr03 e049cb6880 fix: clear dependency security advisories 2026-07-10 18:59:03 +08:00
hongjr03 d730e51c3d fix: enforce active organization boundary 2026-07-10 18:42:12 +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 73fa28a178 feat: provision non-root Hub service 2026-07-10 15:59:35 +08:00
hongjr03 1094ebd651 fix: let agents download Feishu message resources 2026-07-10 12:17:07 +08:00
hongjr03 683d5674d1 feat(hub): add org members, teams, and project team-access APIs
Manage memberships with last-OWNER protection, team lifecycle (archive
revokes TEAM grants), and TEAM→PROJECT access under org admin auth.
2026-07-10 00:55:34 +08:00
hongjr03 1f8510a47f feat(hub): add org project explorer admin APIs
Expose folder/project tree, create/move/rename/archive, and Feishu
binding archive under /api/org/:orgSlug for OWNER/ADMIN sessions.
2026-07-10 00:55:26 +08:00
hongjr03 c4f052efa2 feat(hub): add Feishu OAuth session for org admin
Introduce signed cookie sessions, Feishu web OAuth, requireOrgRole
guards, and the first org admin APIs (summary + project settings).
2026-07-10 00:55:19 +08:00
hongjr03 87e3d3f990 feat: let Feishu onboarding create projects in folders 2026-07-10 00:35:18 +08:00
hongjr03 519ea8144f feat: add Feishu project onboarding card 2026-07-10 00:32:09 +08:00
hongjr03 34e07e229f feat: add org admin project onboarding foundation 2026-07-10 00:25:00 +08:00
hongjr03 2b7aa3294f feat: add organization tenant model 2026-07-09 23:37:20 +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 fc5a5365d8 fix: reply to Feishu trigger messages 2026-07-09 20:04:13 +08:00
hongjr03 fc0df7b823 fix: label Feishu trigger senders in agent prompts 2026-07-09 19:56:47 +08:00
hongjr03 f1d29176d3 feat: add slash command help 2026-07-09 19:43:13 +08:00
hongjr03 f260195439 fix: ack Feishu card actions before interrupt work 2026-07-09 19:26:57 +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 346aee5a68 fix: bind agent sessions to role 2026-07-09 16:06:06 +08:00
hongjr03 79505e6d44 fix: use startedAt instead of createdAt in trigger integration test
AgentRun model has startedAt, not createdAt. Codex introduced this
typo when refactoring the trigger queue integration tests.
2026-07-08 23:30:28 +08:00
hongjr03 36c0801e6f feat: add per-project message trigger wait queue
- TriggerQueue: FIFO per-project queue, max 5 items, 5min TTL expiry
- Replace reject-when-locked with queue: users get position feedback
- Run completion auto-starts next queued trigger (skips expired items)
- /reset clears project queue; 60s periodic purge interval
- Add unit tests (10) for queue lifecycle and edge cases
2026-07-08 17:31:43 +08:00
hongjr03 ad65d93b2e feat: add message burst debouncing (MessageBatcher)
- Create MessageBatcher: debounce 600ms, adaptive delay for long chunks,
  max 8 messages/4000 chars before immediate flush, per (chatId, sender) key
- Integrate into trigger: text messages enqueued, slash commands and
  file/image bypass batching, locked projects respond immediately
- Add unit tests (8) for batcher lifecycle and edge cases
2026-07-08 16:40:39 +08:00
hongjr03 a00e4f9871 fix(hub): 保留飞书回复上下文 2026-07-08 16:03:52 +08:00
hongjr03 dfcc2d70f8 feat(hub): 支持教师团队权限 2026-07-08 15:42:53 +08:00
hongjr03 3f486232a8 feat(hub): 抽出运行时配置 2026-07-08 15:25:49 +08:00
hongjr03 1cfda3ccd2 ci(hub): 补齐 Hub 检查和健康 smoke 2026-07-08 15:14:06 +08:00
hongjr03 ead5cf04d6 fix(hub): 稳定 Feishu trigger 集成测试 2026-07-08 15:13:50 +08:00
hongjr03 1ad78dbcce test(hub): 适配 Claude Code SDK 的 mock 修复
helpers.ts: MockLanguageModel 加 doStream 实现(返回 text-delta +
finish part);runner.test.ts: 加 stubPrisma(必填字段)。
这些测试在 Claude Code SDK 迁移后还需要进一步适配,先提交 buffer。
2026-07-08 01:52:21 +08:00
hongjr03 1abcc495f4 test(hub): 真实 model 集成测试 + Gitea CI workflow
real-model.test.ts: 用真 OpenRouter model + 真 cph + 真 workspace 跑 agent
  tool-calls 循环,验证 MockProvider 测不到的路径(model 返回 tool_calls →
  dispatch → tool_result 回传 → 继续/stop)。没 OPENROUTER_API_KEY 时自动
  skip,不阻塞 CI;有 key 时自动跑。
  两个 test:(1) list files → cph check → 回答;(2) write file → read back。
hub-check.yml: Gitea CI workflow,对齐 checker-check/spec-check 的纪律:
  npm ci → prisma generate → tsc → prisma validate → 装 cph → vitest unit →
  vitest integration(postgres service container)→ real-model(可选,从
  secrets 注入 key)。
全 suite: 60 passed + 2 skipped(无 key)。tsc rc=0。
2026-07-07 23:45: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 f8c3e16a52 feat(hub): 事件去重(A-4) + 审计写入路径(A-8)
A-4 FeishuEventReceipt:
- 新增 FeishuEventReceipt 表(eventId @unique),lark ws 至少一次投递的幂等兜底
- MessageReceiveEvent 加 header.event_id 字段
- trigger 入口查重:已存在的 event_id → 跳过(不建第二次 run)
- resetDb 加入 FeishuEventReceipt

A-8 审计写入:
- AuditEntry 加 projectId(权限拒绝/slash 命令等无 run 的审计点可定位)
- 新建 src/audit.ts:writeAudit(prisma, entry) best-effort 写入(吞错,不阻断 trigger)
- trigger 五个审计点:trigger.denied / trigger.role_denied / run.created / run.lock_race / run.finished / run.failed
- 新增 audit unit 测试(3) + trigger 集成测试(去重 + 审计,2)

59 tests 全过。
2026-07-07 21:34:15 +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 e60aa43731 test(hub): integration 测试套件(13 tests,真 prisma+真 cph)
三层 integration,对齐 unit 层:
- trigger.test.ts: @bot→run→lock→release→card 全链路;权限拒绝(READ
  不能 triggerAgent ADR-0004);竞态 busy(ADR-0002);未绑定 chat 忽略
  (ADR-0001);无 @bot 忽略 —— 5 tests
- lock.test.ts: acquire/release 回环;排他性(同 project 二次 acquire 抛);
  WellFormed(终止 run 持锁即抛);release 幂等;isTerminal 对齐 spec —— 5 tests
- cph.test.ts: 真 cph binary 跑 TH-141 examples 的 check(exit 0)+
  build student PDF(exit 0)+ 空目录 check(非 0)—— 3 tests
helpers: resetDb(truncate CASCADE)、mockFeishuRuntime(记录 sentTexts/
  sentCards)、MockProvider(fixed stop 响应)、seedProject(project→user→grant)
prisma migrate dev init 生成初始 migration 并应用到 cph_hub_test 库。
vitest config 改 fileParallelism=false(integration 共享一 DB,串行避免
truncate/insert 竞态)。
vitest run 35/35(22 unit + 13 integration)通过;tsc rc=0。
2026-07-07 15:40:28 +08:00