Files
Netcatty/docs/plugin-platform/ai-context-wip.md
T

19 KiB

Plugin AI and selected context: implementation checkpoint

Base: ThemePack #3978, 91b8927c56314e68c06e2c008e84559ddfb96d78. Branch: feat/plugin-ai-context. Status: source-bound services, default trusted main/renderer adapters, app preload, the AppView host chooser, canonical private IPC types, and an installable minimal example are connected. Complete independent review and native acceptance remain pending. This source is not ready to merge or release.

Public boundaries

The parent approved ai.model-invocation and context.selected-read. The transport task owns the public schema, SDK, and runtime peer. Its c4433d4961ecf4a81bea92b0a14f47b4ec12f679 head contains the public AI/context routes and is merged into this branch. The parent assigned this task the additive trusted-host IPC schema, preload, AppView mount, and AI-only hostService wiring. The base later advanced to f1410f8bb; its source/stream changes are incorporated by ordinary merge. Three overlaps retain this branch's permission explanations, bounded-stream assertions, and six-file assistant staging. The source database and sidecar changes come from their owning task, without local modifications.

The model API uses a host-selected configured model. Plugins receive a chat handle, a display label, an expiry, turn handles, text chunks, and usage totals. Plugins do not supply provider IDs, URLs, credentials, tools, or Agent policies. The internal operations are open, start, read, stop, and close. read pulls one chunk with one pending read per turn through the registered SDK and runtime routes.

Context summary enumeration returns only references explicitly shared with the current runtime and source owner. Content reads require an opaque handle and an expected version. Summary enumeration never calls the content reader. Supported selected scopes are workspace, tab, session, host, and Note. There is no inventory or arbitrary entity-ID lookup.

File ownership

Owner Files or responsibility
This task electron/plugins/ai/, electron/plugins/context/, infrastructure/ai/plugin/, examples/plugins/assistant/, this document
This task Opt-in requireBridge and maxStreamBytes request context in infrastructure/ai/sdk/providers.ts
This task Additive trusted-host IPC types and generation, app preload methods/types, AppView host mount, AI-only hostService adapter construction/registration
This task Host security-dialog visibility hook in the modal/tab plugin view lifecycle
This task, reassigned by parent Four permission descriptions and the bounded network.stream contract test expectation
Transport task Public canonical schema, generated SDK, runtime forwarding, generic stream registration
Market task Source identity, private data-owner namespace, installer database, marketplace UI
Transport task Generic TCP and TLS streams
Logging task Optional local plugin logs

The existing feat/plugin-terminal-sharing commit 3cdc97782ef3262969eee9d540e03ebe6fdee210 is preserved. Its terminal-sharing-wip.md describes incomplete selection publication and review. This branch does not claim that workflow is complete.

Implemented authority

PluginAISourceBinding authenticates the current browser runtime before reading getActivePlugin().source, getRetainedPluginSource, privateOwner, and publicPluginId. Source binding is rechecked after asynchronous work. The source database and sync implementation are unchanged. Source origin does not imply that a package is officially maintained.

ModelInvocationBroker requires host-provided resolveDataOwner, resolveBudgetOwner, and selectModel adapters. The service derives the stable budget owner from the authenticated source key and opaque private owner. It never accepts a plugin-supplied budget key or falls back to a bare plugin ID. Strict runtime binding objects still govern handle access; their replacement does not reset request or token budgets. It keeps chats and turns in memory. It stores no chat history and makes no database changes. Data owners are opaque host identities; no private storage is keyed by pluginId.

Each handle is bound to a data owner, a runtime ID, and a plugin version. Every read rechecks runtime authority, the current model, handle lifetime, and selected context versions. Revocation aborts pending turns. Late provider results are discarded. Another source with the same plugin ID cannot read, cancel, or close the original source's handles.

SelectedContextAuthority.share is a trusted host operation. A plugin must never call it directly. Its descriptor accepts only a scope, a title, a version, a text reader, and a current-selection guard. Unknown Host fields are rejected. Content has a 16 KiB cap, excludes NUL, and rejects PEM private keys. The production host reader must project safe fields explicitly and exclude passwords, private keys, TOTP values, identity credentials, startup commands, and environment variables. Free-form Notes can contain secrets that field projection cannot detect. The trusted sharing UI must preview the selected text before sharing it.

createPluginModelAdapter calls the existing createModelFromConfig and streamText, through the existing host provider transport. Credential injection stays in Netcatty. Direct-fetch fallback is disabled for this adapter. Incoming SSE payloads have an aggregate 128 KiB cap before SDK buffering. The main transport also enforces a plugin-specific 128 KiB response cap and a 60-second total deadline. Host-owned request tags allow sender/runtime cancellation without cancelling the internal Agent's ordinary streams. SDK retries are disabled. Only text and allowlisted token totals reach the broker. Provider errors, raw chunks, reasoning, headers, and tool payloads do not cross it.

No AgentRuntime, internal prompt, full MCP catalog, tool executor, Node runtime, raw host IPC, or global renderer store is exposed to plugins. A model tool request ends the turn without executing an action. Notes edits and terminal input are outside this checkpoint. No command is written to a terminal or followed by Enter.

The default broker limits are defined in MODEL_INVOCATION_LIMITS and SELECTED_CONTEXT_LIMITS. They include concurrent handles, turn and chat lifetime, input and output bytes, output token caps, event count, requests per minute, and reserved tokens per minute. Input token reservation uses UTF-8 bytes plus 1,024 bytes for framing and the output token cap. This is a conservative estimate, not a tokenizer or a monetary billing cap. Provider usage is reported separately. Reservations are not refunded after cancellation. Rate limits survive runtime restart within the broker's lifetime. Host shutdown clears this in-memory state.

Connected host path

createPluginHostService constructs PluginAITrustedHostAdapter by default from the existing interaction window, app-lock subscription, and native dialog. It registers the trusted ready/response IPC handlers. App preload exposes the request subscription and validated responses only to the trusted application renderer; the sandbox view preload does not expose these methods.

AppView mounts PluginAIContextHost. Its hook chooses the currently configured host model and opens an app-owned Note chooser. trustedHostRuntime keeps the provider adapter and selected readers in renderer-local maps; only canonical, validated request/results cross the main/renderer IPC boundary. The default adapter authenticates the current main-window sender, uses one-use nonces, rechecks the current owner after awaits, and invalidates on lock, navigation, hide, destruction, or renderer replacement. Plugin permission revocation, disable, and runtime release abort that runtime's turns and transport requests.

Summary enumeration, content reads, and model invocation require separate permissions. Before selected text becomes model input, a native host dialog shows that exact text and version with Cancel as the default. Model invocation permission grants neither context nor action access. Menu IDs are never data authority. While the app-owned chooser is present, the active native modal/tab plugin view yields so it cannot cover the chooser. Existing embedded views use their host-overlay occlusion observer. Native stacking still requires desktop QA.

The selected-context authority is read-only text. Its constructor and lifecycle API are documented in Selected context authority. Host metadata handles cannot be promoted into edit/connect authority; those actions require separate typed host targets, grants, and confirmations.

QA evidence

The domain suites and transport suite use synthetic data only. The transport suite starts a deterministic OpenAI-compatible HTTP server on loopback and checks this path:

ModelInvocationBroker -> createPluginModelAdapter -> createModelFromConfig -> createBridgeFetchForSDK -> existing _streamRequestForTests main transport -> local fixture -> text and usage.

This is not real model inference or paid API testing. It uses no paid keys, real Notes, hosts, or chat history. The credential value is a fixed synthetic marker; the existing model factory replaces it with its IPC placeholder. The test does not exercise native credential storage or production credential injection.

The tests cover source, runtime, and version denial; separate summaries and content reads; context invalidation; provider cancellation and late results; deadlines; request, token, event, input, output, and stream-byte limits; and allowlisted response serialization. Raw provider diagnostics remain inside the host.

Commands:

node --test electron/plugins/modelInvocation.test.cjs
node --test --import tsx infrastructure/ai/plugin/modelInvocationAdapter.test.ts
npx eslint infrastructure/ai/plugin/*.ts infrastructure/ai/sdk/providers.ts examples/plugins/assistant/panel.js

The installed assistant command test passes on this branch's ordinary merge with transport head c4433d496. It installs and activates the six-file sample through the actual package manager and runtime peer, then checks canonical SDK/RPC, independent model/content denial, text-review cancellation, text/usage streaming, spoofed fields, revocation, and stale versions. The in-process browser harness is a Node test transport, not native sandbox proof.

components/PluginAIContextHost.test.tsx connects the default main adapter, actual app preload factory, mounted host chooser, installed sample command/runtime peer, existing provider factory, and existing main HTTP transport to the loopback fixture. It checks content denial, review cancellation, streaming usage, Stop, lock revocation, and absence of keys/configuration/policy on the trusted wire. The view-visibility tests cover chooser appearance, dismissal, hidden surfaces, and teardown without restoring an old view. DOM tests do not establish native WebContentsView stacking or Chromium sandbox behavior.

The trusted main/renderer suites cover nonce replay, sender substitution, owner replacement, navigation, hide, runtime release, lock, cancellation, focus recovery, safe field projection, and late selected-content results. The main transport test also verifies targeted plugin cancellation leaves an ordinary synthetic Agent stream running and aborts an oversized plugin stream.

Failed aggregate runs remain evidence, not passing acceptance records:

Log in /tmp Result
netcatty-ai-qa-tests-full.log 14,912 tests: 14,783 passed, 85 failed, 1 cancelled, 43 skipped; a stuck FIDO child was terminated
netcatty-ai-final-tests.log After standard dependency patches: 15,246 tests, 15,192 passed, 11 failed, 43 skipped
netcatty-ai-final-runtime.log 610 tests: 607 passed, 1 failed, 2 skipped
netcatty-ai-connected-final-tests.log Connected code 42a982660: 15,248 tests, 15,204 passed, 1 failed, 0 cancelled, 43 skipped
netcatty-ai-connected-final-runtime.log Connected code: 610 tests, 607 passed, 1 failed, 0 cancelled, 2 skipped
netcatty-ai-fixed-runtime.log After 0ba3ea606: 610 tests, 608 passed, 0 failed, 0 cancelled, 2 skipped
netcatty-ai-fixed-contract-suite.log After 0ba3ea606: all 88 contract/SDK/CLI tests passed
netcatty-ai-fixed-final-tests.log Before the later review corrections, 173ef5abb: 15,248 tests, 15,205 passed, 0 failed, 0 cancelled, 43 skipped
netcatty-ai-review-final-runtime.log After both review corrections: 614 tests, 612 passed, 0 failed, 0 cancelled, 2 skipped
netcatty-ai-review-final-tests.log Review-corrected head d81896b0d: 15,252 tests, 15,209 passed, 0 failed, 0 cancelled, 43 skipped, before the later base merge
netcatty-ai-review-merged-tests.log Integrated head 07eb68c5e: 15,255 tests, 15,212 passed, 0 failed, 0 cancelled, 43 skipped, before the selected-Note expiry correction
netcatty-ai-note-expiry-runtime.log Selected-Note expiry correction: 625 tests, 624 passed, 0 failed, 0 cancelled, 1 skipped

The ten Cursor failures in the second full run came from its missing SQLite native dependency. Rebuilding the existing dependency with the standard node-gyp --devdir option repaired them; the focused rerun passed 42 with 3 skipped. The sole connected full-run failure was a test expectation missing network.stream after network. The runtime failure was permission-copy coverage missing descriptions for ai.invoke, context.selected.list, context.selected.read, and network.stream. The parent reassigned these two hunks here. Commit 0ba3ea606988e95df256d449395adb7190b46fa0 adds accurate English and Chinese descriptions and updates the resource-scope test. The test checks both bounded optional stream declarations and rejection of the unbounded stream shorthand. Transport can consume this isolated two-file commit.

The permission-copy test, complete 88-test plugin contract/SDK/CLI suite, and 610-test runtime aggregate now pass (with 2 existing runtime skips). The earlier whole-repository red totals remain historical records; no passing whole-repository rerun is claimed from these focused repairs. Exact-head CI is recorded on the PR.

The final connected UI/visibility run passes all 3 tests. The 47 focused service/preload/runtime checks and 7 connected provider/renderer checks also pass. npm run check:plugin-contract passes. The six-file package validates and retains the SHA-256 recorded in the native recipe.

The connected AppView production build passes. Full lint after the permission repair reports no errors and seven existing warnings. The repository typecheck remains red with 773 diagnostics; the new adapter, component, hook, and visibility modules have no diagnostics. Native packaging's standard Electron rebuild stops at its hardcoded /home/agent/.electron-gyp cache outside this executor's writable roots. No dependency patch or home-directory override was used to bypass it.

The package build validates six files. The final artifact and native procedure are recorded in the native QA recipe. The source staging excludes build tooling and has no Node entry, network, filesystem, secret, storage, or terminal permission.

Independent review confirmed the malformed-reference revocation defect on afe831bfc. The two regression tests failed before the repair and pass afterward. The repair is retained in #3985 head 4882e0501. Further review was initially blocked. After receiving newer exact maintainer security-task evidence, the same worker completed its bounded read-only review of 173ef5abb without a repeated restriction or alternate route/model. It confirmed the earlier repair and found two additional P2 defects: runtime-bound budget keys and the sample's expired chat recovery.

Commit ee5a4a406 separates stable source budgets from strict runtime handle identity. Its fake-clock regression uses the actual source-binding class, replaces runtime binding objects, tests request and reserved-token budgets, isolates another source with the same public plugin ID, and checks the minute boundary. Both budget subtests fail on the reviewed source and pass after repair. Missing or invalid host budget identities fail closed.

Commit e1cb95ee8 reopens an expired sample chat before an explicit Send and adds Refresh model for changed-model or failed sessions. Refresh preserves the draft and does not send inference. A failed send/read is never automatically retried. Stop or page teardown invalidates pending opens and closes late sessions. Four controlled-time DOM regressions fail before the repair and pass afterward, including an uncertain inference outcome and Stop during reopening. They use no network or paid provider. Failed-before logs are /tmp/netcatty-ai-budget-review-before.log and /tmp/netcatty-ai-panel-review-before.log; passing logs use the corresponding -after.log names. Correction review independently confirmed the stable budget fix, but found the remaining selected-Note expiry path at 07eb68c5e.

The corrected broker suite passes all 17 tests, the sample's controlled-time panel suite passes all 4, and the connected provider/UI suite passes all 4. Full lint still reports zero errors and seven existing warnings; codegen drift and the production build pass. Two independent package builds reproduce SHA-256 c0f733697b8f43a8591f39c02aeea5d8b7b4bd544b642ed87e758e62d98696a5 for that earlier sample checkpoint.

Commit 9e7c9df58 adds explicit Choose reference recovery. The panel retains summary expiry locally, blocks an expired selection before Send, and never sends that local metadata as an authorization field. Refresh model never re-shares a Note. Re-selection opens the existing host chooser and resets the dropdown to No reference; the user selects the fresh handle and clicks Send explicitly.

The service's identical-selection cache now reuses only complete live summaries. After an explicit host re-selection, expired/revoked handles remain invalid and fresh eligible handles are issued. No authority lifetime or revocation rule is changed. Summary selection does not read text. Every new content handle still requires the existing per-context grant and exact-text review.

The new fake-clock panel regression uses the actual host service and selected context authority with a real synthetic Note handle. It covers expiry, model refresh without re-sharing, explicit re-selection, denial of the fresh content grant, cancellation of text review, and one successful explicit send with the draft preserved. It repeats the expiry/re-selection with both chat and Note lifetimes expired, then succeeds without a manual model refresh. A separate service regression reproduces the formerly empty same-Note cache after expiry. Both fail on 07eb68c5e; failed-before evidence is /tmp/netcatty-ai-note-expiry-panel-before.log and /tmp/netcatty-ai-note-expiry-service-before.log. The combined panel/service suite now passes all 10 tests; the connected installed UI/provider suite passes all 4. The replacement six-file artifact has SHA-256 c7d784d5c3bd2feb463afd58eae3a9594615395b168174cb8a3d0677584ae395. Independent verification of the latest correction and native acceptance remain pending. CI is not a substitute for either.

Native Electron launch on this executor aborts because chrome-sandbox is not root-owned mode 4755. No sandbox bypass or native retry was attempted. Native screenshots, video, and installed UI acceptance belong to the parent's working cloud GUI machine on the integrated exact head. No native acceptance is claimed here.