charness 3.2.0 → 3.3.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 +3 -6
- package/DEVELOPMENT.md +15 -0
- package/README.md +111 -38
- package/SETUP.md +121 -16
- package/dist/adapters/adapterEventRouting.d.ts +2 -1
- package/dist/adapters/adapterEventRouting.d.ts.map +1 -1
- package/dist/adapters/adapterEventRouting.js +3 -0
- package/dist/adapters/adapterEventRouting.js.map +1 -1
- package/dist/adapters/adapterNames.d.ts +13 -0
- package/dist/adapters/adapterNames.d.ts.map +1 -1
- package/dist/adapters/adapterNames.js +16 -0
- package/dist/adapters/adapterNames.js.map +1 -1
- package/dist/adapters/backendUnavailableError.d.ts +11 -0
- package/dist/adapters/backendUnavailableError.d.ts.map +1 -0
- package/dist/adapters/backendUnavailableError.js +19 -0
- package/dist/adapters/backendUnavailableError.js.map +1 -0
- package/dist/adapters/claudeCliAdapter.d.ts +29 -6
- package/dist/adapters/claudeCliAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeCliAdapter.js +70 -16
- package/dist/adapters/claudeCliAdapter.js.map +1 -1
- package/dist/adapters/claudeJsonStreamAdapter.d.ts +36 -3
- package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeJsonStreamAdapter.js +93 -30
- package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -1
- package/dist/adapters/createAdapter.d.ts +3 -14
- package/dist/adapters/createAdapter.d.ts.map +1 -1
- package/dist/adapters/createAdapter.js +9 -19
- package/dist/adapters/createAdapter.js.map +1 -1
- package/dist/adapters/openCodeAdapter.d.ts +140 -12
- package/dist/adapters/openCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/openCodeAdapter.js +385 -119
- package/dist/adapters/openCodeAdapter.js.map +1 -1
- package/dist/apiErrorRetry.d.ts +70 -6
- package/dist/apiErrorRetry.d.ts.map +1 -1
- package/dist/apiErrorRetry.js +312 -34
- package/dist/apiErrorRetry.js.map +1 -1
- package/dist/apiRetryKick.d.ts +26 -1
- package/dist/apiRetryKick.d.ts.map +1 -1
- package/dist/apiRetryKick.js +41 -2
- package/dist/apiRetryKick.js.map +1 -1
- package/dist/bot.d.ts +2 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +736 -184
- package/dist/bot.js.map +1 -1
- package/dist/cli/claudeProfiles.d.ts +1 -1
- package/dist/cli/claudeProfiles.js +1 -1
- package/dist/cli/hot.d.ts +7 -1
- package/dist/cli/hot.d.ts.map +1 -1
- package/dist/cli/hot.js +29 -3
- package/dist/cli/hot.js.map +1 -1
- package/dist/configDirFile.d.ts +73 -0
- package/dist/configDirFile.d.ts.map +1 -0
- package/dist/configDirFile.js +216 -0
- package/dist/configDirFile.js.map +1 -0
- package/dist/connectors/telegram/commands/autoContinueLimits.d.ts +2 -2
- package/dist/connectors/telegram/commands/autoContinueLimits.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/autoContinueLimits.js +4 -4
- package/dist/connectors/telegram/commands/autoContinueLimits.js.map +1 -1
- package/dist/connectors/telegram/commands/backendSwitch.d.ts +25 -0
- package/dist/connectors/telegram/commands/backendSwitch.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/backendSwitch.js +104 -0
- package/dist/connectors/telegram/commands/backendSwitch.js.map +1 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts +12 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/callbackMessage.js +24 -0
- package/dist/connectors/telegram/commands/callbackMessage.js.map +1 -1
- package/dist/connectors/telegram/commands/callbackToast.d.ts +36 -0
- package/dist/connectors/telegram/commands/callbackToast.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/callbackToast.js +83 -0
- package/dist/connectors/telegram/commands/callbackToast.js.map +1 -0
- package/dist/connectors/telegram/commands/claudeAccounts.d.ts +18 -0
- package/dist/connectors/telegram/commands/claudeAccounts.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/claudeAccounts.js +220 -66
- package/dist/connectors/telegram/commands/claudeAccounts.js.map +1 -1
- package/dist/connectors/telegram/commands/displayModes.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/displayModes.js +4 -2
- package/dist/connectors/telegram/commands/displayModes.js.map +1 -1
- package/dist/connectors/telegram/commands/fallbackChain.d.ts +5 -8
- package/dist/connectors/telegram/commands/fallbackChain.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/fallbackChain.js +183 -193
- package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -1
- package/dist/connectors/telegram/commands/limitsBoard.d.ts +40 -0
- package/dist/connectors/telegram/commands/limitsBoard.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/limitsBoard.js +281 -0
- package/dist/connectors/telegram/commands/limitsBoard.js.map +1 -0
- package/dist/connectors/telegram/commands/modelPage.d.ts +130 -0
- package/dist/connectors/telegram/commands/modelPage.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/modelPage.js +120 -0
- package/dist/connectors/telegram/commands/modelPage.js.map +1 -0
- package/dist/connectors/telegram/commands/modelProviders.d.ts +9 -1
- package/dist/connectors/telegram/commands/modelProviders.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/modelProviders.js +203 -111
- package/dist/connectors/telegram/commands/modelProviders.js.map +1 -1
- package/dist/connectors/telegram/commands/stt.d.ts +32 -0
- package/dist/connectors/telegram/commands/stt.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/stt.js +460 -0
- package/dist/connectors/telegram/commands/stt.js.map +1 -0
- package/dist/effortLevels.d.ts +57 -7
- package/dist/effortLevels.d.ts.map +1 -1
- package/dist/effortLevels.js +65 -9
- package/dist/effortLevels.js.map +1 -1
- package/dist/i18n/de.d.ts.map +1 -1
- package/dist/i18n/de.js +160 -17
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +160 -17
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +160 -17
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/fr.js +160 -17
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/hi.d.ts.map +1 -1
- package/dist/i18n/hi.js +160 -17
- package/dist/i18n/hi.js.map +1 -1
- package/dist/i18n/ja.d.ts.map +1 -1
- package/dist/i18n/ja.js +160 -17
- package/dist/i18n/ja.js.map +1 -1
- package/dist/i18n/ka.d.ts.map +1 -1
- package/dist/i18n/ka.js +160 -17
- package/dist/i18n/ka.js.map +1 -1
- package/dist/i18n/pt.d.ts.map +1 -1
- package/dist/i18n/pt.js +160 -17
- package/dist/i18n/pt.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +160 -17
- package/dist/i18n/ru.js.map +1 -1
- package/dist/i18n/uk.d.ts.map +1 -1
- package/dist/i18n/uk.js +160 -17
- package/dist/i18n/uk.js.map +1 -1
- package/dist/i18n/uz.d.ts.map +1 -1
- package/dist/i18n/uz.js +160 -17
- package/dist/i18n/uz.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +160 -17
- package/dist/i18n/zh.js.map +1 -1
- package/dist/installManager.d.ts +20 -4
- package/dist/installManager.d.ts.map +1 -1
- package/dist/installManager.js +82 -14
- package/dist/installManager.js.map +1 -1
- package/dist/instanceSettings.d.ts +52 -0
- package/dist/instanceSettings.d.ts.map +1 -0
- package/dist/instanceSettings.js +136 -0
- package/dist/instanceSettings.js.map +1 -0
- package/dist/pinnedStatus.d.ts +7 -1
- package/dist/pinnedStatus.d.ts.map +1 -1
- package/dist/pinnedStatus.js +1 -1
- package/dist/pinnedStatus.js.map +1 -1
- package/dist/requests/requestLedger.d.ts +8 -0
- package/dist/requests/requestLedger.d.ts.map +1 -1
- package/dist/requests/requestLedger.js +19 -1
- package/dist/requests/requestLedger.js.map +1 -1
- package/dist/scheduler/mcpSurface.js +4 -4
- package/dist/scheduler/mcpSurface.js.map +1 -1
- package/dist/shutdown.d.ts +14 -0
- package/dist/shutdown.d.ts.map +1 -1
- package/dist/shutdown.js +16 -0
- package/dist/shutdown.js.map +1 -1
- package/dist/state.d.ts +54 -2
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +62 -1
- package/dist/state.js.map +1 -1
- package/dist/stt/sttAutoInstall.d.ts +39 -0
- package/dist/stt/sttAutoInstall.d.ts.map +1 -0
- package/dist/stt/sttAutoInstall.js +158 -0
- package/dist/stt/sttAutoInstall.js.map +1 -0
- package/dist/stt/sttChildEnv.d.ts +11 -0
- package/dist/stt/sttChildEnv.d.ts.map +1 -0
- package/dist/stt/sttChildEnv.js +30 -0
- package/dist/stt/sttChildEnv.js.map +1 -0
- package/dist/stt/sttConfigEdit.d.ts +71 -0
- package/dist/stt/sttConfigEdit.d.ts.map +1 -0
- package/dist/stt/sttConfigEdit.js +144 -0
- package/dist/stt/sttConfigEdit.js.map +1 -0
- package/dist/stt/sttInstall.d.ts +147 -0
- package/dist/stt/sttInstall.d.ts.map +1 -0
- package/dist/stt/sttInstall.js +491 -0
- package/dist/stt/sttInstall.js.map +1 -0
- package/dist/stt/sttInstallEntry.d.ts +2 -0
- package/dist/stt/sttInstallEntry.d.ts.map +1 -0
- package/dist/stt/sttInstallEntry.js +31 -0
- package/dist/stt/sttInstallEntry.js.map +1 -0
- package/dist/stt/sttInstallReporter.d.ts +45 -0
- package/dist/stt/sttInstallReporter.d.ts.map +1 -0
- package/dist/stt/sttInstallReporter.js +57 -0
- package/dist/stt/sttInstallReporter.js.map +1 -0
- package/dist/stt/sttRuntime.d.ts +35 -0
- package/dist/stt/sttRuntime.d.ts.map +1 -0
- package/dist/stt/sttRuntime.js +73 -0
- package/dist/stt/sttRuntime.js.map +1 -0
- package/dist/stt/sttScreenPlan.d.ts +193 -0
- package/dist/stt/sttScreenPlan.d.ts.map +1 -0
- package/dist/stt/sttScreenPlan.js +541 -0
- package/dist/stt/sttScreenPlan.js.map +1 -0
- package/dist/stt/sttSettings.d.ts +201 -0
- package/dist/stt/sttSettings.d.ts.map +1 -0
- package/dist/stt/sttSettings.js +387 -0
- package/dist/stt/sttSettings.js.map +1 -0
- package/dist/stt/sttSidecarOwner.d.ts +115 -0
- package/dist/stt/sttSidecarOwner.d.ts.map +1 -0
- package/dist/stt/sttSidecarOwner.js +475 -0
- package/dist/stt/sttSidecarOwner.js.map +1 -0
- package/dist/stt/transcribeChain.d.ts +36 -0
- package/dist/stt/transcribeChain.d.ts.map +1 -0
- package/dist/stt/transcribeChain.js +42 -0
- package/dist/stt/transcribeChain.js.map +1 -0
- package/dist/types.d.ts +77 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/adapterSwitchSessionIds.d.ts +32 -0
- package/dist/utils/adapterSwitchSessionIds.d.ts.map +1 -0
- package/dist/utils/adapterSwitchSessionIds.js +44 -0
- package/dist/utils/adapterSwitchSessionIds.js.map +1 -0
- package/dist/utils/backendSwitchPlan.d.ts +63 -0
- package/dist/utils/backendSwitchPlan.d.ts.map +1 -0
- package/dist/utils/backendSwitchPlan.js +73 -0
- package/dist/utils/backendSwitchPlan.js.map +1 -0
- package/dist/utils/claudeAccountPickerPlan.d.ts +12 -1
- package/dist/utils/claudeAccountPickerPlan.d.ts.map +1 -1
- package/dist/utils/claudeAccountPickerPlan.js +20 -1
- package/dist/utils/claudeAccountPickerPlan.js.map +1 -1
- package/dist/utils/claudeTranscriptContent.d.ts +24 -0
- package/dist/utils/claudeTranscriptContent.d.ts.map +1 -0
- package/dist/utils/claudeTranscriptContent.js +107 -0
- package/dist/utils/claudeTranscriptContent.js.map +1 -0
- package/dist/utils/claudeUsage.d.ts +47 -8
- package/dist/utils/claudeUsage.d.ts.map +1 -1
- package/dist/utils/claudeUsage.js +76 -14
- package/dist/utils/claudeUsage.js.map +1 -1
- package/dist/utils/claudeUsageRefresher.d.ts +8 -1
- package/dist/utils/claudeUsageRefresher.d.ts.map +1 -1
- package/dist/utils/claudeUsageRefresher.js +9 -4
- package/dist/utils/claudeUsageRefresher.js.map +1 -1
- package/dist/utils/effortPickerPlan.d.ts +54 -0
- package/dist/utils/effortPickerPlan.d.ts.map +1 -0
- package/dist/utils/effortPickerPlan.js +56 -0
- package/dist/utils/effortPickerPlan.js.map +1 -0
- package/dist/utils/ensureSessionPlan.d.ts +20 -4
- package/dist/utils/ensureSessionPlan.d.ts.map +1 -1
- package/dist/utils/ensureSessionPlan.js +17 -4
- package/dist/utils/ensureSessionPlan.js.map +1 -1
- package/dist/utils/fallbackBridge.d.ts +6 -2
- package/dist/utils/fallbackBridge.d.ts.map +1 -1
- package/dist/utils/fallbackBridge.js +10 -3
- package/dist/utils/fallbackBridge.js.map +1 -1
- package/dist/utils/fallbackChain.d.ts +36 -0
- package/dist/utils/fallbackChain.d.ts.map +1 -1
- package/dist/utils/fallbackChain.js +65 -1
- package/dist/utils/fallbackChain.js.map +1 -1
- package/dist/utils/fallbackSwitchNotice.d.ts +41 -0
- package/dist/utils/fallbackSwitchNotice.d.ts.map +1 -0
- package/dist/utils/fallbackSwitchNotice.js +75 -0
- package/dist/utils/fallbackSwitchNotice.js.map +1 -0
- package/dist/utils/fileSendService.d.ts +33 -10
- package/dist/utils/fileSendService.d.ts.map +1 -1
- package/dist/utils/fileSendService.js +78 -27
- package/dist/utils/fileSendService.js.map +1 -1
- package/dist/utils/jsonStreamHost.d.ts +39 -0
- package/dist/utils/jsonStreamHost.d.ts.map +1 -1
- package/dist/utils/jsonStreamHost.js +60 -1
- package/dist/utils/jsonStreamHost.js.map +1 -1
- package/dist/utils/limitEpisodeRecovery.d.ts +16 -2
- package/dist/utils/limitEpisodeRecovery.d.ts.map +1 -1
- package/dist/utils/limitEpisodeRecovery.js +44 -3
- package/dist/utils/limitEpisodeRecovery.js.map +1 -1
- package/dist/utils/limitSettings.d.ts +25 -0
- package/dist/utils/limitSettings.d.ts.map +1 -0
- package/dist/utils/limitSettings.js +31 -0
- package/dist/utils/limitSettings.js.map +1 -0
- package/dist/utils/limitsBoardPlan.d.ts +52 -0
- package/dist/utils/limitsBoardPlan.d.ts.map +1 -0
- package/dist/utils/limitsBoardPlan.js +93 -0
- package/dist/utils/limitsBoardPlan.js.map +1 -0
- package/dist/utils/openCodeUsage.d.ts +93 -0
- package/dist/utils/openCodeUsage.d.ts.map +1 -0
- package/dist/utils/openCodeUsage.js +248 -0
- package/dist/utils/openCodeUsage.js.map +1 -0
- package/dist/utils/operatorSwitchPlan.d.ts +61 -0
- package/dist/utils/operatorSwitchPlan.d.ts.map +1 -0
- package/dist/utils/operatorSwitchPlan.js +32 -0
- package/dist/utils/operatorSwitchPlan.js.map +1 -0
- package/dist/utils/promptDelivery.d.ts +40 -0
- package/dist/utils/promptDelivery.d.ts.map +1 -1
- package/dist/utils/promptDelivery.js +31 -0
- package/dist/utils/promptDelivery.js.map +1 -1
- package/dist/utils/recvPreviewRedaction.d.ts +14 -4
- package/dist/utils/recvPreviewRedaction.d.ts.map +1 -1
- package/dist/utils/recvPreviewRedaction.js +19 -7
- package/dist/utils/recvPreviewRedaction.js.map +1 -1
- package/dist/utils/startupReadiness.d.ts +3 -3
- package/dist/utils/startupReadiness.d.ts.map +1 -1
- package/dist/utils/startupReadiness.js +2 -2
- package/dist/utils/startupReadiness.js.map +1 -1
- package/dist/utils/threadStatusReport.d.ts +3 -1
- package/dist/utils/threadStatusReport.d.ts.map +1 -1
- package/dist/utils/threadStatusReport.js +4 -3
- package/dist/utils/threadStatusReport.js.map +1 -1
- package/dist/utils/timezone.d.ts +23 -0
- package/dist/utils/timezone.d.ts.map +1 -1
- package/dist/utils/timezone.js +38 -0
- package/dist/utils/timezone.js.map +1 -1
- package/dist/utils/transcribeAudio.d.ts +25 -12
- package/dist/utils/transcribeAudio.d.ts.map +1 -1
- package/dist/utils/transcribeAudio.js +39 -21
- package/dist/utils/transcribeAudio.js.map +1 -1
- package/dist/utils/usageRefreshBudget.d.ts +9 -0
- package/dist/utils/usageRefreshBudget.d.ts.map +1 -0
- package/dist/utils/usageRefreshBudget.js +27 -0
- package/dist/utils/usageRefreshBudget.js.map +1 -0
- package/examples/.env.example +7 -3
- package/examples/docker-compose.yml +9 -7
- package/package.json +2 -1
- package/stt/gigaam_sidecar.py +800 -0
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.OpenCodeAdapter = exports.schedulerMcpRetryDelaysMs = exports.openCodeErrorMessageMaxLength = exports.providerRetryReplacementStartTimeoutMs = exports.criticalSseEventTypes = exports.openCodeRuntimeContextHydrationConcurrency = void 0;
|
|
36
|
+
exports.OpenCodeAdapter = exports.openCodeServerRestartRetryBudgetMs = exports.openCodeServerRestartRetryDelaysMs = exports.schedulerMcpRetryDelaysMs = exports.openCodeErrorMessageMaxLength = exports.providerRetryReplacementStartTimeoutMs = exports.criticalSseEventTypes = exports.openCodeRuntimeContextHydrationConcurrency = void 0;
|
|
37
37
|
exports.listSavedOpenCodeModelLabels = listSavedOpenCodeModelLabels;
|
|
38
38
|
exports.normaliseOpenCodeSseEvent = normaliseOpenCodeSseEvent;
|
|
39
39
|
exports.buildDirectoryScopedPath = buildDirectoryScopedPath;
|
|
@@ -45,6 +45,8 @@ exports.checkIsOpenCodeTurnInFlight = checkIsOpenCodeTurnInFlight;
|
|
|
45
45
|
exports.summarizeProviderRetryReason = summarizeProviderRetryReason;
|
|
46
46
|
exports.checkIsOpenCodeAbortError = checkIsOpenCodeAbortError;
|
|
47
47
|
exports.getOpenCodeErrorMessage = getOpenCodeErrorMessage;
|
|
48
|
+
exports.getOpenCodeErrorLogText = getOpenCodeErrorLogText;
|
|
49
|
+
exports.getOpenCodeErrorRetryAfterAt = getOpenCodeErrorRetryAfterAt;
|
|
48
50
|
exports.checkIsOpenCodeSessionBusy = checkIsOpenCodeSessionBusy;
|
|
49
51
|
exports.applyOpenCodeStatusEvent = applyOpenCodeStatusEvent;
|
|
50
52
|
exports.buildPromptBody = buildPromptBody;
|
|
@@ -53,6 +55,7 @@ exports.getLatestOpenCodeParentAssistantContext = getLatestOpenCodeParentAssista
|
|
|
53
55
|
exports.buildProviderAuthPath = buildProviderAuthPath;
|
|
54
56
|
exports.checkNeedsSchedulerMcpReregister = checkNeedsSchedulerMcpReregister;
|
|
55
57
|
exports.getSchedulerMcpRetryDelayMs = getSchedulerMcpRetryDelayMs;
|
|
58
|
+
exports.getOpenCodeServerRestartRetryDelayMs = getOpenCodeServerRestartRetryDelayMs;
|
|
56
59
|
exports.buildProviderApiAuthPayload = buildProviderApiAuthPayload;
|
|
57
60
|
exports.resetOpenCodeProviderCaches = resetOpenCodeProviderCaches;
|
|
58
61
|
exports.parseProvidersResponse = parseProvidersResponse;
|
|
@@ -66,6 +69,8 @@ const apiErrorRetry_1 = require("../apiErrorRetry");
|
|
|
66
69
|
const installManager_1 = require("../installManager");
|
|
67
70
|
const state_1 = require("../state");
|
|
68
71
|
const diagLog_1 = require("../diagLog");
|
|
72
|
+
const utils_1 = require("../utils");
|
|
73
|
+
const backendUnavailableError_1 = require("./backendUnavailableError");
|
|
69
74
|
const openCodeSessionRouting_1 = require("../openCodeSessionRouting");
|
|
70
75
|
const i18n_1 = require("../i18n");
|
|
71
76
|
const resumeContext_1 = require("../resumeContext");
|
|
@@ -574,6 +579,8 @@ function checkIsOpenCodeAbortError(errorMsg) {
|
|
|
574
579
|
return errorMsg.trim().toLowerCase() === 'aborted';
|
|
575
580
|
}
|
|
576
581
|
const genericOpenCodeErrorMessage = 'OpenCode request failed';
|
|
582
|
+
/** The two channels OpenCode reports a turn's failure on, and the line each one posts. */
|
|
583
|
+
const openCodeFailurePrefixes = { 'session.error': 'OpenCode error: ', 'message.error': 'Error: ' };
|
|
577
584
|
/** Placeholder written over every redacted credential. */
|
|
578
585
|
const openCodeErrorRedactedMarker = '[redacted]';
|
|
579
586
|
/**
|
|
@@ -614,18 +621,45 @@ const openCodeErrorCredentialValueRe = /\bsk-[A-Za-z0-9_-]+|\bghp_[A-Za-z0-9]+|\
|
|
|
614
621
|
* required: a bare `https://<token>@host` is just as much a credential.
|
|
615
622
|
*/
|
|
616
623
|
const openCodeErrorUrlUserInfoRe = /(\w+:\/\/)[^\s/@]+@/g;
|
|
617
|
-
/**
|
|
618
|
-
|
|
619
|
-
* and cap it. Redaction runs BEFORE the cap: capping first could cut a secret
|
|
620
|
-
* assignment in half and leave the head of the credential visible.
|
|
621
|
-
*/
|
|
622
|
-
function redactOpenCodeErrorMessage(message) {
|
|
624
|
+
/** @description Redact the credentials an error string is known to carry (see the rules above). */
|
|
625
|
+
function redactOpenCodeErrorCredentials(message) {
|
|
623
626
|
return message
|
|
624
627
|
.replace(openCodeErrorSecretAssignmentRe, `$1$2${openCodeErrorRedactedMarker}`)
|
|
625
628
|
.replace(openCodeErrorAuthSchemeRe, `$1 ${openCodeErrorRedactedMarker}`)
|
|
626
629
|
.replace(openCodeErrorCredentialValueRe, openCodeErrorRedactedMarker)
|
|
627
|
-
.replace(openCodeErrorUrlUserInfoRe, `$1${openCodeErrorRedactedMarker}@`)
|
|
628
|
-
|
|
630
|
+
.replace(openCodeErrorUrlUserInfoRe, `$1${openCodeErrorRedactedMarker}@`);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* The start of the diagnostics block an auth plugin appends to every provider error it relays
|
|
634
|
+
* (opencode-antigravity-auth: `\n\n[Debug Info]\nRequested Model: …\nProject: …\nEndpoint: …\nTool Debug Payload: …`): the
|
|
635
|
+
* user's Google project id and part of the tool schema — nothing the person in the topic can act on, and it pushed the
|
|
636
|
+
* provider's own reason out of the capped line. Cut before the text is surfaced; the bot log keeps it
|
|
637
|
+
* ({@link getOpenCodeErrorLogText}).
|
|
638
|
+
*/
|
|
639
|
+
const openCodePluginDiagnosticsMarker = '\n\n[Debug Info]';
|
|
640
|
+
/**
|
|
641
|
+
* @description The surfaced form of an error string: the plugin diagnostics cut ({@link openCodePluginDiagnosticsMarker}),
|
|
642
|
+
* the credentials redacted, the length capped. Redaction runs BEFORE the cap: capping first could cut a secret
|
|
643
|
+
* assignment in half and leave the head of the credential visible.
|
|
644
|
+
*/
|
|
645
|
+
function redactOpenCodeErrorMessage(message) {
|
|
646
|
+
const diagnosticsStart = message.indexOf(openCodePluginDiagnosticsMarker);
|
|
647
|
+
const reason = diagnosticsStart >= 0 ? message.slice(0, diagnosticsStart) : message;
|
|
648
|
+
return redactOpenCodeErrorCredentials(reason).slice(0, exports.openCodeErrorMessageMaxLength);
|
|
649
|
+
}
|
|
650
|
+
/** @description The message string of OpenCode's known error shapes (`{ data: { message } }`, `{ message }`, a string), else `null`. */
|
|
651
|
+
function readOpenCodeErrorString(error) {
|
|
652
|
+
if (typeof error === 'string')
|
|
653
|
+
return error;
|
|
654
|
+
if (!error || typeof error !== 'object')
|
|
655
|
+
return null;
|
|
656
|
+
const errorRecord = error;
|
|
657
|
+
if (errorRecord.data && typeof errorRecord.data === 'object') {
|
|
658
|
+
const errorData = errorRecord.data;
|
|
659
|
+
if (typeof errorData.message === 'string')
|
|
660
|
+
return errorData.message;
|
|
661
|
+
}
|
|
662
|
+
return typeof errorRecord.message === 'string' ? errorRecord.message : null;
|
|
629
663
|
}
|
|
630
664
|
/**
|
|
631
665
|
* @description Extract the readable, SAFE-TO-SURFACE error form from OpenCode's
|
|
@@ -638,19 +672,16 @@ function redactOpenCodeErrorMessage(message) {
|
|
|
638
672
|
* The retry classifier reads {@link getOpenCodeErrorClassificationText} instead.
|
|
639
673
|
*/
|
|
640
674
|
function getOpenCodeErrorMessage(error) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (typeof errorRecord.message === 'string')
|
|
652
|
-
return redactOpenCodeErrorMessage(errorRecord.message);
|
|
653
|
-
return genericOpenCodeErrorMessage;
|
|
675
|
+
const errorString = readOpenCodeErrorString(error);
|
|
676
|
+
return errorString === null ? genericOpenCodeErrorMessage : redactOpenCodeErrorMessage(errorString);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* @description The bot-log form of an error: the whole message string, plugin diagnostics included, credentials
|
|
680
|
+
* redacted — what {@link getOpenCodeErrorMessage} cuts for the topic stays findable in the log.
|
|
681
|
+
*/
|
|
682
|
+
function getOpenCodeErrorLogText(error) {
|
|
683
|
+
const errorString = readOpenCodeErrorString(error);
|
|
684
|
+
return errorString === null ? genericOpenCodeErrorMessage : redactOpenCodeErrorCredentials(errorString);
|
|
654
685
|
}
|
|
655
686
|
/**
|
|
656
687
|
* @description Serialise an error payload FOR CLASSIFICATION ONLY. The result
|
|
@@ -664,7 +695,10 @@ function getOpenCodeErrorMessage(error) {
|
|
|
664
695
|
* shape to a constant, and the classifier reads text to decide whether an error
|
|
665
696
|
* is an auto-retryable rate-limit / usage-limit. Feeding it that constant
|
|
666
697
|
* silently cancelled the retry of a real rate limit, so the two paths are split:
|
|
667
|
-
* the user gets the constant, the classifier gets the
|
|
698
|
+
* the user gets the constant, the classifier gets the payload's text. For
|
|
699
|
+
* OpenCode's own `{ name, data }` provider error that text is the message, the
|
|
700
|
+
* response body and the status code only — never its headers or metadata (see
|
|
701
|
+
* the inline note); an unrecognised shape is serialized whole.
|
|
668
702
|
*/
|
|
669
703
|
function getOpenCodeErrorClassificationText(error) {
|
|
670
704
|
if (typeof error === 'string')
|
|
@@ -673,6 +707,17 @@ function getOpenCodeErrorClassificationText(error) {
|
|
|
673
707
|
return error.message;
|
|
674
708
|
if (!error || typeof error !== 'object')
|
|
675
709
|
return '';
|
|
710
|
+
const apiErrorData = getOpenCodeApiErrorData(error);
|
|
711
|
+
if (apiErrorData) {
|
|
712
|
+
// OpenCode's own shape: what the PROVIDER said — its message, its response body, the status code. Never the
|
|
713
|
+
// response headers or the metadata: they name limits and resets of their own (`x-…-limit-…`, a per-minute
|
|
714
|
+
// counter's `…-reset: <ISO>`), which read as a usage window that is not there.
|
|
715
|
+
return [
|
|
716
|
+
apiErrorData.message,
|
|
717
|
+
apiErrorData.responseBody,
|
|
718
|
+
typeof apiErrorData.statusCode === 'number' ? `HTTP ${apiErrorData.statusCode}` : undefined,
|
|
719
|
+
].filter((part) => typeof part === 'string' && part !== '').join('\n');
|
|
720
|
+
}
|
|
676
721
|
try {
|
|
677
722
|
// A payload whose `toJSON` opts out yields undefined rather than text.
|
|
678
723
|
const serialized = JSON.stringify(error);
|
|
@@ -683,6 +728,39 @@ function getOpenCodeErrorClassificationText(error) {
|
|
|
683
728
|
return '';
|
|
684
729
|
}
|
|
685
730
|
}
|
|
731
|
+
const msPerSecond = 1000;
|
|
732
|
+
/** @description The `data` of an OpenCode `{ name, data: { message, … } }` error, or `null` for any other shape. */
|
|
733
|
+
function getOpenCodeApiErrorData(error) {
|
|
734
|
+
const data = 'data' in error && error.data && typeof error.data === 'object' ? error.data : undefined;
|
|
735
|
+
return data && typeof data.message === 'string' ? data : null;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* @description The reset a provider states as a FIELD: the `retry-after-ms` / `retry-after` response header of an
|
|
739
|
+
* OpenCode provider error (seconds, or an HTTP date), as epoch ms; `undefined` when there is none. The one header the
|
|
740
|
+
* bot reads — the others name limits and counters of their own.
|
|
741
|
+
*/
|
|
742
|
+
function getOpenCodeErrorRetryAfterAt(error, now) {
|
|
743
|
+
if (!error || typeof error !== 'object')
|
|
744
|
+
return undefined;
|
|
745
|
+
const headers = getOpenCodeApiErrorData(error)?.responseHeaders;
|
|
746
|
+
if (!headers || typeof headers !== 'object')
|
|
747
|
+
return undefined;
|
|
748
|
+
const readHeader = (name) => {
|
|
749
|
+
const entry = Object.entries(headers).find(([headerName]) => headerName.toLowerCase() === name);
|
|
750
|
+
return typeof entry?.[1] === 'string' ? entry[1] : undefined;
|
|
751
|
+
};
|
|
752
|
+
const retryAfterMs = Number.parseFloat(readHeader('retry-after-ms') ?? '');
|
|
753
|
+
if (Number.isFinite(retryAfterMs))
|
|
754
|
+
return now + retryAfterMs;
|
|
755
|
+
const retryAfter = readHeader('retry-after');
|
|
756
|
+
if (retryAfter === undefined)
|
|
757
|
+
return undefined;
|
|
758
|
+
const retryAfterSeconds = Number.parseFloat(retryAfter);
|
|
759
|
+
if (Number.isFinite(retryAfterSeconds))
|
|
760
|
+
return now + retryAfterSeconds * msPerSecond;
|
|
761
|
+
const retryAfterDateMs = Date.parse(retryAfter);
|
|
762
|
+
return Number.isNaN(retryAfterDateMs) ? undefined : retryAfterDateMs;
|
|
763
|
+
}
|
|
686
764
|
/**
|
|
687
765
|
* @description Sync "is this session occupied with a turn?" decision for the
|
|
688
766
|
* scheduler's wait-for-idle loop ({@link AgentAdapter.checkIsBusy}): busy when
|
|
@@ -915,6 +993,39 @@ exports.schedulerMcpRetryDelaysMs = [15_000, 60_000, 5 * 60_000, 15 * 60_000];
|
|
|
915
993
|
function getSchedulerMcpRetryDelayMs(attempt) {
|
|
916
994
|
return exports.schedulerMcpRetryDelaysMs[attempt] ?? null;
|
|
917
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Pauses before each retry of a failed attempt to bring the OpenCode server up
|
|
998
|
+
* (a restart, or a session start / resume that finds it down). An attempt can
|
|
999
|
+
* fail transiently — the binary missing (`spawn opencode ENOENT`), a server not
|
|
1000
|
+
* ready in time on a loaded machine; one attempt used to tear every active
|
|
1001
|
+
* session down and show the raw error.
|
|
1002
|
+
*/
|
|
1003
|
+
exports.openCodeServerRestartRetryDelaysMs = [2_000, 5_000, 10_000, 15_000, 20_000];
|
|
1004
|
+
/**
|
|
1005
|
+
* The bound on the retries, counted from the first attempt: no retry is paused
|
|
1006
|
+
* for once it would start past this. An attempt that waits out the server
|
|
1007
|
+
* readiness probe takes ~15 s, so the bound — not the pause list — is what keeps
|
|
1008
|
+
* a hopeless start from holding the topics for minutes.
|
|
1009
|
+
*/
|
|
1010
|
+
exports.openCodeServerRestartRetryBudgetMs = 60_000;
|
|
1011
|
+
/**
|
|
1012
|
+
* @description The pause before retry number `retryIndex` (0-based), or
|
|
1013
|
+
* `null` once the pauses are used up or the retry would start past `budgetMs`
|
|
1014
|
+
* after the first attempt began (`elapsedMs` ago).
|
|
1015
|
+
*/
|
|
1016
|
+
function getOpenCodeServerRestartRetryDelayMs(retryIndex, elapsedMs, delaysMs = exports.openCodeServerRestartRetryDelaysMs, budgetMs = exports.openCodeServerRestartRetryBudgetMs) {
|
|
1017
|
+
const delayMs = delaysMs[retryIndex];
|
|
1018
|
+
if (delayMs === undefined || elapsedMs + delayMs > budgetMs)
|
|
1019
|
+
return null;
|
|
1020
|
+
return delayMs;
|
|
1021
|
+
}
|
|
1022
|
+
const defaultOpenCodeServerRestartOps = {
|
|
1023
|
+
checkIsInstalled: () => (0, installManager_1.checkIsInstalled)('opencode'),
|
|
1024
|
+
installTool: () => (0, installManager_1.installTool)('opencode'),
|
|
1025
|
+
checkIsServerRunning: installManager_1.checkIsOpenCodeServerRunning,
|
|
1026
|
+
ensureServer: installManager_1.ensureOpenCodeServer,
|
|
1027
|
+
restartServer: installManager_1.restartOpenCodeServer,
|
|
1028
|
+
};
|
|
918
1029
|
function buildProviderApiAuthPayload(apiKey) {
|
|
919
1030
|
return { type: 'api', key: apiKey };
|
|
920
1031
|
}
|
|
@@ -930,6 +1041,17 @@ function resetOpenCodeProviderCaches() {
|
|
|
930
1041
|
cachedProviders = null;
|
|
931
1042
|
providersCacheTime = 0;
|
|
932
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* @description The variants the LAST providers snapshot lists for `model`, however old: a model's variants hardly
|
|
1046
|
+
* change, and the synchronous effort display must not wait for a request. `[]` is a model the snapshot lists with
|
|
1047
|
+
* no variants (KNOWN to have none); `null` is a model the display cannot judge — no model, no snapshot yet, or a
|
|
1048
|
+
* model the snapshot does not list.
|
|
1049
|
+
*/
|
|
1050
|
+
function getKnownModelVariants(model) {
|
|
1051
|
+
if (!model)
|
|
1052
|
+
return null;
|
|
1053
|
+
return cachedProviders?.providers.get(model.providerID)?.get(model.modelID)?.variants ?? null;
|
|
1054
|
+
}
|
|
933
1055
|
function getFreshProvidersConfig(now = Date.now()) {
|
|
934
1056
|
if (!cachedProviders || now - providersCacheTime >= providersCacheTtlMs)
|
|
935
1057
|
return null;
|
|
@@ -1129,8 +1251,20 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1129
1251
|
runtimeContextHydrationCount = 0;
|
|
1130
1252
|
/** Callers waiting for one of the bounded runtime-context hydration slots. */
|
|
1131
1253
|
runtimeContextHydrationWaiters = [];
|
|
1132
|
-
/**
|
|
1133
|
-
|
|
1254
|
+
/**
|
|
1255
|
+
* The server restart in progress, `null` when none: a second caller shares it
|
|
1256
|
+
* instead of starting another, and a prompt sent meanwhile waits for it
|
|
1257
|
+
* ({@link sendPromptAsync}) rather than posting to a server that is down.
|
|
1258
|
+
*/
|
|
1259
|
+
serverRestart = null;
|
|
1260
|
+
/**
|
|
1261
|
+
* The installManager calls of an attempt to bring the server up and its retry
|
|
1262
|
+
* schedule. Instance fields (initialised from the module defaults) only so the
|
|
1263
|
+
* tests can fail attempts and shrink the pauses — production never changes them.
|
|
1264
|
+
*/
|
|
1265
|
+
serverRestartOps = defaultOpenCodeServerRestartOps;
|
|
1266
|
+
serverRestartRetryDelaysMs = exports.openCodeServerRestartRetryDelaysMs;
|
|
1267
|
+
serverRestartRetryBudgetMs = exports.openCodeServerRestartRetryBudgetMs;
|
|
1134
1268
|
/**
|
|
1135
1269
|
* Per-thread display-prefs reader, injected by the bot at boot via
|
|
1136
1270
|
* `createAdapter.registerDisplayPrefsReader` (S4). DELIBERATE deviation from
|
|
@@ -1215,45 +1349,44 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1215
1349
|
return true;
|
|
1216
1350
|
}
|
|
1217
1351
|
/**
|
|
1218
|
-
* @description Handle unexpected server process death.
|
|
1219
|
-
*
|
|
1352
|
+
* @description Handle unexpected server process death: restart it. The topics
|
|
1353
|
+
* hear nothing unless it matters to them — a turn that was in flight, a session
|
|
1354
|
+
* the new server no longer has, or a restart that failed ({@link restartServer}).
|
|
1220
1355
|
* SSE reconnect loop will detect the restart and recover automatically.
|
|
1221
1356
|
*/
|
|
1222
1357
|
handleServerCrash(code, signal) {
|
|
1223
1358
|
const reason = code === 137 ? 'out of memory (OOM killed)' :
|
|
1224
1359
|
signal ? `signal ${signal}` : `exit code ${code}`;
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
if (session.isActive) {
|
|
1228
|
-
this.emit('output', session.key, `OpenCode server crashed (${reason}). Restarting...`);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
// Auto-restart the server (SSE reconnect loop will pick it up)
|
|
1232
|
-
this.restartServer();
|
|
1360
|
+
console.error(`[OpenCode] Server stopped (${reason}), restarting it`);
|
|
1361
|
+
void this.restartServer();
|
|
1233
1362
|
}
|
|
1234
1363
|
/**
|
|
1235
|
-
* @description Restart the OpenCode server process.
|
|
1236
|
-
*
|
|
1237
|
-
* waits for it to complete and returns its result.
|
|
1364
|
+
* @description Restart the OpenCode server process. A restart already in
|
|
1365
|
+
* progress is shared: its result is the caller's.
|
|
1238
1366
|
*/
|
|
1239
|
-
|
|
1240
|
-
if (this.
|
|
1241
|
-
console.log(`[OpenCode] Server restart already in progress, waiting
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1367
|
+
restartServer(force = false) {
|
|
1368
|
+
if (this.serverRestart) {
|
|
1369
|
+
console.log(`[OpenCode] Server restart already in progress, waiting for it`);
|
|
1370
|
+
return this.serverRestart;
|
|
1371
|
+
}
|
|
1372
|
+
const restart = this.runServerRestart(force).finally(() => {
|
|
1373
|
+
if (this.serverRestart === restart)
|
|
1374
|
+
this.serverRestart = null;
|
|
1375
|
+
});
|
|
1376
|
+
this.serverRestart = restart;
|
|
1377
|
+
return restart;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* @description The body of {@link restartServer}. Bringing the server back is
|
|
1381
|
+
* retried ({@link ensureServerWithRetries}) and silent: a topic whose session
|
|
1382
|
+
* is restored hears about it only when its turn was in flight — that reply is
|
|
1383
|
+
* lost and has to be asked again.
|
|
1384
|
+
*/
|
|
1385
|
+
async runServerRestart(force) {
|
|
1386
|
+
// Read before the restore loop: resuming rebuilds every session object.
|
|
1387
|
+
const turnInFlightKeys = new Set(Array.from(this.sessions.entries()).filter(([, session]) => session.isActive && session.isBusy).map(([k]) => k));
|
|
1250
1388
|
try {
|
|
1251
|
-
|
|
1252
|
-
if (force)
|
|
1253
|
-
await (0, installManager_1.restartOpenCodeServer)();
|
|
1254
|
-
else
|
|
1255
|
-
await (0, installManager_1.ensureOpenCodeServer)();
|
|
1256
|
-
console.log(`[OpenCode] Server restarted successfully`);
|
|
1389
|
+
await this.ensureServerWithRetries(force ? 'replace' : 'adopt');
|
|
1257
1390
|
// Close the global stream up front: its reader points at the crashed
|
|
1258
1391
|
// server's connection. The resume loop below re-opens the (idempotent)
|
|
1259
1392
|
// single stream via connectSse (plan 2026-06-17 S4). Without this the
|
|
@@ -1281,7 +1414,9 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1281
1414
|
// lifecycle lock here exactly like the public resumeSession does, so
|
|
1282
1415
|
// the lock is taken once (no double-acquire from the crash handler).
|
|
1283
1416
|
await this.withLifecycleLock(k, () => this.resumeSessionInner(sessionKey, workDir, sessionId));
|
|
1284
|
-
|
|
1417
|
+
if (turnInFlightKeys.has(k)) {
|
|
1418
|
+
this.emit('output', sessionKey, this.tl(sessionKey, () => (0, i18n_1.t)('openCodeServer.restartedSessionRestoredNotice')));
|
|
1419
|
+
}
|
|
1285
1420
|
}
|
|
1286
1421
|
catch (e) {
|
|
1287
1422
|
// The id is gone on the restarted server (or resume otherwise
|
|
@@ -1290,7 +1425,7 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1290
1425
|
// wipes the persisted session id too — otherwise the next bot
|
|
1291
1426
|
// restart would try to resume an id the server no longer has.
|
|
1292
1427
|
console.error(`[OpenCode] Could not restore session ${sessionId} after restart:`, e);
|
|
1293
|
-
this.emit('output', sessionKey,
|
|
1428
|
+
this.emit('output', sessionKey, this.tl(sessionKey, () => (0, i18n_1.t)('openCodeServer.restartedSessionLostNotice')));
|
|
1294
1429
|
this.stopSessionInner(sessionKey);
|
|
1295
1430
|
this.emit('closed', sessionKey);
|
|
1296
1431
|
// Defensive: stopSessionInner deletes from `this.sessions`, but
|
|
@@ -1301,13 +1436,16 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1301
1436
|
return true;
|
|
1302
1437
|
}
|
|
1303
1438
|
catch (e) {
|
|
1304
|
-
|
|
1439
|
+
// The technical detail stays in the log; the topic gets one short
|
|
1440
|
+
// localized line — a raw exception (`spawn opencode ENOENT`) tells the
|
|
1441
|
+
// person in the topic nothing they can act on.
|
|
1442
|
+
console.error(`[OpenCode] Server restart failed, giving up:`, e);
|
|
1305
1443
|
// Notify threads about the failure and tear them down
|
|
1306
1444
|
// Take a snapshot first so we can safely mutate `this.sessions` inside the loop.
|
|
1307
1445
|
const snapshot = Array.from(this.sessions.entries());
|
|
1308
1446
|
for (const [k, session] of snapshot) {
|
|
1309
1447
|
if (session.isActive) {
|
|
1310
|
-
this.emit('output', session.key,
|
|
1448
|
+
this.emit('output', session.key, this.tl(session.key, () => (0, i18n_1.t)('agent.serverUnavailableNotice', { label: this.label })));
|
|
1311
1449
|
// Mark session as inactive — no point keeping it alive
|
|
1312
1450
|
session.isActive = false;
|
|
1313
1451
|
// This teardown bypasses stopSessionInner, so the replacement bound
|
|
@@ -1319,10 +1457,61 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1319
1457
|
}
|
|
1320
1458
|
return false;
|
|
1321
1459
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* @description The ONE way the adapter brings the server up — a restart, a
|
|
1463
|
+
* session start or resume, the provider sign-in: each attempt first makes sure
|
|
1464
|
+
* the binary is installed, then starts (or adopts) the server per `mode`; a
|
|
1465
|
+
* failed attempt is retried on the {@link openCodeServerRestartRetryDelaysMs}
|
|
1466
|
+
* pauses within {@link openCodeServerRestartRetryBudgetMs}. `onStep` tells a
|
|
1467
|
+
* waiting person what is happening (a start or resume; a restart passes none
|
|
1468
|
+
* and stays silent). Throws {@link AgentBackendUnavailableError} once the
|
|
1469
|
+
* retries are used up — the attempts' errors stay in the log.
|
|
1470
|
+
*/
|
|
1471
|
+
async ensureServerWithRetries(mode, onStep) {
|
|
1472
|
+
const firstAttemptStartedAt = Date.now();
|
|
1473
|
+
for (let retryIndex = 0;; retryIndex++) {
|
|
1474
|
+
try {
|
|
1475
|
+
if (mode === 'ifDown' && await this.serverRestartOps.checkIsServerRunning())
|
|
1476
|
+
return;
|
|
1477
|
+
console.log(`[OpenCode] Bringing the server up (${mode}, attempt ${retryIndex + 1})...`);
|
|
1478
|
+
if (!this.serverRestartOps.checkIsInstalled()) {
|
|
1479
|
+
console.log(`[OpenCode] opencode binary not found, installing it first...`);
|
|
1480
|
+
if (retryIndex === 0)
|
|
1481
|
+
onStep?.('installing');
|
|
1482
|
+
await this.serverRestartOps.installTool();
|
|
1483
|
+
}
|
|
1484
|
+
if (retryIndex === 0)
|
|
1485
|
+
onStep?.('starting');
|
|
1486
|
+
if (mode === 'replace')
|
|
1487
|
+
await this.serverRestartOps.restartServer();
|
|
1488
|
+
else
|
|
1489
|
+
await this.serverRestartOps.ensureServer();
|
|
1490
|
+
console.log(`[OpenCode] Server is up`);
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
catch (e) {
|
|
1494
|
+
const delayMs = getOpenCodeServerRestartRetryDelayMs(retryIndex, Date.now() - firstAttemptStartedAt, this.serverRestartRetryDelaysMs, this.serverRestartRetryBudgetMs);
|
|
1495
|
+
if (delayMs === null) {
|
|
1496
|
+
console.error(`[OpenCode] The server could not be brought up after ${retryIndex + 1} attempts:`, e);
|
|
1497
|
+
throw new backendUnavailableError_1.AgentBackendUnavailableError(this.label, e);
|
|
1498
|
+
}
|
|
1499
|
+
console.warn(`[OpenCode] Attempt ${retryIndex + 1} to bring the server up failed, retrying in ${Math.round(delayMs / 1000)}s:`, e instanceof Error ? e.message : e);
|
|
1500
|
+
await (0, utils_1.sleep)(delayMs);
|
|
1501
|
+
}
|
|
1324
1502
|
}
|
|
1325
1503
|
}
|
|
1504
|
+
/**
|
|
1505
|
+
* @description {@link ensureServerWithRetries} for a session start or resume of
|
|
1506
|
+
* `key`: the server is brought up only when it is down, and the topic is told
|
|
1507
|
+
* (in its language) that OpenCode is being installed or started.
|
|
1508
|
+
*/
|
|
1509
|
+
ensureServerForSession(key) {
|
|
1510
|
+
return this.ensureServerWithRetries('ifDown', (step) => {
|
|
1511
|
+
const noticeKey = step === 'installing' ? 'openCodeServer.installingNotice' : 'openCodeServer.startingNotice';
|
|
1512
|
+
this.emit('output', key, this.tl(key, () => (0, i18n_1.t)(noticeKey)));
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1326
1515
|
/**
|
|
1327
1516
|
* @description Reload credentials written by the out-of-band `opencode auth
|
|
1328
1517
|
* login` CLI. The CLI updates auth.json directly, but a running OpenCode
|
|
@@ -1432,16 +1621,10 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1432
1621
|
// mapping `SessionKey → opencodeSessionId` in state.json (plan §13.19) so
|
|
1433
1622
|
// resumes after a restart go through `resumeSession()`, not startSession.
|
|
1434
1623
|
const k = (0, sessionKey_1.keyToString)(key);
|
|
1624
|
+
await this.waitForServerRestart();
|
|
1435
1625
|
return this.withLifecycleLock(k, async () => {
|
|
1436
1626
|
this.stopSessionInner(key);
|
|
1437
|
-
|
|
1438
|
-
this.emit('output', key, 'Installing OpenCode...');
|
|
1439
|
-
await (0, installManager_1.installTool)('opencode');
|
|
1440
|
-
}
|
|
1441
|
-
if (!await (0, installManager_1.checkIsOpenCodeServerRunning)()) {
|
|
1442
|
-
this.emit('output', key, 'Starting OpenCode server...');
|
|
1443
|
-
await (0, installManager_1.ensureOpenCodeServer)();
|
|
1444
|
-
}
|
|
1627
|
+
await this.ensureServerForSession(key);
|
|
1445
1628
|
console.log(`[OpenCode] Starting session for ${k} in ${workDir}`);
|
|
1446
1629
|
try {
|
|
1447
1630
|
// R1 (verified live 2026-06-04): a session created WITHOUT a title is
|
|
@@ -1490,6 +1673,7 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1490
1673
|
isBusy: false,
|
|
1491
1674
|
awaitingTurnResponse: false,
|
|
1492
1675
|
sawTurnActivity: false,
|
|
1676
|
+
handledTurnErrorTexts: new Set(),
|
|
1493
1677
|
unconsumedInputCount: 0,
|
|
1494
1678
|
seenUserMessageIds: new Set(),
|
|
1495
1679
|
providerRetrySignature: null,
|
|
@@ -1646,6 +1830,14 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1646
1830
|
* model selector. No separate request, no env configuration.
|
|
1647
1831
|
*/
|
|
1648
1832
|
sendPromptAsync(key, input) {
|
|
1833
|
+
if (this.serverRestart) {
|
|
1834
|
+
// Posted now, the prompt would hit the server that is down and be lost with
|
|
1835
|
+
// a raw `fetch failed`; it goes out once the restart has restored the session.
|
|
1836
|
+
// A restart that failed has stopped the session: the topic was told to send again.
|
|
1837
|
+
console.log(`[OpenCode] holding a prompt for ${(0, sessionKey_1.keyToString)(key)} until the server restart ends`);
|
|
1838
|
+
void this.serverRestart.then(() => this.sendPromptAsync(key, input));
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1649
1841
|
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
1650
1842
|
if (!session?.isActive) {
|
|
1651
1843
|
console.log(`[OpenCode] sendInput: no active session for ${(0, sessionKey_1.keyToString)(key)}`);
|
|
@@ -1671,6 +1863,7 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1671
1863
|
// New turn → previous turn's tool-result dedup ids can no longer matter
|
|
1672
1864
|
// (see the field's JSDoc for why the turn flush is too early to clear).
|
|
1673
1865
|
session.emittedToolResultPartIds.clear();
|
|
1866
|
+
session.handledTurnErrorTexts.clear();
|
|
1674
1867
|
// Mark busy optimistically so an immediately-following message correctly
|
|
1675
1868
|
// sees a turn in flight; the `session.status` stream corrects/confirms it.
|
|
1676
1869
|
session.isBusy = true;
|
|
@@ -1960,13 +2153,8 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
1960
2153
|
return (0, i18n_1.t)('disconnect.failed', { provider: normalizedProviderId, reason });
|
|
1961
2154
|
}
|
|
1962
2155
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
await (0, installManager_1.installTool)('opencode');
|
|
1966
|
-
}
|
|
1967
|
-
if (!await (0, installManager_1.checkIsOpenCodeServerRunning)()) {
|
|
1968
|
-
await (0, installManager_1.ensureOpenCodeServer)();
|
|
1969
|
-
}
|
|
2156
|
+
ensureProviderAuthServerReady() {
|
|
2157
|
+
return this.ensureServerWithRetries('ifDown');
|
|
1970
2158
|
}
|
|
1971
2159
|
/**
|
|
1972
2160
|
* @description Fetch a provider's auth methods for the `/connect` picker.
|
|
@@ -2026,7 +2214,7 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
2026
2214
|
}
|
|
2027
2215
|
}
|
|
2028
2216
|
else {
|
|
2029
|
-
session.effortLevel = (0, effortLevels_1.clampEffortToAvailable)(effortLevels_1.
|
|
2217
|
+
session.effortLevel = (0, effortLevels_1.clampEffortToAvailable)((0, effortLevels_1.getInstanceDefaultEffort)(), await this.getModelVariants(resolved));
|
|
2030
2218
|
}
|
|
2031
2219
|
return null;
|
|
2032
2220
|
}
|
|
@@ -2154,7 +2342,7 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
2154
2342
|
* started/resumed session that has NO explicit per-thread `/effort` pref.
|
|
2155
2343
|
*
|
|
2156
2344
|
* Explicit prefs always win and are left untouched (early return). With no
|
|
2157
|
-
* pref, the default (`
|
|
2345
|
+
* pref, the instance default (`getInstanceDefaultEffort`) is clamped to the resolved
|
|
2158
2346
|
* model's variants — OpenCode effort is a model variant and not every model
|
|
2159
2347
|
* ships `xhigh`, so an unclamped value would POST an invalid `body.variant`.
|
|
2160
2348
|
* A model with no variants resolves to `null` (no effort), which is correct.
|
|
@@ -2169,7 +2357,7 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
2169
2357
|
if (loadSavedEffort(key) !== null)
|
|
2170
2358
|
return; // explicit pref wins
|
|
2171
2359
|
const variants = await this.getModelVariants(this.getSessionModelRef(session));
|
|
2172
|
-
session.effortLevel = (0, effortLevels_1.clampEffortToAvailable)(effortLevels_1.
|
|
2360
|
+
session.effortLevel = (0, effortLevels_1.clampEffortToAvailable)((0, effortLevels_1.getInstanceDefaultEffort)(), variants);
|
|
2173
2361
|
}
|
|
2174
2362
|
/**
|
|
2175
2363
|
* @description Resolve the model the thread's effort applies to, WITHOUT
|
|
@@ -2234,19 +2422,57 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
2234
2422
|
}
|
|
2235
2423
|
return this.getModelVariants(await this.getProspectiveModelRef(key));
|
|
2236
2424
|
}
|
|
2425
|
+
async getProspectiveModelLabel(key) {
|
|
2426
|
+
const ref = await this.getProspectiveModelRef(key);
|
|
2427
|
+
return ref ? `${ref.providerID}/${ref.modelID}` : null;
|
|
2428
|
+
}
|
|
2237
2429
|
getEffort(key) {
|
|
2238
2430
|
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
2239
2431
|
if (session)
|
|
2240
2432
|
return session.effortLevel;
|
|
2241
2433
|
return loadSavedEffort(key);
|
|
2242
2434
|
}
|
|
2435
|
+
/** Only the persisted pick: a live session's `effortLevel` may be the clamped default (see {@link getEffort}). */
|
|
2436
|
+
getOwnEffort(key) {
|
|
2437
|
+
return loadSavedEffort(key);
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* The live session sends its `effortLevel` with every prompt (a model variant, or none for `auto` / a model
|
|
2441
|
+
* without variants). The next session seeds the pick, else the default rounded to the variants of the model it
|
|
2442
|
+
* will run (the live one, else the topic's saved `/model`) — read from the last providers snapshot, so this stays
|
|
2443
|
+
* synchronous (the banner needs it); unknown variants (the server's default model, no snapshot yet) leave the
|
|
2444
|
+
* default as it is. A model KNOWN to have no variants runs without any level, so there is no state to show
|
|
2445
|
+
* (`null`, as `/effort` answers «not available» for it).
|
|
2446
|
+
*/
|
|
2447
|
+
getEffortState(key) {
|
|
2448
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
2449
|
+
const liveSession = session?.isActive ? session : null;
|
|
2450
|
+
const pick = loadSavedEffort(key);
|
|
2451
|
+
const defaultLevel = (0, effortLevels_1.getInstanceDefaultEffort)();
|
|
2452
|
+
const variants = getKnownModelVariants(liveSession ? this.getSessionModelRef(liveSession) : loadSavedModel(key));
|
|
2453
|
+
if (variants !== null && variants.length === 0)
|
|
2454
|
+
return null;
|
|
2455
|
+
return {
|
|
2456
|
+
running: liveSession ? liveSession.effortLevel : null,
|
|
2457
|
+
nextStart: pick ?? (0, effortLevels_1.clampEffortToAvailable)(defaultLevel, variants ?? []) ?? defaultLevel,
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
/**
|
|
2461
|
+
* @description Forget the thread's own pick; a live session takes the clamped instance default for its next
|
|
2462
|
+
* prompt ({@link applyDefaultEffortIfUnset}), a later one seeds it at creation.
|
|
2463
|
+
*/
|
|
2464
|
+
async clearEffort(key) {
|
|
2465
|
+
clearEffortPref(key);
|
|
2466
|
+
await this.applyDefaultEffortIfUnset(key);
|
|
2467
|
+
}
|
|
2243
2468
|
/**
|
|
2244
2469
|
* @description Set the per-thread effort (variant). Validates against the
|
|
2245
2470
|
* thread's available variants — live model when a session is running, the
|
|
2246
2471
|
* PROSPECTIVE model (saved `/model` pref → server default) when not — then
|
|
2247
2472
|
* persists on success (applied per-prompt by `sendPromptAsync`, plan D3; a
|
|
2248
|
-
* later session seeds `effortLevel` from this pref at creation).
|
|
2249
|
-
* user-facing
|
|
2473
|
+
* later session seeds `effortLevel` from this pref at creation). `refused` with a
|
|
2474
|
+
* user-facing reason on any non-success, `applied` on success — also with no
|
|
2475
|
+
* session: there is nothing left to do, a later session seeds the pick.
|
|
2250
2476
|
*
|
|
2251
2477
|
* Mirrors `setModel`'s persist-first symmetry (no `'No active session'`
|
|
2252
2478
|
* hard-fail): a level picked BEFORE `/opencode` is saved and replayed by the
|
|
@@ -2261,10 +2487,10 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
2261
2487
|
// session is up, else the prospective model — so the notice is correct
|
|
2262
2488
|
// pre-session (the live `currentModelLabel` is null then).
|
|
2263
2489
|
const modelLabel = await this.getEffortModelLabel(key);
|
|
2264
|
-
return (0, i18n_1.t)('effort.not_supported', { model: modelLabel });
|
|
2490
|
+
return { status: 'refused', reason: (0, i18n_1.t)('effort.not_supported', { model: modelLabel }) };
|
|
2265
2491
|
}
|
|
2266
2492
|
if (!available.includes(level)) {
|
|
2267
|
-
return (0, i18n_1.t)('effort.invalid_level', { level, valid: available.join(', ') });
|
|
2493
|
+
return { status: 'refused', reason: (0, i18n_1.t)('effort.invalid_level', { level, valid: available.join(', ') }) };
|
|
2268
2494
|
}
|
|
2269
2495
|
// Persist unconditionally (like `setModel`'s `saveModelPref`): a session
|
|
2270
2496
|
// started later seeds `effortLevel: loadSavedEffort(key)` at creation.
|
|
@@ -2275,7 +2501,7 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
2275
2501
|
if (session?.isActive)
|
|
2276
2502
|
session.effortLevel = level;
|
|
2277
2503
|
console.log(`[OpenCode] Effort set to: ${level}`);
|
|
2278
|
-
return
|
|
2504
|
+
return { status: 'applied' };
|
|
2279
2505
|
}
|
|
2280
2506
|
/**
|
|
2281
2507
|
* @description Human label for the model the thread's effort validates
|
|
@@ -2494,8 +2720,20 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
2494
2720
|
// resumes to the wrong folder as soon as the bot started managing
|
|
2495
2721
|
// multiple bindings (plan §10.3, fix to old openCodeAdapter.ts:599).
|
|
2496
2722
|
const k = (0, sessionKey_1.keyToString)(key);
|
|
2723
|
+
await this.waitForServerRestart();
|
|
2497
2724
|
return this.withLifecycleLock(k, () => this.resumeSessionInner(key, workDir, sessionId, options));
|
|
2498
2725
|
}
|
|
2726
|
+
/**
|
|
2727
|
+
* @description Let a server restart in progress finish before a session start
|
|
2728
|
+
* or resume brings the server up on its own (it would race the restart's
|
|
2729
|
+
* attempts). Its outcome does not matter here: a failed restart leaves the
|
|
2730
|
+
* server down, and the caller's own attempts follow. Never called from inside
|
|
2731
|
+
* the restart, whose restore loop resumes through the inner body.
|
|
2732
|
+
*/
|
|
2733
|
+
async waitForServerRestart() {
|
|
2734
|
+
if (this.serverRestart)
|
|
2735
|
+
await this.serverRestart;
|
|
2736
|
+
}
|
|
2499
2737
|
/**
|
|
2500
2738
|
* @description Fork the thread's CURRENT session into a new one that carries
|
|
2501
2739
|
* the full conversation, and attach to it. Used by the bot's wedged-turn
|
|
@@ -2554,14 +2792,7 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
2554
2792
|
async resumeSessionInner(key, workDir, sessionId, options) {
|
|
2555
2793
|
this.stopSessionInner(key);
|
|
2556
2794
|
const k = (0, sessionKey_1.keyToString)(key);
|
|
2557
|
-
|
|
2558
|
-
this.emit('output', key, 'Installing OpenCode...');
|
|
2559
|
-
await (0, installManager_1.installTool)('opencode');
|
|
2560
|
-
}
|
|
2561
|
-
if (!await (0, installManager_1.checkIsOpenCodeServerRunning)()) {
|
|
2562
|
-
this.emit('output', key, 'Starting OpenCode server...');
|
|
2563
|
-
await (0, installManager_1.ensureOpenCodeServer)();
|
|
2564
|
-
}
|
|
2795
|
+
await this.ensureServerForSession(key);
|
|
2565
2796
|
console.log(`[OpenCode] Resuming session ${sessionId} for ${k} in ${workDir}`);
|
|
2566
2797
|
try {
|
|
2567
2798
|
// Verify session exists
|
|
@@ -2597,6 +2828,7 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
2597
2828
|
isBusy: false,
|
|
2598
2829
|
awaitingTurnResponse: false,
|
|
2599
2830
|
sawTurnActivity: false,
|
|
2831
|
+
handledTurnErrorTexts: new Set(),
|
|
2600
2832
|
unconsumedInputCount: 0,
|
|
2601
2833
|
seenUserMessageIds: new Set(),
|
|
2602
2834
|
providerRetrySignature: null,
|
|
@@ -4134,12 +4366,60 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
4134
4366
|
if (checkIsOpenCodeAbortError(errorMsg)) {
|
|
4135
4367
|
console.log(`[OpenCode] Swallowed bot-issued abort (message.error "Aborted") for ${(0, sessionKey_1.keyToString)(key)}`);
|
|
4136
4368
|
}
|
|
4369
|
+
else if (info.sessionID && info.sessionID !== session.sessionId) {
|
|
4370
|
+
// A sub-agent's failed step reaches the parent as a failed tool call; it is not the topic's failure.
|
|
4371
|
+
console.log(`[OpenCode] ${(0, sessionKey_1.keyToString)(key)}: message error of session ${info.sessionID}, not the topic's own:`, getOpenCodeErrorLogText(info.error));
|
|
4372
|
+
}
|
|
4137
4373
|
else {
|
|
4138
|
-
|
|
4139
|
-
this.emit('output', key, `Error: ${errorMsg}`);
|
|
4374
|
+
this.handleOwnTurnFailure(key, info.error, 'message.error');
|
|
4140
4375
|
}
|
|
4141
4376
|
}
|
|
4142
4377
|
}
|
|
4378
|
+
/**
|
|
4379
|
+
* @description One failure of the topic's OWN turn, from either channel OpenCode reports it on: `session.error` and the
|
|
4380
|
+
* failed assistant message (`message.updated` with `info.error`). OpenCode publishes most failures on both, in either
|
|
4381
|
+
* order, and may update the message more than once — so a failure is handled ONCE per turn
|
|
4382
|
+
* ({@link OpenCodeSession.handledTurnErrorTexts}); one that comes only on the message (a compaction, a structured-output
|
|
4383
|
+
* step) is handled all the same. Handled = posted (`OpenCode error: …` / `Error: …` by the channel that came first)
|
|
4384
|
+
* and, when the classifier knows it, handed to the
|
|
4385
|
+
* bot's auto-retry (`apiError`). A conversation-tail refusal is not posted: the retry's own notice is its one line.
|
|
4386
|
+
*
|
|
4387
|
+
* Classified from the RAW payload, not from the surfaced text: that collapses an unrecognised shape to a constant
|
|
4388
|
+
* that carries no marker to classify. The raw text stops here — the classifier returns only a class.
|
|
4389
|
+
*/
|
|
4390
|
+
handleOwnTurnFailure(key, error, channel) {
|
|
4391
|
+
const errorMsg = getOpenCodeErrorMessage(error);
|
|
4392
|
+
const session = this.sessions.get((0, sessionKey_1.keyToString)(key));
|
|
4393
|
+
if (session?.handledTurnErrorTexts.has(errorMsg)) {
|
|
4394
|
+
console.log(`[OpenCode] ${(0, sessionKey_1.keyToString)(key)}: the turn's failure came again on ${channel} — already handled, not posted again`);
|
|
4395
|
+
return;
|
|
4396
|
+
}
|
|
4397
|
+
session?.handledTurnErrorTexts.add(errorMsg);
|
|
4398
|
+
console.error(`[OpenCode] ${(0, sessionKey_1.keyToString)(key)}: turn failure (${channel}):`, getOpenCodeErrorLogText(error));
|
|
4399
|
+
// A PARENT-session error aborts the current turn/generation, so the
|
|
4400
|
+
// delegation the dedicated sub-agent status describes is no longer running:
|
|
4401
|
+
// close it defensively (mirrors the parent-idle / parent-finish closes) so
|
|
4402
|
+
// an error that never reaches a clean idle can't leave the "working" frame
|
|
4403
|
+
// and its self-re-arming tick wedged, flooding the topic (live 2026-08-03).
|
|
4404
|
+
// If an auto-retry re-delegates, a fresh `subagentStatus{active:true}` re-opens it.
|
|
4405
|
+
if (session?.isActive)
|
|
4406
|
+
this.closeSubagentStatusOnParentTurnEnd(key);
|
|
4407
|
+
// Provider-side API error at the proxy boundary → emit `apiError`. transient / usageLimit / conversationTail arm
|
|
4408
|
+
// the auto-retry (the session stays active after the error, so the kick reuses it); `auth` (logged out / bad
|
|
4409
|
+
// credentials) is SURFACED as a pinned notice by the bot, never retried. Unrecognised errors classify to `null`.
|
|
4410
|
+
const now = Date.now();
|
|
4411
|
+
const apiError = (0, apiErrorRetry_1.classifyAgentApiError)(getOpenCodeErrorClassificationText(error), now);
|
|
4412
|
+
if (apiError?.kind !== 'conversationTail')
|
|
4413
|
+
this.emit('output', key, `${openCodeFailurePrefixes[channel]}${errorMsg}`);
|
|
4414
|
+
if (!apiError)
|
|
4415
|
+
return;
|
|
4416
|
+
// A usage limit whose text names no reset takes the provider's Retry-After, read as a field — unless it is a
|
|
4417
|
+
// short retry hint (a daily quota comes with Google's 40-second one), which is not the window's reset.
|
|
4418
|
+
const retryAfterAt = apiError.kind === 'usageLimit' && apiError.resetAt === undefined ? getOpenCodeErrorRetryAfterAt(error, now) : undefined;
|
|
4419
|
+
const limitRetryAfterAt = retryAfterAt !== undefined && !(0, apiErrorRetry_1.checkIsShortStatedReset)(retryAfterAt, now) ? retryAfterAt : undefined;
|
|
4420
|
+
const resetAt = apiError.kind === 'usageLimit' ? apiError.resetAt ?? limitRetryAfterAt : undefined;
|
|
4421
|
+
this.emit('apiError', key, apiError.kind === 'usageLimit' ? { ...apiError, resetAt } : apiError);
|
|
4422
|
+
}
|
|
4143
4423
|
/**
|
|
4144
4424
|
* @description Handle session becoming idle (AI done processing).
|
|
4145
4425
|
* Flush any remaining accumulated output.
|
|
@@ -4387,32 +4667,18 @@ class OpenCodeAdapter extends events_1.EventEmitter {
|
|
|
4387
4667
|
}
|
|
4388
4668
|
return;
|
|
4389
4669
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
//
|
|
4393
|
-
//
|
|
4394
|
-
//
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
if (session?.isActive && isOwnSession) {
|
|
4401
|
-
this.closeSubagentStatusOnParentTurnEnd(key);
|
|
4402
|
-
}
|
|
4403
|
-
// Provider-side API error at the proxy boundary → emit `apiError`. transient
|
|
4404
|
-
// / usageLimit arm the auto-retry (the session stays active after
|
|
4405
|
-
// `session.error`, so the kick reuses it); `auth` (logged out / bad
|
|
4406
|
-
// credentials) is SURFACED as a pinned notice by the bot, never retried.
|
|
4407
|
-
// Unrecognised errors classify to `null` and emit nothing.
|
|
4408
|
-
//
|
|
4409
|
-
// Classified from the RAW payload, not from `errorMsg`: the surfaced text
|
|
4410
|
-
// collapses an unrecognised shape to a constant that carries no marker to
|
|
4411
|
-
// classify. The raw text stops here — the classifier returns only a class.
|
|
4412
|
-
const apiError = (0, apiErrorRetry_1.classifyAgentApiError)(getOpenCodeErrorClassificationText(properties.error), Date.now());
|
|
4413
|
-
if (apiError) {
|
|
4414
|
-
this.emit('apiError', key, apiError);
|
|
4670
|
+
// Only the topic's OWN session's error is the topic's: a sub-agent's child session (often on another model or
|
|
4671
|
+
// provider) and a foreign session routed here by the folder report limits that are not this conversation's —
|
|
4672
|
+
// armed, they held a parent that was still working and marked its model limited. A child's real limit reaches the
|
|
4673
|
+
// parent as a failed tool call; if it is the parent's too, the parent's own retry or error says so. Such an error is
|
|
4674
|
+
// still shown, but never closes the parent's sub-agent status (its delegation is still in flight).
|
|
4675
|
+
if (!isOwnSession) {
|
|
4676
|
+
console.error(`[OpenCode] Session error:`, getOpenCodeErrorLogText(properties.error));
|
|
4677
|
+
this.emit('output', key, `OpenCode error: ${errorMsg}`);
|
|
4678
|
+
console.log(`[OpenCode] ${(0, sessionKey_1.keyToString)(key)}: session.error of session ${eventSessionId}, not the topic's own — no retry armed`);
|
|
4679
|
+
return;
|
|
4415
4680
|
}
|
|
4681
|
+
this.handleOwnTurnFailure(key, properties.error, 'session.error');
|
|
4416
4682
|
}
|
|
4417
4683
|
handlePermissionAsked(_key, properties, directory) {
|
|
4418
4684
|
console.log(`[OpenCode] Permission requested:`, JSON.stringify(properties));
|