Skip to content

fix(runtime-host): restore desktop Computer Use capability - #2233

Merged
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability
Aug 5, 2026
Merged

fix(runtime-host): restore desktop Computer Use capability#2233
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability

Conversation

@M4n5ter

@M4n5terM4n5ter commented Aug 5, 2026

Copy link
Copy Markdown
Member
English

Summary

  • accept the real Desktop Computer Use tool schema in the Client Capability protocol
  • keep pre-dispatch rejections, including Client Capability boundary and subagent admission failures, on the generic call/response lane
  • request the native macOS Accessibility grant once, on the first explicit Computer Use attempt
  • cover the real Desktop offer, protocol boundary, durable rejection path, and native backend request

Root cause

Desktop only publishes its Computer Use offer when the native backend is available. Once a real maka-cu backend was present, its draft-07 tuple schemas and tool description exceeded assumptions in the Client Capability decoder, so registration failed and the Host exposed only the Agent and Browser groups.

A separate failure existed when a call was rejected before dispatch, including Client Capability boundary and saturated subagent admission failures: the call received a durable operation id before durable preparation. Its synthetic response then had no matching durable call, producing an orphan response and draining the Runtime Host.

Finally, the native protocol already supported an explicit Accessibility prompt, but Maka used only silent permission probes. The first explicit Computer Use attempt now requests that grant once while routine per-action preflight remains silent.

Validation

  • @maka/runtime: 3179 passed, 9 skipped
  • @maka/computer-use: 134 passed
  • @maka/runtime-host: 693 passed
  • @maka/desktop: 1738 passed
  • Runtime, Runtime Host, Computer Use, and Desktop type checks
  • Biome and git diff --check
  • real host-backed Desktop flow: discover the Desktop Computer Use group, load maka_computer, grant Accessibility, invoke list_apps, and receive the native app list

Follow-up

  • native Screen Recording consent is tracked in maka-cu#1
中文

概述

  • 让 Client Capability 协议接受 Desktop Computer Use 的真实工具 schema
  • 让 dispatch 前被拒绝的调用(包括 Client Capability 边界和 subagent admission 失败)继续使用普通 call/response lane
  • 在首次明确使用 Computer Use 时,仅主动请求一次 macOS Accessibility 权限
  • 覆盖真实 Desktop offer、协议边界、durable 拒绝路径与 native backend 授权请求

根因

Desktop 仅在 native backend 可用时发布 Computer Use offer。接入真实 maka-cu 后,其 draft-07 tuple schema 和工具描述超出了 Client Capability decoder 原有假设,导致注册失败,Host 最终只能暴露 Agent 和 Browser 两个工具组。

另一个问题出现在调用于 dispatch 前被拒绝时,包括 Client Capability 边界拒绝和 subagent admission 饱和:调用在 durable prepare 前已经获得 operation id,随后生成的 synthetic response 找不到对应 durable call,形成 orphan response 并使 Runtime Host 进入 draining。

此外,native 协议已经支持显式触发 Accessibility 授权,但 Maka 之前只执行静默检查。现在首次明确使用 Computer Use 时会请求一次授权,日常逐操作 preflight 仍保持静默。

验证

  • @maka/runtime:3179 passed,9 skipped
  • @maka/computer-use:134 passed
  • @maka/runtime-host:693 passed
  • @maka/desktop:1738 passed
  • Runtime、Runtime Host、Computer Use 与 Desktop typecheck
  • Biome 与 git diff --check
  • 真实 host-backed Desktop 链路:发现 Desktop Computer Use 组、加载 maka_computer、完成 Accessibility 授权、调用 list_apps 并取得 native 应用列表

后续

  • native Screen Recording 主动授权由 maka-cu#1 跟踪

@M4n5ter
M4n5ter marked this pull request as ready for review August 5, 2026 11:58
@M4n5ter
M4n5ter merged commit f8d2ac4 into mainAug 5, 2026
12 checks passed
@M4n5ter
M4n5ter deleted the fix/runtime-host-computer-use-capability branch August 5, 2026 11:58
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. An already
corrupt ledger refuses well-formed writes for a different reason, so it throws
`ToolLedgerCorruptionError` and keeps failing closed.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships.
Refs apache#2234
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces apache#2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in apache#2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs apache#2234
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
…2240)
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (#2234).
#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. #2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else #2233 restored is untouched.
Two things #2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces #2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in #2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs #2234
Sign up for freeto 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

@M4n5ter
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(runtime-host): restore desktop Computer Use capability by M4n5ter · Pull Request #2233 · apache/maka · GitHub
Skip to content

fix(runtime-host): restore desktop Computer Use capability - #2233

Merged
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability
Aug 5, 2026
Merged

fix(runtime-host): restore desktop Computer Use capability#2233
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability

Conversation

@M4n5ter

@M4n5terM4n5ter commented Aug 5, 2026

Copy link
Copy Markdown
Member
English

Summary

  • accept the real Desktop Computer Use tool schema in the Client Capability protocol
  • keep pre-dispatch rejections, including Client Capability boundary and subagent admission failures, on the generic call/response lane
  • request the native macOS Accessibility grant once, on the first explicit Computer Use attempt
  • cover the real Desktop offer, protocol boundary, durable rejection path, and native backend request

Root cause

Desktop only publishes its Computer Use offer when the native backend is available. Once a real maka-cu backend was present, its draft-07 tuple schemas and tool description exceeded assumptions in the Client Capability decoder, so registration failed and the Host exposed only the Agent and Browser groups.

A separate failure existed when a call was rejected before dispatch, including Client Capability boundary and saturated subagent admission failures: the call received a durable operation id before durable preparation. Its synthetic response then had no matching durable call, producing an orphan response and draining the Runtime Host.

Finally, the native protocol already supported an explicit Accessibility prompt, but Maka used only silent permission probes. The first explicit Computer Use attempt now requests that grant once while routine per-action preflight remains silent.

Validation

  • @maka/runtime: 3179 passed, 9 skipped
  • @maka/computer-use: 134 passed
  • @maka/runtime-host: 693 passed
  • @maka/desktop: 1738 passed
  • Runtime, Runtime Host, Computer Use, and Desktop type checks
  • Biome and git diff --check
  • real host-backed Desktop flow: discover the Desktop Computer Use group, load maka_computer, grant Accessibility, invoke list_apps, and receive the native app list

Follow-up

  • native Screen Recording consent is tracked in maka-cu#1
中文

概述

  • 让 Client Capability 协议接受 Desktop Computer Use 的真实工具 schema
  • 让 dispatch 前被拒绝的调用(包括 Client Capability 边界和 subagent admission 失败)继续使用普通 call/response lane
  • 在首次明确使用 Computer Use 时,仅主动请求一次 macOS Accessibility 权限
  • 覆盖真实 Desktop offer、协议边界、durable 拒绝路径与 native backend 授权请求

根因

Desktop 仅在 native backend 可用时发布 Computer Use offer。接入真实 maka-cu 后,其 draft-07 tuple schema 和工具描述超出了 Client Capability decoder 原有假设,导致注册失败,Host 最终只能暴露 Agent 和 Browser 两个工具组。

另一个问题出现在调用于 dispatch 前被拒绝时,包括 Client Capability 边界拒绝和 subagent admission 饱和:调用在 durable prepare 前已经获得 operation id,随后生成的 synthetic response 找不到对应 durable call,形成 orphan response 并使 Runtime Host 进入 draining。

此外,native 协议已经支持显式触发 Accessibility 授权,但 Maka 之前只执行静默检查。现在首次明确使用 Computer Use 时会请求一次授权,日常逐操作 preflight 仍保持静默。

验证

  • @maka/runtime:3179 passed,9 skipped
  • @maka/computer-use:134 passed
  • @maka/runtime-host:693 passed
  • @maka/desktop:1738 passed
  • Runtime、Runtime Host、Computer Use 与 Desktop typecheck
  • Biome 与 git diff --check
  • 真实 host-backed Desktop 链路:发现 Desktop Computer Use 组、加载 maka_computer、完成 Accessibility 授权、调用 list_apps 并取得 native 应用列表

后续

  • native Screen Recording 主动授权由 maka-cu#1 跟踪

@M4n5ter
M4n5ter marked this pull request as ready for review August 5, 2026 11:58
@M4n5ter
M4n5ter merged commit f8d2ac4 into mainAug 5, 2026
12 checks passed
@M4n5ter
M4n5ter deleted the fix/runtime-host-computer-use-capability branch August 5, 2026 11:58
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. An already
corrupt ledger refuses well-formed writes for a different reason, so it throws
`ToolLedgerCorruptionError` and keeps failing closed.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships.
Refs apache#2234
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces apache#2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in apache#2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs apache#2234
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
…2240)
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (#2234).
#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. #2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else #2233 restored is untouched.
Two things #2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces #2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in #2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs #2234
Sign up for freeto 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

@M4n5ter
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(runtime-host): restore desktop Computer Use capability by M4n5ter · Pull Request #2233 · apache/maka · GitHub
Skip to content

fix(runtime-host): restore desktop Computer Use capability - #2233

Merged
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability
Aug 5, 2026
Merged

fix(runtime-host): restore desktop Computer Use capability#2233
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability

Conversation

@M4n5ter

@M4n5terM4n5ter commented Aug 5, 2026

Copy link
Copy Markdown
Member
English

Summary

  • accept the real Desktop Computer Use tool schema in the Client Capability protocol
  • keep pre-dispatch rejections, including Client Capability boundary and subagent admission failures, on the generic call/response lane
  • request the native macOS Accessibility grant once, on the first explicit Computer Use attempt
  • cover the real Desktop offer, protocol boundary, durable rejection path, and native backend request

Root cause

Desktop only publishes its Computer Use offer when the native backend is available. Once a real maka-cu backend was present, its draft-07 tuple schemas and tool description exceeded assumptions in the Client Capability decoder, so registration failed and the Host exposed only the Agent and Browser groups.

A separate failure existed when a call was rejected before dispatch, including Client Capability boundary and saturated subagent admission failures: the call received a durable operation id before durable preparation. Its synthetic response then had no matching durable call, producing an orphan response and draining the Runtime Host.

Finally, the native protocol already supported an explicit Accessibility prompt, but Maka used only silent permission probes. The first explicit Computer Use attempt now requests that grant once while routine per-action preflight remains silent.

Validation

  • @maka/runtime: 3179 passed, 9 skipped
  • @maka/computer-use: 134 passed
  • @maka/runtime-host: 693 passed
  • @maka/desktop: 1738 passed
  • Runtime, Runtime Host, Computer Use, and Desktop type checks
  • Biome and git diff --check
  • real host-backed Desktop flow: discover the Desktop Computer Use group, load maka_computer, grant Accessibility, invoke list_apps, and receive the native app list

Follow-up

  • native Screen Recording consent is tracked in maka-cu#1
中文

概述

  • 让 Client Capability 协议接受 Desktop Computer Use 的真实工具 schema
  • 让 dispatch 前被拒绝的调用(包括 Client Capability 边界和 subagent admission 失败)继续使用普通 call/response lane
  • 在首次明确使用 Computer Use 时,仅主动请求一次 macOS Accessibility 权限
  • 覆盖真实 Desktop offer、协议边界、durable 拒绝路径与 native backend 授权请求

根因

Desktop 仅在 native backend 可用时发布 Computer Use offer。接入真实 maka-cu 后,其 draft-07 tuple schema 和工具描述超出了 Client Capability decoder 原有假设,导致注册失败,Host 最终只能暴露 Agent 和 Browser 两个工具组。

另一个问题出现在调用于 dispatch 前被拒绝时,包括 Client Capability 边界拒绝和 subagent admission 饱和:调用在 durable prepare 前已经获得 operation id,随后生成的 synthetic response 找不到对应 durable call,形成 orphan response 并使 Runtime Host 进入 draining。

此外,native 协议已经支持显式触发 Accessibility 授权,但 Maka 之前只执行静默检查。现在首次明确使用 Computer Use 时会请求一次授权,日常逐操作 preflight 仍保持静默。

验证

  • @maka/runtime:3179 passed,9 skipped
  • @maka/computer-use:134 passed
  • @maka/runtime-host:693 passed
  • @maka/desktop:1738 passed
  • Runtime、Runtime Host、Computer Use 与 Desktop typecheck
  • Biome 与 git diff --check
  • 真实 host-backed Desktop 链路:发现 Desktop Computer Use 组、加载 maka_computer、完成 Accessibility 授权、调用 list_apps 并取得 native 应用列表

后续

  • native Screen Recording 主动授权由 maka-cu#1 跟踪

@M4n5ter
M4n5ter marked this pull request as ready for review August 5, 2026 11:58
@M4n5ter
M4n5ter merged commit f8d2ac4 into mainAug 5, 2026
12 checks passed
@M4n5ter
M4n5ter deleted the fix/runtime-host-computer-use-capability branch August 5, 2026 11:58
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. An already
corrupt ledger refuses well-formed writes for a different reason, so it throws
`ToolLedgerCorruptionError` and keeps failing closed.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships.
Refs apache#2234
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces apache#2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in apache#2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs apache#2234
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
…2240)
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (#2234).
#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. #2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else #2233 restored is untouched.
Two things #2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces #2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in #2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs #2234
Sign up for freeto 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

@M4n5ter
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(runtime-host): restore desktop Computer Use capability by M4n5ter · Pull Request #2233 · apache/maka · GitHub
Skip to content

fix(runtime-host): restore desktop Computer Use capability - #2233

Merged
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability
Aug 5, 2026
Merged

fix(runtime-host): restore desktop Computer Use capability#2233
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability

Conversation

@M4n5ter

@M4n5terM4n5ter commented Aug 5, 2026

Copy link
Copy Markdown
Member
English

Summary

  • accept the real Desktop Computer Use tool schema in the Client Capability protocol
  • keep pre-dispatch rejections, including Client Capability boundary and subagent admission failures, on the generic call/response lane
  • request the native macOS Accessibility grant once, on the first explicit Computer Use attempt
  • cover the real Desktop offer, protocol boundary, durable rejection path, and native backend request

Root cause

Desktop only publishes its Computer Use offer when the native backend is available. Once a real maka-cu backend was present, its draft-07 tuple schemas and tool description exceeded assumptions in the Client Capability decoder, so registration failed and the Host exposed only the Agent and Browser groups.

A separate failure existed when a call was rejected before dispatch, including Client Capability boundary and saturated subagent admission failures: the call received a durable operation id before durable preparation. Its synthetic response then had no matching durable call, producing an orphan response and draining the Runtime Host.

Finally, the native protocol already supported an explicit Accessibility prompt, but Maka used only silent permission probes. The first explicit Computer Use attempt now requests that grant once while routine per-action preflight remains silent.

Validation

  • @maka/runtime: 3179 passed, 9 skipped
  • @maka/computer-use: 134 passed
  • @maka/runtime-host: 693 passed
  • @maka/desktop: 1738 passed
  • Runtime, Runtime Host, Computer Use, and Desktop type checks
  • Biome and git diff --check
  • real host-backed Desktop flow: discover the Desktop Computer Use group, load maka_computer, grant Accessibility, invoke list_apps, and receive the native app list

Follow-up

  • native Screen Recording consent is tracked in maka-cu#1
中文

概述

  • 让 Client Capability 协议接受 Desktop Computer Use 的真实工具 schema
  • 让 dispatch 前被拒绝的调用(包括 Client Capability 边界和 subagent admission 失败)继续使用普通 call/response lane
  • 在首次明确使用 Computer Use 时,仅主动请求一次 macOS Accessibility 权限
  • 覆盖真实 Desktop offer、协议边界、durable 拒绝路径与 native backend 授权请求

根因

Desktop 仅在 native backend 可用时发布 Computer Use offer。接入真实 maka-cu 后,其 draft-07 tuple schema 和工具描述超出了 Client Capability decoder 原有假设,导致注册失败,Host 最终只能暴露 Agent 和 Browser 两个工具组。

另一个问题出现在调用于 dispatch 前被拒绝时,包括 Client Capability 边界拒绝和 subagent admission 饱和:调用在 durable prepare 前已经获得 operation id,随后生成的 synthetic response 找不到对应 durable call,形成 orphan response 并使 Runtime Host 进入 draining。

此外,native 协议已经支持显式触发 Accessibility 授权,但 Maka 之前只执行静默检查。现在首次明确使用 Computer Use 时会请求一次授权,日常逐操作 preflight 仍保持静默。

验证

  • @maka/runtime:3179 passed,9 skipped
  • @maka/computer-use:134 passed
  • @maka/runtime-host:693 passed
  • @maka/desktop:1738 passed
  • Runtime、Runtime Host、Computer Use 与 Desktop typecheck
  • Biome 与 git diff --check
  • 真实 host-backed Desktop 链路:发现 Desktop Computer Use 组、加载 maka_computer、完成 Accessibility 授权、调用 list_apps 并取得 native 应用列表

后续

  • native Screen Recording 主动授权由 maka-cu#1 跟踪

@M4n5ter
M4n5ter marked this pull request as ready for review August 5, 2026 11:58
@M4n5ter
M4n5ter merged commit f8d2ac4 into mainAug 5, 2026
12 checks passed
@M4n5ter
M4n5ter deleted the fix/runtime-host-computer-use-capability branch August 5, 2026 11:58
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. An already
corrupt ledger refuses well-formed writes for a different reason, so it throws
`ToolLedgerCorruptionError` and keeps failing closed.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships.
Refs apache#2234
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces apache#2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in apache#2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs apache#2234
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
…2240)
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (#2234).
#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. #2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else #2233 restored is untouched.
Two things #2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces #2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in #2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs #2234
Sign up for freeto 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

@M4n5ter
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(runtime-host): restore desktop Computer Use capability by M4n5ter · Pull Request #2233 · apache/maka · GitHub
Skip to content

fix(runtime-host): restore desktop Computer Use capability - #2233

Merged
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability
Aug 5, 2026
Merged

fix(runtime-host): restore desktop Computer Use capability#2233
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability

Conversation

@M4n5ter

@M4n5terM4n5ter commented Aug 5, 2026

Copy link
Copy Markdown
Member
English

Summary

  • accept the real Desktop Computer Use tool schema in the Client Capability protocol
  • keep pre-dispatch rejections, including Client Capability boundary and subagent admission failures, on the generic call/response lane
  • request the native macOS Accessibility grant once, on the first explicit Computer Use attempt
  • cover the real Desktop offer, protocol boundary, durable rejection path, and native backend request

Root cause

Desktop only publishes its Computer Use offer when the native backend is available. Once a real maka-cu backend was present, its draft-07 tuple schemas and tool description exceeded assumptions in the Client Capability decoder, so registration failed and the Host exposed only the Agent and Browser groups.

A separate failure existed when a call was rejected before dispatch, including Client Capability boundary and saturated subagent admission failures: the call received a durable operation id before durable preparation. Its synthetic response then had no matching durable call, producing an orphan response and draining the Runtime Host.

Finally, the native protocol already supported an explicit Accessibility prompt, but Maka used only silent permission probes. The first explicit Computer Use attempt now requests that grant once while routine per-action preflight remains silent.

Validation

  • @maka/runtime: 3179 passed, 9 skipped
  • @maka/computer-use: 134 passed
  • @maka/runtime-host: 693 passed
  • @maka/desktop: 1738 passed
  • Runtime, Runtime Host, Computer Use, and Desktop type checks
  • Biome and git diff --check
  • real host-backed Desktop flow: discover the Desktop Computer Use group, load maka_computer, grant Accessibility, invoke list_apps, and receive the native app list

Follow-up

  • native Screen Recording consent is tracked in maka-cu#1
中文

概述

  • 让 Client Capability 协议接受 Desktop Computer Use 的真实工具 schema
  • 让 dispatch 前被拒绝的调用(包括 Client Capability 边界和 subagent admission 失败)继续使用普通 call/response lane
  • 在首次明确使用 Computer Use 时,仅主动请求一次 macOS Accessibility 权限
  • 覆盖真实 Desktop offer、协议边界、durable 拒绝路径与 native backend 授权请求

根因

Desktop 仅在 native backend 可用时发布 Computer Use offer。接入真实 maka-cu 后,其 draft-07 tuple schema 和工具描述超出了 Client Capability decoder 原有假设,导致注册失败,Host 最终只能暴露 Agent 和 Browser 两个工具组。

另一个问题出现在调用于 dispatch 前被拒绝时,包括 Client Capability 边界拒绝和 subagent admission 饱和:调用在 durable prepare 前已经获得 operation id,随后生成的 synthetic response 找不到对应 durable call,形成 orphan response 并使 Runtime Host 进入 draining。

此外,native 协议已经支持显式触发 Accessibility 授权,但 Maka 之前只执行静默检查。现在首次明确使用 Computer Use 时会请求一次授权,日常逐操作 preflight 仍保持静默。

验证

  • @maka/runtime:3179 passed,9 skipped
  • @maka/computer-use:134 passed
  • @maka/runtime-host:693 passed
  • @maka/desktop:1738 passed
  • Runtime、Runtime Host、Computer Use 与 Desktop typecheck
  • Biome 与 git diff --check
  • 真实 host-backed Desktop 链路:发现 Desktop Computer Use 组、加载 maka_computer、完成 Accessibility 授权、调用 list_apps 并取得 native 应用列表

后续

  • native Screen Recording 主动授权由 maka-cu#1 跟踪

@M4n5ter
M4n5ter marked this pull request as ready for review August 5, 2026 11:58
@M4n5ter
M4n5ter merged commit f8d2ac4 into mainAug 5, 2026
12 checks passed
@M4n5ter
M4n5ter deleted the fix/runtime-host-computer-use-capability branch August 5, 2026 11:58
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. An already
corrupt ledger refuses well-formed writes for a different reason, so it throws
`ToolLedgerCorruptionError` and keeps failing closed.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships.
Refs apache#2234
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces apache#2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in apache#2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs apache#2234
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
…2240)
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (#2234).
#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. #2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else #2233 restored is untouched.
Two things #2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces #2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in #2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs #2234
Sign up for freeto 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

@M4n5ter
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(runtime-host): restore desktop Computer Use capability by M4n5ter · Pull Request #2233 · apache/maka · GitHub
Skip to content

fix(runtime-host): restore desktop Computer Use capability - #2233

Merged
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability
Aug 5, 2026
Merged

fix(runtime-host): restore desktop Computer Use capability#2233
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability

Conversation

@M4n5ter

@M4n5terM4n5ter commented Aug 5, 2026

Copy link
Copy Markdown
Member
English

Summary

  • accept the real Desktop Computer Use tool schema in the Client Capability protocol
  • keep pre-dispatch rejections, including Client Capability boundary and subagent admission failures, on the generic call/response lane
  • request the native macOS Accessibility grant once, on the first explicit Computer Use attempt
  • cover the real Desktop offer, protocol boundary, durable rejection path, and native backend request

Root cause

Desktop only publishes its Computer Use offer when the native backend is available. Once a real maka-cu backend was present, its draft-07 tuple schemas and tool description exceeded assumptions in the Client Capability decoder, so registration failed and the Host exposed only the Agent and Browser groups.

A separate failure existed when a call was rejected before dispatch, including Client Capability boundary and saturated subagent admission failures: the call received a durable operation id before durable preparation. Its synthetic response then had no matching durable call, producing an orphan response and draining the Runtime Host.

Finally, the native protocol already supported an explicit Accessibility prompt, but Maka used only silent permission probes. The first explicit Computer Use attempt now requests that grant once while routine per-action preflight remains silent.

Validation

  • @maka/runtime: 3179 passed, 9 skipped
  • @maka/computer-use: 134 passed
  • @maka/runtime-host: 693 passed
  • @maka/desktop: 1738 passed
  • Runtime, Runtime Host, Computer Use, and Desktop type checks
  • Biome and git diff --check
  • real host-backed Desktop flow: discover the Desktop Computer Use group, load maka_computer, grant Accessibility, invoke list_apps, and receive the native app list

Follow-up

  • native Screen Recording consent is tracked in maka-cu#1
中文

概述

  • 让 Client Capability 协议接受 Desktop Computer Use 的真实工具 schema
  • 让 dispatch 前被拒绝的调用(包括 Client Capability 边界和 subagent admission 失败)继续使用普通 call/response lane
  • 在首次明确使用 Computer Use 时,仅主动请求一次 macOS Accessibility 权限
  • 覆盖真实 Desktop offer、协议边界、durable 拒绝路径与 native backend 授权请求

根因

Desktop 仅在 native backend 可用时发布 Computer Use offer。接入真实 maka-cu 后,其 draft-07 tuple schema 和工具描述超出了 Client Capability decoder 原有假设,导致注册失败,Host 最终只能暴露 Agent 和 Browser 两个工具组。

另一个问题出现在调用于 dispatch 前被拒绝时,包括 Client Capability 边界拒绝和 subagent admission 饱和:调用在 durable prepare 前已经获得 operation id,随后生成的 synthetic response 找不到对应 durable call,形成 orphan response 并使 Runtime Host 进入 draining。

此外,native 协议已经支持显式触发 Accessibility 授权,但 Maka 之前只执行静默检查。现在首次明确使用 Computer Use 时会请求一次授权,日常逐操作 preflight 仍保持静默。

验证

  • @maka/runtime:3179 passed,9 skipped
  • @maka/computer-use:134 passed
  • @maka/runtime-host:693 passed
  • @maka/desktop:1738 passed
  • Runtime、Runtime Host、Computer Use 与 Desktop typecheck
  • Biome 与 git diff --check
  • 真实 host-backed Desktop 链路:发现 Desktop Computer Use 组、加载 maka_computer、完成 Accessibility 授权、调用 list_apps 并取得 native 应用列表

后续

  • native Screen Recording 主动授权由 maka-cu#1 跟踪

@M4n5ter
M4n5ter marked this pull request as ready for review August 5, 2026 11:58
@M4n5ter
M4n5ter merged commit f8d2ac4 into mainAug 5, 2026
12 checks passed
@M4n5ter
M4n5ter deleted the fix/runtime-host-computer-use-capability branch August 5, 2026 11:58
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. An already
corrupt ledger refuses well-formed writes for a different reason, so it throws
`ToolLedgerCorruptionError` and keeps failing closed.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships.
Refs apache#2234
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces apache#2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in apache#2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs apache#2234
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
…2240)
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (#2234).
#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. #2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else #2233 restored is untouched.
Two things #2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces #2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in #2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs #2234
Sign up for freeto 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

@M4n5ter
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(runtime-host): restore desktop Computer Use capability by M4n5ter · Pull Request #2233 · apache/maka · GitHub
Skip to content

fix(runtime-host): restore desktop Computer Use capability - #2233

Merged
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability
Aug 5, 2026
Merged

fix(runtime-host): restore desktop Computer Use capability#2233
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability

Conversation

@M4n5ter

@M4n5terM4n5ter commented Aug 5, 2026

Copy link
Copy Markdown
Member
English

Summary

  • accept the real Desktop Computer Use tool schema in the Client Capability protocol
  • keep pre-dispatch rejections, including Client Capability boundary and subagent admission failures, on the generic call/response lane
  • request the native macOS Accessibility grant once, on the first explicit Computer Use attempt
  • cover the real Desktop offer, protocol boundary, durable rejection path, and native backend request

Root cause

Desktop only publishes its Computer Use offer when the native backend is available. Once a real maka-cu backend was present, its draft-07 tuple schemas and tool description exceeded assumptions in the Client Capability decoder, so registration failed and the Host exposed only the Agent and Browser groups.

A separate failure existed when a call was rejected before dispatch, including Client Capability boundary and saturated subagent admission failures: the call received a durable operation id before durable preparation. Its synthetic response then had no matching durable call, producing an orphan response and draining the Runtime Host.

Finally, the native protocol already supported an explicit Accessibility prompt, but Maka used only silent permission probes. The first explicit Computer Use attempt now requests that grant once while routine per-action preflight remains silent.

Validation

  • @maka/runtime: 3179 passed, 9 skipped
  • @maka/computer-use: 134 passed
  • @maka/runtime-host: 693 passed
  • @maka/desktop: 1738 passed
  • Runtime, Runtime Host, Computer Use, and Desktop type checks
  • Biome and git diff --check
  • real host-backed Desktop flow: discover the Desktop Computer Use group, load maka_computer, grant Accessibility, invoke list_apps, and receive the native app list

Follow-up

  • native Screen Recording consent is tracked in maka-cu#1
中文

概述

  • 让 Client Capability 协议接受 Desktop Computer Use 的真实工具 schema
  • 让 dispatch 前被拒绝的调用(包括 Client Capability 边界和 subagent admission 失败)继续使用普通 call/response lane
  • 在首次明确使用 Computer Use 时,仅主动请求一次 macOS Accessibility 权限
  • 覆盖真实 Desktop offer、协议边界、durable 拒绝路径与 native backend 授权请求

根因

Desktop 仅在 native backend 可用时发布 Computer Use offer。接入真实 maka-cu 后,其 draft-07 tuple schema 和工具描述超出了 Client Capability decoder 原有假设,导致注册失败,Host 最终只能暴露 Agent 和 Browser 两个工具组。

另一个问题出现在调用于 dispatch 前被拒绝时,包括 Client Capability 边界拒绝和 subagent admission 饱和:调用在 durable prepare 前已经获得 operation id,随后生成的 synthetic response 找不到对应 durable call,形成 orphan response 并使 Runtime Host 进入 draining。

此外,native 协议已经支持显式触发 Accessibility 授权,但 Maka 之前只执行静默检查。现在首次明确使用 Computer Use 时会请求一次授权,日常逐操作 preflight 仍保持静默。

验证

  • @maka/runtime:3179 passed,9 skipped
  • @maka/computer-use:134 passed
  • @maka/runtime-host:693 passed
  • @maka/desktop:1738 passed
  • Runtime、Runtime Host、Computer Use 与 Desktop typecheck
  • Biome 与 git diff --check
  • 真实 host-backed Desktop 链路:发现 Desktop Computer Use 组、加载 maka_computer、完成 Accessibility 授权、调用 list_apps 并取得 native 应用列表

后续

  • native Screen Recording 主动授权由 maka-cu#1 跟踪

@M4n5ter
M4n5ter marked this pull request as ready for review August 5, 2026 11:58
@M4n5ter
M4n5ter merged commit f8d2ac4 into mainAug 5, 2026
12 checks passed
@M4n5ter
M4n5ter deleted the fix/runtime-host-computer-use-capability branch August 5, 2026 11:58
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. An already
corrupt ledger refuses well-formed writes for a different reason, so it throws
`ToolLedgerCorruptionError` and keeps failing closed.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships.
Refs apache#2234
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces apache#2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in apache#2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs apache#2234
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
…2240)
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (#2234).
#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. #2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else #2233 restored is untouched.
Two things #2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces #2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in #2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs #2234
Sign up for freeto 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

@M4n5ter
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(runtime-host): restore desktop Computer Use capability by M4n5ter · Pull Request #2233 · apache/maka · GitHub
Skip to content

fix(runtime-host): restore desktop Computer Use capability - #2233

Merged
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability
Aug 5, 2026
Merged

fix(runtime-host): restore desktop Computer Use capability#2233
M4n5ter merged 2 commits into
mainfrom
fix/runtime-host-computer-use-capability

Conversation

@M4n5ter

@M4n5terM4n5ter commented Aug 5, 2026

Copy link
Copy Markdown
Member
English

Summary

  • accept the real Desktop Computer Use tool schema in the Client Capability protocol
  • keep pre-dispatch rejections, including Client Capability boundary and subagent admission failures, on the generic call/response lane
  • request the native macOS Accessibility grant once, on the first explicit Computer Use attempt
  • cover the real Desktop offer, protocol boundary, durable rejection path, and native backend request

Root cause

Desktop only publishes its Computer Use offer when the native backend is available. Once a real maka-cu backend was present, its draft-07 tuple schemas and tool description exceeded assumptions in the Client Capability decoder, so registration failed and the Host exposed only the Agent and Browser groups.

A separate failure existed when a call was rejected before dispatch, including Client Capability boundary and saturated subagent admission failures: the call received a durable operation id before durable preparation. Its synthetic response then had no matching durable call, producing an orphan response and draining the Runtime Host.

Finally, the native protocol already supported an explicit Accessibility prompt, but Maka used only silent permission probes. The first explicit Computer Use attempt now requests that grant once while routine per-action preflight remains silent.

Validation

  • @maka/runtime: 3179 passed, 9 skipped
  • @maka/computer-use: 134 passed
  • @maka/runtime-host: 693 passed
  • @maka/desktop: 1738 passed
  • Runtime, Runtime Host, Computer Use, and Desktop type checks
  • Biome and git diff --check
  • real host-backed Desktop flow: discover the Desktop Computer Use group, load maka_computer, grant Accessibility, invoke list_apps, and receive the native app list

Follow-up

  • native Screen Recording consent is tracked in maka-cu#1
中文

概述

  • 让 Client Capability 协议接受 Desktop Computer Use 的真实工具 schema
  • 让 dispatch 前被拒绝的调用(包括 Client Capability 边界和 subagent admission 失败)继续使用普通 call/response lane
  • 在首次明确使用 Computer Use 时,仅主动请求一次 macOS Accessibility 权限
  • 覆盖真实 Desktop offer、协议边界、durable 拒绝路径与 native backend 授权请求

根因

Desktop 仅在 native backend 可用时发布 Computer Use offer。接入真实 maka-cu 后,其 draft-07 tuple schema 和工具描述超出了 Client Capability decoder 原有假设,导致注册失败,Host 最终只能暴露 Agent 和 Browser 两个工具组。

另一个问题出现在调用于 dispatch 前被拒绝时,包括 Client Capability 边界拒绝和 subagent admission 饱和:调用在 durable prepare 前已经获得 operation id,随后生成的 synthetic response 找不到对应 durable call,形成 orphan response 并使 Runtime Host 进入 draining。

此外,native 协议已经支持显式触发 Accessibility 授权,但 Maka 之前只执行静默检查。现在首次明确使用 Computer Use 时会请求一次授权,日常逐操作 preflight 仍保持静默。

验证

  • @maka/runtime:3179 passed,9 skipped
  • @maka/computer-use:134 passed
  • @maka/runtime-host:693 passed
  • @maka/desktop:1738 passed
  • Runtime、Runtime Host、Computer Use 与 Desktop typecheck
  • Biome 与 git diff --check
  • 真实 host-backed Desktop 链路:发现 Desktop Computer Use 组、加载 maka_computer、完成 Accessibility 授权、调用 list_apps 并取得 native 应用列表

后续

  • native Screen Recording 主动授权由 maka-cu#1 跟踪

@M4n5ter
M4n5ter marked this pull request as ready for review August 5, 2026 11:58
@M4n5ter
M4n5ter merged commit f8d2ac4 into mainAug 5, 2026
12 checks passed
@M4n5ter
M4n5ter deleted the fix/runtime-host-computer-use-capability branch August 5, 2026 11:58
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. An already
corrupt ledger refuses well-formed writes for a different reason, so it throws
`ToolLedgerCorruptionError` and keeps failing closed.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships.
Refs apache#2234
UncertaintyDeterminesYou4ndMe added a commit to UncertaintyDeterminesYou4ndMe/maka-agent that referenced this pull request Aug 6, 2026
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (apache#2234).
apache#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. apache#2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else apache#2233 restored is untouched.
Two things apache#2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces apache#2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in apache#2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs apache#2234
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
…2240)
A pre-dispatch tool refusal used to kill the turn. The call event's lane was
guessed when the event was built: an `operationId` claims the T1 dispatch
protocol, so AgentRun skips the generic projection of that `function_call` and
waits for `commitToolPrepared` — which sits after every refusal return. The call
fact was never persisted, the synthetic result landed on the generic lane with
nothing to attach to, and the ledger refused the `orphan_response` (#2234).
#2233 closed that by predicting the refusals instead: every guard hoisted into a
`preflightRejected` boolean read before the operationId. It is correct only
while the prediction and the guards agree, and nothing holds them together — a
new refusal path, or a guard that grows a condition its hoisted twin does not,
silently restores the orphan.
This decides the lane where it is known. `pushCallEvent('preflight'|'dispatch')`
pushes at most once, every refusal routes through one `refuseBeforeDispatch`
helper that keeps call and result together on the generic lane, and only
`prepareDurableToolAttempt` asks for T1. The decision is the code path taken, so
it cannot drift. #2233's predicate, its hoisted boundary read and its early slot
reservation go with it; the boundary read and the reservation return to the
guards they belong to, and everything else #2233 restored is untouched.
Two things #2233 did not cover, both from the same failure:
- A rejected append latched the RuntimeEvent store unavailable, and
`commitTerminalRun` returns early on an unavailable store, so one refused
event cost the run its own terminal write — a run stuck at `running` with no
terminal event. `ToolLedgerRejectionError` now marks a refusal of one bad
candidate against a healthy store, and only that skips the latch. Damage
found by the workspace health scan throws `ToolLedgerCorruptionError` and
keeps failing closed. Be precise about what that second class buys, because
the obvious rationale is wrong: the health scan runs only for tool-bearing
events, so a damaged ledger refuses tool facts and would have taken the
terminal event. The latch is what keeps it out, which reproduces #2234's shape
for an already-damaged workspace. Left standing deliberately — it is a
behaviour change on a path this commit does not otherwise touch — and tracked
in #2313, with a test that pins the current price rather than hiding it.
- `agent_swarm`'s selector refusal said only "Provide exactly one of subagent_id
or legacy profile", naming neither which of the two mistakes it was nor one
value that would work — and the field list an args violation appends is the
top-level one, not `items[n]` where the violation was.
Tests: `pre-dispatch-refusal-ledger.test.ts` drives all seven refusal paths
through the real `scanToolLedger`; on the tagged lane every one of them
reproduces the production `orphan_response`. The terminal-write and
ledger-corruption cases run against a `canonical` store, the only durability
production ships. Both error classes are pinned where they are produced, in
`sqlite-runtime-store.test.ts`: their messages are byte-identical to the plain
`Error` strings they replaced, so nothing else in that suite would notice a
regression to `throw new Error(...)` — and the latch exemption would silently
stop working.
Refs #2234
Sign up for freeto 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

@M4n5ter