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.
- package/README.md +4 -0
- package/dist/bin/commands/browser-web-ai.js +29 -10
- package/dist/bin/commands/browser-web-ai.js.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/src/agent/claude-runtime-run.js +2 -1
- package/dist/src/agent/claude-runtime-run.js.map +1 -1
- package/dist/src/agent/lifecycle-handler.js +15 -4
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/pi-runtime.js +103 -86
- package/dist/src/agent/pi-runtime.js.map +1 -1
- package/dist/src/agent/spawn/exit-settle.js +74 -0
- package/dist/src/agent/spawn/exit-settle.js.map +1 -0
- package/dist/src/agent/spawn/kill-reason.js +38 -0
- package/dist/src/agent/spawn/kill-reason.js.map +1 -0
- package/dist/src/agent/spawn/queue.js +8 -0
- package/dist/src/agent/spawn/queue.js.map +1 -1
- package/dist/src/agent/spawn/steer-event.js +33 -0
- package/dist/src/agent/spawn/steer-event.js.map +1 -0
- package/dist/src/agent/spawn.js +157 -173
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/agent/watchdog.js +9 -4
- package/dist/src/agent/watchdog.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/browser-escalation.js +18 -1
- package/dist/src/browser/adaptive-fetch/browser-escalation.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/browser-flow.js +36 -9
- package/dist/src/browser/adaptive-fetch/browser-flow.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/browser-session.js +10 -1
- package/dist/src/browser/adaptive-fetch/browser-session.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/camoufox-session.js +16 -1
- package/dist/src/browser/adaptive-fetch/camoufox-session.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/defuddle-provenance.js +49 -0
- package/dist/src/browser/adaptive-fetch/defuddle-provenance.js.map +1 -0
- package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js +61 -31
- package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/fetcher.js +12 -1
- package/dist/src/browser/adaptive-fetch/fetcher.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/live-smoke-manifest.js +12 -18
- package/dist/src/browser/adaptive-fetch/live-smoke-manifest.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/public-endpoint-normalizers.js +85 -1
- package/dist/src/browser/adaptive-fetch/public-endpoint-normalizers.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/safety.js +5 -3
- package/dist/src/browser/adaptive-fetch/safety.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/scheduler.js +34 -5
- package/dist/src/browser/adaptive-fetch/scheduler.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/tls-fetch.js +54 -33
- package/dist/src/browser/adaptive-fetch/tls-fetch.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/vendor/README.md +33 -1
- package/dist/src/browser/adaptive-fetch/ytdlp-reader.js +54 -9
- package/dist/src/browser/adaptive-fetch/ytdlp-reader.js.map +1 -1
- package/dist/src/browser/web-ai/capability-observation-presets.js +12 -10
- package/dist/src/browser/web-ai/capability-observation-presets.js.map +1 -1
- package/dist/src/browser/web-ai/chatgpt-model.js +80 -7
- package/dist/src/browser/web-ai/chatgpt-model.js.map +1 -1
- package/dist/src/browser/web-ai/gemini-model.js +18 -0
- package/dist/src/browser/web-ai/gemini-model.js.map +1 -1
- package/dist/src/browser/web-ai/grok-model.js +64 -15
- package/dist/src/browser/web-ai/grok-model.js.map +1 -1
- package/dist/src/code-mode/providers/codex-app.js +83 -0
- package/dist/src/code-mode/providers/codex-app.js.map +1 -1
- package/dist/src/core/config.js +77 -73
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +227 -151
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/settings-merge.js +78 -102
- package/dist/src/core/settings-merge.js.map +1 -1
- package/dist/src/discord/bot.js +37 -54
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/discord-file.js +29 -3
- package/dist/src/discord/discord-file.js.map +1 -1
- package/dist/src/discord/send-only-client.js +56 -8
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/manager/routes/telegram-hub.js +8 -1
- package/dist/src/manager/routes/telegram-hub.js.map +1 -1
- package/dist/src/manager/telegram-hub/hub-bot.js +7 -1
- package/dist/src/manager/telegram-hub/hub-bot.js.map +1 -1
- package/dist/src/messaging/delivery-outcome.js +30 -0
- package/dist/src/messaging/delivery-outcome.js.map +1 -1
- package/dist/src/messaging/file-receipt.js +49 -0
- package/dist/src/messaging/file-receipt.js.map +1 -0
- package/dist/src/messaging/native-body.js +31 -0
- package/dist/src/messaging/native-body.js.map +1 -0
- package/dist/src/messaging/queue-notice-record.js +48 -0
- package/dist/src/messaging/queue-notice-record.js.map +1 -0
- package/dist/src/messaging/send.js +13 -2
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/messaging/target-reply-guard.js +66 -0
- package/dist/src/messaging/target-reply-guard.js.map +1 -0
- package/dist/src/orchestrator/collect.js +18 -3
- package/dist/src/orchestrator/collect.js.map +1 -1
- package/dist/src/orchestrator/gateway.js +3 -1
- package/dist/src/orchestrator/gateway.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +7 -1
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/session-lanes.js +2 -2
- package/dist/src/orchestrator/session-lanes.js.map +1 -1
- package/dist/src/prompt/builder.js +2 -2
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/routes/i18n.js +9 -4
- package/dist/src/routes/i18n.js.map +1 -1
- package/dist/src/routes/instance.js +8 -4
- package/dist/src/routes/instance.js.map +1 -1
- package/dist/src/routes/jaw-memory.js +5 -5
- package/dist/src/routes/jaw-memory.js.map +1 -1
- package/dist/src/routes/link-preview.js +5 -1
- package/dist/src/routes/link-preview.js.map +1 -1
- package/dist/src/routes/memory.js +9 -7
- package/dist/src/routes/memory.js.map +1 -1
- package/dist/src/routes/messaging.js +21 -10
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/orchestrate.js +127 -118
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/settings.js +7 -7
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/routes/system.js +9 -4
- package/dist/src/routes/system.js.map +1 -1
- package/dist/src/slack/bot.js +199 -132
- package/dist/src/slack/bot.js.map +1 -1
- package/dist/src/slack/events.js +31 -8
- package/dist/src/slack/events.js.map +1 -1
- package/dist/src/slack/ingress.js +3 -1
- package/dist/src/slack/ingress.js.map +1 -1
- package/dist/src/slack/progress-activity.js +24 -3
- package/dist/src/slack/progress-activity.js.map +1 -1
- package/dist/src/slack/progress-lifecycle.js +29 -0
- package/dist/src/slack/progress-lifecycle.js.map +1 -1
- package/dist/src/slack/progress.js +1 -1
- package/dist/src/slack/progress.js.map +1 -1
- package/dist/src/slack/send-only-client.js +16 -3
- package/dist/src/slack/send-only-client.js.map +1 -1
- package/dist/src/slack/slack-file.js +5 -3
- package/dist/src/slack/slack-file.js.map +1 -1
- package/dist/src/slack/workflow.js +134 -0
- package/dist/src/slack/workflow.js.map +1 -0
- package/dist/src/telegram/bot.js +60 -55
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/telegram/rich-message.js +28 -20
- package/dist/src/telegram/rich-message.js.map +1 -1
- package/dist/src/telegram/telegram-file.js +45 -9
- package/dist/src/telegram/telegram-file.js.map +1 -1
- package/dist/src/trace/activity-journal.js +3 -6
- package/dist/src/trace/activity-journal.js.map +1 -1
- package/dist/src/trace/activity-retention.js +29 -0
- package/dist/src/trace/activity-retention.js.map +1 -1
- package/package.json +1 -1
- package/public/dist/.vite/manifest.json +20 -20
- package/public/dist/assets/{ChannelEnablementControl-DwUDKjX9.js → ChannelEnablementControl-egwTtO9-.js} +8 -8
- package/public/dist/assets/{ChannelSetupEntry-DH0AuNNX.js → ChannelSetupEntry-CCrUYd2-.js} +1 -1
- package/public/dist/assets/{ChannelsDiscord-7NmoTjR7.js → ChannelsDiscord-B6rJAsQG.js} +1 -1
- package/public/dist/assets/{ChannelsSlack-Ba0ax6iT.js → ChannelsSlack-CrSIw4Wd.js} +1 -1
- package/public/dist/assets/{ChannelsTelegram-DKA9atf2.js → ChannelsTelegram-DPrdLEnT.js} +1 -1
- package/public/dist/assets/CodeCanvas-D5jp8-t5.js +2 -0
- package/public/dist/assets/{manager-iX7KosWI.js → manager-CFwOAoe0.js} +2 -2
- package/public/dist/assets/{settings-embedding-DIqST-91.js → settings-embedding-DspkJ6sr.js} +1 -1
- package/public/dist/assets/{settings-KIPNUOIV.js → settings-qkC0NuQ7.js} +1 -1
- package/public/dist/manager/index.html +1 -1
- package/public/dist/settings/index.html +1 -1
- package/public/locales/en.json +9 -3
- package/public/locales/ja.json +9 -3
- package/public/locales/ko.json +9 -3
- package/public/locales/zh.json +9 -3
- package/public/manager/src/code/CodeWorkbench.tsx +11 -4
- package/public/manager/src/code/code-controller-draft-storage.ts +7 -3
- package/public/manager/src/code/code-controller-drafts.ts +9 -2
- package/public/manager/src/code/code-controller-runtime.ts +125 -12
- package/public/manager/src/code/code-controller-types.ts +2 -0
- package/public/manager/src/code/pending-user-item.ts +5 -0
- package/scripts/ci/windows-unit-manifest.txt +6 -0
- 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-
|
|
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-
|
|
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>
|
package/public/locales/en.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/public/locales/ja.json
CHANGED
|
@@ -193,7 +193,8 @@
|
|
|
193
193
|
"stt.openaiModel": "モデル",
|
|
194
194
|
"stt.vertexConfig": "Vertex AI 設定(JSON)",
|
|
195
195
|
"tg.timeout": "タイムアウト(20 分間応答なし)",
|
|
196
|
-
|
|
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
|
-
|
|
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
|
}
|
package/public/locales/ko.json
CHANGED
|
@@ -195,7 +195,8 @@
|
|
|
195
195
|
"stt.openaiModel": "모델",
|
|
196
196
|
"stt.vertexConfig": "Vertex AI 설정 (JSON)",
|
|
197
197
|
"tg.timeout": "시간 초과 (20분 무응답)",
|
|
198
|
-
|
|
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
|
-
|
|
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
|
}
|
package/public/locales/zh.json
CHANGED
|
@@ -193,7 +193,8 @@
|
|
|
193
193
|
"stt.openaiModel": "模型",
|
|
194
194
|
"stt.vertexConfig": "Vertex AI 配置(JSON)",
|
|
195
195
|
"tg.timeout": "超时(20 分钟无响应)",
|
|
196
|
-
|
|
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
|
-
|
|
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"
|
|
70
|
-
|
|
71
|
-
<
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|