ops-desk
GitHub用于执行OPS Desk Sweep,将待决事项、草稿、发票等转化为可审批的动作包队列,实现收件箱清零。
触发场景
安装
npx skills add Lifecycle-Innovations-Limited/claude-ops --skill ops-desk -g -y
SKILL.md
Frontmatter
{
"name": "ops-desk",
"description": "OPS on-demand: This skill should be used when the user asks to \"desk sweep\", \"open decisions\", or…",
"allowed-tools": [
"Bash",
"Read",
"Grep",
"Glob",
"Skill",
"Agent",
"Workflow",
"AskUserQuestion",
"TeamCreate",
"SendMessage",
"TaskCreate",
"TaskUpdate",
"TaskList",
"CronCreate",
"mcp__gog__gmail_search",
"mcp__gog__gmail_read_thread",
"mcp__whatsapp__list_messages",
"mcp__whatsapp__send_message",
"mcp__whatsapp__archive_chat",
"mcp__linear__list_issues",
"mcp__linear__get_issue",
"mcp__linear__update_issue"
],
"argument-hint": "[scope: all|<board\/company>|explicit item list]"
}
OPS ► DESK SWEEP
Load ops-rules before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If AskUserQuestion / Workflow are missing, follow Rule 10 in ops-rules (Hermes: numbered options / two-turn Telegram card; delegate_task).
Goal: everything sitting on the owner's desk — pending decisions, staged drafts awaiting approval, unpaid invoices to chase, documents to sign, blocked issues needing an owner action — becomes a ranked queue of ready-to-approve action packages, then gets worked down one item at a time. The success metric is the same spirit as inbox-zero: an empty desk, with every remaining item either executed, decided, or parked with an explicit reason and a reminder.
When to use ops-desk vs ops-inbox
/ops:ops-inbox— fresh inbound comms (who wrote me, what needs a reply)./ops:ops-desk— the standing backlog (what am I blocking, what decision is waiting on me, what draft is staged, what must I sign/pay/approve). Run it after an inbox pass, or whenever the owner says "what's left / get this off my desk".
Step 1 — Build the item list (unless passed explicitly in ``)
- Task tracker (SSOT). Whichever the box uses, in order of preference:
- Paperclip —
paperclip issue list -C <company-id>for each company in${CLAUDE_PLUGIN_DATA_DIR}/preferences.json → paperclip.companies[], falling back topaperclip company list. Keepstatus=blocked|todo|in_progressissues whose title/description implies an OWNER action — matcher:decision,owner decision,approve,sign,pay,chase,staged,awaiting owner,Rule-6. - Linear —
mcp__linear__list_issuesfiltered to the owner as assignee, when Paperclip is absent. - GitHub —
gh issue list --assignee @meas last resort.
- Paperclip —
- Inbox residue. Genuine NEEDS_REPLY plus todo/action-labeled emails surviving the
most recent
/ops:ops-inboxpass (runbin/ops-inbox-scanwithOIS_NO_REFRESH=1if one already ran this session). - Dedupe against items already handled this session, and collapse duplicate issues tracking the same underlying ask (mirror boards, Linear↔Paperclip twins).
- Shape each item as
{key, refs: [issue identifiers], hint}wherehintstates the concrete question to answer AND what must be verified (never trust the issue title alone).
Step 2 — Run the gather workflow (READ-ONLY, batched)
Invoke the Workflow tool with the script below and args = [items]. Hard rules the
script encodes:
- Agents are READ-ONLY. No sends, archives, payments, signatures, mark-reads, or tracker mutations from inside the workflow — they read, verify, and prepare only. All execution happens later in the main session under the outbound-approval gate.
- Batched fan-out (3 at a time). A wide parallel burst can rate-limit (429) the model-account pool — pooled CLIProxyAPI setups especially. Batches of 3 finish nearly as fast and never starve the rest of the fleet.
- Verification over trust. Every load-bearing claim in a package must come from an
actual read (issue body, thread, sqlite row) with dates/ids cited in
status_summary. An item that turns out already handled comes back asaction_type=already_donewith evidence. - Partial failure → resume. If some agents die (rate limits, transient errors), fix
nothing and relaunch with
resumeFromRunId— completed agents replay from cache.
export const meta = {
name: 'desk-sweep',
description: 'Fan out read-only context agents over open decision/action items and return ready-to-approve action packages',
phases: [
{ title: 'Gather', detail: 'one read-only agent per item, batches of 3' },
{ title: 'Synthesize', detail: 'order into an approval queue: quick wins first' },
],
}
const ACTION_SCHEMA = {
type: 'object', additionalProperties: false,
required: ['key', 'title', 'status_summary', 'recommendation', 'action_type'],
properties: {
key: { type: 'string' },
title: { type: 'string' },
status_summary: { type: 'string', description: '2-4 sentences: what this is, current state, evidence (dates/ids)' },
recommendation: { type: 'string', description: 'single concrete next action with an approvable default' },
action_type: { type: 'string', enum: ['send_email', 'send_whatsapp', 'decision', 'sign', 'pay', 'chase', 'info_only', 'archive', 'already_done'] },
draft: { type: ['object', 'null'], additionalProperties: false, properties: {
channel: { type: 'string', enum: ['email', 'whatsapp'] },
to: { type: 'string' }, cc: { type: 'string' }, subject: { type: 'string' },
body: { type: 'string', description: "FULL final text in the owner's voice + thread language" },
reply_to_message_id: { type: 'string' },
}},
decision_options: { type: 'array', items: { type: 'string' } },
blockers: { type: 'array', items: { type: 'string' } },
effort_minutes: { type: 'number' },
confidence: { type: 'string', enum: ['high', 'medium', 'low'] },
},
}
phase('Gather')
const ITEMS = (typeof args === 'string' ? JSON.parse(args) : args) || []
if (!ITEMS.length) throw new Error('desk-sweep needs args: [{key, refs, hint}]')
const CHUNK = 3
const packages = []
for (let i = 0; i < ITEMS.length; i += CHUNK) {
const batch = ITEMS.slice(i, i + CHUNK)
const res = (await parallel(batch.map(it => () =>
agent(
`You are a READ-ONLY desk-sweep context agent. You MUST NOT send, archive, pay, sign, ` +
`mark-read, or mutate ANYTHING — no mail sends, no POSTs to send/archive endpoints, no ` +
`tracker mutations. Read, search, and prepare only.\n\n` +
`ITEM key="${it.key}": ${it.hint}\nReferences: ${JSON.stringify(it.refs || [])}\n\n` +
`Gather context read-only, every shell command with an explicit timeout ` +
`("timeout --signal=TERM --kill-after=5s 30s <cmd>"): the task tracker record(s) for each ` +
`ref incl. comments; Gmail via gog (search then thread get -j; messages nest under ` +
`.thread.messages); the WhatsApp bridge store read-only via sqlite3 (merge a person's @lid ` +
`and @s.whatsapp.net chats; resolve names via contacts); local files if referenced.\n\n` +
`RULES: verify every load-bearing claim with an actual read; if already handled, return ` +
`action_type=already_done with evidence. Drafts must be the FULL final text in the owner's ` +
`voice and the thread's language, no AI tone. Decisions get 2-4 concrete options, ` +
`recommended first. Your final output MUST be the structured action package.`,
{ label: `gather:${it.key}`, phase: 'Gather', schema: ACTION_SCHEMA }
)
))).filter(Boolean)
packages.push(...res)
log(`gathered ${packages.length}/${ITEMS.length}`)
}
phase('Synthesize')
return await agent(
`You are READ-ONLY. Order these action packages into an approval queue: quick wins first ` +
`(low effort, high confidence, drafts ready), then decisions, then heavy/blocked. Drop ` +
`nothing; flag conflicts between packages.\n\n` + JSON.stringify(packages, null, 2) +
`\n\nReturn {queue: [same packages verbatim + "rank"], conflicts: [], stats: {total, ` +
`ready_drafts, decisions, already_done}}.`,
{ label: 'synthesize', phase: 'Synthesize',
schema: { type: 'object', additionalProperties: true, required: ['queue'] } }
)
Step 3 — Work the approval queue (main session, outbound gate)
Per package, in rank order:
already_done/archive/info_only→ report in one line, archive/close immediately (tracker status → done). These are the free wins; do them without asking.send_email/send_whatsapp/chase→ PER-DRAFT APPROVAL exactly as in/ops:ops-inbox: ONEAskUserQuestionper draft, single-select[Send][Edit][Skip], thepreviewcarrying the FULL text plus a short "Reasoning / facts verified" block (≤10 short lines — split longer drafts). After[Send]: send (email viagog gmail send, WhatsApp via the bridge), then archive the thread and update the tracker. Where an out-of-band send token is enforced (e.g. an outbound-comms hook), remind the owner ONCE up front — not per item.decision→ present the options (recommended first) viaAskUserQuestion, record the outcome in the tracker, execute any follow-through it implies.sign/pay→ prepare only: surface the exact link/document/amount and what to check before signing/paying. NEVER sign or move money autonomously (Rule 5).
Step 4 — Wrap up
Report: sent / decided / closed / still-open-with-reason. Schedule reminders
(CronCreate, non-outbound) for anything parked with a follow-up horizon. The task
tracker is the SSOT — every touched item gets its status updated there before the
summary prints.
Agent Teams support
If CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is set, use Agent Teams when fanning out
the gather workflow across independent item sources (Paperclip per company, Linear, GitHub,
Gmail/WhatsApp residue, etc.) in parallel. This enables:
- Gather agents share context mid-flight (one surfaces a high-priority payment → the ranker can prioritize before the desk-sweep plan is emitted)
- You can steer scope in real time ("skip Linear, focus on Paperclip only")
- Agents report progress as each gather round completes, so the main session can start the approval-queue step earlier on the items already returned
Team setup (only when the flag is enabled, and only for genuinely parallel sources — a single-source desk sweep stays inline):
TeamCreate("ops-desk")
Agent(team_name="ops-desk", name="paperclip-gather", prompt="Gather owner-action items across all configured Paperclip companies")
Agent(team_name="ops-desk", name="linear-gather", prompt="Gather Linear items assigned to the owner")
Agent(team_name="ops-desk", name="inbox-residue", prompt="Gmail/WhatsApp NEEDS_REPLY + todo-labeled items from the last inbox pass")
Agent(team_name="ops-desk", name="github-chases", prompt="Open PRs awaiting owner review, blocked issues")
After the team reports, the main session runs Step 3 (work the approval queue) on
the merged, ranked list. Each SendMessage from a gather agent is a structured action
package — never an outbound send (Rule 6: per-draft outbound approval gate stays in
the main session, not in the team).
If the flag is NOT set, fall back to standard fire-and-forget Agent subagents (one
per source) — same shape, slower wall-clock because no shared context.
版本历史
-
6f1e3e0
当前 2026-08-27 09:31
优化技能发现上下文加载性能,修复文件竞态条件及缺失时的错误处理,修正市场发布标识。
-
5751870
2026-08-19 11:47
移除 CRS 中继模块,使 CLIProxyAPI 成为唯一多账户路径;重命名相关配置和状态文件以适配新架构。
- 64bad13 2026-08-12 09:00


