Skip to content

feat(desktop-policies): let organization prompts launch a named skill - #3115

Closed
benjaminshafii wants to merge 2 commits into
devfrom
feat/prompt-capability-launchers
Closed

feat(desktop-policies): let organization prompts launch a named skill#3115
benjaminshafii wants to merge 2 commits into
devfrom
feat/prompt-capability-launchers

Conversation

@benjaminshafii

@benjaminshafii benjaminshafii commented Jul 25, 2026

Copy link
Copy Markdown
Member

Stacked on #3114 (feat/desktop-policy-prompt-preview). Review that first; this extends the preview it adds.

The problem

We tell administrators that the organization prompt cards are how members discover what the platform can do. That promise is unbacked.

A prompt is free text only. There is no skillId, pluginId, or capabilityName on the schema, and normalizeDesktopPolicyDocument strips unknown keys. So a card reading "Find what needs my attention" just drops that sentence into the composer and hopes the agent selects the right skill.

Meanwhile a binding already works by accident: editor.tsx setPrompt() parses a leading /slug or [skill x] into a pill, unvalidated and never mentioned in the Cloud UI.

The change: formalize the accident

A prompt card can now name the skill it launches, and tells the member the truth about readiness before they click.

  1. Schema — prompts accept an optional bound skill reference (local by slug, or organization skill with the marketplace + capability identifiers needed to actually resolve it). No DB migration: prompts ride in the existing policy JSON column.
  2. Desktop — a bound card invokes its skill through the existing token/expansion mechanism rather than a second invocation path; the guarantee becomes explicit instead of accidental. Applied to both render surfaces (task-suggestions.tsx and session-empty-hero.tsx) so they cannot diverge.
  3. Readiness precheck — if the bound skill needs a connection the member has not made, the card says so up front instead of failing mid-run, reusing the existing needs_signin / needs_admin_setup / ready vocabulary and copy. Needs-sign-in offers the route to fix it; needs-admin-setup says so plainly and does not send a member somewhere they have no permission.
  4. Cloud admin — the admin picks from skills that actually exist in the organization's marketplaces (reusing the dashboard's existing marketplace data, no raw identifier typing), and the live preview shows the bound skill.

Compatibility is the risk, so it is tested in both directions

  • Existing policies are string[] and keep working unchanged; cards with no bound skill behave exactly as today (fill the composer, do not auto-send).
  • An old desktop reading a new policy degrades safely; a new desktop reading an old policy behaves as today.

Why this shape

Per-team and per-person scoping already ships and is tested (desktop_policy_member.team_id / org_member_id), so this adds a payload field to working infrastructure rather than building a system. It is also the substrate for scheduled work later: a bound, readiness-checked prompt is exactly the thing you can put on a schedule — without building a dashboard.

Verification

pnpm typecheck                                        clean
pnpm --filter @openwork/app test                      all pass
pnpm --filter @openwork-ee/den-web build               succeeded
den-api desktop-policy suites (real DB)                9 pass, 0 fail

OpenAPI snapshot regenerated via the generator, not hand-edited. New tests cover legacy-string resolution, bound resolution, old-shape-reads-new-shape, each readiness state mapping to the right affordance, and unbound cards still only filling the composer.

@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-app Ready Ready Preview, Comment Jul 25, 2026 1:01pm
openwork-den Ready Ready Preview, Comment Jul 25, 2026 1:01pm
openwork-den-worker-proxy Ready Ready Preview, Comment Jul 25, 2026 1:01pm
openwork-diagnostics Ready Ready Preview, Comment Jul 25, 2026 1:01pm
openwork-landing Ready Ready Preview, Comment, Open in v0 Jul 25, 2026 1:01pm

@mintlify

mintlify Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
differentai 🟢 Ready View Preview Jul 25, 2026, 1:02 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@mintlify

mintlify Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
differentai 🟡 Building Jul 25, 2026, 1:01 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@benjaminshafii

Copy link
Copy Markdown
Member Author

Validation: real app, isolated Linux sandbox, matched A/B

All nine branches of this series were merged into one integration branch (f91884c3) and driven as a real user through CDP in a fresh Daytona sandbox. Baseline dev was run in a separate, identically-provisioned fresh sandbox with the same model (GPT-4o mini) so the comparison is apples-to-apples.

core-flow step clean dev (2fda6070) this series (f91884c3)
App boots to a usable session surface ✅ 105ms ✅ 94ms
User creates a fresh task in the workspace ✅ 674ms ✅ 662ms
User writes a message and runs it (real inference) ✅ 2768ms ✅ 111405ms
User closes and reopens; the session survives ❌ not persisted ❌ not persisted

Step 4 fails identically on clean dev (Session ses_… was not listed after reopen (not persisted)), so it is pre-existing on dev and not introduced here. Against the canonical core flow this series is inert, which is exactly the claim for a cleanup/wiring series.

Validated frames: core-flow-01-booted.png, core-flow-02-session-created.png, core-flow-03-task-response.png — including the assertion "Visible text does not include 'Something went wrong'".

A real regression this caught, in this series

Booting the real app initially failed with:

TypeError: Unknown file extension ".ts" for packages/types/src/skill-markdown.ts
→ "OpenWork server did not finish starting. Please restart OpenWork."

@openwork/types is a source-only package — every exports entry resolves to raw ./src/*.ts. apps/server is built with plain tsc (no bundling), so a runtime import there survives into emitted JS and Node cannot load it. Only the bun build-bundled files under src/opencode-plugins/ may import it.

pnpm typecheck and both bun suites passed straight through this, because bun loads .ts natively and Vite bundles for the renderer. Only booting the real product surfaced it. It is fixed in #3112, and that PR adds an automated guard that fails if any non-bundled apps/server file imports @openwork/types, plus a plain-node import proof of the built output.

Static verification on the merged result

pnpm typecheck                                        clean
pnpm --filter @openwork/app test                      435 pass,  0 fail
pnpm --filter openwork-server test                    526 pass,  0 fail
pnpm --filter @openwork-ee/den-web test                73 pass,  0 fail
pnpm --filter @openwork-ee/den-web build              succeeded
den-api desktop-policy suites (real MySQL)              9 pass,  0 fail

Honest gaps

  • No per-PR fraimz flow was authored; this is the canonical core-flow inertness proof plus the matched baseline, which is the right standard for a series that is mostly wiring, deletion, and policy gating.
  • The cloud prompt-card behavior in feat(desktop-policies): let organization prompts launch a named skill #3115 is covered by unit + real-DB API tests, not by a driven cloud fraimz — that needs the Den stack and a seeded org.
  • Local macOS pnpm dev could not be used: every worktree shares one userData dir, so app.requestSingleInstanceLock() makes any second instance quit before creating a window (CDP stays up, so it looks like a hang). Worth a separate DX fix.

@benjaminshafii

Copy link
Copy Markdown
Member Author

Correction to my validation note above

In the "Honest gaps" section I wrote that local macOS pnpm dev could not be used because all worktrees share one userData dir, so app.requestSingleInstanceLock() makes any second instance quit before creating a window.

That diagnosis was wrong. I tested it properly afterwards and disproved it:

  • With the lock successfully acquired (verified: the boot banner printed and no contention message appeared), the instance still produced 0 CDP page targets.
  • With an isolated profile and OPENWORK_ELECTRON_USE_MOCK_KEYCHAIN=1, still 0 page targets.
  • From the unmodified main checkout on dev with an isolated profile, still 0 page targets.

So dev Electron currently opens no window on this macOS host regardless of profile, keychain, or worktree. It is pre-existing, unrelated to this series, and I have not root-caused it. The same commits do open a window in a clean Linux sandbox, which is where the validation above was run — so the core-flow evidence in this comment stands unchanged.

The shared-profile collision is nonetheless real (two worktrees genuinely cannot both hold the lock, and the loser lingers with an open CDP port and no log line). That is fixed separately in #3124, which adds OPENWORK_DEV_PROFILE and makes lock contention explain itself and exit.

Flagging it because the original wording would have sent a reviewer chasing the wrong cause.

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.

1 participant