ops-desk
GitHub用于自动化处理待办积压任务,将决策、草稿、发票等整理为可批准的动作队列,实现类似收件箱清零的办公桌面管理。
Trigger Scenarios
Install
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__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/info_only→ report in one line and close the item in the task tracker (status → done). This is tracker-only closure: no mailbox, chat or file is archived, moved or marked read. These are the free wins.archive→ a package from a read-only agent is a recommendation, not owner authorization. Show the item and the evidence, then archive it only after explicit owner authorization for that item (e.g. viaAskUserQuestion). Without it, leave the thread where it is and report the item as still open.send_email/send_whatsapp/chase→ exactly as in/ops:ops-inboxandops-rulesRule 6: one draft, one approval, one send, only through the host's approved outbound gate. The gate shows the exact recipient and full latest text; approval is the owner's native yes bound to those bytes, never a token, counter or earlier approval. Re-read the live thread before sending, verify delivery on a fresh read, then update the tracker. Archive only under explicit archive authorization. If the gate refuses or does not support the channel, keep the item owned with the exact diagnostic; never use a direct CLI, bridge or API sender.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.
Version History
-
6ac2237
Current 2026-10-18 17:53
合并本地维护修复与发布安全加固,完善收件箱上下文及证明合同,修复审查缺口。
-
6f1e3e0
2026-08-27 09:31
优化技能发现上下文加载性能,修复文件竞态条件及缺失时的错误处理,修正市场发布标识。
-
5751870
2026-08-19 11:47
移除 CRS 中继模块,使 CLIProxyAPI 成为唯一多账户路径;重命名相关配置和状态文件以适配新架构。
- 64bad13 2026-08-12 09:00


