maka-agent 0.2.0-dev.30.20260912 → 0.2.0-dev.31.20260913

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 (194) hide show
  1. package/THIRD_PARTY_NOTICES.txt +392 -0
  2. package/dist/acp/maka-acp-agent.js +8 -2
  3. package/dist/acp/prompt-content.js +225 -0
  4. package/dist/acp/session-event-mapper.js +103 -0
  5. package/dist/acp/session-registry.js +654 -17
  6. package/dist/acp/stdio-server.js +10 -1
  7. package/dist/cli-core.js +1 -1
  8. package/dist/pi-transcript-tools.js +3 -2
  9. package/dist/pi-transcript.js +12 -2
  10. package/dist/runtime-host-session-channel.js +63 -9
  11. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  12. package/node_modules/@maka/core/dist/artifacts.js +6 -0
  13. package/node_modules/@maka/core/dist/attachments.js +7 -2
  14. package/node_modules/@maka/core/dist/durable-tool-result-projection.js +3 -1
  15. package/node_modules/@maka/core/dist/events.js +21 -0
  16. package/node_modules/@maka/core/dist/goal.js +44 -2
  17. package/node_modules/@maka/core/dist/permission.js +0 -1
  18. package/node_modules/@maka/core/dist/runtime-boundary.js +73 -29
  19. package/node_modules/@maka/core/dist/runtime-event.js +6 -3
  20. package/node_modules/@maka/core/dist/runtime-handoff.js +10 -2
  21. package/node_modules/@maka/core/dist/runtime-logical-execution.js +39 -14
  22. package/node_modules/@maka/core/dist/tool-result-archive-evidence.js +3 -1
  23. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  24. package/node_modules/@maka/core/dist/unified-diff.js +33 -26
  25. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +49 -178
  26. package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +7 -2
  27. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +15 -53
  28. package/node_modules/@maka/runtime/dist/bash-model-output.js +7 -1
  29. package/node_modules/@maka/runtime/dist/bash-tail-buffer.js +13 -52
  30. package/node_modules/@maka/runtime/dist/builtin-tools.js +59 -111
  31. package/node_modules/@maka/runtime/dist/child-fd-input.js +3 -2
  32. package/node_modules/@maka/runtime/dist/context-budget-policy.js +1 -11
  33. package/node_modules/@maka/runtime/dist/context-budget.js +12 -1
  34. package/node_modules/@maka/runtime/dist/conversation-copy.js +104 -9
  35. package/node_modules/@maka/runtime/dist/filesystem-executor.js +10 -6
  36. package/node_modules/@maka/runtime/dist/filesystem-worker/client.js +77 -19
  37. package/node_modules/@maka/runtime/dist/filesystem-worker/launch-spec.js +66 -55
  38. package/node_modules/@maka/runtime/dist/filesystem-worker/operations.js +34 -97
  39. package/node_modules/@maka/runtime/dist/filesystem-worker/protocol.js +25 -10
  40. package/node_modules/@maka/runtime/dist/filesystem-worker/worker-entry.js +2 -0
  41. package/node_modules/@maka/runtime/dist/glob-search.js +74 -0
  42. package/node_modules/@maka/runtime/dist/goal-continuation.js +78 -8
  43. package/node_modules/@maka/runtime/dist/goal-state.js +7 -0
  44. package/node_modules/@maka/runtime/dist/grep-search.js +155 -0
  45. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +84 -94
  46. package/node_modules/@maka/runtime/dist/model-history.js +25 -3
  47. package/node_modules/@maka/runtime/dist/pipe-tail-collector.js +6 -9
  48. package/node_modules/@maka/runtime/dist/read-page.js +231 -0
  49. package/node_modules/@maka/runtime/dist/request-projection.js +2 -15
  50. package/node_modules/@maka/runtime/dist/ripgrep-executable.js +62 -0
  51. package/node_modules/@maka/runtime/dist/ripgrep-guidance.js +81 -0
  52. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +187 -20
  53. package/node_modules/@maka/runtime/dist/runtime-read-model.js +2 -1
  54. package/node_modules/@maka/runtime/dist/sandbox/linux-profile-path.js +7 -1
  55. package/node_modules/@maka/runtime/dist/sandbox/linux-sandbox.js +20 -5
  56. package/node_modules/@maka/runtime/dist/session-manager.js +6 -6
  57. package/node_modules/@maka/runtime/dist/session-recap.js +9 -5
  58. package/node_modules/@maka/runtime/dist/shell-exec.js +7 -25
  59. package/node_modules/@maka/runtime/dist/shell-run-manager.js +1 -1
  60. package/node_modules/@maka/runtime/dist/shell-run-tool-result.js +1 -1
  61. package/node_modules/@maka/runtime/dist/shell-tools.js +6 -12
  62. package/node_modules/@maka/runtime/dist/tool-availability.js +0 -1
  63. package/node_modules/@maka/runtime/dist/tool-output.js +1 -1
  64. package/node_modules/@maka/runtime/dist/tool-result-archive-capability.js +24 -36
  65. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +32 -527
  66. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +17 -119
  67. package/node_modules/@maka/runtime/dist/tool-result-archive.js +7 -25
  68. package/node_modules/@maka/runtime/dist/tool-runtime.js +7 -5
  69. package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +6933 -3681
  70. package/node_modules/@maka/runtime/dist/workspace-executor.js +20 -24
  71. package/node_modules/@maka/runtime/package.json +2 -0
  72. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +185 -191
  73. package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +3 -0
  74. package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
  75. package/node_modules/@maka/runtime-host/dist/protocol/message.js +2 -2
  76. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
  77. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +2 -2
  78. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +10 -2
  79. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +38 -0
  80. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +2 -8
  81. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +2 -0
  82. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +81 -7
  83. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +13 -13
  84. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +65 -0
  85. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +1 -0
  86. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +5 -2
  87. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +88 -72
  88. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +21 -4
  89. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +145 -1
  90. package/node_modules/@maka/storage/dist/agent-run-store.js +5 -2
  91. package/node_modules/@maka/storage/dist/execution-stores.js +2 -1
  92. package/node_modules/@maka/storage/dist/runtime-transcript-query.js +27 -14
  93. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +69 -3
  94. package/node_modules/balanced-match/LICENSE.md +23 -0
  95. package/node_modules/balanced-match/README.md +57 -0
  96. package/node_modules/balanced-match/dist/commonjs/index.js +59 -0
  97. package/node_modules/balanced-match/dist/commonjs/package.json +3 -0
  98. package/node_modules/balanced-match/dist/esm/index.js +54 -0
  99. package/node_modules/balanced-match/dist/esm/package.json +3 -0
  100. package/node_modules/balanced-match/package.json +68 -0
  101. package/node_modules/brace-expansion/LICENSE +23 -0
  102. package/node_modules/brace-expansion/README.md +105 -0
  103. package/node_modules/brace-expansion/dist/commonjs/index.js +289 -0
  104. package/node_modules/brace-expansion/dist/commonjs/package.json +3 -0
  105. package/node_modules/brace-expansion/dist/esm/index.js +285 -0
  106. package/node_modules/brace-expansion/dist/esm/package.json +3 -0
  107. package/node_modules/brace-expansion/package.json +64 -0
  108. package/node_modules/glob/LICENSE.md +63 -0
  109. package/node_modules/glob/README.md +1203 -0
  110. package/node_modules/glob/dist/commonjs/glob.js +248 -0
  111. package/node_modules/glob/dist/commonjs/has-magic.js +27 -0
  112. package/node_modules/glob/dist/commonjs/ignore.js +119 -0
  113. package/node_modules/glob/dist/commonjs/index.js +68 -0
  114. package/node_modules/glob/dist/commonjs/index.min.js +4 -0
  115. package/node_modules/glob/dist/commonjs/package.json +3 -0
  116. package/node_modules/glob/dist/commonjs/pattern.js +223 -0
  117. package/node_modules/glob/dist/commonjs/processor.js +301 -0
  118. package/node_modules/glob/dist/commonjs/walker.js +387 -0
  119. package/node_modules/glob/dist/esm/glob.js +244 -0
  120. package/node_modules/glob/dist/esm/has-magic.js +23 -0
  121. package/node_modules/glob/dist/esm/ignore.js +115 -0
  122. package/node_modules/glob/dist/esm/index.js +55 -0
  123. package/node_modules/glob/dist/esm/index.min.js +4 -0
  124. package/node_modules/glob/dist/esm/package.json +3 -0
  125. package/node_modules/glob/dist/esm/pattern.js +219 -0
  126. package/node_modules/glob/dist/esm/processor.js +294 -0
  127. package/node_modules/glob/dist/esm/walker.js +381 -0
  128. package/node_modules/glob/package.json +98 -0
  129. package/node_modules/minimatch/LICENSE.md +55 -0
  130. package/node_modules/minimatch/README.md +528 -0
  131. package/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js +14 -0
  132. package/node_modules/minimatch/dist/commonjs/ast.js +845 -0
  133. package/node_modules/minimatch/dist/commonjs/brace-expressions.js +150 -0
  134. package/node_modules/minimatch/dist/commonjs/escape.js +30 -0
  135. package/node_modules/minimatch/dist/commonjs/index.js +1127 -0
  136. package/node_modules/minimatch/dist/commonjs/package.json +3 -0
  137. package/node_modules/minimatch/dist/commonjs/unescape.js +38 -0
  138. package/node_modules/minimatch/dist/esm/assert-valid-pattern.js +10 -0
  139. package/node_modules/minimatch/dist/esm/ast.js +841 -0
  140. package/node_modules/minimatch/dist/esm/brace-expressions.js +146 -0
  141. package/node_modules/minimatch/dist/esm/escape.js +26 -0
  142. package/node_modules/minimatch/dist/esm/index.js +1114 -0
  143. package/node_modules/minimatch/dist/esm/package.json +3 -0
  144. package/node_modules/minimatch/dist/esm/unescape.js +34 -0
  145. package/node_modules/minimatch/package.json +73 -0
  146. package/node_modules/minipass/LICENSE.md +55 -0
  147. package/node_modules/minipass/README.md +825 -0
  148. package/node_modules/minipass/dist/commonjs/index.js +1038 -0
  149. package/node_modules/minipass/dist/commonjs/package.json +3 -0
  150. package/node_modules/minipass/dist/esm/index.js +1020 -0
  151. package/node_modules/minipass/dist/esm/package.json +3 -0
  152. package/node_modules/minipass/package.json +77 -0
  153. package/node_modules/path-scurry/LICENSE.md +55 -0
  154. package/node_modules/path-scurry/README.md +636 -0
  155. package/node_modules/path-scurry/dist/commonjs/index.js +2018 -0
  156. package/node_modules/path-scurry/dist/commonjs/package.json +3 -0
  157. package/node_modules/path-scurry/dist/esm/index.js +1983 -0
  158. package/node_modules/path-scurry/dist/esm/package.json +3 -0
  159. package/node_modules/path-scurry/node_modules/lru-cache/LICENSE.md +55 -0
  160. package/node_modules/path-scurry/node_modules/lru-cache/README.md +469 -0
  161. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
  162. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
  163. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
  164. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
  165. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
  166. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
  167. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +1726 -0
  168. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +2 -0
  169. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
  170. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
  171. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
  172. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
  173. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json +3 -0
  174. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
  175. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
  176. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
  177. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
  178. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
  179. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
  180. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
  181. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +1722 -0
  182. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +2 -0
  183. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
  184. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
  185. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
  186. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
  187. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json +3 -0
  188. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
  189. package/node_modules/path-scurry/node_modules/lru-cache/package.json +154 -0
  190. package/node_modules/path-scurry/package.json +72 -0
  191. package/package.json +1 -1
  192. package/node_modules/@maka/runtime/dist/active-tool-result-prune.js +0 -248
  193. package/node_modules/@maka/runtime/dist/active-tool-result-working-set.js +0 -218
  194. package/node_modules/@maka/runtime/dist/archive-read-tool.js +0 -115
@@ -17,18 +17,20 @@
17
17
  * under the License.
18
18
  */
19
19
  import { promises as fs } from 'node:fs';
20
- import { exec, execFile } from 'node:child_process';
21
- import { glob as nodeGlob } from 'node:fs/promises';
20
+ import { exec } from 'node:child_process';
21
+ import { globFiles } from './glob-search.js';
22
22
  import { isAbsolute, resolve } from 'node:path';
23
23
  import { isPathInside, realpathAllowMissing, resolveCanonicalDirectoryEntryTarget, } from './path-containment.js';
24
24
  import { createPatchedFile, updatePatchedFile } from './apply-patch-file.js';
25
25
  import { compareAndDeleteEntry, hostVisibilityAfterWrite, openStableTarget, writeThroughHandle, } from './file-stable-write.js';
26
26
  import { promisify } from 'node:util';
27
+ import { currentRipgrepEnvironment, RipgrepUnavailableError, ripgrepMissingOnPathMessage, } from './ripgrep-guidance.js';
27
28
  import { runProcessWithBoundedTail, runShellWithBoundedTail } from './shell-exec.js';
28
29
  import { isSupportedImagePath, readWorkspaceImage } from './image-file.js';
29
30
  import { readTextLineWindow } from './text-line-window.js';
31
+ import { searchFiles } from './grep-search.js';
32
+ import { defaultRipgrepCandidates, resolveRipgrepExecutable } from './ripgrep-executable.js';
30
33
  const execAsync = promisify(exec);
31
- const execFileAsync = promisify(execFile);
32
34
  export const LOCAL_WORKSPACE_EXECUTOR_FACTS = {
33
35
  isolation: 'none',
34
36
  writesAffectHost: true,
@@ -37,7 +39,11 @@ export const LOCAL_WORKSPACE_EXECUTOR_FACTS = {
37
39
  secrets: 'host_env',
38
40
  };
39
41
  export class LocalWorkspaceExecutor {
42
+ input;
40
43
  facts = LOCAL_WORKSPACE_EXECUTOR_FACTS;
44
+ constructor(input = {}) {
45
+ this.input = input;
46
+ }
41
47
  async exec(input) {
42
48
  const options = {
43
49
  cwd: input.cwd,
@@ -165,36 +171,26 @@ export class LocalWorkspaceExecutor {
165
171
  return { key: path };
166
172
  }
167
173
  async globFiles(input) {
168
- const files = [];
169
- const limit = input.limit ?? 200;
170
- for await (const file of nodeGlob(input.pattern, { cwd: input.cwd })) {
171
- files.push(typeof file === 'string' ? file : file.name);
172
- if (files.length >= limit)
173
- break;
174
- }
175
- return { files };
174
+ return globFiles(input);
176
175
  }
177
176
  async grepFiles(input) {
178
- const args = ['-n', '--no-heading', `--max-count=${input.maxCountPerFile}`];
179
- if (input.glob)
180
- args.push('--glob', input.glob);
181
- args.push('--', input.pattern, input.path);
177
+ const executable = await resolveRipgrepExecutable(this.input.rgCandidates ??
178
+ defaultRipgrepCandidates(this.input.hostEnv ?? process.env, this.input.platform ?? process.platform));
182
179
  try {
183
- const { stdout } = await execFileAsync('rg', args, {
184
- cwd: input.cwd,
185
- maxBuffer: 5 * 1024 * 1024,
186
- timeout: input.timeoutMs,
187
- ...(input.abortSignal ? { signal: input.abortSignal } : {}),
188
- });
189
- return { matches: stdout.split('\n').filter(Boolean).slice(0, input.limit) };
180
+ return await searchFiles({ ...input, executable: executable ?? 'rg' });
190
181
  }
191
182
  catch (error) {
192
- if (error?.code === 1)
193
- return { matches: [] };
183
+ // Node reports a missing spawn cwd exactly like a missing executable
184
+ // (both `spawn rg ENOENT`), so only blame ripgrep once the cwd exists.
185
+ if (error?.code === 'ENOENT' && (await isDirectory(input.cwd)))
186
+ throw new RipgrepUnavailableError(ripgrepMissingOnPathMessage(currentRipgrepEnvironment()), { cause: error });
194
187
  throw error;
195
188
  }
196
189
  }
197
190
  }
191
+ async function isDirectory(path) {
192
+ return await fs.stat(path).then((stat) => stat.isDirectory(), () => false);
193
+ }
198
194
  export function createLocalWorkspaceExecutor() {
199
195
  return new LocalWorkspaceExecutor();
200
196
  }
@@ -8,6 +8,7 @@
8
8
  "./ask-user-question-tool": "./dist/ask-user-question-tool.js",
9
9
  "./ai-sdk-backend": "./dist/ai-sdk-backend.js",
10
10
  "./mcp-tools": "./dist/mcp-tools.js",
11
+ "./read-page": "./dist/read-page.js",
11
12
  "./builtin-tools": "./dist/builtin-tools.js",
12
13
  "./shell-tools": "./dist/shell-tools.js",
13
14
  "./shell-run-manager": "./dist/shell-run-manager.js",
@@ -154,6 +155,7 @@
154
155
  "@xterm/headless": "^6.0.0",
155
156
  "ai": "7.0.99",
156
157
  "ajv": "^8.20.0",
158
+ "glob": "^13.0.6",
157
159
  "https-proxy-agent": "^9.1.0",
158
160
  "image-dimensions": "^2.5.1",
159
161
  "linkedom": "^0.18.13",
@@ -16,103 +16,187 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- /** Shared consequence-oriented copy, not a second lifecycle policy. */
20
- export function formatHostHandoff(view, locale) {
21
- const zh = locale !== 'en';
22
- const tw = locale === 'zh-TW';
23
- const titles = tw
24
- ? {
19
+ const COPY = {
20
+ 'zh-CN': {
21
+ titles: {
22
+ replacement_required: '需要切换 WSL 后台服务',
23
+ busy: '后台服务仍在使用中',
24
+ activity_unknown: '需要确认是否停止后台服务',
25
+ operator_required: '后台服务需要手动更新',
26
+ repair_required: '后台服务需要修复',
27
+ retry_required: '暂时无法完成交接',
28
+ },
29
+ descriptions: (targetName) => ({
30
+ replacement_required: '当前尚未更新。选择继续后,将通过已安装的管理程序安全停止旧服务并启动选定版本;有活动工作时会另行确认。',
31
+ busy: '其它连接或正在执行的工作阻止了自动交接。停止并继续可能中断这些工作。',
32
+ activity_unknown: '后台服务与当前客户端不兼容,且无法确认有哪些工作仍在运行。停止并继续会重新启动服务,可能中断其他窗口或设备上的工作。',
33
+ operator_required: `目前无法从这里更新后台服务。请通过 ${targetName} 上管理此服务的应用或命令更新,然后重试。`,
34
+ repair_required: '上次启动或交接失败。请查看下方诊断信息,修复原因后再重试;如果状态没有变化,重复重试通常不会解决问题。',
35
+ retry_required: '服务状态发生了变化,或交接尚未完成。可以安全重试,不会默认中断工作。',
36
+ }),
37
+ recoveryGuidance: {
38
+ managed: '此后台服务由安装它的 Maka 管理。如果由 Desktop 安装,请在该 Desktop 中打开此工作区,然后选择“停止旧服务并继续”;否则请使用安装它的 Maka 更新服务,再回到这里重试。',
39
+ owner: '此后台服务属于另一个 Maka 安装。请使用管理它的 Maka 安装更新,然后重试。',
40
+ installation: '本次启动使用临时安装,无法接管现有后台服务。请使用已安装的 Maka 更新服务,然后重试。',
41
+ identity: '无法确认旧后台服务的进程身份,因此不能安全停止它。请关闭启动它的 Maka 或通过其管理程序停止服务,然后重试。',
42
+ },
43
+ manualBusy: '旧服务拒绝安全停止;这可能是其他连接或后台工作所致。关闭其他客户端后可再次尝试安全停止,或明确选择中断工作并继续。',
44
+ manualOperatorTitle: '无法连接后台服务',
45
+ manualOperatorDescription: '当前版本无法连接,且尚未执行更新。请查看诊断或更新客户端,完成后重新检查。',
46
+ activity: (connections, operations) => `${connections} 个连接 · ${operations} 个进行中的操作`,
47
+ background: (count) => `${count} 个后台工作`,
48
+ backgroundUnknown: '后台工作数量未知',
49
+ waiting: {
50
+ recheck: '重新检查只会重试连接,不会更新服务。',
51
+ natural: 'Maka 会持续检查,并在可以安全继续时自动继续。',
52
+ blocked: 'Maka 会持续检查。若状态没有变化,等待或重试不会解决此问题。',
53
+ },
54
+ repairNotice: '修复将使用当前应用配套的版本,可能替换现有服务版本。',
55
+ labels: {
56
+ cancel: '取消',
57
+ retry: '安全重试',
58
+ recheck: '重新检查',
59
+ replace: '停止旧服务并继续',
60
+ interrupt: '停止并继续',
61
+ interruptRepair: '中断并修复',
62
+ interruptRecheck: '中断工作并继续',
63
+ },
64
+ phases: {
65
+ checking: '正在检查服务',
66
+ staging: '正在准备更新',
67
+ pausing: '正在等待安全暂停点',
68
+ retiring: '正在停止旧服务',
69
+ replacing: '正在替换服务',
70
+ verifying: '正在确认工作区已就绪',
71
+ },
72
+ progressTitle: '正在继续打开工作区',
73
+ progressDetail: '正在完成交接或安全收尾,请稍候。',
74
+ },
75
+ 'zh-TW': {
76
+ titles: {
25
77
  replacement_required: '需要切換 WSL 背景服務',
26
78
  busy: '背景服務仍在使用中',
27
79
  activity_unknown: '需要確認是否停止背景服務',
28
80
  operator_required: '背景服務需要手動更新',
29
81
  repair_required: '背景服務需要修復',
30
82
  retry_required: '暫時無法完成交接',
31
- }
32
- : zh
33
- ? {
34
- replacement_required: '需要切换 WSL 后台服务',
35
- busy: '后台服务仍在使用中',
36
- activity_unknown: '需要确认是否停止后台服务',
37
- operator_required: '后台服务需要手动更新',
38
- repair_required: '后台服务需要修复',
39
- retry_required: '暂时无法完成交接',
40
- }
41
- : {
42
- replacement_required: 'Switch the WSL background service',
43
- busy: 'Your background service is still in use',
44
- activity_unknown: 'Confirm before stopping the service',
45
- operator_required: 'The background service needs a manual update',
46
- repair_required: 'Your background service needs repair',
47
- retry_required: 'The handoff could not finish yet',
48
- };
49
- const descriptions = tw
50
- ? {
83
+ },
84
+ descriptions: (targetName) => ({
51
85
  replacement_required: '目前尚未更新。選擇繼續後,將透過已安裝的管理程式安全停止舊服務並啟動選定版本;有進行中的工作時會另行確認。',
52
86
  busy: '其他連線或正在執行的工作阻止了自動交接。停止並繼續可能中斷這些工作。',
53
87
  activity_unknown: '背景服務與目前用戶端不相容,且無法確認有哪些工作仍在執行。停止並繼續會重新啟動服務,可能中斷其他視窗或裝置上的工作。',
54
- operator_required: `目前無法從這裡更新背景服務。請透過 ${view.target.name} 上管理此服務的應用程式或命令更新,再重試。`,
88
+ operator_required: `目前無法從這裡更新背景服務。請透過 ${targetName} 上管理此服務的應用程式或命令更新,再重試。`,
55
89
  repair_required: '上次啟動或交接失敗。請查看下方診斷資訊,修復原因後再重試;如果狀態沒有改變,重複重試通常不會解決問題。',
56
90
  retry_required: '服務狀態發生了變化,或交接尚未完成。可以安全重試,不會預設中斷工作。',
57
- }
58
- : zh
59
- ? {
60
- replacement_required: '当前尚未更新。选择继续后,将通过已安装的管理程序安全停止旧服务并启动选定版本;有活动工作时会另行确认。',
61
- busy: '其它连接或正在执行的工作阻止了自动交接。停止并继续可能中断这些工作。',
62
- activity_unknown: '后台服务与当前客户端不兼容,且无法确认有哪些工作仍在运行。停止并继续会重新启动服务,可能中断其他窗口或设备上的工作。',
63
- operator_required: `目前无法从这里更新后台服务。请通过 ${view.target.name} 上管理此服务的应用或命令更新,然后重试。`,
64
- repair_required: '上次启动或交接失败。请查看下方诊断信息,修复原因后再重试;如果状态没有变化,重复重试通常不会解决问题。',
65
- retry_required: '服务状态发生了变化,或交接尚未完成。可以安全重试,不会默认中断工作。',
66
- }
67
- : {
68
- replacement_required: 'No update is running. Continue to retire the old service through its installed operator and start the selected version. Interrupting active work requires a separate confirmation.',
69
- busy: 'Other connections or work in progress prevent an automatic handoff. Stopping the service may interrupt that work.',
70
- activity_unknown: 'The background service is incompatible with this client, and its active work is unknown. Stop and continue restarts it and may interrupt work in other windows or devices.',
71
- operator_required: `This client cannot update the background service here. Use its managing app or operator command on ${view.target.name}, then retry.`,
72
- repair_required: 'The last startup or handoff failed. Review the diagnostic below, correct the cause, then retry. Repeating the same action without a state change will usually fail again.',
73
- retry_required: 'The service changed or the handoff has not finished. A safe retry will not interrupt work by default.',
74
- };
91
+ }),
92
+ recoveryGuidance: {
93
+ managed: '此背景服務由安裝它的 Maka 管理。如果由 Desktop 安裝,請在該 Desktop 中開啟此工作區,然後選擇「停止舊服務並繼續」;否則請使用安裝它的 Maka 更新服務,再回到這裡重試。',
94
+ owner: '此背景服務屬於另一個 Maka 安裝。請使用管理它的 Maka 安裝更新,再重試。',
95
+ installation: '這次啟動使用暫存安裝,無法接管現有背景服務。請使用已安裝的 Maka 更新服務,再重試。',
96
+ identity: '無法確認舊背景服務的處理程序身分,因此不能安全停止它。請關閉啟動它的 Maka 或透過其管理程式停止服務,再重試。',
97
+ },
98
+ manualBusy: '舊服務拒絕安全停止;這可能是其他連線或背景工作所致。關閉其他用戶端後可再次嘗試安全停止,或明確選擇中斷工作並繼續。',
99
+ manualOperatorTitle: '無法連線到背景服務',
100
+ manualOperatorDescription: '目前版本無法連線,且尚未執行更新。請查看診斷或更新用戶端,完成後重新檢查。',
101
+ activity: (connections, operations) => `${connections} 個連線 · ${operations} 個進行中的操作`,
102
+ background: (count) => `${count} 個背景工作`,
103
+ backgroundUnknown: '背景工作數量未知',
104
+ waiting: {
105
+ recheck: '重新檢查只會重試連線,不會更新服務。',
106
+ natural: 'Maka 會持續檢查,並在可以安全繼續時自動繼續。',
107
+ blocked: 'Maka 會持續檢查。若狀態沒有改變,等待或重試不會解決此問題。',
108
+ },
109
+ repairNotice: '修復將使用目前應用程式配套的版本,可能替換現有服務版本。',
110
+ labels: {
111
+ cancel: '取消',
112
+ retry: '安全重試',
113
+ recheck: '重新檢查',
114
+ replace: '停止舊服務並繼續',
115
+ interrupt: '停止並繼續',
116
+ interruptRepair: '中斷並修復',
117
+ interruptRecheck: '中斷工作並繼續',
118
+ },
119
+ phases: {
120
+ checking: '正在檢查服務',
121
+ staging: '正在準備更新',
122
+ pausing: '正在等待安全暫停點',
123
+ retiring: '正在停止舊服務',
124
+ replacing: '正在替換服務',
125
+ verifying: '正在確認工作區已就緒',
126
+ },
127
+ progressTitle: '正在繼續開啟工作區',
128
+ progressDetail: '正在完成交接或安全收尾,請稍候。',
129
+ },
130
+ en: {
131
+ titles: {
132
+ replacement_required: 'Switch the WSL background service',
133
+ busy: 'Your background service is still in use',
134
+ activity_unknown: 'Confirm before stopping the service',
135
+ operator_required: 'The background service needs a manual update',
136
+ repair_required: 'Your background service needs repair',
137
+ retry_required: 'The handoff could not finish yet',
138
+ },
139
+ descriptions: (targetName) => ({
140
+ replacement_required: 'No update is running. Continue to retire the old service through its installed operator and start the selected version. Interrupting active work requires a separate confirmation.',
141
+ busy: 'Other connections or work in progress prevent an automatic handoff. Stopping the service may interrupt that work.',
142
+ activity_unknown: 'The background service is incompatible with this client, and its active work is unknown. Stop and continue restarts it and may interrupt work in other windows or devices.',
143
+ operator_required: `This client cannot update the background service here. Use its managing app or operator command on ${targetName}, then retry.`,
144
+ repair_required: 'The last startup or handoff failed. Review the diagnostic below, correct the cause, then retry. Repeating the same action without a state change will usually fail again.',
145
+ retry_required: 'The service changed or the handoff has not finished. A safe retry will not interrupt work by default.',
146
+ }),
147
+ recoveryGuidance: {
148
+ managed: 'This background service is managed by the Maka installation that created it. If Desktop installed it, open this workspace there and choose Stop old service and continue. Otherwise update the service with that Maka installation, then return here and retry.',
149
+ owner: 'Another Maka installation owns this background service. Update it using that installation, then retry.',
150
+ installation: 'This temporary installation cannot take over the existing background service. Update it using an installed Maka, then retry.',
151
+ identity: 'The old background process could not be identified safely. Close the Maka instance that started it or stop it through its operator, then retry.',
152
+ },
153
+ manualBusy: 'The old service refused safe retirement. Other connections or background work may be keeping it in use. Close other clients and try stopping safely again, or explicitly interrupt work and continue.',
154
+ manualOperatorTitle: 'Cannot connect to the background service',
155
+ manualOperatorDescription: 'These builds cannot connect and no update is running. Check the diagnostic or update the client, then recheck.',
156
+ activity: (connections, operations) => `${connections} connections · ${operations} operations in progress`,
157
+ background: (count) => `${count} background activities`,
158
+ backgroundUnknown: 'Background activity count unknown',
159
+ waiting: {
160
+ recheck: 'Recheck retries the connection without updating the service.',
161
+ natural: 'Maka keeps checking and continues automatically when it is safe.',
162
+ blocked: 'Maka keeps checking. Waiting or retrying will not resolve this unless the service state changes.',
163
+ },
164
+ repairNotice: 'Repair uses the version supplied with this app and may replace the installed service version.',
165
+ labels: {
166
+ cancel: 'Cancel',
167
+ retry: 'Retry safely',
168
+ recheck: 'Recheck',
169
+ replace: 'Stop old service and continue',
170
+ interrupt: 'Stop and continue',
171
+ interruptRepair: 'Interrupt and repair',
172
+ interruptRecheck: 'Interrupt work and continue',
173
+ },
174
+ phases: {
175
+ checking: 'Checking the service',
176
+ staging: 'Preparing the update',
177
+ pausing: 'Waiting for a safe pause point',
178
+ retiring: 'Stopping the previous service',
179
+ replacing: 'Replacing the service',
180
+ verifying: 'Verifying your workspace is ready',
181
+ },
182
+ progressTitle: 'Continuing to your workspace',
183
+ progressDetail: 'Finishing the handoff or its safe recovery. Please wait.',
184
+ },
185
+ };
186
+ /** Shared consequence-oriented copy, not a second lifecycle policy. */
187
+ export function formatHostHandoff(view, locale) {
188
+ const copy = COPY[locale];
189
+ let title = copy.titles[view.reason];
190
+ let description = copy.descriptions(view.target.name)[view.reason];
75
191
  if (view.recoveryBlocker && view.reason === 'operator_required') {
76
- const guidance = zh
77
- ? tw
78
- ? {
79
- managed: '此背景服務由安裝它的 Maka 管理。如果由 Desktop 安裝,請在該 Desktop 中開啟此工作區,然後選擇「停止舊服務並繼續」;否則請使用安裝它的 Maka 更新服務,再回到這裡重試。',
80
- owner: '此背景服務屬於另一個 Maka 安裝。請使用管理它的 Maka 安裝更新,再重試。',
81
- installation: '這次啟動使用暫存安裝,無法接管現有背景服務。請使用已安裝的 Maka 更新服務,再重試。',
82
- identity: '無法確認舊背景服務的處理程序身分,因此不能安全停止它。請關閉啟動它的 Maka 或透過其管理程式停止服務,再重試。',
83
- }
84
- : {
85
- managed: '此后台服务由安装它的 Maka 管理。如果由 Desktop 安装,请在该 Desktop 中打开此工作区,然后选择“停止旧服务并继续”;否则请使用安装它的 Maka 更新服务,再回到这里重试。',
86
- owner: '此后台服务属于另一个 Maka 安装。请使用管理它的 Maka 安装更新,然后重试。',
87
- installation: '本次启动使用临时安装,无法接管现有后台服务。请使用已安装的 Maka 更新服务,然后重试。',
88
- identity: '无法确认旧后台服务的进程身份,因此不能安全停止它。请关闭启动它的 Maka 或通过其管理程序停止服务,然后重试。',
89
- }
90
- : {
91
- managed: 'This background service is managed by the Maka installation that created it. If Desktop installed it, open this workspace there and choose Stop old service and continue. Otherwise update the service with that Maka installation, then return here and retry.',
92
- owner: 'Another Maka installation owns this background service. Update it using that installation, then retry.',
93
- installation: 'This temporary installation cannot take over the existing background service. Update it using an installed Maka, then retry.',
94
- identity: 'The old background process could not be identified safely. Close the Maka instance that started it or stop it through its operator, then retry.',
95
- };
96
- descriptions.operator_required = guidance[view.recoveryBlocker];
192
+ description = copy.recoveryGuidance[view.recoveryBlocker];
97
193
  }
98
194
  if (view.manualRecheck && view.reason === 'busy') {
99
- descriptions.busy = zh
100
- ? tw
101
- ? '舊服務拒絕安全停止;這可能是其他連線或背景工作所致。關閉其他用戶端後可再次嘗試安全停止,或明確選擇中斷工作並繼續。'
102
- : '旧服务拒绝安全停止;这可能是其他连接或后台工作所致。关闭其他客户端后可再次尝试安全停止,或明确选择中断工作并继续。'
103
- : 'The old service refused safe retirement. Other connections or background work may be keeping it in use. Close other clients and try stopping safely again, or explicitly interrupt work and continue.';
195
+ description = copy.manualBusy;
104
196
  }
105
197
  if (view.manualRecheck && view.reason === 'operator_required') {
106
- titles.operator_required = zh
107
- ? tw
108
- ? '無法連線到背景服務'
109
- : '无法连接后台服务'
110
- : 'Cannot connect to the background service';
111
- descriptions.operator_required = zh
112
- ? tw
113
- ? '目前版本無法連線,且尚未執行更新。請查看診斷或更新用戶端,完成後重新檢查。'
114
- : '当前版本无法连接,且尚未执行更新。请查看诊断或更新客户端,完成后重新检查。'
115
- : 'These builds cannot connect and no update is running. Check the diagnostic or update the client, then recheck.';
198
+ title = copy.manualOperatorTitle;
199
+ description = copy.manualOperatorDescription;
116
200
  }
117
201
  const packageChange = view.packageChange
118
202
  ? `${view.target.name}: ${view.packageChange.current} → ${view.packageChange.target}`
@@ -122,120 +206,30 @@ export function formatHostHandoff(view, locale) {
122
206
  const backgroundFacts = !activity
123
207
  ? ''
124
208
  : background !== undefined
125
- ? tw
126
- ? `${background} 個背景工作`
127
- : zh
128
- ? `${background} 个后台工作`
129
- : `${background} background activities`
130
- : tw
131
- ? '背景工作數量未知'
132
- : zh
133
- ? '后台工作数量未知'
134
- : 'Background activity count unknown';
135
- const facts = activity
136
- ? tw
137
- ? `${activity.connections} 個連線 · ${activity.activeOperations} 個進行中的操作`
138
- : zh
139
- ? `${activity.connections} 个连接 · ${activity.activeOperations} 个进行中的操作`
140
- : `${activity.connections} connections · ${activity.activeOperations} operations in progress`
141
- : '';
209
+ ? copy.background(background)
210
+ : copy.backgroundUnknown;
211
+ const facts = activity ? copy.activity(activity.connections, activity.activeOperations) : '';
142
212
  const waiting = view.manualRecheck
143
- ? zh
144
- ? tw
145
- ? '重新檢查只會重試連線,不會更新服務。'
146
- : '重新检查只会重试连接,不会更新服务。'
147
- : 'Recheck retries the connection without updating the service.'
213
+ ? copy.waiting.recheck
148
214
  : view.mayExitNaturally
149
- ? tw
150
- ? 'Maka 會持續檢查,並在可以安全繼續時自動繼續。'
151
- : zh
152
- ? 'Maka 会持续检查,并在可以安全继续时自动继续。'
153
- : 'Maka keeps checking and continues automatically when it is safe.'
154
- : tw
155
- ? 'Maka 會持續檢查。若狀態沒有改變,等待或重試不會解決此問題。'
156
- : zh
157
- ? 'Maka 会持续检查。若状态没有变化,等待或重试不会解决此问题。'
158
- : 'Maka keeps checking. Waiting or retrying will not resolve this unless the service state changes.';
159
- const repairNotice = view.operation === 'repair'
160
- ? tw
161
- ? '修復將使用目前應用程式配套的版本,可能替換現有服務版本。'
162
- : zh
163
- ? '修复将使用当前应用配套的版本,可能替换现有服务版本。'
164
- : 'Repair uses the version supplied with this app and may replace the installed service version.'
165
- : '';
166
- const labels = tw
167
- ? {
168
- cancel: '取消',
169
- retry: view.manualRecheck ? '重新檢查' : '安全重試',
170
- replace: '停止舊服務並繼續',
171
- interrupt: view.manualRecheck
172
- ? '中斷工作並繼續'
173
- : view.operation === 'repair'
174
- ? '中斷並修復'
175
- : '停止並繼續',
176
- }
177
- : zh
178
- ? {
179
- cancel: '取消',
180
- retry: view.manualRecheck ? '重新检查' : '安全重试',
181
- replace: '停止旧服务并继续',
182
- interrupt: view.manualRecheck
183
- ? '中断工作并继续'
184
- : view.operation === 'repair'
185
- ? '中断并修复'
186
- : '停止并继续',
187
- }
188
- : {
189
- cancel: 'Cancel',
190
- retry: view.manualRecheck ? 'Recheck' : 'Retry safely',
191
- replace: 'Stop old service and continue',
192
- interrupt: view.manualRecheck
193
- ? 'Interrupt work and continue'
194
- : view.operation === 'repair'
195
- ? 'Interrupt and repair'
196
- : 'Stop and continue',
197
- };
198
- const phases = tw
199
- ? {
200
- checking: '正在檢查服務',
201
- staging: '正在準備更新',
202
- pausing: '正在等待安全暫停點',
203
- retiring: '正在停止舊服務',
204
- replacing: '正在替換服務',
205
- verifying: '正在確認工作區已就緒',
206
- }
207
- : zh
208
- ? {
209
- checking: '正在检查服务',
210
- staging: '正在准备更新',
211
- pausing: '正在等待安全暂停点',
212
- retiring: '正在停止旧服务',
213
- replacing: '正在替换服务',
214
- verifying: '正在确认工作区已就绪',
215
- }
216
- : {
217
- checking: 'Checking the service',
218
- staging: 'Preparing the update',
219
- pausing: 'Waiting for a safe pause point',
220
- retiring: 'Stopping the previous service',
221
- replacing: 'Replacing the service',
222
- verifying: 'Verifying your workspace is ready',
223
- };
215
+ ? copy.waiting.natural
216
+ : copy.waiting.blocked;
217
+ const repairNotice = view.operation === 'repair' ? copy.repairNotice : '';
218
+ const labels = {
219
+ cancel: copy.labels.cancel,
220
+ retry: view.manualRecheck ? copy.labels.recheck : copy.labels.retry,
221
+ replace: copy.labels.replace,
222
+ interrupt: view.manualRecheck
223
+ ? copy.labels.interruptRecheck
224
+ : view.operation === 'repair'
225
+ ? copy.labels.interruptRepair
226
+ : copy.labels.interrupt,
227
+ };
224
228
  return {
225
- title: view.state === 'progress'
226
- ? tw
227
- ? '正在繼續開啟工作區'
228
- : zh
229
- ? '正在继续打开工作区'
230
- : 'Continuing to your workspace'
231
- : titles[view.reason],
232
- description: view.state === 'progress' ? phases[view.phase ?? 'checking'] : descriptions[view.reason],
229
+ title: view.state === 'progress' ? copy.progressTitle : title,
230
+ description: view.state === 'progress' ? copy.phases[view.phase ?? 'checking'] : description,
233
231
  detail: view.state === 'progress'
234
- ? tw
235
- ? '正在完成交接或安全收尾,請稍候。'
236
- : zh
237
- ? '正在完成交接或安全收尾,请稍候。'
238
- : 'Finishing the handoff or its safe recovery. Please wait.'
232
+ ? copy.progressDetail
239
233
  : [packageChange, facts, backgroundFacts, waiting, repairNotice, view.operatorStep]
240
234
  .filter(Boolean)
241
235
  .join('\n'),
@@ -139,6 +139,9 @@ class RuntimeHostReconnectingConnectionImpl {
139
139
  }
140
140
  }
141
141
  }
142
+ openSessionSubscriptionOnce(input, timeoutMs) {
143
+ return this.#requireCurrent('subscription.open').openSessionSubscription(input, timeoutMs);
144
+ }
142
145
  async replaceClientCapabilities(provider, timeoutMs) {
143
146
  const connection = this.#requireCurrent('client.capability.replace');
144
147
  return connection.replaceClientCapabilities(provider, timeoutMs);
@@ -62,7 +62,11 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1;
62
62
  export const RUNTIME_HOST_PROTOCOL_VERSION = 0;
63
63
  // Increment when the same protocol version no longer guarantees safe Client-Host
64
64
  // interoperability. Mismatches are rejected before domain commands are admitted.
65
- export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 149;
65
+ export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 151;
66
+ // 151: WorkHub selects and delegates through a durable Host Form interaction.
67
+ // 150: Message admission accepts an empty-text Message that carries a quote or
68
+ // an attachment (#4804). Peers older than this epoch reject that frame at
69
+ // admission, so the pair must refuse each other at the handshake.
66
70
  // 149: Connection model overrides retain disabled identities and separate capacity
67
71
  // from compaction. Catalog entries carry overrides; clients do not rebuild them.
68
72
  // 148: Model catalog entries include image support before a user override.
@@ -19,7 +19,7 @@
19
19
  import { invalidProtocolFrame } from './errors.js';
20
20
  import { assertExactKeys, requireCount, requireEntityId, requireExactRecord, requireId, requireRecord, requireShapedRecord, requireUtf8String, } from './codec.js';
21
21
  import { defineOperation } from './operation-spec.js';
22
- import { decodeMessageContent, decodeMessageAdmissionContent, decodeSkillIds, decodeTurnOrchestration, decodeTurnSnapshot, TURN_MESSAGE_TEXT_MAX_BYTES, } from './turn.js';
22
+ import { decodeMessageAdmissionContent, decodeSkillIds, decodeTurnOrchestration, decodeTurnSnapshot, TURN_MESSAGE_TEXT_MAX_BYTES, } from './turn.js';
23
23
  import { decodeSkillInvocationResult } from '@maka/core/skill-invocation';
24
24
  export const MESSAGE_QUEUE_MAX_ENTRIES = 64;
25
25
  export const MESSAGE_QUEUE_PROJECTION_MAX_BYTES = 52 * 1024;
@@ -432,7 +432,7 @@ function decodeMessageQueueEntrySnapshot(value) {
432
432
  const base = {
433
433
  entryId: requireEntityId(record.entryId, 'entryId'),
434
434
  messageId: requireEntityId(record.messageId, 'messageId'),
435
- content: decodeMessageContent(record.content),
435
+ content: decodeMessageAdmissionContent(record.content),
436
436
  placement: requireMessagePlacement(record.placement),
437
437
  };
438
438
  if (record.state === 'queued' || record.state === 'retracted') {
@@ -222,6 +222,7 @@ export const REMOTE_OWNER_OPERATION_GRANTS = Object.freeze([
222
222
  'web-search.execute',
223
223
  'workhub.coordination.answer',
224
224
  'workhub.coordination.actFromTurn',
225
+ 'workhub.coordination.selectAndDelegate',
225
226
  'workhub.coordination.candidates',
226
227
  'workhub.coordination.configureModel',
227
228
  'workhub.coordination.query',
@@ -24,7 +24,7 @@ import { decodeSessionStatus } from './session-status.js';
24
24
  import { decodeSessionInteractionProjection, } from './interaction.js';
25
25
  import { decodeSessionMessageQueueProjection, } from './message.js';
26
26
  import { defineOperation } from './operation-spec.js';
27
- import { decodeMessageContent, decodeTurnSnapshot, } from './turn.js';
27
+ import { decodeMessageAdmissionContent, decodeTurnSnapshot, } from './turn.js';
28
28
  import { decodeGoalProjection } from './goal.js';
29
29
  import { decodeRuntimeResourceRef } from './runtime-resource.js';
30
30
  import { decodeSessionTranscriptBootstrap, SESSION_TRANSCRIPT_BOOTSTRAP_MAX_BYTES, } from './session-transcript.js';
@@ -510,7 +510,7 @@ function decodeSessionSteeringEvent(record) {
510
510
  turnId: requireEntityId(record.turnId, 'turnId'),
511
511
  ts: requireCount(record.ts, 'Session steering event timestamp'),
512
512
  messageId: requireEntityId(record.messageId, 'messageId'),
513
- content: decodeMessageContent(record.content),
513
+ content: decodeMessageAdmissionContent(record.content),
514
514
  };
515
515
  }
516
516
  function decodeSessionToolEvent(value) {
@@ -17,7 +17,7 @@
17
17
  * under the License.
18
18
  */
19
19
  import { MAX_ATTACHMENT_BYTES, MAX_ATTACHMENT_COUNT } from '@maka/core/attachments';
20
- import { decodeMessageContent as decodeCanonicalMessageContent, DIRECTORY_REFERENCE_MAX_COUNT, isCanonicalAttachmentRef, } from '@maka/core/events';
20
+ import { decodeMessageContent as decodeCanonicalMessageContent, DIRECTORY_REFERENCE_MAX_COUNT, hasMeaningfulMessageContent, isCanonicalAttachmentRef, } from '@maka/core/events';
21
21
  import { isOrchestrationMode, isTurnOrchestrationSource, } from '@maka/core/orchestration';
22
22
  import { decodeSkillInvocationResult, } from '@maka/core/skill-invocation';
23
23
  import { invalidProtocolFrame } from './errors.js';
@@ -268,7 +268,15 @@ export function decodeMessageContent(value, allowEmptyText = false) {
268
268
  }
269
269
  /** Client-authored Messages cannot claim Host-owned Session context references. */
270
270
  export function decodeMessageAdmissionContent(value, allowEmptyText = false) {
271
- const content = decodeMessageContent(value, allowEmptyText);
271
+ // Structure first with text emptiness unconstrained, then apply the
272
+ // shared meaningful-content predicate: a quote or an attachment carries
273
+ // the turn by itself, so empty inline text is admissible when either is
274
+ // present (#4804). A truly contentless Message still throws, with the
275
+ // same frame error the text-length rule produced.
276
+ const content = decodeMessageContent(value, true);
277
+ if (!allowEmptyText && !hasMeaningfulMessageContent(content)) {
278
+ throw invalidProtocolFrame('Invalid Message text');
279
+ }
272
280
  if (content.attachments?.some((attachment) => attachment.ref.kind === 'session_context')) {
273
281
  throw invalidProtocolFrame('Session context references are Host-owned');
274
282
  }