153 Commits

Author SHA1 Message Date
hongjr03 2dc125aec1 docs: chart SaaS production readiness 2026-07-10 02:34:30 +08:00
hongjr03 b0eedfab05 test(cph-model): isolate version fixtures 2026-07-10 02:33:12 +08:00
hongjr03 008c8bcd09 feat(hub): add org sessions and usage admin APIs
List project agent sessions/runs and aggregate AgentRun cost and token
usage by project and optional folder for org admins.
2026-07-10 00:55:43 +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 491fd13ca8 fix: resolve Feishu sender names via basic profile API 2026-07-08 23:59:17 +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 a0df8b6bcf fix: repair streaming output broken by markdown rendering changes
Two bugs were introduced when sendTextMessage was changed to auto-detect
message format (text/post/interactive):

1. Message type mismatch: sendTextMessage could create a text or post
   message, but patchTextMessage only works on interactive cards.
   Fix: add sendInteractiveCardMessage for streaming create (always
   interactive, always patchable).

2. flushTextToSink state corruption: the text management after flush
   had a flawed appendedDuringFlush heuristic that could lose text or
   duplicate it. Fix: properly track sent vs unsent text by removing
   the sent prefix length, keeping only the last chunk for future patches.

Also fix finish() to flush pending text regardless of currentMessageId.
2026-07-08 17:48:55 +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 4479f88f4f feat: add sender profile cache with LRU eviction
- SenderNameCache: TTL 10min, LRU max 2048 entries, getOrFetch pattern
- resolveSenderName: API call with cache integration
- Trigger uses cache for approval card resolutions and audit metadata
- Add unit tests (9) for cache lifecycle, LRU, expiry, and null handling
2026-07-08 17:22:23 +08:00
hongjr03 2736006262 feat: add retry with exponential backoff for file send/download
- withRetry helper: configurable maxAttempts, baseDelayMs, shouldRetry
- sendFile: upload and message send wrapped with 3 retries, 1s/2s/4s backoff
- downloadMessageFile: wrapped with 3 retries
- Add unit tests (6) for retry behavior and sendFile retry integration
2026-07-08 17:15:30 +08:00
hongjr03 294d51dbfe feat: parse inbound post (rich text) messages
- parsePostMessage: full Feishu post payload parser with locale fallback,
  supports text/a/at/img/media/file/code_block/br/hr/emotion elements
- Trigger accepts post messages, extracts text + downloads attachments
- Post messages with attachments bypass batching, text-only posts batch
- Add unit tests (10) for all element types and edge cases
2026-07-08 17:05:51 +08:00
hongjr03 bef10149a9 feat: improve outbound message chunking with code-block-aware splitting
- Increase max message length from 4000 to 8000 chars
- Add splitAtBoundary: paragraph > newline > space priority, never split
  inside fenced code blocks (move split to before opening fence)
- Add sendLongText: sends multi-chunk long messages sequentially
- Update PatchableTextStream.flush to use splitAtBoundary
- Add unit tests (6) for splitting edge cases
2026-07-08 16:57:53 +08:00
hongjr03 4736610cf3 feat: add interactive approval/confirmation cards
- sendApprovalCard: interactive card with configurable buttons
- resolveCard: patch card to show resolution state (green/red)
- ApprovalManager: register/resolve lifecycle with 5min timeout
- request_approval MCP tool: agent can ask user for confirmation
- Wire onCardAction in trigger handler and server startup
- Add unit tests (5) for card JSON, manager, and routing
2026-07-08 16:47:31 +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 6227e1931b feat: improve Feishu markdown rendering and processing status feedback
- Add buildOutboundPayload: detect markdown format, isolate code blocks,
  force plain text for tables, fallback from post to text on API rejection
- Add addReaction/removeReaction for processing status lifecycle
- Replace THUMBSUP with Typing→(remove on success | CrossMark on failure)
- Add unit tests for markdown rendering (15) and reactions (6)
2026-07-08 16:31:07 +08:00
hongjr03 a00e4f9871 fix(hub): 保留飞书回复上下文 2026-07-08 16:03:52 +08:00
hongjr03 a025d23af8 fix(hub): 启动前应用 Prisma 迁移 2026-07-08 15:45:12 +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 a766d99573 fix(hub): 修复 Hub 部署脚本失败处理 2026-07-08 15:14:23 +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 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 5b7fd70124 spec: 钉死 agent 执行面边界(ADR-0018)
ADR-0001/0002/0004 覆盖协作治理到 triggerAgent,但触发后 agent 在执行层
能干什么——读哪些文件、跑什么命令——无 ADR / spec 覆盖。ADR-0017 落地时
采用 bypassPermissions + 全量内置工具,agent 文件/shell 面对宿主无界;
workspace.ts 的 confine() 沙箱意图存在但被 ADR-0017 静默覆盖成死代码。

新增 ADR-0018 + Spec.System.AgentSurface 模块补这一层:
- AgentFileOp(run × path)+ Authorized 谓词(路径必须落在 run 工作区内)
- 与 Lock 正交:Lock 限定并发,Surface 限定波及面,都按 run × project 作用域
- 机制 OPEN(工具包装 / OS 沙箱 / SDK 钩子),契约只钉不变式
- ADR-0017 的 bypassPermissions + workspace.ts 死代码标为偏离契约,对齐为 follow-up
2026-07-08 12:37:37 +08:00
hongjr03 6178664696 fix(hub): send Feishu files explicitly 2026-07-08 11:48:25 +08:00
hongjr03 c73b41e1da fix(hub): resume Claude SDK sessions 2026-07-08 11:48:20 +08:00
hongjr03 012c8a7d89 fix(hub): default to OpenRouter Sonnet model 2026-07-08 11:48:16 +08:00
hongjr03 250f918346 chore: delete dead code 2026-07-08 02:37:44 +08:00
hongjr03 30d20421b3 fix(hub): busy 表情从 Hourglass 改为 OnIt(飞书支持的 emoji_type) 2026-07-08 02:22:05 +08:00
hongjr03 20bf7e271f fix(hub): 启动重置状态 + lazy-init 修复空白消息 + busy 改表情
三个修复:
1. 启动时清锁+标死run为FAILED(防重启后卡死)
2. 空白消息修复:lazy-init 改为第一个 text-delta 立即创建消息
  (不走 throttle);.then 加 fallback:没收到 text-delta 时用
  result.text 发完整回复
3. busy 提示从文本'项目正在处理中'改为表情回复

tsc rc=0。
2026-07-08 02:20:49 +08:00
hongjr03 eaa7fd4fd8 feat(hub): 文件收发 + 卡片回调 + 更好的流式
client.ts 重写:
- downloadMessageFile: 下载飞书消息里的文件/图片到本地
- sendFile: 上传文件到飞书 + 发送文件消息(msg_type=file)
- CardActionEvent 类型 + startFeishuListenerWithClient 加 onCardAction
  回调参数,注册 card.action.trigger 事件(消除了 'no card.action.trigger
  handle' 警告)
- 去掉未使用的 card builder 函数

trigger.ts:
- 处理 file/image 消息:下载到 workspace/.cph/inbox/,prompt 里告诉
  agent 文件路径
- 流式 throttle 从 800ms 降到 400ms,初始占位从 '…' 改为空(更干净)
- run 完成后扫描 workspace/build/ 下 5 分钟内的新文件,自动发送给
  老师(cph build 产出的 PDF 自动回传)

tsc rc=0。
2026-07-08 02:13:51 +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 085f7c7186 fix(hub): 去掉冗余 OPENROUTER_API_KEY,直接用 ANTHROPIC_AUTH_TOKEN 2026-07-08 01:48:01 +08:00
hongjr03 b9f50407be fix(hub): Claude Code SDK 经 OpenRouter Anthropic Skin,保留 provider-agnostic
我之前判断错了——OpenRouter 有 Anthropic Messages API 兼容端点
(https://openrouter.ai/api),Claude Code SDK 直连不需要代理。
ANTHROPIC_AUTH_TOKEN=OpenRouter key,ANTHROPIC_API_KEY='' 即可。

而且 ANTHROPIC_DEFAULT_SONNET_MODEL 可设 OpenRouter model ID
(z-ai/glm-4.7 等),所以 provider-agnostic 没丢。

ADR-0017 重写:从'Anthropic 专有'改为'Claude Code SDK via OpenRouter,
保留 provider-agnostic'。tsc rc=0。
2026-07-08 01:46:09 +08:00
hongjr03 082562ca08 docs(adr): ADR-0017 重写——从 provider-agnostic 改为 Claude Code SDK
放弃 provider-agnostic,用 @anthropic-ai/claude-agent-sdk。代价:不能走
OpenRouter(GLM 等),需 Anthropic API key。收益:compaction、工具审批、
partial message 流式、Claude-to-IM 原生兼容。迁移路径:如果将来需要
provider-agnostic,用 streamText + SSE 转换层(~100 行)替回 query()。
2026-07-08 01:40:28 +08:00