charness 3.6.1 → 3.8.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/CHANGELOG.md +297 -0
- package/DEVELOPMENT.md +35 -5
- package/MIGRATION_RENAME_TO_CHARNESS.md +3 -2
- package/README.md +84 -782
- package/SETUP.md +48 -10
- package/agent-plugin/.claude-plugin/plugin.json +7 -0
- package/agent-plugin/README.md +35 -0
- package/agent-plugin/skills/charness-delegate-claude/SKILL.md +85 -0
- package/agent-plugin/skills/charness-delegate-claude/summarize-events.py +90 -0
- package/agent-plugin/skills/charness-delegate-opencode/SKILL.md +68 -0
- package/agent-plugin/skills/charness-manager/SKILL.md +152 -0
- package/dist/adapters/adapterNames.d.ts +6 -0
- package/dist/adapters/adapterNames.d.ts.map +1 -1
- package/dist/adapters/adapterNames.js +8 -2
- package/dist/adapters/adapterNames.js.map +1 -1
- package/dist/adapters/claudeCliAdapter.d.ts +8 -0
- package/dist/adapters/claudeCliAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeCliAdapter.js +42 -5
- package/dist/adapters/claudeCliAdapter.js.map +1 -1
- package/dist/adapters/claudeJsonStreamAdapter.d.ts +10 -9
- package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeJsonStreamAdapter.js +53 -55
- package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -1
- package/dist/adapters/createAdapter.d.ts +8 -0
- package/dist/adapters/createAdapter.d.ts.map +1 -1
- package/dist/adapters/createAdapter.js +14 -2
- package/dist/adapters/createAdapter.js.map +1 -1
- package/dist/adapters/openCodeAdapter.d.ts +43 -6
- package/dist/adapters/openCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/openCodeAdapter.js +97 -7
- package/dist/adapters/openCodeAdapter.js.map +1 -1
- package/dist/agentLogin/agentLogin.d.ts +1 -1
- package/dist/agentLogin/agentLogin.d.ts.map +1 -1
- package/dist/apiRetryKick.d.ts +4 -3
- package/dist/apiRetryKick.d.ts.map +1 -1
- package/dist/apiRetryKick.js +16 -9
- package/dist/apiRetryKick.js.map +1 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +1360 -387
- package/dist/bot.js.map +1 -1
- package/dist/cli/claudeProfiles.d.ts +11 -6
- package/dist/cli/claudeProfiles.d.ts.map +1 -1
- package/dist/cli/claudeProfiles.js +50 -22
- package/dist/cli/claudeProfiles.js.map +1 -1
- package/dist/cli/hot.d.ts +5 -0
- package/dist/cli/hot.d.ts.map +1 -1
- package/dist/cli/hot.js +71 -25
- package/dist/cli/hot.js.map +1 -1
- package/dist/cli/tscWatchGuard.d.ts +95 -0
- package/dist/cli/tscWatchGuard.d.ts.map +1 -0
- package/dist/cli/tscWatchGuard.js +295 -0
- package/dist/cli/tscWatchGuard.js.map +1 -0
- package/dist/cli.js +55 -1
- package/dist/cli.js.map +1 -1
- package/dist/configDirFile.d.ts +7 -3
- package/dist/configDirFile.d.ts.map +1 -1
- package/dist/configDirFile.js +12 -5
- package/dist/configDirFile.js.map +1 -1
- package/dist/connectors/jira/answerSink.d.ts +10 -0
- package/dist/connectors/jira/answerSink.d.ts.map +1 -1
- package/dist/connectors/jira/answerSink.js +13 -0
- package/dist/connectors/jira/answerSink.js.map +1 -1
- package/dist/connectors/jira/config.d.ts +2 -0
- package/dist/connectors/jira/config.d.ts.map +1 -1
- package/dist/connectors/jira/config.js +9 -1
- package/dist/connectors/jira/config.js.map +1 -1
- package/dist/connectors/jira/connector.d.ts +11 -1
- package/dist/connectors/jira/connector.d.ts.map +1 -1
- package/dist/connectors/jira/connector.js +7 -0
- package/dist/connectors/jira/connector.js.map +1 -1
- package/dist/connectors/jira/inbound.d.ts +27 -4
- package/dist/connectors/jira/inbound.d.ts.map +1 -1
- package/dist/connectors/jira/inbound.js +69 -8
- package/dist/connectors/jira/inbound.js.map +1 -1
- package/dist/connectors/jira/issueBlocks.d.ts +5 -0
- package/dist/connectors/jira/issueBlocks.d.ts.map +1 -1
- package/dist/connectors/jira/issueBlocks.js +11 -0
- package/dist/connectors/jira/issueBlocks.js.map +1 -1
- package/dist/connectors/telegram/bindPicker.d.ts +5 -0
- package/dist/connectors/telegram/bindPicker.d.ts.map +1 -1
- package/dist/connectors/telegram/bindPicker.js +18 -1
- package/dist/connectors/telegram/bindPicker.js.map +1 -1
- package/dist/connectors/telegram/bindPickerClaims.d.ts +46 -0
- package/dist/connectors/telegram/bindPickerClaims.d.ts.map +1 -0
- package/dist/connectors/telegram/bindPickerClaims.js +83 -0
- package/dist/connectors/telegram/bindPickerClaims.js.map +1 -0
- package/dist/connectors/telegram/bindSweep.d.ts +32 -0
- package/dist/connectors/telegram/bindSweep.d.ts.map +1 -0
- package/dist/connectors/telegram/bindSweep.js +47 -0
- package/dist/connectors/telegram/bindSweep.js.map +1 -0
- package/dist/connectors/telegram/bindingWelcome.d.ts +23 -0
- package/dist/connectors/telegram/bindingWelcome.d.ts.map +1 -0
- package/dist/connectors/telegram/bindingWelcome.js +26 -0
- package/dist/connectors/telegram/bindingWelcome.js.map +1 -0
- package/dist/connectors/telegram/botApiConnection.d.ts +74 -0
- package/dist/connectors/telegram/botApiConnection.d.ts.map +1 -0
- package/dist/connectors/telegram/botApiConnection.js +201 -0
- package/dist/connectors/telegram/botApiConnection.js.map +1 -0
- package/dist/connectors/telegram/commands/botCore.d.ts +13 -3
- package/dist/connectors/telegram/commands/botCore.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/botSkills.d.ts +21 -0
- package/dist/connectors/telegram/commands/botSkills.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/botSkills.js +133 -0
- package/dist/connectors/telegram/commands/botSkills.js.map +1 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts +5 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/callbackMessage.js +9 -0
- package/dist/connectors/telegram/commands/callbackMessage.js.map +1 -1
- package/dist/connectors/telegram/commands/compaction.d.ts +65 -3
- package/dist/connectors/telegram/commands/compaction.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/compaction.js +512 -94
- package/dist/connectors/telegram/commands/compaction.js.map +1 -1
- package/dist/connectors/telegram/commands/displayModes.d.ts +1 -1
- package/dist/connectors/telegram/commands/displayModes.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/displayModes.js +6 -18
- package/dist/connectors/telegram/commands/displayModes.js.map +1 -1
- package/dist/connectors/telegram/commands/fallbackChain.js +5 -5
- package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -1
- package/dist/connectors/telegram/commands/reminders.d.ts +1 -1
- package/dist/connectors/telegram/commands/reminders.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/reminders.js +4 -15
- package/dist/connectors/telegram/commands/reminders.js.map +1 -1
- package/dist/connectors/telegram/commands/runners.d.ts +52 -0
- package/dist/connectors/telegram/commands/runners.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/runners.js +486 -0
- package/dist/connectors/telegram/commands/runners.js.map +1 -0
- package/dist/connectors/telegram/commands/selfUpdate.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/selfUpdate.js +1 -11
- package/dist/connectors/telegram/commands/selfUpdate.js.map +1 -1
- package/dist/connectors/telegram/commands/taskAgents.d.ts +22 -0
- package/dist/connectors/telegram/commands/taskAgents.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/taskAgents.js +104 -0
- package/dist/connectors/telegram/commands/taskAgents.js.map +1 -0
- package/dist/connectors/telegram/commands/todo.d.ts +82 -0
- package/dist/connectors/telegram/commands/todo.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/todo.js +384 -0
- package/dist/connectors/telegram/commands/todo.js.map +1 -0
- package/dist/connectors/telegram/commands/topicCommand.d.ts +6 -2
- package/dist/connectors/telegram/commands/topicCommand.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/topicCommand.js +6 -9
- package/dist/connectors/telegram/commands/topicCommand.js.map +1 -1
- package/dist/connectors/telegram/openBindMessages.d.ts +45 -0
- package/dist/connectors/telegram/openBindMessages.d.ts.map +1 -0
- package/dist/connectors/telegram/openBindMessages.js +67 -0
- package/dist/connectors/telegram/openBindMessages.js.map +1 -0
- package/dist/connectors/telegram/outputFlushPlan.d.ts +49 -0
- package/dist/connectors/telegram/outputFlushPlan.d.ts.map +1 -1
- package/dist/connectors/telegram/outputFlushPlan.js +46 -0
- package/dist/connectors/telegram/outputFlushPlan.js.map +1 -1
- package/dist/connectors/telegram/pendingTopicName.d.ts +26 -0
- package/dist/connectors/telegram/pendingTopicName.d.ts.map +1 -0
- package/dist/connectors/telegram/pendingTopicName.js +27 -0
- package/dist/connectors/telegram/pendingTopicName.js.map +1 -0
- package/dist/connectors/telegram/runnerPairingMessages.d.ts +27 -0
- package/dist/connectors/telegram/runnerPairingMessages.d.ts.map +1 -0
- package/dist/connectors/telegram/runnerPairingMessages.js +105 -0
- package/dist/connectors/telegram/runnerPairingMessages.js.map +1 -0
- package/dist/connectors/test/connector.d.ts.map +1 -1
- package/dist/connectors/test/connector.js +6 -1
- package/dist/connectors/test/connector.js.map +1 -1
- package/dist/i18n/de.d.ts.map +1 -1
- package/dist/i18n/de.js +148 -7
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +148 -7
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +148 -7
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/fr.js +148 -7
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/hi.d.ts.map +1 -1
- package/dist/i18n/hi.js +148 -7
- package/dist/i18n/hi.js.map +1 -1
- package/dist/i18n/ja.d.ts.map +1 -1
- package/dist/i18n/ja.js +148 -7
- package/dist/i18n/ja.js.map +1 -1
- package/dist/i18n/ka.d.ts.map +1 -1
- package/dist/i18n/ka.js +148 -7
- package/dist/i18n/ka.js.map +1 -1
- package/dist/i18n/pt.d.ts.map +1 -1
- package/dist/i18n/pt.js +148 -7
- package/dist/i18n/pt.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +148 -7
- package/dist/i18n/ru.js.map +1 -1
- package/dist/i18n/uk.d.ts.map +1 -1
- package/dist/i18n/uk.js +148 -7
- package/dist/i18n/uk.js.map +1 -1
- package/dist/i18n/uz.d.ts.map +1 -1
- package/dist/i18n/uz.js +148 -7
- package/dist/i18n/uz.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +148 -7
- package/dist/i18n/zh.js.map +1 -1
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +1 -1
- package/dist/installManager.d.ts +70 -1
- package/dist/installManager.d.ts.map +1 -1
- package/dist/installManager.js +125 -13
- package/dist/installManager.js.map +1 -1
- package/dist/instanceSettings.d.ts +50 -1
- package/dist/instanceSettings.d.ts.map +1 -1
- package/dist/instanceSettings.js +119 -26
- package/dist/instanceSettings.js.map +1 -1
- package/dist/openCodeQuestionFlow.d.ts +30 -0
- package/dist/openCodeQuestionFlow.d.ts.map +1 -1
- package/dist/openCodeQuestionFlow.js +43 -0
- package/dist/openCodeQuestionFlow.js.map +1 -1
- package/dist/outputTrace.d.ts +6 -0
- package/dist/outputTrace.d.ts.map +1 -1
- package/dist/outputTrace.js +24 -1
- package/dist/outputTrace.js.map +1 -1
- package/dist/postToSession.d.ts +20 -5
- package/dist/postToSession.d.ts.map +1 -1
- package/dist/postToSession.js +38 -7
- package/dist/postToSession.js.map +1 -1
- package/dist/remoteRun/cliArgs.d.ts +75 -0
- package/dist/remoteRun/cliArgs.d.ts.map +1 -0
- package/dist/remoteRun/cliArgs.js +206 -0
- package/dist/remoteRun/cliArgs.js.map +1 -0
- package/dist/remoteRun/commands.d.ts +77 -0
- package/dist/remoteRun/commands.d.ts.map +1 -0
- package/dist/remoteRun/commands.js +327 -0
- package/dist/remoteRun/commands.js.map +1 -0
- package/dist/remoteRun/depsPlan.d.ts +16 -0
- package/dist/remoteRun/depsPlan.d.ts.map +1 -0
- package/dist/remoteRun/depsPlan.js +82 -0
- package/dist/remoteRun/depsPlan.js.map +1 -0
- package/dist/remoteRun/exitCodes.d.ts +34 -0
- package/dist/remoteRun/exitCodes.d.ts.map +1 -0
- package/dist/remoteRun/exitCodes.js +38 -0
- package/dist/remoteRun/exitCodes.js.map +1 -0
- package/dist/remoteRun/foreground.d.ts +29 -0
- package/dist/remoteRun/foreground.d.ts.map +1 -0
- package/dist/remoteRun/foreground.js +159 -0
- package/dist/remoteRun/foreground.js.map +1 -0
- package/dist/remoteRun/interrupts.d.ts +27 -0
- package/dist/remoteRun/interrupts.d.ts.map +1 -0
- package/dist/remoteRun/interrupts.js +78 -0
- package/dist/remoteRun/interrupts.js.map +1 -0
- package/dist/remoteRun/jobFiles.d.ts +31 -0
- package/dist/remoteRun/jobFiles.d.ts.map +1 -0
- package/dist/remoteRun/jobFiles.js +84 -0
- package/dist/remoteRun/jobFiles.js.map +1 -0
- package/dist/remoteRun/jobFormat.d.ts +16 -0
- package/dist/remoteRun/jobFormat.d.ts.map +1 -0
- package/dist/remoteRun/jobFormat.js +130 -0
- package/dist/remoteRun/jobFormat.js.map +1 -0
- package/dist/remoteRun/jobId.d.ts +15 -0
- package/dist/remoteRun/jobId.d.ts.map +1 -0
- package/dist/remoteRun/jobId.js +85 -0
- package/dist/remoteRun/jobId.js.map +1 -0
- package/dist/remoteRun/jobMeta.d.ts +18 -0
- package/dist/remoteRun/jobMeta.d.ts.map +1 -0
- package/dist/remoteRun/jobMeta.js +33 -0
- package/dist/remoteRun/jobMeta.js.map +1 -0
- package/dist/remoteRun/jobQuery.d.ts +12 -0
- package/dist/remoteRun/jobQuery.d.ts.map +1 -0
- package/dist/remoteRun/jobQuery.js +22 -0
- package/dist/remoteRun/jobQuery.js.map +1 -0
- package/dist/remoteRun/jobRecords.d.ts +22 -0
- package/dist/remoteRun/jobRecords.d.ts.map +1 -0
- package/dist/remoteRun/jobRecords.js +145 -0
- package/dist/remoteRun/jobRecords.js.map +1 -0
- package/dist/remoteRun/jobState.d.ts +62 -0
- package/dist/remoteRun/jobState.d.ts.map +1 -0
- package/dist/remoteRun/jobState.js +132 -0
- package/dist/remoteRun/jobState.js.map +1 -0
- package/dist/remoteRun/leaseTiming.d.ts +18 -0
- package/dist/remoteRun/leaseTiming.d.ts.map +1 -0
- package/dist/remoteRun/leaseTiming.js +43 -0
- package/dist/remoteRun/leaseTiming.js.map +1 -0
- package/dist/remoteRun/output.d.ts +37 -0
- package/dist/remoteRun/output.d.ts.map +1 -0
- package/dist/remoteRun/output.js +127 -0
- package/dist/remoteRun/output.js.map +1 -0
- package/dist/remoteRun/pairedRunners.d.ts +51 -0
- package/dist/remoteRun/pairedRunners.d.ts.map +1 -0
- package/dist/remoteRun/pairedRunners.js +165 -0
- package/dist/remoteRun/pairedRunners.js.map +1 -0
- package/dist/remoteRun/pathPatterns.d.ts +33 -0
- package/dist/remoteRun/pathPatterns.d.ts.map +1 -0
- package/dist/remoteRun/pathPatterns.js +115 -0
- package/dist/remoteRun/pathPatterns.js.map +1 -0
- package/dist/remoteRun/probe.d.ts +78 -0
- package/dist/remoteRun/probe.d.ts.map +1 -0
- package/dist/remoteRun/probe.js +157 -0
- package/dist/remoteRun/probe.js.map +1 -0
- package/dist/remoteRun/project.d.ts +26 -0
- package/dist/remoteRun/project.d.ts.map +1 -0
- package/dist/remoteRun/project.js +159 -0
- package/dist/remoteRun/project.js.map +1 -0
- package/dist/remoteRun/projectConfig.d.ts +38 -0
- package/dist/remoteRun/projectConfig.d.ts.map +1 -0
- package/dist/remoteRun/projectConfig.js +87 -0
- package/dist/remoteRun/projectConfig.js.map +1 -0
- package/dist/remoteRun/remoteRunCli.d.ts +22 -0
- package/dist/remoteRun/remoteRunCli.d.ts.map +1 -0
- package/dist/remoteRun/remoteRunCli.js +119 -0
- package/dist/remoteRun/remoteRunCli.js.map +1 -0
- package/dist/remoteRun/remoteRunError.d.ts +14 -0
- package/dist/remoteRun/remoteRunError.d.ts.map +1 -0
- package/dist/remoteRun/remoteRunError.js +28 -0
- package/dist/remoteRun/remoteRunError.js.map +1 -0
- package/dist/remoteRun/rsync.d.ts +17 -0
- package/dist/remoteRun/rsync.d.ts.map +1 -0
- package/dist/remoteRun/rsync.js +39 -0
- package/dist/remoteRun/rsync.js.map +1 -0
- package/dist/remoteRun/runnerClient.d.ts +52 -0
- package/dist/remoteRun/runnerClient.d.ts.map +1 -0
- package/dist/remoteRun/runnerClient.js +143 -0
- package/dist/remoteRun/runnerClient.js.map +1 -0
- package/dist/remoteRun/runnerSummary.d.ts +24 -0
- package/dist/remoteRun/runnerSummary.d.ts.map +1 -0
- package/dist/remoteRun/runnerSummary.js +86 -0
- package/dist/remoteRun/runnerSummary.js.map +1 -0
- package/dist/remoteRun/runnerWait.d.ts +68 -0
- package/dist/remoteRun/runnerWait.d.ts.map +1 -0
- package/dist/remoteRun/runnerWait.js +137 -0
- package/dist/remoteRun/runnerWait.js.map +1 -0
- package/dist/remoteRun/runnerWaitTiming.d.ts +16 -0
- package/dist/remoteRun/runnerWaitTiming.d.ts.map +1 -0
- package/dist/remoteRun/runnerWaitTiming.js +37 -0
- package/dist/remoteRun/runnerWaitTiming.js.map +1 -0
- package/dist/remoteRun/runners.d.ts +57 -0
- package/dist/remoteRun/runners.d.ts.map +1 -0
- package/dist/remoteRun/runners.js +209 -0
- package/dist/remoteRun/runners.js.map +1 -0
- package/dist/remoteRun/runnersCatalog.d.ts +14 -0
- package/dist/remoteRun/runnersCatalog.d.ts.map +1 -0
- package/dist/remoteRun/runnersCatalog.js +68 -0
- package/dist/remoteRun/runnersCatalog.js.map +1 -0
- package/dist/remoteRun/runnersConfig.d.ts +53 -0
- package/dist/remoteRun/runnersConfig.d.ts.map +1 -0
- package/dist/remoteRun/runnersConfig.js +109 -0
- package/dist/remoteRun/runnersConfig.js.map +1 -0
- package/dist/remoteRun/safeCopy.d.ts +31 -0
- package/dist/remoteRun/safeCopy.d.ts.map +1 -0
- package/dist/remoteRun/safeCopy.js +137 -0
- package/dist/remoteRun/safeCopy.js.map +1 -0
- package/dist/remoteRun/sendPlan.d.ts +67 -0
- package/dist/remoteRun/sendPlan.d.ts.map +1 -0
- package/dist/remoteRun/sendPlan.js +139 -0
- package/dist/remoteRun/sendPlan.js.map +1 -0
- package/dist/remoteRun/ssh.d.ts +82 -0
- package/dist/remoteRun/ssh.d.ts.map +1 -0
- package/dist/remoteRun/ssh.js +236 -0
- package/dist/remoteRun/ssh.js.map +1 -0
- package/dist/remoteRun/submit.d.ts +29 -0
- package/dist/remoteRun/submit.d.ts.map +1 -0
- package/dist/remoteRun/submit.js +142 -0
- package/dist/remoteRun/submit.js.map +1 -0
- package/dist/requests/answerRequest.d.ts +5 -0
- package/dist/requests/answerRequest.d.ts.map +1 -1
- package/dist/requests/answerRequest.js +16 -1
- package/dist/requests/answerRequest.js.map +1 -1
- package/dist/requests/requestLedger.d.ts +13 -0
- package/dist/requests/requestLedger.d.ts.map +1 -1
- package/dist/requests/requestLedger.js +9 -0
- package/dist/requests/requestLedger.js.map +1 -1
- package/dist/requests/topicRequestSettle.d.ts +30 -0
- package/dist/requests/topicRequestSettle.d.ts.map +1 -0
- package/dist/requests/topicRequestSettle.js +23 -0
- package/dist/requests/topicRequestSettle.js.map +1 -0
- package/dist/requests/wakeUpEngine.d.ts +4 -0
- package/dist/requests/wakeUpEngine.d.ts.map +1 -1
- package/dist/requests/wakeUpEngine.js +5 -1
- package/dist/requests/wakeUpEngine.js.map +1 -1
- package/dist/runnerUtility/adminCheck.d.ts +11 -0
- package/dist/runnerUtility/adminCheck.d.ts.map +1 -0
- package/dist/runnerUtility/adminCheck.js +68 -0
- package/dist/runnerUtility/adminCheck.js.map +1 -0
- package/dist/runnerUtility/appInstall.d.ts +25 -0
- package/dist/runnerUtility/appInstall.d.ts.map +1 -0
- package/dist/runnerUtility/appInstall.js +147 -0
- package/dist/runnerUtility/appInstall.js.map +1 -0
- package/dist/runnerUtility/appUpdate.d.ts +39 -0
- package/dist/runnerUtility/appUpdate.d.ts.map +1 -0
- package/dist/runnerUtility/appUpdate.js +90 -0
- package/dist/runnerUtility/appUpdate.js.map +1 -0
- package/dist/runnerUtility/execEnv.d.ts +21 -0
- package/dist/runnerUtility/execEnv.d.ts.map +1 -0
- package/dist/runnerUtility/execEnv.js +109 -0
- package/dist/runnerUtility/execEnv.js.map +1 -0
- package/dist/runnerUtility/execSession.d.ts +13 -0
- package/dist/runnerUtility/execSession.d.ts.map +1 -0
- package/dist/runnerUtility/execSession.js +58 -0
- package/dist/runnerUtility/execSession.js.map +1 -0
- package/dist/runnerUtility/hubClient.d.ts +30 -0
- package/dist/runnerUtility/hubClient.d.ts.map +1 -0
- package/dist/runnerUtility/hubClient.js +127 -0
- package/dist/runnerUtility/hubClient.js.map +1 -0
- package/dist/runnerUtility/injectStream.d.ts +9 -0
- package/dist/runnerUtility/injectStream.d.ts.map +1 -0
- package/dist/runnerUtility/injectStream.js +12 -0
- package/dist/runnerUtility/injectStream.js.map +1 -0
- package/dist/runnerUtility/iosCheck.d.ts +62 -0
- package/dist/runnerUtility/iosCheck.d.ts.map +1 -0
- package/dist/runnerUtility/iosCheck.js +200 -0
- package/dist/runnerUtility/iosCheck.js.map +1 -0
- package/dist/runnerUtility/launcher.d.ts +5 -0
- package/dist/runnerUtility/launcher.d.ts.map +1 -0
- package/dist/runnerUtility/launcher.js +85 -0
- package/dist/runnerUtility/launcher.js.map +1 -0
- package/dist/runnerUtility/linkLoop.d.ts +47 -0
- package/dist/runnerUtility/linkLoop.d.ts.map +1 -0
- package/dist/runnerUtility/linkLoop.js +281 -0
- package/dist/runnerUtility/linkLoop.js.map +1 -0
- package/dist/runnerUtility/localLinkStop.d.ts +23 -0
- package/dist/runnerUtility/localLinkStop.d.ts.map +1 -0
- package/dist/runnerUtility/localLinkStop.js +79 -0
- package/dist/runnerUtility/localLinkStop.js.map +1 -0
- package/dist/runnerUtility/machineReport.d.ts +39 -0
- package/dist/runnerUtility/machineReport.d.ts.map +1 -0
- package/dist/runnerUtility/machineReport.js +161 -0
- package/dist/runnerUtility/machineReport.js.map +1 -0
- package/dist/runnerUtility/packageId.d.ts +5 -0
- package/dist/runnerUtility/packageId.d.ts.map +1 -0
- package/dist/runnerUtility/packageId.js +64 -0
- package/dist/runnerUtility/packageId.js.map +1 -0
- package/dist/runnerUtility/pairingRecovery.d.ts +31 -0
- package/dist/runnerUtility/pairingRecovery.d.ts.map +1 -0
- package/dist/runnerUtility/pairingRecovery.js +24 -0
- package/dist/runnerUtility/pairingRecovery.js.map +1 -0
- package/dist/runnerUtility/pueueDaemon.d.ts +36 -0
- package/dist/runnerUtility/pueueDaemon.d.ts.map +1 -0
- package/dist/runnerUtility/pueueDaemon.js +165 -0
- package/dist/runnerUtility/pueueDaemon.js.map +1 -0
- package/dist/runnerUtility/runnerCli.d.ts +18 -0
- package/dist/runnerUtility/runnerCli.d.ts.map +1 -0
- package/dist/runnerUtility/runnerCli.js +184 -0
- package/dist/runnerUtility/runnerCli.js.map +1 -0
- package/dist/runnerUtility/runnerCommands.d.ts +67 -0
- package/dist/runnerUtility/runnerCommands.d.ts.map +1 -0
- package/dist/runnerUtility/runnerCommands.js +418 -0
- package/dist/runnerUtility/runnerCommands.js.map +1 -0
- package/dist/runnerUtility/runnerConfig.d.ts +63 -0
- package/dist/runnerUtility/runnerConfig.d.ts.map +1 -0
- package/dist/runnerUtility/runnerConfig.js +153 -0
- package/dist/runnerUtility/runnerConfig.js.map +1 -0
- package/dist/runnerUtility/runnerFlags.d.ts +9 -0
- package/dist/runnerUtility/runnerFlags.d.ts.map +1 -0
- package/dist/runnerUtility/runnerFlags.js +53 -0
- package/dist/runnerUtility/runnerFlags.js.map +1 -0
- package/dist/runnerUtility/runnerHome.d.ts +30 -0
- package/dist/runnerUtility/runnerHome.d.ts.map +1 -0
- package/dist/runnerUtility/runnerHome.js +82 -0
- package/dist/runnerUtility/runnerHome.js.map +1 -0
- package/dist/runnerUtility/runnerKeys.d.ts +11 -0
- package/dist/runnerUtility/runnerKeys.d.ts.map +1 -0
- package/dist/runnerUtility/runnerKeys.js +80 -0
- package/dist/runnerUtility/runnerKeys.js.map +1 -0
- package/dist/runnerUtility/runnerServers.d.ts +40 -0
- package/dist/runnerUtility/runnerServers.d.ts.map +1 -0
- package/dist/runnerUtility/runnerServers.js +79 -0
- package/dist/runnerUtility/runnerServers.js.map +1 -0
- package/dist/runnerUtility/serviceManager.d.ts +54 -0
- package/dist/runnerUtility/serviceManager.d.ts.map +1 -0
- package/dist/runnerUtility/serviceManager.js +413 -0
- package/dist/runnerUtility/serviceManager.js.map +1 -0
- package/dist/runnerUtility/serviceUnits.d.ts +36 -0
- package/dist/runnerUtility/serviceUnits.d.ts.map +1 -0
- package/dist/runnerUtility/serviceUnits.js +169 -0
- package/dist/runnerUtility/serviceUnits.js.map +1 -0
- package/dist/runnerUtility/toolInstall.d.ts +95 -0
- package/dist/runnerUtility/toolInstall.d.ts.map +1 -0
- package/dist/runnerUtility/toolInstall.js +232 -0
- package/dist/runnerUtility/toolInstall.js.map +1 -0
- package/dist/runnerUtility/versionPollTiming.d.ts +16 -0
- package/dist/runnerUtility/versionPollTiming.d.ts.map +1 -0
- package/dist/runnerUtility/versionPollTiming.js +36 -0
- package/dist/runnerUtility/versionPollTiming.js.map +1 -0
- package/dist/runners/authorizedKeys.d.ts +52 -0
- package/dist/runners/authorizedKeys.d.ts.map +1 -0
- package/dist/runners/authorizedKeys.js +153 -0
- package/dist/runners/authorizedKeys.js.map +1 -0
- package/dist/runners/bootstrapLine.d.ts +4 -0
- package/dist/runners/bootstrapLine.d.ts.map +1 -0
- package/dist/runners/bootstrapLine.js +69 -0
- package/dist/runners/bootstrapLine.js.map +1 -0
- package/dist/runners/gate.d.ts +41 -0
- package/dist/runners/gate.d.ts.map +1 -0
- package/dist/runners/gate.js +216 -0
- package/dist/runners/gate.js.map +1 -0
- package/dist/runners/gateLink.d.ts +26 -0
- package/dist/runners/gateLink.d.ts.map +1 -0
- package/dist/runners/gateLink.js +240 -0
- package/dist/runners/gateLink.js.map +1 -0
- package/dist/runners/gateProtocol.d.ts +98 -0
- package/dist/runners/gateProtocol.d.ts.map +1 -0
- package/dist/runners/gateProtocol.js +149 -0
- package/dist/runners/gateProtocol.js.map +1 -0
- package/dist/runners/gateWrapper.d.ts +6 -0
- package/dist/runners/gateWrapper.d.ts.map +1 -0
- package/dist/runners/gateWrapper.js +70 -0
- package/dist/runners/gateWrapper.js.map +1 -0
- package/dist/runners/hubAddress.d.ts +33 -0
- package/dist/runners/hubAddress.d.ts.map +1 -0
- package/dist/runners/hubAddress.js +88 -0
- package/dist/runners/hubAddress.js.map +1 -0
- package/dist/runners/hubAddressCandidates.d.ts +16 -0
- package/dist/runners/hubAddressCandidates.d.ts.map +1 -0
- package/dist/runners/hubAddressCandidates.js +96 -0
- package/dist/runners/hubAddressCandidates.js.map +1 -0
- package/dist/runners/hubIds.d.ts +16 -0
- package/dist/runners/hubIds.d.ts.map +1 -0
- package/dist/runners/hubIds.js +25 -0
- package/dist/runners/hubIds.js.map +1 -0
- package/dist/runners/hubKey.d.ts +6 -0
- package/dist/runners/hubKey.d.ts.map +1 -0
- package/dist/runners/hubKey.js +78 -0
- package/dist/runners/hubKey.js.map +1 -0
- package/dist/runners/hubLock.d.ts +7 -0
- package/dist/runners/hubLock.d.ts.map +1 -0
- package/dist/runners/hubLock.js +230 -0
- package/dist/runners/hubLock.js.map +1 -0
- package/dist/runners/hubPackage.d.ts +23 -0
- package/dist/runners/hubPackage.d.ts.map +1 -0
- package/dist/runners/hubPackage.js +184 -0
- package/dist/runners/hubPackage.js.map +1 -0
- package/dist/runners/hubPairing.d.ts +63 -0
- package/dist/runners/hubPairing.d.ts.map +1 -0
- package/dist/runners/hubPairing.js +261 -0
- package/dist/runners/hubPairing.js.map +1 -0
- package/dist/runners/hubPaths.d.ts +38 -0
- package/dist/runners/hubPaths.d.ts.map +1 -0
- package/dist/runners/hubPaths.js +94 -0
- package/dist/runners/hubPaths.js.map +1 -0
- package/dist/runners/hubRegistry.d.ts +112 -0
- package/dist/runners/hubRegistry.d.ts.map +1 -0
- package/dist/runners/hubRegistry.js +210 -0
- package/dist/runners/hubRegistry.js.map +1 -0
- package/dist/runners/linkControl.d.ts +27 -0
- package/dist/runners/linkControl.d.ts.map +1 -0
- package/dist/runners/linkControl.js +124 -0
- package/dist/runners/linkControl.js.map +1 -0
- package/dist/runners/pairCode.d.ts +30 -0
- package/dist/runners/pairCode.d.ts.map +1 -0
- package/dist/runners/pairCode.js +46 -0
- package/dist/runners/pairCode.js.map +1 -0
- package/dist/runners/pairingTiming.d.ts +18 -0
- package/dist/runners/pairingTiming.d.ts.map +1 -0
- package/dist/runners/pairingTiming.js +42 -0
- package/dist/runners/pairingTiming.js.map +1 -0
- package/dist/runners/runnerProxy.d.ts +9 -0
- package/dist/runners/runnerProxy.d.ts.map +1 -0
- package/dist/runners/runnerProxy.js +67 -0
- package/dist/runners/runnerProxy.js.map +1 -0
- package/dist/runners/runnerRemoval.d.ts +21 -0
- package/dist/runners/runnerRemoval.d.ts.map +1 -0
- package/dist/runners/runnerRemoval.js +82 -0
- package/dist/runners/runnerRemoval.js.map +1 -0
- package/dist/runners/runnersCli.d.ts +16 -0
- package/dist/runners/runnersCli.d.ts.map +1 -0
- package/dist/runners/runnersCli.js +214 -0
- package/dist/runners/runnersCli.js.map +1 -0
- package/dist/runners/runnersList.d.ts +17 -0
- package/dist/runners/runnersList.d.ts.map +1 -0
- package/dist/runners/runnersList.js +49 -0
- package/dist/runners/runnersList.js.map +1 -0
- package/dist/runners/runnersOverview.d.ts +20 -0
- package/dist/runners/runnersOverview.d.ts.map +1 -0
- package/dist/runners/runnersOverview.js +55 -0
- package/dist/runners/runnersOverview.js.map +1 -0
- package/dist/runners/sshKeys.d.ts +26 -0
- package/dist/runners/sshKeys.d.ts.map +1 -0
- package/dist/runners/sshKeys.js +60 -0
- package/dist/runners/sshKeys.js.map +1 -0
- package/dist/runners/tarArchive.d.ts +20 -0
- package/dist/runners/tarArchive.d.ts.map +1 -0
- package/dist/runners/tarArchive.js +70 -0
- package/dist/runners/tarArchive.js.map +1 -0
- package/dist/scheduler/delivery.d.ts +2 -0
- package/dist/scheduler/delivery.d.ts.map +1 -1
- package/dist/scheduler/delivery.js +8 -4
- package/dist/scheduler/delivery.js.map +1 -1
- package/dist/scheduler/mcpSurface.d.ts +65 -10
- package/dist/scheduler/mcpSurface.d.ts.map +1 -1
- package/dist/scheduler/mcpSurface.js +319 -89
- package/dist/scheduler/mcpSurface.js.map +1 -1
- package/dist/scheduler/recurrence.d.ts +5 -0
- package/dist/scheduler/recurrence.d.ts.map +1 -1
- package/dist/scheduler/recurrence.js +12 -2
- package/dist/scheduler/recurrence.js.map +1 -1
- package/dist/scheduler/strictToolInput.d.ts +45 -0
- package/dist/scheduler/strictToolInput.d.ts.map +1 -0
- package/dist/scheduler/strictToolInput.js +155 -0
- package/dist/scheduler/strictToolInput.js.map +1 -0
- package/dist/scheduler/types.d.ts +5 -0
- package/dist/scheduler/types.d.ts.map +1 -1
- package/dist/scheduler/workerWatch.d.ts +23 -0
- package/dist/scheduler/workerWatch.d.ts.map +1 -0
- package/dist/scheduler/workerWatch.js +26 -0
- package/dist/scheduler/workerWatch.js.map +1 -0
- package/dist/selfUpdate/emittedSource.d.ts +11 -0
- package/dist/selfUpdate/emittedSource.d.ts.map +1 -0
- package/dist/selfUpdate/emittedSource.js +24 -0
- package/dist/selfUpdate/emittedSource.js.map +1 -0
- package/dist/selfUpdate/gitSource.d.ts +0 -6
- package/dist/selfUpdate/gitSource.d.ts.map +1 -1
- package/dist/selfUpdate/gitSource.js +2 -18
- package/dist/selfUpdate/gitSource.js.map +1 -1
- package/dist/selfUpdate/selfCheck.js +2 -2
- package/dist/selfUpdate/selfCheck.js.map +1 -1
- package/dist/selfUpdate/updatePending.d.ts +1 -1
- package/dist/sendErrorClassifier.d.ts +10 -0
- package/dist/sendErrorClassifier.d.ts.map +1 -1
- package/dist/sendErrorClassifier.js +22 -0
- package/dist/sendErrorClassifier.js.map +1 -1
- package/dist/state.d.ts +78 -7
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +125 -8
- package/dist/state.js.map +1 -1
- package/dist/stateFile.d.ts +9 -0
- package/dist/stateFile.d.ts.map +1 -0
- package/dist/stateFile.js +57 -0
- package/dist/stateFile.js.map +1 -0
- package/dist/stt/sttInstall.d.ts +7 -3
- package/dist/stt/sttInstall.d.ts.map +1 -1
- package/dist/stt/sttInstall.js +8 -4
- package/dist/stt/sttInstall.js.map +1 -1
- package/dist/stt/sttSettings.d.ts +2 -1
- package/dist/stt/sttSettings.d.ts.map +1 -1
- package/dist/stt/sttSettings.js +3 -2
- package/dist/stt/sttSettings.js.map +1 -1
- package/dist/stt/transcribeChain.js +2 -0
- package/dist/stt/transcribeChain.js.map +1 -1
- package/dist/testTimings.d.ts +16 -3
- package/dist/testTimings.d.ts.map +1 -1
- package/dist/testTimings.js +35 -2
- package/dist/testTimings.js.map +1 -1
- package/dist/todos/board.d.ts +83 -0
- package/dist/todos/board.d.ts.map +1 -0
- package/dist/todos/board.js +227 -0
- package/dist/todos/board.js.map +1 -0
- package/dist/todos/format.d.ts +33 -0
- package/dist/todos/format.d.ts.map +1 -0
- package/dist/todos/format.js +90 -0
- package/dist/todos/format.js.map +1 -0
- package/dist/todos/merge.d.ts +46 -0
- package/dist/todos/merge.d.ts.map +1 -0
- package/dist/todos/merge.js +137 -0
- package/dist/todos/merge.js.map +1 -0
- package/dist/todos/midTurn.d.ts +75 -0
- package/dist/todos/midTurn.d.ts.map +1 -0
- package/dist/todos/midTurn.js +214 -0
- package/dist/todos/midTurn.js.map +1 -0
- package/dist/todos/nag.d.ts +85 -0
- package/dist/todos/nag.d.ts.map +1 -0
- package/dist/todos/nag.js +103 -0
- package/dist/todos/nag.js.map +1 -0
- package/dist/todos/order.d.ts +25 -0
- package/dist/todos/order.d.ts.map +1 -0
- package/dist/todos/order.js +61 -0
- package/dist/todos/order.js.map +1 -0
- package/dist/todos/projects.d.ts +102 -0
- package/dist/todos/projects.d.ts.map +1 -0
- package/dist/todos/projects.js +342 -0
- package/dist/todos/projects.js.map +1 -0
- package/dist/todos/registry.d.ts +64 -0
- package/dist/todos/registry.d.ts.map +1 -0
- package/dist/todos/registry.js +116 -0
- package/dist/todos/registry.js.map +1 -0
- package/dist/todos/reminderEngine.d.ts +78 -0
- package/dist/todos/reminderEngine.d.ts.map +1 -0
- package/dist/todos/reminderEngine.js +162 -0
- package/dist/todos/reminderEngine.js.map +1 -0
- package/dist/todos/store.d.ts +34 -0
- package/dist/todos/store.d.ts.map +1 -0
- package/dist/todos/store.js +208 -0
- package/dist/todos/store.js.map +1 -0
- package/dist/todos/tools.d.ts +26 -0
- package/dist/todos/tools.d.ts.map +1 -0
- package/dist/todos/tools.js +25 -0
- package/dist/todos/tools.js.map +1 -0
- package/dist/todos/types.d.ts +85 -0
- package/dist/todos/types.d.ts.map +1 -0
- package/dist/todos/types.js +30 -0
- package/dist/todos/types.js.map +1 -0
- package/dist/trackers/taskAgentLimit.d.ts +58 -0
- package/dist/trackers/taskAgentLimit.d.ts.map +1 -0
- package/dist/trackers/taskAgentLimit.js +62 -0
- package/dist/trackers/taskAgentLimit.js.map +1 -0
- package/dist/trackers/taskAgentSlots.d.ts +109 -0
- package/dist/trackers/taskAgentSlots.d.ts.map +1 -0
- package/dist/trackers/taskAgentSlots.js +227 -0
- package/dist/trackers/taskAgentSlots.js.map +1 -0
- package/dist/trackers/types.d.ts +14 -0
- package/dist/trackers/types.d.ts.map +1 -0
- package/dist/trackers/types.js +3 -0
- package/dist/trackers/types.js.map +1 -0
- package/dist/types.d.ts +80 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/adoptReplayGate.d.ts +16 -0
- package/dist/utils/adoptReplayGate.d.ts.map +1 -0
- package/dist/utils/adoptReplayGate.js +35 -0
- package/dist/utils/adoptReplayGate.js.map +1 -0
- package/dist/utils/agentEnvironment.d.ts +6 -2
- package/dist/utils/agentEnvironment.d.ts.map +1 -1
- package/dist/utils/agentEnvironment.js +13 -2
- package/dist/utils/agentEnvironment.js.map +1 -1
- package/dist/utils/agentPlugin.d.ts +113 -0
- package/dist/utils/agentPlugin.d.ts.map +1 -0
- package/dist/utils/agentPlugin.js +192 -0
- package/dist/utils/agentPlugin.js.map +1 -0
- package/dist/utils/autoContinueOnLimit.d.ts +7 -4
- package/dist/utils/autoContinueOnLimit.d.ts.map +1 -1
- package/dist/utils/autoContinueOnLimit.js +8 -5
- package/dist/utils/autoContinueOnLimit.js.map +1 -1
- package/dist/utils/botSkills.d.ts +62 -0
- package/dist/utils/botSkills.d.ts.map +1 -0
- package/dist/utils/botSkills.js +53 -0
- package/dist/utils/botSkills.js.map +1 -0
- package/dist/utils/botSkillsSetting.d.ts +7 -0
- package/dist/utils/botSkillsSetting.d.ts.map +1 -0
- package/dist/utils/botSkillsSetting.js +9 -0
- package/dist/utils/botSkillsSetting.js.map +1 -0
- package/dist/utils/claudeCodeVersion.d.ts +7 -0
- package/dist/utils/claudeCodeVersion.d.ts.map +1 -1
- package/dist/utils/claudeCodeVersion.js +14 -1
- package/dist/utils/claudeCodeVersion.js.map +1 -1
- package/dist/utils/claudeCompactHook.d.ts +33 -15
- package/dist/utils/claudeCompactHook.d.ts.map +1 -1
- package/dist/utils/claudeCompactHook.js +36 -17
- package/dist/utils/claudeCompactHook.js.map +1 -1
- package/dist/utils/claudeCompactSummary.d.ts +15 -0
- package/dist/utils/claudeCompactSummary.d.ts.map +1 -0
- package/dist/utils/claudeCompactSummary.js +96 -0
- package/dist/utils/claudeCompactSummary.js.map +1 -0
- package/dist/utils/claudeLimitSwitch.d.ts +11 -9
- package/dist/utils/claudeLimitSwitch.d.ts.map +1 -1
- package/dist/utils/claudeLimitSwitch.js +18 -16
- package/dist/utils/claudeLimitSwitch.js.map +1 -1
- package/dist/utils/claudeProfiles.d.ts +5 -0
- package/dist/utils/claudeProfiles.d.ts.map +1 -1
- package/dist/utils/claudeProfiles.js +14 -0
- package/dist/utils/claudeProfiles.js.map +1 -1
- package/dist/utils/claudeSessionTitle.d.ts +2 -1
- package/dist/utils/claudeSessionTitle.d.ts.map +1 -1
- package/dist/utils/claudeSessionTitle.js +14 -14
- package/dist/utils/claudeSessionTitle.js.map +1 -1
- package/dist/utils/claudeUsage.d.ts +95 -15
- package/dist/utils/claudeUsage.d.ts.map +1 -1
- package/dist/utils/claudeUsage.js +241 -41
- package/dist/utils/claudeUsage.js.map +1 -1
- package/dist/utils/claudeUsageRefresher.d.ts +10 -0
- package/dist/utils/claudeUsageRefresher.d.ts.map +1 -1
- package/dist/utils/claudeUsageRefresher.js +11 -0
- package/dist/utils/claudeUsageRefresher.js.map +1 -1
- package/dist/utils/claudeUserEntry.d.ts +50 -0
- package/dist/utils/claudeUserEntry.d.ts.map +1 -0
- package/dist/utils/claudeUserEntry.js +120 -0
- package/dist/utils/claudeUserEntry.js.map +1 -0
- package/dist/utils/clearThreadOutputQueues.d.ts +4 -3
- package/dist/utils/clearThreadOutputQueues.d.ts.map +1 -1
- package/dist/utils/clearThreadOutputQueues.js +1 -1
- package/dist/utils/clearThreadOutputQueues.js.map +1 -1
- package/dist/utils/compactOnIdle.d.ts +33 -13
- package/dist/utils/compactOnIdle.d.ts.map +1 -1
- package/dist/utils/compactOnIdle.js +35 -15
- package/dist/utils/compactOnIdle.js.map +1 -1
- package/dist/utils/fallbackBridge.d.ts +5 -0
- package/dist/utils/fallbackBridge.d.ts.map +1 -1
- package/dist/utils/fallbackBridge.js +19 -3
- package/dist/utils/fallbackBridge.js.map +1 -1
- package/dist/utils/fallbackChain.d.ts +9 -3
- package/dist/utils/fallbackChain.d.ts.map +1 -1
- package/dist/utils/fallbackChain.js +21 -3
- package/dist/utils/fallbackChain.js.map +1 -1
- package/dist/utils/fileSendPlan.d.ts +6 -0
- package/dist/utils/fileSendPlan.d.ts.map +1 -1
- package/dist/utils/fileSendPlan.js +11 -0
- package/dist/utils/fileSendPlan.js.map +1 -1
- package/dist/utils/fileSendService.d.ts +7 -1
- package/dist/utils/fileSendService.d.ts.map +1 -1
- package/dist/utils/fileSendService.js +6 -2
- package/dist/utils/fileSendService.js.map +1 -1
- package/dist/utils/groupFinalizePlan.d.ts +8 -10
- package/dist/utils/groupFinalizePlan.d.ts.map +1 -1
- package/dist/utils/groupFinalizePlan.js +8 -12
- package/dist/utils/groupFinalizePlan.js.map +1 -1
- package/dist/utils/jsonStreamHost.d.ts +13 -0
- package/dist/utils/jsonStreamHost.d.ts.map +1 -1
- package/dist/utils/jsonStreamHost.js +13 -0
- package/dist/utils/jsonStreamHost.js.map +1 -1
- package/dist/utils/messageSendService.d.ts +5 -3
- package/dist/utils/messageSendService.d.ts.map +1 -1
- package/dist/utils/messageSendService.js +12 -5
- package/dist/utils/messageSendService.js.map +1 -1
- package/dist/utils/openCodeCompactPlugin.d.ts +39 -14
- package/dist/utils/openCodeCompactPlugin.d.ts.map +1 -1
- package/dist/utils/openCodeCompactPlugin.js +106 -38
- package/dist/utils/openCodeCompactPlugin.js.map +1 -1
- package/dist/utils/promptDelivery.d.ts +29 -12
- package/dist/utils/promptDelivery.d.ts.map +1 -1
- package/dist/utils/promptDelivery.js +13 -5
- package/dist/utils/promptDelivery.js.map +1 -1
- package/dist/utils/runnersScreenPlan.d.ts +130 -0
- package/dist/utils/runnersScreenPlan.d.ts.map +1 -0
- package/dist/utils/runnersScreenPlan.js +257 -0
- package/dist/utils/runnersScreenPlan.js.map +1 -0
- package/dist/utils/sentKindsSummary.d.ts +10 -0
- package/dist/utils/sentKindsSummary.d.ts.map +1 -0
- package/dist/utils/sentKindsSummary.js +18 -0
- package/dist/utils/sentKindsSummary.js.map +1 -0
- package/dist/utils/telegramError.d.ts +6 -0
- package/dist/utils/telegramError.d.ts.map +1 -1
- package/dist/utils/telegramError.js +12 -0
- package/dist/utils/telegramError.js.map +1 -1
- package/dist/utils/topicView.d.ts +1 -1
- package/dist/utils/topicView.js +1 -1
- package/dist/utils/transcribeAudio.d.ts +10 -0
- package/dist/utils/transcribeAudio.d.ts.map +1 -1
- package/dist/utils/transcribeAudio.js +22 -3
- package/dist/utils/transcribeAudio.js.map +1 -1
- package/dist/utils/transcriptErrorTime.d.ts +8 -0
- package/dist/utils/transcriptErrorTime.d.ts.map +1 -0
- package/dist/utils/transcriptErrorTime.js +56 -0
- package/dist/utils/transcriptErrorTime.js.map +1 -0
- package/dist/utils/wedgeRecovery.d.ts +2 -0
- package/dist/utils/wedgeRecovery.d.ts.map +1 -1
- package/dist/utils/wedgeRecovery.js +1 -1
- package/dist/utils/wedgeRecovery.js.map +1 -1
- package/package.json +15 -4
- package/remoteRun/runner.sh +1317 -0
- package/stt/gigaam_sidecar.py +555 -50
package/stt/gigaam_sidecar.py
CHANGED
|
@@ -5,8 +5,10 @@ ONE process: it loads the model at start, binds 127.0.0.1 on an OS-chosen port a
|
|
|
5
5
|
`POST /v1/audio/transcriptions` plus `GET /health`. The bot starts it on demand, adopts it across hot reloads,
|
|
6
6
|
and it takes itself down when it has been idle (`--idle-minutes`) or when its owner process is gone.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Every voice is cut at the centre of each pause of at least 500 ms that Silero VAD finds (the model drops a whole
|
|
9
|
+
phrase that follows a long pause when it decodes several phrases in one pass); no audio is ever discarded, and a
|
|
10
|
+
piece longer than 25 s is cut again at its quietest 50 ms. The pieces are recognised in parallel on a pool of
|
|
11
|
+
threads that share ONE onnxruntime session, strictly in arrival order (a short voice never overtakes an earlier one).
|
|
10
12
|
|
|
11
13
|
It claims the bot's record before it loads the model (an exclusive create, so a second start of the same engine
|
|
12
14
|
exits instead of becoming an unrecorded orphan) and completes it with its port once it listens.
|
|
@@ -21,19 +23,22 @@ Protocol and layout: `src/stt/README.md`.
|
|
|
21
23
|
from __future__ import annotations
|
|
22
24
|
|
|
23
25
|
import argparse
|
|
26
|
+
import collections
|
|
27
|
+
import itertools
|
|
24
28
|
import contextlib
|
|
29
|
+
import dataclasses
|
|
25
30
|
import datetime
|
|
26
31
|
import email.parser
|
|
27
32
|
import email.policy
|
|
28
33
|
import hmac
|
|
29
34
|
import http.server
|
|
30
35
|
import io
|
|
31
|
-
import itertools
|
|
32
36
|
import json
|
|
33
37
|
import logging
|
|
34
38
|
import logging.handlers
|
|
35
39
|
import os
|
|
36
|
-
import
|
|
40
|
+
import select
|
|
41
|
+
import socket
|
|
37
42
|
import signal
|
|
38
43
|
import subprocess
|
|
39
44
|
import sys
|
|
@@ -44,7 +49,7 @@ import traceback
|
|
|
44
49
|
import numpy as np
|
|
45
50
|
|
|
46
51
|
# Bumped with every change of the sidecar's behaviour; the bot hashes it into the adoption check.
|
|
47
|
-
SCRIPT_VERSION = "
|
|
52
|
+
SCRIPT_VERSION = "3"
|
|
48
53
|
MODEL_NAME = "gigaam-v3-e2e-rnnt"
|
|
49
54
|
MODEL_QUANTIZATION = "int8"
|
|
50
55
|
TOKEN_ENV_NAME = "CHARNESS_STT_SIDECAR_TOKEN"
|
|
@@ -56,12 +61,46 @@ EXIT_USAGE = 2
|
|
|
56
61
|
EXIT_DUPLICATE = 4
|
|
57
62
|
|
|
58
63
|
MAX_CHUNK_SECONDS = 25.0
|
|
64
|
+
# The rate the model and the VAD work at: the upload is resampled to it once.
|
|
65
|
+
SPEECH_SAMPLE_RATE = 16000
|
|
66
|
+
VAD_MODEL_NAME = "silero"
|
|
67
|
+
# A gap this long between two stretches of speech the VAD detects ends a piece. Measured on real speech (919 phrases,
|
|
68
|
+
# pauses of 0.3-2 s): 11 phrases lost against 10 when decoding each phrase alone (39 in one pass), and it splits 56 of 356
|
|
69
|
+
# continuous sentences with no loss of accuracy (4.91 % -> 4.86 % WER).
|
|
70
|
+
MIN_PAUSE_MS = 500
|
|
71
|
+
VAD_SPEECH_PAD_MS = 100
|
|
72
|
+
# onnx-asr compares the raw gap with `min_silence_duration_ms + 2 * speech_pad_ms`: given this, a gap of
|
|
73
|
+
# `MIN_PAUSE_MS` is what ends a piece.
|
|
74
|
+
VAD_MIN_SILENCE_MS = MIN_PAUSE_MS - 2 * VAD_SPEECH_PAD_MS
|
|
75
|
+
# The upload is resampled to 16 kHz window by window: onnxruntime keeps the largest buffer a session ever needed, and
|
|
76
|
+
# a 5-minute voice resampled at once raised the peak by ~240 MB (profiled; 30 s windows: ~66 MB). The margin on each
|
|
77
|
+
# side is trimmed away again; with it the joined windows are identical to a one-pass resample on every sample, except
|
|
78
|
+
# that the last one may be dropped (the trims round, the model rounds up: at most 63 µs; measured at every supported
|
|
79
|
+
# rate). Both are whole seconds, so their length at 16 kHz is a whole number of samples for every supported rate.
|
|
80
|
+
RESAMPLE_WINDOW_SECONDS = 30
|
|
81
|
+
RESAMPLE_MARGIN_SECONDS = 1
|
|
82
|
+
# The install check's 1 s of silence is at Telegram's Opus rate, so it runs the resampler too, and a VAD that fails
|
|
83
|
+
# shows as pieces left at this rate (the fallback keeps the file's own rate).
|
|
84
|
+
CHECK_SAMPLE_RATE = 48000
|
|
59
85
|
SILENCE_SEARCH_SECONDS = 8.0
|
|
60
86
|
SILENCE_WINDOW_SECONDS = 0.05
|
|
61
87
|
# The encoder fails on fewer than ~25 ms of audio; a piece this short holds no word anyway.
|
|
62
88
|
MIN_CHUNK_SECONDS = 0.1
|
|
63
89
|
# The sample rates onnx-asr resamples to 16 kHz itself.
|
|
64
90
|
SUPPORTED_SAMPLE_RATES = (8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000)
|
|
91
|
+
# The decoders the install check wants in the `av` package (ffmpeg): AAC (M4A), MP3, Vorbis and Opus.
|
|
92
|
+
FFMPEG_REQUIRED_DECODERS = ("aac", "mp3", "vorbis", "opus")
|
|
93
|
+
# The upload's bytes choose ffmpeg's demuxer, and a few of them open further inputs: a text file in SDP form makes it open
|
|
94
|
+
# an RTP socket and wait for 20 s. A file handed over as bytes needs no protocol, so only the two that read nothing from
|
|
95
|
+
# outside are allowed.
|
|
96
|
+
FFMPEG_PROTOCOL_WHITELIST = "crypto,data"
|
|
97
|
+
# Ogg page header: "OggS", version, a flags byte (bit 1 = first page of a stream), granule position, serial, sequence,
|
|
98
|
+
# checksum, then the number of lace values, which are followed by that many bytes of lace values.
|
|
99
|
+
OGG_CAPTURE_PATTERN = b"OggS"
|
|
100
|
+
OGG_PAGE_HEADER_BYTES = 27
|
|
101
|
+
OGG_FLAGS_OFFSET = 5
|
|
102
|
+
OGG_LACE_COUNT_OFFSET = 26
|
|
103
|
+
OGG_FIRST_PAGE_FLAG = 0x02
|
|
65
104
|
|
|
66
105
|
MAX_BODY_BYTES = 25 * 1024 * 1024
|
|
67
106
|
MAX_HEADER_LINE_BYTES = 8192
|
|
@@ -81,10 +120,20 @@ CLAIM_ATTEMPTS = 3
|
|
|
81
120
|
SECONDS_PER_MINUTE = 60.0
|
|
82
121
|
BEARER_PREFIX = "Bearer "
|
|
83
122
|
TRANSCRIPTIONS_PATH = "/v1/audio/transcriptions"
|
|
123
|
+
# Once a voice is in line the sidecar answers an interim 102 naming the seconds of audio queued ahead of it, so the
|
|
124
|
+
# bot's timeout can count the wait in line (voices are recognised strictly in arrival order).
|
|
125
|
+
QUEUE_REPORT_STATUS = 102
|
|
126
|
+
QUEUED_AUDIO_SECONDS_HEADER = "X-Charness-Queued-Audio-Seconds"
|
|
127
|
+
# How often a waiting handler checks that its client is still connected, and whether the queue ahead grew.
|
|
128
|
+
CLIENT_CHECK_SECONDS = 0.5
|
|
129
|
+
# A waiting voice is told the queue ahead again when it has grown by more than this: a safeguard for a voice ahead whose
|
|
130
|
+
# length was still unknown when this one was reported (see `SIZING_WAIT_SECONDS`).
|
|
131
|
+
QUEUE_REPORT_GROWTH_SECONDS = 1.0
|
|
132
|
+
# A voice takes its place in line before its length is read (reading takes a few milliseconds, more for a long file with
|
|
133
|
+
# no length header), so the voices behind it wait this long at most for that length before they report their queue.
|
|
134
|
+
SIZING_WAIT_SECONDS = 2.0
|
|
84
135
|
HEALTH_PATH = "/health"
|
|
85
136
|
RESPONSE_FORMATS = ("json", "text")
|
|
86
|
-
# The pool queue's last position: a stop sentinel sorts after every real request.
|
|
87
|
-
STOP_PRIORITY = sys.maxsize
|
|
88
137
|
|
|
89
138
|
logger = logging.getLogger("gigaam_sidecar")
|
|
90
139
|
|
|
@@ -102,6 +151,10 @@ class RecognitionTimeout(Exception):
|
|
|
102
151
|
"""A request ran past `--max-seconds`."""
|
|
103
152
|
|
|
104
153
|
|
|
154
|
+
class ClientGone(Exception):
|
|
155
|
+
"""The client hung up while its voice waited (it timed out, or the bot moved on): nobody would read the text."""
|
|
156
|
+
|
|
157
|
+
|
|
105
158
|
# ──────────────────────────────── audio ────────────────────────────────
|
|
106
159
|
|
|
107
160
|
|
|
@@ -132,8 +185,173 @@ def split_at_silences(
|
|
|
132
185
|
return pieces
|
|
133
186
|
|
|
134
187
|
|
|
135
|
-
def
|
|
136
|
-
"""
|
|
188
|
+
def split_at_pauses(audio: np.ndarray, speech_segments: list[tuple[int, int]], sample_rate: int) -> list[np.ndarray]:
|
|
189
|
+
"""Cut `audio` at the centre of the gap between every two consecutive speech segments (sample offsets, in
|
|
190
|
+
order), then cut any piece longer than `MAX_CHUNK_SECONDS` with `split_at_silences`. The segments only choose
|
|
191
|
+
where to cut: the pieces joined give back `audio` exactly, so speech the detector missed is still recognised."""
|
|
192
|
+
cuts = [0]
|
|
193
|
+
for (_, previous_end), (next_start, _) in zip(speech_segments, speech_segments[1:]):
|
|
194
|
+
cut = (previous_end + next_start) // 2
|
|
195
|
+
if cuts[-1] < cut < len(audio):
|
|
196
|
+
cuts.append(cut)
|
|
197
|
+
cuts.append(len(audio))
|
|
198
|
+
pieces: list[np.ndarray] = []
|
|
199
|
+
for start, end in zip(cuts, cuts[1:]):
|
|
200
|
+
pieces.extend(split_at_silences(audio[start:end], sample_rate))
|
|
201
|
+
return pieces
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def split_without_pauses(samples: np.ndarray, sample_rate: int) -> tuple[list[np.ndarray], int]:
|
|
205
|
+
"""The splitter without a VAD: only the 25 s cuts, at the file's own rate."""
|
|
206
|
+
return split_at_silences(samples, sample_rate), sample_rate
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def make_pause_splitter(resample, find_speech):
|
|
210
|
+
"""The request splitter: `resample(samples, rate)` to `SPEECH_SAMPLE_RATE`, then `find_speech(audio)` → speech
|
|
211
|
+
segments, then `split_at_pauses`. Returns the pieces and their rate. A failing resample or VAD falls back to
|
|
212
|
+
`split_without_pauses`: the voice is still transcribed, only cut the old way."""
|
|
213
|
+
|
|
214
|
+
def split(samples: np.ndarray, sample_rate: int) -> tuple[list[np.ndarray], int]:
|
|
215
|
+
try:
|
|
216
|
+
audio = resample(samples, sample_rate)
|
|
217
|
+
speech_segments = find_speech(audio)
|
|
218
|
+
except Exception:
|
|
219
|
+
logger.error("pause detection failed, cutting at %.0f s only: %s", MAX_CHUNK_SECONDS, traceback.format_exc())
|
|
220
|
+
return split_without_pauses(samples, sample_rate)
|
|
221
|
+
return split_at_pauses(audio, speech_segments, SPEECH_SAMPLE_RATE), SPEECH_SAMPLE_RATE
|
|
222
|
+
|
|
223
|
+
return split
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def resample_in_windows(resample_once, samples: np.ndarray, sample_rate: int) -> np.ndarray:
|
|
227
|
+
"""`resample_once(samples, rate)` → 16 kHz, applied to `RESAMPLE_WINDOW_SECONDS` windows that each read
|
|
228
|
+
`RESAMPLE_MARGIN_SECONDS` beyond both ends; the margins are cut off the output again and the windows joined."""
|
|
229
|
+
if sample_rate == SPEECH_SAMPLE_RATE:
|
|
230
|
+
return samples
|
|
231
|
+
ratio = SPEECH_SAMPLE_RATE / sample_rate
|
|
232
|
+
window = RESAMPLE_WINDOW_SECONDS * sample_rate
|
|
233
|
+
margin = RESAMPLE_MARGIN_SECONDS * sample_rate
|
|
234
|
+
parts = []
|
|
235
|
+
for start in range(0, len(samples), window):
|
|
236
|
+
end = min(start + window, len(samples))
|
|
237
|
+
read_from = max(0, start - margin)
|
|
238
|
+
resampled = resample_once(samples[read_from : min(len(samples), end + margin)], sample_rate)
|
|
239
|
+
keep_from = round((start - read_from) * ratio)
|
|
240
|
+
parts.append(resampled[keep_from : keep_from + round((end - start) * ratio)])
|
|
241
|
+
return np.concatenate(parts) if parts else samples[:0]
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def make_model_pause_splitter(model, vad):
|
|
245
|
+
"""`make_pause_splitter` over the loaded model's own resampler and the Silero VAD."""
|
|
246
|
+
|
|
247
|
+
def resample_once(samples: np.ndarray, sample_rate: int) -> np.ndarray:
|
|
248
|
+
waveforms, lengths = model.resampler(samples[None], np.array([len(samples)], dtype=np.int64), sample_rate)
|
|
249
|
+
return np.ascontiguousarray(waveforms[0][: int(lengths[0])], dtype=np.float32)
|
|
250
|
+
|
|
251
|
+
def resample(samples: np.ndarray, sample_rate: int) -> np.ndarray:
|
|
252
|
+
return resample_in_windows(resample_once, samples, sample_rate)
|
|
253
|
+
|
|
254
|
+
def find_speech(audio: np.ndarray) -> list[tuple[int, int]]:
|
|
255
|
+
(segments,) = vad.segment_batch(
|
|
256
|
+
audio[None],
|
|
257
|
+
np.array([len(audio)], dtype=np.int64),
|
|
258
|
+
SPEECH_SAMPLE_RATE,
|
|
259
|
+
min_silence_duration_ms=VAD_MIN_SILENCE_MS,
|
|
260
|
+
speech_pad_ms=VAD_SPEECH_PAD_MS,
|
|
261
|
+
# Never let the VAD cut a long stretch of speech itself: `split_at_pauses` caps the pieces at a quiet spot.
|
|
262
|
+
max_speech_duration_s=len(audio) / SPEECH_SAMPLE_RATE + 1,
|
|
263
|
+
)
|
|
264
|
+
return [(int(start), int(end)) for start, end in segments]
|
|
265
|
+
|
|
266
|
+
return make_pause_splitter(resample, find_speech)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def find_ogg_stream_starts(data: bytes) -> list[int]:
|
|
270
|
+
"""The offsets where each logical stream of an Ogg file begins: more than one means chained streams (two files one
|
|
271
|
+
after the other). Only the page headers are walked (a packet that happens to hold "OggS" is not read), so the cost
|
|
272
|
+
is a few bytes per page. Streams multiplexed side by side open with consecutive first pages and count once. Empty
|
|
273
|
+
when the data does not start with an Ogg page; a damaged page ends the walk, the rest counts as the last stream."""
|
|
274
|
+
starts: list[int] = []
|
|
275
|
+
is_in_opening_pages = False
|
|
276
|
+
offset = 0
|
|
277
|
+
while offset + OGG_PAGE_HEADER_BYTES <= len(data) and data.startswith(OGG_CAPTURE_PATTERN, offset):
|
|
278
|
+
lace_end = offset + OGG_PAGE_HEADER_BYTES + data[offset + OGG_LACE_COUNT_OFFSET]
|
|
279
|
+
if lace_end > len(data):
|
|
280
|
+
break
|
|
281
|
+
is_first_page = bool(data[offset + OGG_FLAGS_OFFSET] & OGG_FIRST_PAGE_FLAG)
|
|
282
|
+
if is_first_page and not is_in_opening_pages:
|
|
283
|
+
starts.append(offset)
|
|
284
|
+
is_in_opening_pages = is_first_page
|
|
285
|
+
offset = lace_end + sum(data[offset + OGG_PAGE_HEADER_BYTES : lace_end])
|
|
286
|
+
return starts
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def split_ogg_streams(data: bytes) -> list[bytes]:
|
|
290
|
+
"""The streams of a chained Ogg file as separate files (the file itself when it holds one or is not Ogg)."""
|
|
291
|
+
starts = find_ogg_stream_starts(data)
|
|
292
|
+
if len(starts) < 2:
|
|
293
|
+
return [data]
|
|
294
|
+
return [data[start:end] for start, end in zip(starts, starts[1:] + [len(data)])]
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@dataclasses.dataclass(frozen=True)
|
|
298
|
+
class AudioUpload:
|
|
299
|
+
"""An uploaded file and the route it takes, decided ONCE by `inspect_audio` and handed to the length read and the
|
|
300
|
+
decode: `header_seconds` is libsndfile's own length when libsndfile reads the file whole, None when ffmpeg decodes it;
|
|
301
|
+
`streams` are what ffmpeg is given (the file, or the streams of a chained Ogg apart)."""
|
|
302
|
+
|
|
303
|
+
data: bytes
|
|
304
|
+
header_seconds: float | None
|
|
305
|
+
streams: list[bytes]
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def inspect_audio(data: bytes) -> AudioUpload:
|
|
309
|
+
"""Decide how an upload is read. libsndfile reads as many frames as the file's header says, which is exact for WAV,
|
|
310
|
+
FLAC and one Ogg stream. It is not for an MP3 without a Xing / Info / VBRI frame (the length is an estimate from
|
|
311
|
+
the first frame's bitrate, and the read stops there: a variable-bitrate file lost 33 s of 40) nor for chained Ogg
|
|
312
|
+
streams (only the first is read, and a chained Vorbis even reports an absurd length); and it cannot open M4A / AAC
|
|
313
|
+
at all. Those go to ffmpeg."""
|
|
314
|
+
import soundfile
|
|
315
|
+
|
|
316
|
+
streams = split_ogg_streams(data)
|
|
317
|
+
try:
|
|
318
|
+
info = soundfile.info(io.BytesIO(data))
|
|
319
|
+
except Exception: # soundfile raises its own LibsndfileError / RuntimeError
|
|
320
|
+
return AudioUpload(data, None, streams)
|
|
321
|
+
if info.format == "MP3" or len(streams) > 1:
|
|
322
|
+
return AudioUpload(data, None, streams)
|
|
323
|
+
return AudioUpload(data, float(info.duration), streams)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def open_ffmpeg_container(data: bytes):
|
|
327
|
+
"""The uploaded bytes as an ffmpeg container that cannot reach beyond them (see `FFMPEG_PROTOCOL_WHITELIST`)."""
|
|
328
|
+
import av
|
|
329
|
+
|
|
330
|
+
return av.open(io.BytesIO(data), options={"protocol_whitelist": FFMPEG_PROTOCOL_WHITELIST})
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def read_ffmpeg_stream_seconds(data: bytes) -> float:
|
|
334
|
+
"""The length of one stream from its packets' durations: exact for MP3 without a length header and for M4A, no
|
|
335
|
+
decoding (the container's own duration is an estimate there)."""
|
|
336
|
+
with open_ffmpeg_container(data) as container:
|
|
337
|
+
stream = container.streams.audio[0]
|
|
338
|
+
return sum((float(packet.duration * packet.time_base) for packet in container.demux(stream) if packet.duration), 0.0)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def read_audio_seconds(upload: AudioUpload) -> float:
|
|
342
|
+
"""The upload's length from the file (a few milliseconds, a 5-minute decode takes seconds), so the voices behind it
|
|
343
|
+
know their wait before it is decoded; 0 when it cannot be read (the decode then refuses it). What ffmpeg decodes is
|
|
344
|
+
demuxed here without decoding and once more by the decode: the early, cheap pass is the point."""
|
|
345
|
+
if upload.header_seconds is not None:
|
|
346
|
+
return upload.header_seconds
|
|
347
|
+
try:
|
|
348
|
+
return sum(read_ffmpeg_stream_seconds(stream) for stream in upload.streams)
|
|
349
|
+
except Exception: # av raises its own FFmpegError subclasses
|
|
350
|
+
return 0.0
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def decode_with_libsndfile(data: bytes) -> tuple[np.ndarray, int]:
|
|
354
|
+
"""Mono float32 samples and the sample rate of a file libsndfile reads whole (Telegram's OGG/Opus, WAV, FLAC)."""
|
|
137
355
|
import soundfile
|
|
138
356
|
|
|
139
357
|
try:
|
|
@@ -147,6 +365,59 @@ def decode_audio(data: bytes) -> tuple[np.ndarray, int]:
|
|
|
147
365
|
return np.ascontiguousarray(samples, dtype=np.float32), int(sample_rate)
|
|
148
366
|
|
|
149
367
|
|
|
368
|
+
def get_mono_samples(frames) -> list[np.ndarray]:
|
|
369
|
+
"""The channels of resampled planar float frames averaged into one, as for libsndfile (ffmpeg's own downmix is
|
|
370
|
+
3 dB louder)."""
|
|
371
|
+
return [frame.to_ndarray().mean(axis=0, dtype=np.float32) for frame in frames]
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def decode_ffmpeg_stream(data: bytes) -> list[np.ndarray]:
|
|
375
|
+
"""The first audio stream of one file as mono float32 arrays at `SPEECH_SAMPLE_RATE` (ffmpeg resamples). A resampler
|
|
376
|
+
is set up for the format, layout and rate of the frames in front of it and replaced when they change: it refuses a
|
|
377
|
+
frame that does not match its setup, and passes a frame through untouched when its first one needed no conversion."""
|
|
378
|
+
import av
|
|
379
|
+
|
|
380
|
+
pieces: list[np.ndarray] = []
|
|
381
|
+
with open_ffmpeg_container(data) as container:
|
|
382
|
+
if not container.streams.audio:
|
|
383
|
+
raise RequestError(400, "the file has no audio")
|
|
384
|
+
resampler = None
|
|
385
|
+
setup = None
|
|
386
|
+
for frame in container.decode(audio=0):
|
|
387
|
+
frame_setup = (frame.format.name, frame.layout.name, frame.sample_rate)
|
|
388
|
+
if frame_setup != setup:
|
|
389
|
+
if resampler is not None:
|
|
390
|
+
pieces.extend(get_mono_samples(resampler.resample(None))) # the old setup's delay, before the new part
|
|
391
|
+
resampler = av.AudioResampler(format="fltp", layout=frame.layout.name, rate=SPEECH_SAMPLE_RATE)
|
|
392
|
+
setup = frame_setup
|
|
393
|
+
pieces.extend(get_mono_samples(resampler.resample(frame)))
|
|
394
|
+
if resampler is not None:
|
|
395
|
+
pieces.extend(get_mono_samples(resampler.resample(None))) # the resampler's delay
|
|
396
|
+
return pieces
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def decode_with_ffmpeg(streams: list[bytes]) -> tuple[np.ndarray, int]:
|
|
400
|
+
"""Decode with the ffmpeg libraries of the `av` package what libsndfile does not read whole (see `inspect_audio`).
|
|
401
|
+
The streams of a chained Ogg are decoded one by one and joined, so streams that differ in codec or rate still make one
|
|
402
|
+
voice. Always mono float32 at `SPEECH_SAMPLE_RATE`."""
|
|
403
|
+
try:
|
|
404
|
+
pieces = [piece for stream in streams for piece in decode_ffmpeg_stream(stream)]
|
|
405
|
+
except RequestError:
|
|
406
|
+
raise
|
|
407
|
+
except Exception as error: # av raises its own FFmpegError subclasses
|
|
408
|
+
raise RequestError(400, "the audio file could not be decoded") from error
|
|
409
|
+
if not pieces:
|
|
410
|
+
raise RequestError(400, "the audio file could not be decoded")
|
|
411
|
+
return np.ascontiguousarray(np.concatenate(pieces), dtype=np.float32), SPEECH_SAMPLE_RATE
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def decode_audio(upload: AudioUpload) -> tuple[np.ndarray, int]:
|
|
415
|
+
"""Decode an uploaded file (Telegram sends OGG/Opus) to mono float32 samples and their sample rate."""
|
|
416
|
+
if upload.header_seconds is not None:
|
|
417
|
+
return decode_with_libsndfile(upload.data)
|
|
418
|
+
return decode_with_ffmpeg(upload.streams)
|
|
419
|
+
|
|
420
|
+
|
|
150
421
|
def make_recognizer(model):
|
|
151
422
|
"""The pool's recognise callback over a loaded model. A piece too short to hold a word is empty text, not
|
|
152
423
|
an error: the encoder cannot take it."""
|
|
@@ -163,12 +434,20 @@ def make_recognizer(model):
|
|
|
163
434
|
|
|
164
435
|
|
|
165
436
|
class PoolRequest:
|
|
166
|
-
"""One upload
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
self.
|
|
171
|
-
self.
|
|
437
|
+
"""One upload in the pool: its place in the arrival order, then its pieces once they are cut. Collects their
|
|
438
|
+
texts in order and tells the waiting handler when all are in."""
|
|
439
|
+
|
|
440
|
+
def __init__(self, on_finished):
|
|
441
|
+
self.sample_rate = 0
|
|
442
|
+
self._on_finished = on_finished
|
|
443
|
+
self._chunks: list[np.ndarray] | None = None
|
|
444
|
+
self._next_chunk = 0
|
|
445
|
+
# The voice's length once read from the file, then once decoded: what it counts for the requests behind it while
|
|
446
|
+
# it is still being cut. `_is_sized` is set when the first of them is known (or the request ended).
|
|
447
|
+
self._audio_seconds = 0.0
|
|
448
|
+
self._is_sized = threading.Event()
|
|
449
|
+
self._texts: list[str | None] = []
|
|
450
|
+
self._remaining = 0
|
|
172
451
|
self._error: Exception | None = None
|
|
173
452
|
self._lock = threading.Lock()
|
|
174
453
|
self._finished = threading.Event()
|
|
@@ -176,41 +455,109 @@ class PoolRequest:
|
|
|
176
455
|
def is_finished(self) -> bool:
|
|
177
456
|
return self._finished.is_set()
|
|
178
457
|
|
|
458
|
+
def attach_chunks(self, chunks: list[np.ndarray], sample_rate: int) -> None:
|
|
459
|
+
"""The pieces are cut: from now on the pool hands them out. No pieces is empty text at once."""
|
|
460
|
+
with self._lock:
|
|
461
|
+
self.sample_rate = sample_rate
|
|
462
|
+
self._texts = [None] * len(chunks)
|
|
463
|
+
self._remaining = len(chunks)
|
|
464
|
+
self._chunks = list(chunks)
|
|
465
|
+
is_empty = not chunks
|
|
466
|
+
if is_empty:
|
|
467
|
+
self._finished.set()
|
|
468
|
+
if is_empty:
|
|
469
|
+
self._on_finished()
|
|
470
|
+
|
|
471
|
+
def note_audio_seconds(self, seconds: float) -> None:
|
|
472
|
+
self._audio_seconds = seconds
|
|
473
|
+
self._is_sized.set()
|
|
474
|
+
|
|
475
|
+
def wait_until_sized(self, timeout_seconds: float) -> None:
|
|
476
|
+
"""Block until the length is noted or the request ended — at most `timeout_seconds`."""
|
|
477
|
+
self._is_sized.wait(timeout_seconds)
|
|
478
|
+
|
|
479
|
+
def is_cut(self) -> bool:
|
|
480
|
+
return self._chunks is not None
|
|
481
|
+
|
|
482
|
+
def get_remaining_audio_seconds(self) -> float:
|
|
483
|
+
"""Audio still to recognise: the pieces not finished (the running ones included), or the decoded length while
|
|
484
|
+
the voice is still being cut."""
|
|
485
|
+
with self._lock:
|
|
486
|
+
if self._finished.is_set():
|
|
487
|
+
return 0.0
|
|
488
|
+
if self._chunks is None:
|
|
489
|
+
return self._audio_seconds
|
|
490
|
+
samples = sum(len(chunk) for chunk, text in zip(self._chunks, self._texts) if text is None)
|
|
491
|
+
return samples / self.sample_rate if self.sample_rate else 0.0
|
|
492
|
+
|
|
493
|
+
def has_unclaimed_chunk(self) -> bool:
|
|
494
|
+
return self._chunks is not None and self._next_chunk < len(self._chunks)
|
|
495
|
+
|
|
496
|
+
def count_unclaimed_chunks(self) -> int:
|
|
497
|
+
return len(self._chunks) - self._next_chunk if self._chunks is not None else 0
|
|
498
|
+
|
|
499
|
+
def claim_next_chunk(self) -> tuple[int, np.ndarray]:
|
|
500
|
+
"""The next piece for a worker (the pool calls this under its own lock, so the pieces go out in order)."""
|
|
501
|
+
index = self._next_chunk
|
|
502
|
+
self._next_chunk += 1
|
|
503
|
+
return index, self._chunks[index]
|
|
504
|
+
|
|
179
505
|
def complete_chunk(self, index: int, text: str) -> None:
|
|
180
506
|
with self._lock:
|
|
181
507
|
if self._finished.is_set():
|
|
182
508
|
return
|
|
183
509
|
self._texts[index] = text
|
|
184
510
|
self._remaining -= 1
|
|
185
|
-
|
|
511
|
+
is_last = self._remaining == 0
|
|
512
|
+
if is_last:
|
|
186
513
|
self._finished.set()
|
|
514
|
+
if is_last:
|
|
515
|
+
self._on_finished()
|
|
187
516
|
|
|
188
517
|
def fail(self, error: Exception) -> None:
|
|
189
|
-
"""Fail the whole request; the pool drops its
|
|
518
|
+
"""Fail the whole request; the pool drops its pieces that were not handed out yet."""
|
|
190
519
|
with self._lock:
|
|
191
|
-
|
|
520
|
+
is_first_end = not self._finished.is_set()
|
|
521
|
+
if is_first_end:
|
|
192
522
|
self._error = error
|
|
193
523
|
self._finished.set()
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
524
|
+
self._is_sized.set()
|
|
525
|
+
if is_first_end:
|
|
526
|
+
self._on_finished()
|
|
527
|
+
|
|
528
|
+
def wait(self, timeout_seconds: float, is_abandoned=lambda: False, on_tick=lambda: None) -> str:
|
|
529
|
+
"""The chunks' texts joined in order with single spaces; raises the first chunk error, `RecognitionTimeout`
|
|
530
|
+
after `timeout_seconds`, or `ClientGone` once `is_abandoned()` says the client hung up (checked every
|
|
531
|
+
`CLIENT_CHECK_SECONDS`, when `on_tick()` also runs); in both cases the pieces not handed out yet are dropped."""
|
|
532
|
+
deadline = time.monotonic() + timeout_seconds
|
|
533
|
+
while not self._finished.wait(max(0.0, min(CLIENT_CHECK_SECONDS, deadline - time.monotonic()))):
|
|
534
|
+
on_tick()
|
|
535
|
+
if is_abandoned():
|
|
536
|
+
self.fail(ClientGone())
|
|
537
|
+
elif time.monotonic() >= deadline:
|
|
538
|
+
self.fail(RecognitionTimeout())
|
|
200
539
|
if self._error is not None:
|
|
201
540
|
raise self._error
|
|
202
541
|
return " ".join(text.strip() for text in self._texts if text and text.strip())
|
|
203
542
|
|
|
204
543
|
|
|
544
|
+
class RequestCancelled(Exception):
|
|
545
|
+
"""The upload never got its pieces (it could not be decoded): its place in the order is given up."""
|
|
546
|
+
|
|
547
|
+
|
|
205
548
|
class RecognitionPool:
|
|
206
|
-
"""W threads over
|
|
207
|
-
|
|
549
|
+
"""W threads over the requests in strict arrival order (FIFO). A request takes its place when it arrives
|
|
550
|
+
(`open_request`, before its audio is decoded and cut), so a voice that is quicker to cut never overtakes one that
|
|
551
|
+
came earlier; the workers hand out the pieces of the first request with pieces left, in order, before any piece of
|
|
552
|
+
the next one, and wait while a request ahead is still being cut. A request stays listed until it is finished, so
|
|
553
|
+
the audio queued ahead of a voice counts the pieces still running too."""
|
|
208
554
|
|
|
209
555
|
def __init__(self, recognize, workers: int):
|
|
210
556
|
self._recognize = recognize
|
|
211
557
|
self._workers = workers
|
|
212
|
-
self.
|
|
213
|
-
self.
|
|
558
|
+
self._condition = threading.Condition()
|
|
559
|
+
self._requests: collections.deque[PoolRequest] = collections.deque()
|
|
560
|
+
self._is_stopping = False
|
|
214
561
|
self._threads: list[threading.Thread] = []
|
|
215
562
|
|
|
216
563
|
def start(self) -> None:
|
|
@@ -220,24 +567,76 @@ class RecognitionPool:
|
|
|
220
567
|
self._threads.append(thread)
|
|
221
568
|
|
|
222
569
|
def stop(self) -> None:
|
|
223
|
-
|
|
224
|
-
|
|
570
|
+
"""The workers finish the piece they hold and leave; pieces not handed out yet are dropped."""
|
|
571
|
+
with self._condition:
|
|
572
|
+
self._is_stopping = True
|
|
573
|
+
self._condition.notify_all()
|
|
225
574
|
|
|
226
575
|
def queued_chunks(self) -> int:
|
|
227
|
-
"""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
576
|
+
"""Pieces waiting for a worker."""
|
|
577
|
+
with self._condition:
|
|
578
|
+
return sum(request.count_unclaimed_chunks() for request in self._requests if not request.is_finished())
|
|
579
|
+
|
|
580
|
+
def count_cutting_requests(self) -> int:
|
|
581
|
+
"""Requests in line whose audio is still being decoded and cut."""
|
|
582
|
+
with self._condition:
|
|
583
|
+
return sum(1 for request in self._requests if not request.is_finished() and not request.is_cut())
|
|
584
|
+
|
|
585
|
+
def get_audio_seconds_ahead(self, request: PoolRequest) -> float:
|
|
586
|
+
"""Seconds of audio that will be recognised before `request`: everything unfinished that arrived earlier."""
|
|
587
|
+
with self._condition:
|
|
588
|
+
ahead = itertools.takewhile(lambda listed: listed is not request, self._requests)
|
|
589
|
+
return sum(listed.get_remaining_audio_seconds() for listed in ahead)
|
|
590
|
+
|
|
591
|
+
def wait_until_sized_ahead(self, request: PoolRequest, timeout_seconds: float) -> None:
|
|
592
|
+
"""Wait (at most `timeout_seconds` in all) until every voice that arrived before `request` has its length
|
|
593
|
+
noted, so the queue reported to `request` counts it. A voice takes its place before its length is read."""
|
|
594
|
+
with self._condition:
|
|
595
|
+
ahead = list(itertools.takewhile(lambda listed: listed is not request, self._requests))
|
|
596
|
+
deadline = time.monotonic() + timeout_seconds
|
|
597
|
+
for listed in ahead:
|
|
598
|
+
listed.wait_until_sized(max(0.0, deadline - time.monotonic()))
|
|
599
|
+
|
|
600
|
+
def open_request(self) -> PoolRequest:
|
|
601
|
+
"""Take the next place in the arrival order; `submit` or `cancel` must follow."""
|
|
602
|
+
with self._condition:
|
|
603
|
+
request = PoolRequest(self._wake_workers)
|
|
604
|
+
self._requests.append(request)
|
|
605
|
+
return request
|
|
606
|
+
|
|
607
|
+
def submit(self, request: PoolRequest, chunks: list[np.ndarray], sample_rate: int) -> None:
|
|
608
|
+
request.attach_chunks(chunks, sample_rate)
|
|
609
|
+
self._wake_workers()
|
|
610
|
+
|
|
611
|
+
def cancel(self, request: PoolRequest) -> None:
|
|
612
|
+
request.fail(RequestCancelled())
|
|
613
|
+
|
|
614
|
+
def _wake_workers(self) -> None:
|
|
615
|
+
with self._condition:
|
|
616
|
+
self._condition.notify_all()
|
|
617
|
+
|
|
618
|
+
def _take_next_chunk(self) -> tuple[PoolRequest, int, np.ndarray] | None:
|
|
619
|
+
with self._condition:
|
|
620
|
+
while not self._is_stopping:
|
|
621
|
+
while self._requests and self._requests[0].is_finished():
|
|
622
|
+
self._requests.popleft()
|
|
623
|
+
for listed in self._requests:
|
|
624
|
+
if listed.is_finished():
|
|
625
|
+
continue
|
|
626
|
+
if listed.has_unclaimed_chunk():
|
|
627
|
+
index, samples = listed.claim_next_chunk()
|
|
628
|
+
return listed, index, samples
|
|
629
|
+
if not listed.is_cut():
|
|
630
|
+
break # still being cut: nothing behind it may go first
|
|
631
|
+
self._condition.wait()
|
|
632
|
+
return None
|
|
235
633
|
|
|
236
634
|
def _work(self) -> None:
|
|
237
635
|
while True:
|
|
238
|
-
|
|
239
|
-
if
|
|
636
|
+
taken = self._take_next_chunk()
|
|
637
|
+
if taken is None:
|
|
240
638
|
return
|
|
639
|
+
request, index, samples = taken
|
|
241
640
|
if request.is_finished():
|
|
242
641
|
continue
|
|
243
642
|
try:
|
|
@@ -317,10 +716,20 @@ class SidecarContext:
|
|
|
317
716
|
"""What the handlers and the monitors share."""
|
|
318
717
|
|
|
319
718
|
def __init__(
|
|
320
|
-
self,
|
|
719
|
+
self,
|
|
720
|
+
token: str,
|
|
721
|
+
pool: RecognitionPool,
|
|
722
|
+
split_audio,
|
|
723
|
+
workers: int,
|
|
724
|
+
idle_seconds: float,
|
|
725
|
+
max_seconds: float,
|
|
726
|
+
clock=time.monotonic,
|
|
321
727
|
):
|
|
728
|
+
"""`split_audio(samples, sample_rate)` → (pieces, their sample rate): `make_model_pause_splitter`, or
|
|
729
|
+
`split_without_pauses` when the VAD could not be loaded."""
|
|
322
730
|
self.token = token
|
|
323
731
|
self.pool = pool
|
|
732
|
+
self.split_audio = split_audio
|
|
324
733
|
self.workers = workers
|
|
325
734
|
self.idle_seconds = idle_seconds
|
|
326
735
|
self.max_seconds = max_seconds
|
|
@@ -394,6 +803,7 @@ class SidecarHandler(http.server.BaseHTTPRequestHandler):
|
|
|
394
803
|
"status": "ok",
|
|
395
804
|
"busyRequests": context.busy_requests,
|
|
396
805
|
"queuedChunks": context.pool.queued_chunks(),
|
|
806
|
+
"cuttingRequests": context.pool.count_cutting_requests(),
|
|
397
807
|
"workers": context.workers,
|
|
398
808
|
"idleMinutes": context.idle_seconds / SECONDS_PER_MINUTE,
|
|
399
809
|
"version": SCRIPT_VERSION,
|
|
@@ -417,6 +827,8 @@ class SidecarHandler(http.server.BaseHTTPRequestHandler):
|
|
|
417
827
|
self._reply_error(error.status, error.message)
|
|
418
828
|
except RecognitionTimeout:
|
|
419
829
|
self._reply_error(504, "recognition took too long")
|
|
830
|
+
except ClientGone:
|
|
831
|
+
self.close_connection = True
|
|
420
832
|
except Exception as error:
|
|
421
833
|
logger.error("request failed: %s", traceback.format_exc())
|
|
422
834
|
self._reply_error(500, f"recognition failed: {type(error).__name__}")
|
|
@@ -438,18 +850,68 @@ class SidecarHandler(http.server.BaseHTTPRequestHandler):
|
|
|
438
850
|
response_format = fields.get("response_format", "json")
|
|
439
851
|
if response_format not in RESPONSE_FORMATS:
|
|
440
852
|
raise RequestError(400, f"response_format must be one of {', '.join(RESPONSE_FORMATS)}")
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
853
|
+
# The upload is in: its place in the arrival order is taken before the decoding and the cutting.
|
|
854
|
+
request = context.pool.open_request()
|
|
855
|
+
try:
|
|
856
|
+
upload = inspect_audio(file_bytes)
|
|
857
|
+
request.note_audio_seconds(read_audio_seconds(upload))
|
|
858
|
+
samples, sample_rate = decode_audio(upload)
|
|
859
|
+
audio_seconds = len(samples) / sample_rate
|
|
860
|
+
request.note_audio_seconds(audio_seconds)
|
|
861
|
+
chunks, chunk_sample_rate = context.split_audio(samples, sample_rate)
|
|
862
|
+
# The pieces hold what is needed: the decoded file would only take memory while the voice waits.
|
|
863
|
+
del samples, file_bytes, body, upload
|
|
864
|
+
except BaseException:
|
|
865
|
+
context.pool.cancel(request)
|
|
866
|
+
raise
|
|
867
|
+
context.pool.submit(request, chunks, chunk_sample_rate)
|
|
868
|
+
context.pool.wait_until_sized_ahead(request, SIZING_WAIT_SECONDS)
|
|
869
|
+
reported_seconds_ahead = context.pool.get_audio_seconds_ahead(request)
|
|
870
|
+
|
|
871
|
+
def report_a_longer_queue() -> None:
|
|
872
|
+
nonlocal reported_seconds_ahead
|
|
873
|
+
seconds_ahead = context.pool.get_audio_seconds_ahead(request)
|
|
874
|
+
if seconds_ahead > reported_seconds_ahead + QUEUE_REPORT_GROWTH_SECONDS:
|
|
875
|
+
self._report_queue(seconds_ahead)
|
|
876
|
+
reported_seconds_ahead = seconds_ahead
|
|
877
|
+
|
|
878
|
+
try:
|
|
879
|
+
self._report_queue(reported_seconds_ahead)
|
|
880
|
+
# A failing piece re-raises here; an OSError of the recognition itself is OUR failure and goes on to the 500.
|
|
881
|
+
text = request.wait(context.max_seconds, self._is_client_gone, report_a_longer_queue)
|
|
882
|
+
except ClientGone:
|
|
883
|
+
request.fail(ClientGone()) # a report that could not be written ends the wait too (no-op once failed)
|
|
884
|
+
logger.info("the client left while %.1fs of audio waited; dropped", audio_seconds)
|
|
885
|
+
raise
|
|
445
886
|
logger.info(
|
|
446
|
-
"transcribed %.1fs of audio in %d chunk(s), %.2fs",
|
|
887
|
+
"transcribed %.1fs of audio in %d chunk(s), %.2fs", audio_seconds, len(chunks), time.monotonic() - started
|
|
447
888
|
)
|
|
448
889
|
if response_format == "text":
|
|
449
890
|
self._reply(200, text.encode("utf-8"), "text/plain; charset=utf-8")
|
|
450
891
|
else:
|
|
451
892
|
self._reply_json(200, {"text": text})
|
|
452
893
|
|
|
894
|
+
def _report_queue(self, seconds_ahead: float) -> None:
|
|
895
|
+
"""The interim 102: the voice is in line, with this much audio before it. A report that cannot be written means
|
|
896
|
+
the client is gone (`ClientGone`)."""
|
|
897
|
+
try:
|
|
898
|
+
self._write_queue_report(seconds_ahead)
|
|
899
|
+
except OSError as error:
|
|
900
|
+
raise ClientGone() from error
|
|
901
|
+
|
|
902
|
+
def _write_queue_report(self, seconds_ahead: float) -> None:
|
|
903
|
+
self.send_response_only(QUEUE_REPORT_STATUS)
|
|
904
|
+
self.send_header(QUEUED_AUDIO_SECONDS_HEADER, f"{seconds_ahead:.1f}")
|
|
905
|
+
self.end_headers()
|
|
906
|
+
|
|
907
|
+
def _is_client_gone(self) -> bool:
|
|
908
|
+
"""The client closed its end (its request is fully read, so a readable socket with nothing to read is EOF)."""
|
|
909
|
+
try:
|
|
910
|
+
readable, _, _ = select.select([self.connection], [], [], 0)
|
|
911
|
+
return bool(readable) and self.connection.recv(1, socket.MSG_PEEK) == b""
|
|
912
|
+
except OSError:
|
|
913
|
+
return True
|
|
914
|
+
|
|
453
915
|
def _reply_json(self, status: int, payload: dict) -> None:
|
|
454
916
|
self._reply(status, json.dumps(payload, ensure_ascii=False).encode("utf-8"), "application/json; charset=utf-8")
|
|
455
917
|
|
|
@@ -625,8 +1087,41 @@ def load_model(threads: int):
|
|
|
625
1087
|
return onnx_asr.load_model(MODEL_NAME, quantization=MODEL_QUANTIZATION, sess_options=options)
|
|
626
1088
|
|
|
627
1089
|
|
|
1090
|
+
def load_vad():
|
|
1091
|
+
"""Silero VAD on one thread: it runs once per request, in that request's handler thread."""
|
|
1092
|
+
import onnx_asr
|
|
1093
|
+
import onnxruntime
|
|
1094
|
+
|
|
1095
|
+
options = onnxruntime.SessionOptions()
|
|
1096
|
+
options.intra_op_num_threads = 1
|
|
1097
|
+
options.inter_op_num_threads = 1
|
|
1098
|
+
return onnx_asr.load_vad(VAD_MODEL_NAME, sess_options=options)
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
def create_audio_splitter(model, load_vad_model=load_vad):
|
|
1102
|
+
"""The request splitter `run_serve` uses: the pause cuts, or — when the VAD cannot be loaded — the 25 s cuts alone,
|
|
1103
|
+
so the engine keeps working the old way instead of failing to start."""
|
|
1104
|
+
try:
|
|
1105
|
+
return make_model_pause_splitter(model, load_vad_model())
|
|
1106
|
+
except Exception:
|
|
1107
|
+
logger.error("the VAD could not be loaded, cutting at %.0f s only: %s", MAX_CHUNK_SECONDS, traceback.format_exc())
|
|
1108
|
+
return split_without_pauses
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
def check_ffmpeg_decoder(name: str) -> bool:
|
|
1112
|
+
"""Whether the ffmpeg inside the `av` wheel has the decoder `name`."""
|
|
1113
|
+
import av
|
|
1114
|
+
|
|
1115
|
+
try:
|
|
1116
|
+
av.Codec(name, "r")
|
|
1117
|
+
except av.codec.codec.UnknownCodecError:
|
|
1118
|
+
return False
|
|
1119
|
+
return True
|
|
1120
|
+
|
|
1121
|
+
|
|
628
1122
|
def run_check(args: argparse.Namespace) -> int:
|
|
629
|
-
"""Load the model, recognise 1 s of silence and print the versions —
|
|
1123
|
+
"""Load the model and the VAD, recognise 1 s of silence through the pause splitter and print the versions —
|
|
1124
|
+
the installer's verify step (it also fetches both while the network is allowed)."""
|
|
630
1125
|
from importlib import metadata
|
|
631
1126
|
import platform
|
|
632
1127
|
|
|
@@ -634,11 +1129,19 @@ def run_check(args: argparse.Namespace) -> int:
|
|
|
634
1129
|
import soundfile
|
|
635
1130
|
|
|
636
1131
|
model = load_model(args.threads)
|
|
637
|
-
|
|
638
|
-
|
|
1132
|
+
split = make_model_pause_splitter(model, load_vad())
|
|
1133
|
+
silence = np.zeros(CHECK_SAMPLE_RATE, dtype=np.float32)
|
|
1134
|
+
pieces, piece_sample_rate = split(silence, CHECK_SAMPLE_RATE)
|
|
1135
|
+
if piece_sample_rate != SPEECH_SAMPLE_RATE:
|
|
1136
|
+
raise RuntimeError("the pause detection failed (its traceback is above, on stderr)")
|
|
1137
|
+
recognize = make_recognizer(model)
|
|
1138
|
+
text = " ".join(recognize(piece, piece_sample_rate) for piece in pieces).strip()
|
|
639
1139
|
is_opus_readable = "OPUS" in soundfile.available_subtypes("OGG")
|
|
640
1140
|
if not is_opus_readable:
|
|
641
1141
|
raise RuntimeError("libsndfile cannot read OGG/Opus")
|
|
1142
|
+
missing_decoders = [name for name in FFMPEG_REQUIRED_DECODERS if not check_ffmpeg_decoder(name)]
|
|
1143
|
+
if missing_decoders:
|
|
1144
|
+
raise RuntimeError(f"ffmpeg (the av package) lacks the decoders {', '.join(missing_decoders)}")
|
|
642
1145
|
report = {
|
|
643
1146
|
"ok": True,
|
|
644
1147
|
"scriptVersion": SCRIPT_VERSION,
|
|
@@ -648,6 +1151,7 @@ def run_check(args: argparse.Namespace) -> int:
|
|
|
648
1151
|
"onnxruntime": metadata.version("onnxruntime"),
|
|
649
1152
|
"soundfile": metadata.version("soundfile"),
|
|
650
1153
|
"libsndfile": soundfile.__libsndfile_version__,
|
|
1154
|
+
"av": metadata.version("av"),
|
|
651
1155
|
"numpy": metadata.version("numpy"),
|
|
652
1156
|
"silenceText": text,
|
|
653
1157
|
}
|
|
@@ -683,10 +1187,11 @@ def run_serve(args: argparse.Namespace, token: str) -> int:
|
|
|
683
1187
|
logger.error("model load failed: %s", traceback.format_exc())
|
|
684
1188
|
return EXIT_SETUP_FAILED
|
|
685
1189
|
logger.info("model loaded in %.2fs", time.monotonic() - loading_started)
|
|
1190
|
+
split_audio = create_audio_splitter(model)
|
|
686
1191
|
|
|
687
1192
|
pool = RecognitionPool(make_recognizer(model), args.workers)
|
|
688
1193
|
pool.start()
|
|
689
|
-
context = SidecarContext(token, pool, args.workers, args.idle_seconds, args.max_seconds)
|
|
1194
|
+
context = SidecarContext(token, pool, split_audio, args.workers, args.idle_seconds, args.max_seconds)
|
|
690
1195
|
try:
|
|
691
1196
|
server = SidecarServer(("127.0.0.1", 0), context)
|
|
692
1197
|
except OSError:
|