feat(plugins): let plugin services contribute to agent launches - #84
Closed
BIackFIame wants to merge 4 commits into
Closed
BIackFIame wants to merge 4 commits into
BIackFIame wants to merge 4 commits into
Conversation
This was referenced Sep 27, 2026
Builds on howdeploy#80 (teo-nex, "restore each Codex card to its own conversation"): its capture of the conversation id from authenticated lifecycle hooks, the validated id saved per card, `codex resume <id>`, the resume picker when no id is known and a plain restart forgetting the id are kept as they are. This extends the same exact resume to Claude Code (`claude --resume <id>`) and OpenCode (`opencode --session <id>`); the field is renamed from codexThreadId to threadId for that, with one per-provider check (canonical UUID for Codex and Claude, `ses_` id for OpenCode) shared by the hook client, the gateway, the store and the launch, and v1 records' codexThreadId still read. Settings → General now offers Don't save / Reopen windows / Continue conversations (settings v21; the old opt-in boolean migrates true→continue, false→off). Session records move to v2 (v1 stays readable): last state at quit or exit, the thread id, a per-card restore flag, and two validated opaque plugin slots (launch options and an environment ref, 4 KB each). No scrollback, prompts or secrets are saved. Restore puts parents before children, resumes a recorded conversation by id, and without one uses a "latest in this folder" flag only when that CLI has one card in the folder (otherwise it starts fresh with a note on the card; Codex opens its picker). Finished cards come back stopped with Restart / Continue (Continue resumes the card's own conversation), and a card whose environment is unavailable is held stopped with its reason instead of running locally. Cards get an options menu with "Don't restore this card". For plugins: the v2 record's two opaque slots are where later extension points keep per-card state across restarts. A launch contributor's chosen options are saved in `options[pluginId]` and an environment's ref in `environment`, both validated and capped at 4 KB, so a restored card can be prepared or placed again (or held stopped with a reason) without the core knowing what the values mean.
Manifest apiVersion 2 adds `services`: bundled single-file JavaScript entries (integrity-declared like hook entries in modular plugins). A new PluginServiceSupervisor runs each service of an enabled plugin as its own process (process.execPath + ELECTRON_RUN_AS_NODE, cwd = plugin folder, allow-listed environment without keys, NODE_OPTIONS or CANVASTTY_*), speaks newline-delimited JSON-RPC 2.0 over stdio (1 MB messages, 15 s request timeouts, 64 pending), restarts with backoff (at most 5 in 10 minutes), stops politely then with SIGTERM/SIGKILL on disable, uninstall, update, module change, revoke and quit, and keeps a bounded per-plugin log. Services run only after a separate per-plugin "Extension native code" confirmation in Settings -> Agents. Install never grants it; it pins each entry's SHA-256 (checked before every start) and is revoked by update, module change, disable, or a changed entry file. How a plugin uses it: its sandboxed surfaces call their own plugin's services with host.service.request(serviceId, method, params) and receive host.service.onEvent; the plugin id is bound by the frame host or the identity-checked plugin window. A service may call back `log`, own-plugin `storage.*` (storage permission), `event`, and `secrets.get` (secrets permission) for its own plugin's secret, for example an API key of a model it calls; anything else is -32601. This is the base the following extension points (launch, environments, decisions, tools, sessions, cards) add host requests to. Docs (en/ru/zh), schema, plugin-api.d.ts, example examples/plugins/service-echo (a canvas app that calls its service, and a token the page saves and the service reads), and tests/plugin-services.test.mjs, tests/plugin-policy-budget-secrets.test.mjs.
A trusted plugin service can now declare a `launch` block (permission
launch:contribute, one service per plugin): up to 8 boolean, select or
text fields, optionally limited to some agents. The agent launcher shows
them under Advanced; the person turns a plugin on for one launch, and the
checked values (at most 4 KB per plugin) are saved in the session
record's plugin options slot and reused on restart and restore.
Before such a card is spawned, LaunchPipeline sends each chosen plugin's
service `canvastty.launch.prepare` (a host-only method surfaces cannot
send) and merges the answers in plugin-id order: env, secretEnv (names of
the plugin's own secrets, resolved in main, never shown to the service or
any UI, masked as <redacted:secret> in observe/result, the control CLI's
screen/result and failure details), args (appended before the resume
selection) and per-run files ({launchFiles}, removed on exit). A refusal,
a 5 s timeout, an error, an invalid answer, a missing secret, two plugins
setting one name, a reserved or core-set variable, or an approval or
conversation argument (coreOwnedLaunchArgument) refuses the launch with
the reason on the card; it is never started without the contribution.
Restore holds a card whose plugin is unavailable stopped with its reason
and keeps its record. Launches without options or policies stay
synchronous and unchanged.
What an account or policy plugin also needs:
- launch.policy: a contributor with `policy: true` is also asked before
every agent launch where the person did not choose it (`chosen: false`,
empty options). That answer may only refuse; a contribution, a timeout
or an error refuses too, so a policy never lets a launch through by
failing. Policy-only contributors are not shown in the launcher.
- A select may declare `optionsFrom: "service"`: the launcher asks
`canvastty.launch.options` (3 s) and lists up to 64 more choices (the
plugin's accounts, say) after the declared ones; such a value is any
short text the service re-checks when it prepares.
- spawn_agent takes `launchOptions` ({pluginId: {field: value}}), checked
exactly like the launcher's, so an orchestrator can start a subagent
with the account a plugin tool picked.
- Claude Code applies only its last inline --settings, so a plugin's
inline --settings is merged into CanvasTTY's own (hooks kept); one
that sets permissions, hooks, sandbox, defaultMode or apiKeyHelper is
refused.
Docs (en/ru/zh), schema, plugin-api.d.ts, examples
examples/plugins/launch-env (options, a service-filled Profile) and
examples/plugins/yolo-guard (a policy that refuses YOLO launches), and
tests/launch-contributors.test.mjs, tests/plugin-launch-choices.test.mjs,
tests/plugin-policy-budget-secrets.test.mjs.
BIackFIame
force-pushed
the
core/3-launch-contributors
branch
from
September 27, 2026 19:44
3112dd4 to
a87401e
Compare
Owner
|
Consolidated into #88 at the maintainer's request. Its branch already includes this implementation (the #81 authentication fix is incorporated through #87). Please continue all follow-up fixes and discussion in #88. Detailed changes-requested review: #88 (review) . Closing this superseded PR preserves its branch, commits and authorship; no code is being merged into main. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
Let a trusted plugin shape how an agent starts (environment, arguments, files, an account) without core knowing about accounts or rules, and let it refuse launches it considers unsafe.
What core gains
services[].launch(launch:contribute, one service per plugin):canvastty.launch.prepare(host-only, 5 s) answersenv,secretEnv(resolved in main from the plugin's own secrets),args, per-runfiles({launchFiles}) orrefuse. Contributors merge in plugin-id order.launch.policy: the service is also asked before every agent launch the person did not choose it for (chosen: false). It may only refuse.optionsFrom: "service": the launcher askscanvastty.launch.options(3 s) for up to 64 more choices, such as the plugin's accounts.spawn_agenttakeslaunchOptions, checked exactly like the launcher's.--settings, so a plugin's inline settings are merged into core's and the hooks are kept.Security posture
CANVASTTY_,ELECTRON_,DYLD_,LD_,NODE_OPTIONS,PATH,TERM) or a variable core already sets;--permission-mode,--sandbox, resume and session;permissions,hooks,sandbox,defaultModeorapiKeyHelper.Docs and examples
plugin-api.d.tsand the schema.examples/plugins/launch-env: options and a service-filled Profile.examples/plugins/yolo-guard: a policy that refuses YOLO launches. feat(plugins): let plugin services provide session environments #85 teaches it environments.Tests and checks
launch-contributors,plugin-launch-choicesandplugin-policy-budget-secrets(the policy part). Suite 929/929 and typecheck with a fake HOME.--settings(hooks plus route);Dependency
Stacked on #80 and #83. Review only the top commit.
Used by canvastty-plugin-accounts (accounts list, keys via
secretEnv,spawn_agentoptions), canvastty-plugin-context (rules via--append-system-prompt-fileanddeveloper_instructions) and canvastty-plugin-assistant (launch triage, YOLO-only-isolated policy).