実際の製品を操作して、利用者が確認できる動作証拠を集め、検証計画に沿った受け入れレポートとして公開するスキルです。
- 変更後の製品動作を実地で検証したいとき
- 画面キャプチャなどの証拠を集めたいとき
- 検証計画とレポートを作成したいとき
エージェントが作成・更新した課題やファイルなどをWorkとして登録し、重複を防ぎながらカード表示や履歴、CLI結果の検出につなげる仕組みを扱います。
Use for the Agent Work output registry: registration, deduplication, cards, skill providers, CLI scanners and Work types.
実行スコア 未検証
インストール方法を見るエージェントが作成・編集したPR、課題、文書、ファイルなどをWorkとして記録し、会話内のカードや履歴につなぐ仕組みを扱います。同じリソースは種類とIDで識別し、登録イベントはツール呼び出しIDで重複を防ぎます。GitHubやLinearなどのスキル結果を正規化する方法、CLI実行のコマンドと出力から作成・編集結果を検出するスキャナー、ファイル・タスクの登録経路、カード表示位置の付与も説明します。共有会話では所有者だけでなく共有範囲を含むアクセス境界を設け、対応していない古いクライアントに新しいWork種別を返さないための仕組みも扱います。
エージェントの操作結果を後から一覧・追跡できるようにしたい開発者向けです。新しいツール提供元やCLIスキャナー、カード種別を追加するとき、重複登録や共有範囲、削除済みリソースの表示を検討する場面で役立ちます。
特記事項はありません。
この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。
SKILL.md の記述から、注意が要る操作と導入の前提を AI が確認しました。点数は実際に動かした結果(実行検証)だけで付けています。
点数には入れていません。 2026年10月26日 時点で AI(OpenAI Codex)が判定しました。目安なので、入れる前に原文も確認してください。
インストールする前に、エージェントに与えられる指示の中身を確認できます。
A Work is a durable record of something an agent produced or touched — a GitHub PR/issue, a Linear issue, an entity file (pptx/xlsx/docx/pdf), a document, a task. Works render as cards under the assistant message that produced them and accumulate versions across operations, so the same PR edited twice shows one card with history.
Two tables (packages/database/src/schemas/work.ts):
works — one row per resource. Identity/dedup key: (resourceType, resourceId) within the user/workspace scope and the share scope (see below). currentVersionId soft-references the latest version.work_versions — one row per registration event. Dedup: unique (workId, toolCallId), so a retried registration with the same real tool call id is a no-op. Versions carry provenance (source message, producing tool) and operation-level cumulativeCost / cumulativeUsage.packages/database/src/models/work/registry.ts is the single registry of Work types (document / external / file / task): WORK_TYPE_ADAPTERS is satisfies Record<WorkType, WorkTypeAdapter>, so a type added to @lobechat/types without an adapter is a compile error.
Read-path compatibility gate: OPT_IN_WORK_TYPES (currently {'file'}) hides newer types from clients that did not opt in (includeFileWorks). Released Electron clients lag by weeks and crash on unknown type descriptors (descriptor.getIcon on undefined), so a request without the opt-in receives exactly the pre-file set. Any NEW Work type must ship behind the same kind of opt-in.
The gate has two kinds of emission points, and both must honor the client's declaration:
message.getMessages, work.* lists, shareChat.getMessages): the client service passes includeFileWorks: true on the request.uiMessages snapshots and message_patch revisions built by AgentRuntimeService.queryUiMessages): no request carries the opt-in, so the starting client declares the file_works stream feature and the run stores it as state.host.acceptsFileWorks; approval continuations and group members inherit it. A pushed path that skips this check drops the card until the user reloads the page.A share visitor's run executes as the creator, so owner scope alone cannot separate its Works. Every Work read and write takes a WorkAccessScope (packages/types/src/work.ts): ordinary sees only rows without share provenance; agentShare sees only rows stamped in works.metadata.agentShare with exactly that share/topic/visitor.
workOwnership applies the scope on every path, so a new query built on it inherits the fence. Do not query works without it.WorkModel with the run's scope via resolveRunWorkAccessScope. A visitor run without a topic yields null: skip registration (fail closed) rather than registering unscoped into the creator's lists.userId / workspaceId and gate spend on showModelInfo (toVisitorMessage).works.resourceId has no FK, and deleting a document, task, or file never cascades into its Work (LOBE-13917). Deleting the Work would also drop its version history and its project and goal references. Each read surface handles orphans itself:
listByWorkspace surfaces: /resource, /resource/works, and the project dashboard. They filter orphans out with resourceLiveFilter (packages/database/src/models/work/internal.ts). A task or document must still exist and not be trashed. A file Work's current-version fileId must point to a files row that exists and is not trashed. Restoring the resource brings the card back. Any new gallery-style Work list must apply the same filter.listByConversation and the cards under messages; the conversation did produce the Work. resourceDeleted drives a click-time prompt (useResourceDeletedPrompt) that lets the user remove it.Four write paths, two timing classes:
| Path | When | Where |
|---|---|---|
Skill structured tools + sandbox runCommand (github / linear) | at tool execution | WorkModel.handleSkillToolResult, called from server toolExecution and the client executor (registerClientWorkFromIntent.ts on the legacy non-gateway runtime) |
Shell Work scan (hetero codex/claude-code + device lobe-local-system) | at operation completion | apps/server/src/services/workRegistration/shellWorkRegistration.ts via registerWorksForOperation |
| File Work scan (sandbox entity files) | at operation completion | apps/server/src/services/workRegistration/registerWorksForOperation.ts |
| Task / document works | at creation by their owning feature | WorkModel.registerTask / registerDocument |
SKILL_TOOL_RESULT_NORMALIZERS in packages/database/src/models/work/index.ts maps WorkSkillProvider (github, linear; vocabulary in packages/types/src/work.ts) to a normalizer. Both satisfies a Record<WorkSkillProvider, …>, so provider list and normalizer map cannot drift.
A normalizer turns one tool result into ExternalToolWorkOperation | null — null means "not Work-worthy", which is the common case and never an error.
Heterogeneous CLI agents (codex, claude-code) and the device lobe-local-system tool run CLIs like gh through their own shell surfaces, which never pass the skill-tool hook. registerWorksForOperation recovers their Works at completion from the persisted command text + stdout. Layering:
registerWorksForOperation (registerWorksForOperation.ts)
├─ collectOperationRecords one pass over the operation tree, shared by both scans
├─ registerShellWorks (shellWorkRegistration.ts — the ENGINE, command-agnostic)
│ ├─ SHELL_COMMAND_SOURCES identifier→apiName scoping of shell surfaces
│ ├─ success gate plugin error / state.success===false / state.error → skip
│ ├─ extract {command, exitCode, output} from arguments / state / message content
│ └─ shellWorkScanners/ one file per CLI family
│ ├─ types.ts ShellWorkScanner = { matches, name, register }
│ ├─ github.ts matches: includes('gh ') → workModel.registerShellGithubResult
│ └─ index.ts SHELL_WORK_SCANNERS registry
└─ file Work scan aggregate per-path fold + sandbox export pipeline (NOT a scanner)
Command parsing is split the same way: packages/database/src/models/work/shellCommandParsing.ts holds the command-agnostic layer (POSIX-ish tokenizer, control-operator segmenting, codex /bin/zsh -lc wrapper expansion — parseShellCommandSegments), while githubToolResult.ts holds only the gh-specific parseGhSegment + field mapping.
cumulativeCost / cumulativeUsage are per-version SNAPSHOTS ("spend up to this version"), never deltas — summing them across versions double-counts on every path.
UsageCounter.accumulateTool for that call, so create + edit + edit within one run carries an INCREASING series (e.g. $0.30 → $0.70 → $1.20) and step deltas are recoverable.Why file Works are not a scanner: their unit is a file path folded across ALL records (multi-edit merge, renames, last-edit provenance), the registration is a heavy IO pipeline (idempotency probe → sandbox export → upload → version → redeploy, bounded concurrency), hetero records are deliberately EXCLUDED (the file lives on the executing device, not the exportable sandbox), and the version key is the synthetic op:${operationId} instead of a real toolCallId. Shoehorning that into ShellWorkScanner would break the interface. If a second aggregate-style scan ever appears, extract an aggregate layer then.
A Works card renders only under an assistant message stamped with metadata.work.rootOperationId (the "anchor"). Who stamps it:
callLlmFinalizer stamps the round's final assistant message.registerWorksForOperation stamps the anchor itself when the shell scan registered anything — using the completion's assistantMessageId, falling back to the parentId of the last registered tool message (hetero single-step runs persist no final-assistant pointer at all; see heteroFinish).The whole scan returns {attempted, failed} and the completion backstop (CompletionLifecycle) writes its idempotency marker only when failed === 0. Anything that must be retried — a thrown registration, a {success: false} anchor stamp, an unresolvable anchor — counts into failed. Registration is retry-safe end to end (probe short-circuits, (workId, toolCallId) guard backs it up), so counting a failure is always the right move.
gh pr view printing an entity URL must NOT register.owner/repo#number is the canonical github identity, not node_id: the gh CLI surface never returns node_id, and the same entity touched via REST tools and CLI must land on one Work row.gh … create/edit prints the entity URL); the command's edit target is the fallback. The LAST gh create/edit segment of a chained command owns the trailing URL.sanitizeExternalUrl): gh stdout / tool results are member-controlled and the URL reaches shell.openExternal on desktop.shell-quote dep): a real parser would expand what must stay literal, and the worst failure mode is skipping a bookkeeping registration.gh create that succeeds before a later terminal failure registers no Work that round. Accepted — bounded cost, missing card only.agent_runtime_end publishes before completeOperation), so a hetero-created card appears on the next refetch, not in the terminal snapshot. Accepted for now.New skill provider (structured tools), e.g. Notion:
packages/types/src/work.ts — extend WORK_SKILL_PROVIDERS + WORK_PROVIDER_RESOURCE_TYPES (+ resource-type unions).packages/database/src/models/work/<provider>ToolResult.ts — write the normalizer; mirror linearToolResult.ts.packages/database/src/models/work/index.ts — add it to SKILL_TOOL_RESULT_NORMALIZERS (the satisfies forces this).resourceId (the github lesson: choose the identity every surface can produce).New shell CLI scanner, e.g. linear CLI or vercel deploy:
packages/database/src/models/work/ reusing parseShellCommandSegments, plus a WorkModel.registerShell<X>Result facade method.apps/server/src/services/workRegistration/shellWorkScanners/<name>.ts with { matches, name, register } — matches stays a cheap substring check.SHELL_WORK_SCANNERS in shellWorkScanners/index.ts. The engine needs zero changes.packages/database/src/models/work/__tests__/ (wrapper unwrap, chained segments, failed/read-only commands must not register).New Work type (new card kind):
@lobechat/types — extend WorkType and related unions.packages/database/src/models/work/ — new adapter module + WORK_TYPE_ADAPTERS entry (compile-enforced).OPT_IN_WORK_TYPES pattern) — released clients throw on unknown types; never ship a new type into the legacy result set.| Concern | Path |
|---|---|
| Schema | packages/database/src/schemas/work.ts |
| Type registry + read gate | packages/database/src/models/work/registry.ts |
| WorkModel facade + provider normalizer map | packages/database/src/models/work/index.ts |
| gh CLI + structured github normalizer | packages/database/src/models/work/githubToolResult.ts |
| Command-agnostic shell parsing | packages/database/src/models/work/shellCommandParsing.ts |
| Completion scan orchestrator (file + shell + anchor) | apps/server/src/services/workRegistration/registerWorksForOperation.ts |
| Shell scan engine | apps/server/src/services/workRegistration/shellWorkRegistration.ts |
| Desktop-local run scan (client-reported) | apps/server/src/services/workRegistration/localRunWorkRegistration.ts |
| Per-CLI scanners | apps/server/src/services/workRegistration/shellWorkScanners/ |
| Client (non-gateway) registration | src/store/chat/agents/registerClientWorkFromIntent.ts |
まだレビューはありません。使ってみた感想をお寄せください。
ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。
まだコメントはありません。最初のコメントを書いてみませんか。
実際の製品を操作して、利用者が確認できる動作証拠を集め、検証計画に沿った受け入れレポートとして公開するスキルです。
LobeHubにAIモデルプロバイダーを追加し、実行時連携からモデル表示、設定、ブランド表記、文書化、動作確認まで進めるスキルです。
ユーザー設定の初期値をサーバー側の環境変数で切り替えるため、型定義や設定の組み立て、ユーザーストアへの反映、環境変数の例と説明書きを追加する手順です。
エージェント実行中の各段階をフックで監視し、ツール呼び出しの模擬・制御や人の承認、圧縮、サブエージェント処理に介入する仕組みを扱います。
イベントを起点にエージェントのバックグラウンド処理を組み立て、ソース・シグナル・アクション・ポリシーの役割分担や非同期連携を実装するためのスキルです。
DiscordやSlackなどの実際のチャットアプリでボットにメッセージを送り、応答を画面で確認する受け入れテストを行うスキルです。