@ckanner/flywheel 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -0
  3. package/README.zh-CN.md +2 -0
  4. package/THIRD_PARTY_NOTICES.md +5 -1
  5. package/bin/flywheel-kimi-native-worker.js +5 -0
  6. package/bin/flywheel.js +8 -1
  7. package/bin/runtime.js +5 -1
  8. package/dist/bridge.mjs +89 -7
  9. package/dist/claude-code-pro-bridge.mjs +55 -6
  10. package/dist/client.js +107 -10
  11. package/dist/codex-pro-bridge.mjs +12 -5
  12. package/dist/control.mjs +1 -1
  13. package/dist/kimi-native-bridge.mjs +429 -0
  14. package/dist/kimi-native-runtime.mjs +233 -0
  15. package/dist/kimi-native-worker.mjs +77 -0
  16. package/dist/kimi-tower-candidate.mjs +227 -0
  17. package/dist/machine-worker.mjs +39 -5
  18. package/dist/managed-lcu.mjs +223 -0
  19. package/dist/native-doctor.mjs +36 -0
  20. package/dist/native-jobs.mjs +72 -0
  21. package/dist/native-policy.mjs +371 -0
  22. package/dist/native-resources.mjs +144 -0
  23. package/dist/native.mjs +251 -0
  24. package/dist/policy.mjs +13 -3
  25. package/dist/prewarm.mjs +4 -1
  26. package/dist/protocol.mjs +8 -1
  27. package/dist/worker.mjs +16 -9
  28. package/docs/README.md +9 -0
  29. package/docs/README.zh-CN.md +3 -0
  30. package/docs/design.md +34 -12
  31. package/docs/en/claude-code-pro.md +2 -2
  32. package/docs/en/configuration.md +17 -5
  33. package/docs/en/contributing.md +5 -3
  34. package/docs/en/flow-console.md +1 -1
  35. package/docs/en/getting-started.md +2 -2
  36. package/docs/en/model-policy.md +2 -2
  37. package/docs/en/native-capabilities.md +185 -0
  38. package/docs/en/releasing.md +2 -2
  39. package/docs/en/session-recovery.md +2 -2
  40. package/docs/plans/2026-11-13-native-harness-capabilities.en.md +258 -0
  41. package/docs/plans/2026-11-13-native-harness-capabilities.md +276 -0
  42. package/docs/plans/README.md +1 -0
  43. package/docs/validation/2026-11-14-desktop-native-configuration.md +128 -0
  44. package/docs/validation/2026-11-14-native-harness-implementation.md +215 -0
  45. package/docs/validation/2026-11-14-published-companions.md +15 -0
  46. package/docs/validation/2026-11-14-release-0.33.0.md +49 -0
  47. package/docs/validation/2026-11-14-release-0.33.1.md +78 -0
  48. package/docs/validation/README.md +4 -0
  49. package/docs/validation/evidence/2026-11-14-native-harness-candidate.json +255 -0
  50. package/docs/validation/evidence/2026-11-14-native-harness-sessions.json +6009 -0
  51. package/docs/validation/evidence/2026-11-14-native-harness-tower-candidate.json +192 -0
  52. package/docs/zh-CN/claude-code-pro.md +2 -2
  53. package/docs/zh-CN/configuration.md +21 -8
  54. package/docs/zh-CN/contributing.md +5 -3
  55. package/docs/zh-CN/flow-console.md +1 -1
  56. package/docs/zh-CN/getting-started.md +2 -2
  57. package/docs/zh-CN/model-policy.md +2 -2
  58. package/docs/zh-CN/native-capabilities.md +184 -0
  59. package/docs/zh-CN/releasing.md +2 -2
  60. package/docs/zh-CN/session-recovery.md +1 -1
  61. package/native/darwin-amd64/flywheel +0 -0
  62. package/native/darwin-arm64/flywheel +0 -0
  63. package/native/linux-amd64/flywheel +0 -0
  64. package/native/linux-arm64/flywheel +0 -0
  65. package/native/manifest.json +9 -9
  66. package/node_modules/dsh-preset-generator/CHANGELOG.md +77 -0
  67. package/node_modules/dsh-preset-generator/LICENSE +21 -0
  68. package/node_modules/dsh-preset-generator/README.md +149 -0
  69. package/node_modules/dsh-preset-generator/docs/README.zh.md +95 -0
  70. package/node_modules/dsh-preset-generator/package.json +43 -0
  71. package/node_modules/dsh-preset-generator/src/composition.mjs +150 -0
  72. package/node_modules/dsh-preset-generator/src/gen-presets.mjs +367 -0
  73. package/node_modules/dsh-preset-generator/src/policy.mjs +17 -0
  74. package/node_modules/dsh-subagent-codex-pro/CHANGELOG.md +16 -1
  75. package/node_modules/dsh-subagent-codex-pro/README.md +57 -4
  76. package/node_modules/dsh-subagent-codex-pro/assets/native-client.js +118 -0
  77. package/node_modules/dsh-subagent-codex-pro/cordis.patch.yml +6 -0
  78. package/node_modules/dsh-subagent-codex-pro/docs/README.zh.md +49 -2
  79. package/node_modules/dsh-subagent-codex-pro/docs/machine-lifecycle-validation.md +1 -1
  80. package/node_modules/dsh-subagent-codex-pro/docs/machine-lifecycle.md +1 -1
  81. package/node_modules/dsh-subagent-codex-pro/docs/machine-lifecycle.zh.md +1 -1
  82. package/node_modules/dsh-subagent-codex-pro/docs/machine.md +2 -2
  83. package/node_modules/dsh-subagent-codex-pro/docs/native-capabilities.md +254 -0
  84. package/node_modules/dsh-subagent-codex-pro/docs/native-capabilities.zh.md +180 -0
  85. package/node_modules/dsh-subagent-codex-pro/lib/config.js +56 -0
  86. package/node_modules/dsh-subagent-codex-pro/lib/config.js.map +1 -0
  87. package/node_modules/dsh-subagent-codex-pro/lib/desktop-native.js +109 -0
  88. package/node_modules/dsh-subagent-codex-pro/lib/desktop-native.js.map +1 -0
  89. package/node_modules/dsh-subagent-codex-pro/lib/dsh-types.js +3 -4
  90. package/node_modules/dsh-subagent-codex-pro/lib/dsh-types.js.map +1 -1
  91. package/node_modules/dsh-subagent-codex-pro/lib/index.js +64 -15
  92. package/node_modules/dsh-subagent-codex-pro/lib/index.js.map +1 -1
  93. package/node_modules/dsh-subagent-codex-pro/lib/jobs.js +6 -2
  94. package/node_modules/dsh-subagent-codex-pro/lib/jobs.js.map +1 -1
  95. package/node_modules/dsh-subagent-codex-pro/lib/machine-cli.js +2 -2
  96. package/node_modules/dsh-subagent-codex-pro/lib/machine-cli.js.map +1 -1
  97. package/node_modules/dsh-subagent-codex-pro/lib/machine-local.js +5 -2
  98. package/node_modules/dsh-subagent-codex-pro/lib/machine-local.js.map +1 -1
  99. package/node_modules/dsh-subagent-codex-pro/lib/machine-supervision.js +11 -0
  100. package/node_modules/dsh-subagent-codex-pro/lib/machine-supervision.js.map +1 -1
  101. package/node_modules/dsh-subagent-codex-pro/lib/machine-supervisor.js +1 -0
  102. package/node_modules/dsh-subagent-codex-pro/lib/machine-supervisor.js.map +1 -1
  103. package/node_modules/dsh-subagent-codex-pro/lib/machine.js +20 -3
  104. package/node_modules/dsh-subagent-codex-pro/lib/machine.js.map +1 -1
  105. package/node_modules/dsh-subagent-codex-pro/lib/native.js +297 -0
  106. package/node_modules/dsh-subagent-codex-pro/lib/native.js.map +1 -0
  107. package/node_modules/dsh-subagent-codex-pro/lib/process-guardian.js +11 -1
  108. package/node_modules/dsh-subagent-codex-pro/lib/process-guardian.js.map +1 -1
  109. package/node_modules/dsh-subagent-codex-pro/lib/run.js +19 -1
  110. package/node_modules/dsh-subagent-codex-pro/lib/run.js.map +1 -1
  111. package/node_modules/dsh-subagent-codex-pro/lib/tool.js +123 -57
  112. package/node_modules/dsh-subagent-codex-pro/lib/tool.js.map +1 -1
  113. package/node_modules/dsh-subagent-codex-pro/lib/types/config.d.ts +31 -0
  114. package/node_modules/dsh-subagent-codex-pro/lib/types/config.d.ts.map +1 -0
  115. package/node_modules/dsh-subagent-codex-pro/lib/types/desktop-native.d.ts +30 -0
  116. package/node_modules/dsh-subagent-codex-pro/lib/types/desktop-native.d.ts.map +1 -0
  117. package/node_modules/dsh-subagent-codex-pro/lib/types/dsh-types.d.ts +7 -5
  118. package/node_modules/dsh-subagent-codex-pro/lib/types/dsh-types.d.ts.map +1 -1
  119. package/node_modules/dsh-subagent-codex-pro/lib/types/index.d.ts +31 -0
  120. package/node_modules/dsh-subagent-codex-pro/lib/types/index.d.ts.map +1 -1
  121. package/node_modules/dsh-subagent-codex-pro/lib/types/jobs.d.ts +2 -0
  122. package/node_modules/dsh-subagent-codex-pro/lib/types/jobs.d.ts.map +1 -1
  123. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-local.d.ts.map +1 -1
  124. package/node_modules/dsh-subagent-codex-pro/lib/types/machine-supervision.d.ts.map +1 -1
  125. package/node_modules/dsh-subagent-codex-pro/lib/types/machine.d.ts +7 -0
  126. package/node_modules/dsh-subagent-codex-pro/lib/types/machine.d.ts.map +1 -1
  127. package/node_modules/dsh-subagent-codex-pro/lib/types/native.d.ts +120 -0
  128. package/node_modules/dsh-subagent-codex-pro/lib/types/native.d.ts.map +1 -0
  129. package/node_modules/dsh-subagent-codex-pro/lib/types/run.d.ts +11 -0
  130. package/node_modules/dsh-subagent-codex-pro/lib/types/run.d.ts.map +1 -1
  131. package/node_modules/dsh-subagent-codex-pro/lib/types/tool.d.ts +11 -0
  132. package/node_modules/dsh-subagent-codex-pro/lib/types/tool.d.ts.map +1 -1
  133. package/node_modules/dsh-subagent-codex-pro/lib/types/wire.d.ts +7 -1
  134. package/node_modules/dsh-subagent-codex-pro/lib/types/wire.d.ts.map +1 -1
  135. package/node_modules/dsh-subagent-codex-pro/lib/wire.js +34 -6
  136. package/node_modules/dsh-subagent-codex-pro/lib/wire.js.map +1 -1
  137. package/node_modules/dsh-subagent-codex-pro/package.json +28 -4
  138. package/npm/install.js +2 -2
  139. package/package.json +25 -10
  140. package/plugins/dsh/KIMI_NATIVE_PROTOCOL.en.md +76 -0
  141. package/plugins/dsh/KIMI_NATIVE_PROTOCOL.md +76 -0
  142. package/plugins/dsh/PROTOCOL.md +151 -17
  143. package/plugins/dsh/README.md +15 -12
  144. package/plugins/dsh/README.zh-CN.md +8 -8
  145. package/plugins/dsh/THIRD_PARTY_NOTICES.md +5 -1
  146. package/plugins/dsh/vendor/README.md +40 -11
  147. package/plugins/dsh/vendor/chatgpt.json +21 -0
  148. package/plugins/dsh/vendor/claude-code-pro.json +21 -0
  149. package/plugins/dsh/vendor/codex-pro.json +13 -8
  150. package/plugins/dsh/vendor/lcu.json +21 -0
  151. package/plugins/dsh/vendor/preset-generator.json +16 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,38 @@ results are recorded separately in the [release runbook](docs/en/releasing.md)
5
5
  and [validation index](docs/validation/README.md). Historical entries retain
6
6
  their original language.
7
7
 
8
+ ## 0.34.0 — Native harness capabilities
9
+
10
+ - Align native companion fixtures and provenance with published Codex 0.10.0, Claude 0.5.0, LCU 0.5.0 and ChatGPT 0.1.20; verify the exact registry Claude tarball in CI and release jobs. Document separate desktop, worker and SDK dependency trees.
11
+
12
+ - Add versioned native profiles across loops, routes, model profiles, second
13
+ opinions and allowed execution bindings, including workbench selection,
14
+ frozen history identity, durable native receipts and import-only diagnostics.
15
+ - Run DSH PTC composition from the shared generator with actual behavior probes,
16
+ native subagents/Team/Workflow and managed LCU. Serialize desktop ownership,
17
+ retain epoch-safe handles and durable media, and prove journal/physical cleanup.
18
+ - Add an owned Kimi Server worker for Agent/Swarm/Tower. Use independent Tower
19
+ Git candidates, preserve dirty/untracked/binary changes and transfer only after
20
+ completion, cleanup and source/contract checks.
21
+ - Integrate Codex Pro 0.10.0 and Claude Code Pro 0.5.0 native controls, child events
22
+ and truthful accounting. Add independent Review/interactive Team job commands;
23
+ unknown cloud work, child usage and cleanup never become inferred success.
24
+ - Preserve role independence, explicit accounts/permissions and existing JEV
25
+ controls. Keep one complete npm package, bundle generator 0.4.0 and record the
26
+ reviewed companion source artifacts without claiming registry publication.
27
+ - Refresh the bundled generator to create a profile's first preset patch or a
28
+ new output file, preserving existing settings and concurrent file creation.
29
+
30
+ ## 0.33.1 — Strict desktop read codecs
31
+
32
+ - Fix task discovery and configuration reads rejected by the actual DSH Client
33
+ Gateway. Supply strict schema factories for session identity, read requests and
34
+ result envelopes; consume the dynamic namespace through an owned injected
35
+ scope, use the host Agent registry's `agentId` wire field, and preserve session
36
+ isolation, teardown and silent reads.
37
+ - Exercise the shipped Client descriptor through the real public Client and Host
38
+ Gateway on both supported host fixtures instead of mocking Remote mounting.
39
+
8
40
  ## 0.33.0 — One complete npm distribution
9
41
 
10
42
  - Consolidate the CLI, DSH host/client, workers, embedded skills and four native
package/README.md CHANGED
@@ -27,6 +27,7 @@ retries, budgets, workspaces, and delivery. Use it from the DeepSeek Harness
27
27
  - **Coordinates bounded parallel execution.** Packets with explicit
28
28
  dependencies and non-overlapping write scopes can execute concurrently;
29
29
  review and integration remain coordinated by the engine.
30
+ - **Uses native harness capabilities.** Select PTC Agents/Teams, Kimi Swarm/Tower, Codex agents, Claude Workflows or managed LCU per role; see [native capabilities](docs/en/native-capabilities.md).
30
31
  - **Provides a desktop workbench.** Inspect task progress, evidence, sessions,
31
32
  run history, and configuration from DSH's native Flywheel view.
32
33
  - **Makes failures visible.** Unknown execution, usage, or cleanup stays
package/README.zh-CN.md CHANGED
@@ -22,6 +22,8 @@ Flywheel 是面向 coding agent 的确定性 Go 编排器。模型负责规划
22
22
  - **提供桌面工作台。** 在 DSH 原生 Flywheel 视图中查看任务进度、证据、会话、运行记录和配置。
23
23
  - **明确呈现失败。** 未知执行结果、用量或清理状态保持未知;无法安全继续时保留成果并停止。
24
24
 
25
+ 原生能力可以按阶段配置:DSH PTC/Team、Kimi Swarm/Tower、Codex agents、Claude Workflow 和受管 LCU。入口、依赖和实际运行边界见[原生能力指南](docs/zh-CN/native-capabilities.md)。
26
+
25
27
  ## 安装
26
28
 
27
29
  使用桌面入口前,请准备 **Node.js 22.18.0 或更高版本**、npm、Git,以及带可用 `dsh` 命令的受支持 DSH 桌面。插件采用 **DSH 0.2 兼容系列**范围,从 **0.2.1-rc.5** 起,到 **0.3.0 的任何预发布版之前**;范围内常规 rc/补丁升级无需重新发布 Flywheel。还需准备所选模型路由使用的账号和 CLI 工具。DSH 自身可能要求更高版本的 Node;DSH 0.2.2 SDK 要求 Node 24.9 或更高。
@@ -39,4 +39,8 @@ SOFTWARE.
39
39
 
40
40
  ## Bundled Codex Pro public package
41
41
 
42
- This distribution bundles `dsh-subagent-codex-pro@0.9.0`, Copyright (c) 2026 ckanner, under the MIT License. Its complete license is retained at `node_modules/dsh-subagent-codex-pro/LICENSE` inside the complete package. Source identity and tarball integrity are recorded in `plugins/dsh/vendor/README.md` and `plugins/dsh/vendor/codex-pro.json` in the source repository. The bundled package is the upstream public machine API implementation, including its compiled supervisor; it contains no native Codex executable or user credentials.
42
+ This distribution bundles `dsh-subagent-codex-pro@0.10.0`, Copyright (c) 2026 ckanner, under the MIT License. Its complete license is retained at `node_modules/dsh-subagent-codex-pro/LICENSE` inside the complete package. Source identity and tarball integrity are recorded in `plugins/dsh/vendor/README.md` and `plugins/dsh/vendor/codex-pro.json` in the source repository. The bundled package is the upstream public machine API implementation, including its compiled supervisor; it contains no native Codex executable or user credentials.
43
+
44
+ ## Bundled PTC composition source
45
+
46
+ This distribution also bundles `dsh-preset-generator@0.4.0`, Copyright (c) 2026 ckanner, under the MIT License. Its original license remains at `node_modules/dsh-preset-generator/LICENSE`. The public composition module shares the desktop daily/heavy policy with the owned SDK worker. Source identity and actual tarball integrity are recorded in `plugins/dsh/vendor/preset-generator.json`; no DSH application resources, native executable or credentials are bundled.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const { loadKimiNativeWorkerPath, runChild } = require('./runtime.js');
4
+ runChild(process.execPath, [loadKimiNativeWorkerPath(), ...process.argv.slice(2)], { label: 'flywheel-kimi-native-worker' })
5
+ .then(code => { process.exitCode = code; }).catch(error => { console.error(`flywheel-kimi-native-worker: ${error.message}`); process.exitCode = 1; });
package/bin/flywheel.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright (c) 2026 ckanner. SPDX-License-Identifier: MIT
3
3
  "use strict";
4
4
 
5
- const { loadNativePath, loadWorkerPath, loadCodexProWorkerPath, loadClaudeCodeProWorkerPath, assertSupportedNode, runChild } = require("./runtime.js");
5
+ const { loadNativePath, loadWorkerPath, loadCodexProWorkerPath, loadClaudeCodeProWorkerPath, loadKimiNativeWorkerPath, assertSupportedNode, runChild } = require("./runtime.js");
6
6
 
7
7
  async function main() {
8
8
  const args = process.argv.slice(2);
@@ -11,6 +11,10 @@ async function main() {
11
11
  }
12
12
  assertSupportedNode();
13
13
 
14
+ if (args[0] === 'native') {
15
+ return (await import('../dist/native-jobs.mjs')).main(args.slice(1));
16
+ }
17
+
14
18
  const env = { ...process.env };
15
19
  // An npm dependency's bin is not necessarily on a desktop application's
16
20
  // PATH. Resolve this installation's worker without changing an explicit
@@ -24,6 +28,9 @@ async function main() {
24
28
  if (env.FLYWHEEL_CLAUDE_CODE_PRO_WORKER === undefined) {
25
29
  env.FLYWHEEL_CLAUDE_CODE_PRO_WORKER = loadClaudeCodeProWorkerPath();
26
30
  }
31
+ if (env.FLYWHEEL_KIMI_NATIVE_WORKER === undefined) {
32
+ env.FLYWHEEL_KIMI_NATIVE_WORKER = loadKimiNativeWorkerPath();
33
+ }
27
34
  return runChild(loadNativePath(), args, { env });
28
35
  }
29
36
 
package/bin/runtime.js CHANGED
@@ -53,6 +53,10 @@ function loadClaudeCodeProWorkerPath() {
53
53
  return packageFile('dist/claude-code-pro-worker.mjs');
54
54
  }
55
55
 
56
+ function loadKimiNativeWorkerPath() {
57
+ return packageFile('dist/kimi-native-worker.mjs');
58
+ }
59
+
56
60
  function assertSupportedNode(version = process.versions.node) {
57
61
  const match = /^(\d+)\.(\d+)\.(\d+)(?:$|-)/.exec(version);
58
62
  if (!match || Number(match[1]) < 22 || (Number(match[1]) === 22 && Number(match[2]) < 18)) {
@@ -97,4 +101,4 @@ function runChild(command, args, { env = process.env, cwd, label = "flywheel" }
97
101
  });
98
102
  }
99
103
 
100
- module.exports = { loadNativePath, loadWorkerPath, loadCodexProWorkerPath, loadClaudeCodeProWorkerPath, assertSupportedNode, runChild };
104
+ module.exports = { loadNativePath, loadWorkerPath, loadCodexProWorkerPath, loadClaudeCodeProWorkerPath, loadKimiNativeWorkerPath, assertSupportedNode, runChild };
package/dist/bridge.mjs CHANGED
@@ -1,13 +1,18 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { randomUUID } from 'node:crypto';
3
- import { mkdtemp, realpath, rm, writeFile } from 'node:fs/promises';
3
+ import { mkdtemp, realpath, rm, writeFile, readFile, open } from 'node:fs/promises';
4
+ import { constants } from 'node:fs';
4
5
  import { tmpdir } from 'node:os';
5
6
  import { join } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
7
8
  import { validatePrepare, matchWarmStart, writeWarmReceipt } from './prewarm.mjs';
8
9
  import { BridgeError, MAX_FRAME_BYTES, normalizedError, readPolicyReceipt, record, SESSION_PROTOCOL, SessionFold, validateStart } from './protocol.mjs';
10
+ import { nativeOverlay } from './native-policy.mjs';
11
+ import { NativeTree } from './native.mjs';
12
+ import { acquireGuiLease, settleGuiLease } from './native-resources.mjs';
9
13
 
10
14
  export function makeOverlay(request, policyPath = fileURLToPath(new URL('./policy.mjs', import.meta.url)), admission = {}, historyRoot) {
15
+ const native = request.native ? nativeOverlay(request, admission.runtimeProfile ?? 'sdk') : null;
11
16
  // Each patch replaces an entire config row. Pin the checked rc.5/rc.1 enforcement
12
17
  // implementations, not the user's mutable permission dropdown defaults.
13
18
  const disable = ['permission', 'tool-plugin-manager', 'tool-working-directory',
@@ -17,6 +22,7 @@ export function makeOverlay(request, policyPath = fileURLToPath(new URL('./polic
17
22
  'session-title-llm', 'tool-web', 'compaction-basic', 'command-compact', 'hmr'];
18
23
  return [
19
24
  ...disable.map(id => ({ id, disabled: true })),
25
+ ...(native?.overlay ?? []),
20
26
  ...(historyRoot ? [{ id: 'session-persistence-jsonl', config: { root: historyRoot } }] : []),
21
27
  { id: 'sandbox', name: '@deepseek-ai/dsh-sandbox-local', disabled: false, config: {} },
22
28
  { id: 'sandbox-policy', name: '@deepseek-ai/dsh-sandbox-policy', disabled: false,
@@ -28,6 +34,8 @@ export function makeOverlay(request, policyPath = fileURLToPath(new URL('./polic
28
34
  { id: 'sdk-jsonrpc-server', config: { maxTokensAsSuccess: false } },
29
35
  { insert: [{ id: 'flywheel-worker-policy', name: policyPath,
30
36
  config: { permissionMode: request.permissionMode, cwd: request.cwd,
37
+ ...(native ? { native: request.native, managed: request.managed, composition: native.composition,
38
+ provider: request.provider, model: request.model, reasoningEffort: request.reasoningEffort, resuming: Boolean(request.sessionId) } : {}),
31
39
  ...(request.workspaceCwd ? { workspaceCwd: request.workspaceCwd } : {}),
32
40
  ...(!request.sessionId && request.sessionTitle ? { sessionTitle: request.sessionTitle } : {}), ...admission } }] },
33
41
  ];
@@ -55,6 +63,26 @@ async function abortable(promise, signal) {
55
63
  }
56
64
  }
57
65
 
66
+ export async function readNativeEvents(directory, identity, summary) {
67
+ const file = await open(join(directory, 'native-events.jsonl'), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
68
+ let events;
69
+ try {
70
+ const stat = await file.stat();
71
+ if (!stat.isFile() || stat.size > 2 * 1024 * 1024) throw new Error('native runtime evidence exceeds its bounded receipt');
72
+ events = await file.readFile('utf8');
73
+ } finally { await file.close(); }
74
+ const bytes = Buffer.byteLength(events);
75
+ if (bytes > 2 * 1024 * 1024 || (bytes && !events.endsWith('\n'))) throw new Error('native runtime evidence is incomplete or oversized');
76
+ const values = events.split('\n').filter(Boolean).map(line => JSON.parse(line));
77
+ if (values.length > 4096 || values.some((value, index) => !record(value) || value.version !== 1 ||
78
+ value.journalSequence !== index + 1 || value.requestId !== identity.requestId || value.nonce !== identity.nonce ||
79
+ value.rootSessionId !== identity.rootSessionId)) throw new Error('native runtime evidence has invalid sequence or invocation ownership');
80
+ if (summary !== undefined && (!record(summary) || summary.complete !== true || summary.events !== values.length || summary.bytes !== bytes)) {
81
+ throw new Error('native runtime journal did not confirm complete durable evidence');
82
+ }
83
+ return values;
84
+ }
85
+
58
86
  /** A bounded JSON-RPC transport. It does not own a new process group. */
59
87
  export class Runtime {
60
88
  constructor(bin, args, options, notify) {
@@ -144,7 +172,7 @@ export class Runtime {
144
172
  });
145
173
  }
146
174
 
147
- async close() {
175
+ async close({ graceMs = 2000 } = {}) {
148
176
  // The Go watchdog owns the whole inherited process group. We request the
149
177
  // SDK's child-first drain and bound the leader wait; Go performs the final
150
178
  // group sweep even on normal exit. A child must not detach/setsid.
@@ -154,7 +182,7 @@ export class Runtime {
154
182
  // SDK shutdown includes persistent history flush and plugin disposal.
155
183
  // Keep this bounded below Go's default 5s cancellation grace, while
156
184
  // allowing a normal fsync to complete under concurrent local workloads.
157
- await bounded(this.exited, 2000);
185
+ await bounded(this.exited, graceMs);
158
186
  }
159
187
  if (!this.exit) {
160
188
  forced = true;
@@ -184,6 +212,10 @@ export async function runWorker(input, { emit, env = process.env, signal, doctor
184
212
  let temp;
185
213
  let sessionId;
186
214
  let fold;
215
+ let nativeTree, guiLease, nativeCleanup, nativeAdmission, policyIdentity;
216
+ let nativeRuntimeEvents = [];
217
+ let completionAtRootEnd;
218
+ let nativeAttempted = false;
187
219
  let outcome;
188
220
  let failure;
189
221
  let cancelled = false;
@@ -232,9 +264,24 @@ export async function runWorker(input, { emit, env = process.env, signal, doctor
232
264
  sessionId = prepared ? undefined : request.sessionId ?? `flywheel-${randomUUID()}`;
233
265
  const admission = { receiptPath: join(temp, 'policy.json'), requestId: prepared ? prepared.warmId : request.requestId,
234
266
  ...(prepared ? { deferredAdmissionPath: join(temp, 'admission.json') } : { sessionId }), nonce: randomUUID() };
267
+ policyIdentity = { requestId: admission.requestId, nonce: admission.nonce };
268
+ if (!prepared && request.native?.lcu?.enabled) {
269
+ guiLease = await acquireGuiLease(request.managed, { env, signal: controller.signal });
270
+ request = { ...request, managed: { ...request.managed, lease: guiLease } };
271
+ }
272
+ if (request.native) admission.runtimeProfile = profile;
235
273
  await writeFile(patch, JSON.stringify(makeOverlay(request, undefined, admission, doctor ? join(temp, 'probe-history') : undefined)), { mode: 0o600 });
236
274
  controller.signal.throwIfAborted();
237
275
  if (!prepared) fold = new SessionFold(sessionId, { strict: request.protocol === 2 });
276
+ const observeNative = event => {
277
+ const eventFrame = { type: 'native-event', sessionId, event };
278
+ if (readySent) frame(eventFrame);
279
+ else {
280
+ beforeReady.push(eventFrame);
281
+ if (beforeReady.length > 256) throw new BridgeError('TRANSPORT', 'excessive native events before readiness');
282
+ }
283
+ };
284
+ if (!prepared && request.native) nativeTree = new NativeTree(sessionId, request.native, observeNative);
238
285
  runtime = new Runtime(bin, ['--profile', profile, '--patch', patch], {
239
286
  cwd: request.cwd,
240
287
  env: { ...env, DSH_PERMISSION_MODE: request.permissionMode,
@@ -252,7 +299,8 @@ export async function runWorker(input, { emit, env = process.env, signal, doctor
252
299
  if (beforeReady.length > 256) throw new BridgeError('TRANSPORT', 'excessive SDK events before readiness');
253
300
  }
254
301
  }
255
- if (method === 'subagent.started' && params?.parentSessionId === sessionId) {
302
+ if (nativeTree) nativeTree.notify(method, params);
303
+ else if (method === 'subagent.started' && params?.parentSessionId === sessionId) {
256
304
  throw new BridgeError('INVALID_REQUEST', 'DSH child delegation is disabled: the Go engine owns workers');
257
305
  }
258
306
  });
@@ -284,12 +332,18 @@ export async function runWorker(input, { emit, env = process.env, signal, doctor
284
332
  deadline = setTimeout(() => controller.abort(new BridgeError('TIMEOUT', 'DSH worker lifecycle deadline exceeded')), formalBudget);
285
333
  request = await abortable(validateStart(next), controller.signal);
286
334
  matchWarmStart(prepared, request);
335
+ if (request.native?.lcu?.enabled) {
336
+ guiLease = await acquireGuiLease(request.managed, { env, signal: controller.signal });
337
+ request = { ...request, managed: { ...request.managed, lease: guiLease } };
338
+ }
287
339
  sessionId = request.sessionId ?? `flywheel-${randomUUID()}`;
288
340
  fold = new SessionFold(sessionId, { strict: request.protocol === 2 });
341
+ if (request.native) nativeTree = new NativeTree(sessionId, request.native, observeNative);
289
342
  await writeFile(admission.deferredAdmissionPath, JSON.stringify({ requestId: admission.requestId,
290
- nonce: admission.nonce, sessionId, ...(!request.sessionId && request.sessionTitle ? { sessionTitle: request.sessionTitle } : {}) }),
343
+ nonce: admission.nonce, sessionId, ...(request.native ? { managed: request.managed, resuming: Boolean(request.sessionId) } : {}), ...(!request.sessionId && request.sessionTitle ? { sessionTitle: request.sessionTitle } : {}) }),
291
344
  { mode: 0o600, flag: 'wx' });
292
345
  }
346
+ nativeAttempted = Boolean(request.native);
293
347
  if (request.sessionId !== undefined) {
294
348
  // Public SDK resume restores exactly this identity, without a new input
295
349
  // and without a replacement on failure. A generic RPC error cannot tell
@@ -314,6 +368,8 @@ export async function runWorker(input, { emit, env = process.env, signal, doctor
314
368
  throw new BridgeError('INVALID_REQUEST', 'DSH session project workspace differs from the requested project');
315
369
  }
316
370
  fold.verifiedPolicy(request);
371
+ nativeAdmission = fold.policy.native;
372
+ if (request.native && nativeAdmission?.matched !== true) throw new BridgeError('INVALID_REQUEST', 'native runtime did not attest its effective capabilities');
317
373
  frame({ type: 'ready', sessionId, cwd: request.cwd, ...(request.workspaceCwd ? { workspaceCwd: request.workspaceCwd } : {}), provider: request.provider, model: request.model,
318
374
  ...(request.reasoningEffort === undefined ? {} : { reasoningEffort: request.reasoningEffort }),
319
375
  permissionMode: request.permissionMode, approvalPolicy: 'never',
@@ -347,13 +403,19 @@ export async function runWorker(input, { emit, env = process.env, signal, doctor
347
403
  }
348
404
  waited = true;
349
405
  outcome = fold.finish(receipt.messageId);
406
+ if (nativeTree) {
407
+ nativeRuntimeEvents = await readNativeEvents(temp, { ...policyIdentity, rootSessionId: sessionId });
408
+ nativeTree.observeRuntime(nativeRuntimeEvents);
409
+ completionAtRootEnd = nativeTree.completion();
410
+ if (!completionAtRootEnd.confirmed) throw new BridgeError('TRANSPORT', 'Native children remain active at root completion');
411
+ }
350
412
  }
351
413
  } catch (error) {
352
414
  if (!warmClosed) failure = normalizedError(error);
353
415
  } finally {
354
416
  if (runtime) {
355
417
  try {
356
- const exit = await runtime.close();
418
+ const exit = await runtime.close({ graceMs: request?.native?.lcu?.enabled ? 135000 : request?.native ? 15000 : 2000 });
357
419
  if (exit.spawned && (exit.forced || exit.code !== 0 || exit.signal || !exit.graceful)) {
358
420
  settled = false;
359
421
  failure ??= { code: 'TRANSPORT', message: 'DSH runtime did not prove graceful shutdown of its managed processes' };
@@ -376,6 +438,23 @@ export async function runWorker(input, { emit, env = process.env, signal, doctor
376
438
  if (request && Date.now() - started > request.timeoutMs && !cancelled) {
377
439
  failure = { code: 'TIMEOUT', message: 'DSH worker lifecycle deadline exceeded (including cleanup)' };
378
440
  }
441
+ if (temp && nativeAttempted) {
442
+ try {
443
+ nativeCleanup = JSON.parse(await readFile(join(temp, 'native-cleanup.json'), 'utf8'));
444
+ if (nativeCleanup.version !== 1 || nativeCleanup.requestId !== policyIdentity.requestId || nativeCleanup.nonce !== policyIdentity.nonce ||
445
+ nativeCleanup.error !== undefined || nativeCleanup.journal?.complete !== true ||
446
+ (request.native?.lcu?.enabled && (!Array.isArray(nativeCleanup.cleanup) || nativeCleanup.cleanup.length < 1 ||
447
+ nativeCleanup.cleanup.some(value => value.cleanup !== 'confirmed' || !value.runtimeExitConfirmed || !value.turnCleanupConfirmed)))) {
448
+ throw new Error('native runtime did not confirm complete owned cleanup');
449
+ }
450
+ nativeRuntimeEvents = await readNativeEvents(temp, { ...policyIdentity, rootSessionId: sessionId }, nativeCleanup.journal);
451
+ nativeTree.observeRuntime(nativeRuntimeEvents);
452
+ } catch (error) { settled = false; failure ??= normalizedError(error); }
453
+ }
454
+ if (guiLease) {
455
+ try { await settleGuiLease(guiLease, settled && (!nativeAttempted || Boolean(nativeCleanup)), nativeCleanup ?? null); }
456
+ catch (error) { settled = false; failure ??= normalizedError(error); }
457
+ }
379
458
  if (temp) await rm(temp, { recursive: true, force: true }).catch(error => {
380
459
  failure ??= { code: 'TRANSPORT', message: `could not remove worker overlay: ${error.message}` };
381
460
  });
@@ -395,7 +474,10 @@ export async function runWorker(input, { emit, env = process.env, signal, doctor
395
474
  type: 'result', status: cancelled ? 'cancelled' : failure ? 'error' : 'ok', settled,
396
475
  ...(sessionId ? { sessionId } : {}),
397
476
  text: outcome?.text ?? fold?.text ?? '',
398
- usage: outcome?.usage ?? null, costUsd: null,
477
+ usage: nativeTree ? nativeTree.addUsage(outcome?.usage ?? null) : outcome?.usage ?? null, costUsd: null,
478
+ ...(nativeTree ? { native: { ...nativeTree.receipt(nativeAdmission ?? { matched: false, observed: null },
479
+ { confirmed: settled, scope: 'owned-sdk-tree', gui: nativeCleanup?.lcu ?? null }), runtimeEvents: nativeRuntimeEvents,
480
+ completion: completionAtRootEnd ?? { confirmed: false, pendingIds: [] } } } : {}),
399
481
  ...(failure ? { error: failure } : {}),
400
482
  };
401
483
  if ((formalStarted ? request.protocol : input?.protocol) === 2) {
@@ -1,8 +1,9 @@
1
1
  // Claude Code is an independent harness, not a DSH native model provider.
2
2
  import { createRequire } from 'node:module';
3
3
  import { isAbsolute } from 'node:path';
4
+ import { validateNativeAdmission } from './machine-worker.mjs';
4
5
 
5
- export const ADAPTER = Object.freeze({ package: 'dsh-subagent-claude-code-pro', version: '0.4.0', protocol: 1 });
6
+ export const ADAPTER = Object.freeze({ package: 'dsh-subagent-claude-code-pro', version: '0.5.0', protocol: 1 });
6
7
  export const LIMITS = Object.freeze({ requestBytes: 1024 * 1024, machineOutputBytes: 1024 * 1024,
7
8
  progressBytes: 1024 * 1024, receiptFrameBytes: 8 * 1024 * 1024, admissionMs: 10000 });
8
9
  const require = createRequire(import.meta.url);
@@ -19,9 +20,9 @@ export async function loadAdapter() {
19
20
  }
20
21
  let manifest, api;
21
22
  try { manifest = require('dsh-subagent-claude-code-pro/package.json'); }
22
- catch { throw new WorkerError('ADAPTER_UNAVAILABLE', 'Install dsh-subagent-claude-code-pro@0.4.0 alongside @ckanner/flywheel; the optional adapter is not installed'); }
23
+ catch { throw new WorkerError('ADAPTER_UNAVAILABLE', 'Install dsh-subagent-claude-code-pro@0.5.0 alongside @ckanner/flywheel; the optional adapter is not installed'); }
23
24
  if (manifest.name !== ADAPTER.package || manifest.version !== ADAPTER.version) {
24
- throw new WorkerError('ADAPTER_MISMATCH', 'Claude Code Pro package identity or version does not match the pinned 0.4.0 machine contract');
25
+ throw new WorkerError('ADAPTER_MISMATCH', 'Claude Code Pro package identity or version does not match the pinned 0.5.0 machine contract');
25
26
  }
26
27
  try { api = await import('dsh-subagent-claude-code-pro/machine'); }
27
28
  catch { throw new WorkerError('ADAPTER_UNAVAILABLE', 'The pinned Claude Code Pro public machine API could not be imported'); }
@@ -29,7 +30,12 @@ export async function loadAdapter() {
29
30
  typeof api.runMachine !== 'function' || typeof api.MachineInputError !== 'function' ||
30
31
  api.MACHINE_LIFECYCLE_VERSION !== 1 || typeof api.recoverMachine !== 'function' ||
31
32
  api.MACHINE_LIFECYCLE_CAPABILITIES?.durableJournal !== true || api.MACHINE_LIFECYCLE_CAPABILITIES?.readOnlyRecovery !== true ||
32
- api.MACHINE_LIFECYCLE_CAPABILITIES?.threadReadyBeforeDispatch !== false) {
33
+ api.MACHINE_LIFECYCLE_CAPABILITIES?.threadReadyBeforeDispatch !== false ||
34
+ api.MACHINE_NATIVE_VERSION !== 1 || api.MACHINE_NATIVE_CAPABILITIES?.version !== 1 ||
35
+ api.MACHINE_NATIVE_CAPABILITIES?.agents !== true || api.MACHINE_NATIVE_CAPABILITIES?.workflows !== true ||
36
+ typeof api.validateNativeReviewRequest !== 'function' || typeof api.validateInteractiveTeamRequest !== 'function' ||
37
+ typeof api.runNativeReview !== 'function' || typeof api.runInteractiveTeam !== 'function' ||
38
+ typeof api.recoverNativeJob !== 'function' || typeof api.controlInteractiveTeam !== 'function') {
33
39
  throw new WorkerError('ADAPTER_MISMATCH', 'Claude Code Pro does not expose the pinned public machine API');
34
40
  }
35
41
  return api;
@@ -39,8 +45,9 @@ export async function loadAdapter() {
39
45
  export function machineRequest(value, api, env) {
40
46
  const lifecycle = value?.protocol === 3;
41
47
  const persistent = value?.protocol === 2 || (lifecycle && value.threadMode === 'persistent');
42
- const keys = new Set(['type', 'protocol', 'requestId', 'cwd', 'model', 'reasoningEffort', 'permissionMode', 'prompt', 'timeoutMs',
48
+ const keys = new Set(['type', 'protocol', 'requestId', 'cwd', 'model', 'reasoningEffort', 'permissionMode', 'prompt', 'timeoutMs', 'native', 'managed', 'images', 'outputSchema', 'maxBudgetUsd',
43
49
  ...(persistent || lifecycle ? ['threadMode', 'threadId'] : []), ...(lifecycle ? ['recoveryDirectory'] : [])]);
50
+ if (lifecycle) keys.add('nativeOperation');
44
51
  if (value === null || typeof value !== 'object' || Array.isArray(value) || value.type !== 'start' ||
45
52
  ![1, 2, 3].includes(value.protocol) || Object.keys(value).some(key => !keys.has(key)) ||
46
53
  typeof value.requestId !== 'string' || !value.requestId.trim() || value.requestId.length > 256 || value.requestId.includes('\0') ||
@@ -56,18 +63,60 @@ export function machineRequest(value, api, env) {
56
63
  if (value.timeoutMs !== undefined && (!Number.isSafeInteger(value.timeoutMs) || value.timeoutMs < 1 || value.timeoutMs > 2147483647)) {
57
64
  throw new WorkerError('INVALID_REQUEST', 'timeoutMs must be a positive safe integer no greater than 2147483647');
58
65
  }
66
+ validateNativeAdmission(value.managed, value.requestId, WorkerError);
59
67
  try {
68
+ if (value.nativeOperation !== undefined) return nativeJobRequest(value, api, env);
60
69
  return api.parseMachineRequest({ protocol: 1, requestId: value.requestId, cwd: value.cwd, prompt: value.prompt,
61
70
  model: value.model, effort: value.reasoningEffort, permissionMode: 'bypassPermissions',
62
71
  sessionMode: persistent ? 'persistent' : 'ephemeral',
63
72
  ...(persistent && value.threadId !== undefined ? { sessionId: value.threadId } : {}),
64
73
  ...(value.timeoutMs === undefined ? {} : { timeoutMs: value.timeoutMs }),
74
+ ...(value.native === undefined ? {} : { native: value.native }),
75
+ ...(value.images === undefined ? {} : { images: value.images }),
76
+ ...(value.outputSchema === undefined ? {} : { outputSchema: value.outputSchema }),
77
+ ...(value.maxBudgetUsd === undefined ? {} : { maxBudgetUsd: value.maxBudgetUsd }),
65
78
  command: env.FLYWHEEL_CLAUDE_BIN || 'claude', maxOutputBytes: LIMITS.machineOutputBytes });
66
79
  } catch (error) {
67
80
  throw new WorkerError('INVALID_REQUEST', error instanceof api.MachineInputError ? error.message : 'Invalid Claude Code Pro machine request');
68
81
  }
69
82
  }
70
83
 
84
+ /** Independent operations have their own public API, fresh identity and journal. */
85
+ function nativeJobRequest(value, api, env) {
86
+ const op = value.nativeOperation;
87
+ const common = { protocol: 1, requestId: value.requestId, cwd: value.cwd,
88
+ command: env.FLYWHEEL_CLAUDE_BIN || 'claude', ...(value.timeoutMs === undefined ? {} : { timeoutMs: value.timeoutMs }) };
89
+ if (!op || typeof op !== 'object' || Array.isArray(op) || value.threadMode !== 'ephemeral' || value.threadId !== undefined ||
90
+ value.images !== undefined || value.outputSchema !== undefined || value.maxBudgetUsd !== undefined) {
91
+ throw new api.MachineInputError('native operations require a fresh independent request without query-only media, schema or budget controls');
92
+ }
93
+ let request;
94
+ if (op.kind === 'remote-review') {
95
+ if (Object.keys(op).some(key => !['kind', 'target', 'consent', 'trustWorkspace', 'timeoutMinutes'].includes(key)) || value.native !== undefined) {
96
+ throw new api.MachineInputError('remote review accepts only its native target, consent, trust and timeout controls');
97
+ }
98
+ const { kind, ...review } = op;
99
+ request = { ...common, ...review, maxOutputBytes: LIMITS.machineOutputBytes };
100
+ api.validateNativeReviewRequest(request);
101
+ } else if (op.kind === 'interactive-team') {
102
+ if (Object.keys(op).some(key => !['kind', 'tmuxCommand', 'columns', 'rows'].includes(key))) throw new api.MachineInputError('unsupported interactive team control');
103
+ const { kind, ...team } = op;
104
+ request = { ...common, ...team, prompt: value.prompt, model: value.model, effort: value.reasoningEffort,
105
+ permissionMode: 'bypassPermissions', ...(value.native === undefined ? {} : { native: value.native }) };
106
+ api.validateInteractiveTeamRequest(request);
107
+ } else throw new api.MachineInputError('unsupported independent native operation');
108
+ return { requestId: value.requestId, nativeJob: { kind: op.kind, request } };
109
+ }
110
+
111
+ export function invokeAdapter(api, request, options) {
112
+ if (request.nativeJob?.kind === 'remote-review') return api.runNativeReview(request.nativeJob.request, options);
113
+ if (request.nativeJob?.kind === 'interactive-team') return api.runInteractiveTeam(request.nativeJob.request, options);
114
+ return api.runMachine(request, options);
115
+ }
116
+ export function recoverAdapter(api, request, original) {
117
+ return original.nativeOperation === undefined ? api.recoverMachine(request) : api.recoverNativeJob(request);
118
+ }
119
+
71
120
  // Go has already filtered its child's environment: credentials only enter via
72
121
  // the explicitly selected channel, never from the orchestrator's ambient env.
73
122
  export function runtimeOptions(env) {
@@ -78,5 +127,5 @@ export function runtimeOptions(env) {
78
127
 
79
128
  export function doctorResult() {
80
129
  return { protocol: 1, type: 'doctor', package: ADAPTER.package, version: ADAPTER.version,
81
- machineProtocol: ADAPTER.protocol, available: true, inferenceTested: false };
130
+ machineProtocol: ADAPTER.protocol, nativeVersion: 1, available: true, inferenceTested: false };
82
131
  }