maka-agent 0.2.0-dev.24.20260907 → 0.2.0-dev.26.20260909

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 (154) hide show
  1. package/README.md +38 -19
  2. package/README.zh-CN.md +34 -19
  3. package/dist/mcp-capability-provider.js +4 -1
  4. package/dist/pi-transcript-tools.js +6 -0
  5. package/dist/pi-tui-layout.js +56 -10
  6. package/dist/pi-tui-mcp-status.js +2 -2
  7. package/dist/pi-tui-pickers.js +121 -30
  8. package/dist/pi-tui-runner.js +156 -42
  9. package/dist/pi-tui-todo.js +221 -0
  10. package/dist/pi-tui-transcript-viewer.js +134 -7
  11. package/dist/runtime-host-cli-context.js +70 -13
  12. package/dist/runtime-host-handoff-surface.js +51 -26
  13. package/dist/runtime-host-local-handoff.js +11 -1
  14. package/dist/runtime-host-service-manager.js +0 -3
  15. package/dist/runtime-host-session-channel.js +13 -0
  16. package/dist/runtime-host-session-driver.js +42 -1
  17. package/dist/tui-autocomplete-layout.js +3 -1
  18. package/dist/tui-copy-catalog.js +85 -6
  19. package/dist/tui-editor-render.js +36 -0
  20. package/dist/tui-mcp-control.js +41 -6
  21. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  22. package/node_modules/@maka/core/dist/agent-run.js +1 -0
  23. package/node_modules/@maka/core/dist/attachments.js +3 -0
  24. package/node_modules/@maka/core/dist/canonical-json.js +27 -0
  25. package/node_modules/@maka/core/dist/capabilities.js +35 -0
  26. package/node_modules/@maka/core/dist/computer-use.js +0 -1
  27. package/node_modules/@maka/core/dist/display-redaction.js +18 -0
  28. package/node_modules/@maka/core/dist/llm-connections.js +13 -0
  29. package/node_modules/@maka/core/dist/model-call-attempt.js +2 -0
  30. package/node_modules/@maka/core/dist/model-metadata.js +4 -0
  31. package/node_modules/@maka/core/dist/redaction.js +35 -0
  32. package/node_modules/@maka/core/dist/run-composition.js +77 -2
  33. package/node_modules/@maka/core/dist/runtime-event.js +3 -0
  34. package/node_modules/@maka/core/dist/session.js +57 -6
  35. package/node_modules/@maka/core/dist/slash-command-catalog.js +1 -0
  36. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  37. package/node_modules/@maka/core/dist/workhub-action-result.js +68 -0
  38. package/node_modules/@maka/core/package.json +3 -3
  39. package/node_modules/@maka/eval/dist/experiment-directory.js +4 -13
  40. package/node_modules/@maka/eval/dist/toolchain-verification.js +1 -1
  41. package/node_modules/@maka/mcp/dist/form-elicitation.js +213 -0
  42. package/node_modules/@maka/mcp/dist/index.js +337 -71
  43. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +0 -13
  44. package/node_modules/@maka/runtime/dist/agent-run.js +90 -2
  45. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +16 -8
  46. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +57 -50
  47. package/node_modules/@maka/runtime/dist/archive-read-tool.js +2 -2
  48. package/node_modules/@maka/runtime/dist/context-budget-helpers.js +0 -16
  49. package/node_modules/@maka/runtime/dist/conversation-copy.js +153 -11
  50. package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +3 -3
  51. package/node_modules/@maka/runtime/dist/goal-continuation.js +5 -1
  52. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +69 -1
  53. package/node_modules/@maka/runtime/dist/mcp-tools.js +7 -2
  54. package/node_modules/@maka/runtime/dist/model-fetcher.js +42 -2
  55. package/node_modules/@maka/runtime/dist/model-history.js +0 -112
  56. package/node_modules/@maka/runtime/dist/plugin-runtime.js +20 -10
  57. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +205 -0
  58. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +8 -1
  59. package/node_modules/@maka/runtime/dist/request-shape.js +23 -0
  60. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -1
  61. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +24 -1
  62. package/node_modules/@maka/runtime/dist/runtime-kernel.js +127 -13
  63. package/node_modules/@maka/runtime/dist/session-manager.js +20 -36
  64. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +21 -0
  65. package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +0 -5
  66. package/node_modules/@maka/runtime/dist/test-connection.js +0 -1
  67. package/node_modules/@maka/runtime/dist/tool-activation-identity.js +41 -0
  68. package/node_modules/@maka/runtime/dist/tool-availability.js +11 -1
  69. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +63 -1
  70. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +42 -12
  71. package/node_modules/@maka/runtime/dist/tool-result-archive.js +27 -4
  72. package/node_modules/@maka/runtime/package.json +1 -0
  73. package/node_modules/@maka/runtime-host/dist/client/connection.js +30 -5
  74. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +52 -13
  75. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +2 -0
  76. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +4 -2
  77. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +18 -1
  78. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +19 -0
  79. package/node_modules/@maka/runtime-host/dist/control/endpoint.js +1 -3
  80. package/node_modules/@maka/runtime-host/dist/node-error.js +22 -0
  81. package/node_modules/@maka/runtime-host/dist/operator/managed-deployment.js +1 -3
  82. package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +0 -1
  83. package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +1 -1
  84. package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +1 -3
  85. package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -1
  86. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +3 -2
  87. package/node_modules/@maka/runtime-host/dist/protocol/plan.js +1 -0
  88. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +27 -5
  89. package/node_modules/@maka/runtime-host/dist/protocol/project-catalog.js +2 -2
  90. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -0
  91. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +86 -157
  92. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +4 -0
  93. package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +0 -3
  94. package/node_modules/@maka/runtime-host/dist/server/canonical-session-projection.js +2 -1
  95. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +28 -8
  96. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +112 -56
  97. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +32 -32
  98. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +85 -25
  99. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +42 -14
  100. package/node_modules/@maka/runtime-host/dist/server/failure-diagnostic.js +24 -0
  101. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +7 -3
  102. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -3
  103. package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +5 -2
  104. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +3 -2
  105. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +34 -4
  106. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +51 -23
  107. package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +2 -7
  108. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  109. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
  110. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +110 -14
  111. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-coordinator.js +11 -8
  112. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +31 -21
  113. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +1 -1
  114. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +20 -8
  115. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +5 -2
  116. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +4 -0
  117. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +15 -104
  118. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
  119. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +59 -82
  120. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +132 -139
  121. package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +46 -0
  122. package/node_modules/@maka/storage/dist/agent-run-store.js +14 -1
  123. package/node_modules/@maka/storage/dist/artifact-store.js +40 -3
  124. package/node_modules/@maka/storage/dist/atomic-file-write.js +74 -0
  125. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -1
  126. package/node_modules/@maka/storage/dist/codex-session-adapter.js +284 -196
  127. package/node_modules/@maka/storage/dist/credential-store.js +12 -63
  128. package/node_modules/@maka/storage/dist/execution-stores.js +4 -1
  129. package/node_modules/@maka/storage/dist/git-exec.js +61 -0
  130. package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +8 -35
  131. package/node_modules/@maka/storage/dist/goal-authority.js +2 -2
  132. package/node_modules/@maka/storage/dist/interaction-store.js +2 -2
  133. package/node_modules/@maka/storage/dist/mcp-config-store.js +10 -25
  134. package/node_modules/@maka/storage/dist/project-catalog.js +2 -23
  135. package/node_modules/@maka/storage/dist/runtime-event-invariants.js +2 -2
  136. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +8 -3
  137. package/node_modules/@maka/storage/dist/scheduled-task-store.js +249 -283
  138. package/node_modules/@maka/storage/dist/session-bundle-manifest.js +5 -17
  139. package/node_modules/@maka/storage/dist/session-bundle-policy.js +2 -1
  140. package/node_modules/@maka/storage/dist/session-copy-cleanup.js +2 -1
  141. package/node_modules/@maka/storage/dist/session-store.js +14 -2
  142. package/node_modules/@maka/storage/dist/settings-store.js +8 -4
  143. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +11 -0
  144. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +1 -2
  145. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +12 -1
  146. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +67 -3
  147. package/node_modules/@maka/storage/dist/stable-storage.js +14 -1
  148. package/node_modules/@maka/storage/dist/storage-id.js +28 -0
  149. package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +31 -4
  150. package/node_modules/@maka/storage/dist/workspace-identity.js +2 -15
  151. package/node_modules/@maka/storage/dist/workspace-version-authority-internal.js +1 -8
  152. package/package.json +1 -1
  153. package/node_modules/@maka/core/dist/workhub-creation-intent.js +0 -990
  154. package/node_modules/@maka/core/dist/workhub-session-resolver.js +0 -50
@@ -17,6 +17,64 @@
17
17
  * under the License.
18
18
  */
19
19
  export const TUI_COPY_RESOURCES = {
20
+ todo: {
21
+ en: {
22
+ title: 'Current Todo',
23
+ markedComplete: 'marked complete',
24
+ open: '/todo to view',
25
+ usage: 'Usage: /todo',
26
+ progress: 'Todo',
27
+ unavailable: 'Todo unavailable',
28
+ empty: 'No Todo items',
29
+ loading: 'Loading Todo…',
30
+ hint: '↑↓/PgUp/PgDn scroll · Home/End · Esc close',
31
+ },
32
+ 'zh-CN': {
33
+ title: '当前待办',
34
+ markedComplete: '标记完成',
35
+ open: '/todo 查看',
36
+ usage: '用法:/todo',
37
+ progress: '待办',
38
+ unavailable: '待办不可用',
39
+ empty: '暂无待办',
40
+ loading: '正在加载待办…',
41
+ hint: '↑↓/PgUp/PgDn 滚动 · Home/End · Esc 关闭',
42
+ },
43
+ 'zh-TW': {
44
+ title: '目前待辦',
45
+ markedComplete: '標記完成',
46
+ open: '/todo 查看',
47
+ usage: '用法:/todo',
48
+ progress: '待辦',
49
+ unavailable: '待辦不可用',
50
+ empty: '沒有待辦',
51
+ loading: '正在載入待辦…',
52
+ hint: '↑↓/PgUp/PgDn 捲動 · Home/End · Esc 關閉',
53
+ },
54
+ },
55
+ 'transcript-reader': {
56
+ en: {
57
+ title: 'DETAILED TRANSCRIPT',
58
+ scope: 'loaded history',
59
+ hint: 'Esc/Ctrl+O close · Ctrl+E details · / search · ↑↓/PgUp/PgDn · Home/End',
60
+ matches: 'matches in displayed text',
61
+ back: 'return',
62
+ },
63
+ 'zh-CN': {
64
+ title: '详细记录',
65
+ scope: '已加载历史',
66
+ hint: 'Esc/Ctrl+O 返回 · Ctrl+E 详情 · / 搜索 · ↑↓/PgUp/PgDn · Home/End',
67
+ matches: '处匹配(当前显示内容)',
68
+ back: '回到原位置',
69
+ },
70
+ 'zh-TW': {
71
+ title: '詳細記錄',
72
+ scope: '已載入歷史',
73
+ hint: 'Esc/Ctrl+O 返回 · Ctrl+E 詳情 · / 搜尋 · ↑↓/PgUp/PgDn · Home/End',
74
+ matches: '處符合(目前顯示內容)',
75
+ back: '回到原位置',
76
+ },
77
+ },
20
78
  'host-owner': {
21
79
  en: {
22
80
  unavailable: 'Runtime Host owner controls are unavailable.',
@@ -268,6 +326,7 @@ export const TUI_COPY_RESOURCES = {
268
326
  'credential-cleanup-failed': 'Stored credentials could not be removed; the configuration was not changed.',
269
327
  'publication-credential-failed': 'The provider credential could not be stored or applied.',
270
328
  'persist-failed': 'The configuration could not be saved.',
329
+ 'commit-unknown': 'Configuration published; crash durability is uncertain. Check synchronization before retrying.',
271
330
  'manager-failed': 'The MCP connection action failed.',
272
331
  turn_active: 'MCP cannot be changed while a turn or another control action is running.',
273
332
  invalid: 'Check the value and try again.',
@@ -366,6 +425,7 @@ export const TUI_COPY_RESOURCES = {
366
425
  'credential-cleanup-failed': '无法删除旧凭据,配置未修改。',
367
426
  'publication-credential-failed': '无法保存或应用 Provider 凭据。',
368
427
  'persist-failed': '无法保存配置。',
428
+ 'commit-unknown': '配置已发布,但无法确认崩溃后的持久性。请先检查同步状态再重试。',
369
429
  'manager-failed': 'MCP 连接操作失败。',
370
430
  turn_active: 'Turn 或其他控制操作运行期间不能修改 MCP。',
371
431
  invalid: '请检查输入后重试。',
@@ -464,6 +524,7 @@ export const TUI_COPY_RESOURCES = {
464
524
  'credential-cleanup-failed': '無法移除已儲存的認證資料;設定未變更。',
465
525
  'publication-credential-failed': '無法儲存或套用 Provider 認證資料。',
466
526
  'persist-failed': '無法儲存設定。',
527
+ 'commit-unknown': '設定已發布,但無法確認當機後的持久性。請先檢查同步狀態再重試。',
467
528
  'manager-failed': 'MCP 連線操作失敗。',
468
529
  turn_active: 'Turn 或其他控制操作執行期間無法修改 MCP。',
469
530
  invalid: '請檢查輸入後重試。',
@@ -562,10 +623,15 @@ export const TUI_COPY_RESOURCES = {
562
623
  pickers: {
563
624
  en: {
564
625
  modelPickerTitle: 'Select Model',
565
- modelSwitchCacheWarning: '⚠ Switching models may rebuild the prompt cache; the next request may be slower or cost more.',
626
+ modelSwitchCacheWarning: '\u26a0 Switching models may rebuild the prompt cache; the next request may be slower or cost more.',
566
627
  modelSearchHint: 'Search models / providers / connections · ↑↓ select · Enter confirm · Esc cancel',
567
628
  searchLabel: 'Search',
568
629
  noMatchingModels: 'No matching models',
630
+ resumeSessionTitle: 'Resume Session',
631
+ sessionScopeCurrent: 'Current',
632
+ sessionScopeAll: 'All',
633
+ sessionSearchHint: 'Search name / id / path / model / connection · Tab scope · ↑↓ select · Enter open · Esc close',
634
+ noMatchingSessions: 'No matching sessions',
569
635
  selectPickerHint: '↑↓ select · Enter confirm · Esc close',
570
636
  providerConfigured: 'configured',
571
637
  addAccount: 'add account',
@@ -652,10 +718,15 @@ export const TUI_COPY_RESOURCES = {
652
718
  },
653
719
  'zh-CN': {
654
720
  modelPickerTitle: '选择模型',
655
- modelSwitchCacheWarning: '⚠ 切换模型可能需要重建提示缓存;下一次请求可能更慢或成本更高。',
721
+ modelSwitchCacheWarning: '\u26a0 切换模型可能需要重建提示缓存;下一次请求可能更慢或成本更高。',
656
722
  modelSearchHint: '搜索模型 / 服务商 / 连接 · ↑↓ 选择 · Enter 确认 · Esc 取消',
657
723
  searchLabel: '搜索',
658
724
  noMatchingModels: '没有匹配的模型',
725
+ resumeSessionTitle: '恢复会话',
726
+ sessionScopeCurrent: '当前目录',
727
+ sessionScopeAll: '全部',
728
+ sessionSearchHint: '搜索名称 / ID / 路径 / 模型 / 连接 · Tab 切换范围 · ↑↓ 选择 · Enter 打开 · Esc 关闭',
729
+ noMatchingSessions: '没有匹配的会话',
659
730
  selectPickerHint: '↑↓ 选择 · Enter 确认 · Esc 关闭',
660
731
  providerConfigured: '已设置',
661
732
  addAccount: '添加账号',
@@ -741,10 +812,15 @@ export const TUI_COPY_RESOURCES = {
741
812
  },
742
813
  'zh-TW': {
743
814
  modelPickerTitle: '選擇模型',
744
- modelSwitchCacheWarning: '⚠ 切換模型可能需要重建提示快取;下一次請求可能較慢或成本較高。',
815
+ modelSwitchCacheWarning: '\u26a0 切換模型可能需要重建提示快取;下一次請求可能較慢或成本較高。',
745
816
  modelSearchHint: '搜尋模型 / 服務商 / 連線 · ↑↓ 選擇 · Enter 確認 · Esc 取消',
746
817
  searchLabel: '搜尋',
747
818
  noMatchingModels: '沒有符合的模型',
819
+ resumeSessionTitle: '恢復會話',
820
+ sessionScopeCurrent: '目前目錄',
821
+ sessionScopeAll: '全部',
822
+ sessionSearchHint: '搜尋名稱 / ID / 路徑 / 模型 / 連線 · Tab 切換範圍 · ↑↓ 選取 · Enter 開啟 · Esc 關閉',
823
+ noMatchingSessions: '沒有符合的會話',
748
824
  selectPickerHint: '↑↓ 選擇 · Enter 確認 · Esc 關閉',
749
825
  providerConfigured: '已設定',
750
826
  addAccount: '新增帳號',
@@ -863,6 +939,7 @@ export const TUI_COPY_RESOURCES = {
863
939
  skill: 'Invoke a skill (or type /skill:<name> inline)',
864
940
  swarm: 'Show, enable, disable, or run one Swarm turn',
865
941
  thinking: 'Set thinking level',
942
+ todo: 'Show the current session Todo list',
866
943
  transcript: 'Browse the full transcript inside Maka',
867
944
  },
868
945
  help: {
@@ -870,7 +947,7 @@ export const TUI_COPY_RESOURCES = {
870
947
  userCommand: ' !<command> — run one shell command visible only to you',
871
948
  keybindingsHeading: 'Keybindings',
872
949
  keybindings: [
873
- ' Ctrl+O — expand or collapse all tool output',
950
+ ' Ctrl+O — open detailed transcript (Ctrl+E toggles details inside)',
874
951
  ' Ctrl+T — expand or collapse all thinking in view',
875
952
  ' Scroll the transcript with your terminal or trackpad',
876
953
  ' Enter (during a turn) — steer: inject a message into the running turn',
@@ -916,6 +993,7 @@ export const TUI_COPY_RESOURCES = {
916
993
  skill: '调用 Skill(也可直接输入 /skill:<name>)',
917
994
  swarm: '查看、启用、停用 Swarm 模式,或执行一次 Swarm 任务',
918
995
  thinking: '设置思考级别',
996
+ todo: '查看当前会话待办清单',
919
997
  transcript: '在 Maka 内浏览完整对话记录',
920
998
  },
921
999
  help: {
@@ -923,7 +1001,7 @@ export const TUI_COPY_RESOURCES = {
923
1001
  userCommand: ' !<command> — 执行一次仅用户可见的 shell 命令',
924
1002
  keybindingsHeading: '快捷键',
925
1003
  keybindings: [
926
- ' Ctrl+O — 展开或折叠所有工具输出',
1004
+ ' Ctrl+O — 打开详细记录(其中 Ctrl+E 切换详情)',
927
1005
  ' Ctrl+T — 展开或折叠视图中的所有思考块',
928
1006
  ' 使用终端或触控板滚动对话记录',
929
1007
  ' Enter(任务运行中)— 将消息注入当前任务',
@@ -969,6 +1047,7 @@ export const TUI_COPY_RESOURCES = {
969
1047
  skill: '呼叫 Skill(也可直接輸入 /skill:<name>)',
970
1048
  swarm: '檢視、啟用、停用 Swarm 模式,或執行一次 Swarm 任務',
971
1049
  thinking: '設定思考級別',
1050
+ todo: '檢視目前會話待辦清單',
972
1051
  transcript: '在 Maka 內瀏覽完整對話記錄',
973
1052
  },
974
1053
  help: {
@@ -976,7 +1055,7 @@ export const TUI_COPY_RESOURCES = {
976
1055
  userCommand: ' !<command> — 執行一次僅使用者可見的 shell 命令',
977
1056
  keybindingsHeading: '快捷鍵',
978
1057
  keybindings: [
979
- ' Ctrl+O — 展開或摺疊所有工具輸出',
1058
+ ' Ctrl+O — 開啟詳細記錄(其中 Ctrl+E 切換詳情)',
980
1059
  ' Ctrl+T — 展開或摺疊檢視中的所有思考區塊',
981
1060
  ' 使用終端機或觸控板捲動對話記錄',
982
1061
  ' Enter(任務執行中)— 將訊息注入目前任務',
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ export function stripUnfocusedCursorStyle(lines, focused) {
20
+ if (focused)
21
+ return lines;
22
+ // pi-tui 0.84.4 paints its cursor even when the editor is unfocused.
23
+ // Remove the reverse-video wrapper, keeping the captured text ($1):
24
+ //
25
+ // Input: \x1b[7mhello\x1b[0m
26
+ // └─────┘└───┘└─────┘
27
+ // reverse text reset
28
+ // remove keep remove
29
+ // $1
30
+ // Output: hello
31
+ //
32
+ // ([^\x1b]*) captures text without ESC, so the match cannot cross another
33
+ // ANSI sequence. Other text using this same wrapper would also lose its
34
+ // reverse styling. Recheck when changing themes or upgrading pi-tui.
35
+ return lines.map((line) => line.replace(/\x1b\[7m([^\x1b]*)\x1b\[0m/gu, '$1'));
36
+ }
@@ -20,7 +20,7 @@ import { createHash, randomUUID } from 'node:crypto';
20
20
  import { MCP_CONFIG_VERSION, mcpConfigChangeRetiresCredentials, resolveMcpProtocolPreference, } from '@maka/core/mcp';
21
21
  import { createCredentialMcpOAuthStorage, McpClientManager } from '@maka/mcp';
22
22
  import { createFileCredentialStore } from '@maka/storage/credential-store';
23
- import { createMcpConfigStore, assertMcpEndpointPolicyOnChanges, McpConfigSourceError, normalizeMcpConfig, normalizeMcpImport, } from '@maka/storage/mcp-config-store';
23
+ import { AtomicFileWriteCommitUnknownError, createMcpConfigStore, assertMcpEndpointPolicyOnChanges, McpConfigSourceError, normalizeMcpConfig, normalizeMcpImport, } from '@maka/storage/mcp-config-store';
24
24
  import { createMcpCapabilityProvider } from './mcp-capability-provider.js';
25
25
  const RUNTIME_HOST_CREDENTIAL_ENV = 'MAKA_RUNTIME_HOST_ACCESS_CREDENTIAL';
26
26
  export function createTuiMcpController(input, overrides = {}) {
@@ -289,10 +289,45 @@ class TuiMcpControllerImpl {
289
289
  catch (error) {
290
290
  if (error instanceof TuiMcpMutationError)
291
291
  return error.result;
292
+ if (error instanceof AtomicFileWriteCommitUnknownError) {
293
+ // The transform has already published, including any credential
294
+ // retirement. Reload its authority; never replay those effects.
295
+ this.#preparedImport = undefined;
296
+ let reconciliationError;
297
+ if (!this.#closed) {
298
+ try {
299
+ ({ reconciliationError } = await this.#synchronizeCommittedConfig(await this.#deps.configStore.get()));
300
+ }
301
+ catch (failure) {
302
+ reconciliationError = failure;
303
+ this.#publicationSuppressed = false;
304
+ this.#snapshot = freezeSnapshot({
305
+ ...this.#snapshot,
306
+ configuration: 'out_of_sync',
307
+ servers: this.#snapshot.servers.map((server) => ({
308
+ ...server,
309
+ synchronized: false,
310
+ })),
311
+ });
312
+ this.#notify();
313
+ }
314
+ }
315
+ // Reconciliation does not establish the missing durability fence,
316
+ // and its own failure must not replace the original write error.
317
+ return {
318
+ status: 'failed',
319
+ reason: 'commit-unknown',
320
+ cause: error,
321
+ ...(reconciliationError === undefined ? {} : { reconciliationError }),
322
+ };
323
+ }
292
324
  return { status: 'failed', reason: 'persist-failed' };
293
325
  }
326
+ return (await this.#synchronizeCommittedConfig(committed)).result;
327
+ }
328
+ async #synchronizeCommittedConfig(committed) {
294
329
  if (this.#closed)
295
- return { status: 'failed', reason: 'closed' };
330
+ return { result: { status: 'failed', reason: 'closed' } };
296
331
  this.#preparedImport = undefined;
297
332
  this.#config = cloneConfig(committed);
298
333
  this.#updateSnapshot({ configuration: 'synchronizing' });
@@ -301,19 +336,19 @@ class TuiMcpControllerImpl {
301
336
  try {
302
337
  await this.#deps.manager.sync(committed);
303
338
  }
304
- catch {
339
+ catch (error) {
305
340
  this.#publicationSuppressed = false;
306
341
  this.#updateSnapshot({ configuration: 'out_of_sync' });
307
342
  this.#refreshManagerSnapshot();
308
343
  await this.#settlePublication();
309
- return { status: 'applied', effect: 'sync_failed' };
344
+ return { result: { status: 'applied', effect: 'sync_failed' }, reconciliationError: error };
310
345
  }
311
346
  this.#publicationSuppressed = false;
312
347
  if (this.#closed)
313
- return { status: 'failed', reason: 'closed' };
348
+ return { result: { status: 'failed', reason: 'closed' } };
314
349
  this.#updateSnapshot({ configuration: 'ready' });
315
350
  this.#refreshManagerSnapshot();
316
- return { status: 'applied', effect: await this.#settlePublication() };
351
+ return { result: { status: 'applied', effect: await this.#settlePublication() } };
317
352
  }
318
353
  #prepareMutation(current, action) {
319
354
  const servers = { ...current.mcpServers };
@@ -65,6 +65,7 @@ export const AGENT_RUN_EVENT_TYPES = [
65
65
  'sandbox_escalation_applied',
66
66
  'sandbox_escalation_failed',
67
67
  'sandbox_denial_detected',
68
+ 'request_composition_resolved',
68
69
  'model_call_attempt_recorded',
69
70
  'history_compact_checkpoint_recorded',
70
71
  'model_projection_transition_recorded',
@@ -251,3 +251,6 @@ export function attachmentKindFromMimeType(mimeType, fileName) {
251
251
  }
252
252
  return 'other';
253
253
  }
254
+ export function attachmentIngestBlocked(code) {
255
+ return new Error(`attachment_ingest:${code}`);
256
+ }
@@ -0,0 +1,27 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ /**
20
+ * Neutral entrypoint for the strict, key-sorted JSON serializer shared by
21
+ * identity and persistence authorities.
22
+ *
23
+ * The implementation remains in `tool-args-identity` so its existing import
24
+ * path and wire behavior stay compatible while other packages use a domain-
25
+ * neutral name.
26
+ */
27
+ export { stableJsonStringify } from './tool-args-identity.js';
@@ -75,6 +75,41 @@ export const CAPABILITY_READINESS_STATES = [
75
75
  'degraded',
76
76
  'paused',
77
77
  ];
78
+ /**
79
+ * Stable machine codes for capability and OS-permission reasons. Producers
80
+ * emit these instead of locale-bound prose; presenters own the code→copy map
81
+ * per locale. Bot capabilities pass their bridge status reasons through as-is
82
+ * (`rate-limited`, `gateway-closed-4004`, …), so signal `reason` fields stay
83
+ * `string` — this union types the desktop producers and the presenter maps.
84
+ */
85
+ export const CAPABILITY_REASON_CODES = [
86
+ 'disabled',
87
+ 'platform_credentials_missing',
88
+ 'macos_tcc_only',
89
+ 'apple_events_tcc_status_unavailable',
90
+ 'cu_artifact_missing',
91
+ 'cu_backend_status',
92
+ 'cu_backend_unavailable',
93
+ 'cu_executor_undistributable',
94
+ 'cu_executor_stopped',
95
+ 'cu_executor_start_failed',
96
+ 'cu_executor_recovering',
97
+ 'cu_executor_ready',
98
+ 'cu_executor_lazy_start',
99
+ 'activity_recorder_partial',
100
+ 'activity_recorder_probe_hint',
101
+ 'memory_partial',
102
+ 'memory_no_probe',
103
+ 'accessibility_status_ambiguous',
104
+ 'screen_recording_status_mac_only',
105
+ 'notifications_status_unreadable_macos',
106
+ 'notifications_status_unreadable',
107
+ 'notifications_unsupported',
108
+ 'permission_probe_failed',
109
+ ];
110
+ export function isCapabilityReasonCode(value) {
111
+ return (typeof value === 'string' && CAPABILITY_REASON_CODES.includes(value));
112
+ }
78
113
  export function isOsPermissionState(value) {
79
114
  return typeof value === 'string' && OS_PERMISSION_STATES.includes(value);
80
115
  }
@@ -81,7 +81,6 @@ export function isComputerUseErrorCode(value) {
81
81
  return (typeof value === 'string' && COMPUTER_USE_ERROR_CODES.includes(value));
82
82
  }
83
83
  export const CU_ACTION_TYPES = ['screenshot', 'type', 'key', 'wait'];
84
- export const COMPUTER_USE_ACTION_TYPES = CU_ACTION_TYPES;
85
84
  /**
86
85
  * The semantic actions, which name an element rather than a pixel. They are not
87
86
  * in `CU_ACTION_TYPES` because they are not `CuAction`s — they are dispatched
@@ -28,6 +28,24 @@ const PATTERNS = [
28
28
  streamingTerminator: /[\s"'<>]/,
29
29
  streamingValueGroup: 3,
30
30
  },
31
+ // URL userinfo: https://user:pass@host / https://token@host
32
+ // Structural — any authority that contains `@` is credential-bearing, so
33
+ // this does not depend on a provider prefix list. Runs before the query
34
+ // rule so only the userinfo is replaced and host/path survive.
35
+ // Character class matches streamingTerminator so a bare `https://host`
36
+ // cannot swallow later `@` across whitespace/quotes/angle brackets.
37
+ // Known boundary: punctuation like commas can still join a bare URL to a
38
+ // later `@`; a proper fix would restrict userinfo to the RFC 3986 set.
39
+ // http(s) only for now. Streaming cannot recognize userinfo before `@`
40
+ // arrives (`https://user:pa` stays clear until then); tightening earlier
41
+ // would eat `https://host:8080/`.
42
+ {
43
+ label: 'url userinfo',
44
+ regex: /(https?:\/\/)([^\s"'<>/?#]*@)/gi,
45
+ replacement: (m) => `${m[1]}<redacted>@`,
46
+ streamingTerminator: /[/?#\s"'<>]/,
47
+ streamingValueGroup: 2,
48
+ },
31
49
  // URL query secrets: ?key=[redacted] ?token=[redacted] ?api_key=[redacted] &access_token=[redacted]
32
50
  // (runs before the api-key-header rule so the URL form isn't mangled.)
33
51
  {
@@ -228,6 +228,19 @@ export function reconcileConnectionAfterModelFetch(connection, models, options)
228
228
  const previousEnabled = [
229
229
  ...new Set(connectionEnabledModelIds(connection).map((id) => supersededModelId(id, live, options?.aliases))),
230
230
  ];
231
+ if (options?.authoritative) {
232
+ // The first account-scoped fetch replaces the provider fallback guess: no
233
+ // user chose those bootstrap ids, and every usable model should be offered.
234
+ // Later refreshes preserve explicit user choices only while they remain in
235
+ // the account catalog; newly introduced models stay opt-in.
236
+ const enabledModelIds = connection.hasModelInventory
237
+ ? previousEnabled.filter((id) => live.has(id))
238
+ : liveIds;
239
+ const defaultModel = enabledModelIds.includes(previousDefault)
240
+ ? previousDefault
241
+ : (enabledModelIds[0] ?? '');
242
+ return { defaultModel, enabledModelIds };
243
+ }
231
244
  // Seed a first choice only for a connection that has never had a list to
232
245
  // pick from: four providers ship no `fallbackModels`, so for them discovery
233
246
  // is the only place a first default can come from.
@@ -83,6 +83,7 @@ const MODEL_CALL_ATTEMPT_SHAPE = defineObjectShape()([
83
83
  ], [
84
84
  'connectionSlug',
85
85
  'historyCompactRoute',
86
+ 'requestCompositionId',
86
87
  'contextWindow',
87
88
  'captureArtifactId',
88
89
  'promptComposition',
@@ -294,6 +295,7 @@ export function decodeModelCallAttempt(value) {
294
295
  isNonEmptyString(value.runId) &&
295
296
  isNonEmptyString(value.turnId) &&
296
297
  isNonNegativeInteger(value.step) &&
298
+ isOptionalString(value.requestCompositionId) &&
297
299
  isNonNegativeInteger(value.attempt) &&
298
300
  MODEL_CALL_KINDS.includes(value.callKind) &&
299
301
  (value.historyCompactRoute === undefined ||
@@ -48,6 +48,10 @@ const GENERATED_METADATA_PROVIDER_ALIASES = {
48
48
  function generatedMetadataProviderType(providerType) {
49
49
  return GENERATED_METADATA_PROVIDER_ALIASES[providerType] ?? providerType;
50
50
  }
51
+ /** Whether discovery is the complete usable model catalog for this account. */
52
+ export function providerReportsCompleteModelCatalog(providerType) {
53
+ return providerType === 'github-copilot';
54
+ }
51
55
  /**
52
56
  * Whether the active metadata describes this model at all. `lookupModelMetadata`
53
57
  * answers "no" with an empty object, and callers were reading that sentinel by
@@ -53,6 +53,7 @@ export function redactSecrets(value) {
53
53
  }
54
54
  function redactTextSecrets(value) {
55
55
  let next = value;
56
+ next = redactUrlUserinfoSecrets(next);
56
57
  next = redactUrlQuerySecrets(next);
57
58
  next = next.replace(QUOTED_SECRET_KEY_VALUE_PATTERN, (match, prefix, key) => isSensitiveKey(key) ? `${prefix}[redacted]` : match);
58
59
  next = next.replace(AUTHORIZATION_HEADER_PATTERN, (_match, boundary, prefix) => `${boundary}${prefix}[redacted]`);
@@ -134,6 +135,17 @@ function redactJsonValue(value) {
134
135
  }
135
136
  return { value: next, changed };
136
137
  }
138
+ function redactUrlUserinfoSecrets(value) {
139
+ // Authority runs through the first `/`, `?`, `#`, whitespace, quote, or
140
+ // angle bracket. If it contains `@`, everything from the host-start through
141
+ // the last `@` is userinfo. The class matches display-redaction's
142
+ // streamingTerminator so a bare `https://host` followed later by an
143
+ // email/`@package` (including across JSON quotes) cannot swallow the gap.
144
+ // Known boundary: punctuation like commas can still join a bare URL to a
145
+ // later `@` into one fake credentialed match; a proper fix would restrict
146
+ // userinfo to the RFC 3986 set instead of exclusion. http(s) only for now.
147
+ return value.replace(/(https?:\/\/)[^\s"'<>/?#]*@/gi, '$1[redacted]@');
148
+ }
137
149
  function redactUrlQuerySecrets(value) {
138
150
  return value.replace(/([?&])([^=\s&?#]+)=([^&\s#]*)/g, (match, sep, key) => {
139
151
  if (!isSensitiveKey(key))
@@ -191,6 +203,10 @@ export function classifyGeneralizedError(error) {
191
203
  return 'provider_error';
192
204
  if (lower.includes('network') ||
193
205
  lower.includes('fetch') ||
206
+ // Chromium network stack error codes (`net::ERR_CONNECTION_RESET`,
207
+ // `net::ERR_NAME_NOT_RESOLVED`, ...) never match the Node errno
208
+ // spellings below.
209
+ lower.includes('net::err') ||
194
210
  lower.includes('econn') ||
195
211
  lower.includes('enotfound'))
196
212
  return 'network_error';
@@ -231,3 +247,22 @@ export function generalizedErrorMessage(error, fallback = 'Operation failed') {
231
247
  export function isAuthenticationErrorText(message) {
232
248
  return message.replace(/\bauthorit\w*/g, '').includes('auth');
233
249
  }
250
+ const reportedFailures = new WeakSet();
251
+ /** Redacted diagnostics channel for unexpected operation failures. Copy
252
+ * catalogs live here (bare-importable) because a depended-on copy catalog may
253
+ * only hold bare package runtime imports. */
254
+ export function reportUnexpectedOperation(scope, error) {
255
+ // One failure, one diagnostic: a rejection formatted again by an outer layer
256
+ // is the same defect, not a second one.
257
+ if (typeof error === 'object' && error !== null) {
258
+ if (reportedFailures.has(error))
259
+ return;
260
+ reportedFailures.add(error);
261
+ }
262
+ const detail = error instanceof Error ? (error.stack ?? `${error.name}: ${error.message}`) : String(error);
263
+ console.error(`[${scope}] operation failed:`, redactSecrets(detail));
264
+ }
265
+ export function unexpectedOperationFallback(error, fallback, scope) {
266
+ reportUnexpectedOperation(scope, error);
267
+ return fallback;
268
+ }
@@ -18,6 +18,12 @@
18
18
  */
19
19
  import { defineObjectShape, hasExactShape, isRecord } from './record-schema.js';
20
20
  export const RUN_COMPOSITION_SCHEMA_VERSION = 1;
21
+ export const REQUEST_COMPOSITION_SCHEMA_VERSION = 1;
22
+ // Composition evidence is exact: reject an over-bound provider surface rather
23
+ // than silently truncating the resolved snapshot.
24
+ export const COMPOSITION_MAX_TOOLS = 256;
25
+ export const COMPOSITION_MAX_TOOL_NAME_LENGTH = 128;
26
+ export const REQUEST_COMPOSITION_MAX_TOOL_DESCRIPTION_LENGTH = 16_384;
21
27
  const RUN_COMPOSITION_SHAPE = defineObjectShape()([
22
28
  'schemaVersion',
23
29
  'composerId',
@@ -30,6 +36,20 @@ const RUN_COMPOSITION_SHAPE = defineObjectShape()([
30
36
  'toolNames',
31
37
  'contextWindow',
32
38
  ], []);
39
+ const REQUEST_COMPOSITION_SHAPE = defineObjectShape()([
40
+ 'schemaVersion',
41
+ 'compositionId',
42
+ 'step',
43
+ 'reason',
44
+ 'sourceRevisions',
45
+ 'systemPromptHash',
46
+ 'toolCatalogHash',
47
+ 'toolAvailabilityHash',
48
+ 'providerOptionsHash',
49
+ 'toolNames',
50
+ 'toolSchemas',
51
+ ], []);
52
+ const REQUEST_COMPOSITION_TOOL_SCHEMA_SHAPE = defineObjectShape()(['name', 'description', 'inputSchema'], ['providerTool']);
33
53
  const ID_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
34
54
  const HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
35
55
  const SOURCE_REVISION_SHAPE = defineObjectShape()(['id', 'revision'], []);
@@ -64,6 +84,41 @@ export function createRunCompositionSnapshot(input) {
64
84
  toolNames: [...input.toolNames].sort(compareExactString),
65
85
  });
66
86
  }
87
+ export function createRequestCompositionSnapshot(input, reason) {
88
+ return decodeRequestCompositionSnapshot({
89
+ schemaVersion: REQUEST_COMPOSITION_SCHEMA_VERSION,
90
+ ...input,
91
+ reason,
92
+ sourceRevisions: [...input.sourceRevisions].sort((left, right) => compareExactString(left.id, right.id)),
93
+ toolNames: [...input.toolNames].sort(compareExactString),
94
+ toolSchemas: [...input.toolSchemas].sort((left, right) => compareExactString(left.name, right.name)),
95
+ });
96
+ }
97
+ export function decodeRequestCompositionSnapshot(value) {
98
+ if (!isRecord(value) || !hasExactShape(value, REQUEST_COMPOSITION_SHAPE)) {
99
+ throw new Error('Invalid Request Composition snapshot schema');
100
+ }
101
+ const valid = value.schemaVersion === REQUEST_COMPOSITION_SCHEMA_VERSION &&
102
+ boundedString(value.compositionId, 128) &&
103
+ Number.isSafeInteger(value.step) &&
104
+ value.step >= 0 &&
105
+ (value.reason === 'initial' || value.reason === 'change') &&
106
+ canonicalSourceRevisions(value.sourceRevisions) &&
107
+ hash(value.systemPromptHash) &&
108
+ hash(value.toolCatalogHash) &&
109
+ hash(value.toolAvailabilityHash) &&
110
+ hash(value.providerOptionsHash) &&
111
+ canonicalToolNames(value.toolNames) &&
112
+ canonicalToolSchemas(value.toolSchemas);
113
+ if (!valid)
114
+ throw new Error('Invalid Request Composition snapshot schema');
115
+ return Object.freeze({
116
+ ...value,
117
+ sourceRevisions: Object.freeze(value.sourceRevisions.map((source) => Object.freeze({ ...source }))),
118
+ toolNames: Object.freeze([...value.toolNames]),
119
+ toolSchemas: Object.freeze(value.toolSchemas.map((schema) => Object.freeze(structuredClone(schema)))),
120
+ });
121
+ }
67
122
  function compareExactString(left, right) {
68
123
  return left < right ? -1 : left > right ? 1 : 0;
69
124
  }
@@ -84,16 +139,36 @@ function canonicalSourceRevisions(value) {
84
139
  return true;
85
140
  }
86
141
  function canonicalToolNames(value) {
87
- if (!Array.isArray(value) || value.length > 256)
142
+ if (!Array.isArray(value) || value.length > COMPOSITION_MAX_TOOLS)
88
143
  return false;
89
144
  let previous;
90
145
  for (const name of value) {
91
- if (!boundedString(name, 128) || (previous !== undefined && previous >= name))
146
+ if (!boundedString(name, COMPOSITION_MAX_TOOL_NAME_LENGTH) ||
147
+ (previous !== undefined && previous >= name)) {
92
148
  return false;
149
+ }
93
150
  previous = name;
94
151
  }
95
152
  return true;
96
153
  }
154
+ function canonicalToolSchemas(value) {
155
+ if (!Array.isArray(value) || value.length > COMPOSITION_MAX_TOOLS)
156
+ return false;
157
+ let previous;
158
+ for (const schema of value) {
159
+ if (!isRecord(schema) ||
160
+ !hasExactShape(schema, REQUEST_COMPOSITION_TOOL_SCHEMA_SHAPE) ||
161
+ !boundedString(schema.name, COMPOSITION_MAX_TOOL_NAME_LENGTH) ||
162
+ !boundedString(schema.description, REQUEST_COMPOSITION_MAX_TOOL_DESCRIPTION_LENGTH) ||
163
+ !isRecord(schema.inputSchema) ||
164
+ (schema.providerTool !== undefined && !isRecord(schema.providerTool)) ||
165
+ (previous !== undefined && previous >= schema.name)) {
166
+ return false;
167
+ }
168
+ previous = schema.name;
169
+ }
170
+ return true;
171
+ }
97
172
  function hash(value) {
98
173
  return typeof value === 'string' && HASH_PATTERN.test(value);
99
174
  }