openmausbot 0.1.97 → 0.1.98
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/dist/assets/ActivityPanel-H6D3SD39.js +1 -0
- package/dist/assets/BotSettingsDialog-Dqz_AiBZ.js +7 -0
- package/dist/assets/CloudBackendPicker-iezpJFpF.js +1 -0
- package/dist/assets/ComputerPanel-DZPj-IWu.js +1 -0
- package/dist/assets/DesktopViewer-utONms1p.js +5 -0
- package/dist/assets/InspectorPanel-OVaBVrzC.js +5 -0
- package/dist/assets/KeyboardShortcutsModal-gk9mvHj_.js +1 -0
- package/dist/assets/LocalVmWorkspace-DLIvd2kC.js +1 -0
- package/dist/assets/MacLocalControl-bJv8yiPB.js +1 -0
- package/dist/assets/NewBotDialog-1qGOAdUM.js +2 -0
- package/dist/assets/RemoteAgentSettingsPanel-DqKr0HkO.js +1 -0
- package/dist/assets/RoutinesPage-BM2D4qGi.js +1 -0
- package/dist/assets/RoutinesSection-Dny9ra-c.js +1 -0
- package/dist/assets/SettingsModal-gC6XJJmt.js +11 -0
- package/dist/assets/TeamMapPage-B6fuu4r_.js +8 -0
- package/dist/assets/TriggersPanel-j0qTP2CO.js +1 -0
- package/dist/assets/VisibilitySection-DT0FqPvu.js +12 -0
- package/dist/assets/WebhooksPanel--YQyCjKO.js +3 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-k1UCNXb7.js → abnfDiagram-O67JEVCF-BsjcFX0-.js} +1 -1
- package/dist/assets/activity-BxOkayvD.js +1 -0
- package/dist/assets/{arc-BUWGa9UU.js → arc-CJNA94c6.js} +1 -1
- package/dist/assets/architectureDiagram-NJMV4G6O-dz2ud7lP.js +36 -0
- package/dist/assets/{blockDiagram-BEXU5L5S-Ag9nLW4H.js → blockDiagram-BEXU5L5S-ChBEALoR.js} +1 -1
- package/dist/assets/brain-CFZ7PEXc.js +1 -0
- package/dist/assets/{c4Diagram-YGBWAQC7-Bh0sjgUa.js → c4Diagram-YGBWAQC7-KZNRKDQe.js} +1 -1
- package/dist/assets/channel-WhFyujhZ.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BFBcIIqu.js → chunk-2Q5K7J3B-BLLwmCET.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-Dvfpex-f.js → chunk-3FUC2YCW-CQxyreBZ.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-CMjP2YZW.js → chunk-5VM5RSS4-D6sFMW0u.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BryoeTc7.js → chunk-GTNCS2PH-BaZ1YgLF.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BhfvcchO.js → chunk-GWA4HPMP-CcalphmC.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-bcCVp8rp.js → chunk-JWPE2WC7-CSk8R3ve.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-yKIjIlkQ.js → chunk-NETBCI7D-CTmLjA57.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-D3Zqbmup.js → chunk-WEXAMYUT-CM9Z77Ne.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-CjVoIMG6.js → chunk-XXDRQBXY-B1fxpMS2.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-mUCWdVyH.js → classDiagram-v2-NBCMYWYE-DCCyoLpj.js} +1 -1
- package/dist/assets/computer-control-NLov352p.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-KyImBKvE.js +1 -0
- package/dist/assets/{cynefin-OW5HDTMX-BpRrxoO4.js → cynefin-OW5HDTMX-fsZc6MGa.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-D3VBWCPK.js → cynefinDiagram-VND7K2PF-CGmHsShB.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DxEIACOO.js → dagre-6A5THRUB-Dah1h-CH.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BANgZEOS.js → diagram-22UHCM2B-BIu71OyV.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-Cnq7nJir.js → diagram-3UASUU5V-gxhUB_Mu.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-4TBDIWnz.js → diagram-ATOU4E4O-CBhusUBn.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CD_B39D4.js → diagram-CDSNMT55-BdPrc86x.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-CzvnFhEC.js → diagram-MLGK6HIB-Clp110R_.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-C4w2SNml.js → diagram-MPIPVDR6-DJKfbugw.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-gkUEBm2s.js → ebnfDiagram-ZINNZB2B-DnsltoBu.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-B_56X6mw.js → elk-276RUBZZ-jWdq6gqH.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-C_oByoDS.js → erDiagram-OPXOYQCR-DArPVf8u.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CR9xXJkC.js → flowDiagram-KWPJA3E3-D2HZ-2f7.js} +1 -1
- package/dist/assets/ganttDiagram-FUAMR5RP--o0KIxEo.js +292 -0
- package/dist/assets/{gitGraphDiagram-X574FWY7-VJ6-Tp_p.js → gitGraphDiagram-X574FWY7-CsoXr2D7.js} +1 -1
- package/dist/assets/image-plus-BbVjGHin.js +1 -0
- package/dist/assets/index-BrPW5HDO.css +1 -0
- package/dist/assets/index-C3NqfdVU.js +585 -0
- package/dist/assets/{index-Cp4S4NOL.js → index-CJTp9JEp.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-2Lu9te4D.js → infoDiagram-VRGFBTTK-g-6lNp7r.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-DR49XY-e.js → ishikawaDiagram-OU5B5YK6-B1d6qVTV.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-ZoR0M305.js → journeyDiagram-ZHPQQLJL-BTPuiU6T.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DLKHxrO-.js → kanban-definition-PNTS6WVX-bDDuRJvP.js} +1 -1
- package/dist/assets/{linear-BASsfWly.js → linear-BVVKbEMy.js} +1 -1
- package/dist/assets/{mermaid.core-BVidiPkk.js → mermaid.core-JJdoKyKB.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-g_Qx0U5C.js → mindmap-definition-NLK3R4M7-B4aE3T0q.js} +1 -1
- package/dist/assets/module-DCUsy2BQ.js +3 -0
- package/dist/assets/page-visible-BBo_Zm8g.js +1 -0
- package/dist/assets/{pegDiagram-GJSIUBJH-DI9Cjs8w.js → pegDiagram-GJSIUBJH-DFKQWpR7.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-sRI0Uhr5.js → pieDiagram-5QR66LMP-Iv7OKVJi.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-CamKTby3.js → quadrantDiagram-O4NWA36T-U2YnfwBk.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-DOLlkbf3.js → railroadDiagram-XR7U4H2S-CuPy6RqM.js} +1 -1
- package/dist/assets/remote-desktop-panel-BNEj7tUx.js +1 -0
- package/dist/assets/{requirementDiagram-PLB6GJNP-DrRQ5r97.js → requirementDiagram-PLB6GJNP-bMI5AK7T.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-DGS_lXcX.js → sankeyDiagram-IPEJSGJF-BKcFE2WQ.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-CkjWqwtf.js → sequenceDiagram-PO4LG4MO-Bn9q88kl.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CepRgful.js → sizeCapture-INFHLROL-Cvmdq9HD.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-hykr-9RS.js → stateDiagram-v2-GCMORJYK-JJQYw3eQ.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-CKOHEZbI.js → swimlanes-2SLR337P-BkSCBEwp.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CmTirbVT.js +8 -0
- package/dist/assets/table-file.worker-KkMx-SNx.js +10 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CB8pJ6AC.js → timeline-definition-EJHVYXUP-DsaM4GHr.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-DrclJHdl.js → usecaseDiagram-POWQR4AR-3FOwLq7B.js} +1 -1
- package/dist/assets/user-DcZBnAuv.js +1 -0
- package/dist/assets/{vennDiagram-UO4OBE2U-zS23qjv8.js → vennDiagram-UO4OBE2U-DyNjJsKt.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-52jgeKxz.js → wardleyDiagram-VNRHLVJA-Y4ZMcnBC.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DSdl9rQ8.js → xychartDiagram-PMCCYNJV-BXZpg955.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +19 -8
- package/dist-server/container-mcp.js +8 -0
- package/dist-server/desktop-entry.mjs +54 -0
- package/dist-server/drivers/agents-proxy.js +21 -4
- package/dist-server/index.js +1412 -821
- package/dist-server/openmausbot.js +837 -571
- package/dist-server/pair-cli.js +837 -571
- package/dist-server/permission-proxy.js +2 -0
- package/dist-server/server/atomic.js +70 -2
- package/dist-server/server/cloud-home-start.js +3 -3
- package/dist-server/server/config.js +22 -1
- package/dist-server/server/connector-card-text.js +15 -0
- package/dist-server/server/default-model-selection.js +7 -4
- package/dist-server/server/drivers/acp/core.js +10 -0
- package/dist-server/server/drivers/acp/grok.js +47 -5
- package/dist-server/server/drivers/agents-catalog.js +24 -4
- package/dist-server/server/drivers/agents-proxy.js +1 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +5 -3
- package/dist-server/server/drivers/claude-login-auth.js +1 -1
- package/dist-server/server/drivers/claude.js +35 -11
- package/dist-server/server/drivers/codex-device-auth.js +27 -319
- package/dist-server/server/drivers/device-auth.js +342 -0
- package/dist-server/server/drivers/openai-chat.js +12 -4
- package/dist-server/server/fleet-agent.js +18 -5
- package/dist-server/server/harness/http.js +5 -2
- package/dist-server/server/harness/registry.js +19 -0
- package/dist-server/server/harness-capabilities.js +7 -4
- package/dist-server/server/hosted-models.js +3 -1
- package/dist-server/server/index.js +268 -54
- package/dist-server/server/key-rejections.js +57 -0
- package/dist-server/server/message-file.js +7 -4
- package/dist-server/server/permission-proxy.js +4 -0
- package/dist-server/server/provider-key-check.js +5 -1
- package/dist-server/server/request-auth.js +1 -1
- package/dist-server/server/room-turn-timeout.js +28 -0
- package/dist-server/server/routes/thread-models.js +36 -0
- package/dist-server/server/routines.js +2 -2
- package/dist-server/server/skills.js +3 -2
- package/dist-server/server/store.js +148 -60
- package/dist-server/server/team-backup.js +19 -4
- package/dist-server/server/thread-model.js +25 -0
- package/dist-server/server/turn-continuation.js +8 -2
- package/dist-server/server/webhook-ingress.js +8 -3
- package/dist-server/server/webhooks.js +40 -5
- package/dist-server/shared/device-sign-in.js +57 -0
- package/dist-server/shared/team-backup.js +2 -0
- package/dist-server/shared/thread-model.js +13 -0
- package/dist-server/vps-container-mcp.js +8 -0
- package/dist-server/workspace-backup.worker.js +41 -22
- package/package.json +1 -1
- package/dist/assets/DesktopViewer-CPidMCtt.js +0 -5
- package/dist/assets/architectureDiagram-NJMV4G6O-DiPor3UT.js +0 -36
- package/dist/assets/channel-DR8eD6QW.js +0 -1
- package/dist/assets/cose-bilkent-JH36ORCC-BBmR1bbX.js +0 -1
- package/dist/assets/ganttDiagram-FUAMR5RP-gtYBTu0B.js +0 -292
- package/dist/assets/index-CpaIXRLp.js +0 -614
- package/dist/assets/index-Z81SMsgR.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D-8vqZ4-.js +0 -8
|
@@ -52,7 +52,7 @@ import { messageFileDisposition, messageFileDownloadName, messageAttachmentName,
|
|
|
52
52
|
import { avatarGenerationRequestSchema, avatarGenerationStateMatches, generateAvatarImage, avatarImageStatus, snapshotAvatarGenerationState, } from "./avatar-image.js";
|
|
53
53
|
import { fitsOnOneLine, parseBotProfilePatch } from "./bot-profile.js";
|
|
54
54
|
import { groupTurnCwd } from "./room-cwd.js";
|
|
55
|
-
import { RoomTurnDeadline, RoomTurnStallRegistry, roomTurnTimeoutMessage } from "./room-turn-timeout.js";
|
|
55
|
+
import { RoomTurnDeadline, RoomTurnStallRegistry, effectiveRoomTurnTimeoutMinutes, parseConversationTurnTimeout, roomTurnTimeoutMessage } from "./room-turn-timeout.js";
|
|
56
56
|
import { roomTurnEnd } from "./room-turn-end.js";
|
|
57
57
|
import { pickComputer } from "./computer-selection.js";
|
|
58
58
|
import * as boat from "./boat.js";
|
|
@@ -67,6 +67,7 @@ import { liveCardKind, liveDecisionRefusal } from "../shared/live-approval.js";
|
|
|
67
67
|
import { boatAccountResourceChangeError, cloudBackendChangeError, vpsAliasResourceChangeError, } from "./cloud-backend.js";
|
|
68
68
|
import * as composio from "./composio.js";
|
|
69
69
|
import { connectorCallFromFrame, connectorRefusalText, connectorUnrecognizedText, evaluateConnectorTools } from "./connector-verdict.js";
|
|
70
|
+
import { connectorCardText } from "./connector-card-text.js";
|
|
70
71
|
import { chiefOfStaffSystemPrompt } from "./chief-of-staff.js";
|
|
71
72
|
import { buildRecall } from "./recall.js";
|
|
72
73
|
import { createMemoryUpkeep, upkeepEnabled } from "./memory-upkeep.js";
|
|
@@ -74,7 +75,7 @@ import { appendAboutMe, commitLearned, planLearned } from "./profile-learned.js"
|
|
|
74
75
|
import { canAccessTeam, canReachPeer, coordinatorSupervises, livePeerRoster, livePeerRosterBlock, peerAllowed, peerName, peerRosterSystemPrompt, peerStatus, peerStatusWords, reachablePeers, resolveTeammate, roomPeerRosterSystemPrompt, roomRosterLine, PEER_ACCESS_HELP } from "./peer-roster.js";
|
|
75
76
|
import { containerComputerAction, containerComputerExists, containerComputerFrame, containerComputerMcp, containerComputerScreenshot, containerComputerStatus, containerExec, containerRuntimeStatus, localVmWakeAction, localVmWorkspaceExists, perBotLocalVmTarget, poolLocalVmTarget, SHARED_LOCAL_VM_TARGET, setupCommands, VM_WORKSPACE_GUEST, } from "./container-computer.js";
|
|
76
77
|
import { attachForTurn, saveBotAttachment } from "./bot-attachment.js";
|
|
77
|
-
import { cacheUntilConfigChanges, ensureDirs, instanceConfigs, loadConfig, providerReloadKeys, localVmIdleTimeoutMinutes, localVmMaxInstances, localVmMode, parseConfigPatch, roomTurnTimeoutMinutes, threadEventLogMaxBytes, maxConcurrentBotThreads, threadEventLogRetentionDays, saveConfig, showToolCallsEnabled, routinesInConversationEnabled, claudeUserMcpEnabled, skillAuthoringEnabled, autoRecallEnabled, captureQuietMs, tidyHour, sharedComputersEnabled, builtInBrowserEnabled, llmThreadTitlesEnabled, browserProfileReplacementConflict, browserProfilePartitionTarget, syncCredentialEnv, stripWorkspaceCredentialEnv, PROVIDER_CREDENTIAL_ENV, withInstanceCli, persistableInstanceConfigs, vpsSshAlias, browserEngineAttachCdpUrl, DATA_DIR, skillsLibraryEnabled, EVENTS_DIR, NATIVE_DIR, customMcpServers, roomHandoffLimits, onConfigSaved, CLAUDE_API_INSTANCE, liveSettingsFor, mergeOpenCodeProviderKeys, openCodeProviderKeys, } from "./config.js";
|
|
78
|
+
import { cacheUntilConfigChanges, ensureDirs, instanceConfigs, loadConfig, providerReloadKeys, localVmIdleTimeoutMinutes, localVmMaxInstances, localVmMode, parseConfigPatch, roomTurnTimeoutMinutes, mcpCallTimeoutMinutes, threadEventLogMaxBytes, maxConcurrentBotThreads, threadEventLogRetentionDays, saveConfig, showToolCallsEnabled, routinesInConversationEnabled, claudeUserMcpEnabled, skillAuthoringEnabled, autoRecallEnabled, captureQuietMs, tidyHour, sharedComputersEnabled, builtInBrowserEnabled, llmThreadTitlesEnabled, browserProfileReplacementConflict, browserProfilePartitionTarget, syncCredentialEnv, stripWorkspaceCredentialEnv, PROVIDER_CREDENTIAL_ENV, withInstanceCli, persistableInstanceConfigs, vpsSshAlias, browserEngineAttachCdpUrl, DATA_DIR, skillsLibraryEnabled, EVENTS_DIR, NATIVE_DIR, customMcpServers, roomHandoffLimits, onConfigSaved, CLAUDE_API_INSTANCE, liveSettingsFor, mergeOpenCodeProviderKeys, openCodeProviderKeys, } from "./config.js";
|
|
78
79
|
import { sweepThreadEventLogs } from "./thread-retention.js";
|
|
79
80
|
import { ComputerControl } from "./computer-control.js";
|
|
80
81
|
import { augmentedPath, findCliCandidates, resetPathCache } from "./env-path.js";
|
|
@@ -83,7 +84,8 @@ import { appendUsage, parseUsageRange, readUsage, summarizeUsage, usageCsv, USAG
|
|
|
83
84
|
import { loadPlanUsage, planAccountsFromInstances } from "./plan-usage.js";
|
|
84
85
|
import { GroupUsageReader } from "./group-thread-usage.js";
|
|
85
86
|
import { ledgerCost } from "./model-prices.js";
|
|
86
|
-
import { checkProviderKey, PROVIDER_KEY_KINDS } from "./provider-key-check.js";
|
|
87
|
+
import { checkProviderKey, PROVIDER_KEY_KINDS, providerBaseUrl } from "./provider-key-check.js";
|
|
88
|
+
import { forgetKey, noteKeyAccepted, noteKeyRejected, onKeyRejectionChange } from "./key-rejections.js";
|
|
87
89
|
import { assertWithinBudget, noteSpend, spendAlertText, spendState, takeSpendAlert } from "./spend.js";
|
|
88
90
|
import { fleetAvailable, fleetRequest, fleetSocketPath } from "./fleet-client.js";
|
|
89
91
|
import { entitled } from "./enterprise.js";
|
|
@@ -126,12 +128,13 @@ import { createCloudMoveRoutes, workspaceShared } from "./cloud-move-http.js";
|
|
|
126
128
|
import { RESTART_EXIT_CODE } from "./restart.js";
|
|
127
129
|
import { holdIncludedServices } from "./included-services.js";
|
|
128
130
|
import { selectDefaultModelSelection, withNewBotEffort } from "./default-model-selection.js";
|
|
131
|
+
import { threadModelFallback } from "./thread-model.js";
|
|
129
132
|
import { computerEngineMoveText, removedComputerInstanceIds, writeComputerEngineMoveLines } from "./computer-engine-removal.js";
|
|
130
133
|
import { cancelPeerApprovalsFor, cancelPeerApprovalsForThread, dismissStalePeerCards, peerApprovalFailure, requestPeerApproval, resolvePeerComms } from "./peer-approval.js";
|
|
131
134
|
import { peerDeliveryReceipt } from "./peer-delivery.js";
|
|
132
135
|
import { peerProvenanceNote, withPeerProvenance } from "./peer-provenance.js";
|
|
133
136
|
import { decideRoomPost, emptyRoomPostBudget } from "./room-post-budget.js";
|
|
134
|
-
import { isProjectEmoji, mentionedBots, parseConnectorTools, roomResponders, sectionKey, Store, titleFromLlm, toWireTask, } from "./store.js";
|
|
137
|
+
import { isProjectEmoji, mentionedBots, parseConnectorTools, roomResponders, sectionKey, Store, threadTitleFrom, titleFromLlm, toWireTask, } from "./store.js";
|
|
135
138
|
import * as tts from "./tts/index.js";
|
|
136
139
|
import { createDecider, deciderIncludedHere, deciderReady, deciderSavePatch, describeDecider } from "./decider/index.js";
|
|
137
140
|
import { decideRoomResponder } from "./decider/room-routing.js";
|
|
@@ -244,6 +247,7 @@ import { createHostedSlackRoutes } from "./routes/hosted-slack.js";
|
|
|
244
247
|
import { createBotPresetRoutes } from "./routes/bot-presets.js";
|
|
245
248
|
import { createBotMemoryRoutes } from "./routes/bot-memory.js";
|
|
246
249
|
import { createDeciderRoutes } from "./routes/decider.js";
|
|
250
|
+
import { createThreadModelRoutes } from "./routes/thread-models.js";
|
|
247
251
|
import { createDesktopViewer, desktopViewerUrl } from "./routes/desktop-viewer.js";
|
|
248
252
|
import { localDesktopTarget, localVmViewerStatus, viewerTargetId } from "./desktop-viewer-targets.js";
|
|
249
253
|
import { createAntigravityLeftoverRoutes } from "./routes/antigravity-leftovers.js";
|
|
@@ -835,12 +839,13 @@ function openerFrom(fromThreadId) {
|
|
|
835
839
|
return person;
|
|
836
840
|
return person && !cloudOwnerPerson(person) ? person : CLOUD_NOBODY_KEY;
|
|
837
841
|
}
|
|
838
|
-
/** The model a thread a bot opens from one of its own threads starts on
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
842
|
+
/** The model a thread a bot opens from one of its own threads starts on
|
|
843
|
+
* (#1950): the model a person picked in that thread, kept as the new thread's
|
|
844
|
+
* own. A thread that follows the bot opens one that follows it too. Only the
|
|
845
|
+
* bot's own thread counts — a room or a teammate's thread is not one of its
|
|
846
|
+
* tasks, and another bot's model belongs to another engine. Approval stays
|
|
847
|
+
* the bot's, except that a level the inherited engine would have to confirm
|
|
848
|
+
* on a model switch starts the new thread in Ask. */
|
|
844
849
|
function parentThreadModel(botId, parentThreadId) {
|
|
845
850
|
// The stored profile, never a per-thread projection: the comparison is
|
|
846
851
|
// against the defaults the new thread would otherwise get.
|
|
@@ -1668,6 +1673,9 @@ const managedPolicy = new ManagedDesktopPolicy({ onChange: () => {
|
|
|
1668
1673
|
broadcast({ kind: "config", ...configStatus() });
|
|
1669
1674
|
resyncOpenCodeProviderKeys();
|
|
1670
1675
|
} });
|
|
1676
|
+
// A key the provider refused, or accepted again, changes what Model
|
|
1677
|
+
// providers and the picker show; the config event refreshes /api/instances.
|
|
1678
|
+
onKeyRejectionChange(() => broadcast({ kind: "config", ...configStatus() }));
|
|
1671
1679
|
/** A computer kind this server will not use, refused before anything is
|
|
1672
1680
|
* prepared: a Cloud home never offers this computer or a Local VM
|
|
1673
1681
|
* (cloud-home.ts), and an enrolled organisation may disallow any kind. */
|
|
@@ -2055,6 +2063,9 @@ function agentsIntegration(botId, threadId, depth, skillAuthoring, generation, r
|
|
|
2055
2063
|
const speaking = botForThread(botId, threadId) ?? store.bot(botId);
|
|
2056
2064
|
return tts.voiceReady(cfg, speaking?.voice) && speaking?.voiceNotes !== false ? "1" : "0";
|
|
2057
2065
|
})(),
|
|
2066
|
+
// And for a role: team setup, bot creation and deletion, rooms and
|
|
2067
|
+
// retries are shown only to a Chief of Staff, whom their routes require.
|
|
2068
|
+
OMB_CHIEF_OF_STAFF: store.bot(botId)?.chiefOfStaff === true ? "1" : "0",
|
|
2058
2069
|
},
|
|
2059
2070
|
};
|
|
2060
2071
|
}
|
|
@@ -3005,6 +3016,62 @@ function checkedTaskModelSwitch(current, raw, updateBotDefault, resetApprovalToA
|
|
|
3005
3016
|
}
|
|
3006
3017
|
return checked;
|
|
3007
3018
|
}
|
|
3019
|
+
/** An engine as a turn's start sees it, without waiting on its CLI: the live
|
|
3020
|
+
* instance and what its last read said (registry.lastSnapshot). Before the
|
|
3021
|
+
* first read it counts as available, so only what is known stops a pick. */
|
|
3022
|
+
function threadEngine(instanceId) {
|
|
3023
|
+
const live = registry.get(instanceId);
|
|
3024
|
+
if (!live)
|
|
3025
|
+
return undefined;
|
|
3026
|
+
return {
|
|
3027
|
+
instanceId, driverKind: live.driverKind, enabled: live.enabled, models: live.models,
|
|
3028
|
+
capabilities: { modelVariants: live.adapter.capabilities.modelVariants === true },
|
|
3029
|
+
snapshot: registry.lastSnapshot(instanceId) ?? { state: "available" },
|
|
3030
|
+
access: providerConfigs()[instanceId]?.access ?? registry.access(instanceId),
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
3033
|
+
/** This thread's own model, when it gives way to its bot's now
|
|
3034
|
+
* (threadModelFallback); null when the thread follows its bot or its own
|
|
3035
|
+
* model runs as picked. */
|
|
3036
|
+
function threadModelGivingWay(botId, threadId) {
|
|
3037
|
+
const bot = store.bot(botId);
|
|
3038
|
+
const own = store.taskByThread(botId, threadId)?.modelSelection;
|
|
3039
|
+
if (!bot || !own)
|
|
3040
|
+
return null;
|
|
3041
|
+
return threadModelFallback(own, bot.modelSelection, threadEngine, {
|
|
3042
|
+
refusal: policyModelRefusal,
|
|
3043
|
+
...(hostedModels ? { allows: (selection) => hostedModels.allows(selection) } : {}),
|
|
3044
|
+
}) ? own : null;
|
|
3045
|
+
}
|
|
3046
|
+
/** The model a turn in this thread would run on now (healThreadModel). */
|
|
3047
|
+
function turnSelection(botId, threadId) {
|
|
3048
|
+
return threadModelGivingWay(botId, threadId) ? store.bot(botId)?.modelSelection : store.projectBotForTask(botId, threadId)?.modelSelection;
|
|
3049
|
+
}
|
|
3050
|
+
/** A model as a person reads it: its catalog label, after its engine's name
|
|
3051
|
+
* while that engine is here. */
|
|
3052
|
+
function modelName(selection) {
|
|
3053
|
+
const instance = registry.get(selection.instanceId);
|
|
3054
|
+
const label = instance?.models.options.find((option) => option.id === selection.model)?.label ?? selection.model;
|
|
3055
|
+
return instance ? `${instance.displayName || instance.driverKind} · ${label}` : label;
|
|
3056
|
+
}
|
|
3057
|
+
/** A thread's own model that can't run here gives way to its bot's: the turn
|
|
3058
|
+
* about to start runs on the bot's model, the thread follows the bot from
|
|
3059
|
+
* then on, and one plain line in it says so. Every turn starts through
|
|
3060
|
+
* startTurn (a person's message, a routine, a delegation, a webhook, a
|
|
3061
|
+
* queued follow-up), so this is the one place it happens; a room turn
|
|
3062
|
+
* already runs on each member's own model. */
|
|
3063
|
+
function healThreadModel(botId, threadId) {
|
|
3064
|
+
const bot = store.bot(botId);
|
|
3065
|
+
const own = threadModelGivingWay(botId, threadId);
|
|
3066
|
+
if (!bot || !own || bot.approvalGrant || threadBusy(botId, threadId))
|
|
3067
|
+
return;
|
|
3068
|
+
if (!store.followBotModel(botId, [threadId], (from, to) => hostedModels?.resetTask(from, to) ?? {}).length)
|
|
3069
|
+
return;
|
|
3070
|
+
store.appendMessage(threadId, { role: "bot", kind: "activity", tool: {
|
|
3071
|
+
name: `notice: This thread's model (${modelName(own)}) isn't available, so it now uses ${bot.name}'s model (${modelName(bot.modelSelection)}).`,
|
|
3072
|
+
ok: true,
|
|
3073
|
+
} });
|
|
3074
|
+
}
|
|
3008
3075
|
function checkedExportSkillNames(value, bots) {
|
|
3009
3076
|
// Sharing instructions is explicit: ordinary package exports include none.
|
|
3010
3077
|
if (value === undefined)
|
|
@@ -3201,15 +3268,27 @@ function checkedMemberIds(value) {
|
|
|
3201
3268
|
return { ok: true, memberIds };
|
|
3202
3269
|
}
|
|
3203
3270
|
let bootSelection = { instanceId: "", model: "" };
|
|
3271
|
+
/** The engines read at start, while it runs (below). It is where the server
|
|
3272
|
+
* learns what each engine supports (a Claude CLI's version and flags), so a
|
|
3273
|
+
* turn waits for it, as every turn did when the server listened only after
|
|
3274
|
+
* that read. */
|
|
3275
|
+
let enginesRead = null;
|
|
3204
3276
|
/** Preset bots for New bot, from shared files and the organization (presets.ts). */
|
|
3205
3277
|
const presetStore = createPresetStore();
|
|
3206
|
-
const store = new Store(() => bootSelection, (selection) => withNewBotEffort(selection, cfg.newBots?.effort, registry.get(selection.instanceId)?.adapter.capabilities.effortLevels));
|
|
3278
|
+
const store = new Store(() => bootSelection, (selection) => withNewBotEffort(selection, cfg.newBots?.effort, registry.get(selection.instanceId)?.adapter.capabilities.effortLevels), (instanceId) => registry.cliTarget(instanceId)?.driverKind);
|
|
3207
3279
|
const teamComputers = new TeamComputers(join(DATA_DIR, "team-computers.json"), ENVIRONMENT_ID);
|
|
3208
3280
|
let followupsReady = false;
|
|
3209
3281
|
const sendSequencer = new SendSequencer();
|
|
3210
3282
|
// Before the new-bot default is read: a saved default may name the engine.
|
|
3211
3283
|
await retryComputerEngineMove();
|
|
3212
|
-
|
|
3284
|
+
// Reading the new-bot default probes every engine CLI (each up to its
|
|
3285
|
+
// timeout), and at start only the first-run seed needs the answer. A later
|
|
3286
|
+
// start listens without waiting: the same read runs behind it, and turns
|
|
3287
|
+
// wait for it (enginesRead).
|
|
3288
|
+
if (store.needsSeed())
|
|
3289
|
+
bootSelection = await defaultSelection();
|
|
3290
|
+
else
|
|
3291
|
+
enginesRead = defaultSelection().then((selection) => { bootSelection = selection; }, (error) => console.warn(`[engines] reading the engines at start failed: ${error instanceof Error ? error.message : String(error)}`)).finally(() => { enginesRead = null; });
|
|
3213
3292
|
store.seedIfEmpty();
|
|
3214
3293
|
hostedModels?.reconcile(store);
|
|
3215
3294
|
// Skills library boot sweep (features.skillsLibrary): migrate per-bot
|
|
@@ -3263,11 +3342,13 @@ if (browserCleanupReferencesReconciled)
|
|
|
3263
3342
|
* than the desktop window did. Stripped here rather than at each call site
|
|
3264
3343
|
* so a new broadcast cannot forget. */
|
|
3265
3344
|
let activeCoordinationForThread = (_threadId) => false;
|
|
3266
|
-
|
|
3345
|
+
/** One thread as a client sees it. `botModel` is its bot's model: a thread
|
|
3346
|
+
* without a model of its own runs on it (toWireTask). */
|
|
3347
|
+
const wireTask = (task, botModel) => {
|
|
3267
3348
|
// Time-based snoozes heal on read against the server clock — no client
|
|
3268
3349
|
// timer, no device skew. The 0 sentinel ("until new activity") is not a
|
|
3269
3350
|
// time and survives reads; only a wake event in the store clears it.
|
|
3270
|
-
const { snoozedUntil, ...base } = toWireTask(task);
|
|
3351
|
+
const { snoozedUntil, ...base } = toWireTask(task, botModel);
|
|
3271
3352
|
const coordinated = { ...base, waitingForTeammates: activeCoordinationForThread(task.threadId) && !task.busy };
|
|
3272
3353
|
const asleep = snoozedUntil === 0 || (snoozedUntil !== undefined && snoozedUntil > Date.now());
|
|
3273
3354
|
return asleep ? { ...coordinated, snoozedUntil } : coordinated;
|
|
@@ -3281,7 +3362,7 @@ const wireBot = (bot) => {
|
|
|
3281
3362
|
? { ...rest, approvalMode: "ask", autoApprove: false }
|
|
3282
3363
|
: rest;
|
|
3283
3364
|
return { ...visible, waitingForTeammates: activeCoordinationForThread(bot.threadId) && !threadBusy(bot.id, bot.threadId),
|
|
3284
|
-
avatarUrl: visible.avatarUrl ?? null, ...(tasks ? { tasks: tasks.map(wireTask) } : {}) };
|
|
3365
|
+
avatarUrl: visible.avatarUrl ?? null, ...(tasks ? { tasks: tasks.map((task) => wireTask(task, bot.modelSelection)) } : {}) };
|
|
3285
3366
|
};
|
|
3286
3367
|
function toolScopeForTurn(botId) {
|
|
3287
3368
|
const bot = store.bot(botId);
|
|
@@ -3296,7 +3377,7 @@ function toolScopeForTurn(botId) {
|
|
|
3296
3377
|
* can validate it before sending the confirmation that makes it effective. */
|
|
3297
3378
|
const wireTrustedApprovalBot = (bot) => {
|
|
3298
3379
|
const { resumeCursors: _resumeCursors, tasks, approvalGrant: _approvalGrant, lastProfileRequestId: _lastProfileRequestId, lastTeamSetupReceipt: _lastTeamSetupReceipt, packageBase: _packageBase, assignedSkills: _assignedSkills, ...rest } = bot;
|
|
3299
|
-
return { ...rest, approvalMode: approvalModeFor(rest), avatarUrl: rest.avatarUrl ?? null, ...(tasks ? { tasks: tasks.map(wireTask) } : {}) };
|
|
3380
|
+
return { ...rest, approvalMode: approvalModeFor(rest), avatarUrl: rest.avatarUrl ?? null, ...(tasks ? { tasks: tasks.map((task) => wireTask(task, bot.modelSelection)) } : {}) };
|
|
3300
3381
|
};
|
|
3301
3382
|
/** A settings-based preview, not a receipt of a dispatched turn. No
|
|
3302
3383
|
* provisioning or credentials are needed to inspect it. The selected engine
|
|
@@ -3950,7 +4031,7 @@ const storedAvatarExists = (avatarUrl) => attachmentExists(avatarUrl.slice("/api
|
|
|
3950
4031
|
const publicBot = (bot) => ({
|
|
3951
4032
|
...wireBot(bot),
|
|
3952
4033
|
...messagePage(bot.threadId, DEFAULT_PAGE),
|
|
3953
|
-
tasks: store.tasks(bot.id).map(wireTask),
|
|
4034
|
+
tasks: store.tasks(bot.id).map((task) => wireTask(task, bot.modelSelection)),
|
|
3954
4035
|
});
|
|
3955
4036
|
const publicBotQueuedMessages = () => queuedSteerSnapshot((botId, threadId) => Boolean(store.taskByThread(botId, threadId)));
|
|
3956
4037
|
// busyBotId names only the speaker that currently owns the provider process.
|
|
@@ -4142,6 +4223,15 @@ function updateChannel(groupId, value) {
|
|
|
4142
4223
|
throw Object.assign(new Error("invalid default responder"), { status: 400 });
|
|
4143
4224
|
patch.defaultResponder = responder;
|
|
4144
4225
|
}
|
|
4226
|
+
if (Object.prototype.hasOwnProperty.call(body, "turnTimeoutMinutes")) {
|
|
4227
|
+
if (!existing.dm) {
|
|
4228
|
+
throw Object.assign(new Error("set the turn limit on that conversation"), { status: 400 });
|
|
4229
|
+
}
|
|
4230
|
+
const parsed = parseConversationTurnTimeout(body.turnTimeoutMinutes);
|
|
4231
|
+
if (!parsed.ok)
|
|
4232
|
+
throw Object.assign(new Error(parsed.error), { status: 400 });
|
|
4233
|
+
patch.turnTimeoutMinutes = parsed.minutes;
|
|
4234
|
+
}
|
|
4145
4235
|
if (body.cwd !== undefined) {
|
|
4146
4236
|
if (existing.dm)
|
|
4147
4237
|
throw Object.assign(new Error("direct-message channels cannot have a working folder"), { status: 400 });
|
|
@@ -4462,7 +4552,14 @@ function publicGroupState(record) {
|
|
|
4462
4552
|
usage = groupUsageReader.forThread(group.threadId);
|
|
4463
4553
|
}
|
|
4464
4554
|
catch { /* accounting must not block chat */ }
|
|
4465
|
-
return {
|
|
4555
|
+
return {
|
|
4556
|
+
...group,
|
|
4557
|
+
// Always present so a client can tell "back to the group default"
|
|
4558
|
+
// apart from a partial patch that did not mention the field.
|
|
4559
|
+
turnTimeoutMinutes: record.turnTimeoutMinutes ?? null,
|
|
4560
|
+
usage: usage ?? null,
|
|
4561
|
+
working: groupIsWorking(group) || [...roomHandoffs.nodes.values()].some(n => n.groupId === group.id && !["completed", "failed", "cancelled"].includes(n.status)),
|
|
4562
|
+
};
|
|
4466
4563
|
}
|
|
4467
4564
|
function beginGroupTurnOperation(groupId, threadId, botIds = []) {
|
|
4468
4565
|
const operation = {
|
|
@@ -8847,6 +8944,8 @@ async function startTurn(botId, text, opts) {
|
|
|
8847
8944
|
},
|
|
8848
8945
|
};
|
|
8849
8946
|
}
|
|
8947
|
+
// A thread whose own model can't run runs on its bot's from now on.
|
|
8948
|
+
healThreadModel(botId, threadId);
|
|
8850
8949
|
const bot = store.projectBotForTask(botId, threadId);
|
|
8851
8950
|
if (!bot)
|
|
8852
8951
|
throw Object.assign(new Error("no such task"), { status: 404 });
|
|
@@ -9088,6 +9187,11 @@ async function startTurn(botId, text, opts) {
|
|
|
9088
9187
|
turnContext.delete(threadId);
|
|
9089
9188
|
void (async () => {
|
|
9090
9189
|
try {
|
|
9190
|
+
if (enginesRead) {
|
|
9191
|
+
await enginesRead;
|
|
9192
|
+
if (!directTurnClaimIsCurrent(bot.id, dispatchClaimId, threadId))
|
|
9193
|
+
throw new DirectTurnSetupCancelled("turn stopped while the engines were read");
|
|
9194
|
+
}
|
|
9091
9195
|
// Readiness can wait on the network. Admit the task first so its busy
|
|
9092
9196
|
// state, Stop action and watchdog own that wait just like VM setup.
|
|
9093
9197
|
if (opts?.runOn === "cloud") {
|
|
@@ -10042,6 +10146,7 @@ async function startTurn(botId, text, opts) {
|
|
|
10042
10146
|
mentionTurn: tagged.length > 0,
|
|
10043
10147
|
integrations,
|
|
10044
10148
|
mcpFromUserConfig: claudeUserMcpEnabled(cfg) && !managedPolicy.restrictsMcp(),
|
|
10149
|
+
mcpCallTimeoutMs: mcpCallTimeoutMinutes(cfg) * 60_000,
|
|
10045
10150
|
cwd,
|
|
10046
10151
|
}), () => !directTurnClaimExists(bot.id, dispatchClaimId, threadId), async () => {
|
|
10047
10152
|
await instance.adapter.interruptTurn(threadId).catch(() => { });
|
|
@@ -10279,6 +10384,9 @@ function routineSourceOwner(run) {
|
|
|
10279
10384
|
? { bot, group: undefined, threadId: task.threadId }
|
|
10280
10385
|
: null;
|
|
10281
10386
|
}
|
|
10387
|
+
// Without a results snapshot (a run from before every routine reported to
|
|
10388
|
+
// its bot's main thread, or a bot with no visible thread) the chat that
|
|
10389
|
+
// made the routine is the destination.
|
|
10282
10390
|
const threadId = run.sourceThreadId?.trim();
|
|
10283
10391
|
if (!threadId)
|
|
10284
10392
|
return null;
|
|
@@ -10299,6 +10407,21 @@ function routineSourceOwner(run) {
|
|
|
10299
10407
|
const group = store.groupByThread(threadId);
|
|
10300
10408
|
return group?.memberIds.includes(bot.id) ? { bot, group, threadId } : null;
|
|
10301
10409
|
}
|
|
10410
|
+
/** There is no durable "main thread" (bot.threadId is only the UI
|
|
10411
|
+
* selection), so a routine reports into the bot's oldest open conversation:
|
|
10412
|
+
* the one it was created with until that is deleted or archived. Deleting
|
|
10413
|
+
* the last one already gives the bot a fresh conversation (Store.deleteTask). */
|
|
10414
|
+
function botMainThread(botId) {
|
|
10415
|
+
const visible = store.tasks(botId).filter((task) => !task.routineRunId);
|
|
10416
|
+
const open = visible.filter((task) => task.archivedAt === undefined);
|
|
10417
|
+
return (open.length ? open : visible).reduce((oldest, task) => (!oldest || task.createdAt < oldest.createdAt ? task : oldest), undefined)?.threadId;
|
|
10418
|
+
}
|
|
10419
|
+
/** Routines without a chosen thread used to get an automatic "<name> ·
|
|
10420
|
+
* Results" thread. One nobody renamed is that automatic default, not a
|
|
10421
|
+
* choice: new runs move to the main thread, and the old thread stays. */
|
|
10422
|
+
function legacyResultsThread(routineName, task) {
|
|
10423
|
+
return task.title.endsWith(" · Results") || task.title === threadTitleFrom(`${routineName} · Results`);
|
|
10424
|
+
}
|
|
10302
10425
|
function routineSourceThread(run) {
|
|
10303
10426
|
return routineSourceOwner(run)?.threadId ?? null;
|
|
10304
10427
|
}
|
|
@@ -10532,21 +10655,32 @@ routines = new RoutineManager({
|
|
|
10532
10655
|
return Boolean(bot && !bot.hidden && task && !task.routineRunId);
|
|
10533
10656
|
},
|
|
10534
10657
|
resolveResultsThread: (routine, forceNew) => {
|
|
10535
|
-
//
|
|
10536
|
-
//
|
|
10537
|
-
//
|
|
10538
|
-
|
|
10539
|
-
|
|
10658
|
+
// Every run reports into the routine's bot's main thread, wherever the
|
|
10659
|
+
// routine was made (chat, editor, API, or a teammate's request). A run
|
|
10660
|
+
// still executes in its own hidden task, which Run logs and Open run use.
|
|
10661
|
+
// A thread the person chose in "Post results to" keeps winning.
|
|
10662
|
+
const bot = store.bot(routine.botId);
|
|
10663
|
+
const chosen = !forceNew && routine.resultsThreadId && bot && !bot.hidden
|
|
10664
|
+
? store.taskByThread(bot.id, routine.resultsThreadId) : undefined;
|
|
10665
|
+
// On a Cloud home a routine reports only where its writer may write: a
|
|
10666
|
+
// guest's (or nobody's) keeps a results conversation opened as theirs.
|
|
10667
|
+
const opener = CLOUD_HOME ? routineOpener(routine.id) : undefined;
|
|
10668
|
+
const main = bot && !bot.hidden ? botMainThread(bot.id) : undefined;
|
|
10669
|
+
const mainAllowed = main && (!CLOUD_HOME || (opener === CLOUD_OWNER_KEY && !cloudGuestOpened(main)));
|
|
10670
|
+
const keepChosen = chosen && !chosen.routineRunId && !(mainAllowed && legacyResultsThread(routine.name, chosen));
|
|
10671
|
+
if (keepChosen)
|
|
10672
|
+
return chosen.threadId;
|
|
10673
|
+
if (mainAllowed || !CLOUD_HOME)
|
|
10674
|
+
return main;
|
|
10540
10675
|
const threadId = store.createTask(routine.botId, `${routine.name} · Results`, false)?.threadId;
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
if (threadId && CLOUD_HOME)
|
|
10544
|
-
threadStarters.set(threadId, routineOpener(routine.id));
|
|
10676
|
+
if (threadId)
|
|
10677
|
+
threadStarters.set(threadId, opener);
|
|
10545
10678
|
return threadId;
|
|
10546
10679
|
},
|
|
10547
10680
|
discardResultsThread: (botId, threadId) => {
|
|
10548
10681
|
const task = store.taskByThread(botId, threadId);
|
|
10549
|
-
|
|
10682
|
+
// The main thread is reused, never allocated, so a failed write keeps it.
|
|
10683
|
+
if (task && threadId !== botMainThread(botId) && !task.busy && !task.routineRunId && store.messagesFor(threadId).length === 0) {
|
|
10550
10684
|
store.deleteTask(botId, threadId);
|
|
10551
10685
|
handoffs.forget(threadId);
|
|
10552
10686
|
}
|
|
@@ -10718,7 +10852,11 @@ if (recoveryOwners.length > 0) {
|
|
|
10718
10852
|
// after the user confirms a durable card. Keeping this beside the scheduler
|
|
10719
10853
|
// makes the card resolvable after an app restart without involving the model.
|
|
10720
10854
|
async function cloudRoutineReadiness(botId, threadId) {
|
|
10721
|
-
const
|
|
10855
|
+
const projected = threadId ? store.projectBotForTask(botId, threadId) : store.bot(botId);
|
|
10856
|
+
// The model its turn will run on: a thread's own model that can't run
|
|
10857
|
+
// gives way to the bot's when the turn starts (healThreadModel).
|
|
10858
|
+
const selection = threadId ? turnSelection(botId, threadId) : projected?.modelSelection;
|
|
10859
|
+
const bot = projected && selection ? { ...projected, modelSelection: selection } : projected;
|
|
10722
10860
|
if (!bot || bot.hidden)
|
|
10723
10861
|
return { ready: false, reason: "The routine's target bot no longer exists." };
|
|
10724
10862
|
if (!boat.boatConfigured(cfg)) {
|
|
@@ -11437,10 +11575,9 @@ const webhooks = new WebhookManager({
|
|
|
11437
11575
|
return;
|
|
11438
11576
|
store.appendMessage(threadId, { role: "bot", kind: "text", text, webhookPost: true });
|
|
11439
11577
|
},
|
|
11440
|
-
//
|
|
11441
|
-
//
|
|
11442
|
-
//
|
|
11443
|
-
// does), reused forever after via trigger.resultsThreadId.
|
|
11578
|
+
// Create-on-first-use, never activated (so it never steals the bot's
|
|
11579
|
+
// live selection the way POST /api/bots/:id/tasks does), reused forever
|
|
11580
|
+
// after via trigger.resultsThreadId.
|
|
11444
11581
|
resolvePostThread: (trigger, forceNew) => {
|
|
11445
11582
|
if (!forceNew && trigger.resultsThreadId)
|
|
11446
11583
|
return trigger.resultsThreadId;
|
|
@@ -12207,6 +12344,10 @@ setupRetry = 0) {
|
|
|
12207
12344
|
{ id: "skill-instructions", label: "Skill instructions", text: renderSkillInstructions(selectedSkills, { includeRoot: Boolean(workspace) }) },
|
|
12208
12345
|
{ id: "playbooks", label: "Playbooks", text: installedPlaybookInstructions(text, bot.playbooks) },
|
|
12209
12346
|
]);
|
|
12347
|
+
// The engines read at start is part of setup (it learns what this engine
|
|
12348
|
+
// supports): wait for it before the claim below.
|
|
12349
|
+
if (enginesRead)
|
|
12350
|
+
await enginesRead;
|
|
12210
12351
|
// run the turn and wait for it to settle, folding the reply text so a
|
|
12211
12352
|
// chained @mention can be routed afterwards
|
|
12212
12353
|
// Claim only after setup succeeded. An unavailable, busy, unsupported, or
|
|
@@ -12246,7 +12387,10 @@ setupRetry = 0) {
|
|
|
12246
12387
|
else
|
|
12247
12388
|
markCancelledProviderHandshake(threadId, retirementOwner);
|
|
12248
12389
|
};
|
|
12249
|
-
const
|
|
12390
|
+
const liveGroup = store.group(groupId) ?? group;
|
|
12391
|
+
const timeoutMinutes = effectiveRoomTurnTimeoutMinutes(roomTurnTimeoutMinutes(cfg), liveGroup.dm
|
|
12392
|
+
? liveGroup.turnTimeoutMinutes
|
|
12393
|
+
: store.groupTaskByThread(liveGroup.id, threadId)?.turnTimeoutMinutes);
|
|
12250
12394
|
const outcome = await new Promise((resolve) => {
|
|
12251
12395
|
let done = false;
|
|
12252
12396
|
let unsub = () => { };
|
|
@@ -12334,6 +12478,7 @@ setupRetry = 0) {
|
|
|
12334
12478
|
cwd,
|
|
12335
12479
|
integrations,
|
|
12336
12480
|
mcpFromUserConfig: claudeUserMcpEnabled(cfg) && !managedPolicy.restrictsMcp(),
|
|
12481
|
+
mcpCallTimeoutMs: mcpCallTimeoutMinutes(cfg) * 60_000,
|
|
12337
12482
|
...(instance.instanceId === readyBot.modelSelection.instanceId
|
|
12338
12483
|
? memberTurnSelection(readyBot.modelSelection)
|
|
12339
12484
|
: { model: instance.models.default }),
|
|
@@ -14262,6 +14407,7 @@ function configStatus() {
|
|
|
14262
14407
|
// not a secret — the settings picker shows it; "" = follow the system
|
|
14263
14408
|
language: cfg.language ?? "",
|
|
14264
14409
|
rooms: { turnTimeoutMinutes: roomTurnTimeoutMinutes(cfg) },
|
|
14410
|
+
mcp: { callTimeoutMinutes: mcpCallTimeoutMinutes(cfg) },
|
|
14265
14411
|
automaticRecovery: cfg.automaticRecovery ?? { enabled: false },
|
|
14266
14412
|
// absent effort = no level is sent, so clients can tell it from any level
|
|
14267
14413
|
newBots: cfg.newBots?.effort ? { effort: cfg.newBots.effort } : {},
|
|
@@ -14617,15 +14763,22 @@ function serveStatic(res, path) {
|
|
|
14617
14763
|
try {
|
|
14618
14764
|
const data = readFileSync(file);
|
|
14619
14765
|
const type = MIME[extname(file)] ?? "application/octet-stream";
|
|
14620
|
-
|
|
14766
|
+
// Vite puts a content hash in the name of every file it writes under
|
|
14767
|
+
// /assets/, so a URL there never changes: the browser keeps it instead of
|
|
14768
|
+
// downloading the bundle on every load. That only holds while public/ has
|
|
14769
|
+
// no assets/ dir (index.test.ts checks). Pages are revalidated, so a new
|
|
14770
|
+
// build shows up on the next load.
|
|
14771
|
+
const cache = type === "text/html" ? "no-cache" : safe.startsWith("/assets/") ? "public, max-age=31536000, immutable" : null;
|
|
14772
|
+
res.writeHead(200, { "content-type": type, ...(type === "text/html" ? PAGE_HEADERS : {}), ...(cache ? { "cache-control": cache } : {}) });
|
|
14621
14773
|
res.end(data);
|
|
14622
14774
|
return true;
|
|
14623
14775
|
}
|
|
14624
14776
|
catch {
|
|
14625
|
-
// SPA fallback
|
|
14777
|
+
// SPA fallback. Never immutable: a missing chunk answered with the page
|
|
14778
|
+
// must not stay cached under the chunk's URL.
|
|
14626
14779
|
try {
|
|
14627
14780
|
const data = readFileSync(join(STATIC_DIR, "index.html"));
|
|
14628
|
-
res.writeHead(200, { "content-type": "text/html", ...PAGE_HEADERS });
|
|
14781
|
+
res.writeHead(200, { "content-type": "text/html", "cache-control": "no-cache", ...PAGE_HEADERS });
|
|
14629
14782
|
res.end(data);
|
|
14630
14783
|
return true;
|
|
14631
14784
|
}
|
|
@@ -14740,6 +14893,15 @@ ROUTES.push(createBotMemoryRoutes({
|
|
|
14740
14893
|
},
|
|
14741
14894
|
}));
|
|
14742
14895
|
ROUTES.push(createDeciderRoutes({ decider }));
|
|
14896
|
+
// "Switch them too": a bot's threads on a model of their own follow its model again.
|
|
14897
|
+
ROUTES.push(createThreadModelRoutes({
|
|
14898
|
+
bot: (id) => store.bot(id),
|
|
14899
|
+
busy: (botId, threadId) => threadBusy(botId, threadId),
|
|
14900
|
+
follow: (botId, threadIds) => store.followBotModel(botId, threadIds, (from, to) => hostedModels?.resetTask(from, to) ?? {}),
|
|
14901
|
+
// Like updateBotDefault on a thread: on a Cloud home, the owner's alone.
|
|
14902
|
+
mayChangeModel: (auth) => !CLOUD_HOME || cloudOwnerSession(auth),
|
|
14903
|
+
reply: (botId) => publicBot(store.bot(botId)),
|
|
14904
|
+
}));
|
|
14743
14905
|
ROUTES.push(createAntigravityLeftoverRoutes({
|
|
14744
14906
|
hosted: Boolean(hostedModels),
|
|
14745
14907
|
isAntigravity: (instanceId) => registry.get(instanceId)?.driverKind === "antigravityAgent",
|
|
@@ -18048,6 +18210,9 @@ const handleRequest = async (req, res) => {
|
|
|
18048
18210
|
const message = store.appendMessage(threadId, {
|
|
18049
18211
|
role: "bot",
|
|
18050
18212
|
kind: "connector",
|
|
18213
|
+
// Read only by clients that predate the card (older phones draw
|
|
18214
|
+
// an unknown kind by its text); see connector-card-text.ts.
|
|
18215
|
+
text: connectorCardText(toolkit.label, item.alias),
|
|
18051
18216
|
...(owner.group ? { from: { botId: owner.bot.id, name: owner.bot.name, color: owner.bot.color } } : {}),
|
|
18052
18217
|
connector: {
|
|
18053
18218
|
slug: item.slug,
|
|
@@ -18460,7 +18625,7 @@ const handleRequest = async (req, res) => {
|
|
|
18460
18625
|
return json(res, 200, {
|
|
18461
18626
|
bots: shownBots.map((bot) => memberBot({
|
|
18462
18627
|
...wireBot(bot),
|
|
18463
|
-
tasks: store.tasks(bot.id).map(wireTask),
|
|
18628
|
+
tasks: store.tasks(bot.id).map((task) => wireTask(task, bot.modelSelection)),
|
|
18464
18629
|
...messagePage(bot.threadId, limit),
|
|
18465
18630
|
}, visible)),
|
|
18466
18631
|
botQueuedMessages: visible.everything ? queued : Object.fromEntries(Object.entries(queued).filter(([threadId]) => visible.thread(threadId))),
|
|
@@ -19424,7 +19589,7 @@ const handleRequest = async (req, res) => {
|
|
|
19424
19589
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
19425
19590
|
return json(res, 400, { error: "body must be a JSON object" });
|
|
19426
19591
|
}
|
|
19427
|
-
const allowed = new Set(["title", "pinned"]);
|
|
19592
|
+
const allowed = new Set(["title", "pinned", "turnTimeoutMinutes"]);
|
|
19428
19593
|
if (Object.keys(body).some((key) => !allowed.has(key)))
|
|
19429
19594
|
return json(res, 400, { error: "unsupported channel thread setting" });
|
|
19430
19595
|
const existing = store.groupTaskByThread(group.id, m[2]);
|
|
@@ -19432,12 +19597,16 @@ const handleRequest = async (req, res) => {
|
|
|
19432
19597
|
return json(res, 404, { error: "no such channel task" });
|
|
19433
19598
|
const pinning = body.pinned !== undefined;
|
|
19434
19599
|
const renaming = body.title !== undefined;
|
|
19435
|
-
|
|
19600
|
+
const limiting = Object.prototype.hasOwnProperty.call(body, "turnTimeoutMinutes");
|
|
19601
|
+
if (!pinning && !renaming && !limiting)
|
|
19436
19602
|
return json(res, 400, { error: "unsupported channel thread setting" });
|
|
19437
19603
|
if (pinning && typeof body.pinned !== "boolean")
|
|
19438
19604
|
return json(res, 400, { error: "pinned must be a boolean" });
|
|
19439
19605
|
if (renaming && typeof body.title !== "string")
|
|
19440
19606
|
return json(res, 400, { error: "title must be a string" });
|
|
19607
|
+
const parsedLimit = limiting ? parseConversationTurnTimeout(body.turnTimeoutMinutes) : null;
|
|
19608
|
+
if (parsedLimit && !parsedLimit.ok)
|
|
19609
|
+
return json(res, 400, { error: parsedLimit.error });
|
|
19441
19610
|
// Echoing the current title lets a newer client pin on an older server
|
|
19442
19611
|
// without the old handler turning a missing title into "Untitled".
|
|
19443
19612
|
// That echo is not a rename. A real rename stays blocked while working.
|
|
@@ -19445,10 +19614,19 @@ const handleRequest = async (req, res) => {
|
|
|
19445
19614
|
const notYours = titleChange ? cloudThreadRefusal(auth, m[2]) : null;
|
|
19446
19615
|
if (notYours)
|
|
19447
19616
|
return json(res, 403, { error: notYours });
|
|
19448
|
-
|
|
19617
|
+
// A longer limit applies to the next turn, so it can be saved while
|
|
19618
|
+
// this one is still running. A rename still waits.
|
|
19619
|
+
const settingsOnly = !titleChange && (pinning || limiting);
|
|
19620
|
+
if (channelTaskBlocked(group) && !settingsOnly) {
|
|
19449
19621
|
return json(res, 409, { error: "this channel is working or waiting on you — finish that turn first" });
|
|
19450
19622
|
}
|
|
19451
19623
|
let task = existing;
|
|
19624
|
+
if (limiting && parsedLimit?.ok) {
|
|
19625
|
+
const limited = store.setGroupTaskTurnTimeout(group.id, m[2], parsedLimit.minutes);
|
|
19626
|
+
if (!limited)
|
|
19627
|
+
return json(res, 404, { error: "no such channel task" });
|
|
19628
|
+
task = limited;
|
|
19629
|
+
}
|
|
19452
19630
|
if (pinning) {
|
|
19453
19631
|
const pinned = store.setGroupTaskPinned(group.id, m[2], body.pinned === true);
|
|
19454
19632
|
if (!pinned)
|
|
@@ -20282,10 +20460,12 @@ const handleRequest = async (req, res) => {
|
|
|
20282
20460
|
}
|
|
20283
20461
|
// patchBot persists first and emits the canonical bot change, which the
|
|
20284
20462
|
// store listener above turns into the slim wire-format SSE broadcast.
|
|
20463
|
+
// Every thread that follows the bot moves with it; the selected thread
|
|
20464
|
+
// follows it from now on too, as if picked there.
|
|
20285
20465
|
const bot = store.patchBot(existing.id, { modelSelection: checked.selection });
|
|
20286
20466
|
if (!bot)
|
|
20287
20467
|
return json(res, 404, { error: "no such bot" });
|
|
20288
|
-
store.patchTask(bot.id, selected.threadId, { modelSelection:
|
|
20468
|
+
store.patchTask(bot.id, selected.threadId, { modelSelection: undefined,
|
|
20289
20469
|
...hostedModels?.resetTask(selected.modelSelection, checked.selection) });
|
|
20290
20470
|
return json(res, 200, { bot: wireBot(bot) });
|
|
20291
20471
|
}
|
|
@@ -20971,8 +21151,10 @@ const handleRequest = async (req, res) => {
|
|
|
20971
21151
|
// and pins that already match stay; returning to Auto sweeps nothing.
|
|
20972
21152
|
if (computerSpecified && requestedComputer !== undefined)
|
|
20973
21153
|
store.clearAutoSurfacePins(m[1], requestedComputer);
|
|
21154
|
+
// A new bot model moves every thread that follows the bot; the selected
|
|
21155
|
+
// thread follows it from now on too.
|
|
20974
21156
|
if (normalizedSelection && selectedTask)
|
|
20975
|
-
store.patchTask(bot.id, selectedTask.threadId, { modelSelection:
|
|
21157
|
+
store.patchTask(bot.id, selectedTask.threadId, { modelSelection: undefined,
|
|
20976
21158
|
...hostedModels?.resetTask(selectedTask.modelSelection, normalizedSelection) });
|
|
20977
21159
|
if (existingBot && (bot.browserProfile !== beforeBrowserProfile || bot.browser !== beforeBrowserEnabled)) {
|
|
20978
21160
|
browserLive.closeForBot(bot.id);
|
|
@@ -21769,12 +21951,15 @@ const handleRequest = async (req, res) => {
|
|
|
21769
21951
|
if (phoneSecretSubmissions.hasThread(bot.threadId)) {
|
|
21770
21952
|
return json(res, 409, { error: "this task is securely saving a credential — try again when it finishes" });
|
|
21771
21953
|
}
|
|
21772
|
-
|
|
21954
|
+
// A thread's own model that can't run gives way to the bot's when the
|
|
21955
|
+
// rerun starts (healThreadModel), so check the model it will run on.
|
|
21956
|
+
const rerunSelection = turnSelection(bot.id, bot.threadId) ?? bot.modelSelection;
|
|
21957
|
+
if (!registry.get(rerunSelection.instanceId)) {
|
|
21773
21958
|
return json(res, 409, {
|
|
21774
|
-
error: `provider instance "${
|
|
21959
|
+
error: `provider instance "${rerunSelection.instanceId}" is unavailable — pick another model in settings`,
|
|
21775
21960
|
});
|
|
21776
21961
|
}
|
|
21777
|
-
const rerunInstance = registry.get(
|
|
21962
|
+
const rerunInstance = registry.get(rerunSelection.instanceId);
|
|
21778
21963
|
const rerunRefusal = policyModelRefusal(rerunInstance);
|
|
21779
21964
|
if (rerunRefusal)
|
|
21780
21965
|
return json(res, 409, { error: rerunRefusal });
|
|
@@ -22205,7 +22390,7 @@ const handleRequest = async (req, res) => {
|
|
|
22205
22390
|
// Who opened it decides who may answer its cards on a shared workspace.
|
|
22206
22391
|
if (auth.kind === "session")
|
|
22207
22392
|
threadStarters.set(task.threadId, actorKey(auth));
|
|
22208
|
-
return json(res, 201, { bot: publicBot(store.bot(bot.id)), task: wireTask(task) });
|
|
22393
|
+
return json(res, 201, { bot: publicBot(store.bot(bot.id)), task: wireTask(task, store.bot(bot.id).modelSelection) });
|
|
22209
22394
|
}
|
|
22210
22395
|
m = path.match(/^\/api\/bots\/([\w-]+)\/tasks\/([\w-]+)$/);
|
|
22211
22396
|
if (m && method === "POST") {
|
|
@@ -22223,7 +22408,7 @@ const handleRequest = async (req, res) => {
|
|
|
22223
22408
|
const switched = store.switchTask(bot.id, m[2]);
|
|
22224
22409
|
if (!switched)
|
|
22225
22410
|
return json(res, 404, { error: "no such task" });
|
|
22226
|
-
const switchedSettings = { ...wireBot(switched), tasks: store.tasks(switched.id).map(wireTask) };
|
|
22411
|
+
const switchedSettings = { ...wireBot(switched), tasks: store.tasks(switched.id).map((task) => wireTask(task, switched.modelSelection)) };
|
|
22227
22412
|
// "0" is settings only, a positive page is a bounded transcript, and no
|
|
22228
22413
|
// parameter is the whole thread — the one branch that materialises it.
|
|
22229
22414
|
// Search results land through that branch (src/lib/focus-message.ts):
|
|
@@ -22305,7 +22490,7 @@ const handleRequest = async (req, res) => {
|
|
|
22305
22490
|
const task = store.refreshTaskPermissions(profile.id, m[2]);
|
|
22306
22491
|
if (!task)
|
|
22307
22492
|
return json(res, 404, { error: "no such task" });
|
|
22308
|
-
return json(res, 200, { task: wireTask(task), bot: publicBot(store.bot(profile.id)) });
|
|
22493
|
+
return json(res, 200, { task: wireTask(task, store.bot(profile.id).modelSelection), bot: publicBot(store.bot(profile.id)) });
|
|
22309
22494
|
}
|
|
22310
22495
|
const patch = {};
|
|
22311
22496
|
if (body.projectId !== undefined) {
|
|
@@ -22411,7 +22596,7 @@ const handleRequest = async (req, res) => {
|
|
|
22411
22596
|
const task = patch.modelSelection
|
|
22412
22597
|
? store.switchTaskModel(m[1], m[2], patch.modelSelection, body.updateBotDefault === true, body.resetApprovalToAsk === true, { ...patch, ...hostedModels?.resetTask(current.modelSelection, patch.modelSelection) })
|
|
22413
22598
|
: store.patchTask(m[1], m[2], patch);
|
|
22414
|
-
return json(res, 200, { task: wireTask(task), bot: publicBot(store.bot(m[1])) });
|
|
22599
|
+
return json(res, 200, { task: wireTask(task, store.bot(m[1]).modelSelection), bot: publicBot(store.bot(m[1])) });
|
|
22415
22600
|
}
|
|
22416
22601
|
if (m && method === "DELETE") {
|
|
22417
22602
|
const bot = store.bot(m[1]);
|
|
@@ -22483,7 +22668,7 @@ const handleRequest = async (req, res) => {
|
|
|
22483
22668
|
const task = store.retitleTask(m[1], m[2], startedFrom, title) ?? store.taskByThread(m[1], m[2]);
|
|
22484
22669
|
if (!task)
|
|
22485
22670
|
return json(res, 404, { error: "no such task" });
|
|
22486
|
-
return json(res, 200, { task: wireTask(task) });
|
|
22671
|
+
return json(res, 200, { task: wireTask(task, store.bot(m[1]).modelSelection) });
|
|
22487
22672
|
}
|
|
22488
22673
|
finally {
|
|
22489
22674
|
titleRegenerations.delete(m[2]);
|
|
@@ -23061,7 +23246,17 @@ const handleRequest = async (req, res) => {
|
|
|
23061
23246
|
return json(res, 400, { error: "That does not look like an API key." });
|
|
23062
23247
|
const url = typeof body?.url === "string" && body.url.trim() ? body.url.trim() : (saved && "url" in saved ? saved.url : undefined);
|
|
23063
23248
|
res.setHeader("cache-control", "no-store");
|
|
23064
|
-
|
|
23249
|
+
const verdict = await checkProviderKey({ provider: kind, key, url });
|
|
23250
|
+
// A Test is a real use the person chose: the engines on this key agree
|
|
23251
|
+
// with its verdict. Claude runs its key through its CLI, not here.
|
|
23252
|
+
if (kind !== "anthropic") {
|
|
23253
|
+
const base = providerBaseUrl(kind, url);
|
|
23254
|
+
if (verdict.ok)
|
|
23255
|
+
noteKeyAccepted(base, key);
|
|
23256
|
+
else if (verdict.reason === "rejected")
|
|
23257
|
+
noteKeyRejected(base, key);
|
|
23258
|
+
}
|
|
23259
|
+
return json(res, 200, verdict);
|
|
23065
23260
|
}
|
|
23066
23261
|
if (method === "GET" && path === "/api/decisions") {
|
|
23067
23262
|
const rawLimit = url.searchParams.get("limit");
|
|
@@ -24091,6 +24286,12 @@ const handleRequest = async (req, res) => {
|
|
|
24091
24286
|
browserCleanup.abort(request);
|
|
24092
24287
|
throw error;
|
|
24093
24288
|
}
|
|
24289
|
+
// A saved or cleared key starts over: neither it nor the key it
|
|
24290
|
+
// replaced stays marked as rejected.
|
|
24291
|
+
const resetKeys = PROVIDER_KEY_KINDS.flatMap((kind) => {
|
|
24292
|
+
const next = patch[kind]?.key;
|
|
24293
|
+
return next === undefined ? [] : [cfg[kind]?.key, next];
|
|
24294
|
+
});
|
|
24094
24295
|
let configWriteCommitted = false;
|
|
24095
24296
|
const externalSecretStorage = url.searchParams.get("secretStorage") === "external";
|
|
24096
24297
|
try {
|
|
@@ -24156,6 +24357,9 @@ const handleRequest = async (req, res) => {
|
|
|
24156
24357
|
}
|
|
24157
24358
|
throw error;
|
|
24158
24359
|
}
|
|
24360
|
+
for (const key of resetKeys)
|
|
24361
|
+
if (key)
|
|
24362
|
+
forgetKey(key);
|
|
24159
24363
|
// Desktops already counting down move to the new window at once, in
|
|
24160
24364
|
// every isolation mode; busy work still defers its expiry.
|
|
24161
24365
|
if (patch.localVm?.idleTimeoutMinutes !== undefined) {
|
|
@@ -24772,7 +24976,7 @@ const handleRequest = async (req, res) => {
|
|
|
24772
24976
|
catch (e) {
|
|
24773
24977
|
const status = e?.status ?? 500;
|
|
24774
24978
|
const candidateCode = e?.code;
|
|
24775
|
-
const code = typeof candidateCode === "string" && ["guarded_busy", "guarded_branch", "guarded_permissions", "guarded_request_changed", "guarded_request_untracked"].includes(candidateCode)
|
|
24979
|
+
const code = typeof candidateCode === "string" && ["guarded_busy", "guarded_branch", "guarded_permissions", "guarded_request_changed", "guarded_request_untracked", "outside_workspace"].includes(candidateCode)
|
|
24776
24980
|
? candidateCode : undefined;
|
|
24777
24981
|
return json(res, status, { error: e instanceof Error ? e.message : String(e), ...(code ? { code } : {}) });
|
|
24778
24982
|
}
|
|
@@ -24922,12 +25126,22 @@ restoreChannelMessages();
|
|
|
24922
25126
|
if (movedAutoPins)
|
|
24923
25127
|
console.log(`Works on: moved ${movedAutoPins} auto-pinned thread(s) to their bot's current Works on`);
|
|
24924
25128
|
}
|
|
24925
|
-
server.listen(PORT, "127.0.0.1", () => {
|
|
25129
|
+
server.listen(PORT, "127.0.0.1", async () => {
|
|
24926
25130
|
companyRuntimeReady();
|
|
24927
25131
|
console.log(`openmausbot server on http://127.0.0.1:${PORT}`);
|
|
25132
|
+
// Words queued before the restart start first, ahead of anything sent from
|
|
25133
|
+
// now on. Their turns wait for the engines read at start like every turn.
|
|
24928
25134
|
followupsReady = true;
|
|
24929
25135
|
drainQueuedSends();
|
|
24930
25136
|
drainQueuedChannelSends();
|
|
25137
|
+
// The work below wakes delegators, and a woken delegator keeps its session
|
|
25138
|
+
// only on an engine that records what it was handed, which that read finds
|
|
25139
|
+
// out: it starts once the read is done.
|
|
25140
|
+
if (enginesRead) {
|
|
25141
|
+
await enginesRead;
|
|
25142
|
+
if (companyShutdown)
|
|
25143
|
+
return;
|
|
25144
|
+
}
|
|
24931
25145
|
// Startup work uses the same turn dispatcher and local tool endpoint as
|
|
24932
25146
|
// ordinary chat. Start only once every registry is initialized and the
|
|
24933
25147
|
// endpoint is listening; earlier dispatch can hit uninitialized bindings.
|