cli-jaw 2.17.46 → 2.17.47

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 (169) hide show
  1. package/README.md +4 -0
  2. package/dist/bin/commands/browser-web-ai.js +29 -10
  3. package/dist/bin/commands/browser-web-ai.js.map +1 -1
  4. package/dist/server.js +2 -2
  5. package/dist/server.js.map +1 -1
  6. package/dist/src/agent/claude-runtime-run.js +2 -1
  7. package/dist/src/agent/claude-runtime-run.js.map +1 -1
  8. package/dist/src/agent/lifecycle-handler.js +15 -4
  9. package/dist/src/agent/lifecycle-handler.js.map +1 -1
  10. package/dist/src/agent/pi-runtime.js +103 -86
  11. package/dist/src/agent/pi-runtime.js.map +1 -1
  12. package/dist/src/agent/spawn/exit-settle.js +74 -0
  13. package/dist/src/agent/spawn/exit-settle.js.map +1 -0
  14. package/dist/src/agent/spawn/kill-reason.js +38 -0
  15. package/dist/src/agent/spawn/kill-reason.js.map +1 -0
  16. package/dist/src/agent/spawn/queue.js +8 -0
  17. package/dist/src/agent/spawn/queue.js.map +1 -1
  18. package/dist/src/agent/spawn/steer-event.js +33 -0
  19. package/dist/src/agent/spawn/steer-event.js.map +1 -0
  20. package/dist/src/agent/spawn.js +157 -173
  21. package/dist/src/agent/spawn.js.map +1 -1
  22. package/dist/src/agent/watchdog.js +9 -4
  23. package/dist/src/agent/watchdog.js.map +1 -1
  24. package/dist/src/browser/adaptive-fetch/browser-escalation.js +18 -1
  25. package/dist/src/browser/adaptive-fetch/browser-escalation.js.map +1 -1
  26. package/dist/src/browser/adaptive-fetch/browser-flow.js +36 -9
  27. package/dist/src/browser/adaptive-fetch/browser-flow.js.map +1 -1
  28. package/dist/src/browser/adaptive-fetch/browser-session.js +10 -1
  29. package/dist/src/browser/adaptive-fetch/browser-session.js.map +1 -1
  30. package/dist/src/browser/adaptive-fetch/camoufox-session.js +16 -1
  31. package/dist/src/browser/adaptive-fetch/camoufox-session.js.map +1 -1
  32. package/dist/src/browser/adaptive-fetch/defuddle-provenance.js +49 -0
  33. package/dist/src/browser/adaptive-fetch/defuddle-provenance.js.map +1 -0
  34. package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js +61 -31
  35. package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js.map +1 -1
  36. package/dist/src/browser/adaptive-fetch/fetcher.js +12 -1
  37. package/dist/src/browser/adaptive-fetch/fetcher.js.map +1 -1
  38. package/dist/src/browser/adaptive-fetch/live-smoke-manifest.js +12 -18
  39. package/dist/src/browser/adaptive-fetch/live-smoke-manifest.js.map +1 -1
  40. package/dist/src/browser/adaptive-fetch/public-endpoint-normalizers.js +85 -1
  41. package/dist/src/browser/adaptive-fetch/public-endpoint-normalizers.js.map +1 -1
  42. package/dist/src/browser/adaptive-fetch/safety.js +5 -3
  43. package/dist/src/browser/adaptive-fetch/safety.js.map +1 -1
  44. package/dist/src/browser/adaptive-fetch/scheduler.js +34 -5
  45. package/dist/src/browser/adaptive-fetch/scheduler.js.map +1 -1
  46. package/dist/src/browser/adaptive-fetch/tls-fetch.js +54 -33
  47. package/dist/src/browser/adaptive-fetch/tls-fetch.js.map +1 -1
  48. package/dist/src/browser/adaptive-fetch/vendor/README.md +33 -1
  49. package/dist/src/browser/adaptive-fetch/ytdlp-reader.js +54 -9
  50. package/dist/src/browser/adaptive-fetch/ytdlp-reader.js.map +1 -1
  51. package/dist/src/browser/web-ai/capability-observation-presets.js +12 -10
  52. package/dist/src/browser/web-ai/capability-observation-presets.js.map +1 -1
  53. package/dist/src/browser/web-ai/chatgpt-model.js +80 -7
  54. package/dist/src/browser/web-ai/chatgpt-model.js.map +1 -1
  55. package/dist/src/browser/web-ai/gemini-model.js +18 -0
  56. package/dist/src/browser/web-ai/gemini-model.js.map +1 -1
  57. package/dist/src/browser/web-ai/grok-model.js +64 -15
  58. package/dist/src/browser/web-ai/grok-model.js.map +1 -1
  59. package/dist/src/code-mode/providers/codex-app.js +83 -0
  60. package/dist/src/code-mode/providers/codex-app.js.map +1 -1
  61. package/dist/src/core/config.js +77 -73
  62. package/dist/src/core/config.js.map +1 -1
  63. package/dist/src/core/db.js +227 -151
  64. package/dist/src/core/db.js.map +1 -1
  65. package/dist/src/core/settings-merge.js +78 -102
  66. package/dist/src/core/settings-merge.js.map +1 -1
  67. package/dist/src/discord/bot.js +37 -54
  68. package/dist/src/discord/bot.js.map +1 -1
  69. package/dist/src/discord/discord-file.js +29 -3
  70. package/dist/src/discord/discord-file.js.map +1 -1
  71. package/dist/src/discord/send-only-client.js +56 -8
  72. package/dist/src/discord/send-only-client.js.map +1 -1
  73. package/dist/src/manager/routes/telegram-hub.js +8 -1
  74. package/dist/src/manager/routes/telegram-hub.js.map +1 -1
  75. package/dist/src/manager/telegram-hub/hub-bot.js +7 -1
  76. package/dist/src/manager/telegram-hub/hub-bot.js.map +1 -1
  77. package/dist/src/messaging/delivery-outcome.js +30 -0
  78. package/dist/src/messaging/delivery-outcome.js.map +1 -1
  79. package/dist/src/messaging/file-receipt.js +49 -0
  80. package/dist/src/messaging/file-receipt.js.map +1 -0
  81. package/dist/src/messaging/native-body.js +31 -0
  82. package/dist/src/messaging/native-body.js.map +1 -0
  83. package/dist/src/messaging/queue-notice-record.js +48 -0
  84. package/dist/src/messaging/queue-notice-record.js.map +1 -0
  85. package/dist/src/messaging/send.js +13 -2
  86. package/dist/src/messaging/send.js.map +1 -1
  87. package/dist/src/messaging/target-reply-guard.js +66 -0
  88. package/dist/src/messaging/target-reply-guard.js.map +1 -0
  89. package/dist/src/orchestrator/collect.js +18 -3
  90. package/dist/src/orchestrator/collect.js.map +1 -1
  91. package/dist/src/orchestrator/gateway.js +3 -1
  92. package/dist/src/orchestrator/gateway.js.map +1 -1
  93. package/dist/src/orchestrator/pipeline.js +7 -1
  94. package/dist/src/orchestrator/pipeline.js.map +1 -1
  95. package/dist/src/orchestrator/session-lanes.js +2 -2
  96. package/dist/src/orchestrator/session-lanes.js.map +1 -1
  97. package/dist/src/prompt/builder.js +2 -2
  98. package/dist/src/prompt/builder.js.map +1 -1
  99. package/dist/src/routes/i18n.js +9 -4
  100. package/dist/src/routes/i18n.js.map +1 -1
  101. package/dist/src/routes/instance.js +8 -4
  102. package/dist/src/routes/instance.js.map +1 -1
  103. package/dist/src/routes/jaw-memory.js +5 -5
  104. package/dist/src/routes/jaw-memory.js.map +1 -1
  105. package/dist/src/routes/link-preview.js +5 -1
  106. package/dist/src/routes/link-preview.js.map +1 -1
  107. package/dist/src/routes/memory.js +9 -7
  108. package/dist/src/routes/memory.js.map +1 -1
  109. package/dist/src/routes/messaging.js +21 -10
  110. package/dist/src/routes/messaging.js.map +1 -1
  111. package/dist/src/routes/orchestrate.js +127 -118
  112. package/dist/src/routes/orchestrate.js.map +1 -1
  113. package/dist/src/routes/settings.js +7 -7
  114. package/dist/src/routes/settings.js.map +1 -1
  115. package/dist/src/routes/system.js +9 -4
  116. package/dist/src/routes/system.js.map +1 -1
  117. package/dist/src/slack/bot.js +199 -132
  118. package/dist/src/slack/bot.js.map +1 -1
  119. package/dist/src/slack/events.js +31 -8
  120. package/dist/src/slack/events.js.map +1 -1
  121. package/dist/src/slack/ingress.js +3 -1
  122. package/dist/src/slack/ingress.js.map +1 -1
  123. package/dist/src/slack/progress-activity.js +24 -3
  124. package/dist/src/slack/progress-activity.js.map +1 -1
  125. package/dist/src/slack/progress-lifecycle.js +29 -0
  126. package/dist/src/slack/progress-lifecycle.js.map +1 -1
  127. package/dist/src/slack/progress.js +1 -1
  128. package/dist/src/slack/progress.js.map +1 -1
  129. package/dist/src/slack/send-only-client.js +16 -3
  130. package/dist/src/slack/send-only-client.js.map +1 -1
  131. package/dist/src/slack/slack-file.js +5 -3
  132. package/dist/src/slack/slack-file.js.map +1 -1
  133. package/dist/src/slack/workflow.js +134 -0
  134. package/dist/src/slack/workflow.js.map +1 -0
  135. package/dist/src/telegram/bot.js +60 -55
  136. package/dist/src/telegram/bot.js.map +1 -1
  137. package/dist/src/telegram/rich-message.js +28 -20
  138. package/dist/src/telegram/rich-message.js.map +1 -1
  139. package/dist/src/telegram/telegram-file.js +45 -9
  140. package/dist/src/telegram/telegram-file.js.map +1 -1
  141. package/dist/src/trace/activity-journal.js +3 -6
  142. package/dist/src/trace/activity-journal.js.map +1 -1
  143. package/dist/src/trace/activity-retention.js +29 -0
  144. package/dist/src/trace/activity-retention.js.map +1 -1
  145. package/package.json +1 -1
  146. package/public/dist/.vite/manifest.json +20 -20
  147. package/public/dist/assets/{ChannelEnablementControl-DwUDKjX9.js → ChannelEnablementControl-egwTtO9-.js} +8 -8
  148. package/public/dist/assets/{ChannelSetupEntry-DH0AuNNX.js → ChannelSetupEntry-CCrUYd2-.js} +1 -1
  149. package/public/dist/assets/{ChannelsDiscord-7NmoTjR7.js → ChannelsDiscord-B6rJAsQG.js} +1 -1
  150. package/public/dist/assets/{ChannelsSlack-Ba0ax6iT.js → ChannelsSlack-CrSIw4Wd.js} +1 -1
  151. package/public/dist/assets/{ChannelsTelegram-DKA9atf2.js → ChannelsTelegram-DPrdLEnT.js} +1 -1
  152. package/public/dist/assets/CodeCanvas-D5jp8-t5.js +2 -0
  153. package/public/dist/assets/{manager-iX7KosWI.js → manager-CFwOAoe0.js} +2 -2
  154. package/public/dist/assets/{settings-embedding-DIqST-91.js → settings-embedding-DspkJ6sr.js} +1 -1
  155. package/public/dist/assets/{settings-KIPNUOIV.js → settings-qkC0NuQ7.js} +1 -1
  156. package/public/dist/manager/index.html +1 -1
  157. package/public/dist/settings/index.html +1 -1
  158. package/public/locales/en.json +9 -3
  159. package/public/locales/ja.json +9 -3
  160. package/public/locales/ko.json +9 -3
  161. package/public/locales/zh.json +9 -3
  162. package/public/manager/src/code/CodeWorkbench.tsx +11 -4
  163. package/public/manager/src/code/code-controller-draft-storage.ts +7 -3
  164. package/public/manager/src/code/code-controller-drafts.ts +9 -2
  165. package/public/manager/src/code/code-controller-runtime.ts +125 -12
  166. package/public/manager/src/code/code-controller-types.ts +2 -0
  167. package/public/manager/src/code/pending-user-item.ts +5 -0
  168. package/scripts/ci/windows-unit-manifest.txt +6 -0
  169. package/public/dist/assets/CodeCanvas-DWsx6sv5.js +0 -2
@@ -6,7 +6,7 @@
6
6
  <link rel="icon" type="image/png" href="/dist/assets/icon-192-CwXPIYF5.png">
7
7
  <title>Jaw Manager</title>
8
8
  <script src="/manager/theme-boot.js"></script>
9
- <script type="module" crossorigin src="/dist/assets/manager-iX7KosWI.js"></script>
9
+ <script type="module" crossorigin src="/dist/assets/manager-CFwOAoe0.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/dist/assets/vendor-render-DSSkXCir.css">
11
11
  <link rel="stylesheet" crossorigin href="/dist/assets/settings-embedding-LIMq5ebM.css">
12
12
  <link rel="stylesheet" crossorigin href="/dist/assets/manager-Nk7-RYPs.css">
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Settings</title>
7
7
  <script src="/manager/theme-boot.js"></script>
8
- <script type="module" crossorigin src="/dist/assets/settings-KIPNUOIV.js"></script>
8
+ <script type="module" crossorigin src="/dist/assets/settings-qkC0NuQ7.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/dist/assets/settings-embedding-LIMq5ebM.css">
10
10
  </head>
11
11
  <body>
@@ -195,7 +195,8 @@
195
195
  "stt.openaiModel": "Model",
196
196
  "stt.vertexConfig": "Vertex AI Config (JSON)",
197
197
  "tg.timeout": "Timeout (20 min no response)",
198
- "tg.noResponse": "No response",
198
+ "tg.noResponse": "No response",
199
+ "tg.stopped": "Stopped before the run finished",
199
200
  "tg.connected": "Jaw Agent connected! Send a message and the AI agent will respond.",
200
201
  "tg.resetDone": "Reset complete.",
201
202
  "cli.fileNotFound": "File not found: {path}",
@@ -947,6 +948,8 @@
947
948
  "slack.progress.waiting": "Waiting for the next update",
948
949
  "slack.progress.delivering": "Delivering the answer",
949
950
  "slack.progress.complete": "Work observation ended",
951
+ "slack.progress.workflowComplete": "Workflow response ended; production outcome is not verified.",
952
+ "slack.progress.unavailable": "Activity updates unavailable. Execution may continue.",
950
953
  "slack.progress.error": "Request failed",
951
954
  "slack.progress.cancelled": "Request cancelled",
952
955
  "slack.progress.expired": "Progress tracking ended. Execution may continue.",
@@ -955,7 +958,8 @@
955
958
  "slack.progress.restored": "Previous progress tracking ended. The execution result is unconfirmed.",
956
959
  "slack.progress.failure": "Could not complete the response.",
957
960
  "slack.progress.elapsed": "Elapsed: {seconds}s",
958
- "slack.progress.lastActivity": "Last activity: {seconds}s ago",
961
+ "slack.progress.lastActivity": "Last activity: {seconds}s ago",
962
+ "slack.progress.longRunning": "Running for {minutes} min — still in progress",
959
963
  "slack.progress.category.read": "File reading",
960
964
  "slack.progress.category.write": "File editing",
961
965
  "slack.progress.category.search": "Search",
@@ -971,5 +975,7 @@
971
975
  "slack.progress.status.unconfirmed": "Unconfirmed",
972
976
  "slack.progress.deliveryComplete": "Answer delivered",
973
977
  "slack.progress.deliveryFailed": "Answer delivery could not be confirmed",
974
- "slack.progress.deliveryUnconfirmed": "Answer delivery unconfirmed"
978
+ "slack.progress.deliveryUnconfirmed": "Answer delivery unconfirmed",
979
+ "slack.workflow.unavailable": "The automation was not run because its sender/request information or the designated enabled skill could not be verified. Check the configuration.",
980
+ "slack.workflow.unconfirmed": "The registered workflow returned no substantive result. Work may already have begun; inspect its records before retrying."
975
981
  }
@@ -193,7 +193,8 @@
193
193
  "stt.openaiModel": "モデル",
194
194
  "stt.vertexConfig": "Vertex AI 設定(JSON)",
195
195
  "tg.timeout": "タイムアウト(20 分間応答なし)",
196
- "tg.noResponse": "応答なし",
196
+ "tg.noResponse": "応答なし",
197
+ "tg.stopped": "実行が終わる前に停止しました",
197
198
  "tg.connected": "Jaw Agent に接続しました。メッセージを送ると AI Agent が応答します。",
198
199
  "tg.resetDone": "リセット完了。",
199
200
  "cli.fileNotFound": "ファイルが見つかりません: {path}",
@@ -947,6 +948,8 @@
947
948
  "slack.progress.waiting": "次の更新を待機中",
948
949
  "slack.progress.delivering": "回答を送信中",
949
950
  "slack.progress.complete": "作業の観測終了",
951
+ "slack.progress.workflowComplete": "ワークフローの応答が終了しました。実際の制作結果は別途確認が必要です。",
952
+ "slack.progress.unavailable": "活動の更新を表示できません。実行は継続している可能性があります。",
950
953
  "slack.progress.error": "リクエスト失敗",
951
954
  "slack.progress.cancelled": "リクエスト取消済み",
952
955
  "slack.progress.expired": "進捗表示が終了しました。実行は続いている可能性があります。",
@@ -955,7 +958,8 @@
955
958
  "slack.progress.restored": "以前の進捗表示が終了しました。実行結果は確認されていません。",
956
959
  "slack.progress.failure": "応答を完了できませんでした。",
957
960
  "slack.progress.elapsed": "経過: {seconds}秒",
958
- "slack.progress.lastActivity": "最後の活動: {seconds}秒前",
961
+ "slack.progress.lastActivity": "最後の活動: {seconds}秒前",
962
+ "slack.progress.longRunning": "{minutes}分間実行中 — まだ進行中です",
959
963
  "slack.progress.category.read": "ファイル読取",
960
964
  "slack.progress.category.write": "ファイル編集",
961
965
  "slack.progress.category.search": "検索",
@@ -971,5 +975,7 @@
971
975
  "slack.progress.status.unconfirmed": "完了未確認",
972
976
  "slack.progress.deliveryComplete": "回答を送信しました",
973
977
  "slack.progress.deliveryFailed": "回答の送信を確認できませんでした",
974
- "slack.progress.deliveryUnconfirmed": "回答の送信は未確認"
978
+ "slack.progress.deliveryUnconfirmed": "回答の送信は未確認",
979
+ "slack.workflow.unavailable": "自動化の送信元・リクエスト情報、または指定された有効なスキルを確認できなかったため、実行しませんでした。設定を確認してください。",
980
+ "slack.workflow.unconfirmed": "登録されたワークフローから実質的な結果が返されませんでした。作業がすでに始まっている可能性があるため、再試行する前に記録を確認してください。"
975
981
  }
@@ -195,7 +195,8 @@
195
195
  "stt.openaiModel": "모델",
196
196
  "stt.vertexConfig": "Vertex AI 설정 (JSON)",
197
197
  "tg.timeout": "시간 초과 (20분 무응답)",
198
- "tg.noResponse": "응답 없음",
198
+ "tg.noResponse": "응답 없음",
199
+ "tg.stopped": "중단됨 — 실행이 끝나기 전에 멈췄습니다",
199
200
  "tg.connected": "Jaw Agent 연결됨! 메시지를 보내면 AI 에이전트가 응답합니다.",
200
201
  "tg.resetDone": "리셋 완료.",
201
202
  "cli.fileNotFound": "파일 없음: {path}",
@@ -947,6 +948,8 @@
947
948
  "slack.progress.waiting": "다음 업데이트를 기다리는 중",
948
949
  "slack.progress.delivering": "답변 전달 중",
949
950
  "slack.progress.complete": "작업 관찰 종료",
951
+ "slack.progress.workflowComplete": "워크플로 응답이 끝났습니다. 실제 제작 결과는 별도 확인이 필요합니다.",
952
+ "slack.progress.unavailable": "활동 내역을 표시할 수 없습니다. 실행은 계속될 수 있습니다.",
950
953
  "slack.progress.error": "요청 실패",
951
954
  "slack.progress.cancelled": "요청 취소됨",
952
955
  "slack.progress.expired": "진행 표시가 종료되었습니다. 실행은 계속될 수 있습니다.",
@@ -955,7 +958,8 @@
955
958
  "slack.progress.restored": "이전 진행 표시가 종료되었습니다. 실행 결과는 확인되지 않았습니다.",
956
959
  "slack.progress.failure": "응답을 완료하지 못했습니다.",
957
960
  "slack.progress.elapsed": "경과: {seconds}초",
958
- "slack.progress.lastActivity": "마지막 활동: {seconds}초 전",
961
+ "slack.progress.lastActivity": "마지막 활동: {seconds}초 전",
962
+ "slack.progress.longRunning": "{minutes}분째 실행 중 — 아직 진행 중입니다",
959
963
  "slack.progress.category.read": "파일 읽기",
960
964
  "slack.progress.category.write": "파일 편집",
961
965
  "slack.progress.category.search": "검색",
@@ -971,5 +975,7 @@
971
975
  "slack.progress.status.unconfirmed": "완료 미확인",
972
976
  "slack.progress.deliveryComplete": "답변 전달 완료",
973
977
  "slack.progress.deliveryFailed": "답변 전달을 확인하지 못했습니다",
974
- "slack.progress.deliveryUnconfirmed": "답변 전달 여부 미확인"
978
+ "slack.progress.deliveryUnconfirmed": "답변 전달 여부 미확인",
979
+ "slack.workflow.unavailable": "자동화의 발신·요청 정보나 지정된 활성 스킬을 확인할 수 없어 실행하지 않았습니다. 설정을 확인하세요.",
980
+ "slack.workflow.unconfirmed": "등록된 워크플로가 실질적인 결과를 반환하지 않았습니다. 이미 작업이 시작되었을 수 있으니 재시도 전에 기록을 확인하세요."
975
981
  }
@@ -193,7 +193,8 @@
193
193
  "stt.openaiModel": "模型",
194
194
  "stt.vertexConfig": "Vertex AI 配置(JSON)",
195
195
  "tg.timeout": "超时(20 分钟无响应)",
196
- "tg.noResponse": "无响应",
196
+ "tg.noResponse": "无响应",
197
+ "tg.stopped": "运行结束前已停止",
197
198
  "tg.connected": "Jaw Agent 已连接!发送消息后,AI Agent 会回复。",
198
199
  "tg.resetDone": "重置完成。",
199
200
  "cli.fileNotFound": "找不到文件:{path}",
@@ -947,6 +948,8 @@
947
948
  "slack.progress.waiting": "等待下一次更新",
948
949
  "slack.progress.delivering": "正在发送答复",
949
950
  "slack.progress.complete": "工作观察已结束",
951
+ "slack.progress.workflowComplete": "工作流回复已结束,实际制作结果需另行核实。",
952
+ "slack.progress.unavailable": "活动更新不可用,执行可能仍在继续。",
950
953
  "slack.progress.error": "请求失败",
951
954
  "slack.progress.cancelled": "请求已取消",
952
955
  "slack.progress.expired": "进度显示已结束,执行可能仍在继续。",
@@ -955,7 +958,8 @@
955
958
  "slack.progress.restored": "之前的进度显示已结束,执行结果尚未确认。",
956
959
  "slack.progress.failure": "未能完成回复。",
957
960
  "slack.progress.elapsed": "已用时:{seconds}秒",
958
- "slack.progress.lastActivity": "上次活动:{seconds}秒前",
961
+ "slack.progress.lastActivity": "上次活动:{seconds}秒前",
962
+ "slack.progress.longRunning": "已运行 {minutes} 分钟 — 仍在进行中",
959
963
  "slack.progress.category.read": "读取文件",
960
964
  "slack.progress.category.write": "编辑文件",
961
965
  "slack.progress.category.search": "搜索",
@@ -971,5 +975,7 @@
971
975
  "slack.progress.status.unconfirmed": "完成未确认",
972
976
  "slack.progress.deliveryComplete": "答复已发送",
973
977
  "slack.progress.deliveryFailed": "无法确认答复已发送",
974
- "slack.progress.deliveryUnconfirmed": "答复发送情况未确认"
978
+ "slack.progress.deliveryUnconfirmed": "答复发送情况未确认",
979
+ "slack.workflow.unavailable": "由于无法确认该自动化的发送者/请求信息或指定的已启用技能,未予执行。请检查配置。",
980
+ "slack.workflow.unconfirmed": "已注册的工作流未返回实质结果。相关操作可能已经开始,重试前请先检查其记录。"
975
981
  }
@@ -66,12 +66,19 @@ export function CodeWorkbench({ controller: c, endpointKey, onOpenLocalFile }: P
66
66
  <p>Your draft and choices are kept. Press Send when you are ready to create another session.</p>
67
67
  <button type="button" onClick={c.startAnotherSession}>Start another session</button>
68
68
  </section>}
69
- {unknownSend && <section className="code-recovery-strip" aria-label="Unconfirmed send">
70
- <strong>Send outcome not confirmed</strong>
71
- <p>Retry uses the original request. It may submit it if the server has not already accepted it.</p>
69
+ {unknownSend && <section className="code-recovery-strip"
70
+ aria-label={c.resendRequired ? 'Send not started' : 'Unconfirmed send'}>
71
+ <strong>{c.resendRequired ? 'Send did not start' : 'Send outcome not confirmed'}</strong>
72
+ {/* Once the server has spent the key, acceptance is no longer unknown,
73
+ so this must stop offering the weaker, hedged explanation. */}
74
+ <p>{c.resendRequired
75
+ ? 'The original attempt ended on the server without running. Retry sends this as a new message.'
76
+ : 'Retry uses the original request. It may submit it if the server has not already accepted it.'}</p>
72
77
  <pre className="code-retry-preview" aria-label="Original prompt">{c.retryText}</pre>
73
78
  <button type="button" disabled={!c.canRetrySameSend || retrying === sessionKey} onClick={() => void retry()}>
74
- {retrying === sessionKey ? 'Retrying original send…' : 'Retry same send'}</button>
79
+ {retrying === sessionKey
80
+ ? (c.resendRequired ? 'Sending as a new message…' : 'Retrying original send…')
81
+ : (c.resendRequired ? 'Send as a new message' : 'Retry same send')}</button>
75
82
  </section>}
76
83
  {archived ? <section className="code-recovery-strip"><span>Archived · Read-only history</span>
77
84
  <button type="button" disabled={!c.synced || c.pending} onClick={() => { const id = c.selectedId; if (id) perform(() => c.archive(id, false)); }}>Restore session</button>
@@ -20,7 +20,7 @@ export interface StoredCodeDraft {
20
20
  selection: DraftSelection;
21
21
  selectionEdit: number;
22
22
  creating: boolean;
23
- retry: { text: string; key: string; edit: number } | null;
23
+ retry: { text: string; key: string; edit: number; resend?: boolean } | null;
24
24
  stop: { turnId: string; epoch: number } | null;
25
25
  }
26
26
  export interface StoredCodeEndpoint {
@@ -49,7 +49,10 @@ function parseDraft(value: unknown): StoredCodeDraft | null {
49
49
  return {
50
50
  input: value['input'], edit: value['edit'], selectionEdit: value['selectionEdit'], creating: value['creating'],
51
51
  selection: { provider: selection['provider'] as DraftSelection['provider'], cwd: selection['cwd'], model: selection['model'], effort: selection['effort'], permissionMode: selection['permissionMode'] as DraftSelection['permissionMode'] },
52
- retry: retry === null ? null : { text: retry['text'] as string, key: retry['key'] as string, edit: retry['edit'] as number },
52
+ // `resend` is optional on purpose: a draft stored before this field existed
53
+ // must still parse, or recovery is wiped instead of downgraded.
54
+ retry: retry === null ? null : { text: retry['text'] as string, key: retry['key'] as string, edit: retry['edit'] as number,
55
+ ...(retry['resend'] === true ? { resend: true } : {}) },
53
56
  stop: stop === null ? null : { turnId: stop['turnId'] as string, epoch: stop['epoch'] as number },
54
57
  };
55
58
  }
@@ -116,7 +119,8 @@ function pack(draft: CodeDraft): StoredCodeDraft | null {
116
119
  const { provider, cwd, model, effort, permissionMode } = draft.selection;
117
120
  return parseDraft({ input: draft.input, edit: draft.edit, selection: { provider, cwd, model, effort, permissionMode }, selectionEdit: draft.selectionEdit,
118
121
  creating: draft.createUnknown || draft.operation.kind === 'creating',
119
- retry: draft.retry ? { text: draft.retry.text, key: draft.retry.key, edit: draft.retry.edit } : null,
122
+ retry: draft.retry ? { text: draft.retry.text, key: draft.retry.key, edit: draft.retry.edit,
123
+ ...(draft.retry.resend ? { resend: true } : {}) } : null,
120
124
  stop: draft.stopTarget ? { turnId: draft.stopTarget.turnId, epoch: draft.stopTarget.epoch } : null });
121
125
  }
122
126
  export function saveCodeDraftStorage(storage: Storage, endpoint: string, book: CodeDraftBook): string | null {
@@ -8,7 +8,12 @@ export interface CodeDraft {
8
8
  selection: CodeCreateSessionRequest;
9
9
  selectionEdit: number;
10
10
  operation: CodeControllerModel['operation'];
11
- retry: { text: string; key: string; edit: number } | null;
11
+ /**
12
+ * `resend` marks an attempt whose key the server already spent. Acceptance is
13
+ * then known — it failed — so the surfaces that describe an unconfirmed send
14
+ * must stop hedging.
15
+ */
16
+ retry: { text: string; key: string; edit: number; resend?: boolean } | null;
12
17
  createUnknown: boolean;
13
18
  requiredSequence: number;
14
19
  stopTarget: { turnId: string; epoch: number; outcome: 'pending' | 'unknown' | 'retryable' | 'accepted' } | null;
@@ -40,7 +45,9 @@ function restoreDraft(saved: StoredCodeDraft): CodeDraft {
40
45
  draft.createUnknown = saved.creating;
41
46
  draft.retry = saved.retry;
42
47
  if (saved.creating) draft.operation = { kind: 'creating', error: 'Creation was interrupted by reload. The original session may exist; no request has been retried.' };
43
- if (saved.retry) draft.operation = { kind: 'unknown-send', error: 'The original message has not been reconciled after reload. It will not be sent automatically.' };
48
+ if (saved.retry) draft.operation = { kind: 'unknown-send', error: saved.retry.resend
49
+ ? 'The original attempt ended on the server without running. The message was not resent; Retry will submit it as a new message.'
50
+ : 'The original message has not been reconciled after reload. It will not be sent automatically.' };
44
51
  if (saved.stop) {
45
52
  draft.stopTarget = { ...saved.stop, outcome: 'unknown' };
46
53
  draft.operation = { kind: 'stopping', error: 'Checking the captured Stop after reload. No cancellation has been resent.' };
@@ -1,6 +1,6 @@
1
1
  import type {
2
- CodeCreateSessionRequest, CodeModelCatalog, CodePatchSessionRequest, CodePermissionRequest,
3
- CodeSessionInfo, CodeWireEvent,
2
+ CodeContextUsage, CodeCreateSessionRequest, CodeModelCatalog, CodePatchSessionRequest,
3
+ CodePermissionRequest, CodePromptReceipt, CodeSessionInfo, CodeWireEvent,
4
4
  } from '../../../../src/code-mode/wire';
5
5
  import type { CodeControllerModel, CodeControllerOptions, CodeSessionFilter, CodeTransportState } from './code-controller-types';
6
6
  import { CodeClientError, codeBaseOrigin, createCodeSessionClient, type CodeGitInfo } from './code-session-client';
@@ -22,12 +22,63 @@ const newer = (incoming: CodeSessionInfo, current?: CodeSessionInfo | null) => !
22
22
  || incoming.epoch > current.epoch || (incoming.epoch === current.epoch && (incoming.sequence > current.sequence
23
23
  || (incoming.sequence === current.sequence && incoming.revision >= current.revision)));
24
24
 
25
+ /**
26
+ * A receipt status that means the send is spent without having produced work.
27
+ *
28
+ * The server consumes a `clientTurnKey` once. After a restart seals an
29
+ * interrupted turn, the same key returns only a duplicate receipt — HTTP 200,
30
+ * `ok:true`, and the stored status — and starts nothing. Answering a retry with
31
+ * that receipt as if it were an admission is what left the composer idle on a
32
+ * session that had failed.
33
+ *
34
+ * `completed` is deliberately absent: that turn ran, and its output is in the
35
+ * transcript, so closing the send is the honest outcome.
36
+ */
37
+ const spent = (status: CodePromptReceipt['status']): boolean => status === 'failed' || status === 'cancelled';
38
+
39
+ /**
40
+ * Did the transcript already settle the turn this send belongs to?
41
+ *
42
+ * The HTTP receipt is only visible when the client actually posts. After a
43
+ * reload the draft is restored with its original key and the first refresh sees
44
+ * the orphaned turn's own `user_message` still in history, so the guard has to
45
+ * read the transcript rather than the response.
46
+ */
47
+ function deadSend(state: CodeSessionState | undefined, key: string): boolean {
48
+ const sent = state?.items.find(item => item.kind === 'user_message' && item.clientTurnKey === key);
49
+ if (!sent) return false;
50
+ return state!.items.some(item => item.turnId === sent.turnId
51
+ && (item.kind === 'turn_failed' || item.kind === 'turn_cancelled'));
52
+ }
53
+
54
+ /**
55
+ * What a read that could actually observe the runtime said about attention.
56
+ *
57
+ * `contextUsage` and `pendingPermissionCount` are attached at read time and never
58
+ * persisted, so only the two server reads that overlay them — list and snapshot —
59
+ * can speak for them. A stored `code_session` frame and the create/patch/cancel/
60
+ * attach responses carry neither field, and `newer()` ranks only epoch, sequence
61
+ * and revision, so without a separate owner a payload that simply cannot carry a
62
+ * field would outrank one that measured it.
63
+ *
64
+ * Absence is recorded, not skipped: once the runtime is reaped the owning read
65
+ * stops reporting usage, and the meter has to hide in that same turn.
66
+ */
67
+ interface CodeSessionAttention {
68
+ epoch: number;
69
+ sequence: number;
70
+ revision: number;
71
+ pendingPermissionCount?: number;
72
+ contextUsage?: CodeContextUsage;
73
+ }
74
+
25
75
  /** Code's async owner; React only subscribes and supplies the single SSE transport. */
26
76
  export class CodeController {
27
77
  private client;
28
78
  private book: CodeDraftBook;
29
79
  private details = new Map<string, CodeSessionState>();
30
80
  private summaries = new Map<string, CodeSessionInfo>();
81
+ private attention = new Map<string, CodeSessionAttention>();
31
82
  private rows: string[] = [];
32
83
  private catalog: CodeModelCatalog | null = null;
33
84
  private catalogError: string | null = null;
@@ -100,6 +151,7 @@ export class CodeController {
100
151
  this.book.listeners.delete(this.changed);
101
152
  this.details.clear();
102
153
  this.summaries.clear();
154
+ this.attention.clear();
103
155
  this.rows = [];
104
156
  this.gitGeneration++;
105
157
  this.pickerGeneration++;
@@ -133,13 +185,41 @@ export class CodeController {
133
185
  const summary = this.summaries.get(id);
134
186
  return summary && newer(summary, detail) ? summary : detail ?? summary ?? null;
135
187
  }
136
- private accept(session: CodeSessionInfo): void {
188
+ /**
189
+ * Record what an owning read saw. Call this BEFORE accept() on the same object:
190
+ * accept() copies rather than mutates, but the ordering keeps the rule obvious.
191
+ */
192
+ private observe(session: CodeSessionInfo): void {
193
+ const known = this.attention.get(session.sessionId);
194
+ if (known && !newer(session, { ...session, epoch: known.epoch, sequence: known.sequence, revision: known.revision })) return;
195
+ this.attention.set(session.sessionId, {
196
+ epoch: session.epoch, sequence: session.sequence, revision: session.revision,
197
+ ...(session.pendingPermissionCount === undefined ? {} : { pendingPermissionCount: session.pendingPermissionCount }),
198
+ ...(session.contextUsage === undefined ? {} : { contextUsage: session.contextUsage }),
199
+ });
200
+ }
201
+ /** Publish `base` with attention resolved from its owner, absence included. */
202
+ private resolve(id: string, base: CodeSessionInfo): CodeSessionInfo {
203
+ const seen = this.attention.get(id);
204
+ const { contextUsage: _usage, pendingPermissionCount: _count, ...rest } = base;
205
+ return {
206
+ ...rest,
207
+ ...(seen?.pendingPermissionCount === undefined ? {} : { pendingPermissionCount: seen.pendingPermissionCount }),
208
+ ...(seen?.contextUsage === undefined ? {} : { contextUsage: seen.contextUsage }),
209
+ };
210
+ }
211
+ private accept(incoming: CodeSessionInfo): void {
137
212
  if (!this.active) return;
138
- if (!newer(session, this.info(session.sessionId))) return;
213
+ if (!newer(incoming, this.info(incoming.sessionId))) return;
214
+ // The ranked record carries identity and lifecycle only. Attached fields are
215
+ // dropped by copy, never by mutation: readIndex hands the same row to
216
+ // observe(), and update() hands over details.session, which the reducer may
217
+ // still be holding (code-session-state.ts snapshot replace).
218
+ const { contextUsage: _usage, pendingPermissionCount: _count, ...session } = incoming;
139
219
  this.summaries.set(session.sessionId, session);
140
220
  if (this.summaries.size > MAX_INDEX + MAX_DETAILS) {
141
221
  const victim = [...this.summaries.keys()].find(id => id !== this.book.selectedId && !this.details.has(id) && !this.rows.includes(id));
142
- if (victim) this.summaries.delete(victim);
222
+ if (victim) { this.summaries.delete(victim); this.attention.delete(victim); }
143
223
  }
144
224
  const draft = this.book.sessions.get(session.sessionId);
145
225
  if (draft?.stopTarget && (session.turnId !== draft.stopTarget.turnId || session.epoch !== draft.stopTarget.epoch
@@ -174,7 +254,8 @@ export class CodeController {
174
254
  if (state.session) this.accept(state.session);
175
255
  const draft = this.book.sessions.get(id);
176
256
  if (draft?.retry && state.items.some(item => item.kind === 'user_message' && item.clientTurnKey === draft.retry!.key)) {
177
- acknowledgeCodeSend(draft, draft.retry.key);
257
+ if (deadSend(state, draft.retry.key)) this.requireNewKey(draft);
258
+ else acknowledgeCodeSend(draft, draft.retry.key);
178
259
  }
179
260
  if (draft && state.synced) {
180
261
  const current = new Set(state.permissions.map(p => p.permissionId));
@@ -184,12 +265,31 @@ export class CodeController {
184
265
  }
185
266
  this.notify();
186
267
  }
268
+ /**
269
+ * Keep the message, retire the key.
270
+ *
271
+ * The server will answer that spent key with the same duplicate receipt
272
+ * forever, so retrying it is not a retry. Rotating gives Retry something that
273
+ * can actually be admitted, and it also stops the settled turn's own
274
+ * `user_message` from matching, so this fires once rather than on every read.
275
+ */
276
+ private requireNewKey(draft: CodeDraft): void {
277
+ if (!draft.retry) return;
278
+ draft.retry = { ...draft.retry, key: crypto.randomUUID(), resend: true };
279
+ draft.operation = { kind: 'unknown-send', error: 'The original attempt ended on the server without running. The message was not resent; Retry will submit it as a new message.' };
280
+ }
187
281
  private makeModel(): CodeControllerModel {
188
282
  const id = this.book.selectedId;
189
283
  const canonical = this.info(id);
190
284
  const draft = this.draft(id);
191
285
  const detail = id ? this.details.get(id) : undefined;
192
- const session = canonical && detail?.synced ? { ...canonical, pendingPermissionCount: detail.permissions.length } : canonical;
286
+ // One place decides what either screen may show, so the meter and the
287
+ // sidebar cannot disagree about the same read.
288
+ const publish = (row: CodeSessionInfo, state?: CodeSessionState): CodeSessionInfo => {
289
+ const resolved = this.resolve(row.sessionId, row);
290
+ return state?.synced ? { ...resolved, pendingPermissionCount: state.permissions.length } : resolved;
291
+ };
292
+ const session = canonical ? publish(canonical, detail) : canonical;
193
293
  const operation = draft.operation;
194
294
  const persistenceWarning = this.book.storageWarning ?? this.book.recoveryWarning;
195
295
  const pending = ['creating', 'sending', 'stopping', 'resuming', 'patching'].includes(operation.kind) && !operation.error;
@@ -197,7 +297,7 @@ export class CodeController {
197
297
  return {
198
298
  catalog: this.catalog, sessions: this.rows.map(id => {
199
299
  const row = this.info(id), detail = this.details.get(id);
200
- return row && detail?.synced ? { ...row, pendingPermissionCount: detail.permissions.length } : row;
300
+ return row ? publish(row, detail) : row;
201
301
  }).filter((row): row is CodeSessionInfo => !!row),
202
302
  selectedId: id, session, items: withPendingUserItem(draft, detail?.items ?? []),
203
303
  permissions: detail?.permissions ?? [],
@@ -207,6 +307,7 @@ export class CodeController {
207
307
  error: [operation.error ?? detail?.error ?? this.indexError ?? this.catalogError ?? this.gitError ?? session?.error?.message, persistenceWarning].filter(Boolean).join(' ') || null,
208
308
  operation: { ...operation, error: operation.error && persistenceWarning ? `${operation.error} ${persistenceWarning}` : operation.error }, retryText: draft.retry?.text ?? null,
209
309
  canRetrySameSend: !!id && operation.kind === 'unknown-send' && !!draft.retry && synced && session?.archivedAt === null,
310
+ resendRequired: !!draft.retry?.resend,
210
311
  permissionOperations: { ...draft.permissionOperations }, hasMoreSessions: this.moreSessions,
211
312
  hasOlderHistory: detail?.hasOlder ?? false, filter: this.filter,
212
313
  creationUnknown: id === null && draft.createUnknown, startAnotherSession: this.startAnotherSession,
@@ -246,6 +347,8 @@ export class CodeController {
246
347
  ...(this.filter.scope === 'cwd' ? { cwd: this.workingDir } : {}) }, this.abort.signal);
247
348
  if (!this.active || life !== this.lifetime || generation !== this.indexGeneration) return;
248
349
  for (const session of page.sessions) {
350
+ // A listing is one of the two reads that can see the live runtime.
351
+ this.observe(session);
249
352
  this.accept(session);
250
353
  const state = this.details.get(session.sessionId);
251
354
  if (state?.hydrated && !state.hydrating && session.sequence > state.cursor) {
@@ -258,7 +361,7 @@ export class CodeController {
258
361
  this.moreSessions = page.hasMore && this.rows.length < MAX_INDEX;
259
362
  this.indexError = null;
260
363
  const retained = new Set([...this.rows, ...this.details.keys(), ...(this.book.selectedId ? [this.book.selectedId] : [])]);
261
- for (const id of this.summaries.keys()) if (!retained.has(id)) this.summaries.delete(id);
364
+ for (const id of this.summaries.keys()) if (!retained.has(id)) { this.summaries.delete(id); this.attention.delete(id); }
262
365
  } catch (error) {
263
366
  if (this.active && life === this.lifetime && generation === this.indexGeneration) this.indexError = message(error);
264
367
  } finally {
@@ -292,6 +395,8 @@ export class CodeController {
292
395
  const snapshot = await this.client.snapshot(id, read.signal);
293
396
  if (!current()) return;
294
397
  checkedStop = checkingStop;
398
+ // The other owning read. Absence here is the idle reap.
399
+ this.observe(snapshot.session);
295
400
  this.update(id, reduceCodeSession(this.state(id), { type: 'snapshot', snapshot }));
296
401
  }
297
402
  state = this.state(id);
@@ -335,7 +440,8 @@ export class CodeController {
335
440
  const draft = this.book.sessions.get(event.sessionId);
336
441
  if (event.item?.kind === 'user_message' && event.item.clientTurnKey && draft?.retry?.key === event.item.clientTurnKey) {
337
442
  draft.requiredSequence = Math.max(draft.requiredSequence, event.sequence);
338
- acknowledgeCodeSend(draft, event.item.clientTurnKey);
443
+ if (deadSend(this.details.get(event.sessionId), event.item.clientTurnKey)) this.requireNewKey(draft);
444
+ else acknowledgeCodeSend(draft, event.item.clientTurnKey);
339
445
  }
340
446
  const previous = this.details.get(event.sessionId);
341
447
  if (!previous) { this.notify(); return; }
@@ -473,7 +579,11 @@ export class CodeController {
473
579
  this.accept(await this.client.patchSession(id, { ...input, expectedRevision: session.revision }));
474
580
  draft.operation = { kind: 'idle', error: null };
475
581
  } catch (error) {
476
- if (error instanceof CodeClientError && error.session?.sessionId === id) this.accept(error.session);
582
+ if (error instanceof CodeClientError && error.session?.sessionId === id) {
583
+ // A revision conflict answers with the snapshot session, overlay included.
584
+ this.observe(error.session);
585
+ this.accept(error.session);
586
+ }
477
587
  failure = error instanceof CodeClientError ? error : new Error(message(error));
478
588
  draft.operation = { kind: 'idle', error: failure.message };
479
589
  }
@@ -538,7 +648,10 @@ export class CodeController {
538
648
  draft.requiredSequence = Math.max(draft.requiredSequence, receipt.sequence);
539
649
  const state = this.details.get(id);
540
650
  if (state) this.details.set(id, reduceCodeSession(state, { type: 'stale' }));
541
- acknowledgeCodeSend(draft, attempt.key);
651
+ // A duplicate receipt for a spent key is a report about a turn that is
652
+ // already over, not an admission of this one.
653
+ if (spent(receipt.status)) this.requireNewKey(draft);
654
+ else acknowledgeCodeSend(draft, attempt.key);
542
655
  } catch (error) {
543
656
  // The committed user event may already have acknowledged a lost HTTP response.
544
657
  if (draft.retry?.key === attempt.key) {
@@ -27,6 +27,8 @@ export interface CodeControllerModel {
27
27
  operation: { kind: CodeOperationKind; error: string | null };
28
28
  retryText: string | null;
29
29
  canRetrySameSend: boolean;
30
+ /** The previous key is spent: Retry sends a new message rather than reusing it. */
31
+ resendRequired?: boolean;
30
32
  permissionOperations: Record<string, { pending: boolean; error: string | null }>;
31
33
  hasMoreSessions: boolean;
32
34
  hasOlderHistory: boolean;
@@ -19,6 +19,11 @@ export const PENDING_USER_ITEM_ID = 'pending:user';
19
19
  export function pendingUserItem(draft: CodeDraft, items: CodeItem[], now = Date.now()): CodeItem | null {
20
20
  const retry = draft.retry;
21
21
  if (!retry) return null;
22
+ // The server already spent the previous key, so nothing is in flight. This row
23
+ // renders as "Sending"; showing it here would restate the very confusion the
24
+ // re-key exists to remove. The failed attempt stays in history and the recovery
25
+ // strip still previews the text.
26
+ if (retry.resend) return null;
22
27
  // The authoritative item is here: show that one instead.
23
28
  if (items.some(item => item.kind === 'user_message' && item.clientTurnKey === retry.key)) return null;
24
29
  const unknown = draft.operation.kind === 'unknown-send';
@@ -18,3 +18,9 @@ tests/unit/claude-sdk-control.test.ts
18
18
  tests/unit/claude-sdk-core-hardening.test.ts
19
19
  tests/unit/claude-sdk-deferred-core.test.ts
20
20
 
21
+ # Schema migration only runs against a home that already exists, so every
22
+ # fresh-CLI_JAW_HOME CI run skips it everywhere else. These two build an old
23
+ # home and walk the source tree by path, which is where Windows separators and
24
+ # the better-sqlite3 prebuild actually differ (#691).
25
+ tests/unit/db-schema-migration.test.ts
26
+ tests/unit/db-legacy-mention-watch-surface.test.ts