Skip to content

feat(weekly-report): bundled cron-triggered Feishu weekly-report plugin#2

Merged
Desicool merged 9 commits into
mainfrom
add-weekly-report-plugin
Jun 1, 2026
Merged

feat(weekly-report): bundled cron-triggered Feishu weekly-report plugin#2
Desicool merged 9 commits into
mainfrom
add-weekly-report-plugin

Conversation

@Desicool

Copy link
Copy Markdown
Owner

Summary

Adds the bundled weekly-report plugin (extensions/weekly-report/): a cron-triggered Feishu/Lark weekly-report flow. A scheduled systemEvent fires begin_weekly_report, the agent drafts a first-person report from three fact sources (session messages, git activity, recent group messages), delivers it as a CardKit form card, and on confirm writes it non-destructively to a Lark doc.

Key pieces:

  • Non-destructive doc write (src/doc-writer.ts) — official lark-cli docs +update block ops. Fetches the outline, replaces the same-ISO-week ## <week_title> H2 section in place (or inserts at the document head on first run), and cleans up legacy sentinel comments. Replaces the old destructive mode:overwrite path that clobbered images/comments on every write.
  • Doc title bump — after a successful write, bumps the doc's (updated at MMDD) marker to today's date.
  • Plugin-owned drafting contract (src/drafting-contract.ts) — ships a first-person ("我"/I), facts-not-chat-recap drafting contract so a colleague can install the plugin and add a one-line cron trigger without hand-authoring a prompt.
  • Card callback fix — the confirm handler acks Feishu's ~3s card callback immediately (info toast) and runs the doc write detached, so the write (which exceeds the ack deadline) no longer logs error 200530 "callback timeout".
  • begin_weekly_report kickoff tool + contracts.tools manifest entry; CardKit form-card delivery (src/card-sender.ts); managed-flow timeout sweeper.

Verification

  • node scripts/run-vitest.mjs extensions/weekly-report — green (149 tests).
  • pnpm tsgo:extensions && pnpm tsgo:extensions:test — clean.
  • oxlint + oxfmt clean on touched files.
  • esbuild bundle (dist/index.js) rebuilt and committed.

Real behavior proof

  • Behavior addressed: non-destructive top-insert doc write, same-week replace, doc-title date bump, and the card-callback-timeout fix on a live gateway.
  • Real environment tested: prod OpenClaw gateway node instance-1n7ut600-25 (10.0.3.84), deployed via openclaw plugins install --force + systemctl --user restart.
  • Exact steps or command run after this patch: triggered the weekly-report flow; confirmed the card; re-confirmed the same week; inspected the target Lark doc and gateway logs.
  • Evidence after fix: new week section appears at the top with all prior weeks/images/comments intact; re-confirm replaces (does not duplicate) the same-week section; doc title marker bumped (e.g. 05240530); after the detached-write change the gateway no longer logs 200530, and [gateway] http server listening (… weekly-report) / ready with NRestarts=0, all 6 crons intact.
  • Observed result after fix: confirm → doc updated non-destructively, title bumped, card acked within deadline.
  • What was not tested: automated end-to-end CI for the Lark CLI path (host CLI auth is environment-specific and unverifiable from the dev checkout); the --as user doc identity (this node runs --as bot with the doc shared to the bot).

🤖 Generated with Claude Code

actions-user and others added 9 commits May 24, 2026 16:52
Direct 'git fetch origin main:refs/heads/main' fails when main is the
currently checked-out branch ('refusing to fetch into branch X checked
out at Y'). Update remote-tracking ref via plain 'git fetch origin main'
and push origin/main directly.

Signed-off-by: Chiliang Yao <[email protected]>
Co-Authored-By: Claude Opus 4.7 <[email protected]>
…ort flow

Cron-triggered TaskFlow extension that drafts a weekly report from the agent's
own session transcript, sends a Feishu confirmation card, loops on user
supplements, and writes the result into a configured Feishu doc using invisible
HTML-comment sentinels for idempotent splicing.

v2 adds an optional git-activity fact source: a clone-on-demand, author-filtered
`git log` runner exposed as `fetch_git_activity`, so completed bullets are
anchored in real commits. SSH-only by default; URL allowlist, name regex,
argv-only invocation, hooks/submodules/file-protocol disabled, per-op and
overall timeout caps, concurrency-bounded parallel ops, and offline tests with
local bare-repo fixtures.

Five tools: submit_weekly_report_draft, respond_to_weekly_report_card,
splice_weekly_report_doc, finalize_weekly_report, fetch_git_activity. CAS-
idempotent timeout sweeper for reminders + fail-on-expiry. Card-action trust
guards (controllerId / sessionKey / status / weekKey match before any flow
mutation). 84 colocated vitest cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…w JSON reply

The submit_weekly_report_draft tool was returning a Feishu card JSON in its
response and instructing the agent to "send it via the appropriate Feishu card
delivery mechanism". With no concrete tool named, the agent dumped the card
JSON as a plain-text reply — Feishu rendered it as a code block instead of an
interactive card.

Fix: the tool now returns previewMarkdown + question shape + explicit
instructions to call `feishu_ask_user_question` (the @larksuite/openclaw-lark
plugin's purpose-built tool for interactive card prompts). The user's selection
arrives as a synthetic message via that tool's existing callback machinery, so
respond_to_weekly_report_card no longer needs to parse a card-action envelope.

Tool surface changes:
- submit_weekly_report_draft: drops `card` from response, adds previewMarkdown,
  questionHeader, confirmLabel, supplementLabel, and explicit two-step
  instructions naming feishu_ask_user_question.
- respond_to_weekly_report_card: parameters changed from {metadataJson,
  supplement, sessionKey} to {flowId, weekKey, action, supplement?, sessionKey}
  — the agent constructs these directly from its memory + the user's reply,
  not from a card envelope.
- prompt.ts: drafting prompt rewritten to make the
  submit_weekly_report_draft → feishu_ask_user_question → respond_to_weekly_report_card
  chain explicit, and to forbid raw-text replies of the preview.

The card.ts envelope codec and card-action-handler.ts envelope parser remain
for the future raw-card path but are no longer in the critical flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ect tool description

Two related doc fixes that bit a real deployment:

1. README "Cron entry" now has a callout block warning that `payload.kind:
   "agentTurn"` (the alternative to systemEvent) triggers the cron runner's
   hardcoded 60 s isolated-agent setup watchdog. On hosts with several plugins,
   setup routinely exceeds that budget and the job fails with
   "cron: isolated agent setup timed out before runner start" before our tool
   ever runs. Also calls out the `payload.text` vs `payload.message` field-name
   mismatch between systemEvent and agentTurn, which is the obvious migration
   trap.

2. submit_weekly_report_draft tool description was still saying "Returns the
   card JSON to send to the user" — left over from the pre-fix shape. The
   agent reads this description first and sets its mental model from it, then
   ignores conflicting instructions in the response body. Rewritten to spell
   out the actual return shape and instruct the agent to call
   `feishu_ask_user_question` for delivery.

Also brought the README Tools section up to date: now lists all five tools
(was four), includes `fetch_git_activity`, drops the stale
"returns card JSON" / "card JSON is returned to the agent" sentences.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ing fact source

Adds `fetch_recent_group_messages` as a sixth tool. Together with the existing
`getSessionMessages` (DM transcript) and `fetch_git_activity` (commits), the
drafting agent now sees the user's contributions across Feishu group chats —
own messages + @-mentions + threads they participated in.

Why: real weekly reports also capture group-chat status updates / decisions /
announcements, which the chat-history-only collector misses.

How:
- New `runGroupActivity` runner enumerates the agent's sessions via
  `runtime.agent.session.listSessionEntries` (precedent: extensions/active-memory),
  filters for `:feishu:group:`, drops stale (lastInteractionAt /
  updatedAt > groupStaleAfterDays), drops denylisted (full sessionKey or bare
  chatId), optionally collapses Feishu topic-group sub-sessions by groupId.
- Per group: `runtime.subagent.getSessionMessages` (cross-session reads are
  unguarded server-side per src/gateway/server-methods/sessions.ts:2169-2200),
  message-shape introspection probes common field names (the gateway returns
  `unknown[]`), time-window + author/mention/thread-participation classify.
- Partial-trace drop: messages with thread metadata but no user thread root
  match are dropped conservatively (avoids false positives).
- userOpenId auto-derived from recipientSessionKey's `:direct:<openid>` suffix
  with `^ou_[A-Za-z0-9_-]{8,}$` validation; explicit override via config.
  Unresolved userOpenId → single ok:false entry, drafting continues without
  group data (does not throw at parse).
- botOpenId optional config to exclude bot self-messages; no auto-derivation
  (appId != open_id; would require a Feishu contacts API call we don't make).
- Bounded parallelism (groupMaxParallelOps=5), per-group budget enforcement,
  overall deadline (groupOverallTimeoutMs=60s) with budget_exhausted reason,
  safety cap (groupMaxGroupsScanned=50).
- Drafting prompt rewritten to enumerate THREE parallel data sources with
  failure-transparency rule applied to all three.

Tests: 119 (was 86). Offline group-activity coverage spans empty, filter,
denylist, staleness, author/mention/thread classification, time window,
budget_exhausted, per-group failure isolation, includeReasons subset,
topicGroups=exclude dedupe, threadFilterAvailable propagation, derivation
edge cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…erson contract

- Replace destructive larkcli 'doc update --mode overwrite' (cleared the whole doc) with
  block-level insert at the document head via official lark-cli 'docs +update' (doc-writer.ts):
  insert this week's section at the top, replace only the same-week section, preserve everything
  else. Inline content (lark-cli rejects --content @file and --format on +update); no move step
  (block_insert_after --block-id <page_id> already lands at the head).
- Add begin_weekly_report tool returning a first-person, facts-only drafting contract owned by
  the plugin (drafting-contract.ts), so the cron is a one-line trigger and behaviour is shareable.
- Hard-gate respond_to_weekly_report_card out of the supplement re-draft sub-session so a revision
  can only submit via submit_weekly_report_draft (types.ts isWeeklyReportSupplementSession + index.ts).
- Add docIdentity (user/bot) config for the official lark-cli doc ops; retire doc-splicer/sentinels
  and the host-owned prompt; README rewritten; .oxfmtrc ignores the golden markdown fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…r writing

After the section write, read the doc title (drive +inspect) and, if it carries an
'(updated at MMDD)' / '(updated at MMDD)' marker (full- or half-width parens), patch it to
today via 'drive files patch'. Opt-in by marker (titles without it are left untouched, safe for
other deployments) and best-effort (a rename hiccup never fails the report — surfaced as a
titleNote / ⚠️ line in the DM reply). Adds formatMMDD / nextDocTitle / parseInspectTitle helpers
+ tests; WriteWeeklySectionResult gains an optional titleNote; now-injectable date for testing.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Feishu's card callback has a ~3s ack deadline; the doc write (fetch outline →
delete same-week section → insert at head → bump title) routinely exceeds it,
so the gateway logged error 200530 'callback timeout' even though the write
succeeded. Ack the callback synchronously with an info toast and run the
write/finish/fail/reply inside a detached task so the deadline is always met.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants