Type: plan Status: Implemented Related: LLP 0135, LLP 0128 Generated-by: neutral
Task breakdown for LLP 0135's six-phase
hyp initwizard, thecontributes.pickermanifest mechanism, thectx.commands.runseam, source-scoped export withholding, and the new@hypaware/claude-desktopplugin.@ref LLP 0135 [implements] - refines the design's module map into ordered, independently-mergeable tasks @ref LLP 0128 [implements] - keeps the RFC's constraints (join-before-pick, additive-local-layer, attended-only) as per-task exit criteria
The design's module map has three layers with real code dependencies between them:
- Foundations: the
contributes.pickermanifest shape and thectx.commandsseam live inhypaware-plugin-kernel-types.d.ts; everything else reads or writes against them. - Shared plumbing:
buildPluginCatalog'spickerDescriptors,detect.js's probe dispatch,classifyClientProvenance, thectx.commands.runruntime wiring, the descriptor-drivencomposePickerConfigfold, and thereadRowsSincewithhold resolver. Each of the threeclassifyClientProvenancecall sites (pick-phase locking,hyp status, the export seam) and the configure phase's command dispatch depend on their respective plumbing piece landing first. - The wizard itself:
fork.js,join.js,pick.js,configure.js, and theindex.jsstate machine that wires them together, plus the new@hypaware/claude-desktopplugin that supplies the first (and today only)needs_setuppicker entry.
Tasks below follow that ordering: kernel-types and the catalog/detect/provenance
plumbing first, then the plumbing's consumers (storage withholding, hyp status,
composePickerConfig), then the wizard phase modules (which can build in
parallel once their specific plumbing dependency lands), then orchestration,
then the Claude Desktop plugin. The non-macOS app_bundle detection question
LLP 0135#open-questions
stays deferred; no task below implements it.
Every task lands with npm test green on its own. Repo style: no
semicolons, no em dashes, JSDoc types (no @typedef, no inline import()
types).
Completion note. T1-T10, T12, and T13 shipped with the
install-experience overhaul PR (#375). T11 (orchestration) landed in a
follow-up along with three items the phase modules had left open: picker
manifest entries for @hypaware/openclaw and @hypaware/hermes (both use
the settings_file probe - the path probe's literal is absolute-only,
which a static manifest cannot express for a home-relative directory), the
pick phase's managed-machine "stays on this machine" row annotation
(LLP 0132 #never-silent), and the wizard's closing first-sync privacy
narration. One deliberate deviation from T11's letter: runWizardFinale
skips only the daemon install on the team pathway (a new
skipDaemonInstall finale flag) - the restart still runs, because the
wizard just wrote a new local layer the daemon must pick up.
- id: T1 branch: task/install-experience-overhaul/T1 deps: [] complexity: 4 -- Kernel-types and manifest schema: add
PluginPickerContribution(label,summary,detect,needs_setup,configure_command) and thePickerDetectProbeunion (settings_file/app_bundle/path) tohypaware-plugin-kernel-types.d.ts, changecontributes.pickerto an array (PluginPickerContribution[], sibling tocontributes.client), add the optionalattribution_columnstring field to thecontributes.datasets[]contribution shape, and add thecommands: { run(name: string, argv: string[]): Promise<number> }member toCommandRunContext(hypaware-plugin-kernel-types.d.ts:654). Types only, no runtime wiring (T4 and T5 wire the runtime). The judgment is getting the published-contract shape right on the first pass: this .d.ts is consumed by every plugin manifest and every command implementation. Update the plugin manifest JSON-schema validator (wherevercontributes.clientis currently validated) to accept the new array field and probe variants. - id: T2 branch: task/install-experience-overhaul/T2 deps: [T1] complexity: 3 --
buildPluginCatalogpicker descriptors +detect.jsmigration: insrc/core/plugin_catalog.js, readcontributes.pickerin the same pass that already readscontributes.client(today's lines 61-77) into a newpickerDescriptors: Map<string, PickerDescriptor>keyed by picker source id, first-manifest-wins. ReplacedetectClientSources's hardcodedDETECTABLE_CLIENT_SOURCEStable insrc/core/cli/detect.jswithdetectPickerSources(catalog, env), iteratingcatalog.pickerDescriptorswith a small switch on thePickerDetectProbevariant (settings_filereuses the existingresolveClientSettingsPathparent-dir-exists check;app_bundleandpathstat the literal path,pathhonoring the same$FOO_HOMEenv-override resolutionhermesneeds). Still best-effort: a probe failure is "not present," never thrown. Existingclaude/codexdetection behavior is byte-identical; prove it with the existing detect tests re-pointed at the new function. - id: T3 branch: task/install-experience-overhaul/T3 deps: [T2] complexity: 4 --
classifyClientProvenance(clientName, layered)in newsrc/core/cli/wizard/provenance.js: generalizes the central-vs-local checkclassifyInactiveStatealready does for one disabled-plugin case (src/core/cli/dispatch.js:788) into'central' | 'local' | 'absent'for any picker source id, resolving it to its owning plugin viapickerDescriptors/clientDescriptorsand checking membership inlayered.centralConfig.pluginsvslayered.effective.plugins. Also lands thehyp statusconsumer: the new "syncing: X - local-only: Y" split line insrc/core/daemon/status.js, grouping picked clients by provenance so a local addition on a managed machine is never a silent state (@ref LLP 0132#never-silent). The other two call sites (pick-phase row locking, export-seam withholding) are separate tasks (T9, T5) since they land with their own consuming module. Unit-test the three-way classification directly; it is shared, correctness-critical code. - id: T4 branch: task/install-experience-overhaul/T4 deps: [T1] complexity: 4 --
ctx.commands.runseam: implement theCommandRunContext.commandsmember declared in T1 as a thin wrapper over the existing module-privaterunCommandByName(src/core/cli/dispatch.js:400, today used only for the config-repair redirect), populated by the dispatcher the same wayctx.skills/ctx.agents/ctx.backfillsalready are. Deliberately does not expose the full mutable registry to command code, justrun(name, argv).initalready boots withdecideBootProfile(argv) === 'all-available'(dispatch.js:474), so no boot-profile change is needed for aneeds_setupplugin's command to be registered by the time a caller uses this seam. No wizard code calls it yet (T10 does); test it by invoking one existing registered command through the new seam and asserting identical exit code/output to invoking it via the normal CLI path. - id: T5 branch: task/install-experience-overhaul/T5 deps: [T1, T2, T3] complexity: 5 -- Source-scoped export withholding: extend
readRowsSince(src/core/cache/storage.js:243) with a second optional resolver,sourceWithholdResolver, threaded the same wayusagePolicyResolveralready is (drop-but-advance: a withheld row still moves the watermark, per the existingcwd-filter continuation semantics). BuildsourceWithholdResolveronce at boot fromclassifyClientProvenance(T3): the set of picker source ids classified'local'on a machine with a central layer. Per-row matching uses the newattribution_columnmanifest field (T1); declareattribution_column: "client_name"on@hypaware/ai-gateway'sai_gateway_messagesdataset contribution. A dataset with no declaredattribution_columnis never subject to source-scoped withholding (conservative default, matcheslocal-only's existing design). This is the highest-risk task in the plan: it is a privacy boundary on the streaming read path, not a UI affordance. Test both the withhold-and-advance behavior and the no-attribution-column passthrough default directly againstreadRowsSince. - id: T6 branch: task/install-experience-overhaul/T6 deps: [T2] complexity: 4 -- Descriptor-driven
composePickerConfig+ migrate the hardcoded picker table onto manifests: replacewalkthrough.js'swantsAnthropic/wantsCodex/... switch (walkthrough.js:654-729) with a fold over each picked descriptor's owncomposedata ({ plugin, requires_gateway, gateway_upstream }), unioning requested gateway upstreams deduped bynameand including@hypaware/ai-gatewayiff any picked descriptor setsrequires_gateway. DeletePICKER_SOURCES(walkthrough.js:308); its five entries becomecontributes.pickerblocks (withcompose) on@hypaware/claude,@hypaware/codex,@hypaware/ai-gateway(two descriptors,raw-anthropicandraw-openai, from one plugin's manifest array) and@hypaware/otel.PICKER_EXPORTS(the sink-choice half) is untouched. The exact output shape must match today's: prove it with the existingcomposePickerConfigtests re-pointed at the new fold, unchanged assertions. - id: T7 branch: task/install-experience-overhaul/T7 deps: [] complexity: 3 -- Wizard fork phase + returning-gate amendment:
runWizardFork(opts)in newsrc/core/cli/wizard/fork.js(the top-level "Join a team" / "Local install and configuration" / quit choice), andevaluateReturningGate(opts)amending LLP 0011's gate per LLP 0129: on a managed machine, offer a scoped "adjust what this machine collects" entry (no fork,pathwaypreset to'scoped') instead of dropping Reconfigure outright; on a solo machine, Reconfigure re-enters the full fork. Quit stays the default on bare enter. No picker-descriptor dependency: this phase only reads the existinghyp statussummary and central-layer check. Test both gate branches (managed scoped-reconfigure, solo full-reenter) and the fork's own three choices in isolation. - id: T8 branch: task/install-experience-overhaul/T8 deps: [T2, T3] complexity: 4 -- Wizard join phase:
runWizardJoin(opts)in newsrc/core/cli/wizard/join.js, a thin narration wrapper around the existingrunRemoteLogin(@ref LLP 0134#login-lane, never a second enrollment mechanism). ExportwaitForCentralConvergeas a small helper fromremote_commands.js(reusing the bounded reconcile-waitrunRemoteLoginalready performs internally, not a second poll loop) and addclassifyLoginFailure, mapping the login lane's existingno_membership/org_not_permitted/ transient-network taxonomy (LLP 0058 D7) to'failed' | 'abandoned'. On convergence, computelockedSourcesas[...catalog.pickerDescriptors.keys()].filter(id => classifyClientProvenance(id, layered) === 'central')(T2's map, T3's classifier). On timeout or the no-org-config 404 steady state, narrate and return an empty lock set rather than blocking. Test the converge/timeout/failure branches and the locked-source computation against a fixture layered config. - id: T9 branch: task/install-experience-overhaul/T9 deps: [T2, T3, T6] complexity: 4 -- Wizard pick phase:
runWizardPick(opts)in newsrc/core/cli/wizard/pick.js, keepingwalkthrough.js's existing prompt/write/guard/overwrite-confirm shape but sourcing options fromcatalog.pickerDescriptors(T2) instead ofPICKER_SOURCES. A row'scheckedstate isdetected.has(id) || locked.includes(id); a locked id rendersdisabled: truelabeled· managed by your fleet(T3'sclassifyClientProvenance, LLP 0031 provenance vocabulary) and is filtered out of the returnedsourcesbefore composition (already in the central layer; composing again would be the exact collision join-before-pick exists to avoid). Composition itself calls T6's descriptor-drivencomposePickerConfig. Non-interactive callers (--yes,--dry-run, presets,--from-file) invoke this phase directly, matching today'sinteractive = !opts.pickssplit (walkthrough.js:394) so every existing non-interactive picker test keeps its current shape. - id: T10 branch: task/install-experience-overhaul/T10 deps: [T4] complexity: 4 -- Wizard configure phase:
runConfigurePhase(picked, opts)in newsrc/core/cli/wizard/configure.js, looping picked descriptors withneeds_setup: true, narrating one at a time, running eachconfigure_commandthrough T4'sctx.commands.runseam, and applying the drop-on-failure rule (@ref LLP 0131#drop-on-failure: a failed or thrown configure drops that source from this run with a printed catch-up command hint, the wizard continues with the rest). Threads--print-commandsthrough to the invoked command's own argv rather than implementing a second no-sudo escape hatch. Never runs offopts.picks(non-interactive) paths (@ref LLP 0131#attended-only). Test the success, drop-on-nonzero-exit, and drop-on-throw branches with a fakectx.commands.run. - id: T11 branch: task/install-experience-overhaul/T11 deps: [T7, T8, T9, T10] complexity: 5 -- Wizard orchestration:
runInitWizard(opts)in newsrc/core/cli/wizard/index.js, the fork -> join -> pick -> configure -> privacy -> finale state machine per the design's pseudocode, including the join-failure-returns-to-fork loop (@ref LLP 0129#failed-join-returns-to-fork), theevaluateReturningGateshort-circuit for scoped re-entry,narratePrivacyIfTeamPathon the team path only (existing LLP 0100/0101 mechanism, narration-only), andrunWizardFinale(existingrunPickerFinale,walkthrough.js:757, plus the newjoinedAlreadyinput that skips daemon-install/attach stepshyp statusalready reports done from the join lane). Switchessrc/core/commands/init.js'srunInitcall site fromrunPickerWalkthrough(...)torunInitWizard(...). Renames thewalkthrough.start/write_config/finishspans to theirwizard.*equivalents and updates or removes the@ref LLP 0011#interactive-walkthroughannotation on the now-supersededrunPickerWalkthroughper CLAUDE.md's ref-hygiene rule. This is the task where every prior wizard-phase task's contract gets proven together; the existing interactive walkthrough integration tests are the regression bar. - id: T12 branch: task/install-experience-overhaul/T12 deps: [T1] complexity: 2 --
@hypaware/claude-desktopplugin scaffold: newhypaware-core/plugins-workspace/claude-desktop/mirroring the hermes plugin's manifest/activation shape.hypaware.plugin.jsondeclarescontributes.client(name: "claude-desktop",skill_dir/agent_dir, and deliberately noattach_probe: Desktop has no core-reversible settings file, per@ref LLP 0115#no-attach-on-join [constrained-by]and@ref LLP 0135#no-probe. An earlier revision of this task asked for one "reflecting theentrypoint: "claude-desktop-3p"attribution finding"; that was a category error, since anattach_probeis not a label but the input toprobeClientAttachFromDescriptor/detachClientFromDisk. Attribution ridesentrypointper@ref LLP 0133#attributionand needs no descriptor change),contributes.picker(label: "Claude Desktop",detect: { app_bundle: "/Applications/Claude.app" },needs_setup: true,configure_command: "claude-desktop install"), andcontributes.commands(claude-desktop install,claude-desktop verify).src/index.js'sactivate()registers both commands (stubs are fine here; T13 fills in the implementations) and the client descriptor. No wizard-runtime dependency: this plugin is picked up generically by T2's catalog reading and T9's pick phase once both are merged, with no code change to either. - id: T13 branch: task/install-experience-overhaul/T13 deps: [T12] complexity: 5 --
claude-desktop installandclaude-desktop verifycommand implementations (hypaware-core/plugins-workspace/claude-desktop/src/install.js,src/verify.js), per@ref LLP 0133#one-surface: (1) credential login chain (LLP 0117), (2) helper write (LLP 0116), (3) residue check: detect a pre-existingClaude-3pprofile-directory config, back it up, clear it, unconditionally on every install (@ref LLP 0133#dialog-residue, a silent shadowed plist is a per-machine no-op at fleet scale), (4) managed-preferences plist write to/Library/Managed Preferences/com.anthropic.claudefordesktop.plistvia an inline sudo prompt (@ref LLP 0133#solo-sudo), refused up front if the effective config pins an ephemeral gateway listen (127.0.0.1:0,@ref LLP 0133#consequences, LLP 0114), (5) desktop restart prompt (killall cfprefsd+ relaunch hint), (6) two-tier verify: the automatic half (plist present, residue cleared) setsverify's exit code, the in-app half (send a message, confirm capture) is a printed hint only, never a blocking wizard step (@ref LLP 0131#verify-is-a-hint). Every step re-checks its own already-done state first so re-running after a bailed sudo prompt converges without re-prompting completed steps (@ref LLP 0131#idempotent-rerun) -- this idempotency is the hardest and most safety-critical part of the whole plan (real filesystem/plist/sudo side effects, a wrong residue-clear or plist write is a silent per-machine capture failure at fleet scale).--print-commandsprints the privileged commands without running them.
- T1 and T4 are the only tasks that touch the published kernel-types contract; land them first and get them right, since T2, T3, T5, T10, T12, and T13 all build on their shapes.
- T7 (fork) is the one wizard-phase task with no picker-descriptor dependency; it can be built and merged in parallel with T2/T3/T6 if useful.
- T5 (storage withholding) and T13 (claude-desktop install) are the two complexity-5 tasks outside the orchestration state machine itself (T11); both are privacy- or safety-critical side-effecting code, not UI affordances, and deserve the most test scrutiny per task.
- None of these tasks implement the non-macOS
app_bundledetection open question (LLP 0135#open-questions); it stays deferred until a non-Macneeds_setupclient is proposed. - Repo prose style applies to all narration strings the wizard and the claude-desktop commands print: no em dashes, runtime strings prefer "-".