Skip to content

feat(extensions): add agent-authored Tool and UI packages - #3003

Draft
xxhZs wants to merge 48 commits into
apache:mainfrom
xxhZs:feat/extension-tool-contributions
Draft

feat(extensions): add agent-authored Tool and UI packages#3003
xxhZs wants to merge 48 commits into
apache:mainfrom
xxhZs:feat/extension-tool-contributions

Conversation

@xxhZs

@xxhZsxxhZs commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add the extension lifecycle kernel and runtime-host control plane for content-addressed, immutable Extension revisions
  • let human and agent workflows define, sandbox-test, activate, disable, update, and remove Tool and UI packages
  • run Extension Tools in managed workers with capability validation, invocation leases, last-good recovery, and restart persistence
  • replace the complete Desktop root with an admitted app.root UI Revision instead of reserving a fixed Maka workspace or panel
  • keep only a protected Maka UI kernel for revision selection, opaque-origin iframe isolation, strict CSP, last-good fallback, and emergency safe-mode recovery
  • grant a full-root Revision an explicit bounded Session capability (list/send/stop) without exposing credentials, filesystem authority, preload APIs, or arbitrary host access
  • compose UI and its visualized Tools from one external Extension directory into one immutable Revision; a successful Tool result updates the matching Revision-scoped UI state
  • return exact contributed Tool declarations and inputSchema from manage_tool activation/update so an Agent can invoke the visual Tool without guessing its contract

Architecture boundary

Maka owns the framework: window bootstrap, Extension Store/Binding lifecycle, immutable Revision admission, typed bridges, isolation, state authority, fallback, and recovery. Maka does not own the installed visual layout.

The user/Agent Extension owns the complete visible root: navigation, layout, conversation composer, visual Tool surface, art direction, and responsive behavior. Replacing or disabling that Revision removes the whole custom UI together; the compiled official UI is only the trusted fallback snapshot.

Maka remains one runtime and one repository. Tool and UI scripts are external Extension artifacts, not separate services or linked application libraries.

Graph workflow

Main plans -> subagents author/test candidates in parallel -> Main validates/activates/invokes -> Main rejects defects and dispatches repair nodes -> independent tools cross-check the result.

The warehouse-location Graph scenario completed with 7 define calls and 8 test calls from child sessions, plus 6 activation and 6 invocation calls from the Main session. No forbidden shell, file-editing, or direct-web tools were used.

Verification

  • @maka/runtime: 2829 passed, 12 skipped, 0 failed
  • @maka/runtime-host: 933 passed
  • Desktop: 841 passed
  • focused root-composition, Session bridge, import, Tool schema, combined UI+Tool Revision, rollback, and recovery tests passed
  • repository typecheck, lint/format checks, and production Renderer build passed
  • visual Electron checks passed at 1440×920 and 980×720 with no root-frame overflow and no official AppShell residue
  • real DeepSeek E2E passed with one external anime-style app.root + project_plan_commit Revision: 3 model requests, one manage_tool, one invoke_tool, then the complete UI committed 6 tasks and 4 risks

Part of #2973.

@ARE404ARE404 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against the repository standards and the invariants in #2973. The existing build and test suites pass (@maka/runtime: 2827/0, @maka/runtime-host: 931/0), but the inline findings below expose security and lifecycle cases not covered by those tests. Please also restore the required PR-template checklist and obtain an independent human review before merge, as required by CONTRIBUTING.md. This is submitted as a COMMENT review while the PR is still a draft.

env: workerEnvironment(),
profile: workerProfile(this.packageRevision.manifest),
pathContext: {
workspaceRoots: [canonicalCwd],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Keep installed revisions read-only during health checks. healthCheck() passes packageRevision.root as cwd, and this mapping turns that directory into :workspace_roots. A package requesting workspace: write can therefore rewrite its own entry during health check; later invocations still identify and import it under the original SHA revision. Please use a neutral/session workspace for health checks and expose the package root only as read-only runtime input.

description:
'Activate, update, stop, or delete a Tool package for the current session. Activation persists with the session until stopped.',
parameters: manageInput,
categoryHint: 'file_write',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] file_write understates what activate/update does. Controller convergence imports the package and executes its health check under the manifest-requested workspace/network sandbox, so arbitrary package code can run after only a file-write approval. Please split these operations or gate them with an execution category/facts that reflect the package manifest, like test_tool does.

return {
kind: 'enable',
bindingId: requireEntityId(input.bindingId, 'extension bindingId'),
scopeId: requireEntityId(input.scopeId, 'extension scopeId'),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] This protocol decoder uses generic requireEntityId, whose grammar rejects . and :, while the canonical extension/scope validators intentionally allow dotted extension IDs and dotted/colon scope IDs. For example, the supported dev.maka.calculator package can be installed internally but fail external response decoding. Please use the shared extension/scope identity validators consistently in every input and projection decoder.

throw error;
}
const resolveModelTools = (): readonly MakaTool[] =>
input.extensions?.resolveTools(input.context.sessionId, modelComposition.tools, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Extension tools are appended after createInteractiveRunComposer has already applied the final Plan, Deep Research, hosted-profile, permission, and product filters. This can re-expose extension tools in modes where the final composition deliberately removed tools. Please feed contributions into the canonical composition/filter pipeline rather than appending them afterward.

sourceRevisions: resolved.sourceRevisions,
baseSystemPromptHash: stableHash(resolved.text ?? ''),
toolCatalogHash: toolCatalogHash(modelComposition.tools),
toolCatalogHash: toolCatalogHash(tools),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] A catalog hash plus tool names/schema hashes does not pin the exact extension revision used by an admitted Run. Two revisions with identical schemas are indistinguishable, and update/stop can retire the activation referenced by the current send snapshot. Please persist the extension ID/revision (or a canonical composition digest) and retain those revisions until the Run reaches a terminal state so recovery can reproduce the exact composition.

export class ToolPackageStore {
readonly root: string;

constructor(controlDirectory: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Agent-authored packages are stored in the Host-global control directory without session/owner identity. define_tool and inspection also do not bind ownership, so another session can discover and activate the package, and session disposal does not remove it. #2973 calls for the initial agent-authored surface to be session-scoped; please persist/enforce ownership and clean it up with the owning session.

): Promise<ExecutionRuntimeHostComposition> {
const stores = await openInteractiveExecutionStoresForWrite(context.owner.lease);
await stores.sessionStore.ready();
const extensions = new HostExtensionRuntime();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] The production registry is created without protected core tool names. A package declaring Read, manage_tool, or another protected name can therefore install and activate successfully; the conflict is discovered only during a later model composition, after the binding has been persisted. Please reject protected-name conflicts before the activation commit and construct the production registry with the authoritative core-name set.

.find(({ name }) => name === input.toolName);
if (!tool) throw new Error(`Active session Tool was not found: ${input.toolName}`);
await validateArgs(tool, input.args);
return tool.impl(input.args, context);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Calling tool.impl() directly bypasses the contributed tool's canonical Tool Runtime path, including its own permission category, recovery mode, durable settlement/identity, and product routing. invoke_tool should dispatch through the same stable Tool Runtime bridge used by normal model tool calls.

const [program, ...args] = transformed.exec.argv;
if (!program)
throw new ToolPackageWorkerError('sandbox_unavailable', 'Sandbox launch is empty');
const child = spawn(program, args, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] The worker has wall-time and output bounds, but this launch has no explicit heap/CPU/process-count limit. A package can exhaust Host memory or create child processes before the timeout, which does not meet #2973's resource-limited requirement. Please add bounded Node heap and sandbox-level CPU/process limits, including descendants.

@xxhZsxxhZs changed the title feat(runtime): add agent-authored extension toolsfeat(extensions): add agent-authored Tool and UI packagesAug 14, 2026
@xxhZs

Copy link
Copy Markdown
ContributorAuthor

Test case: website → Graph-authored presentation → restored Maka UI

This prompt-driven E2E validates the complete dynamic capability loop with a real source page: Building effective agents.

Flow

  1. Main inspects the available UI, Tool, and subagent capabilities.
  2. One Agent Graph fans out three bounded workers in parallel:
    • Content: fetches the real page and produces a five-slide Chinese deck.
    • Tool: defines and tests prepare_narration for validation, sentence splitting, and timing.
    • UI: defines a full app.root presentation UI with Morph transitions, subtitles, browser speech synthesis, and the bounded Session Bridge.
  3. Main joins the three results, activates the exact Tool/UI revisions, invokes the Tool, and publishes the resulting deck once.
  4. The conversation contracts into a right rail while the PPT expands, speaks with real system audio, and advances all five slides automatically.
  5. After the final sentence, the UI performs the reverse Morph and sends a completion signal to the same Session. Main calls manage_ui stop once; removing the active root Binding restores the official Maka composer without a Git or Revision rollback.

Result

  • Graph workers: 3/3 completed
  • Model requests: 27; tokens: 561,257; cost: $0.0373876
  • define_ui=1, define_tool=1, publish_ui_state=1, manage_ui=2 (activate + stop)
  • Five-slide narration, real audio, automatic page changes, forward/reverse Morph, and official UI restoration all passed
  • Repository build, Biome checks, and 84 focused tests passed
  • Implementation exercised: d80ccd2ad feat(graph): add bounded UI author routing

The 52.4-second video below is an accelerated summary of the original continuous 478.7-second audit recording; no successful or recovery stage was omitted.

Demo video

maka-graph-ppt-52s-demo.mp4

Simplified flow

Simplified MAKA dynamic presentation flow

@coderabbitai

coderabbitaiBot commented Aug 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c445b750-e318-4f68-84d0-fae9f98f7bc3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@M4n5terM4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

English

Thanks for building this out. I reviewed the implementation against #2973 and the current multi-Host Runtime Host architecture.

The lifecycle kernel, typed contribution registries, content-addressed packages, out-of-process Tool workers, and fixed extension Domain Module are solid foundations. However, I do not think the unified Tool/UI plugin platform is ready to merge in its current form. The following are blocking:

  1. UI ownership conflicts with the multi-Host Desktop architecture.
    UI selection uses one Host-owned desktop-ui scope, ui-extension-frame-protocol-main.ts keeps one module-global activeClient, and an app.root contribution may replace the entire Desktop. The current Desktop keeps multiple Hosts connected and identifies Sessions by (hostId, sessionId), so one State Root cannot authoritatively own a Client-global root. The frame URL and bridge also lack Host identity. UI package selection, safe mode, and root/slot state should be Client-owned; Host-linked contributions must be explicitly scoped by hostId/targetEpoch. I also recommend keeping the official root protected initially and starting with slots/overlays, as proposed in #2973.

  2. An admitted Run is not pinned to an exact Extension Composition.
    execution-model-composition.ts appends Extension Tools after the canonical collaboration/product filters. The durable Run Composition records Tool names and schema hashes, but not Extension IDs, revisions, or the Extension composition digest. Two revisions with identical schemas but different implementations are therefore indistinguishable. In addition, update/stop can retire an activation already referenced by an in-flight send, causing a later Tool call from that Run to fail as retired. Please resolve Extensions before the final Tool filtering pipeline, persist the exact revision composition at Run admission, and hold activation leases until the Run reaches a terminal state.

  3. Health checks can mutate their own immutable revision.
    ToolPackageActivation.healthCheck() uses packageRevision.root as both cwd and :workspace_roots. A package declaring workspace: write can modify its installed entry or add files while being imported for health checking, while the health check itself still succeeds. I reproduced this with a fault-injection test: the package wrote into its content-addressed revision, and only a later store.load() detected the hash mismatch. Health checks need a dedicated scratch cwd and a read-only package mount.

  4. Binding and Configuration persistence is not atomic.
    HostExtensionController.#persist() commits the Binding document before the Configuration document. I reproduced a case where the first write succeeded and the second failed: the enable operation returned persistence_failed and did not activate immediately, but after Host restart the persisted Binding was recovered and activated. This is an unreported partial commit. Binding plus Configuration need one transactional authority, or at minimum this path must become commit_outcome_unknown and drain the Host.

  5. Session-scoped Extension ownership is not connected to Session retirement.
    manage_tool persists bindings under the Session ID, but HostSessionRetirementCoordinator has no Extension retirement participant, and production code never calls disposeScope() for a removed Session. The binding and registration can therefore survive Session deletion and Host restart, retain revisions indefinitely, and become ownerless. Agent-authored packages are also globally discoverable by other Sessions. Immutable blobs may be globally deduplicated, but ownership/reference authorization and cleanup need to follow the owning Session or revision family through the durable retirement pipeline.

  6. The permission and isolation boundary is still incomplete.
    manage_tool and manage_ui are categorized as file_write, although activation/update can execute package health code and enable network access, Host methods, or the complete Session bridge. invoke_tool still calls tool.impl() directly instead of using the canonical Tool Runtime path. Production also does not provide authoritative protectedToolNames, so Core-name conflicts may be committed and discovered only during later model composition. Finally, workers have wall-time/output limits but no explicit heap, CPU, or process/descendant limits.

Most of these align with still-unresolved inline review threads. The earlier identity-decoder issue appears fixed, but the outdated invoke_tool thread remains semantically applicable because the new code still invokes the implementation directly.

Validation performed on head f007f70f1:

  • full repository build passed;
  • 45 focused Extension/Tool/UI tests passed;
  • fault injection reproduced both the revision self-mutation and partial persistence commit;
  • merging current main produces five direct conflicts, including Desktop root, Run composition, AI SDK, and UI navigation;
  • the PR is still draft/DIRTY, with no current usable CI result.

I recommend splitting this into independently reviewable phases: lifecycle kernel, Host-owned trusted Tool slice, exact Run composition and leases, Session-owned agent authoring, and then a separate Client-owned UI Extension proposal. The current implementation should not be approved as the final Runtime Host plugin architecture.

中文

感谢完成这套实现。我按照 #2973 以及当前 multi-Host Runtime Host 架构,对代码进行了审查。

Lifecycle Kernel、类型化 Contribution Registry、content-addressed package、进程外 Tool Worker,以及固定的 extension Domain Module 都是很好的基础。不过,目前这套统一 Tool/UI 插件平台还不适合合入,以下问题属于阻断项:

  1. UI 所有权与 multi-Host Desktop 架构冲突。
    UI 选择依赖一个 Host-owned desktop-ui scope,ui-extension-frame-protocol-main.ts 只有一个模块级 activeClient,同时 app.root Contribution 可以替换整个 Desktop。当前 Desktop 会同时连接多个 Host,Session 身份是 (hostId, sessionId),因此单个 State Root 不应拥有 Client-global root。Frame URL 和 bridge 也没有携带 Host 身份。UI package 选择、safe mode、root/slot 状态应由 Client 管理;与 Host 关联的 Contribution 必须显式使用 hostId/targetEpoch。同时建议遵循 #2973,初期保护官方 root,只开放 slots/overlays。

  2. 已经 admission 的 Run 没有固定到 exact Extension Composition。
    execution-model-composition.ts 在标准 collaboration/product Tool 过滤完成后才追加 Extension Tools。持久化的 Run Composition 只记录 Tool 名称和 schema hash,没有记录 Extension ID、revision 或 Extension composition digest,因此两个 schema 相同但实现不同的 revision 无法区分。另外,update/stop 可以 retire 已经被进行中 send 引用的 activation,导致该 Run 稍后调用 Tool 时收到 retired。Extension 应在最终 Tool 过滤前参与组合,在 Run admission 时持久化 exact revision composition,并持有 activation lease 直到 Run terminal。

  3. 健康检查可以修改自己的 immutable revision。
    ToolPackageActivation.healthCheck() 同时把 packageRevision.root 用作 cwd:workspace_roots。声明 workspace: write 的 package 可以在 health check import 阶段修改已安装 entry 或添加文件,而健康检查本身仍然成功。我通过故障注入复现了这一点:package 成功写入 content-addressed revision,直到之后调用 store.load() 才检测到 hash mismatch。健康检查必须使用独立 scratch cwd,并且 package root 必须始终只读。

  4. Binding 与 Configuration 持久化不是原子的。
    HostExtensionController.#persist() 先提交 Binding 文档,再提交 Configuration 文档。我复现了第一步成功、第二步失败的情况:enable 返回 persistence_failed,当下也没有激活,但 Host 重启后会恢复已经提交的 Binding 并自动激活。这是一个未被报告的 partial commit。Binding 与 Configuration 需要一个事务性权威;至少该路径应返回 commit_outcome_unknown 并 drain Host。

  5. Session-scoped Extension ownership 没有接入 Session retirement。
    manage_tool 使用 Session ID 持久化 Binding,但 HostSessionRetirementCoordinator 没有 Extension retirement participant,生产代码也不会在 Session 删除时调用 disposeScope()。因此 Binding 和 registration 可以在 Session 删除及 Host 重启后继续存在,永久占用 revision,并失去 owner。Agent-authored package 也可以被其他 Session 全局发现。Immutable blob 可以全局去重,但 ownership/reference authorization 和清理必须跟随所属 Session 或 revision family 进入持久化 retirement pipeline。

  6. 权限和隔离边界仍未闭合。
    manage_toolmanage_ui 只被归类为 file_write,但 activate/update 会执行 package health code,并可能启用网络、Host methods 或完整 Session bridge。invoke_tool 仍直接调用 tool.impl(),没有经过标准 Tool Runtime。生产环境也没有提供 authoritative protectedToolNames,因此 Core Tool 名称冲突可能先被提交,之后才在 model composition 阶段暴露。Worker 虽然有 wall-time/output 限制,但没有明确的 heap、CPU、process/descendant 限制。

这些问题大多与当前尚未解决的 inline review thread 一致。之前的 identity decoder 问题已经修复;不过 invoke_tool thread 虽然因为代码移动显示为 outdated,其问题仍然存在,因为新代码依然直接调用 implementation。

我在 head f007f70f1 上完成了以下验证:

  • 全仓 build 通过;
  • 45 个 Extension/Tool/UI 聚焦测试全部通过;
  • 故障注入稳定复现了 revision 自修改和持久化 partial commit;
  • 合并当前 main 会产生 5 个直接冲突,涉及 Desktop root、Run composition、AI SDK 和 UI navigation;
  • PR 仍是 draft/DIRTY,目前没有可用的最新 CI 结果。

建议将其拆成可独立审查的阶段:Lifecycle Kernel、Host-owned trusted Tool vertical slice、exact Run composition 与 activation lease、Session-owned agent authoring,最后再单独提出 Client-owned UI Extension 设计。当前实现不应作为最终 Runtime Host 插件架构获得批准。

@M4n5ter

Copy link
Copy Markdown
Member
English

One architectural addendum after comparing this design with DeepSeek Harness and Cordis:

The current implementation is not literally “insert-only”: it exposes stop, removeBinding, disposeScope, and uninstall, and the lifecycle kernel correctly retracts activation-owned effects and dependents. However, these APIs currently provide registry-level removal, not yet a complete Runtime Host safe-unplug contract.

The missing distinction is between:

  1. stopping admission of new work;
  2. retiring a mounted Activation;
  3. releasing resources after already-admitted work finishes; and
  4. physically garbage-collecting an immutable Revision.

Today, stop/remove can immediately retire an Activation that an admitted send() may still reference. The design document explicitly leaves larger Run-composition pinning and in-flight draining as later decisions. Likewise, Session removal does not currently drive disposeScope(sessionId), and package uninstallation does not account for active Run leases or durable recovery references. This is the common lifecycle cause behind the exact Run Composition and Session ownership blockers in my earlier review.

Cordis addresses the mount/unmount symmetry by giving every loaded plugin instance a first-class Fiber: registrations, listeners, services, and child plugins are owned effects, dependency loss unloads consumers, and fiber.dispose() can be awaited until recursive cleanup finishes. Its config tree also uses stable entry IDs so update, disable, and removal are symmetric composition operations:

Harness applies those primitives to agent-authored dynamic Plugins with explicit paired operations: define/undefine and run/stop. stop awaits Host Fiber disposal to quiescence and retracts the exact Client activation by its run identity. More importantly, its long-running-operation contract distinguishes provider removal from accepted-run ownership: removing a provider prevents new starts but does not revoke runs already returned to their holders.

I recommend making the equivalent distinction explicit in Maka:

Installed Revision
-> Mounted Activation
-> Draining (reject new Runs, retain existing leases)
-> Unmounted (after leases reach zero and disposal settles)
-> Garbage Collected (after no live or durable reference remains)

Concretely, this likely requires:

  • a first-class ExtensionMount that owns every contribution/effect and exposes an awaitable dispose();
  • a RunExtensionLease captured at Run admission, containing the exact Extension IDs, revisions, composition digest, and implementation references;
  • update/stop/remove to switch admission away from an Activation while allowing already-admitted Runs to finish against their leased revision;
  • Session retirement to participate in Extension scope disposal and durable binding/configuration cleanup;
  • physical package uninstall/GC to require no desired binding, live Activation, pending transition, Run lease, or durable recovery reference;
  • Client UI mounts to be owned by explicit (clientId, hostId, targetEpoch) identities rather than one process-global UI owner.

I would not copy Cordis wholesale. Its generic Loader removal starts Fiber disposal without awaiting it, while Runtime Host teardown should follow Harness’s stricter “dispose reaches quiescence” rule. Harness’s dynamic packages are also process-local, non-durable, and explicitly not protected by a true sandbox boundary. Maka still needs its protected Host kernel, durable reference accounting, permissions, and out-of-process isolation.

In short: the PR has removal APIs, but it still needs admission leases, ownership-driven teardown, and revision GC before Extensions can be considered safely hot-unpluggable.

中文

在对照 DeepSeek HarnessCordis 后,再补充一个架构层面的意见:

当前实现并非字面意义上的“只能插、不能拔”:它已经提供 stopremoveBindingdisposeScopeuninstall,Lifecycle Kernel 也会撤销 Activation-owned effect 及其依赖方。不过,这些 API 目前完成的是注册表层面的移除,还没有构成 Runtime Host 层面完整的安全热拔契约

当前缺少的是对以下阶段的明确区分:

  1. 停止接纳新的工作;
  2. retire 已挂载的 Activation;
  3. 等待已经 admission 的工作完成后再释放资源;
  4. 最后物理回收 immutable Revision。

现在的 stop/remove 可以立即 retire 一个仍可能被已接纳 send() 引用的 Activation。设计文档也明确把更大范围的 Run Composition 固定和 in-flight drain 留作后续决定。与此同时,Session 删除还不会驱动 disposeScope(sessionId),package uninstall 也没有考虑 active Run lease 或 durable recovery reference。这其实是我之前提出的 exact Run Composition 和 Session ownership 两项 blocker 的共同生命周期根因。

Cordis 通过为每个已加载插件实例建立一等 Fiber 来保证 mount/unmount 对称:注册、监听器、Service 和子插件都是 owner-scoped effect;依赖消失会卸载消费者;fiber.dispose() 可以等待递归清理完成。它的配置树也通过稳定 entry ID,让 update、disable 和 remove 成为对称的组合操作:

Harness 在此基础上为 agent-authored dynamic Plugin 提供了成对操作:define/undefinerun/stopstop 会等待 Host Fiber 清理到 quiescence,并通过精确 run identity 撤回对应的 Client Activation。更重要的是,它对长期运行操作明确区分 provider removal 与 accepted-run ownership:移除 provider 只会阻止新的 start,不会撤销已经交给 holder 的 run。

建议 Maka 也明确建立对应的状态模型:

Installed Revision
-> Mounted Activation
-> Draining(拒绝新 Run,保留已有 lease)
-> Unmounted(lease 清零且 dispose 完成)
-> Garbage Collected(不存在任何 live 或 durable reference)

具体可能需要:

  • 一等 ExtensionMount,统一拥有所有 Contribution/effect,并提供可等待的 dispose()
  • 在 Run admission 时创建 RunExtensionLease,固定 exact Extension IDs、revisions、composition digest 和 implementation references;
  • update/stop/remove 只切走新的 admission,已接纳 Run 继续使用其 leased revision,直到完成;
  • Session retirement 参与 Extension scope disposal,以及 durable binding/configuration 清理;
  • 只有在不存在 desired binding、live Activation、pending transition、Run lease 或 durable recovery reference 时,才允许物理 uninstall/GC package;
  • Client UI mount 使用明确的 (clientId, hostId, targetEpoch) owner,而不是进程级全局 UI owner。

不建议完整照搬 Cordis。它的通用 Loader remove 会启动 Fiber disposal,但不会等待其完成;Runtime Host 应采用 Harness 更严格的“dispose 必须达到 quiescence”契约。Harness 的 dynamic package 也是进程内、非持久化的,并明确不具备真正的 sandbox 安全边界。Maka 仍然需要受保护的 Host kernel、durable reference accounting、权限控制和进程外隔离。

简而言之:这个 PR 已经有 removal API,但在补齐 admission lease、ownership-driven teardown 和 revision GC 之前,还不能认为 Extension 真正支持安全热拔。

@me2seeks

Copy link
Copy Markdown
Contributor

Really like the observe-mode core events — maka.session.created / maka.agent.status are exactly the seams I've been hoping for on the terminal side.

One gap I hit while wiring Maka into herdr: session.created fires once when the Host creates a session, but nothing fires when a TUI client attaches to an existing session (maka --resume <id>, /session <id>). For a terminal runtime that is the event that matters — that is the moment the pane knows its session and can report it for restore. Right now there is no attach event, no start source (startup/resume/select), and because listeners run in the Host process, a shared or remote Host cannot attribute the session back to the pane anyway.

Would a maka.session.attached observe event make sense, dispatched from the client attach/resume/switch path with { sessionId, startSource }? Keeping it observe/fail-open like the other session events would be enough. If it is in scope I am happy to put up a small stacked PR on top of this.

@github-actionsgithub-actionsBot added the effort/XL Over 1000 readable lines label Aug 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/XLOver 1000 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@xxhZs@M4n5ter@me2seeks@ARE404