charness 0.0.0-stage → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +88 -0
- package/DEVELOPMENT.md +110 -0
- package/LICENSE +21 -0
- package/MIGRATION_1_TO_2.md +24 -0
- package/README.md +1024 -2
- package/dist/accessControl.d.ts +71 -0
- package/dist/accessControl.d.ts.map +1 -0
- package/dist/accessControl.js +108 -0
- package/dist/accessControl.js.map +1 -0
- package/dist/adapters/adapterEventRouting.d.ts +51 -0
- package/dist/adapters/adapterEventRouting.d.ts.map +1 -0
- package/dist/adapters/adapterEventRouting.js +74 -0
- package/dist/adapters/adapterEventRouting.js.map +1 -0
- package/dist/adapters/adapterNames.d.ts +19 -0
- package/dist/adapters/adapterNames.d.ts.map +1 -0
- package/dist/adapters/adapterNames.js +25 -0
- package/dist/adapters/adapterNames.js.map +1 -0
- package/dist/adapters/claudeCliAdapter.d.ts +1336 -0
- package/dist/adapters/claudeCliAdapter.d.ts.map +1 -0
- package/dist/adapters/claudeCliAdapter.js +4204 -0
- package/dist/adapters/claudeCliAdapter.js.map +1 -0
- package/dist/adapters/claudeJsonStreamAdapter.d.ts +548 -0
- package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -0
- package/dist/adapters/claudeJsonStreamAdapter.js +1810 -0
- package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -0
- package/dist/adapters/claudePlatformFlags.d.ts +35 -0
- package/dist/adapters/claudePlatformFlags.d.ts.map +1 -0
- package/dist/adapters/claudePlatformFlags.js +46 -0
- package/dist/adapters/claudePlatformFlags.js.map +1 -0
- package/dist/adapters/createAdapter.d.ts +252 -0
- package/dist/adapters/createAdapter.d.ts.map +1 -0
- package/dist/adapters/createAdapter.js +475 -0
- package/dist/adapters/createAdapter.js.map +1 -0
- package/dist/adapters/openCodeAdapter.d.ts +1446 -0
- package/dist/adapters/openCodeAdapter.d.ts.map +1 -0
- package/dist/adapters/openCodeAdapter.js +4540 -0
- package/dist/adapters/openCodeAdapter.js.map +1 -0
- package/dist/adapters/sessionLaunchDefaults.d.ts +28 -0
- package/dist/adapters/sessionLaunchDefaults.d.ts.map +1 -0
- package/dist/adapters/sessionLaunchDefaults.js +15 -0
- package/dist/adapters/sessionLaunchDefaults.js.map +1 -0
- package/dist/adapters/terminalAdapter.d.ts +68 -0
- package/dist/adapters/terminalAdapter.d.ts.map +1 -0
- package/dist/adapters/terminalAdapter.js +371 -0
- package/dist/adapters/terminalAdapter.js.map +1 -0
- package/dist/agentLogin/agentLogin.d.ts +31 -0
- package/dist/agentLogin/agentLogin.d.ts.map +1 -0
- package/dist/agentLogin/agentLogin.js +493 -0
- package/dist/agentLogin/agentLogin.js.map +1 -0
- package/dist/agentTrigger.d.ts +28 -0
- package/dist/agentTrigger.d.ts.map +1 -0
- package/dist/agentTrigger.js +53 -0
- package/dist/agentTrigger.js.map +1 -0
- package/dist/apiErrorRetry.d.ts +209 -0
- package/dist/apiErrorRetry.d.ts.map +1 -0
- package/dist/apiErrorRetry.js +351 -0
- package/dist/apiErrorRetry.js.map +1 -0
- package/dist/apiRetryKick.d.ts +148 -0
- package/dist/apiRetryKick.d.ts.map +1 -0
- package/dist/apiRetryKick.js +158 -0
- package/dist/apiRetryKick.js.map +1 -0
- package/dist/bootClassifier.d.ts +46 -0
- package/dist/bootClassifier.d.ts.map +1 -0
- package/dist/bootClassifier.js +43 -0
- package/dist/bootClassifier.js.map +1 -0
- package/dist/bot.d.ts +207 -0
- package/dist/bot.d.ts.map +1 -0
- package/dist/bot.js +10611 -0
- package/dist/bot.js.map +1 -0
- package/dist/botFileStorage.d.ts +60 -0
- package/dist/botFileStorage.d.ts.map +1 -0
- package/dist/botFileStorage.js +205 -0
- package/dist/botFileStorage.js.map +1 -0
- package/dist/cli/applyDnsFix.d.ts +7 -0
- package/dist/cli/applyDnsFix.d.ts.map +1 -0
- package/dist/cli/applyDnsFix.js +46 -0
- package/dist/cli/applyDnsFix.js.map +1 -0
- package/dist/cli/bot.d.ts +27 -0
- package/dist/cli/bot.d.ts.map +1 -0
- package/dist/cli/bot.js +98 -0
- package/dist/cli/bot.js.map +1 -0
- package/dist/cli/botEntry.d.ts +3 -0
- package/dist/cli/botEntry.d.ts.map +1 -0
- package/dist/cli/botEntry.js +11 -0
- package/dist/cli/botEntry.js.map +1 -0
- package/dist/cli/connectorGuards.d.ts +31 -0
- package/dist/cli/connectorGuards.d.ts.map +1 -0
- package/dist/cli/connectorGuards.js +159 -0
- package/dist/cli/connectorGuards.js.map +1 -0
- package/dist/cli/envLoader.d.ts +34 -0
- package/dist/cli/envLoader.d.ts.map +1 -0
- package/dist/cli/envLoader.js +132 -0
- package/dist/cli/envLoader.js.map +1 -0
- package/dist/cli/hot.d.ts +82 -0
- package/dist/cli/hot.d.ts.map +1 -0
- package/dist/cli/hot.js +323 -0
- package/dist/cli/hot.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/connectors/jira/adf.d.ts +72 -0
- package/dist/connectors/jira/adf.d.ts.map +1 -0
- package/dist/connectors/jira/adf.js +407 -0
- package/dist/connectors/jira/adf.js.map +1 -0
- package/dist/connectors/jira/agentBinaries.d.ts +14 -0
- package/dist/connectors/jira/agentBinaries.d.ts.map +1 -0
- package/dist/connectors/jira/agentBinaries.js +75 -0
- package/dist/connectors/jira/agentBinaries.js.map +1 -0
- package/dist/connectors/jira/answerSink.d.ts +52 -0
- package/dist/connectors/jira/answerSink.d.ts.map +1 -0
- package/dist/connectors/jira/answerSink.js +220 -0
- package/dist/connectors/jira/answerSink.js.map +1 -0
- package/dist/connectors/jira/attachmentDownload.d.ts +41 -0
- package/dist/connectors/jira/attachmentDownload.d.ts.map +1 -0
- package/dist/connectors/jira/attachmentDownload.js +92 -0
- package/dist/connectors/jira/attachmentDownload.js.map +1 -0
- package/dist/connectors/jira/attachmentTool.d.ts +43 -0
- package/dist/connectors/jira/attachmentTool.d.ts.map +1 -0
- package/dist/connectors/jira/attachmentTool.js +150 -0
- package/dist/connectors/jira/attachmentTool.js.map +1 -0
- package/dist/connectors/jira/client.d.ts +490 -0
- package/dist/connectors/jira/client.d.ts.map +1 -0
- package/dist/connectors/jira/client.js +453 -0
- package/dist/connectors/jira/client.js.map +1 -0
- package/dist/connectors/jira/config.d.ts +117 -0
- package/dist/connectors/jira/config.d.ts.map +1 -0
- package/dist/connectors/jira/config.js +350 -0
- package/dist/connectors/jira/config.js.map +1 -0
- package/dist/connectors/jira/configFile.d.ts +10 -0
- package/dist/connectors/jira/configFile.d.ts.map +1 -0
- package/dist/connectors/jira/configFile.js +50 -0
- package/dist/connectors/jira/configFile.js.map +1 -0
- package/dist/connectors/jira/connector.d.ts +68 -0
- package/dist/connectors/jira/connector.d.ts.map +1 -0
- package/dist/connectors/jira/connector.js +257 -0
- package/dist/connectors/jira/connector.js.map +1 -0
- package/dist/connectors/jira/contextLedger.d.ts +59 -0
- package/dist/connectors/jira/contextLedger.d.ts.map +1 -0
- package/dist/connectors/jira/contextLedger.js +194 -0
- package/dist/connectors/jira/contextLedger.js.map +1 -0
- package/dist/connectors/jira/contextPreamble.d.ts +18 -0
- package/dist/connectors/jira/contextPreamble.d.ts.map +1 -0
- package/dist/connectors/jira/contextPreamble.js +31 -0
- package/dist/connectors/jira/contextPreamble.js.map +1 -0
- package/dist/connectors/jira/inbound.d.ts +132 -0
- package/dist/connectors/jira/inbound.d.ts.map +1 -0
- package/dist/connectors/jira/inbound.js +316 -0
- package/dist/connectors/jira/inbound.js.map +1 -0
- package/dist/connectors/jira/issueBlocks.d.ts +51 -0
- package/dist/connectors/jira/issueBlocks.d.ts.map +1 -0
- package/dist/connectors/jira/issueBlocks.js +182 -0
- package/dist/connectors/jira/issueBlocks.js.map +1 -0
- package/dist/connectors/jira/issueContext.d.ts +25 -0
- package/dist/connectors/jira/issueContext.d.ts.map +1 -0
- package/dist/connectors/jira/issueContext.js +53 -0
- package/dist/connectors/jira/issueContext.js.map +1 -0
- package/dist/connectors/jira/issueDelta.d.ts +35 -0
- package/dist/connectors/jira/issueDelta.d.ts.map +1 -0
- package/dist/connectors/jira/issueDelta.js +50 -0
- package/dist/connectors/jira/issueDelta.js.map +1 -0
- package/dist/connectors/jira/mediaPlaceholders.d.ts +20 -0
- package/dist/connectors/jira/mediaPlaceholders.d.ts.map +1 -0
- package/dist/connectors/jira/mediaPlaceholders.js +92 -0
- package/dist/connectors/jira/mediaPlaceholders.js.map +1 -0
- package/dist/connectors/jira/outbound.d.ts +6 -0
- package/dist/connectors/jira/outbound.d.ts.map +1 -0
- package/dist/connectors/jira/outbound.js +38 -0
- package/dist/connectors/jira/outbound.js.map +1 -0
- package/dist/connectors/jira/prompt.d.ts +37 -0
- package/dist/connectors/jira/prompt.d.ts.map +1 -0
- package/dist/connectors/jira/prompt.js +99 -0
- package/dist/connectors/jira/prompt.js.map +1 -0
- package/dist/connectors/jira/promptSpill.d.ts +50 -0
- package/dist/connectors/jira/promptSpill.d.ts.map +1 -0
- package/dist/connectors/jira/promptSpill.js +157 -0
- package/dist/connectors/jira/promptSpill.js.map +1 -0
- package/dist/connectors/jira/promptText.d.ts +7 -0
- package/dist/connectors/jira/promptText.d.ts.map +1 -0
- package/dist/connectors/jira/promptText.js +26 -0
- package/dist/connectors/jira/promptText.js.map +1 -0
- package/dist/connectors/jira/sessionKeyCodec.d.ts +16 -0
- package/dist/connectors/jira/sessionKeyCodec.d.ts.map +1 -0
- package/dist/connectors/jira/sessionKeyCodec.js +101 -0
- package/dist/connectors/jira/sessionKeyCodec.js.map +1 -0
- package/dist/connectors/jira/trigger.d.ts +50 -0
- package/dist/connectors/jira/trigger.d.ts.map +1 -0
- package/dist/connectors/jira/trigger.js +93 -0
- package/dist/connectors/jira/trigger.js.map +1 -0
- package/dist/connectors/jira/triggerLog.d.ts +51 -0
- package/dist/connectors/jira/triggerLog.d.ts.map +1 -0
- package/dist/connectors/jira/triggerLog.js +78 -0
- package/dist/connectors/jira/triggerLog.js.map +1 -0
- package/dist/connectors/jira/unconfirmedPosts.d.ts +58 -0
- package/dist/connectors/jira/unconfirmedPosts.d.ts.map +1 -0
- package/dist/connectors/jira/unconfirmedPosts.js +105 -0
- package/dist/connectors/jira/unconfirmedPosts.js.map +1 -0
- package/dist/connectors/telegram/answerSink.d.ts +46 -0
- package/dist/connectors/telegram/answerSink.d.ts.map +1 -0
- package/dist/connectors/telegram/answerSink.js +71 -0
- package/dist/connectors/telegram/answerSink.js.map +1 -0
- package/dist/connectors/telegram/commands/autoContinueLimits.d.ts +31 -0
- package/dist/connectors/telegram/commands/autoContinueLimits.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/autoContinueLimits.js +142 -0
- package/dist/connectors/telegram/commands/autoContinueLimits.js.map +1 -0
- package/dist/connectors/telegram/commands/botCore.d.ts +46 -0
- package/dist/connectors/telegram/commands/botCore.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/botCore.js +3 -0
- package/dist/connectors/telegram/commands/botCore.js.map +1 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts +12 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/callbackMessage.js +13 -0
- package/dist/connectors/telegram/commands/callbackMessage.js.map +1 -0
- package/dist/connectors/telegram/commands/compaction.d.ts +47 -0
- package/dist/connectors/telegram/commands/compaction.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/compaction.js +868 -0
- package/dist/connectors/telegram/commands/compaction.js.map +1 -0
- package/dist/connectors/telegram/commands/displayModes.d.ts +13 -0
- package/dist/connectors/telegram/commands/displayModes.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/displayModes.js +335 -0
- package/dist/connectors/telegram/commands/displayModes.js.map +1 -0
- package/dist/connectors/telegram/commands/modelProviders.d.ts +95 -0
- package/dist/connectors/telegram/commands/modelProviders.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/modelProviders.js +841 -0
- package/dist/connectors/telegram/commands/modelProviders.js.map +1 -0
- package/dist/connectors/telegram/commands/reminders.d.ts +54 -0
- package/dist/connectors/telegram/commands/reminders.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/reminders.js +624 -0
- package/dist/connectors/telegram/commands/reminders.js.map +1 -0
- package/dist/connectors/telegram/fileIntake.d.ts +166 -0
- package/dist/connectors/telegram/fileIntake.d.ts.map +1 -0
- package/dist/connectors/telegram/fileIntake.js +304 -0
- package/dist/connectors/telegram/fileIntake.js.map +1 -0
- package/dist/connectors/telegram/fileSendGateway.d.ts +57 -0
- package/dist/connectors/telegram/fileSendGateway.d.ts.map +1 -0
- package/dist/connectors/telegram/fileSendGateway.js +221 -0
- package/dist/connectors/telegram/fileSendGateway.js.map +1 -0
- package/dist/connectors/telegram/foreignKeyFallbacks.d.ts +52 -0
- package/dist/connectors/telegram/foreignKeyFallbacks.d.ts.map +1 -0
- package/dist/connectors/telegram/foreignKeyFallbacks.js +71 -0
- package/dist/connectors/telegram/foreignKeyFallbacks.js.map +1 -0
- package/dist/connectors/telegram/inbound.d.ts +131 -0
- package/dist/connectors/telegram/inbound.d.ts.map +1 -0
- package/dist/connectors/telegram/inbound.js +278 -0
- package/dist/connectors/telegram/inbound.js.map +1 -0
- package/dist/connectors/telegram/languagePicker.d.ts +32 -0
- package/dist/connectors/telegram/languagePicker.d.ts.map +1 -0
- package/dist/connectors/telegram/languagePicker.js +55 -0
- package/dist/connectors/telegram/languagePicker.js.map +1 -0
- package/dist/connectors/telegram/messageSplit.d.ts +62 -0
- package/dist/connectors/telegram/messageSplit.d.ts.map +1 -0
- package/dist/connectors/telegram/messageSplit.js +119 -0
- package/dist/connectors/telegram/messageSplit.js.map +1 -0
- package/dist/connectors/telegram/outbound.d.ts +62 -0
- package/dist/connectors/telegram/outbound.d.ts.map +1 -0
- package/dist/connectors/telegram/outbound.js +116 -0
- package/dist/connectors/telegram/outbound.js.map +1 -0
- package/dist/connectors/telegram/output/createOutputTransport.d.ts +34 -0
- package/dist/connectors/telegram/output/createOutputTransport.d.ts.map +1 -0
- package/dist/connectors/telegram/output/createOutputTransport.js +77 -0
- package/dist/connectors/telegram/output/createOutputTransport.js.map +1 -0
- package/dist/connectors/telegram/output/dmOutputTransport.d.ts +47 -0
- package/dist/connectors/telegram/output/dmOutputTransport.d.ts.map +1 -0
- package/dist/connectors/telegram/output/dmOutputTransport.js +412 -0
- package/dist/connectors/telegram/output/dmOutputTransport.js.map +1 -0
- package/dist/connectors/telegram/outputBacklogGlue.d.ts +38 -0
- package/dist/connectors/telegram/outputBacklogGlue.d.ts.map +1 -0
- package/dist/connectors/telegram/outputBacklogGlue.js +49 -0
- package/dist/connectors/telegram/outputBacklogGlue.js.map +1 -0
- package/dist/connectors/telegram/outputFlushPlan.d.ts +93 -0
- package/dist/connectors/telegram/outputFlushPlan.d.ts.map +1 -0
- package/dist/connectors/telegram/outputFlushPlan.js +120 -0
- package/dist/connectors/telegram/outputFlushPlan.js.map +1 -0
- package/dist/connectors/telegram/questionKeyboards.d.ts +30 -0
- package/dist/connectors/telegram/questionKeyboards.d.ts.map +1 -0
- package/dist/connectors/telegram/questionKeyboards.js +37 -0
- package/dist/connectors/telegram/questionKeyboards.js.map +1 -0
- package/dist/connectors/telegram/renderAgentHtml.d.ts +27 -0
- package/dist/connectors/telegram/renderAgentHtml.d.ts.map +1 -0
- package/dist/connectors/telegram/renderAgentHtml.js +97 -0
- package/dist/connectors/telegram/renderAgentHtml.js.map +1 -0
- package/dist/connectors/telegram/sessionKeyCodec.d.ts +28 -0
- package/dist/connectors/telegram/sessionKeyCodec.d.ts.map +1 -0
- package/dist/connectors/telegram/sessionKeyCodec.js +107 -0
- package/dist/connectors/telegram/sessionKeyCodec.js.map +1 -0
- package/dist/connectors/telegram/telegramCallGuard.d.ts +19 -0
- package/dist/connectors/telegram/telegramCallGuard.d.ts.map +1 -0
- package/dist/connectors/telegram/telegramCallGuard.js +35 -0
- package/dist/connectors/telegram/telegramCallGuard.js.map +1 -0
- package/dist/connectors/telegram/timezonePicker.d.ts +85 -0
- package/dist/connectors/telegram/timezonePicker.d.ts.map +1 -0
- package/dist/connectors/telegram/timezonePicker.js +142 -0
- package/dist/connectors/telegram/timezonePicker.js.map +1 -0
- package/dist/connectors/telegram/updateDispatcher.d.ts +85 -0
- package/dist/connectors/telegram/updateDispatcher.d.ts.map +1 -0
- package/dist/connectors/telegram/updateDispatcher.js +159 -0
- package/dist/connectors/telegram/updateDispatcher.js.map +1 -0
- package/dist/connectors/test/connector.d.ts +52 -0
- package/dist/connectors/test/connector.d.ts.map +1 -0
- package/dist/connectors/test/connector.js +143 -0
- package/dist/connectors/test/connector.js.map +1 -0
- package/dist/connectors/test/sessionKeyCodec.d.ts +25 -0
- package/dist/connectors/test/sessionKeyCodec.d.ts.map +1 -0
- package/dist/connectors/test/sessionKeyCodec.js +50 -0
- package/dist/connectors/test/sessionKeyCodec.js.map +1 -0
- package/dist/diagLog.d.ts +6 -0
- package/dist/diagLog.d.ts.map +1 -0
- package/dist/diagLog.js +58 -0
- package/dist/diagLog.js.map +1 -0
- package/dist/effortLevels.d.ts +68 -0
- package/dist/effortLevels.d.ts.map +1 -0
- package/dist/effortLevels.js +114 -0
- package/dist/effortLevels.js.map +1 -0
- package/dist/folderName.d.ts +42 -0
- package/dist/folderName.d.ts.map +1 -0
- package/dist/folderName.js +51 -0
- package/dist/folderName.js.map +1 -0
- package/dist/i18n/de.d.ts +2 -0
- package/dist/i18n/de.d.ts.map +1 -0
- package/dist/i18n/de.js +537 -0
- package/dist/i18n/de.js.map +1 -0
- package/dist/i18n/en.d.ts +7 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +566 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/es.d.ts +2 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +537 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/fr.d.ts +2 -0
- package/dist/i18n/fr.d.ts.map +1 -0
- package/dist/i18n/fr.js +537 -0
- package/dist/i18n/fr.js.map +1 -0
- package/dist/i18n/hi.d.ts +2 -0
- package/dist/i18n/hi.d.ts.map +1 -0
- package/dist/i18n/hi.js +537 -0
- package/dist/i18n/hi.js.map +1 -0
- package/dist/i18n/ja.d.ts +2 -0
- package/dist/i18n/ja.d.ts.map +1 -0
- package/dist/i18n/ja.js +537 -0
- package/dist/i18n/ja.js.map +1 -0
- package/dist/i18n/ka.d.ts +2 -0
- package/dist/i18n/ka.d.ts.map +1 -0
- package/dist/i18n/ka.js +537 -0
- package/dist/i18n/ka.js.map +1 -0
- package/dist/i18n/pt.d.ts +2 -0
- package/dist/i18n/pt.d.ts.map +1 -0
- package/dist/i18n/pt.js +537 -0
- package/dist/i18n/pt.js.map +1 -0
- package/dist/i18n/ru.d.ts +6 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +540 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/i18n/uk.d.ts +8 -0
- package/dist/i18n/uk.d.ts.map +1 -0
- package/dist/i18n/uk.js +542 -0
- package/dist/i18n/uk.js.map +1 -0
- package/dist/i18n/uz.d.ts +2 -0
- package/dist/i18n/uz.d.ts.map +1 -0
- package/dist/i18n/uz.js +537 -0
- package/dist/i18n/uz.js.map +1 -0
- package/dist/i18n/zh.d.ts +2 -0
- package/dist/i18n/zh.d.ts.map +1 -0
- package/dist/i18n/zh.js +537 -0
- package/dist/i18n/zh.js.map +1 -0
- package/dist/i18n.d.ts +115 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +233 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/installManager.d.ts +99 -0
- package/dist/installManager.d.ts.map +1 -0
- package/dist/installManager.js +943 -0
- package/dist/installManager.js.map +1 -0
- package/dist/instanceLock.d.ts +161 -0
- package/dist/instanceLock.d.ts.map +1 -0
- package/dist/instanceLock.js +388 -0
- package/dist/instanceLock.js.map +1 -0
- package/dist/mcpConfig.d.ts +76 -0
- package/dist/mcpConfig.d.ts.map +1 -0
- package/dist/mcpConfig.js +270 -0
- package/dist/mcpConfig.js.map +1 -0
- package/dist/openCodeOpenQuestion.d.ts +44 -0
- package/dist/openCodeOpenQuestion.d.ts.map +1 -0
- package/dist/openCodeOpenQuestion.js +64 -0
- package/dist/openCodeOpenQuestion.js.map +1 -0
- package/dist/openCodeQuestionFlow.d.ts +105 -0
- package/dist/openCodeQuestionFlow.d.ts.map +1 -0
- package/dist/openCodeQuestionFlow.js +143 -0
- package/dist/openCodeQuestionFlow.js.map +1 -0
- package/dist/openCodeSessionRouting.d.ts +133 -0
- package/dist/openCodeSessionRouting.d.ts.map +1 -0
- package/dist/openCodeSessionRouting.js +253 -0
- package/dist/openCodeSessionRouting.js.map +1 -0
- package/dist/openCodeSessionTitle.d.ts +50 -0
- package/dist/openCodeSessionTitle.d.ts.map +1 -0
- package/dist/openCodeSessionTitle.js +84 -0
- package/dist/openCodeSessionTitle.js.map +1 -0
- package/dist/outputTrace.d.ts +108 -0
- package/dist/outputTrace.d.ts.map +1 -0
- package/dist/outputTrace.js +430 -0
- package/dist/outputTrace.js.map +1 -0
- package/dist/pendingQuestionRepost.d.ts +47 -0
- package/dist/pendingQuestionRepost.d.ts.map +1 -0
- package/dist/pendingQuestionRepost.js +29 -0
- package/dist/pendingQuestionRepost.js.map +1 -0
- package/dist/pinnedStatus.d.ts +52 -0
- package/dist/pinnedStatus.d.ts.map +1 -0
- package/dist/pinnedStatus.js +36 -0
- package/dist/pinnedStatus.js.map +1 -0
- package/dist/platform/answerSink.d.ts +93 -0
- package/dist/platform/answerSink.d.ts.map +1 -0
- package/dist/platform/answerSink.js +31 -0
- package/dist/platform/answerSink.js.map +1 -0
- package/dist/platform/capabilityFallback.d.ts +39 -0
- package/dist/platform/capabilityFallback.d.ts.map +1 -0
- package/dist/platform/capabilityFallback.js +55 -0
- package/dist/platform/capabilityFallback.js.map +1 -0
- package/dist/platform/commandRouter.d.ts +51 -0
- package/dist/platform/commandRouter.d.ts.map +1 -0
- package/dist/platform/commandRouter.js +53 -0
- package/dist/platform/commandRouter.js.map +1 -0
- package/dist/platform/connectorSet.d.ts +39 -0
- package/dist/platform/connectorSet.d.ts.map +1 -0
- package/dist/platform/connectorSet.js +55 -0
- package/dist/platform/connectorSet.js.map +1 -0
- package/dist/platform/inbound.d.ts +147 -0
- package/dist/platform/inbound.d.ts.map +1 -0
- package/dist/platform/inbound.js +18 -0
- package/dist/platform/inbound.js.map +1 -0
- package/dist/platform/outbound.d.ts +225 -0
- package/dist/platform/outbound.d.ts.map +1 -0
- package/dist/platform/outbound.js +30 -0
- package/dist/platform/outbound.js.map +1 -0
- package/dist/platform/unservedStateGuard.d.ts +27 -0
- package/dist/platform/unservedStateGuard.d.ts.map +1 -0
- package/dist/platform/unservedStateGuard.js +42 -0
- package/dist/platform/unservedStateGuard.js.map +1 -0
- package/dist/postToSession.d.ts +94 -0
- package/dist/postToSession.d.ts.map +1 -0
- package/dist/postToSession.js +56 -0
- package/dist/postToSession.js.map +1 -0
- package/dist/progressLine.d.ts +180 -0
- package/dist/progressLine.d.ts.map +1 -0
- package/dist/progressLine.js +293 -0
- package/dist/progressLine.js.map +1 -0
- package/dist/rateLimiter.d.ts +246 -0
- package/dist/rateLimiter.d.ts.map +1 -0
- package/dist/rateLimiter.js +555 -0
- package/dist/rateLimiter.js.map +1 -0
- package/dist/requests/answerRequest.d.ts +57 -0
- package/dist/requests/answerRequest.d.ts.map +1 -0
- package/dist/requests/answerRequest.js +96 -0
- package/dist/requests/answerRequest.js.map +1 -0
- package/dist/requests/answerTail.d.ts +83 -0
- package/dist/requests/answerTail.d.ts.map +1 -0
- package/dist/requests/answerTail.js +102 -0
- package/dist/requests/answerTail.js.map +1 -0
- package/dist/requests/limitWaitAnswer.d.ts +87 -0
- package/dist/requests/limitWaitAnswer.d.ts.map +1 -0
- package/dist/requests/limitWaitAnswer.js +83 -0
- package/dist/requests/limitWaitAnswer.js.map +1 -0
- package/dist/requests/requestGroup.d.ts +44 -0
- package/dist/requests/requestGroup.d.ts.map +1 -0
- package/dist/requests/requestGroup.js +63 -0
- package/dist/requests/requestGroup.js.map +1 -0
- package/dist/requests/requestHeader.d.ts +70 -0
- package/dist/requests/requestHeader.d.ts.map +1 -0
- package/dist/requests/requestHeader.js +83 -0
- package/dist/requests/requestHeader.js.map +1 -0
- package/dist/requests/requestLedger.d.ts +217 -0
- package/dist/requests/requestLedger.d.ts.map +1 -0
- package/dist/requests/requestLedger.js +468 -0
- package/dist/requests/requestLedger.js.map +1 -0
- package/dist/requests/sessionTurnProbe.d.ts +52 -0
- package/dist/requests/sessionTurnProbe.d.ts.map +1 -0
- package/dist/requests/sessionTurnProbe.js +29 -0
- package/dist/requests/sessionTurnProbe.js.map +1 -0
- package/dist/requests/types.d.ts +181 -0
- package/dist/requests/types.d.ts.map +1 -0
- package/dist/requests/types.js +9 -0
- package/dist/requests/types.js.map +1 -0
- package/dist/requests/wakeUpEngine.d.ts +160 -0
- package/dist/requests/wakeUpEngine.d.ts.map +1 -0
- package/dist/requests/wakeUpEngine.js +399 -0
- package/dist/requests/wakeUpEngine.js.map +1 -0
- package/dist/requests/wakeUpRules.d.ts +151 -0
- package/dist/requests/wakeUpRules.d.ts.map +1 -0
- package/dist/requests/wakeUpRules.js +167 -0
- package/dist/requests/wakeUpRules.js.map +1 -0
- package/dist/resumeContext.d.ts +87 -0
- package/dist/resumeContext.d.ts.map +1 -0
- package/dist/resumeContext.js +114 -0
- package/dist/resumeContext.js.map +1 -0
- package/dist/scheduler/checkRun.d.ts +95 -0
- package/dist/scheduler/checkRun.d.ts.map +1 -0
- package/dist/scheduler/checkRun.js +172 -0
- package/dist/scheduler/checkRun.js.map +1 -0
- package/dist/scheduler/delivery.d.ts +99 -0
- package/dist/scheduler/delivery.d.ts.map +1 -0
- package/dist/scheduler/delivery.js +212 -0
- package/dist/scheduler/delivery.js.map +1 -0
- package/dist/scheduler/deliveryKind.d.ts +29 -0
- package/dist/scheduler/deliveryKind.d.ts.map +1 -0
- package/dist/scheduler/deliveryKind.js +39 -0
- package/dist/scheduler/deliveryKind.js.map +1 -0
- package/dist/scheduler/directoryThreads.d.ts +19 -0
- package/dist/scheduler/directoryThreads.d.ts.map +1 -0
- package/dist/scheduler/directoryThreads.js +28 -0
- package/dist/scheduler/directoryThreads.js.map +1 -0
- package/dist/scheduler/engine.d.ts +77 -0
- package/dist/scheduler/engine.d.ts.map +1 -0
- package/dist/scheduler/engine.js +337 -0
- package/dist/scheduler/engine.js.map +1 -0
- package/dist/scheduler/injection.d.ts +102 -0
- package/dist/scheduler/injection.d.ts.map +1 -0
- package/dist/scheduler/injection.js +122 -0
- package/dist/scheduler/injection.js.map +1 -0
- package/dist/scheduler/mcpBoot.d.ts +65 -0
- package/dist/scheduler/mcpBoot.d.ts.map +1 -0
- package/dist/scheduler/mcpBoot.js +60 -0
- package/dist/scheduler/mcpBoot.js.map +1 -0
- package/dist/scheduler/mcpSurface.d.ts +355 -0
- package/dist/scheduler/mcpSurface.d.ts.map +1 -0
- package/dist/scheduler/mcpSurface.js +1257 -0
- package/dist/scheduler/mcpSurface.js.map +1 -0
- package/dist/scheduler/rebindResume.d.ts +27 -0
- package/dist/scheduler/rebindResume.d.ts.map +1 -0
- package/dist/scheduler/rebindResume.js +17 -0
- package/dist/scheduler/rebindResume.js.map +1 -0
- package/dist/scheduler/recurrence.d.ts +58 -0
- package/dist/scheduler/recurrence.d.ts.map +1 -0
- package/dist/scheduler/recurrence.js +210 -0
- package/dist/scheduler/recurrence.js.map +1 -0
- package/dist/scheduler/runLedger.d.ts +59 -0
- package/dist/scheduler/runLedger.d.ts.map +1 -0
- package/dist/scheduler/runLedger.js +54 -0
- package/dist/scheduler/runLedger.js.map +1 -0
- package/dist/scheduler/store.d.ts +97 -0
- package/dist/scheduler/store.d.ts.map +1 -0
- package/dist/scheduler/store.js +124 -0
- package/dist/scheduler/store.js.map +1 -0
- package/dist/scheduler/timezoneRecompute.d.ts +61 -0
- package/dist/scheduler/timezoneRecompute.d.ts.map +1 -0
- package/dist/scheduler/timezoneRecompute.js +46 -0
- package/dist/scheduler/timezoneRecompute.js.map +1 -0
- package/dist/scheduler/types.d.ts +139 -0
- package/dist/scheduler/types.d.ts.map +1 -0
- package/dist/scheduler/types.js +8 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/sendErrorClassifier.d.ts +45 -0
- package/dist/sendErrorClassifier.d.ts.map +1 -0
- package/dist/sendErrorClassifier.js +65 -0
- package/dist/sendErrorClassifier.js.map +1 -0
- package/dist/sessionKey.d.ts +136 -0
- package/dist/sessionKey.d.ts.map +1 -0
- package/dist/sessionKey.js +153 -0
- package/dist/sessionKey.js.map +1 -0
- package/dist/sessionPick.d.ts +42 -0
- package/dist/sessionPick.d.ts.map +1 -0
- package/dist/sessionPick.js +39 -0
- package/dist/sessionPick.js.map +1 -0
- package/dist/shutdown.d.ts +126 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +164 -0
- package/dist/shutdown.js.map +1 -0
- package/dist/startupPromptBuffer.d.ts +85 -0
- package/dist/startupPromptBuffer.d.ts.map +1 -0
- package/dist/startupPromptBuffer.js +123 -0
- package/dist/startupPromptBuffer.js.map +1 -0
- package/dist/state.d.ts +1245 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +1830 -0
- package/dist/state.js.map +1 -0
- package/dist/threadContextPreamble.d.ts +89 -0
- package/dist/threadContextPreamble.d.ts.map +1 -0
- package/dist/threadContextPreamble.js +102 -0
- package/dist/threadContextPreamble.js.map +1 -0
- package/dist/threadRouting.d.ts +164 -0
- package/dist/threadRouting.d.ts.map +1 -0
- package/dist/threadRouting.js +182 -0
- package/dist/threadRouting.js.map +1 -0
- package/dist/types.d.ts +1015 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/abortableFifo.d.ts +10 -0
- package/dist/utils/abortableFifo.d.ts.map +1 -0
- package/dist/utils/abortableFifo.js +55 -0
- package/dist/utils/abortableFifo.js.map +1 -0
- package/dist/utils/adoptedToolList.d.ts +27 -0
- package/dist/utils/adoptedToolList.d.ts.map +1 -0
- package/dist/utils/adoptedToolList.js +22 -0
- package/dist/utils/adoptedToolList.js.map +1 -0
- package/dist/utils/agentEnvironment.d.ts +42 -0
- package/dist/utils/agentEnvironment.d.ts.map +1 -0
- package/dist/utils/agentEnvironment.js +122 -0
- package/dist/utils/agentEnvironment.js.map +1 -0
- package/dist/utils/ansiClean.d.ts +11 -0
- package/dist/utils/ansiClean.d.ts.map +1 -0
- package/dist/utils/ansiClean.js +139 -0
- package/dist/utils/ansiClean.js.map +1 -0
- package/dist/utils/autoContinueOnLimit.d.ts +49 -0
- package/dist/utils/autoContinueOnLimit.d.ts.map +1 -0
- package/dist/utils/autoContinueOnLimit.js +69 -0
- package/dist/utils/autoContinueOnLimit.js.map +1 -0
- package/dist/utils/canonicalPathContainment.d.ts +7 -0
- package/dist/utils/canonicalPathContainment.d.ts.map +1 -0
- package/dist/utils/canonicalPathContainment.js +48 -0
- package/dist/utils/canonicalPathContainment.js.map +1 -0
- package/dist/utils/claudeAuthLogin.d.ts +71 -0
- package/dist/utils/claudeAuthLogin.d.ts.map +1 -0
- package/dist/utils/claudeAuthLogin.js +93 -0
- package/dist/utils/claudeAuthLogin.js.map +1 -0
- package/dist/utils/claudeChunkClassifier.d.ts +97 -0
- package/dist/utils/claudeChunkClassifier.d.ts.map +1 -0
- package/dist/utils/claudeChunkClassifier.js +254 -0
- package/dist/utils/claudeChunkClassifier.js.map +1 -0
- package/dist/utils/claudeCodeVersion.d.ts +31 -0
- package/dist/utils/claudeCodeVersion.d.ts.map +1 -0
- package/dist/utils/claudeCodeVersion.js +62 -0
- package/dist/utils/claudeCodeVersion.js.map +1 -0
- package/dist/utils/claudeCompactHook.d.ts +64 -0
- package/dist/utils/claudeCompactHook.d.ts.map +1 -0
- package/dist/utils/claudeCompactHook.js +98 -0
- package/dist/utils/claudeCompactHook.js.map +1 -0
- package/dist/utils/claudeLivenessAction.d.ts +208 -0
- package/dist/utils/claudeLivenessAction.d.ts.map +1 -0
- package/dist/utils/claudeLivenessAction.js +135 -0
- package/dist/utils/claudeLivenessAction.js.map +1 -0
- package/dist/utils/claudeMcpHeal.d.ts +92 -0
- package/dist/utils/claudeMcpHeal.d.ts.map +1 -0
- package/dist/utils/claudeMcpHeal.js +91 -0
- package/dist/utils/claudeMcpHeal.js.map +1 -0
- package/dist/utils/claudeRelayRouting.d.ts +86 -0
- package/dist/utils/claudeRelayRouting.d.ts.map +1 -0
- package/dist/utils/claudeRelayRouting.js +159 -0
- package/dist/utils/claudeRelayRouting.js.map +1 -0
- package/dist/utils/claudeRuntimeInfo.d.ts +26 -0
- package/dist/utils/claudeRuntimeInfo.d.ts.map +1 -0
- package/dist/utils/claudeRuntimeInfo.js +151 -0
- package/dist/utils/claudeRuntimeInfo.js.map +1 -0
- package/dist/utils/claudeScrapeShapes.d.ts +256 -0
- package/dist/utils/claudeScrapeShapes.d.ts.map +1 -0
- package/dist/utils/claudeScrapeShapes.js +290 -0
- package/dist/utils/claudeScrapeShapes.js.map +1 -0
- package/dist/utils/claudeSessionTitle.d.ts +27 -0
- package/dist/utils/claudeSessionTitle.d.ts.map +1 -0
- package/dist/utils/claudeSessionTitle.js +90 -0
- package/dist/utils/claudeSessionTitle.js.map +1 -0
- package/dist/utils/claudeStreamJson.d.ts +202 -0
- package/dist/utils/claudeStreamJson.d.ts.map +1 -0
- package/dist/utils/claudeStreamJson.js +351 -0
- package/dist/utils/claudeStreamJson.js.map +1 -0
- package/dist/utils/claudeSubagentTail.d.ts +93 -0
- package/dist/utils/claudeSubagentTail.d.ts.map +1 -0
- package/dist/utils/claudeSubagentTail.js +136 -0
- package/dist/utils/claudeSubagentTail.js.map +1 -0
- package/dist/utils/clearThreadOutputQueues.d.ts +43 -0
- package/dist/utils/clearThreadOutputQueues.d.ts.map +1 -0
- package/dist/utils/clearThreadOutputQueues.js +39 -0
- package/dist/utils/clearThreadOutputQueues.js.map +1 -0
- package/dist/utils/compactCommandRoute.d.ts +45 -0
- package/dist/utils/compactCommandRoute.d.ts.map +1 -0
- package/dist/utils/compactCommandRoute.js +36 -0
- package/dist/utils/compactCommandRoute.js.map +1 -0
- package/dist/utils/compactOnIdle.d.ts +329 -0
- package/dist/utils/compactOnIdle.d.ts.map +1 -0
- package/dist/utils/compactOnIdle.js +379 -0
- package/dist/utils/compactOnIdle.js.map +1 -0
- package/dist/utils/consoleFileTap.d.ts +51 -0
- package/dist/utils/consoleFileTap.d.ts.map +1 -0
- package/dist/utils/consoleFileTap.js +86 -0
- package/dist/utils/consoleFileTap.js.map +1 -0
- package/dist/utils/displayVerbosity.d.ts +44 -0
- package/dist/utils/displayVerbosity.d.ts.map +1 -0
- package/dist/utils/displayVerbosity.js +75 -0
- package/dist/utils/displayVerbosity.js.map +1 -0
- package/dist/utils/download.d.ts +72 -0
- package/dist/utils/download.d.ts.map +1 -0
- package/dist/utils/download.js +216 -0
- package/dist/utils/download.js.map +1 -0
- package/dist/utils/draftFinalize.d.ts +110 -0
- package/dist/utils/draftFinalize.d.ts.map +1 -0
- package/dist/utils/draftFinalize.js +100 -0
- package/dist/utils/draftFinalize.js.map +1 -0
- package/dist/utils/draftId.d.ts +36 -0
- package/dist/utils/draftId.d.ts.map +1 -0
- package/dist/utils/draftId.js +46 -0
- package/dist/utils/draftId.js.map +1 -0
- package/dist/utils/draftPacer.d.ts +72 -0
- package/dist/utils/draftPacer.d.ts.map +1 -0
- package/dist/utils/draftPacer.js +53 -0
- package/dist/utils/draftPacer.js.map +1 -0
- package/dist/utils/effortStartupKeystroke.d.ts +20 -0
- package/dist/utils/effortStartupKeystroke.d.ts.map +1 -0
- package/dist/utils/effortStartupKeystroke.js +28 -0
- package/dist/utils/effortStartupKeystroke.js.map +1 -0
- package/dist/utils/ensureSessionPlan.d.ts +70 -0
- package/dist/utils/ensureSessionPlan.d.ts.map +1 -0
- package/dist/utils/ensureSessionPlan.js +54 -0
- package/dist/utils/ensureSessionPlan.js.map +1 -0
- package/dist/utils/fileSendPlan.d.ts +217 -0
- package/dist/utils/fileSendPlan.d.ts.map +1 -0
- package/dist/utils/fileSendPlan.js +291 -0
- package/dist/utils/fileSendPlan.js.map +1 -0
- package/dist/utils/fileSendService.d.ts +94 -0
- package/dist/utils/fileSendService.d.ts.map +1 -0
- package/dist/utils/fileSendService.js +387 -0
- package/dist/utils/fileSendService.js.map +1 -0
- package/dist/utils/groupFinalizePlan.d.ts +38 -0
- package/dist/utils/groupFinalizePlan.d.ts.map +1 -0
- package/dist/utils/groupFinalizePlan.js +35 -0
- package/dist/utils/groupFinalizePlan.js.map +1 -0
- package/dist/utils/identicalOutputGuard.d.ts +83 -0
- package/dist/utils/identicalOutputGuard.d.ts.map +1 -0
- package/dist/utils/identicalOutputGuard.js +99 -0
- package/dist/utils/identicalOutputGuard.js.map +1 -0
- package/dist/utils/isoTimestamp.d.ts +16 -0
- package/dist/utils/isoTimestamp.d.ts.map +1 -0
- package/dist/utils/isoTimestamp.js +34 -0
- package/dist/utils/isoTimestamp.js.map +1 -0
- package/dist/utils/jsonStreamBusyWatchdog.d.ts +130 -0
- package/dist/utils/jsonStreamBusyWatchdog.d.ts.map +1 -0
- package/dist/utils/jsonStreamBusyWatchdog.js +122 -0
- package/dist/utils/jsonStreamBusyWatchdog.js.map +1 -0
- package/dist/utils/jsonStreamHost.d.ts +227 -0
- package/dist/utils/jsonStreamHost.d.ts.map +1 -0
- package/dist/utils/jsonStreamHost.js +417 -0
- package/dist/utils/jsonStreamHost.js.map +1 -0
- package/dist/utils/keyedTransitionQueue.d.ts +18 -0
- package/dist/utils/keyedTransitionQueue.d.ts.map +1 -0
- package/dist/utils/keyedTransitionQueue.js +34 -0
- package/dist/utils/keyedTransitionQueue.js.map +1 -0
- package/dist/utils/limitEpisodeRecovery.d.ts +92 -0
- package/dist/utils/limitEpisodeRecovery.d.ts.map +1 -0
- package/dist/utils/limitEpisodeRecovery.js +133 -0
- package/dist/utils/limitEpisodeRecovery.js.map +1 -0
- package/dist/utils/limitHeldPromptQueue.d.ts +48 -0
- package/dist/utils/limitHeldPromptQueue.d.ts.map +1 -0
- package/dist/utils/limitHeldPromptQueue.js +59 -0
- package/dist/utils/limitHeldPromptQueue.js.map +1 -0
- package/dist/utils/limitHeldPrompts.d.ts +58 -0
- package/dist/utils/limitHeldPrompts.d.ts.map +1 -0
- package/dist/utils/limitHeldPrompts.js +65 -0
- package/dist/utils/limitHeldPrompts.js.map +1 -0
- package/dist/utils/linkPreviewSuppression.d.ts +18 -0
- package/dist/utils/linkPreviewSuppression.d.ts.map +1 -0
- package/dist/utils/linkPreviewSuppression.js +37 -0
- package/dist/utils/linkPreviewSuppression.js.map +1 -0
- package/dist/utils/localClock.d.ts +15 -0
- package/dist/utils/localClock.d.ts.map +1 -0
- package/dist/utils/localClock.js +29 -0
- package/dist/utils/localClock.js.map +1 -0
- package/dist/utils/markdownTableRender.d.ts +83 -0
- package/dist/utils/markdownTableRender.d.ts.map +1 -0
- package/dist/utils/markdownTableRender.js +298 -0
- package/dist/utils/markdownTableRender.js.map +1 -0
- package/dist/utils/mediaGroupCollector.d.ts +51 -0
- package/dist/utils/mediaGroupCollector.d.ts.map +1 -0
- package/dist/utils/mediaGroupCollector.js +90 -0
- package/dist/utils/mediaGroupCollector.js.map +1 -0
- package/dist/utils/messageSendService.d.ts +136 -0
- package/dist/utils/messageSendService.d.ts.map +1 -0
- package/dist/utils/messageSendService.js +165 -0
- package/dist/utils/messageSendService.js.map +1 -0
- package/dist/utils/minutesOverride.d.ts +7 -0
- package/dist/utils/minutesOverride.d.ts.map +1 -0
- package/dist/utils/minutesOverride.js +16 -0
- package/dist/utils/minutesOverride.js.map +1 -0
- package/dist/utils/modelPickerPlan.d.ts +119 -0
- package/dist/utils/modelPickerPlan.d.ts.map +1 -0
- package/dist/utils/modelPickerPlan.js +171 -0
- package/dist/utils/modelPickerPlan.js.map +1 -0
- package/dist/utils/modelSetReplyDecision.d.ts +67 -0
- package/dist/utils/modelSetReplyDecision.d.ts.map +1 -0
- package/dist/utils/modelSetReplyDecision.js +27 -0
- package/dist/utils/modelSetReplyDecision.js.map +1 -0
- package/dist/utils/openCodeAuthLogin.d.ts +181 -0
- package/dist/utils/openCodeAuthLogin.d.ts.map +1 -0
- package/dist/utils/openCodeAuthLogin.js +393 -0
- package/dist/utils/openCodeAuthLogin.js.map +1 -0
- package/dist/utils/openCodeCompactPlugin.d.ts +81 -0
- package/dist/utils/openCodeCompactPlugin.d.ts.map +1 -0
- package/dist/utils/openCodeCompactPlugin.js +146 -0
- package/dist/utils/openCodeCompactPlugin.js.map +1 -0
- package/dist/utils/openCodeTurnActivity.d.ts +67 -0
- package/dist/utils/openCodeTurnActivity.d.ts.map +1 -0
- package/dist/utils/openCodeTurnActivity.js +27 -0
- package/dist/utils/openCodeTurnActivity.js.map +1 -0
- package/dist/utils/outputDebounce.d.ts +24 -0
- package/dist/utils/outputDebounce.d.ts.map +1 -0
- package/dist/utils/outputDebounce.js +30 -0
- package/dist/utils/outputDebounce.js.map +1 -0
- package/dist/utils/outputFlushTiming.d.ts +54 -0
- package/dist/utils/outputFlushTiming.d.ts.map +1 -0
- package/dist/utils/outputFlushTiming.js +48 -0
- package/dist/utils/outputFlushTiming.js.map +1 -0
- package/dist/utils/paginateList.d.ts +25 -0
- package/dist/utils/paginateList.d.ts.map +1 -0
- package/dist/utils/paginateList.js +32 -0
- package/dist/utils/paginateList.js.map +1 -0
- package/dist/utils/paneDiff.d.ts +55 -0
- package/dist/utils/paneDiff.d.ts.map +1 -0
- package/dist/utils/paneDiff.js +88 -0
- package/dist/utils/paneDiff.js.map +1 -0
- package/dist/utils/paneResizeGuard.d.ts +69 -0
- package/dist/utils/paneResizeGuard.d.ts.map +1 -0
- package/dist/utils/paneResizeGuard.js +91 -0
- package/dist/utils/paneResizeGuard.js.map +1 -0
- package/dist/utils/persistAdapterSessionIds.d.ts +39 -0
- package/dist/utils/persistAdapterSessionIds.d.ts.map +1 -0
- package/dist/utils/persistAdapterSessionIds.js +28 -0
- package/dist/utils/persistAdapterSessionIds.js.map +1 -0
- package/dist/utils/pinnedBannerSkipDecision.d.ts +44 -0
- package/dist/utils/pinnedBannerSkipDecision.d.ts.map +1 -0
- package/dist/utils/pinnedBannerSkipDecision.js +20 -0
- package/dist/utils/pinnedBannerSkipDecision.js.map +1 -0
- package/dist/utils/pollBackoff.d.ts +45 -0
- package/dist/utils/pollBackoff.d.ts.map +1 -0
- package/dist/utils/pollBackoff.js +44 -0
- package/dist/utils/pollBackoff.js.map +1 -0
- package/dist/utils/promptDelivery.d.ts +46 -0
- package/dist/utils/promptDelivery.d.ts.map +1 -0
- package/dist/utils/promptDelivery.js +49 -0
- package/dist/utils/promptDelivery.js.map +1 -0
- package/dist/utils/providerDisconnectPlan.d.ts +82 -0
- package/dist/utils/providerDisconnectPlan.d.ts.map +1 -0
- package/dist/utils/providerDisconnectPlan.js +110 -0
- package/dist/utils/providerDisconnectPlan.js.map +1 -0
- package/dist/utils/rateLimitLog.d.ts +48 -0
- package/dist/utils/rateLimitLog.d.ts.map +1 -0
- package/dist/utils/rateLimitLog.js +43 -0
- package/dist/utils/rateLimitLog.js.map +1 -0
- package/dist/utils/recentRelayWindow.d.ts +165 -0
- package/dist/utils/recentRelayWindow.d.ts.map +1 -0
- package/dist/utils/recentRelayWindow.js +284 -0
- package/dist/utils/recentRelayWindow.js.map +1 -0
- package/dist/utils/recvPreviewRedaction.d.ts +23 -0
- package/dist/utils/recvPreviewRedaction.d.ts.map +1 -0
- package/dist/utils/recvPreviewRedaction.js +39 -0
- package/dist/utils/recvPreviewRedaction.js.map +1 -0
- package/dist/utils/reminderFlow.d.ts +148 -0
- package/dist/utils/reminderFlow.d.ts.map +1 -0
- package/dist/utils/reminderFlow.js +122 -0
- package/dist/utils/reminderFlow.js.map +1 -0
- package/dist/utils/reminderScheduleText.d.ts +87 -0
- package/dist/utils/reminderScheduleText.d.ts.map +1 -0
- package/dist/utils/reminderScheduleText.js +135 -0
- package/dist/utils/reminderScheduleText.js.map +1 -0
- package/dist/utils/reminderWizard.d.ts +710 -0
- package/dist/utils/reminderWizard.d.ts.map +1 -0
- package/dist/utils/reminderWizard.js +1164 -0
- package/dist/utils/reminderWizard.js.map +1 -0
- package/dist/utils/replyQuote.d.ts +111 -0
- package/dist/utils/replyQuote.d.ts.map +1 -0
- package/dist/utils/replyQuote.js +122 -0
- package/dist/utils/replyQuote.js.map +1 -0
- package/dist/utils/resolveBinary.d.ts +25 -0
- package/dist/utils/resolveBinary.d.ts.map +1 -0
- package/dist/utils/resolveBinary.js +99 -0
- package/dist/utils/resolveBinary.js.map +1 -0
- package/dist/utils/retryAfterHeader.d.ts +7 -0
- package/dist/utils/retryAfterHeader.d.ts.map +1 -0
- package/dist/utils/retryAfterHeader.js +17 -0
- package/dist/utils/retryAfterHeader.js.map +1 -0
- package/dist/utils/rotatingJsonlFile.d.ts +40 -0
- package/dist/utils/rotatingJsonlFile.d.ts.map +1 -0
- package/dist/utils/rotatingJsonlFile.js +106 -0
- package/dist/utils/rotatingJsonlFile.js.map +1 -0
- package/dist/utils/rotatingLogFile.d.ts +25 -0
- package/dist/utils/rotatingLogFile.d.ts.map +1 -0
- package/dist/utils/rotatingLogFile.js +74 -0
- package/dist/utils/rotatingLogFile.js.map +1 -0
- package/dist/utils/sendRateTracker.d.ts +59 -0
- package/dist/utils/sendRateTracker.d.ts.map +1 -0
- package/dist/utils/sendRateTracker.js +144 -0
- package/dist/utils/sendRateTracker.js.map +1 -0
- package/dist/utils/serialQueue.d.ts +5 -0
- package/dist/utils/serialQueue.d.ts.map +1 -0
- package/dist/utils/serialQueue.js +14 -0
- package/dist/utils/serialQueue.js.map +1 -0
- package/dist/utils/sseStreamLifecycle.d.ts +32 -0
- package/dist/utils/sseStreamLifecycle.d.ts.map +1 -0
- package/dist/utils/sseStreamLifecycle.js +41 -0
- package/dist/utils/sseStreamLifecycle.js.map +1 -0
- package/dist/utils/startupReadiness.d.ts +119 -0
- package/dist/utils/startupReadiness.d.ts.map +1 -0
- package/dist/utils/startupReadiness.js +148 -0
- package/dist/utils/startupReadiness.js.map +1 -0
- package/dist/utils/statusFlushDecision.d.ts +18 -0
- package/dist/utils/statusFlushDecision.d.ts.map +1 -0
- package/dist/utils/statusFlushDecision.js +53 -0
- package/dist/utils/statusFlushDecision.js.map +1 -0
- package/dist/utils/subagentRender.d.ts +52 -0
- package/dist/utils/subagentRender.d.ts.map +1 -0
- package/dist/utils/subagentRender.js +66 -0
- package/dist/utils/subagentRender.js.map +1 -0
- package/dist/utils/subagentStatusRender.d.ts +90 -0
- package/dist/utils/subagentStatusRender.d.ts.map +1 -0
- package/dist/utils/subagentStatusRender.js +124 -0
- package/dist/utils/subagentStatusRender.js.map +1 -0
- package/dist/utils/telegramError.d.ts +2 -0
- package/dist/utils/telegramError.d.ts.map +1 -0
- package/dist/utils/telegramError.js +16 -0
- package/dist/utils/telegramError.js.map +1 -0
- package/dist/utils/terminalEmitPlan.d.ts +74 -0
- package/dist/utils/terminalEmitPlan.d.ts.map +1 -0
- package/dist/utils/terminalEmitPlan.js +86 -0
- package/dist/utils/terminalEmitPlan.js.map +1 -0
- package/dist/utils/thinkingRender.d.ts +74 -0
- package/dist/utils/thinkingRender.d.ts.map +1 -0
- package/dist/utils/thinkingRender.js +85 -0
- package/dist/utils/thinkingRender.js.map +1 -0
- package/dist/utils/threadStatusReport.d.ts +55 -0
- package/dist/utils/threadStatusReport.d.ts.map +1 -0
- package/dist/utils/threadStatusReport.js +65 -0
- package/dist/utils/threadStatusReport.js.map +1 -0
- package/dist/utils/threadToggle.d.ts +29 -0
- package/dist/utils/threadToggle.d.ts.map +1 -0
- package/dist/utils/threadToggle.js +39 -0
- package/dist/utils/threadToggle.js.map +1 -0
- package/dist/utils/timezone.d.ts +125 -0
- package/dist/utils/timezone.d.ts.map +1 -0
- package/dist/utils/timezone.js +278 -0
- package/dist/utils/timezone.js.map +1 -0
- package/dist/utils/tmuxExec.d.ts +61 -0
- package/dist/utils/tmuxExec.d.ts.map +1 -0
- package/dist/utils/tmuxExec.js +109 -0
- package/dist/utils/tmuxExec.js.map +1 -0
- package/dist/utils/tmuxSessionName.d.ts +34 -0
- package/dist/utils/tmuxSessionName.d.ts.map +1 -0
- package/dist/utils/tmuxSessionName.js +51 -0
- package/dist/utils/tmuxSessionName.js.map +1 -0
- package/dist/utils/toolResultRender.d.ts +61 -0
- package/dist/utils/toolResultRender.d.ts.map +1 -0
- package/dist/utils/toolResultRender.js +62 -0
- package/dist/utils/toolResultRender.js.map +1 -0
- package/dist/utils/topicRequest.d.ts +61 -0
- package/dist/utils/topicRequest.d.ts.map +1 -0
- package/dist/utils/topicRequest.js +68 -0
- package/dist/utils/topicRequest.js.map +1 -0
- package/dist/utils/topicView.d.ts +55 -0
- package/dist/utils/topicView.d.ts.map +1 -0
- package/dist/utils/topicView.js +56 -0
- package/dist/utils/topicView.js.map +1 -0
- package/dist/utils/transcribeAudio.d.ts +89 -0
- package/dist/utils/transcribeAudio.d.ts.map +1 -0
- package/dist/utils/transcribeAudio.js +249 -0
- package/dist/utils/transcribeAudio.js.map +1 -0
- package/dist/utils/transientFrames.d.ts +52 -0
- package/dist/utils/transientFrames.d.ts.map +1 -0
- package/dist/utils/transientFrames.js +41 -0
- package/dist/utils/transientFrames.js.map +1 -0
- package/dist/utils/typingActive.d.ts +35 -0
- package/dist/utils/typingActive.d.ts.map +1 -0
- package/dist/utils/typingActive.js +22 -0
- package/dist/utils/typingActive.js.map +1 -0
- package/dist/utils/typingLoopBackstop.d.ts +64 -0
- package/dist/utils/typingLoopBackstop.d.ts.map +1 -0
- package/dist/utils/typingLoopBackstop.js +68 -0
- package/dist/utils/typingLoopBackstop.js.map +1 -0
- package/dist/utils/verbosityRender.d.ts +18 -0
- package/dist/utils/verbosityRender.d.ts.map +1 -0
- package/dist/utils/verbosityRender.js +17 -0
- package/dist/utils/verbosityRender.js.map +1 -0
- package/dist/utils/wedgeRecovery.d.ts +57 -0
- package/dist/utils/wedgeRecovery.d.ts.map +1 -0
- package/dist/utils/wedgeRecovery.js +24 -0
- package/dist/utils/wedgeRecovery.js.map +1 -0
- package/dist/utils.d.ts +30 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +61 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation.d.ts +113 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +226 -0
- package/dist/validation.js.map +1 -0
- package/dist/voiceQueue.d.ts +7 -0
- package/dist/voiceQueue.d.ts.map +1 -0
- package/dist/voiceQueue.js +33 -0
- package/dist/voiceQueue.js.map +1 -0
- package/examples/.env.example +17 -0
- package/examples/docker-compose.yml +36 -0
- package/package.json +83 -4
|
@@ -0,0 +1,1810 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ClaudeJsonStreamAdapter = exports.claudeJsonStreamUsageLogPrefix = void 0;
|
|
37
|
+
exports.readLatestCompactSummaryFromTranscript = readLatestCompactSummaryFromTranscript;
|
|
38
|
+
const crypto_1 = require("crypto");
|
|
39
|
+
const events_1 = require("events");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const sessionKey_1 = require("../sessionKey");
|
|
42
|
+
const apiErrorRetry_1 = require("../apiErrorRetry");
|
|
43
|
+
const installManager_1 = require("../installManager");
|
|
44
|
+
const mcpConfig_1 = require("../mcpConfig");
|
|
45
|
+
const claudeCompactHook_1 = require("../utils/claudeCompactHook");
|
|
46
|
+
const claudePlatformFlags_1 = require("./claudePlatformFlags");
|
|
47
|
+
const state_1 = require("../state");
|
|
48
|
+
const resolveBinary_1 = require("../utils/resolveBinary");
|
|
49
|
+
const displayVerbosity_1 = require("../utils/displayVerbosity");
|
|
50
|
+
const i18n_1 = require("../i18n");
|
|
51
|
+
const resumeContext_1 = require("../resumeContext");
|
|
52
|
+
const effortLevels_1 = require("../effortLevels");
|
|
53
|
+
const claudeCliAdapter_1 = require("./claudeCliAdapter");
|
|
54
|
+
const claudeStreamJson_1 = require("../utils/claudeStreamJson");
|
|
55
|
+
const claudeMcpHeal_1 = require("../utils/claudeMcpHeal");
|
|
56
|
+
const tmuxExec_1 = require("../utils/tmuxExec");
|
|
57
|
+
const pollBackoff_1 = require("../utils/pollBackoff");
|
|
58
|
+
const claudeRuntimeInfo_1 = require("../utils/claudeRuntimeInfo");
|
|
59
|
+
const jsonStreamBusyWatchdog_1 = require("../utils/jsonStreamBusyWatchdog");
|
|
60
|
+
const jsonStreamHost_1 = require("../utils/jsonStreamHost");
|
|
61
|
+
const adapterNames_1 = require("./adapterNames");
|
|
62
|
+
const claudeCodeVersion_1 = require("../utils/claudeCodeVersion");
|
|
63
|
+
const sessionLaunchDefaults_1 = require("./sessionLaunchDefaults");
|
|
64
|
+
/**
|
|
65
|
+
* In-flight explicit stops, per key — a second stop (or a start's implicit stop)
|
|
66
|
+
* AWAITS the first instead of racing it: the delayed first `tmux kill-session`
|
|
67
|
+
* could otherwise land AFTER a fresh same-name spawn and kill the new session.
|
|
68
|
+
* MODULE-level, shared by the idle and the per-turn adapter instances: both host
|
|
69
|
+
* a conversation under the SAME tmux name and dir, and a `/claude_mode` switch
|
|
70
|
+
* stops the process on one instance while the other spawns the next.
|
|
71
|
+
*/
|
|
72
|
+
const stopsInFlightByKey = new Map();
|
|
73
|
+
/**
|
|
74
|
+
* @description Coalesce window for streamed answer/thinking deltas before an
|
|
75
|
+
* `output`/`thinking` emit — the CLI streams per-token, so raw per-delta emits
|
|
76
|
+
* would flood Telegram with edits. Mirrors OpenCode's `sseOutputBatchMs` (500).
|
|
77
|
+
*/
|
|
78
|
+
const streamOutputBatchMs = 350;
|
|
79
|
+
/** Prefix of the per-turn token accounting line (L-D11); the process-level e2e
|
|
80
|
+
* and the live cache check read it. */
|
|
81
|
+
exports.claudeJsonStreamUsageLogPrefix = '[ClaudeJson] usage ';
|
|
82
|
+
/** How long to wait for the `initialize` control-response handshake before
|
|
83
|
+
* proceeding without the interactive control channel (questions unavailable). */
|
|
84
|
+
const initializeHandshakeTimeoutMs = 15000;
|
|
85
|
+
/**
|
|
86
|
+
* @description SECOND Claude backend: drives the `claude` CLI over its
|
|
87
|
+
* documented `--input-format stream-json --output-format stream-json` protocol
|
|
88
|
+
* (typed events, no TUI scrape) as an EXTERNAL tmux-hosted process per
|
|
89
|
+
* {@link SessionKey} (plan 2026-07-05-jsonstream-restart-isolation): a `#!/bin/sh`
|
|
90
|
+
* wrapper backgrounds claude with stdin on a FIFO the process itself holds
|
|
91
|
+
* `0<>` and stdout appended to a `stdout.jsonl` the adapter TAILS — so a bot
|
|
92
|
+
* restart (every hot reload) neither EOFs claude's stdin nor loses its output;
|
|
93
|
+
* the restarted bot re-adopts the tmux session and replays the missed tail.
|
|
94
|
+
* Same structured events, on the SAME subscription billing (proven
|
|
95
|
+
* `apiKeySource:"none"` + `seven_day` rate-limit event; the wrapper runs
|
|
96
|
+
* `env -u ANTHROPIC_API_KEY`, never `--bare`).
|
|
97
|
+
*
|
|
98
|
+
* Structural template is {@link import('./openCodeAdapter').OpenCodeAdapter}
|
|
99
|
+
* (event-driven, own process, per-key session map); the Claude on-disk
|
|
100
|
+
* transcript readers are reused for `/sessions` / resume / recap.
|
|
101
|
+
*
|
|
102
|
+
* Interactive questions (`AskUserQuestion`) reach Telegram through the same
|
|
103
|
+
* `question` event + pin flow as OpenCode: the CLI surfaces them as a
|
|
104
|
+
* `control_request` (`subtype:"can_use_tool"`, `tool_name:"AskUserQuestion"`)
|
|
105
|
+
* on stdout, answered with a `control_response` carrying
|
|
106
|
+
* `updatedInput.answers` on stdin — a wire protocol reverse-engineered from a
|
|
107
|
+
* live capture + the open-source `@anthropic-ai/claude-agent-sdk`. Ordinary
|
|
108
|
+
* tool permissions are auto-allowed (operator trusts their own agent — parity
|
|
109
|
+
* with the tmux backend's `bypassPermissions`), so only questions block.
|
|
110
|
+
*
|
|
111
|
+
* A topic flips between it and the tmux-scrape backend via `/claude_mode`
|
|
112
|
+
* (they share the on-disk transcript, so the switch resumes the same
|
|
113
|
+
* conversation). The DEFAULT Claude backend (`getDefaultClaudeBackendName`):
|
|
114
|
+
* `/login` is handled out-of-band by the bot (`claude auth login` in a pty),
|
|
115
|
+
* so it no longer needs a TUI to sign in.
|
|
116
|
+
*/
|
|
117
|
+
class ClaudeJsonStreamAdapter extends events_1.EventEmitter {
|
|
118
|
+
name;
|
|
119
|
+
label;
|
|
120
|
+
/**
|
|
121
|
+
* `idle` — the process stays between turns and is stopped at the idle mark (L3);
|
|
122
|
+
* `perTurn` — the process is stopped right after each turn (L5, L-D3), by the
|
|
123
|
+
* bot on the `turnEnded` event. Same host, same session layout: one process per
|
|
124
|
+
* conversation at a time whichever lifecycle runs it.
|
|
125
|
+
*/
|
|
126
|
+
lifecycle;
|
|
127
|
+
constructor(options = { lifecycle: 'idle' }) {
|
|
128
|
+
super();
|
|
129
|
+
this.lifecycle = options.lifecycle;
|
|
130
|
+
this.name = options.lifecycle === 'perTurn' ? adapterNames_1.claudePerTurnAdapterName : adapterNames_1.claudeJsonStreamAdapterName;
|
|
131
|
+
this.label = options.lifecycle === 'perTurn' ? 'Claude Code (per-turn)' : 'Claude Code (stream)';
|
|
132
|
+
}
|
|
133
|
+
/** Emits incremental text deltas (like the tmux scrape) → the transports
|
|
134
|
+
* synthesise continuation (`getDmDraftContinuation` / group delta path). */
|
|
135
|
+
outputsDeltas = true;
|
|
136
|
+
/** No TUI banner → keep the bot's `agent.ready` cue + one-shot boot ping (L2). */
|
|
137
|
+
selfGreetsOnStart = false;
|
|
138
|
+
sessions = new Map();
|
|
139
|
+
claudePath = (0, resolveBinary_1.resolveClaudeBinary)();
|
|
140
|
+
displayPrefsReader = null;
|
|
141
|
+
seenWatermarkWriter = null;
|
|
142
|
+
jsonStreamTailWriter = null;
|
|
143
|
+
sessionLaunchDefaultsReader = null;
|
|
144
|
+
/** The stops in flight — the module-level map shared by both lifecycles' instances (see {@link stopsInFlightByKey}). */
|
|
145
|
+
stopsInFlight = stopsInFlightByKey;
|
|
146
|
+
setDisplayPrefsReader(reader) {
|
|
147
|
+
this.displayPrefsReader = reader;
|
|
148
|
+
}
|
|
149
|
+
setSeenWatermarkWriter(writer) {
|
|
150
|
+
this.seenWatermarkWriter = writer;
|
|
151
|
+
}
|
|
152
|
+
setJsonStreamTailWriter(writer) {
|
|
153
|
+
this.jsonStreamTailWriter = writer;
|
|
154
|
+
}
|
|
155
|
+
setSessionLaunchDefaultsReader(reader) {
|
|
156
|
+
this.sessionLaunchDefaultsReader = reader;
|
|
157
|
+
}
|
|
158
|
+
getLaunchOptions(key) {
|
|
159
|
+
return (0, sessionLaunchDefaults_1.getSessionLaunchOptions)({
|
|
160
|
+
savedEffort: this.getEffort(key),
|
|
161
|
+
defaults: this.sessionLaunchDefaultsReader?.(key) ?? null,
|
|
162
|
+
botDefaultEffort: effortLevels_1.defaultEffortLevel,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
getDisplayPrefs(key) {
|
|
166
|
+
return this.displayPrefsReader?.(key) ?? (0, displayVerbosity_1.getDefaultDisplayPrefs)();
|
|
167
|
+
}
|
|
168
|
+
advanceSeenWatermark(key, watermark) {
|
|
169
|
+
this.seenWatermarkWriter?.(key, watermark);
|
|
170
|
+
}
|
|
171
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
172
|
+
// Lifecycle (S1)
|
|
173
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
174
|
+
async startSession(key, workDir, _args, sessionId) {
|
|
175
|
+
await this.stopSessionInternal(key);
|
|
176
|
+
const id = sessionId && (0, claudeCliAdapter_1.checkIsValidUuid)(sessionId) ? sessionId : (0, crypto_1.randomUUID)();
|
|
177
|
+
await this.spawnSession(key, workDir, id, { ...this.getLaunchOptions(key), resume: false });
|
|
178
|
+
}
|
|
179
|
+
async resumeSession(key, workDir, sessionId, options) {
|
|
180
|
+
await this.stopSessionInternal(key);
|
|
181
|
+
if (!(0, claudeCliAdapter_1.checkIsValidUuid)(sessionId))
|
|
182
|
+
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
183
|
+
await this.spawnSession(key, workDir, sessionId, { ...this.getLaunchOptions(key), resume: true });
|
|
184
|
+
// Post the short last-N-turn context block ONLY on the explicit user resume
|
|
185
|
+
// (`/sessions` pick) — a silent re-attach stays quiet (ResumeSessionOptions).
|
|
186
|
+
if (options?.isWithRecentContext) {
|
|
187
|
+
try {
|
|
188
|
+
const turns = await this.getRecentTurns(key, workDir, sessionId, resumeContext_1.resumeContextTurnLimit);
|
|
189
|
+
const rendered = (0, resumeContext_1.formatResumeContext)(turns);
|
|
190
|
+
if (rendered)
|
|
191
|
+
this.emit('output', key, rendered, { isComplete: true });
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
console.warn(`[ClaudeJson] resume context block failed:`, e instanceof Error ? e.message : e);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* @description Spawn (or re-spawn) the CLI process for a thread as an
|
|
200
|
+
* EXTERNAL tmux-hosted process (wrapper + FIFO stdin + stdout file — the
|
|
201
|
+
* probe-proven layout in `utils/jsonStreamHost.ts`), start the stdout tail
|
|
202
|
+
* poller, and complete the `initialize` control handshake (which enables the
|
|
203
|
+
* `AskUserQuestion` control channel). Rejects — with the wrapper's exit code
|
|
204
|
+
* and a stderr excerpt — if the process fails to come up. Reused by start /
|
|
205
|
+
* resume / effort-respawn.
|
|
206
|
+
*/
|
|
207
|
+
async spawnSession(key, workDir, sessionId, opts) {
|
|
208
|
+
if (!(0, installManager_1.checkIsInstalled)('claude')) {
|
|
209
|
+
this.emit('output', key, 'Installing Claude Code...');
|
|
210
|
+
await (0, installManager_1.installTool)('claude');
|
|
211
|
+
}
|
|
212
|
+
const mcpFlags = await (0, mcpConfig_1.prepareMcpFlags)({ key, dataDir: (0, state_1.resolveDataDir)() });
|
|
213
|
+
const args = [
|
|
214
|
+
'-p',
|
|
215
|
+
// Followed by an option on purpose: `--disallowedTools` takes several values.
|
|
216
|
+
...(0, claudePlatformFlags_1.getClaudePlatformFlags)(key),
|
|
217
|
+
'--input-format', 'stream-json',
|
|
218
|
+
'--output-format', 'stream-json',
|
|
219
|
+
'--include-partial-messages',
|
|
220
|
+
'--verbose',
|
|
221
|
+
'--replay-user-messages',
|
|
222
|
+
// Bypass ALL permission checks (tmux-backend parity) so the operator's own
|
|
223
|
+
// agent never stalls on a permission prompt. `--dangerously-skip-permissions`
|
|
224
|
+
// + `bypassPermissions` means regular tools (Bash/Read/…) never route through
|
|
225
|
+
// the stdio prompt tool at all. `--permission-prompt-tool stdio` STAYS: it is
|
|
226
|
+
// what keeps `AskUserQuestion` in the tool list AND still routes it through the
|
|
227
|
+
// control channel even under bypass (verified live on v2.1.201 — bypass does
|
|
228
|
+
// NOT suppress AskUserQuestion, and `apiKeySource` stays `none`). The residual
|
|
229
|
+
// circuit-breakers claude never bypasses (`rm -rf /`, `rm -rf ~`) still arrive
|
|
230
|
+
// as `can_use_tool` and are answered by the generic auto-allow below.
|
|
231
|
+
'--dangerously-skip-permissions',
|
|
232
|
+
'--permission-mode', 'bypassPermissions',
|
|
233
|
+
'--permission-prompt-tool', 'stdio',
|
|
234
|
+
];
|
|
235
|
+
if (opts.resume)
|
|
236
|
+
args.push('--resume', sessionId);
|
|
237
|
+
else
|
|
238
|
+
args.push('--session-id', sessionId);
|
|
239
|
+
if (opts.model)
|
|
240
|
+
args.push('--model', opts.model);
|
|
241
|
+
if (opts.effort)
|
|
242
|
+
args.push('--effort', opts.effort);
|
|
243
|
+
args.push(...mcpFlags);
|
|
244
|
+
// Overflow-triggered compaction gets the bot's summary guidance (see the module).
|
|
245
|
+
args.push(...(0, claudeCompactHook_1.prepareClaudeCompactHookFlags)((0, state_1.resolveDataDir)()));
|
|
246
|
+
// Subscription billing: the wrapper runs `env -u ANTHROPIC_API_KEY` (a set
|
|
247
|
+
// key would meter the API); a tracker conversation's wrapper starts claude
|
|
248
|
+
// with `env -i` and the agent allowlist instead (R32) — no key either. The CLI
|
|
249
|
+
// reads the OAuth login from ~/.claude — `apiKeySource:"none"` (probe-verified
|
|
250
|
+
// under the wrapper).
|
|
251
|
+
const sessionDir = (0, jsonStreamHost_1.resolveJsonStreamSessionDir)((0, state_1.resolveDataDir)(), key);
|
|
252
|
+
const paths = (0, jsonStreamHost_1.getJsonStreamSessionPaths)(sessionDir);
|
|
253
|
+
const tmuxName = (0, jsonStreamHost_1.buildJsonStreamTmuxSessionName)(key);
|
|
254
|
+
console.log(`[ClaudeJson] spawn ${(0, sessionKey_1.keyToString)(key)} session=${sessionId} resume=${opts.resume} effort=${opts.effort} model=${opts.model ?? 'default'} (tmux ${tmuxName})`);
|
|
255
|
+
let pid;
|
|
256
|
+
let fifoFd;
|
|
257
|
+
try {
|
|
258
|
+
// Fresh host layout — a stale dir/fifo from a crashed run must not be reused.
|
|
259
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
260
|
+
fs.mkdirSync(sessionDir, { recursive: true, mode: 0o700 });
|
|
261
|
+
await (0, tmuxExec_1.execFilePromise)('mkfifo', [paths.stdinFifo]);
|
|
262
|
+
fs.writeFileSync(paths.wrapperFile, (0, jsonStreamHost_1.buildWrapperScript)(this.claudePath, args, workDir, paths, (0, claudePlatformFlags_1.getClaudePlatformEnvironment)(key)), { mode: 0o755 });
|
|
263
|
+
// A stale same-name tmux session (crashed bot / pre-adopt build) would make
|
|
264
|
+
// `new-session` fail — and must not keep running unowned next to ours.
|
|
265
|
+
await (0, tmuxExec_1.tmuxAsync)('kill-session', '-t', (0, tmuxExec_1.getTmuxSessionTarget)(tmuxName));
|
|
266
|
+
await (0, tmuxExec_1.tmuxOrThrowAsync)('new-session', '-d', '-s', tmuxName, paths.wrapperFile);
|
|
267
|
+
const pidRead = await (0, jsonStreamHost_1.waitForPidFile)(paths.pidFile);
|
|
268
|
+
if (pidRead === null)
|
|
269
|
+
throw new Error(`claude never wrote its pid file${describeSpawnFailure(paths)}`);
|
|
270
|
+
pid = pidRead;
|
|
271
|
+
const fdRead = await (0, jsonStreamHost_1.openFifoWriterNonBlocking)(paths.stdinFifo);
|
|
272
|
+
if (fdRead === null)
|
|
273
|
+
throw new Error(`claude is not holding its stdin FIFO${describeSpawnFailure(paths)}`);
|
|
274
|
+
fifoFd = fdRead;
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
(0, mcpConfig_1.cleanupMcpTempFiles)({ key, dataDir: (0, state_1.resolveDataDir)() });
|
|
278
|
+
await (0, tmuxExec_1.tmuxAsync)('kill-session', '-t', (0, tmuxExec_1.getTmuxSessionTarget)(tmuxName));
|
|
279
|
+
try {
|
|
280
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
281
|
+
}
|
|
282
|
+
catch { /* best-effort */ }
|
|
283
|
+
throw new Error(`Failed to start Claude stream session: ${e instanceof Error ? e.message : String(e)}`);
|
|
284
|
+
}
|
|
285
|
+
const session = {
|
|
286
|
+
key, workDir, sessionId,
|
|
287
|
+
pid, paths, fifoFd,
|
|
288
|
+
stdinWriteChain: Promise.resolve(),
|
|
289
|
+
tail: (0, jsonStreamHost_1.createStdoutTailState)(0),
|
|
290
|
+
pollTimer: null, pollDelayMs: pollBackoff_1.basePollIntervalMs, unchangedStreak: 0,
|
|
291
|
+
isOversizeWarned: false, lastPersistedTailOffset: 0,
|
|
292
|
+
reader: new claudeStreamJson_1.ClaudeStreamLineReader(),
|
|
293
|
+
isActive: true, isStopping: false, isSuspending: false, isRespawning: false, isBusy: false, unconsumedInputCount: 0,
|
|
294
|
+
lastStdoutActivityAt: Date.now(), outstandingToolUseIds: new Set(),
|
|
295
|
+
model: opts.model, reportedModel: null, effort: opts.effort,
|
|
296
|
+
currentResponseText: '', emittedLength: 0, outputTimer: null,
|
|
297
|
+
reasoningText: '', reasoningStartedAt: null, reasoningTimer: null, reasoningActive: false,
|
|
298
|
+
toolNamesById: new Map(), questionToolUseIds: new Set(),
|
|
299
|
+
subagentActive: false, childResponseText: '', childEmittedLength: 0, childOutputTimer: null,
|
|
300
|
+
pendingInitResolve: null, initRequestId: null, pendingControlRequests: new Map(),
|
|
301
|
+
compactionInProgress: false, pendingCompaction: null,
|
|
302
|
+
pendingQuestion: null, apiErrorFired: false, swallowNextAbortError: false,
|
|
303
|
+
lastWatermarkOffset: -1,
|
|
304
|
+
backgroundTaskIds: new Set(), claudeCodeVersion: null, applyingChunk: null,
|
|
305
|
+
adoptCatchUpOffset: null, adoptCatchUpResolvers: [],
|
|
306
|
+
};
|
|
307
|
+
this.sessions.set((0, sessionKey_1.keyToString)(key), session);
|
|
308
|
+
// A fresh spawn starts a fresh stdout file — reset the persisted tail offset
|
|
309
|
+
// so a later adopt never resumes from a previous run's position.
|
|
310
|
+
this.jsonStreamTailWriter?.(key, buildTailRecord(session, 0));
|
|
311
|
+
this.schedulePoll(session, pollBackoff_1.basePollIntervalMs);
|
|
312
|
+
// Initialize handshake: declares the interactive control channel (permission
|
|
313
|
+
// prompts + AskUserQuestion route to stdio). Without it AskUserQuestion is not
|
|
314
|
+
// even in the tool list (verified). Best-effort — proceed on timeout so a
|
|
315
|
+
// handshake hiccup never blocks a plain-output session. The response arrives
|
|
316
|
+
// through the tail poller started above.
|
|
317
|
+
await this.performInitialize(session);
|
|
318
|
+
this.emit('started', key);
|
|
319
|
+
}
|
|
320
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
321
|
+
// External-process transport: stdout tail poll + exit detection
|
|
322
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
323
|
+
/** Arm the next tail poll (single-flight `setTimeout` chain, mirroring the
|
|
324
|
+
* scrape backend's `schedulePoll` — never overlapping ticks). */
|
|
325
|
+
schedulePoll(session, delayMs) {
|
|
326
|
+
if (!session.isActive)
|
|
327
|
+
return;
|
|
328
|
+
if (session.pollTimer)
|
|
329
|
+
clearTimeout(session.pollTimer);
|
|
330
|
+
session.pollDelayMs = delayMs;
|
|
331
|
+
session.pollTimer = setTimeout(() => {
|
|
332
|
+
session.pollTimer = null;
|
|
333
|
+
this.pollTailTick(session);
|
|
334
|
+
}, delayMs);
|
|
335
|
+
}
|
|
336
|
+
/** Snap the tail cadence back to base — called on every stdin write, when
|
|
337
|
+
* fresh output is expected shortly (mirrors the scrape backend's write snap). */
|
|
338
|
+
snapPollToBase(session) {
|
|
339
|
+
if (!session.isActive)
|
|
340
|
+
return;
|
|
341
|
+
session.unchangedStreak = 0;
|
|
342
|
+
this.schedulePoll(session, pollBackoff_1.basePollIntervalMs);
|
|
343
|
+
}
|
|
344
|
+
pollTailTick(session) {
|
|
345
|
+
if (!session.isActive || this.sessions.get((0, sessionKey_1.keyToString)(session.key)) !== session)
|
|
346
|
+
return;
|
|
347
|
+
let isChanged = false;
|
|
348
|
+
try {
|
|
349
|
+
isChanged = this.drainStdoutTail(session);
|
|
350
|
+
}
|
|
351
|
+
catch (e) {
|
|
352
|
+
console.warn(`[ClaudeJson] stdout tail read failed for ${(0, sessionKey_1.keyToString)(session.key)}:`, e instanceof Error ? e.message : e);
|
|
353
|
+
}
|
|
354
|
+
// Exit detection (locked decision): the wrapper writes `exitcode` when
|
|
355
|
+
// claude exits; pid-alive covers a hard-killed wrapper that never wrote it.
|
|
356
|
+
const exitCode = (0, jsonStreamHost_1.readExitCodeFile)(session.paths.exitCodeFile);
|
|
357
|
+
if (exitCode !== null || !(0, jsonStreamHost_1.checkIsPidAlive)(session.pid)) {
|
|
358
|
+
// Final drain — bytes flushed at process exit may postdate the read above.
|
|
359
|
+
try {
|
|
360
|
+
this.drainStdoutTail(session);
|
|
361
|
+
}
|
|
362
|
+
catch { /* already reported above */ }
|
|
363
|
+
this.finalizeExternalExit(session, exitCode ?? (0, jsonStreamHost_1.readExitCodeFile)(session.paths.exitCodeFile));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
this.settleAdoptCatchUp(session, false);
|
|
367
|
+
// Backstop: a busy session gone silent with nothing in flight lost its
|
|
368
|
+
// terminal `result` — clear the stuck flag so the typing indicator can't hang.
|
|
369
|
+
this.maybeClearBusyOnIdle(session);
|
|
370
|
+
const next = (0, pollBackoff_1.getNextPollDelay)({ isChanged, currentDelayMs: session.pollDelayMs, unchangedStreak: session.unchangedStreak });
|
|
371
|
+
session.unchangedStreak = next.unchangedStreak;
|
|
372
|
+
this.schedulePoll(session, next.delayMs);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* @description Idle watchdog (bounded safety net). `isBusy` is cleared in
|
|
376
|
+
* exactly one place — a processed terminal `result` (`handleTurnEnd`) — so a
|
|
377
|
+
* single missed `result` (an aborted `interrupt`, a process gone quiet after
|
|
378
|
+
* the answer, a future CLI dropping the line) hangs the native typing
|
|
379
|
+
* indicator indefinitely. When the session is busy but stdout has been silent
|
|
380
|
+
* past {@link busyIdleWatchdogMs} and nothing is genuinely in flight, the turn
|
|
381
|
+
* has really ended: clear the flag. The pure decision + its rationale live in
|
|
382
|
+
* `utils/jsonStreamBusyWatchdog` — every in-flight signal (tool / sub-agent /
|
|
383
|
+
* question / batched answer) vetoes the clear, so a legitimately long-running
|
|
384
|
+
* turn is never truncated (a working agent never goes silent with nothing in
|
|
385
|
+
* flight — it streams deltas or waits on a tool/sub-agent/user).
|
|
386
|
+
*/
|
|
387
|
+
maybeClearBusyOnIdle(session) {
|
|
388
|
+
const shouldClear = (0, jsonStreamBusyWatchdog_1.checkShouldClearBusyOnIdle)({
|
|
389
|
+
isBusy: session.isBusy,
|
|
390
|
+
msSinceStdoutActivity: Date.now() - session.lastStdoutActivityAt,
|
|
391
|
+
idleTimeoutMs: jsonStreamBusyWatchdog_1.busyIdleWatchdogMs,
|
|
392
|
+
outstandingToolCount: session.outstandingToolUseIds.size,
|
|
393
|
+
subagentActive: session.subagentActive,
|
|
394
|
+
hasPendingQuestion: session.pendingQuestion !== null,
|
|
395
|
+
hasUnflushedAnswer: session.currentResponseText.length !== session.emittedLength,
|
|
396
|
+
});
|
|
397
|
+
if (!shouldClear)
|
|
398
|
+
return;
|
|
399
|
+
console.warn(`[ClaudeJson] busy-idle watchdog: ${(0, sessionKey_1.keyToString)(session.key)} idle ${jsonStreamBusyWatchdog_1.busyIdleWatchdogMs}ms with nothing in flight — clearing stuck busy (missed terminal result?)`);
|
|
400
|
+
session.isBusy = false;
|
|
401
|
+
this.finishReasoning(session);
|
|
402
|
+
// The record must not keep saying "turn in flight": an adopt would restore the cleared flag.
|
|
403
|
+
this.jsonStreamTailWriter?.(session.key, buildTailRecord(session, session.lastPersistedTailOffset));
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* @description Read whatever `stdout.jsonl` grew since the last poll and feed
|
|
407
|
+
* it through the SAME decode → line-reader → classifier pipeline the stdio
|
|
408
|
+
* pipe used to feed. Returns whether anything new was consumed (drives the
|
|
409
|
+
* adaptive poll backoff). After consuming, persists the line-boundary tail
|
|
410
|
+
* offset so a bot restart resumes exactly where processing stopped.
|
|
411
|
+
*/
|
|
412
|
+
drainStdoutTail(session) {
|
|
413
|
+
const size = (0, jsonStreamHost_1.getFileSize)(session.paths.stdoutFile);
|
|
414
|
+
if (size === null)
|
|
415
|
+
return false; // not created yet (claude still booting)
|
|
416
|
+
if (size > jsonStreamHost_1.stdoutOversizeWarnBytes && !session.isOversizeWarned) {
|
|
417
|
+
session.isOversizeWarned = true;
|
|
418
|
+
console.warn(`[ClaudeJson] stdout log for ${(0, sessionKey_1.keyToString)(session.key)} exceeds ${jsonStreamHost_1.stdoutOversizeWarnBytes} bytes (no rotation in v1)`);
|
|
419
|
+
}
|
|
420
|
+
const decision = (0, jsonStreamHost_1.getStdoutTailDecision)(session.tail, size);
|
|
421
|
+
if (decision.kind === 'reseed') {
|
|
422
|
+
// External truncation — the retained partial line died with the old file.
|
|
423
|
+
session.reader = new claudeStreamJson_1.ClaudeStreamLineReader();
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
if (decision.kind === 'none')
|
|
427
|
+
return false;
|
|
428
|
+
const chunk = (0, jsonStreamHost_1.readFileByteRange)(session.paths.stdoutFile, decision.startOffset, decision.endOffset);
|
|
429
|
+
if (chunk.length === 0)
|
|
430
|
+
return false;
|
|
431
|
+
const text = (0, jsonStreamHost_1.decodeStdoutTailChunk)(session.tail, chunk);
|
|
432
|
+
if (text) {
|
|
433
|
+
// Any new stdout — even a `tool_progress` frame the classifier ignores —
|
|
434
|
+
// means the process is alive and working; feed the idle watchdog's clock.
|
|
435
|
+
session.lastStdoutActivityAt = Date.now();
|
|
436
|
+
this.onStdout(session, text);
|
|
437
|
+
}
|
|
438
|
+
// Persist the consumption boundary ONLY when nothing sits in the answer /
|
|
439
|
+
// child batchers: the offset means "everything before here has LEFT the
|
|
440
|
+
// adapter", so text still waiting in a 350ms batch must hold it back —
|
|
441
|
+
// otherwise a reload in that window skips the batched text on replay
|
|
442
|
+
// (live seam-loss 2026-07-05: lines 216–221 were consumed
|
|
443
|
+
// into the batch, the offset moved past them, and the kill dropped them).
|
|
444
|
+
// A deferred persist happens in `flushAnswer` / the child flush, right
|
|
445
|
+
// after the batched text is emitted.
|
|
446
|
+
if (checkIsEmitCaughtUp(session))
|
|
447
|
+
this.persistTailOffset(session);
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
/** Persist the tail's line-boundary offset (monotonic; inert until the
|
|
451
|
+
* writer is registered — same idiom as the seen-watermark writer). */
|
|
452
|
+
persistTailOffset(session) {
|
|
453
|
+
if (session.applyingChunk) {
|
|
454
|
+
session.applyingChunk.isPersistDeferred = true;
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const boundary = (0, jsonStreamHost_1.getStdoutLineBoundaryOffset)(session.tail, session.reader.pending);
|
|
458
|
+
if (boundary <= session.lastPersistedTailOffset)
|
|
459
|
+
return;
|
|
460
|
+
session.lastPersistedTailOffset = boundary;
|
|
461
|
+
this.jsonStreamTailWriter?.(session.key, buildTailRecord(session, boundary));
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* @description Single teardown convergence point for the external process —
|
|
465
|
+
* both the explicit stop (`isStopping` → `stopped`) and the poll tick's exit
|
|
466
|
+
* detection (→ `closed`, with the wrapper-reported exit code and a stderr
|
|
467
|
+
* excerpt). Kills any tmux leftovers, removes the host dir, and cleans the
|
|
468
|
+
* MCP temp files. A respawn owns its own transition (`isRespawning`).
|
|
469
|
+
*/
|
|
470
|
+
finalizeExternalExit(session, exitCode) {
|
|
471
|
+
if (session.isRespawning)
|
|
472
|
+
return; // an effort/model re-spawn owns the transition
|
|
473
|
+
const key = session.key;
|
|
474
|
+
// Only act if this session is still the current one for the key.
|
|
475
|
+
if (this.sessions.get((0, sessionKey_1.keyToString)(key)) !== session)
|
|
476
|
+
return;
|
|
477
|
+
this.clearTimers(session);
|
|
478
|
+
session.isActive = false;
|
|
479
|
+
this.sessions.delete((0, sessionKey_1.keyToString)(key));
|
|
480
|
+
this.settleAdoptCatchUp(session, true);
|
|
481
|
+
this.closeFifo(session);
|
|
482
|
+
const stderrTail = session.isStopping ? '' : (0, jsonStreamHost_1.readStderrTail)(session.paths.stderrFile);
|
|
483
|
+
void (0, tmuxExec_1.tmuxAsync)('kill-session', '-t', (0, tmuxExec_1.getTmuxSessionTarget)((0, jsonStreamHost_1.buildJsonStreamTmuxSessionName)(key)));
|
|
484
|
+
try {
|
|
485
|
+
fs.rmSync(session.paths.dir, { recursive: true, force: true });
|
|
486
|
+
}
|
|
487
|
+
catch { /* best-effort */ }
|
|
488
|
+
(0, mcpConfig_1.cleanupMcpTempFiles)({ key, dataDir: (0, state_1.resolveDataDir)() });
|
|
489
|
+
if (session.isSuspending) {
|
|
490
|
+
// The session id stays persisted: the next trigger resumes the conversation (L3).
|
|
491
|
+
console.log(`[ClaudeJson] session ${(0, sessionKey_1.keyToString)(key)} suspended (code=${exitCode})`);
|
|
492
|
+
this.emit('suspended', key);
|
|
493
|
+
}
|
|
494
|
+
else if (session.isStopping) {
|
|
495
|
+
console.log(`[ClaudeJson] session ${(0, sessionKey_1.keyToString)(key)} stopped (code=${exitCode})`);
|
|
496
|
+
this.emit('stopped', key);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
console.warn(`[ClaudeJson] session ${(0, sessionKey_1.keyToString)(key)} exited unexpectedly (code=${exitCode})${stderrTail ? ` stderr: ${stderrTail}` : ''}`);
|
|
500
|
+
this.emit('closed', key);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/** Close the FIFO fd AFTER any queued writes settle, so an in-flight write
|
|
504
|
+
* never hits a closed fd (EBADF noise on every stop). */
|
|
505
|
+
closeFifo(session) {
|
|
506
|
+
const fd = session.fifoFd;
|
|
507
|
+
session.stdinWriteChain = session.stdinWriteChain
|
|
508
|
+
.catch(() => { })
|
|
509
|
+
.then(() => { try {
|
|
510
|
+
fs.closeSync(fd);
|
|
511
|
+
}
|
|
512
|
+
catch { /* already closed */ } });
|
|
513
|
+
}
|
|
514
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
515
|
+
// Reattach at boot (S3) — adopt the external process, resume the tail
|
|
516
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
517
|
+
/** Scan tmux for `cjson-…` sessions that outlived the bot (mirror of the
|
|
518
|
+
* scrape backend's scan). The bot decides adopt-vs-orphan per key — this
|
|
519
|
+
* method only lists. */
|
|
520
|
+
async listExistingTmuxSessions() {
|
|
521
|
+
const raw = await (0, tmuxExec_1.tmuxAsync)('list-sessions', '-F', '#{session_name}');
|
|
522
|
+
if (!raw)
|
|
523
|
+
return [];
|
|
524
|
+
const result = [];
|
|
525
|
+
for (const name of raw.split('\n').map((s) => s.trim()).filter(Boolean)) {
|
|
526
|
+
const key = (0, jsonStreamHost_1.parseJsonStreamTmuxSessionName)(name);
|
|
527
|
+
if (key)
|
|
528
|
+
result.push({ key, sessionName: name });
|
|
529
|
+
}
|
|
530
|
+
return result;
|
|
531
|
+
}
|
|
532
|
+
/** Kill an orphan `cjson-…` tmux session AND its host dir (the dir is
|
|
533
|
+
* per-thread, derived from the parsed name; an unparseable name only
|
|
534
|
+
* kills the tmux session). */
|
|
535
|
+
async killOrphanTmuxSession(sessionName) {
|
|
536
|
+
console.log(`[ClaudeJson] kill orphan tmux session: ${sessionName}`);
|
|
537
|
+
await (0, tmuxExec_1.tmuxAsync)('kill-session', '-t', (0, tmuxExec_1.getTmuxSessionTarget)(sessionName));
|
|
538
|
+
const key = (0, jsonStreamHost_1.parseJsonStreamTmuxSessionName)(sessionName);
|
|
539
|
+
if (!key)
|
|
540
|
+
return;
|
|
541
|
+
try {
|
|
542
|
+
fs.rmSync((0, jsonStreamHost_1.resolveJsonStreamSessionDir)((0, state_1.resolveDataDir)(), key), { recursive: true, force: true });
|
|
543
|
+
}
|
|
544
|
+
catch { /* best-effort */ }
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* @description Adopt an external json-stream process that survived a bot
|
|
548
|
+
* restart: reopen the stdin FIFO writer and resume the stdout tail from the
|
|
549
|
+
* persisted line-boundary offset — everything claude produced during the
|
|
550
|
+
* downtime replays through the normal pipeline, so the in-flight turn is
|
|
551
|
+
* delivered end-to-end (which is why the bot posts NO reattach recap for an
|
|
552
|
+
* adopt). An unknown / foreign-session offset seeds to the CURRENT EOF (no
|
|
553
|
+
* backlog flood — the first-migration case). No initialize handshake — the
|
|
554
|
+
* surviving process completed it at spawn; a pending question is restored
|
|
555
|
+
* from the sidecar so its buttons stay answerable. Returns `false` — after
|
|
556
|
+
* killing the leftovers — when the process is dead (exitcode present / pid
|
|
557
|
+
* gone / FIFO unheld): the caller falls back to the dead-process `--resume`
|
|
558
|
+
* reopen.
|
|
559
|
+
*/
|
|
560
|
+
async adoptExistingTmuxSession(key, sessionName, workDir, claudeSessionId, persistedTail) {
|
|
561
|
+
const names = await (0, tmuxExec_1.tmuxAsync)('list-sessions', '-F', '#{session_name}');
|
|
562
|
+
if (!names.split('\n').includes(sessionName)) {
|
|
563
|
+
console.log(`[ClaudeJson] adopt: tmux session ${sessionName} no longer exists`);
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
const k = (0, sessionKey_1.keyToString)(key);
|
|
567
|
+
if (this.sessions.has(k)) {
|
|
568
|
+
console.log(`[ClaudeJson] adopt: already tracking ${k}, skipping`);
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
const sessionDir = (0, jsonStreamHost_1.resolveJsonStreamSessionDir)((0, state_1.resolveDataDir)(), key);
|
|
572
|
+
const paths = (0, jsonStreamHost_1.getJsonStreamSessionPaths)(sessionDir);
|
|
573
|
+
const pid = (0, jsonStreamHost_1.readPidFile)(paths.pidFile);
|
|
574
|
+
const exitCode = (0, jsonStreamHost_1.readExitCodeFile)(paths.exitCodeFile);
|
|
575
|
+
if (pid === null || exitCode !== null || !(0, jsonStreamHost_1.checkIsPidAlive)(pid)) {
|
|
576
|
+
console.log(`[ClaudeJson] adopt: ${sessionName} process is dead (pid=${pid} exit=${exitCode}), cleaning up`);
|
|
577
|
+
await this.killOrphanTmuxSession(sessionName);
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
const fifoFd = await (0, jsonStreamHost_1.openFifoWriterNonBlocking)(paths.stdinFifo);
|
|
581
|
+
if (fifoFd === null) {
|
|
582
|
+
console.log(`[ClaudeJson] adopt: ${sessionName} is not holding its stdin FIFO, cleaning up`);
|
|
583
|
+
await this.killOrphanTmuxSession(sessionName);
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
const size = (0, jsonStreamHost_1.getFileSize)(paths.stdoutFile) ?? 0;
|
|
587
|
+
const { startOffset, backgroundTaskIds, isTurnInFlight, outstandingToolUseIds, claudeCodeVersion } = (0, jsonStreamHost_1.resolveAdoptedTail)(persistedTail, claudeSessionId, size);
|
|
588
|
+
console.log(`[ClaudeJson] adopt: re-attaching to ${sessionName} in ${workDir} (pid=${pid}, tail=${startOffset}/${size})`);
|
|
589
|
+
const session = {
|
|
590
|
+
key, workDir, sessionId: claudeSessionId,
|
|
591
|
+
pid, paths, fifoFd,
|
|
592
|
+
stdinWriteChain: Promise.resolve(),
|
|
593
|
+
tail: (0, jsonStreamHost_1.createStdoutTailState)(startOffset),
|
|
594
|
+
pollTimer: null, pollDelayMs: pollBackoff_1.basePollIntervalMs, unchangedStreak: 0,
|
|
595
|
+
isOversizeWarned: false, lastPersistedTailOffset: startOffset,
|
|
596
|
+
reader: new claudeStreamJson_1.ClaudeStreamLineReader(),
|
|
597
|
+
// isBusy starts from the PERSISTED turn-in-flight flag: a turn in a long, silent
|
|
598
|
+
// tool call leaves no frame after the tail offset to rebuild it from. The
|
|
599
|
+
// replayed/live events then keep it right (deltas/toolUse set it, `result`
|
|
600
|
+
// clears it) — see `applyAction`.
|
|
601
|
+
isActive: true, isStopping: false, isSuspending: false, isRespawning: false, isBusy: isTurnInFlight, unconsumedInputCount: 0,
|
|
602
|
+
// The outstanding tools come from the record too: they veto the busy-idle watchdog, which would
|
|
603
|
+
// otherwise clear the restored turn after its silence window and let the stale-tool stop kill it.
|
|
604
|
+
lastStdoutActivityAt: Date.now(), outstandingToolUseIds: new Set(outstandingToolUseIds),
|
|
605
|
+
model: null, reportedModel: null, effort: null,
|
|
606
|
+
currentResponseText: '', emittedLength: 0, outputTimer: null,
|
|
607
|
+
reasoningText: '', reasoningStartedAt: null, reasoningTimer: null, reasoningActive: false,
|
|
608
|
+
toolNamesById: new Map(), questionToolUseIds: new Set(),
|
|
609
|
+
subagentActive: false, childResponseText: '', childEmittedLength: 0, childOutputTimer: null,
|
|
610
|
+
pendingInitResolve: null, initRequestId: null, pendingControlRequests: new Map(),
|
|
611
|
+
compactionInProgress: false, pendingCompaction: null,
|
|
612
|
+
pendingQuestion: this.readQuestionSidecar(paths),
|
|
613
|
+
apiErrorFired: false, swallowNextAbortError: false,
|
|
614
|
+
lastWatermarkOffset: -1,
|
|
615
|
+
// The frames that built the list and reported the version lie before the
|
|
616
|
+
// tail offset (not replayed): both come from the persisted record.
|
|
617
|
+
backgroundTaskIds: new Set(backgroundTaskIds), claudeCodeVersion, applyingChunk: null,
|
|
618
|
+
// Nothing to catch up on when the tail already sits at EOF.
|
|
619
|
+
adoptCatchUpOffset: startOffset < size ? size : null, adoptCatchUpResolvers: [],
|
|
620
|
+
};
|
|
621
|
+
if (session.pendingQuestion) {
|
|
622
|
+
// The external process is still blocked on this question — busy, and the
|
|
623
|
+
// question's internal tool_result echo must stay suppressed after answer.
|
|
624
|
+
session.isBusy = true;
|
|
625
|
+
session.questionToolUseIds.add(session.pendingQuestion.toolUseId);
|
|
626
|
+
}
|
|
627
|
+
this.sessions.set(k, session);
|
|
628
|
+
this.schedulePoll(session, pollBackoff_1.basePollIntervalMs);
|
|
629
|
+
this.emit('started', key);
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* @description Resolves once an ADOPTED session's tail has consumed what its
|
|
634
|
+
* stdout held at the adopt — the replayed frames have rebuilt the live state
|
|
635
|
+
* (busy, tasks, input) — or once the session is gone. Immediate for a session
|
|
636
|
+
* that was not adopted or whose tail already sat at EOF. The stale-tool-list
|
|
637
|
+
* stop (L4) looks only after this: a look before it would read a turn in flight
|
|
638
|
+
* as idle.
|
|
639
|
+
*/
|
|
640
|
+
whenAdoptReplayed(key) {
|
|
641
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
642
|
+
if (!session || session.adoptCatchUpOffset === null)
|
|
643
|
+
return Promise.resolve();
|
|
644
|
+
return new Promise((resolve) => { session.adoptCatchUpResolvers.push(resolve); });
|
|
645
|
+
}
|
|
646
|
+
/** Settle the adopt catch-up once the tail consumed up to the adopt-time EOF (or the session ended). */
|
|
647
|
+
settleAdoptCatchUp(session, isSessionGone) {
|
|
648
|
+
if (session.adoptCatchUpOffset === null)
|
|
649
|
+
return;
|
|
650
|
+
if (!isSessionGone && session.tail.consumedBytes < session.adoptCatchUpOffset)
|
|
651
|
+
return;
|
|
652
|
+
session.adoptCatchUpOffset = null;
|
|
653
|
+
const resolvers = session.adoptCatchUpResolvers.splice(0);
|
|
654
|
+
for (const resolve of resolvers)
|
|
655
|
+
resolve();
|
|
656
|
+
}
|
|
657
|
+
/** Restore a pending question persisted by a previous bot life (see
|
|
658
|
+
* {@link writeQuestionSidecar}). Parse-boundary narrowing; the questions
|
|
659
|
+
* array re-runs the same validator as a live control_request, so a corrupt
|
|
660
|
+
* sidecar yields `null`, never a malformed pending question. */
|
|
661
|
+
readQuestionSidecar(paths) {
|
|
662
|
+
let parsed;
|
|
663
|
+
try {
|
|
664
|
+
parsed = JSON.parse(fs.readFileSync(paths.questionFile, 'utf8'));
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
return null; // absent (the common case) or corrupt
|
|
668
|
+
}
|
|
669
|
+
if (!(0, claudeStreamJson_1.checkIsStreamRecord)(parsed))
|
|
670
|
+
return null;
|
|
671
|
+
const { requestId, toolUseId, rawInput, questions } = parsed;
|
|
672
|
+
if (typeof requestId !== 'string' || typeof toolUseId !== 'string')
|
|
673
|
+
return null;
|
|
674
|
+
if (!(0, claudeStreamJson_1.checkIsStreamRecord)(rawInput) || !Array.isArray(questions))
|
|
675
|
+
return null;
|
|
676
|
+
const revalidated = this.parseQuestions({ questions });
|
|
677
|
+
if (revalidated.length === 0)
|
|
678
|
+
return null;
|
|
679
|
+
return { requestId, toolUseId, rawInput, questions: revalidated };
|
|
680
|
+
}
|
|
681
|
+
performInitialize(session) {
|
|
682
|
+
const requestId = 'init_' + (0, crypto_1.randomUUID)();
|
|
683
|
+
session.initRequestId = requestId;
|
|
684
|
+
const request = {
|
|
685
|
+
type: 'control_request',
|
|
686
|
+
request_id: requestId,
|
|
687
|
+
request: { subtype: 'initialize', supportedDialogKinds: ['refusal_fallback_prompt'] },
|
|
688
|
+
};
|
|
689
|
+
return new Promise((resolve) => {
|
|
690
|
+
let settled = false;
|
|
691
|
+
const done = () => { if (!settled) {
|
|
692
|
+
settled = true;
|
|
693
|
+
session.pendingInitResolve = null;
|
|
694
|
+
resolve();
|
|
695
|
+
} };
|
|
696
|
+
session.pendingInitResolve = done;
|
|
697
|
+
this.writeStdin(session, request);
|
|
698
|
+
setTimeout(() => {
|
|
699
|
+
if (!settled)
|
|
700
|
+
console.warn(`[ClaudeJson] initialize handshake timed out for ${(0, sessionKey_1.keyToString)(session.key)} — questions may be unavailable`);
|
|
701
|
+
done();
|
|
702
|
+
}, initializeHandshakeTimeoutMs);
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
stopSession(key) {
|
|
706
|
+
void this.stopSessionInternal(key);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* @description Stop the process but KEEP the session resumable (lifecycle plan
|
|
710
|
+
* L3): the same teardown as a stop (SIGTERM, tmux kill, host dir removed —
|
|
711
|
+
* L-D13), converging on `suspended` instead of `stopped`, so the bot keeps the
|
|
712
|
+
* persisted id and the retry / compaction state the stop would drop. The caller
|
|
713
|
+
* checks `checkIsWorking` first — a suspend kills a background task like any
|
|
714
|
+
* stop. Joins a stop already in flight.
|
|
715
|
+
*/
|
|
716
|
+
async suspendSession(key) {
|
|
717
|
+
await this.stopSessionInternal(key, { isSuspend: true });
|
|
718
|
+
}
|
|
719
|
+
async stopSessionInternal(key, options = { isSuspend: false }) {
|
|
720
|
+
const k = (0, sessionKey_1.keyToString)(key);
|
|
721
|
+
const inFlight = this.stopsInFlight.get(k);
|
|
722
|
+
if (inFlight)
|
|
723
|
+
return inFlight; // join the running stop instead of racing it
|
|
724
|
+
const session = this.sessions.get(k);
|
|
725
|
+
if (!session)
|
|
726
|
+
return;
|
|
727
|
+
const stopPromise = this.performStop(session, options).finally(() => { this.stopsInFlight.delete(k); });
|
|
728
|
+
this.stopsInFlight.set(k, stopPromise);
|
|
729
|
+
return stopPromise;
|
|
730
|
+
}
|
|
731
|
+
/** Explicit stop = hard-kill the EXTERNAL process (released sessions are
|
|
732
|
+
* never adopted — locked decision): SIGTERM the pid, kill the tmux session,
|
|
733
|
+
* then converge through {@link finalizeExternalExit} (dir removal + `stopped`,
|
|
734
|
+
* or `suspended` for an idle stop that keeps the session resumable). */
|
|
735
|
+
async performStop(session, options) {
|
|
736
|
+
// Reject a pending question server-side before teardown so the model's
|
|
737
|
+
// AskUserQuestion turn is unblocked (deny), mirroring OpenCode's reject.
|
|
738
|
+
this.rejectQuestion(session.key);
|
|
739
|
+
session.isStopping = true;
|
|
740
|
+
session.isSuspending = options.isSuspend;
|
|
741
|
+
// Let the reject (and any queued frame) reach the FIFO before the kill.
|
|
742
|
+
await session.stdinWriteChain.catch(() => { });
|
|
743
|
+
try {
|
|
744
|
+
process.kill(session.pid, 'SIGTERM');
|
|
745
|
+
}
|
|
746
|
+
catch { /* already gone */ }
|
|
747
|
+
await (0, tmuxExec_1.tmuxAsync)('kill-session', '-t', (0, tmuxExec_1.getTmuxSessionTarget)((0, jsonStreamHost_1.buildJsonStreamTmuxSessionName)(session.key)));
|
|
748
|
+
this.finalizeExternalExit(session, null);
|
|
749
|
+
}
|
|
750
|
+
checkIsActive(key) {
|
|
751
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
752
|
+
return session?.isActive === true;
|
|
753
|
+
}
|
|
754
|
+
checkIsBusy(key) {
|
|
755
|
+
return this.sessions.get((0, sessionKey_1.keyToString)(key))?.isBusy === true;
|
|
756
|
+
}
|
|
757
|
+
checkHasUnconsumedInput(key) {
|
|
758
|
+
return (this.sessions.get((0, sessionKey_1.keyToString)(key))?.unconsumedInputCount ?? 0) > 0;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* @description Whether the process is WORKING and must not be stopped (L-D2):
|
|
762
|
+
* a running turn, a background task the CLI still runs (background Bash /
|
|
763
|
+
* Monitor / sub-agent — a stop kills them, and a finished one starts its own
|
|
764
|
+
* turn), input written but not yet taken in, or a bot-issued compaction in
|
|
765
|
+
* flight. An unknown / inactive session is not working.
|
|
766
|
+
*/
|
|
767
|
+
checkIsWorking(key) {
|
|
768
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
769
|
+
if (!session?.isActive)
|
|
770
|
+
return false;
|
|
771
|
+
return session.isBusy || this.checkHasBackgroundWork(key);
|
|
772
|
+
}
|
|
773
|
+
/** Background tasks, input not yet taken in, a compaction in flight — "working" minus the turn (L-D2). */
|
|
774
|
+
checkHasBackgroundWork(key) {
|
|
775
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
776
|
+
if (!session?.isActive)
|
|
777
|
+
return false;
|
|
778
|
+
return session.backgroundTaskIds.size > 0
|
|
779
|
+
|| session.unconsumedInputCount > 0
|
|
780
|
+
|| session.compactionInProgress;
|
|
781
|
+
}
|
|
782
|
+
/** The CLI version the live process reported on `system/init`, `null` until then (L-D10 gate input). */
|
|
783
|
+
getClaudeCodeVersion(key) {
|
|
784
|
+
return this.sessions.get((0, sessionKey_1.keyToString)(key))?.claudeCodeVersion ?? null;
|
|
785
|
+
}
|
|
786
|
+
/** L-D10: only a CLI that reports the background-task list may be stopped automatically. */
|
|
787
|
+
checkIsAutoStopSupported(key) {
|
|
788
|
+
return (0, claudeCodeVersion_1.checkIsClaudeAutoStopSupported)(this.getClaudeCodeVersion(key));
|
|
789
|
+
}
|
|
790
|
+
async getRuntimeInfo(key) {
|
|
791
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
792
|
+
if (!session?.isActive) {
|
|
793
|
+
return { version: null, model: null, contextWindowTokens: null, contextUsedTokens: null };
|
|
794
|
+
}
|
|
795
|
+
const filePath = (0, claudeCliAdapter_1.getClaudeTranscriptPath)(session.workDir, session.sessionId);
|
|
796
|
+
try {
|
|
797
|
+
return await (0, claudeRuntimeInfo_1.readClaudeRuntimeInfo)(filePath);
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
return { version: null, model: null, contextWindowTokens: null, contextUsedTokens: null };
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
getClaudeSessionId(key) {
|
|
804
|
+
return this.sessions.get((0, sessionKey_1.keyToString)(key))?.sessionId ?? null;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* @description Real, CONFIRMED compaction on the json-stream backend (F3):
|
|
808
|
+
* send `/compact` (or `/compact <instruction>` for F2's closing section) as a
|
|
809
|
+
* stream-json user turn — the CLI parses the slash command, compacts, and
|
|
810
|
+
* reports the outcome on a `compact_boundary` (success, with token counts) /
|
|
811
|
+
* a failing `compact_status` frame. The whole turn's own output is suppressed
|
|
812
|
+
* (`compactionInProgress`) so only the bot's confirmation shows. Resolves the
|
|
813
|
+
* {@link CompactionResult} — a success carrying the boundary frame's pre/post
|
|
814
|
+
* token counts (`null` when only `compact_status success` was seen) — awaiting
|
|
815
|
+
* the terminal signal so the caller can post its notice AFTER the context was
|
|
816
|
+
* really compacted.
|
|
817
|
+
*/
|
|
818
|
+
async compactContext(key, instruction) {
|
|
819
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
820
|
+
if (!session?.isActive)
|
|
821
|
+
return { ok: false, error: (0, i18n_1.t)('compact.start_agent_first') };
|
|
822
|
+
if (session.pendingCompaction) {
|
|
823
|
+
return { ok: false, error: (0, i18n_1.t)('compact.failed', { reason: 'a compaction is already running' }) };
|
|
824
|
+
}
|
|
825
|
+
if (session.isBusy)
|
|
826
|
+
return { ok: false, error: (0, i18n_1.t)('compact.busy') };
|
|
827
|
+
const trimmed = instruction?.trim();
|
|
828
|
+
const text = trimmed ? `/compact ${trimmed}` : '/compact';
|
|
829
|
+
const startedAt = Date.now();
|
|
830
|
+
const result = await new Promise((resolve) => {
|
|
831
|
+
// Bounded by SILENCE, not by elapsed time. A flat 3-minute cap on total
|
|
832
|
+
// elapsed time is what made idle compaction look mute: a real 3 min 15.6 s
|
|
833
|
+
// compaction (`compact_metadata.duration_ms: 195649`) was declared failed
|
|
834
|
+
// while the CLI was succeeding, so the notice was never posted. Summarising
|
|
835
|
+
// scales with the context being summarised, and compact-on-idle exists FOR
|
|
836
|
+
// big contexts — any fixed cap fails exactly where the feature matters.
|
|
837
|
+
// While it works the CLI heartbeats `system/status status:"compacting"`
|
|
838
|
+
// every few seconds, so real silence is the honest death signal.
|
|
839
|
+
const timer = setInterval(() => {
|
|
840
|
+
const verdict = (0, jsonStreamBusyWatchdog_1.getCompactionWaitVerdict)({
|
|
841
|
+
msSinceStdoutActivity: Date.now() - session.lastStdoutActivityAt,
|
|
842
|
+
msSinceCompactionStarted: Date.now() - startedAt,
|
|
843
|
+
silenceTimeoutMs: jsonStreamBusyWatchdog_1.compactionSilenceTimeoutMs,
|
|
844
|
+
absoluteTimeoutMs: jsonStreamBusyWatchdog_1.compactionAbsoluteTimeoutMs,
|
|
845
|
+
});
|
|
846
|
+
if (verdict === 'keepWaiting')
|
|
847
|
+
return;
|
|
848
|
+
const outcome = (0, jsonStreamBusyWatchdog_1.getCompactionTimeoutOutcome)({
|
|
849
|
+
verdict,
|
|
850
|
+
sawSuccess: session.pendingCompaction?.sawSuccess === true,
|
|
851
|
+
});
|
|
852
|
+
if (outcome.kind === 'succeededWithoutTokenCounts') {
|
|
853
|
+
this.resolveCompaction(session, { ok: true, preTokens: null, postTokens: null });
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
this.resolveCompaction(session, {
|
|
857
|
+
ok: false,
|
|
858
|
+
error: outcome.reason === 'silent'
|
|
859
|
+
? `the CLI went silent for ${Math.round(jsonStreamBusyWatchdog_1.compactionSilenceTimeoutMs / 1000)}s without confirming the compaction`
|
|
860
|
+
: `the compaction did not finish within ${Math.round(jsonStreamBusyWatchdog_1.compactionAbsoluteTimeoutMs / 60_000)} min`,
|
|
861
|
+
});
|
|
862
|
+
}, jsonStreamBusyWatchdog_1.compactionWaitPollMs);
|
|
863
|
+
timer.unref?.();
|
|
864
|
+
session.pendingCompaction = { resolve, timer, sawSuccess: false };
|
|
865
|
+
session.compactionInProgress = true;
|
|
866
|
+
// Fresh turn: reset the answer cursor + idle-watchdog clock, mark busy.
|
|
867
|
+
session.currentResponseText = '';
|
|
868
|
+
session.emittedLength = 0;
|
|
869
|
+
session.isBusy = true;
|
|
870
|
+
session.lastStdoutActivityAt = Date.now();
|
|
871
|
+
// Not counted as unconsumed input: a bot-issued `/compact` is a control
|
|
872
|
+
// turn whose echo is not guaranteed, and it only runs on an idle session.
|
|
873
|
+
this.writeStdin(session, { type: 'user', message: { role: 'user', content: text } });
|
|
874
|
+
});
|
|
875
|
+
if (result.ok) {
|
|
876
|
+
console.log(`[ClaudeJson] compacted ${(0, sessionKey_1.keyToString)(key)} (pre=${result.preTokens ?? '?'} post=${result.postTokens ?? '?'})`);
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
console.warn(`[ClaudeJson] compaction failed for ${(0, sessionKey_1.keyToString)(key)}: ${result.error}`);
|
|
880
|
+
// The raw internal reason is wrapped into the user-facing template HERE (not at
|
|
881
|
+
// the resolve sites) so every failure path reads the same way in the topic.
|
|
882
|
+
return { ok: false, error: (0, i18n_1.t)('compact.failed', { reason: result.error }) };
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* @description Read the newest compaction summary from the on-disk transcript
|
|
886
|
+
* (the `isCompactSummary:true` message) so F2 can lift its appended closing
|
|
887
|
+
* section into the idle-compaction notice. Returns `null` when the session is
|
|
888
|
+
* inactive, the transcript is unreadable, or no summary is present.
|
|
889
|
+
*/
|
|
890
|
+
async getLatestCompactionSummary(key) {
|
|
891
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
892
|
+
if (!session?.isActive)
|
|
893
|
+
return null;
|
|
894
|
+
return readLatestCompactSummaryFromTranscript((0, claudeCliAdapter_1.getClaudeTranscriptPath)(session.workDir, session.sessionId));
|
|
895
|
+
}
|
|
896
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
897
|
+
// Input (S3)
|
|
898
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
899
|
+
sendInput(key, input) {
|
|
900
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
901
|
+
if (!session?.isActive) {
|
|
902
|
+
console.warn(`[ClaudeJson] sendInput to inactive session ${(0, sessionKey_1.keyToString)(key)} dropped`);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
// A fresh user turn begins → reset the answer cursor so its first tail is a
|
|
906
|
+
// new message (the bot also flips `needsNewMessage` for delta adapters).
|
|
907
|
+
session.currentResponseText = '';
|
|
908
|
+
session.emittedLength = 0;
|
|
909
|
+
session.apiErrorFired = false;
|
|
910
|
+
session.isBusy = true;
|
|
911
|
+
// A fresh turn resets the idle-watchdog clock: without this a session that
|
|
912
|
+
// sat idle for minutes (busy=false, clock stale) would trip the watchdog on
|
|
913
|
+
// its very next poll — before claude has a chance to emit the first token.
|
|
914
|
+
session.lastStdoutActivityAt = Date.now();
|
|
915
|
+
// The turn in flight is persisted at once (same offset, flipped flag): a restart
|
|
916
|
+
// during a long, silent tool call finds no frame after the offset to rebuild it.
|
|
917
|
+
this.jsonStreamTailWriter?.(key, buildTailRecord(session, session.lastPersistedTailOffset));
|
|
918
|
+
this.writeUserMessage(session, input);
|
|
919
|
+
}
|
|
920
|
+
sendSignal(key, _signal) {
|
|
921
|
+
this.sendInterrupt(key);
|
|
922
|
+
}
|
|
923
|
+
sendEscape(key) {
|
|
924
|
+
this.sendInterrupt(key);
|
|
925
|
+
}
|
|
926
|
+
sendInterrupt(key) {
|
|
927
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
928
|
+
if (!session?.isActive)
|
|
929
|
+
return;
|
|
930
|
+
this.writeStdin(session, {
|
|
931
|
+
type: 'control_request',
|
|
932
|
+
request_id: 'interrupt_' + (0, crypto_1.randomUUID)(),
|
|
933
|
+
request: { subtype: 'interrupt' },
|
|
934
|
+
});
|
|
935
|
+
// An interrupt ABORTS the current turn. The CLI does not reliably emit a
|
|
936
|
+
// terminal `result` for an aborted turn, so clearing `isBusy` here (rather
|
|
937
|
+
// than waiting on the idle watchdog) makes the explicit stop deterministic —
|
|
938
|
+
// the typing indicator drops immediately. If output resumes, a delta re-arms
|
|
939
|
+
// busy; if a `result` does arrive, `handleTurnEnd` is a harmless no-op.
|
|
940
|
+
session.isBusy = false;
|
|
941
|
+
session.outstandingToolUseIds.clear();
|
|
942
|
+
// When the CLI DOES emit a terminal `result{is_error}` for this abort, it
|
|
943
|
+
// carries no text → `claudeStreamJson` labels it the literal `'API error'`.
|
|
944
|
+
// Arm the one-shot so `handleTurnEnd` swallows it instead of relaying a bogus
|
|
945
|
+
// "Claude error: API error" (the reported triple-message on question-cancel,
|
|
946
|
+
// and any bare `/esc` mid-turn). Consumed by the next `handleTurnEnd`.
|
|
947
|
+
session.swallowNextAbortError = true;
|
|
948
|
+
}
|
|
949
|
+
/** Write one user turn, counting it until Claude echoes it back (it has read it). */
|
|
950
|
+
writeUserMessage(session, content) {
|
|
951
|
+
session.unconsumedInputCount += 1;
|
|
952
|
+
this.writeStdin(session, { type: 'user', message: { role: 'user', content } });
|
|
953
|
+
}
|
|
954
|
+
/** Enqueue one stream-json frame onto the stdin FIFO. Fire-and-forget for
|
|
955
|
+
* callers (sync signature preserved); the per-session chain keeps wire
|
|
956
|
+
* order and absorbs transient `EAGAIN` (see `writeFifoText`). */
|
|
957
|
+
writeStdin(session, obj) {
|
|
958
|
+
const line = JSON.stringify(obj) + '\n';
|
|
959
|
+
session.stdinWriteChain = session.stdinWriteChain
|
|
960
|
+
.then(() => (0, jsonStreamHost_1.writeFifoText)(session.fifoFd, line))
|
|
961
|
+
.catch((e) => console.error(`[ClaudeJson] stdin write failed for ${(0, sessionKey_1.keyToString)(session.key)}:`, e instanceof Error ? e.message : e));
|
|
962
|
+
// New input → output expected shortly; snap the tail poll back to base.
|
|
963
|
+
this.snapPollToBase(session);
|
|
964
|
+
}
|
|
965
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
966
|
+
// Output parsing (S2) + control channel (S4)
|
|
967
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
968
|
+
onStdout(session, chunk) {
|
|
969
|
+
const applyingChunk = { isPersistDeferred: false };
|
|
970
|
+
session.applyingChunk = applyingChunk;
|
|
971
|
+
try {
|
|
972
|
+
this.applyStdoutLines(session, session.reader.push(chunk));
|
|
973
|
+
}
|
|
974
|
+
finally {
|
|
975
|
+
session.applyingChunk = null;
|
|
976
|
+
}
|
|
977
|
+
// A persist a handler asked for mid-chunk (see `applyingChunk`) runs now,
|
|
978
|
+
// under the same emit-caught-up gate the drain applies.
|
|
979
|
+
if (applyingChunk.isPersistDeferred && checkIsEmitCaughtUp(session))
|
|
980
|
+
this.persistTailOffset(session);
|
|
981
|
+
}
|
|
982
|
+
applyStdoutLines(session, lines) {
|
|
983
|
+
for (const line of lines) {
|
|
984
|
+
const msg = (0, claudeStreamJson_1.parseStreamJsonLine)(line);
|
|
985
|
+
if (!msg)
|
|
986
|
+
continue;
|
|
987
|
+
for (const action of (0, claudeStreamJson_1.classifyClaudeStreamMessage)(msg)) {
|
|
988
|
+
this.applyAction(session, action);
|
|
989
|
+
}
|
|
990
|
+
// S7: advance the seen-watermark as each PARENT assistant message settles
|
|
991
|
+
// (is relayed), not only at turn end. The external process normally
|
|
992
|
+
// SURVIVES a bot restart now (adopt replays the stdout tail — no recap),
|
|
993
|
+
// but when the process itself DIES mid-turn the dead-process `--resume`
|
|
994
|
+
// fallback recaps from this watermark: without the per-message advance
|
|
995
|
+
// the aborted turn's already-relayed assistant messages would re-count
|
|
996
|
+
// as a false "missed N" (same shared recap reader as the tmux backend,
|
|
997
|
+
// the 2026-07-04 bug). A settled `assistant` message is on disk here, so
|
|
998
|
+
// the transcript EOF includes it. A CHILD (sub-agent) message
|
|
999
|
+
// (`parent_tool_use_id` set) must never advance the watermark.
|
|
1000
|
+
if (msg.type === 'assistant' && msg.parent_tool_use_id == null) {
|
|
1001
|
+
this.advanceWatermarkFromTranscript(session);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* @description Settle the CLI's reply to a control_request the BOT wrote,
|
|
1007
|
+
* matched by `request_id`: the spawn-time `initialize` handshake, plus the MCP
|
|
1008
|
+
* heal's `mcp_status` / `mcp_reconnect` round-trips. A response for an id we
|
|
1009
|
+
* are not waiting on belongs to nothing of ours — ignore it rather than guess
|
|
1010
|
+
* which awaiter it might settle.
|
|
1011
|
+
*/
|
|
1012
|
+
handleControlResponse(session, action) {
|
|
1013
|
+
if (session.pendingInitResolve && action.requestId === session.initRequestId) {
|
|
1014
|
+
session.pendingInitResolve();
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const pending = session.pendingControlRequests.get(action.requestId);
|
|
1018
|
+
if (!pending)
|
|
1019
|
+
return;
|
|
1020
|
+
session.pendingControlRequests.delete(action.requestId);
|
|
1021
|
+
clearTimeout(pending.timer);
|
|
1022
|
+
pending.resolve({ isSuccess: action.isSuccess, payload: action.payload });
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* @description Write ONE bot-issued control_request and resolve with its
|
|
1026
|
+
* `control_response`, or `null` when none arrives within
|
|
1027
|
+
* {@link mcpControlRequestTimeoutMs}. The reply comes back through the normal
|
|
1028
|
+
* stdout-tail → classifier path (the CLI answers on the same stdio control
|
|
1029
|
+
* channel `can_use_tool` uses), so the bounded timer is what keeps a silent
|
|
1030
|
+
* CLI from hanging the caller. Never rejects: an unanswered request is an
|
|
1031
|
+
* unknown, not an error.
|
|
1032
|
+
*
|
|
1033
|
+
* A session that is no longer the live one for its key gets NOTHING written:
|
|
1034
|
+
* its fd is closed (and its number reusable), so a write after teardown could
|
|
1035
|
+
* land in an unrelated file — and no reply could ever settle the awaiter
|
|
1036
|
+
* anyway. Same identity guard as the poll tick and the exit finalizer.
|
|
1037
|
+
*/
|
|
1038
|
+
requestControl(session, requestId, frame) {
|
|
1039
|
+
if (!session.isActive || this.sessions.get((0, sessionKey_1.keyToString)(session.key)) !== session)
|
|
1040
|
+
return Promise.resolve(null);
|
|
1041
|
+
return new Promise((resolve) => {
|
|
1042
|
+
const timer = setTimeout(() => {
|
|
1043
|
+
// Only the entry still parked here is ours to time out — a response that
|
|
1044
|
+
// landed first already removed it (and resolved).
|
|
1045
|
+
if (!session.pendingControlRequests.delete(requestId))
|
|
1046
|
+
return;
|
|
1047
|
+
resolve(null);
|
|
1048
|
+
}, claudeMcpHeal_1.mcpControlRequestTimeoutMs);
|
|
1049
|
+
timer.unref?.();
|
|
1050
|
+
session.pendingControlRequests.set(requestId, { resolve, timer });
|
|
1051
|
+
this.writeStdin(session, frame);
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* @description Heal the bot's own injected MCP server inside this LIVE session:
|
|
1056
|
+
* ask `mcp_status`, and reconnect the server ONLY when it reports `failed`
|
|
1057
|
+
* (the latched state a bot restart leaves behind — the CLI never retries it,
|
|
1058
|
+
* so the bot's tools stay gone for the rest of the session). `connected` is
|
|
1059
|
+
* left strictly alone, and any other status (`needs-auth`, an unknown future
|
|
1060
|
+
* one) is skipped rather than guessed at — see `decideMcpHeal`.
|
|
1061
|
+
*
|
|
1062
|
+
* Control frames only: nothing written here can be read as a user turn, the
|
|
1063
|
+
* session is never marked busy, and the pending-request map is keyed by
|
|
1064
|
+
* `request_id` so the `can_use_tool` / `interrupt` paths are untouched.
|
|
1065
|
+
*
|
|
1066
|
+
* `healed` is reported off the reconnect's own ack, and that ack is MEASURED to
|
|
1067
|
+
* be both synchronous and honest — so no confirming third round-trip is needed
|
|
1068
|
+
* and adding one would only be ceremony. Against a still-dead endpoint the
|
|
1069
|
+
* reconnect answers `{subtype:'error', error:'ECONNREFUSED…'}` (never a hollow
|
|
1070
|
+
* success), and after a successful one an IMMEDIATELY following `mcp_status`
|
|
1071
|
+
* already reads `connected`. Do not re-litigate this as a missing verification.
|
|
1072
|
+
*/
|
|
1073
|
+
async healMcpServer(key, serverName) {
|
|
1074
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
1075
|
+
if (!session?.isActive)
|
|
1076
|
+
return 'unavailable';
|
|
1077
|
+
const statusRequestId = 'mcp_status_' + (0, crypto_1.randomUUID)();
|
|
1078
|
+
const status = await this.requestControl(session, statusRequestId, (0, claudeMcpHeal_1.buildMcpStatusControlRequest)(statusRequestId));
|
|
1079
|
+
// No answer (timeout) or an `error` verdict: nothing is known about the
|
|
1080
|
+
// server, so nothing may be done to it.
|
|
1081
|
+
if (!status?.isSuccess)
|
|
1082
|
+
return 'unavailable';
|
|
1083
|
+
const decision = (0, claudeMcpHeal_1.decideMcpHeal)((0, claudeMcpHeal_1.getMcpServerStatus)(status.payload, serverName));
|
|
1084
|
+
if (decision === 'healthy')
|
|
1085
|
+
return 'healthy';
|
|
1086
|
+
if (decision === 'skip')
|
|
1087
|
+
return 'skipped';
|
|
1088
|
+
const reconnectRequestId = 'mcp_reconnect_' + (0, crypto_1.randomUUID)();
|
|
1089
|
+
const reconnect = await this.requestControl(session, reconnectRequestId, (0, claudeMcpHeal_1.buildMcpReconnectControlRequest)(reconnectRequestId, serverName));
|
|
1090
|
+
// `mcp_reconnect` answers a bare success ack (no inner record); anything
|
|
1091
|
+
// else means the reconnect did not land, so the server is still broken.
|
|
1092
|
+
return reconnect?.isSuccess === true ? 'healed' : 'unavailable';
|
|
1093
|
+
}
|
|
1094
|
+
applyAction(session, action) {
|
|
1095
|
+
// A bot-issued `/compact` turn produces its own noise ("Not enough messages
|
|
1096
|
+
// to compact.", the summarisation itself) — suppress ALL of it so only the
|
|
1097
|
+
// bot's confirmation shows. The compact_* frames + turn end still drive the
|
|
1098
|
+
// awaiter; a stray provider error still surfaces. (A CLI-side AUTO compaction
|
|
1099
|
+
// during a normal turn is NOT gated here — `compactionInProgress` is only set
|
|
1100
|
+
// by our own `compactContext`, and `pendingCompaction` guards the handlers.)
|
|
1101
|
+
if (session.compactionInProgress) {
|
|
1102
|
+
switch (action.kind) {
|
|
1103
|
+
case 'turnEnd':
|
|
1104
|
+
this.handleTurnEnd(session, action);
|
|
1105
|
+
return;
|
|
1106
|
+
case 'compactStatus':
|
|
1107
|
+
this.handleCompactStatus(session, action);
|
|
1108
|
+
return;
|
|
1109
|
+
case 'compactBoundary':
|
|
1110
|
+
this.handleCompactBoundary(session, action);
|
|
1111
|
+
return;
|
|
1112
|
+
case 'init':
|
|
1113
|
+
this.handleInit(session, action);
|
|
1114
|
+
return;
|
|
1115
|
+
case 'backgroundTasks':
|
|
1116
|
+
this.handleBackgroundTasks(session, action);
|
|
1117
|
+
return;
|
|
1118
|
+
case 'apiRetry':
|
|
1119
|
+
this.maybeEmitApiError(session, action.text);
|
|
1120
|
+
return;
|
|
1121
|
+
// A reply to one of OUR control requests is never the compaction turn's
|
|
1122
|
+
// output — its awaiter must settle whatever else is running.
|
|
1123
|
+
case 'controlResponse':
|
|
1124
|
+
this.handleControlResponse(session, action);
|
|
1125
|
+
return;
|
|
1126
|
+
default: return; // swallow text / thinking / tool / toolResult / control / echo
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
switch (action.kind) {
|
|
1130
|
+
case 'init':
|
|
1131
|
+
// Per-turn `init` re-emits; the id is fixed by our `--session-id`, so the
|
|
1132
|
+
// id itself is nothing to act on. The MODEL it carries is load-bearing:
|
|
1133
|
+
// it is the only report of what claude actually runs. Without capturing
|
|
1134
|
+
// it `/status` and the pinned banner showed no model at all on a default
|
|
1135
|
+
// start / resume / adopt (nothing ever set the `--model` pick).
|
|
1136
|
+
this.handleInit(session, action);
|
|
1137
|
+
return;
|
|
1138
|
+
case 'backgroundTasks':
|
|
1139
|
+
this.handleBackgroundTasks(session, action);
|
|
1140
|
+
return;
|
|
1141
|
+
case 'textDelta':
|
|
1142
|
+
// Mid-turn activity marks the session busy: `sendInput` normally set it,
|
|
1143
|
+
// but an ADOPTED session (bot restart mid-turn) reconstructs the busy
|
|
1144
|
+
// state purely from the replayed/live events; `turnEnd` clears it.
|
|
1145
|
+
session.isBusy = true;
|
|
1146
|
+
if (action.isSubagent)
|
|
1147
|
+
this.handleSubagentText(session, action.text);
|
|
1148
|
+
else
|
|
1149
|
+
this.appendAnswerDelta(session, action.text);
|
|
1150
|
+
return;
|
|
1151
|
+
case 'thinkingDelta':
|
|
1152
|
+
session.isBusy = true;
|
|
1153
|
+
if (action.isSubagent)
|
|
1154
|
+
return; // child reasoning never rendered (parity)
|
|
1155
|
+
this.appendThinkingDelta(session, action.text);
|
|
1156
|
+
return;
|
|
1157
|
+
case 'toolUse':
|
|
1158
|
+
session.isBusy = true;
|
|
1159
|
+
this.handleToolUse(session, action);
|
|
1160
|
+
return;
|
|
1161
|
+
case 'toolResult':
|
|
1162
|
+
this.handleToolResult(session, action);
|
|
1163
|
+
return;
|
|
1164
|
+
case 'turnEnd':
|
|
1165
|
+
this.handleTurnEnd(session, action);
|
|
1166
|
+
return;
|
|
1167
|
+
case 'controlRequest':
|
|
1168
|
+
this.handleControlRequest(session, action);
|
|
1169
|
+
return;
|
|
1170
|
+
case 'controlResponse':
|
|
1171
|
+
this.handleControlResponse(session, action);
|
|
1172
|
+
return;
|
|
1173
|
+
case 'apiRetry':
|
|
1174
|
+
this.maybeEmitApiError(session, action.text);
|
|
1175
|
+
return;
|
|
1176
|
+
case 'compactStatus':
|
|
1177
|
+
// A CLI-side auto-compaction (no bot-issued `/compact`) — no awaiter to
|
|
1178
|
+
// resolve, so this is a no-op; the guard lives in the handler.
|
|
1179
|
+
this.handleCompactStatus(session, action);
|
|
1180
|
+
return;
|
|
1181
|
+
case 'compactBoundary':
|
|
1182
|
+
this.handleCompactBoundary(session, action);
|
|
1183
|
+
return;
|
|
1184
|
+
case 'rateLimit':
|
|
1185
|
+
// Subscription usage-window signal; not surfaced to the topic (parity).
|
|
1186
|
+
return;
|
|
1187
|
+
case 'userEcho':
|
|
1188
|
+
// Claude has read one of our user messages. A message written mid-turn
|
|
1189
|
+
// is taken into the RUNNING turn, so the session is busy from here until
|
|
1190
|
+
// the turn's `result`, even if an earlier `result` already cleared it.
|
|
1191
|
+
session.unconsumedInputCount = Math.max(0, session.unconsumedInputCount - 1);
|
|
1192
|
+
session.isBusy = true;
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
handleInit(session, action) {
|
|
1197
|
+
if (action.model)
|
|
1198
|
+
session.reportedModel = action.model;
|
|
1199
|
+
if (action.claudeCodeVersion)
|
|
1200
|
+
session.claudeCodeVersion = action.claudeCodeVersion;
|
|
1201
|
+
}
|
|
1202
|
+
/** Replace the live background-task list with the frame's (it is re-sent
|
|
1203
|
+
* WHOLE on every change, so no diffing). The tail-offset persist that follows
|
|
1204
|
+
* this frame's drain carries the new list to `state.json`. */
|
|
1205
|
+
handleBackgroundTasks(session, action) {
|
|
1206
|
+
session.backgroundTaskIds = new Set(action.taskIds);
|
|
1207
|
+
}
|
|
1208
|
+
// — compaction (F3) —
|
|
1209
|
+
handleCompactStatus(session, action) {
|
|
1210
|
+
const pending = session.pendingCompaction;
|
|
1211
|
+
if (!pending)
|
|
1212
|
+
return;
|
|
1213
|
+
if (action.result === 'success') {
|
|
1214
|
+
// Wait for the boundary frame (it carries the pre/post token counts).
|
|
1215
|
+
pending.sawSuccess = true;
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
// Any non-success result (e.g. "not enough messages") is a failure.
|
|
1219
|
+
this.resolveCompaction(session, {
|
|
1220
|
+
ok: false,
|
|
1221
|
+
error: action.error ?? action.result ?? 'compaction did not run',
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
handleCompactBoundary(session, action) {
|
|
1225
|
+
if (!session.pendingCompaction) {
|
|
1226
|
+
// The CLI's own compaction (a context overflow mid-turn, or one met while replaying the downtime tail):
|
|
1227
|
+
// nobody waits for it, but the conversation's context was replaced all the same.
|
|
1228
|
+
this.emit('contextCompacted', session.key, action.trigger);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
this.resolveCompaction(session, { ok: true, preTokens: action.preTokens, postTokens: action.postTokens }, action.trigger);
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Resolve (and clear) the in-flight compaction awaiter exactly once. Every way a bot-issued compaction can
|
|
1235
|
+
* succeed (its boundary, its turn end, the timeout that saw its success) ends here, so a success is told
|
|
1236
|
+
* once as `contextCompacted`.
|
|
1237
|
+
*/
|
|
1238
|
+
resolveCompaction(session, result, trigger = null) {
|
|
1239
|
+
const pending = session.pendingCompaction;
|
|
1240
|
+
if (!pending)
|
|
1241
|
+
return;
|
|
1242
|
+
session.pendingCompaction = null;
|
|
1243
|
+
session.compactionInProgress = false;
|
|
1244
|
+
if (pending.timer)
|
|
1245
|
+
clearInterval(pending.timer);
|
|
1246
|
+
pending.resolve(result);
|
|
1247
|
+
if (result.ok)
|
|
1248
|
+
this.emit('contextCompacted', session.key, trigger);
|
|
1249
|
+
}
|
|
1250
|
+
// — answer text —
|
|
1251
|
+
appendAnswerDelta(session, text) {
|
|
1252
|
+
// Reasoning is over the moment the answer starts (Claude closes the thinking
|
|
1253
|
+
// block before the text block).
|
|
1254
|
+
this.finishReasoning(session);
|
|
1255
|
+
session.currentResponseText += text;
|
|
1256
|
+
if (session.outputTimer)
|
|
1257
|
+
return; // a flush is already scheduled
|
|
1258
|
+
session.outputTimer = setTimeout(() => {
|
|
1259
|
+
session.outputTimer = null;
|
|
1260
|
+
this.flushAnswer(session, false);
|
|
1261
|
+
}, streamOutputBatchMs);
|
|
1262
|
+
}
|
|
1263
|
+
flushAnswer(session, isFinal) {
|
|
1264
|
+
if (session.outputTimer) {
|
|
1265
|
+
clearTimeout(session.outputTimer);
|
|
1266
|
+
session.outputTimer = null;
|
|
1267
|
+
}
|
|
1268
|
+
const tail = session.currentResponseText.slice(session.emittedLength);
|
|
1269
|
+
if (tail) {
|
|
1270
|
+
session.emittedLength = session.currentResponseText.length;
|
|
1271
|
+
const meta = isFinal ? { isFinal: true } : {};
|
|
1272
|
+
// NO isContinuation: `outputsDeltas` adapters let the transports synthesise it.
|
|
1273
|
+
this.emit('output', session.key, tail, meta);
|
|
1274
|
+
}
|
|
1275
|
+
// The answer batcher is drained — the held-back consumption boundary is now
|
|
1276
|
+
// safe to persist (see `drainStdoutTail`).
|
|
1277
|
+
if (checkIsEmitCaughtUp(session))
|
|
1278
|
+
this.persistTailOffset(session);
|
|
1279
|
+
}
|
|
1280
|
+
// — reasoning / thinking —
|
|
1281
|
+
appendThinkingDelta(session, text) {
|
|
1282
|
+
if (!session.reasoningActive) {
|
|
1283
|
+
session.reasoningActive = true;
|
|
1284
|
+
session.reasoningStartedAt = Date.now();
|
|
1285
|
+
session.reasoningText = '';
|
|
1286
|
+
}
|
|
1287
|
+
session.reasoningText += text;
|
|
1288
|
+
if (session.reasoningTimer)
|
|
1289
|
+
return;
|
|
1290
|
+
session.reasoningTimer = setTimeout(() => {
|
|
1291
|
+
session.reasoningTimer = null;
|
|
1292
|
+
this.emit('thinking', session.key, { phase: 'live', text: session.reasoningText });
|
|
1293
|
+
}, streamOutputBatchMs);
|
|
1294
|
+
}
|
|
1295
|
+
finishReasoning(session) {
|
|
1296
|
+
if (!session.reasoningActive)
|
|
1297
|
+
return;
|
|
1298
|
+
if (session.reasoningTimer) {
|
|
1299
|
+
clearTimeout(session.reasoningTimer);
|
|
1300
|
+
session.reasoningTimer = null;
|
|
1301
|
+
}
|
|
1302
|
+
const durationMs = session.reasoningStartedAt ? Date.now() - session.reasoningStartedAt : undefined;
|
|
1303
|
+
this.emit('thinking', session.key, { phase: 'done', text: session.reasoningText, durationMs });
|
|
1304
|
+
session.reasoningActive = false;
|
|
1305
|
+
session.reasoningStartedAt = null;
|
|
1306
|
+
}
|
|
1307
|
+
// — tools —
|
|
1308
|
+
handleToolUse(session, action) {
|
|
1309
|
+
// AskUserQuestion never renders as a tool (it is intercepted on the control
|
|
1310
|
+
// channel); guard defensively.
|
|
1311
|
+
if (action.tool === 'AskUserQuestion')
|
|
1312
|
+
return;
|
|
1313
|
+
if (action.isSubagent) {
|
|
1314
|
+
this.markSubagentActive(session);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
session.toolNamesById.set(action.toolUseId, action.tool);
|
|
1318
|
+
// Track it as in flight until its tool_result returns — a busy session with
|
|
1319
|
+
// an outstanding tool (e.g. a long silent Bash) must NOT trip the idle
|
|
1320
|
+
// watchdog even though stdout goes quiet while the tool runs.
|
|
1321
|
+
session.outstandingToolUseIds.add(action.toolUseId);
|
|
1322
|
+
// Transient status only; the completed body arrives as a toolResult.
|
|
1323
|
+
this.emit('status', session.key, `🔧 ${action.tool}`);
|
|
1324
|
+
}
|
|
1325
|
+
handleToolResult(session, action) {
|
|
1326
|
+
// The tool returned — no longer in flight (do this before any early return so
|
|
1327
|
+
// the outstanding-tool set can never leak and pin the idle watchdog open).
|
|
1328
|
+
session.outstandingToolUseIds.delete(action.toolUseId);
|
|
1329
|
+
// Skip AskUserQuestion's internal "questions have been answered" tool_result.
|
|
1330
|
+
if (session.questionToolUseIds.has(action.toolUseId))
|
|
1331
|
+
return;
|
|
1332
|
+
const tool = session.toolNamesById.get(action.toolUseId) ?? 'tool';
|
|
1333
|
+
if (!action.output.trim())
|
|
1334
|
+
return;
|
|
1335
|
+
// Mode-agnostic emit — the bot applies the per-thread `/tool_results` pref.
|
|
1336
|
+
this.emit('toolResult', session.key, { tool, output: action.output });
|
|
1337
|
+
}
|
|
1338
|
+
// — sub-agent (v1) —
|
|
1339
|
+
markSubagentActive(session) {
|
|
1340
|
+
session.subagentActive = true;
|
|
1341
|
+
this.emit('subagentStatus', session.key, { active: true, title: null });
|
|
1342
|
+
}
|
|
1343
|
+
handleSubagentText(session, text) {
|
|
1344
|
+
if (this.getDisplayPrefs(session.key).subagent === 'full') {
|
|
1345
|
+
session.childResponseText += text;
|
|
1346
|
+
if (session.childOutputTimer)
|
|
1347
|
+
return;
|
|
1348
|
+
session.childOutputTimer = setTimeout(() => {
|
|
1349
|
+
session.childOutputTimer = null;
|
|
1350
|
+
const tail = session.childResponseText.slice(session.childEmittedLength);
|
|
1351
|
+
if (!tail.trim())
|
|
1352
|
+
return;
|
|
1353
|
+
session.childEmittedLength = session.childResponseText.length;
|
|
1354
|
+
this.emit('output', session.key, tail, { isSubagent: true });
|
|
1355
|
+
// Child batcher drained — release the held-back tail offset (see
|
|
1356
|
+
// `drainStdoutTail`).
|
|
1357
|
+
if (checkIsEmitCaughtUp(session))
|
|
1358
|
+
this.persistTailOffset(session);
|
|
1359
|
+
}, streamOutputBatchMs);
|
|
1360
|
+
}
|
|
1361
|
+
else {
|
|
1362
|
+
this.markSubagentActive(session);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
clearSubagent(session) {
|
|
1366
|
+
if (session.childOutputTimer) {
|
|
1367
|
+
clearTimeout(session.childOutputTimer);
|
|
1368
|
+
session.childOutputTimer = null;
|
|
1369
|
+
}
|
|
1370
|
+
session.childResponseText = '';
|
|
1371
|
+
session.childEmittedLength = 0;
|
|
1372
|
+
if (session.subagentActive) {
|
|
1373
|
+
session.subagentActive = false;
|
|
1374
|
+
this.emit('subagentStatus', session.key, { active: false, title: null });
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
// — turn end —
|
|
1378
|
+
/** Every terminal `result` — a normal turn, a compaction turn, an error — ends with the `turnEnded` event. */
|
|
1379
|
+
handleTurnEnd(session, action) {
|
|
1380
|
+
this.applyTurnEnd(session, action);
|
|
1381
|
+
// After the handlers above: the bot's per-turn stop (L5) reads the session's state as settled here.
|
|
1382
|
+
this.emit('turnEnded', session.key);
|
|
1383
|
+
}
|
|
1384
|
+
applyTurnEnd(session, action) {
|
|
1385
|
+
// L-D11: every turn's accounting, the compaction turn included — the
|
|
1386
|
+
// cross-process prompt-cache check reads these lines, so they must be real
|
|
1387
|
+
// and repeatable, never reconstructed from a transcript afterwards.
|
|
1388
|
+
if (action.usage) {
|
|
1389
|
+
const { inputTokens, cacheReadTokens, cacheWriteTokens, outputTokens } = action.usage;
|
|
1390
|
+
console.log(`${exports.claudeJsonStreamUsageLogPrefix}${(0, sessionKey_1.keyToString)(session.key)}: input=${inputTokens} cacheRead=${cacheReadTokens} cacheWrite=${cacheWriteTokens} output=${outputTokens}`);
|
|
1391
|
+
}
|
|
1392
|
+
session.isBusy = false;
|
|
1393
|
+
session.outstandingToolUseIds.clear();
|
|
1394
|
+
this.finishReasoning(session);
|
|
1395
|
+
this.clearSubagent(session);
|
|
1396
|
+
// A bot-issued `/compact` turn ends here. Emit NOTHING (the summary text /
|
|
1397
|
+
// "not enough" message are suppressed) and settle the awaiter: a still-pending
|
|
1398
|
+
// compaction with no failure signal is a clean success (fallback for a build
|
|
1399
|
+
// that skipped the boundary frame); an errored turn is a failure.
|
|
1400
|
+
if (session.compactionInProgress) {
|
|
1401
|
+
session.swallowNextAbortError = false;
|
|
1402
|
+
if (session.pendingCompaction) {
|
|
1403
|
+
const sawSuccess = session.pendingCompaction.sawSuccess;
|
|
1404
|
+
this.resolveCompaction(session, sawSuccess || !action.isError
|
|
1405
|
+
? { ok: true, preTokens: null, postTokens: null }
|
|
1406
|
+
: { ok: false, error: action.errorText ?? 'compaction failed' });
|
|
1407
|
+
}
|
|
1408
|
+
else {
|
|
1409
|
+
session.compactionInProgress = false;
|
|
1410
|
+
}
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
// One-shot: consume the interrupt's swallow flag for THIS terminal result
|
|
1414
|
+
// (read-and-clear so it can never leak onto a later, unrelated turn).
|
|
1415
|
+
const swallowAbortError = session.swallowNextAbortError;
|
|
1416
|
+
session.swallowNextAbortError = false;
|
|
1417
|
+
if (action.isError && action.errorText) {
|
|
1418
|
+
// Our own interrupt produced this error result — relay nothing (no bogus
|
|
1419
|
+
// "Claude error", no spurious retry). A REAL provider error arriving
|
|
1420
|
+
// WITHOUT a preceding interrupt still classifies / surfaces normally.
|
|
1421
|
+
if (swallowAbortError) {
|
|
1422
|
+
console.log(`[ClaudeJson] swallowed interrupt-aborted turn error for ${(0, sessionKey_1.keyToString)(session.key)}: ${action.errorText}`);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const emitted = this.maybeEmitApiError(session, action.errorText);
|
|
1426
|
+
if (!emitted)
|
|
1427
|
+
this.emit('output', session.key, `Claude error: ${action.errorText}`, { isFinal: true });
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
// Reconciliation backstop: if the delta stream under-delivered vs the final
|
|
1431
|
+
// `result` text, append the gap so the answer is never short.
|
|
1432
|
+
if (action.resultText && action.resultText.length > session.currentResponseText.length
|
|
1433
|
+
&& action.resultText.startsWith(session.currentResponseText)) {
|
|
1434
|
+
session.currentResponseText = action.resultText;
|
|
1435
|
+
}
|
|
1436
|
+
this.flushAnswer(session, true);
|
|
1437
|
+
// Advance the seen watermark to the transcript's current byte size so a bot
|
|
1438
|
+
// restart can count assistant messages produced while it was down. Also runs
|
|
1439
|
+
// per settled parent message (see `onStdout`); this turn-end call is the
|
|
1440
|
+
// safety-net catch-up for the final assistant line.
|
|
1441
|
+
this.advanceWatermarkFromTranscript(session);
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* @description Advance the persisted seen-watermark to the transcript's current
|
|
1445
|
+
* byte size when it grew past the last write (S7). A cheap `fs.statSync().size`
|
|
1446
|
+
* (NOT a whole-file read — this runs per settled parent assistant message, not
|
|
1447
|
+
* only at turn end) plus a monotonic guard, mirroring the tmux backend's
|
|
1448
|
+
* {@link import('./claudeCliAdapter').ClaudeCliAdapter} advance so both Claude
|
|
1449
|
+
* paths track "relayed up to here". Best-effort — a transcript not yet on disk
|
|
1450
|
+
* leaves the watermark where it was; the next relayed message re-tries.
|
|
1451
|
+
*/
|
|
1452
|
+
advanceWatermarkFromTranscript(session) {
|
|
1453
|
+
try {
|
|
1454
|
+
const filePath = (0, claudeCliAdapter_1.getClaudeTranscriptPath)(session.workDir, session.sessionId);
|
|
1455
|
+
const eof = fs.statSync(filePath).size;
|
|
1456
|
+
if (eof <= session.lastWatermarkOffset)
|
|
1457
|
+
return; // monotonic: only real growth writes
|
|
1458
|
+
session.lastWatermarkOffset = eof;
|
|
1459
|
+
this.advanceSeenWatermark(session.key, { sessionId: session.sessionId, claudeTranscriptOffset: eof });
|
|
1460
|
+
}
|
|
1461
|
+
catch {
|
|
1462
|
+
// Transcript not yet on disk (UUID just born) / stat failed — retry next relay.
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
// — api error —
|
|
1466
|
+
/** Classify + emit an `apiError` once per episode; returns whether it fired. */
|
|
1467
|
+
maybeEmitApiError(session, text) {
|
|
1468
|
+
if (session.apiErrorFired)
|
|
1469
|
+
return false;
|
|
1470
|
+
const apiError = (0, apiErrorRetry_1.classifyAgentApiError)(text, Date.now());
|
|
1471
|
+
if (!apiError)
|
|
1472
|
+
return false;
|
|
1473
|
+
session.apiErrorFired = true;
|
|
1474
|
+
this.emit('apiError', session.key, apiError);
|
|
1475
|
+
return true;
|
|
1476
|
+
}
|
|
1477
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1478
|
+
// Questions & permissions — control channel (S4)
|
|
1479
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1480
|
+
handleControlRequest(session, action) {
|
|
1481
|
+
if (action.subtype === 'can_use_tool') {
|
|
1482
|
+
if (action.toolName === 'AskUserQuestion' && action.input && action.toolUseId) {
|
|
1483
|
+
session.questionToolUseIds.add(action.toolUseId);
|
|
1484
|
+
this.surfaceQuestion(session, action.requestId, action.toolUseId, action.input);
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
// Any other tool → auto-allow (operator trusts their own agent; parity with
|
|
1488
|
+
// the tmux backend's bypassPermissions). Under bypass this fires only for the
|
|
1489
|
+
// residual circuit-breakers claude never bypasses (`rm -rf /`, `rm -rf ~`).
|
|
1490
|
+
// `buildCanUseToolAllow` guarantees the schema-required `updatedInput`.
|
|
1491
|
+
this.writeControlResponse(session, action.requestId, (0, claudeStreamJson_1.buildCanUseToolAllow)(action.input, action.toolUseId));
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (action.subtype === 'request_user_dialog') {
|
|
1495
|
+
// No dialog UI in Telegram → cancel so the CLI falls back gracefully.
|
|
1496
|
+
this.writeControlResponse(session, action.requestId, { behavior: 'cancelled' });
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
// Unknown control request → generic success so the CLI is never left hanging.
|
|
1500
|
+
this.writeControlResponse(session, action.requestId, {});
|
|
1501
|
+
}
|
|
1502
|
+
surfaceQuestion(session, requestId, toolUseId, rawInput) {
|
|
1503
|
+
const questions = this.parseQuestions(rawInput);
|
|
1504
|
+
if (questions.length === 0) {
|
|
1505
|
+
// Nothing to ask — allow with the raw input so the turn continues.
|
|
1506
|
+
this.writeControlResponse(session, requestId, (0, claudeStreamJson_1.buildCanUseToolAllow)(rawInput, toolUseId));
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
session.pendingQuestion = { requestId, toolUseId, rawInput, questions };
|
|
1510
|
+
session.isBusy = true;
|
|
1511
|
+
this.writeQuestionSidecar(session);
|
|
1512
|
+
const payload = { requestId, questions };
|
|
1513
|
+
this.emit('question', session.key, payload);
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* @description Persist the pending question to the host dir. The external
|
|
1517
|
+
* claude process stays BLOCKED on its `can_use_tool` across a bot restart,
|
|
1518
|
+
* but the control_request line lies BEFORE the persisted tail offset (it was
|
|
1519
|
+
* consumed pre-restart), so tail replay alone would never resurface it — the
|
|
1520
|
+
* sidecar is what lets an adopting bot still answer over the FIFO. Removed
|
|
1521
|
+
* the moment the question resolves (answer / reject); dies with the dir on
|
|
1522
|
+
* stop.
|
|
1523
|
+
*/
|
|
1524
|
+
writeQuestionSidecar(session) {
|
|
1525
|
+
if (!session.pendingQuestion)
|
|
1526
|
+
return;
|
|
1527
|
+
try {
|
|
1528
|
+
fs.writeFileSync(session.paths.questionFile, JSON.stringify(session.pendingQuestion));
|
|
1529
|
+
}
|
|
1530
|
+
catch (e) {
|
|
1531
|
+
console.warn(`[ClaudeJson] cannot persist pending question for ${(0, sessionKey_1.keyToString)(session.key)}:`, e instanceof Error ? e.message : e);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
clearQuestionSidecar(session) {
|
|
1535
|
+
try {
|
|
1536
|
+
fs.unlinkSync(session.paths.questionFile);
|
|
1537
|
+
}
|
|
1538
|
+
catch { /* absent */ }
|
|
1539
|
+
}
|
|
1540
|
+
/** Map the AskUserQuestion `input.questions` to the shared question shape
|
|
1541
|
+
* (dropping option `preview`, which breaks in the Telegram relay). */
|
|
1542
|
+
parseQuestions(rawInput) {
|
|
1543
|
+
const rawQuestions = Array.isArray(rawInput.questions) ? rawInput.questions : [];
|
|
1544
|
+
const result = [];
|
|
1545
|
+
for (const raw of rawQuestions) {
|
|
1546
|
+
if (typeof raw !== 'object' || raw === null)
|
|
1547
|
+
continue;
|
|
1548
|
+
const q = raw;
|
|
1549
|
+
if (typeof q.question !== 'string')
|
|
1550
|
+
continue;
|
|
1551
|
+
const rawOptions = Array.isArray(q.options) ? q.options : [];
|
|
1552
|
+
const options = rawOptions
|
|
1553
|
+
.map((o) => {
|
|
1554
|
+
if (typeof o !== 'object' || o === null)
|
|
1555
|
+
return null;
|
|
1556
|
+
const oo = o;
|
|
1557
|
+
if (typeof oo.label !== 'string')
|
|
1558
|
+
return null;
|
|
1559
|
+
return typeof oo.description === 'string' ? { label: oo.label, description: oo.description } : { label: oo.label };
|
|
1560
|
+
})
|
|
1561
|
+
.filter((o) => o !== null);
|
|
1562
|
+
if (options.length === 0)
|
|
1563
|
+
continue;
|
|
1564
|
+
const question = { question: q.question, options };
|
|
1565
|
+
if (typeof q.header === 'string')
|
|
1566
|
+
question.header = q.header;
|
|
1567
|
+
if (q.multiSelect === true)
|
|
1568
|
+
question.multiple = true;
|
|
1569
|
+
result.push(question);
|
|
1570
|
+
}
|
|
1571
|
+
return result;
|
|
1572
|
+
}
|
|
1573
|
+
answerQuestion(key, answers) {
|
|
1574
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
1575
|
+
if (!session?.isActive || !session.pendingQuestion)
|
|
1576
|
+
return;
|
|
1577
|
+
const pending = session.pendingQuestion;
|
|
1578
|
+
session.pendingQuestion = null;
|
|
1579
|
+
this.clearQuestionSidecar(session);
|
|
1580
|
+
// The turn resumes now; reset the idle-watchdog clock so the (possibly long)
|
|
1581
|
+
// wait for the user's answer doesn't instantly trip it before output resumes.
|
|
1582
|
+
session.lastStdoutActivityAt = Date.now();
|
|
1583
|
+
// Build the answers map: question text → selected label(s) (comma-joined for
|
|
1584
|
+
// multi-select), the exact shape AskUserQuestion expects.
|
|
1585
|
+
const answersMap = {};
|
|
1586
|
+
pending.questions.forEach((q, i) => {
|
|
1587
|
+
const selected = answers[i] ?? [];
|
|
1588
|
+
answersMap[q.question] = selected.join(',');
|
|
1589
|
+
});
|
|
1590
|
+
this.writeControlResponse(session, pending.requestId, (0, claudeStreamJson_1.buildCanUseToolAllow)({ ...pending.rawInput, answers: answersMap }, pending.toolUseId));
|
|
1591
|
+
}
|
|
1592
|
+
rejectQuestion(key) {
|
|
1593
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
1594
|
+
if (!session?.pendingQuestion)
|
|
1595
|
+
return;
|
|
1596
|
+
const pending = session.pendingQuestion;
|
|
1597
|
+
session.pendingQuestion = null;
|
|
1598
|
+
this.clearQuestionSidecar(session);
|
|
1599
|
+
this.writeControlResponse(session, pending.requestId, (0, claudeStreamJson_1.buildCanUseToolDeny)('User declined to answer the question.', pending.toolUseId));
|
|
1600
|
+
}
|
|
1601
|
+
writeControlResponse(session, requestId, response) {
|
|
1602
|
+
this.writeStdin(session, { type: 'control_response', response: { subtype: 'success', request_id: requestId, response } });
|
|
1603
|
+
}
|
|
1604
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1605
|
+
// Session history / resume (S5) — reuse the shared Claude transcript readers
|
|
1606
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1607
|
+
async getSessions(_key, workDir) {
|
|
1608
|
+
return (0, claudeCliAdapter_1.listClaudeSessionsForWorkDir)((0, claudeCliAdapter_1.getClaudeProjectsRoot)(), workDir);
|
|
1609
|
+
}
|
|
1610
|
+
async getRecentTurns(_key, workDir, sessionId, limit) {
|
|
1611
|
+
const filePath = (0, claudeCliAdapter_1.getClaudeTranscriptPath)(workDir, sessionId);
|
|
1612
|
+
return (0, claudeCliAdapter_1.readRecentClaudeTurns)(filePath, limit);
|
|
1613
|
+
}
|
|
1614
|
+
async getReattachRecap(key, workDir, sessionId, watermark) {
|
|
1615
|
+
const filePath = (0, claudeCliAdapter_1.getClaudeTranscriptPath)(workDir, sessionId);
|
|
1616
|
+
const offset = watermark?.claudeTranscriptOffset;
|
|
1617
|
+
const isWatermarkKnown = typeof offset === 'number' && watermark?.sessionId === sessionId;
|
|
1618
|
+
const { missedCount, turns, headOffset } = (0, claudeCliAdapter_1.readClaudeReattachTranscript)(filePath, offset ?? 0, resumeContext_1.resumeContextTurnLimit);
|
|
1619
|
+
const headWatermark = headOffset === undefined ? undefined : { sessionId, claudeTranscriptOffset: headOffset };
|
|
1620
|
+
return { missedCount: isWatermarkKnown ? missedCount : 0, turns, isWatermarkKnown, isActive: this.checkIsBusy(key), headWatermark };
|
|
1621
|
+
}
|
|
1622
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1623
|
+
// Model / effort (S6) — spawn flags; a live change re-spawns with --resume
|
|
1624
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1625
|
+
async setModel(key, modelId) {
|
|
1626
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
1627
|
+
if (!session?.isActive)
|
|
1628
|
+
return (0, i18n_1.t)('model.start_agent_first');
|
|
1629
|
+
await this.respawnWithChange(session, { model: modelId, effort: session.effort });
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* @description The model the session is RUNNING. Claude's own `system/init`
|
|
1634
|
+
* report wins over the pinned `--model` pick: it is the resolved id (an alias
|
|
1635
|
+
* like `opus` resolves to its snapshot) and it is the only value present when
|
|
1636
|
+
* no pick was ever made. The pick is the fallback for the window between a
|
|
1637
|
+
* `/model` re-spawn and its first `init`, so the label never blanks out
|
|
1638
|
+
* mid-switch.
|
|
1639
|
+
*/
|
|
1640
|
+
getCurrentModel(key) {
|
|
1641
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
1642
|
+
return session?.reportedModel ?? session?.model ?? null;
|
|
1643
|
+
}
|
|
1644
|
+
async getAvailableModels() {
|
|
1645
|
+
// The CLI `--model` accepts these aliases (resolved to the latest snapshot).
|
|
1646
|
+
return ['sonnet', 'opus', 'haiku'];
|
|
1647
|
+
}
|
|
1648
|
+
async setEffort(key, level) {
|
|
1649
|
+
if (!(0, effortLevels_1.checkIsClaudeEffortLevel)(level)) {
|
|
1650
|
+
return (0, i18n_1.t)('effort.invalid_level', { level, valid: (0, effortLevels_1.getClaudeAvailableLevels)().join(', ') });
|
|
1651
|
+
}
|
|
1652
|
+
(0, claudeCliAdapter_1.saveEffortPref)(key, level);
|
|
1653
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
1654
|
+
if (!session?.isActive)
|
|
1655
|
+
return (0, i18n_1.t)('effort.start_agent_first');
|
|
1656
|
+
await this.respawnWithChange(session, { model: session.model, effort: level });
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
getEffort(key) {
|
|
1660
|
+
return (0, claudeCliAdapter_1.loadEffortPrefs)()[(0, sessionKey_1.keyToString)(key)] ?? null;
|
|
1661
|
+
}
|
|
1662
|
+
async getAvailableEffortLevels(_key) {
|
|
1663
|
+
return (0, effortLevels_1.getClaudeAvailableLevels)();
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* @description Apply a model/effort change by re-spawning the child with
|
|
1667
|
+
* `--resume <sessionId>` (the on-disk session persists, so only an in-flight
|
|
1668
|
+
* streaming reply is lost — same trade-off as OpenCode's server restart). The
|
|
1669
|
+
* intermediate exit is suppressed (`isRespawning`) so the bot sees no
|
|
1670
|
+
* closed/stopped churn.
|
|
1671
|
+
*/
|
|
1672
|
+
async respawnWithChange(session, change) {
|
|
1673
|
+
const { key, workDir, sessionId } = session;
|
|
1674
|
+
session.isRespawning = true;
|
|
1675
|
+
this.rejectQuestion(key);
|
|
1676
|
+
await session.stdinWriteChain.catch(() => { });
|
|
1677
|
+
this.clearTimers(session);
|
|
1678
|
+
session.isActive = false;
|
|
1679
|
+
this.closeFifo(session);
|
|
1680
|
+
try {
|
|
1681
|
+
process.kill(session.pid, 'SIGTERM');
|
|
1682
|
+
}
|
|
1683
|
+
catch { /* gone */ }
|
|
1684
|
+
await (0, tmuxExec_1.tmuxAsync)('kill-session', '-t', (0, tmuxExec_1.getTmuxSessionTarget)((0, jsonStreamHost_1.buildJsonStreamTmuxSessionName)(key)));
|
|
1685
|
+
// spawnSession lays the host dir out fresh and replaces the map entry.
|
|
1686
|
+
await this.spawnSession(key, workDir, sessionId, { effort: change.effort, model: change.model, resume: true });
|
|
1687
|
+
}
|
|
1688
|
+
clearTimers(session) {
|
|
1689
|
+
if (session.pollTimer) {
|
|
1690
|
+
clearTimeout(session.pollTimer);
|
|
1691
|
+
session.pollTimer = null;
|
|
1692
|
+
}
|
|
1693
|
+
if (session.outputTimer) {
|
|
1694
|
+
clearTimeout(session.outputTimer);
|
|
1695
|
+
session.outputTimer = null;
|
|
1696
|
+
}
|
|
1697
|
+
if (session.reasoningTimer) {
|
|
1698
|
+
clearTimeout(session.reasoningTimer);
|
|
1699
|
+
session.reasoningTimer = null;
|
|
1700
|
+
}
|
|
1701
|
+
if (session.childOutputTimer) {
|
|
1702
|
+
clearTimeout(session.childOutputTimer);
|
|
1703
|
+
session.childOutputTimer = null;
|
|
1704
|
+
}
|
|
1705
|
+
// An in-flight compaction dies with the session: the process that was going
|
|
1706
|
+
// to confirm it is gone, so settle the caller now instead of leaving it
|
|
1707
|
+
// parked until the silence watchdog notices a session that no longer exists.
|
|
1708
|
+
this.resolveCompaction(session, { ok: false, error: 'the session ended before the compaction confirmed' });
|
|
1709
|
+
// Same reasoning for bot-issued control requests: the process they were
|
|
1710
|
+
// talking to is going away (stop / exit / model-effort respawn), so their
|
|
1711
|
+
// replies can never arrive. Settle each as an unknown (`null`) and drop the
|
|
1712
|
+
// map, rather than leaving a caller parked on the round-trip timeout and a
|
|
1713
|
+
// timer armed against a dead session.
|
|
1714
|
+
const pendingControls = [...session.pendingControlRequests.values()];
|
|
1715
|
+
session.pendingControlRequests.clear();
|
|
1716
|
+
for (const pending of pendingControls) {
|
|
1717
|
+
clearTimeout(pending.timer);
|
|
1718
|
+
pending.resolve(null);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
exports.ClaudeJsonStreamAdapter = ClaudeJsonStreamAdapter;
|
|
1723
|
+
/** The tail record persisted for a session at `offsetBytes` — the offset and the working
|
|
1724
|
+
* state in force there (task list, turn in flight, outstanding tools) travel together
|
|
1725
|
+
* (see `JsonStreamTailOffset`). */
|
|
1726
|
+
function buildTailRecord(session, offsetBytes) {
|
|
1727
|
+
return {
|
|
1728
|
+
sessionId: session.sessionId,
|
|
1729
|
+
offsetBytes,
|
|
1730
|
+
backgroundTaskIds: [...session.backgroundTaskIds],
|
|
1731
|
+
isTurnInFlight: session.isBusy,
|
|
1732
|
+
outstandingToolUseIds: [...session.outstandingToolUseIds],
|
|
1733
|
+
// Only once known: the record never claims a version the process did not report.
|
|
1734
|
+
...(session.claudeCodeVersion !== null ? { claudeCodeVersion: session.claudeCodeVersion } : {}),
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* @description Whether every consumed event has LEFT the adapter — nothing is
|
|
1739
|
+
* waiting in the answer / sub-agent 350ms batchers. Gates the tail-offset
|
|
1740
|
+
* persist: batched-but-unemitted text must keep the persisted offset BEHIND
|
|
1741
|
+
* it, so a restart replays (never skips) it. Live thinking text is
|
|
1742
|
+
* deliberately ignored — it renders as a transient frame, so a lost batch of
|
|
1743
|
+
* it at a reload seam costs nothing permanent.
|
|
1744
|
+
*/
|
|
1745
|
+
function checkIsEmitCaughtUp(session) {
|
|
1746
|
+
return session.currentResponseText.length === session.emittedLength
|
|
1747
|
+
&& session.childResponseText.length === session.childEmittedLength;
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* @description Extract the newest compaction summary from a Claude on-disk
|
|
1751
|
+
* transcript: the last JSONL line with `isCompactSummary === true`, whose
|
|
1752
|
+
* `message.content` (a string, or `{type:'text'}` blocks) holds the summary the
|
|
1753
|
+
* `/compact` turn produced. Returns `null` for an unreadable file or when no
|
|
1754
|
+
* summary line is present. Reads the whole file — cheap enough for the
|
|
1755
|
+
* once-per-idle-compaction call.
|
|
1756
|
+
*/
|
|
1757
|
+
function readLatestCompactSummaryFromTranscript(filePath) {
|
|
1758
|
+
let raw;
|
|
1759
|
+
try {
|
|
1760
|
+
raw = fs.readFileSync(filePath, 'utf8');
|
|
1761
|
+
}
|
|
1762
|
+
catch {
|
|
1763
|
+
return null;
|
|
1764
|
+
}
|
|
1765
|
+
let summary = null;
|
|
1766
|
+
for (const line of raw.split('\n')) {
|
|
1767
|
+
const trimmed = line.trim();
|
|
1768
|
+
if (!trimmed)
|
|
1769
|
+
continue;
|
|
1770
|
+
let obj;
|
|
1771
|
+
try {
|
|
1772
|
+
obj = JSON.parse(trimmed);
|
|
1773
|
+
}
|
|
1774
|
+
catch {
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
if (!(0, claudeStreamJson_1.checkIsStreamRecord)(obj) || obj.isCompactSummary !== true)
|
|
1778
|
+
continue;
|
|
1779
|
+
const message = (0, claudeStreamJson_1.checkIsStreamRecord)(obj.message) ? obj.message : null;
|
|
1780
|
+
const content = message?.content;
|
|
1781
|
+
if (typeof content === 'string' && content.trim()) {
|
|
1782
|
+
summary = content;
|
|
1783
|
+
}
|
|
1784
|
+
else if (Array.isArray(content)) {
|
|
1785
|
+
const chunks = [];
|
|
1786
|
+
for (const block of content) {
|
|
1787
|
+
if ((0, claudeStreamJson_1.checkIsStreamRecord)(block) && block.type === 'text' && typeof block.text === 'string') {
|
|
1788
|
+
chunks.push(block.text);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
if (chunks.join('').trim())
|
|
1792
|
+
summary = chunks.join('');
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
return summary;
|
|
1796
|
+
}
|
|
1797
|
+
/** Spawn-fail diagnostics: the wrapper's exit code + a stderr excerpt (the
|
|
1798
|
+
* passive `stderr.log` is read ONLY here and at unexpected exit — locked
|
|
1799
|
+
* decision, no live stderr consumer). */
|
|
1800
|
+
function describeSpawnFailure(paths) {
|
|
1801
|
+
const exitCode = (0, jsonStreamHost_1.readExitCodeFile)(paths.exitCodeFile);
|
|
1802
|
+
const stderrTail = (0, jsonStreamHost_1.readStderrTail)(paths.stderrFile);
|
|
1803
|
+
const parts = [];
|
|
1804
|
+
if (exitCode !== null)
|
|
1805
|
+
parts.push(`exit=${exitCode}`);
|
|
1806
|
+
if (stderrTail)
|
|
1807
|
+
parts.push(`stderr: ${stderrTail}`);
|
|
1808
|
+
return parts.length > 0 ? ` (${parts.join(', ')})` : '';
|
|
1809
|
+
}
|
|
1810
|
+
//# sourceMappingURL=claudeJsonStreamAdapter.js.map
|