Goal
Create artifact-handoff, the final standalone App in this roadmap: a human-facing Artifact Handoff Canvas App paired with an independently installable OpenHands Plugin. Together they prove an SDLC workflow in which agent-produced handoffs, prototypes, plans, reports, and other durable outputs can be registered in an App-owned artifact store, browsed later, and reused as the starting context for a new conversation.
This must not require any change to OpenHands or introduce a repository-wide shared foundation.
Depends on
Issue #10 is complete. Keep the App, Plugin, data contract, helper script, build, tests, and docs inside this one top-level project. Existing Apps may be studied or copied, but not changed.
Research and product decisions
The implementation should adapt these proven local patterns:
- The existing personal
handoff skill produces a compact Markdown continuation document, references existing artifacts instead of duplicating them, redacts secrets, and includes accomplishments, decisions, workspace state, next steps, open questions, suggested skills, and a continuation prompt. Its temporary-directory output is the gap this App closes.
- Durable-note skills such as
standup use discoverable Markdown files and tolerate evolving schemas. Use the same durable, inspectable posture here, but with a versioned manifest per artifact.
- Prototype skills work best when they produce complete self-contained HTML and report an exact output path plus verification status. The first prototype proof should therefore produce one portable HTML snapshot rather than depending on an asset server.
- Current OpenHands Plugins use
.plugin/plugin.json with bundled skills/; do not substitute the Codex .codex-plugin format.
- Canvas host API 1 can read plugin readiness through
GET /api/plugins/installed. The existing /launch route accepts base64-encoded plugin coordinates and an optional short message, presents the normal trust confirmation, creates the conversation with current settings, and navigates to it. Use that route for “Start conversation with artifact”; do not reproduce conversation settings or credentials in the App.
There is no supported global hook that can intercept every arbitrary artifact-producing skill. This PoC must make registration automatic for its own bundled /handoff and /prototype skills and expose a focused /save-artifact skill plus documented script contract that other skills can call. Do not claim that unmodified third-party skills are captured automatically.
Package shape
Create one independent top-level project similar to:
artifact-handoff/
├── canvas-extension.json
├── extension.js # checked-in self-contained browser ESM
├── src/ ... # App source
├── scripts/ ... # build, one-file, and Blob smoke checks
├── plugin/
│ ├── .plugin/plugin.json # valid OpenHands Plugin manifest
│ ├── skills/
│ │ ├── handoff/SKILL.md
│ │ ├── prototype/SKILL.md
│ │ └── save-artifact/SKILL.md
│ ├── scripts/artifact_store.py # deterministic stdlib-only store CLI
│ └── references/artifact-schema.md
├── tests/ ...
└── README.md
The exact source layout can vary. The App build should import/embed the exact helper source used by the Plugin so both sides exercise one implementation inside this project. This is not a repository-wide package or runtime.
Artifact store and data contract
Discover the active Agent Server home with GET /api/file/home. Store mutable data only below:
<agent-server-home>/.openhands/apps/artifact-handoff/
└── artifacts/<artifact-id>/
├── manifest.json
└── content/<safe-filename> # snapshot mode only
Do not require a global mutable index for the PoC. List artifacts by scanning validated per-artifact manifests in bounded batches. Build each artifact in a sibling temporary directory, write manifest.json last, and atomically rename the completed directory so interrupted registrations do not appear as valid artifacts.
Define and document schema_version: 1. A manifest should include at least:
- opaque artifact id, title, summary, type, tags, created timestamp;
- producer/plugin version and originating skill (
handoff, prototype, or save-artifact);
- optional workspace path and conversation id when available;
- storage mode:
snapshot or reference;
- original source path/reference;
- for snapshots: safe relative content path, media type, byte size, and SHA-256;
- optional self-contained HTML entrypoint metadata for a prototype.
Support artifact types such as handoff, prototype, plan, spec, report, test-report, and other. Unknown additive fields must be ignored. Invalid manifests should be reported individually without hiding valid artifacts.
For this first version, snapshots are single regular files. Reject symlinks, path traversal, device files, unsafe names, oversized files, and content paths outside the artifact directory. A self-contained HTML prototype satisfies the prototype workflow; multi-file directory snapshots and a global full-text index are explicit follow-ups, not hidden scope.
References record metadata and a source path/URL without copying it. The App should show whether a local reference still exists, but it must not automatically read arbitrary referenced paths. Snapshot reads must be bounded and confined to the App-owned artifact directory.
Deterministic helper script
Implement a Python standard-library CLI that owns schema validation and store operations. At minimum it needs structured operations for:
- non-mutating prerequisite/store probe;
- register snapshot or reference;
- list/filter manifests;
- get one manifest and a bounded snapshot preview;
- verify a snapshot checksum and report missing/corrupt content.
Pass variable data as a JSON request file, stdin, or base64-encoded JSON. Never interpolate titles, tags, prompts, artifact paths, or other user-controlled text into shell source. Emit one tagged/base64 JSON envelope so the App can distinguish structured output from command noise.
The App may execute the embedded helper in memory through the existing authenticated Bash endpoint, using the validated Agent Server home as the structured cwd. The Plugin runs its bundled copy. Both must resolve the same App-owned root and produce byte-for-byte compatible manifests.
Registration may create the exact App-owned artifact directory only after an explicit skill invocation or user action. Reads/probes must not mutate. Do not add a general terminal or arbitrary-command surface.
Bundled Plugin skills
/handoff
Produce a concise Markdown continuation document based on the current conversation. Preserve the useful existing handoff structure: accomplishments, decisions, current workspace state, important context, commands/actions, referenced specs/issues/commits, suggested skills, next steps, open questions, continuation prompt, and notes for the next agent. Reference existing durable artifacts instead of copying their full contents and redact credentials, secrets, and unnecessary personal data.
Before finishing, register the Markdown file as a handoff snapshot through the helper. Return both the durable artifact id and stored path. A failure to register is a visible partial failure, not a silent success.
/prototype
Create a focused, self-contained HTML/CSS/JS product prototype from the user request, verify the file exists and perform the best available syntax/browser check. Register the completed file as a prototype snapshot with entrypoint metadata, concise summary, and useful tags. Return its artifact id, stored path, and verification status.
The prototype skill demonstrates the artifact lifecycle, not a giant design framework. Keep it portable and do not fetch remote assets at preview time.
/save-artifact
Register an existing durable output from another workflow as either a snapshot or a reference. Ask only for missing metadata that materially affects storage. Use the structured helper request; do not rebuild or reinterpret the artifact. This is the documented interoperability seam for PRDs, plans, reports, test results, ADRs, release notes, and future skills.
Each skill must have precise trigger metadata (including the slash-command wording), concise instructions, explicit security rules, and tests/validation appropriate to the installed OpenHands skill format.
Canvas App experience
Contribute one Artifacts page with nested detail routing. It should provide:
- Readiness/onboarding — non-mutating Python/store probe plus
GET /api/plugins/installed. Distinguish Plugin missing, installed-disabled, enabled-ready, unsupported/cloud, malformed store, and command/API failure. Explain that App and Plugin enablement are separate trust actions. If setup is required, show exact paths/actions and a copyable prompt for an OpenHands agent; never auto-install or auto-enable the Plugin.
- Artifact library — reloadable list sorted newest first, search over bounded manifest metadata, and filters for type, originating skill, workspace, tag, and storage mode. Include loading, empty, partial-invalid, stale-reference, and error states.
- Artifact detail — validated metadata, provenance, checksum/verification state, source/reference, copyable id/path, and a bounded escaped preview for Markdown/text/JSON. Render prototype HTML only in a sandboxed, script-disabled iframe or equivalent isolated static preview; never inject artifact HTML into the App DOM. Clearly label that interactive scripts are disabled in preview.
- Reuse — allow the user to enter a short objective and choose “Start conversation with artifact.” Build a Canvas
/launch path using the detected installed Plugin’s source, resolved_ref, and repo_path, plus a message of at most the route’s supported 500 characters. The message should cite the artifact id and absolute stored path and ask the agent to inspect it before acting. Then call the mount context navigate(...). The launch screen remains the explicit trust/create confirmation. If the Plugin is missing, offer setup/copy-prompt guidance instead of inventing credentials or posting directly to /api/conversations.
Do not embed an entire large artifact in the launch URL or prompt. Do not expose secrets found in artifact content in list summaries, URL parameters, logs, or errors.
Installation and onboarding
- App install and Plugin install are separate operations. Both begin disabled.
- The README must give local installation coordinates for this repository: App
repo_path/absolute path and Plugin repo_path/absolute path.
- The App should detect the installed Plugin through existing Agent Server APIs. A missing management API is a distinct degraded state.
- Probe Python without creating files. If Python is unavailable, explain the fixed dependency and provide a copyable agent setup prompt; never guess a package manager or install it automatically.
- Document every persistent file, size limit, hash, preview limitation, and mutating operation.
Security and reliability requirements
- Confine snapshot reads/writes to
.openhands/apps/artifact-handoff/; validate every path segment and reject symlinks.
- Treat all manifests and artifact contents as untrusted. Render through text-safe DOM APIs. Do not use untrusted
innerHTML.
- Cap metadata lengths, tag counts, snapshot size, list size, and preview bytes. Make truncation visible.
- Use atomic writes and collision-resistant ids. Verify snapshot hashes on demand.
- Never store credentials intentionally. Skills must inspect/redact likely secrets before registration and tell the user when an artifact may contain sensitive material.
- No deletion is required in this PoC. If implemented, it must use an exact-id two-step confirmation and target only one validated artifact directory.
- Return cleanup for App DOM, listeners, timers, requests, iframe/object URLs, and registration.
Automated verification
Include tests for:
- App activation, one-page registration, root/detail/unknown nested routes, cleanup, and unsupported host API;
- readiness states and safe Plugin coordinate parsing from
/api/plugins/installed;
- Artifact list/filter/detail rendering, malformed manifests, missing references, checksum mismatch, truncation, command errors, and escaped preview content;
- launch-path construction, 500-character message bound, base64 plugin encoding, user confirmation flow, and
navigate call;
- helper probe/register/list/get/verify behavior in temporary homes, atomic completion, ids/collisions, snapshot/reference modes, hashes, size limits, traversal/symlink rejection, malformed entries, and concurrent registrations;
- Plugin manifest plus all bundled skill metadata/resources;
- build output proving exactly one self-contained
extension.js and a real Blob-import runtime smoke test.
Run the App checks, Python tests, Plugin/skill validation, typecheck/lint where configured, and inspect the final bundle for bare imports, external chunks, sibling runtime assets, and Node globals.
Local Canvas acceptance walkthrough
Document and perform what the environment permits:
- Build/check, install the App from its absolute local path, and enable it.
- Confirm the first probe creates nothing and Plugin-missing guidance is accurate.
- Install the companion Plugin separately from
artifact-handoff/plugin, leave it disabled, reload, and verify the installed-disabled state.
- Enable the Plugin and start a fresh conversation so its skills load.
- Run
/handoff; confirm one Markdown snapshot appears in Artifacts after reload and opens with correct metadata and preview.
- Run
/prototype; confirm one self-contained HTML snapshot appears and its script-disabled preview is isolated.
- Run
/save-artifact on another SDLC document in both snapshot and reference modes; confirm the copy remains stable while the reference becomes stale if its source is moved.
- From artifact detail, enter a reuse objective and choose Start conversation with artifact. Confirm Canvas opens the normal launch trust screen with the companion Plugin and a short path-based starting prompt, then creates/navigates after approval.
- Reload, disable/re-enable the App, disable the Plugin, and verify graceful degraded states. Rebuild then uninstall/reinstall the App before retesting a changed bundle because the current Apps UI has no refresh control.
Record unrun manual checks honestly. Automated Blob smoke is not a substitute for local Canvas acceptance.
Non-goals
- No OpenHands repository changes or new host API.
- No shared repository foundation, compound installer, or monorepo workspace.
- No global interception of arbitrary third-party skills.
- No general artifact editor, general-purpose terminal, remote artifact upload, multi-file prototype hosting, or full-text indexing.
- No silent Plugin installation/enablement and no bypass of the existing conversation launch trust flow.
Delivery
Commit the complete independent App and Plugin, push a feature branch, and open a pull request that closes this issue. Report automated checks and a concise human-test checklist; do not merge the PR.
Goal
Create
artifact-handoff, the final standalone App in this roadmap: a human-facing Artifact Handoff Canvas App paired with an independently installable OpenHands Plugin. Together they prove an SDLC workflow in which agent-produced handoffs, prototypes, plans, reports, and other durable outputs can be registered in an App-owned artifact store, browsed later, and reused as the starting context for a new conversation.This must not require any change to OpenHands or introduce a repository-wide shared foundation.
Depends on
Issue #10 is complete. Keep the App, Plugin, data contract, helper script, build, tests, and docs inside this one top-level project. Existing Apps may be studied or copied, but not changed.
Research and product decisions
The implementation should adapt these proven local patterns:
handoffskill produces a compact Markdown continuation document, references existing artifacts instead of duplicating them, redacts secrets, and includes accomplishments, decisions, workspace state, next steps, open questions, suggested skills, and a continuation prompt. Its temporary-directory output is the gap this App closes.standupuse discoverable Markdown files and tolerate evolving schemas. Use the same durable, inspectable posture here, but with a versioned manifest per artifact..plugin/plugin.jsonwith bundledskills/; do not substitute the Codex.codex-pluginformat.GET /api/plugins/installed. The existing/launchroute accepts base64-encoded plugin coordinates and an optional shortmessage, presents the normal trust confirmation, creates the conversation with current settings, and navigates to it. Use that route for “Start conversation with artifact”; do not reproduce conversation settings or credentials in the App.There is no supported global hook that can intercept every arbitrary artifact-producing skill. This PoC must make registration automatic for its own bundled
/handoffand/prototypeskills and expose a focused/save-artifactskill plus documented script contract that other skills can call. Do not claim that unmodified third-party skills are captured automatically.Package shape
Create one independent top-level project similar to:
The exact source layout can vary. The App build should import/embed the exact helper source used by the Plugin so both sides exercise one implementation inside this project. This is not a repository-wide package or runtime.
Artifact store and data contract
Discover the active Agent Server home with
GET /api/file/home. Store mutable data only below:Do not require a global mutable index for the PoC. List artifacts by scanning validated per-artifact manifests in bounded batches. Build each artifact in a sibling temporary directory, write
manifest.jsonlast, and atomically rename the completed directory so interrupted registrations do not appear as valid artifacts.Define and document
schema_version: 1. A manifest should include at least:handoff,prototype, orsave-artifact);snapshotorreference;Support artifact types such as
handoff,prototype,plan,spec,report,test-report, andother. Unknown additive fields must be ignored. Invalid manifests should be reported individually without hiding valid artifacts.For this first version, snapshots are single regular files. Reject symlinks, path traversal, device files, unsafe names, oversized files, and content paths outside the artifact directory. A self-contained HTML prototype satisfies the prototype workflow; multi-file directory snapshots and a global full-text index are explicit follow-ups, not hidden scope.
References record metadata and a source path/URL without copying it. The App should show whether a local reference still exists, but it must not automatically read arbitrary referenced paths. Snapshot reads must be bounded and confined to the App-owned artifact directory.
Deterministic helper script
Implement a Python standard-library CLI that owns schema validation and store operations. At minimum it needs structured operations for:
Pass variable data as a JSON request file, stdin, or base64-encoded JSON. Never interpolate titles, tags, prompts, artifact paths, or other user-controlled text into shell source. Emit one tagged/base64 JSON envelope so the App can distinguish structured output from command noise.
The App may execute the embedded helper in memory through the existing authenticated Bash endpoint, using the validated Agent Server home as the structured
cwd. The Plugin runs its bundled copy. Both must resolve the same App-owned root and produce byte-for-byte compatible manifests.Registration may create the exact App-owned artifact directory only after an explicit skill invocation or user action. Reads/probes must not mutate. Do not add a general terminal or arbitrary-command surface.
Bundled Plugin skills
/handoffProduce a concise Markdown continuation document based on the current conversation. Preserve the useful existing handoff structure: accomplishments, decisions, current workspace state, important context, commands/actions, referenced specs/issues/commits, suggested skills, next steps, open questions, continuation prompt, and notes for the next agent. Reference existing durable artifacts instead of copying their full contents and redact credentials, secrets, and unnecessary personal data.
Before finishing, register the Markdown file as a
handoffsnapshot through the helper. Return both the durable artifact id and stored path. A failure to register is a visible partial failure, not a silent success./prototypeCreate a focused, self-contained HTML/CSS/JS product prototype from the user request, verify the file exists and perform the best available syntax/browser check. Register the completed file as a
prototypesnapshot with entrypoint metadata, concise summary, and useful tags. Return its artifact id, stored path, and verification status.The prototype skill demonstrates the artifact lifecycle, not a giant design framework. Keep it portable and do not fetch remote assets at preview time.
/save-artifactRegister an existing durable output from another workflow as either a snapshot or a reference. Ask only for missing metadata that materially affects storage. Use the structured helper request; do not rebuild or reinterpret the artifact. This is the documented interoperability seam for PRDs, plans, reports, test results, ADRs, release notes, and future skills.
Each skill must have precise trigger metadata (including the slash-command wording), concise instructions, explicit security rules, and tests/validation appropriate to the installed OpenHands skill format.
Canvas App experience
Contribute one Artifacts page with nested detail routing. It should provide:
GET /api/plugins/installed. Distinguish Plugin missing, installed-disabled, enabled-ready, unsupported/cloud, malformed store, and command/API failure. Explain that App and Plugin enablement are separate trust actions. If setup is required, show exact paths/actions and a copyable prompt for an OpenHands agent; never auto-install or auto-enable the Plugin./launchpath using the detected installed Plugin’ssource,resolved_ref, andrepo_path, plus a message of at most the route’s supported 500 characters. The message should cite the artifact id and absolute stored path and ask the agent to inspect it before acting. Then call the mount contextnavigate(...). The launch screen remains the explicit trust/create confirmation. If the Plugin is missing, offer setup/copy-prompt guidance instead of inventing credentials or posting directly to/api/conversations.Do not embed an entire large artifact in the launch URL or prompt. Do not expose secrets found in artifact content in list summaries, URL parameters, logs, or errors.
Installation and onboarding
repo_path/absolute path and Pluginrepo_path/absolute path.Security and reliability requirements
.openhands/apps/artifact-handoff/; validate every path segment and reject symlinks.innerHTML.Automated verification
Include tests for:
/api/plugins/installed;navigatecall;extension.jsand a real Blob-import runtime smoke test.Run the App checks, Python tests, Plugin/skill validation, typecheck/lint where configured, and inspect the final bundle for bare imports, external chunks, sibling runtime assets, and Node globals.
Local Canvas acceptance walkthrough
Document and perform what the environment permits:
artifact-handoff/plugin, leave it disabled, reload, and verify the installed-disabled state./handoff; confirm one Markdown snapshot appears in Artifacts after reload and opens with correct metadata and preview./prototype; confirm one self-contained HTML snapshot appears and its script-disabled preview is isolated./save-artifacton another SDLC document in both snapshot and reference modes; confirm the copy remains stable while the reference becomes stale if its source is moved.Record unrun manual checks honestly. Automated Blob smoke is not a substitute for local Canvas acceptance.
Non-goals
Delivery
Commit the complete independent App and Plugin, push a feature branch, and open a pull request that closes this issue. Report automated checks and a concise human-test checklist; do not merge the PR.