@chibitek/chibi 0.1.155 → 0.1.158
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/{abnfDiagram-O67JEVCF-B8LdsCr1.js → abnfDiagram-O67JEVCF-B8-8zzMS.js} +1 -1
- package/dist/assets/{arc-DU5fSmKU.js → arc-CSHQ9Dx3.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-DdQSIFKv.js → architectureDiagram-NJMV4G6O-BCHM9rXW.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CaPcGYSS.js → blockDiagram-BEXU5L5S-B-5uxPFN.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-unt0ePgO.js → c4Diagram-YGBWAQC7-CuKNM_MR.js} +1 -1
- package/dist/assets/channel-bPN03jIt.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-Babg5Fzu.js → chunk-2Q5K7J3B-BNF02GM4.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-D1-dWVHo.js → chunk-3FUC2YCW-uEWgBd4M.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-DU7nSMbT.js → chunk-5VM5RSS4-eSZuXctY.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-QIjCJIhV.js → chunk-GTNCS2PH-CslS8nRS.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-pSByUE5R.js → chunk-GWA4HPMP-w71-MeKj.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-Dj2AaR1b.js → chunk-JWPE2WC7-Bjju7WGh.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-pZbN0-FP.js → chunk-NETBCI7D-HxrroD4M.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-Bqarwe4D.js → chunk-WEXAMYUT-D7Rz470x.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-WV8lm3aa.js → chunk-XXDRQBXY-BWKnAeyM.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BANBtky1.js → classDiagram-v2-NBCMYWYE-CeBDRHmP.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CtrktgBC.js → cose-bilkent-JH36ORCC-BBbRKlvr.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-DnhVVfba.js → cynefin-OW5HDTMX-W02cnJ0B.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-B42SRR6x.js → cynefinDiagram-VND7K2PF-Cs47eaxd.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DM--vFMK.js → dagre-6A5THRUB-8mG18hzT.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-ZBupBrPU.js → diagram-22UHCM2B-DF4Qrw67.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BPQzNIOd.js → diagram-3UASUU5V-3BAVoed1.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-5kO8vf8Z.js → diagram-ATOU4E4O-CKO3pECE.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DtxogB86.js → diagram-CDSNMT55-CZhrGvur.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Co9-nYw_.js → diagram-MLGK6HIB-C07l9pdQ.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-DWRuMh5y.js → diagram-MPIPVDR6-D3or4lkZ.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DU_nmoG9.js → ebnfDiagram-ZINNZB2B-DV2epXp_.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-Bcje7S94.js → elk-276RUBZZ-DlvEn4-1.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-9aCo_2CI.js → erDiagram-OPXOYQCR-CB6X7Wt5.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-D4FYFyc5.js → flowDiagram-KWPJA3E3-C3bqz4lC.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-WQHX5rbx.js → ganttDiagram-FUAMR5RP-yo_Evhab.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-DRyzBdiV.js → gitGraphDiagram-X574FWY7-Cq2oUlap.js} +1 -1
- package/dist/assets/{index-CG1OvRgO.js → index-CG4AFPb4.js} +1 -1
- package/dist/assets/{index-D_8SD_0K.js → index-CMcr5k3Z.js} +205 -200
- package/dist/assets/index-CZjRcUdn.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-D7SOmp4n.js → infoDiagram-VRGFBTTK-Y3ZrY7qL.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-CdxOnDVX.js → ishikawaDiagram-OU5B5YK6-BpOtSfyi.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CH7477Oa.js → journeyDiagram-ZHPQQLJL-UQM727le.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BdpVvkHd.js → kanban-definition-PNTS6WVX-C3s5oHpY.js} +1 -1
- package/dist/assets/{linear-DTGq6qn5.js → linear-hX6nFOQr.js} +1 -1
- package/dist/assets/{mermaid.core-Sn2sv0Ti.js → mermaid.core-DZObGLN7.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-koCmkYuq.js → mindmap-definition-NLK3R4M7-Br-i2E3P.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-C3sqhm8e.js → pegDiagram-GJSIUBJH-D5SERB-N.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-BELBK122.js → pieDiagram-5QR66LMP-BPNf4J3D.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Cksi5LSm.js → quadrantDiagram-O4NWA36T-Bm_Wh7Jr.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-f1av_EZW.js → railroadDiagram-XR7U4H2S-D6bo0abD.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CW4O5nER.js → requirementDiagram-PLB6GJNP--1n5rkV7.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-Dz8ADHNY.js → sankeyDiagram-IPEJSGJF-Ct4tP2kK.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-Cc3HE0iZ.js → sequenceDiagram-PO4LG4MO-CCG7O5fR.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-gdaZNHnK.js → sizeCapture-INFHLROL-CJJJra9c.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK--u8LCnab.js → stateDiagram-v2-GCMORJYK-BMdRvVEX.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-4MAw4kdO.js → swimlanes-2SLR337P-B1wnogip.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-DWyjdGGR.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CL89YkCT.js → timeline-definition-EJHVYXUP-l-3yhXUP.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-C92l7YQe.js → usecaseDiagram-POWQR4AR-DtwtCOoJ.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-17siYhSj.js → vennDiagram-UO4OBE2U-BZAEce1B.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-CNwa9VmN.js → wardleyDiagram-VNRHLVJA-Na_BCMMO.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-Dnu6Xzih.js → xychartDiagram-PMCCYNJV-BW9dlUfE.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +68 -14
- package/dist-server/index.js +5633 -4562
- package/dist-server/openmausbot.js +140 -81
- package/dist-server/pair-cli.js +140 -81
- package/dist-server/server/app-installers.js +107 -7
- package/dist-server/server/avatar-subscription.js +51 -0
- package/dist-server/server/cli-help.js +74 -0
- package/dist-server/server/cli.js +37 -78
- package/dist-server/server/colima-docker.js +136 -0
- package/dist-server/server/container-computer.js +31 -63
- package/dist-server/server/container-mcp.js +5 -0
- package/dist-server/server/device-routing.js +60 -0
- package/dist-server/server/drivers/claude.js +13 -11
- package/dist-server/server/drivers/local-inject.js +3 -4
- package/dist-server/server/engine-failover.js +22 -12
- package/dist-server/server/engine-tiers.js +5 -14
- package/dist-server/server/index.js +124 -120
- package/dist-server/server/personal-goal-runtime.js +86 -0
- package/dist-server/server/personal-goal-turn.js +125 -0
- package/dist-server/server/personal-goals.js +195 -0
- package/dist-server/server/port-in-use.js +21 -0
- package/dist-server/server/routes/local-models.js +3 -2
- package/dist-server/server/session-fallback.js +76 -0
- package/dist-server/server/skill-fetch.js +14 -0
- package/dist-server/server/vault/routes.js +43 -0
- package/dist-server/server/vault/runtime.js +48 -0
- package/dist-server/server/vault/store.js +177 -0
- package/dist-server/shared/desktop-private-port.js +1 -0
- package/dist-server/shared/engine-choices.js +15 -0
- package/dist-server/shared/engine-tiers.js +74 -2
- package/dist-server/shared/local-models.js +1 -3
- package/dist-server/shared/personal-goal.js +43 -0
- package/dist-server/shared/subscription-bots.js +3 -0
- package/dist-server/shared/vault.js +59 -0
- package/dist-server/vps-container-mcp.js +4 -2
- package/package.json +1 -1
- package/dist/assets/channel-GBaPOaz-.js +0 -1
- package/dist/assets/index-C8YP7Zxj.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CamSa0NE.js +0 -8
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { hasUnboundGoalGuard, goalGuardForEvent, addGoalGuard, removeGoalGuard, runPersonalGoalTurn, updatePersonalGoalReceipt } from "./personal-goal-turn.js";
|
|
2
|
+
import { PersonalGoalRuntime, receiveGoalCommand } from "./personal-goal-runtime.js";
|
|
3
|
+
import { personalGoalInstructions } from "./personal-goals.js";
|
|
4
|
+
import { parseGoalCommand } from "../shared/personal-goal.js";
|
|
1
5
|
// OpenMausBot server — the harness host. Clients hold no transports
|
|
2
6
|
// (upstream rule): the React app dispatches typed commands over HTTP and
|
|
3
7
|
// folds one SSE event stream; every provider process runs here.
|
|
@@ -64,7 +68,7 @@ import { turnTimingFinish, turnTimingMark, turnTimingStart } from "./turn-timing
|
|
|
64
68
|
import { ATOMIC_TEAM_PACKAGE_ID, BUILTIN_CHIBI_REFUSALS, builtinChibiPatchRefusal } from "./builtin-chibi.js";
|
|
65
69
|
import { canAccessTeam, canContactTeamOf, canManagePeer, canReachPeer, canReachTeam, coordinatorSupervises, peerAllowed, peerName, peerRosterSystemPrompt, peerStatus, peerStatusWords, reachablePeers, reachesEveryTeam, roomBoundaryHolds, teamStatusPrompt, PEER_ROSTER_MAX, resolveTeammate, roomPeerRosterSystemPrompt, rosterAbout, roomRosterLine, PEER_ACCESS_HELP } from "./peer-roster.js";
|
|
66
70
|
import { openMausStatusSystemPrompt } from "./openmaus-status-capsule.js";
|
|
67
|
-
import { containerComputerAction, managedImageNeedsUpgrade, containerComputerExists, containerComputerFrame, containerComputerMcp, containerComputerScreenshot, containerComputerStatus, recentContainerComputerStatus, containerRuntimeStatus, ensureContainerRuntime, localVmRecreatableOnDemand, localVmReplaceableWhenStopped, perBotLocalVmTarget, SHARED_LOCAL_VM_TARGET, setupCommands, } from "./container-computer.js";
|
|
71
|
+
import { containerComputerAction, managedImageNeedsUpgrade, containerComputerExists, containerComputerFrame, containerComputerMcp, containerComputerScreenshot, containerComputerStatus, recentContainerComputerStatus, containerRuntimeStatus, ensureContainerRuntime, containerRuntimeError, localVmRecreatableOnDemand, localVmReplaceableWhenStopped, perBotLocalVmTarget, SHARED_LOCAL_VM_TARGET, setupCommands, } from "./container-computer.js";
|
|
68
72
|
import { ensureDirs, instanceConfigs, loadConfig, providerReloadKeys, localVmMaxInstances, localVmAutoStartRuntime, localVmMode, parseConfigPatch, roomTurnTimeoutMinutes, threadEventLogMaxBytes, maxConcurrentBotThreads, threadEventLogRetentionDays, saveConfig, showToolCallsEnabled, claudeUserMcpEnabled, userMcpEnabledFor, addonEnabled, skillAuthoringEnabled, sharedComputersEnabled, teamRoomsEnabled, builtInBrowserEnabled, llmThreadTitlesEnabled, onDeviceHelpersEnabled, browserProfileReplacementConflict, browserProfilePartitionTarget, syncCredentialEnv, withInstanceCli, persistableInstanceConfigs, vpsSshAlias, browserEngineAttachCdpUrl, DATA_DIR, EVENTS_DIR, NATIVE_DIR, customMcpServers, roomHandoffLimits, onConfigSaved, accountBotGrantPolicy, } from "./config.js";
|
|
69
73
|
import { sweepThreadEventLogs } from "./thread-retention.js";
|
|
70
74
|
import { GodModeSwitch } from "./god-mode.js";
|
|
@@ -143,7 +147,7 @@ import { MEMORY_INDEX, MemoryStoreError, memoryCapacity, memoryOverview, openFol
|
|
|
143
147
|
import { beginMemoryTurn, endMemoryTurn, flushAllMemoryJournals, flushMemoryJournal, journalMemoryDelete, journalMemoryWrite, readMemoryJournal, revertMemoryChange, } from "./memory-journal.js";
|
|
144
148
|
import { readSectionContext, readSections, sectionContextKey, sectionContextLabel, sectionContextSystemPrompt, writeSectionContext, SECTION_CONTEXT_MAX_BYTES, } from "./section-context.js";
|
|
145
149
|
import { applyStagedSkillWrite, applySkillWriteWithReceipt, getStagedSkillWrite, installSkill, parseSkillMd, scanSkillText, listSkills, listStagedSkillWrites, isSkillName, readSkillFile, rejectStagedSkillWrite, removeSkill, setSkillEnabled, skillsSystemPrompt, stageSkillWrite, } from "./skills.js";
|
|
146
|
-
import {
|
|
150
|
+
import { resolveSkillImport, skillImportSchema } from "./skill-fetch.js";
|
|
147
151
|
import { pickBotName } from "./names.js";
|
|
148
152
|
import { botSkillTemplateSchema, newBotDefaultsSchema, resolveBotCreationDefaults } from "./new-bot-defaults.js";
|
|
149
153
|
import { expandLearnTurnText, learnSource } from "./skill-learn.js";
|
|
@@ -235,6 +239,7 @@ import { activityCsv, activityEntries, appendAdminAction, botAuditSnapshot, botC
|
|
|
235
239
|
import { frameForMember, memberBody, memberBot, noteSeen, notFoundFor, audienceEquals, memberGroup, narrowestAudience, parseVisibility, pathSubject, roomFeeds, routineVisible, sameAudience, SEES_EVERYTHING, SEES_ALL_BUT_PRIVATE, VisibleSet, } from "./bot-visibility.js";
|
|
236
240
|
import { describeBrand, loadBrand } from "./brand.js";
|
|
237
241
|
import { deliverSseFrame } from "./sse-fanout.js";
|
|
242
|
+
import { DeviceRouteRegistry, removeStream } from "./device-routing.js";
|
|
238
243
|
import { PHONE_SECRET_PROTOCOL_VERSION, PhoneSecretBridge, PhoneSecretError, PhoneSecretSubmissionRegistry, assertPhoneSecretRequestMatches, phoneSecretOperationId, } from "./phone-secret.js";
|
|
239
244
|
// Keep these two last: a route module may import any server module, and
|
|
240
245
|
// loading the table after everything above leaves module start-up order as is.
|
|
@@ -248,7 +253,7 @@ import { createModelLevelsRoutes } from "./routes/model-levels.js";
|
|
|
248
253
|
import { createMcpMarketplaceRoutes } from "./routes/mcp-marketplace.js";
|
|
249
254
|
import { createLocalModelRoutes, OLLAMA_LOCAL_URL, ollamaInstalledHere } from "./routes/local-models.js";
|
|
250
255
|
import { createSetupRoutes } from "./routes/setup.js";
|
|
251
|
-
import { installOllamaApp, openPodmanInstaller, runTool, startPodmanMachine } from "./app-installers.js";
|
|
256
|
+
import { installOllamaApp, openPodmanInstaller, startOllamaApp, runTool, startPodmanMachine } from "./app-installers.js";
|
|
252
257
|
import { CliLoginRunner } from "./cli-login.js";
|
|
253
258
|
import { JobFailure, SetupJobs } from "./setup-jobs.js";
|
|
254
259
|
import { localHost, probeLocalInjects } from "./drivers/local-inject.js";
|
|
@@ -258,6 +263,7 @@ import { createProviderAccountRoutes } from "./routes/provider-accounts.js";
|
|
|
258
263
|
import { accountStops, injectedModel, restScope } from "./injected-model-rest.js";
|
|
259
264
|
import { accountProvider } from "../shared/provider-accounts.js";
|
|
260
265
|
import { createAccountRoutes } from "./routes/accounts.js";
|
|
266
|
+
import { installNativeVault } from "./vault/runtime.js";
|
|
261
267
|
import { OAuthAccounts, takeOAuthClients } from "./oauth-accounts.js";
|
|
262
268
|
import { DesktopAccountVault, FileAccountVault, UnavailableAccountVault, takeAccountsFromEnvironment } from "./oauth-account-vault.js";
|
|
263
269
|
import { accountConnectorNames } from "./claude-account-connectors.js";
|
|
@@ -299,6 +305,7 @@ import { createBoardRoutes } from "./routes/board.js";
|
|
|
299
305
|
import { readBoardFromNexus } from "./board/nexus-board.js";
|
|
300
306
|
import { LocalModelInventory, localModePlacement, machineMemory } from "./local-models-mode.js";
|
|
301
307
|
import { FailoverGate, MAX_ENGINES_PER_TURN, continuationNote, restingLine } from "./engine-failover.js";
|
|
308
|
+
import { enterSessionFallback, onSessionFallback, ownOrFallback, sessionFallbackSpentLine, withoutSubscription } from "./session-fallback.js";
|
|
302
309
|
import { TURN_ERROR_CATEGORIES } from "../shared/runtime-events.js";
|
|
303
310
|
import { EngineProbes, PROBE_PROMPT, isProbeThread, probeThreadId } from "./engine-probe.js";
|
|
304
311
|
// [jev-verifier] opt-in Jev answer checks after a model switch (server/jev).
|
|
@@ -1149,9 +1156,14 @@ if (!hostedModels) {
|
|
|
1149
1156
|
// its add-on's capability, so it loads only while the add-on applies.
|
|
1150
1157
|
const bundledSkills = [...loadBundledSkills(), ...loadAddonSkillLibraries()];
|
|
1151
1158
|
const availableSkills = () => mergeSkills(bundledSkills, loadUserSkills(join(DATA_DIR, "skills")));
|
|
1159
|
+
// Electron's utility-process parent port is private to the desktop main
|
|
1160
|
+
// process. It carries desktop-only messages (approval and browser control,
|
|
1161
|
+
// phone secrets, the account vault) without putting them in the renderer.
|
|
1162
|
+
// Plain Node/dev launches have no parentPort.
|
|
1152
1163
|
// SAFETY: Electron's utility-process runtime is the only environment that
|
|
1153
1164
|
// supplies parentPort; plain Node intentionally leaves it absent.
|
|
1154
1165
|
const utilityParentPort = process.parentPort;
|
|
1166
|
+
installNativeVault(DATA_DIR, utilityParentPort, () => desktopMutationToken, req => authorizedInternalCapability(req.headers.authorization) !== null);
|
|
1155
1167
|
function postDesktopPrivateMessage(message) {
|
|
1156
1168
|
if (!utilityParentPort)
|
|
1157
1169
|
return false;
|
|
@@ -1820,6 +1832,7 @@ function requestedTaskBot(botId, rawThreadId) {
|
|
|
1820
1832
|
return task;
|
|
1821
1833
|
}
|
|
1822
1834
|
async function interruptDirectThread(botId, threadId) {
|
|
1835
|
+
personalGoals.steer(threadId);
|
|
1823
1836
|
const requestOwner = directRequestOwners.get(threadId);
|
|
1824
1837
|
if (requestOwner) {
|
|
1825
1838
|
requestOwner.stopped = true;
|
|
@@ -3039,12 +3052,19 @@ function turnEngineChoice(botId, threadId, needs, runOn) {
|
|
|
3039
3052
|
if (hostedModels || !profile || !task)
|
|
3040
3053
|
return undefined;
|
|
3041
3054
|
if (isSubscriptionBot(profile))
|
|
3042
|
-
return subscriptionTurnEngineChoice(profile, threadId);
|
|
3055
|
+
return ownOrFallback(threadId, () => subscriptionTurnEngineChoice(profile, threadId), () => sessionFallbackChoice(profile, threadId, needs, runOn), (stop) => stop instanceof SubscriptionStop, (stop) => new SubscriptionStop(sessionFallbackSpentLine(stop.message)));
|
|
3043
3056
|
if (isPersonalBot(profile))
|
|
3044
3057
|
return personalTurnEngineChoice(profile, threadId, needs, runOn);
|
|
3045
3058
|
const projected = store.projectBotForTask(botId, threadId);
|
|
3046
3059
|
return projected ? ladderTurnEngineChoice(profile, projected, threadId, needs, runOn) : undefined;
|
|
3047
3060
|
}
|
|
3061
|
+
/** A company subscription bot's conversation on another provider's subscription, same class (server/session-fallback.ts). */
|
|
3062
|
+
function sessionFallbackChoice(profile, threadId, needs, runOn) {
|
|
3063
|
+
const projected = store.projectBotForTask(profile.id, threadId);
|
|
3064
|
+
const choice = isPersonalBot(profile) || !projected ? undefined
|
|
3065
|
+
: ladderTurnEngineChoice(withoutSubscription(profile), withoutSubscription(projected), threadId, needs, runOn, true);
|
|
3066
|
+
return choice && choice.selection.instanceId !== projected.modelSelection.instanceId ? { selection: choice.selection, persist: false } : undefined;
|
|
3067
|
+
}
|
|
3048
3068
|
/** A room member's engine at turn start, by turnEngineChoice's rules for
|
|
3049
3069
|
* the bot's own default (a room turn has no task of its own, so the bot's
|
|
3050
3070
|
* engine mode decides): resting and signed-out engines passed over, a
|
|
@@ -3060,7 +3080,7 @@ function roomTurnEngineChoice(bot, threadId) {
|
|
|
3060
3080
|
: ladderTurnEngineChoice(bot, projected, threadId);
|
|
3061
3081
|
return choice && { selection: choice.selection, persist: false };
|
|
3062
3082
|
}
|
|
3063
|
-
function ladderTurnEngineChoice(profile, projected, threadId, needs, runOn) {
|
|
3083
|
+
function ladderTurnEngineChoice(profile, projected, threadId, needs, runOn, subscriptionsOnly = false) {
|
|
3064
3084
|
const candidates = engineSnapshots.current();
|
|
3065
3085
|
if (!candidates)
|
|
3066
3086
|
return undefined;
|
|
@@ -3069,7 +3089,7 @@ function ladderTurnEngineChoice(profile, projected, threadId, needs, runOn) {
|
|
|
3069
3089
|
const { ladder } = plan;
|
|
3070
3090
|
const computer = turnComputer(projected, threadId, runOn);
|
|
3071
3091
|
const context = { candidates, needsDelegation: needsDelegation(tier, profile), computer, refuse: engineTierRefusal(projected), resting: engineResting,
|
|
3072
|
-
keyBandPaused: payPerUse.pausedUntil() !== undefined };
|
|
3092
|
+
keyBandPaused: subscriptionsOnly || payPerUse.pausedUntil() !== undefined };
|
|
3073
3093
|
const local = (resolution) => localModeTurn(resolution, { tier, context, needs, prefer: [projected.modelSelection.instanceId] });
|
|
3074
3094
|
const wider = (tried, modelClass) => resolveFailover({
|
|
3075
3095
|
ladder, classLadder: (cls) => classLadder(classedModels(), cls), failed: projected.modelSelection, context, tried,
|
|
@@ -3350,7 +3370,7 @@ function localModeTurn(resolution, turn) {
|
|
|
3350
3370
|
return { ...resolution, local: true, choice: { ...choice, selection: placement.selection, candidate: placement.candidate } };
|
|
3351
3371
|
}
|
|
3352
3372
|
const payPerUsePausedLine = (until) => `Pay-per-use is paused until ${restTime(until)}, when a subscription is back, so this turn stopped.`;
|
|
3353
|
-
/** A model this machine serves:
|
|
3373
|
+
/** A model this machine serves: a rung when a ladder names it, else local rules only. */
|
|
3354
3374
|
const isLocalModel = (candidate, model) => localModel(candidate, model) || modelLevels.classOf(candidate.driverKind, model)?.local === true;
|
|
3355
3375
|
// ── silent automatic failover (docs/chibitek/engine-tiers.md, step 2) ────
|
|
3356
3376
|
// Resting engines are shared by every bot and survive a restart.
|
|
@@ -3589,10 +3609,7 @@ const eventBase = (event) => ({
|
|
|
3589
3609
|
...(event.providerInstanceId ? { providerInstanceId: event.providerInstanceId } : {}), ...(event.turnId ? { turnId: event.turnId } : {}),
|
|
3590
3610
|
});
|
|
3591
3611
|
const restTime = (at) => new Date(at).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
3592
|
-
/**
|
|
3593
|
-
* out. Rest the engine, then run the same turn on the next engine at the
|
|
3594
|
-
* same level, silently; or, when nothing is left, stop it with one line
|
|
3595
|
-
* naming the resting engines. Returns what subscribers see. */
|
|
3612
|
+
/** Rest a refused engine and continue on an eligible engine, or show an actionable line. */
|
|
3596
3613
|
function engineFailover(turn, completion) {
|
|
3597
3614
|
const failure = turn.failure;
|
|
3598
3615
|
const record = failoverTurns.get(turn.threadId);
|
|
@@ -3615,17 +3632,14 @@ function engineFailover(turn, completion) {
|
|
|
3615
3632
|
const restFailed = () => engineRests.rest(turn.instanceId, failedCandidate ? engineAccount(failedCandidate) : undefined, failure.kind, { resetAt: failure.resetAt, resetText: failure.resetText, windowMs: failure.windowMs, message: redactSecretsInText(failure.message),
|
|
3616
3633
|
...(failure.permanent ? { permanent: true } : {}), ...(scope ? { scope } : {}), ...(ran?.model ? { model: ran.model } : {}),
|
|
3617
3634
|
...(failure.kind === "auth_failed" && failedCandidate ? { reportedSignedIn: failedCandidate.snapshot.state === "available" && failedCandidate.snapshot.authenticated === true } : {}) });
|
|
3618
|
-
//
|
|
3619
|
-
// next engine: the engine rests (the circuit breaker) only once the turn
|
|
3620
|
-
// really moves, and with nowhere to go the turn ends with the engine's own
|
|
3621
|
-
// report, exactly as before, rather than a "resting" line about it.
|
|
3635
|
+
// Unknown failures rest only when the turn moves; otherwise preserve their report.
|
|
3622
3636
|
const rest = failure.kind === "provider_error" ? undefined : restFailed();
|
|
3623
3637
|
const label = (instanceId) => candidates.find((candidate) => candidate.instanceId === instanceId)?.displayName ?? instanceId;
|
|
3624
3638
|
// The one plain line, never the provider's own words: which account needs
|
|
3625
3639
|
// the person and where to fix it, or that every engine is resting.
|
|
3626
3640
|
const stop = (line, category = "failover_exhausted") => [
|
|
3627
3641
|
{ ...eventBase(completion), type: "runtime.error", message: line, terminal: true, category, ...(failure.setup ? { setup: true } : {}) },
|
|
3628
|
-
completion,
|
|
3642
|
+
failure.kind === "auth_failed" ? { ...completion, stopReason: "auth_required" } : completion,
|
|
3629
3643
|
];
|
|
3630
3644
|
const restingNamed = (instanceIds) => [...new Set(instanceIds)].flatMap((instanceId) => {
|
|
3631
3645
|
const candidate = candidates.find((entry) => entry.instanceId === instanceId);
|
|
@@ -3648,13 +3662,11 @@ function engineFailover(turn, completion) {
|
|
|
3648
3662
|
return stop(pick.line, pick.category);
|
|
3649
3663
|
const { choice } = pick;
|
|
3650
3664
|
const tried = new Set(record.tried);
|
|
3651
|
-
//
|
|
3652
|
-
// chip, no notification, no incident, and nothing settled that the same
|
|
3653
|
-
// turn still owns.
|
|
3665
|
+
// Retire the failed attempt without settling the request that the next engine owns.
|
|
3654
3666
|
const moved = ran ?? record.selection;
|
|
3655
3667
|
const failedOwner = turnResourceOwners.get(turn.threadId);
|
|
3656
3668
|
const tokens = completion.usage ?? turnUsage.get(turn.threadId);
|
|
3657
|
-
const partialText = turn.partial.trim();
|
|
3669
|
+
const partialText = groupGoalCoordinatorTurns.get(turn.threadId)?.size ? parseGroupGoalDecision(turn.partial).visibleText : turn.partial.trim();
|
|
3658
3670
|
const requestOwner = directRequestOwners.get(turn.threadId);
|
|
3659
3671
|
if (partialText) {
|
|
3660
3672
|
// The answer it was in the middle of stays: the person saw it stream.
|
|
@@ -3666,6 +3678,12 @@ function engineFailover(turn, completion) {
|
|
|
3666
3678
|
const tools = earlier.filter((message) => message.kind === "activity" && message.tool?.name).map((message) => message.tool.name);
|
|
3667
3679
|
const note = continuationNote(said.map((message) => message.text ?? ""), tools);
|
|
3668
3680
|
const attempt = record.attempt + 1;
|
|
3681
|
+
for (const guard of groupGoalCoordinatorTurns.get(turn.threadId) ?? []) {
|
|
3682
|
+
if (!guard.discard && (guard.turnId === completion.turnId || guard.turnId === turn.turnId)) {
|
|
3683
|
+
guard.turnId = undefined;
|
|
3684
|
+
guard.assistantItems = [];
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3669
3687
|
const rerun = startTurn(turn.botId, note ? `${record.text}\n\n${note}` : record.text, {
|
|
3670
3688
|
...record.opts,
|
|
3671
3689
|
threadId: turn.threadId,
|
|
@@ -3703,6 +3721,11 @@ function engineFailover(turn, completion) {
|
|
|
3703
3721
|
});
|
|
3704
3722
|
}
|
|
3705
3723
|
retireFailedAttempt(turn.botId, turn.threadId, turn.generation, failedOwner, completion);
|
|
3724
|
+
if (failure.kind === "auth_failed")
|
|
3725
|
+
reportIncident({
|
|
3726
|
+
kind: "engine-fallback", bot: profile, threadId: turn.threadId, notifyChief: true,
|
|
3727
|
+
detail: `${engineLabel(candidates, moved)} needs signing in again in Settings > Engines; the turn continued on ${engineLabel(candidates, choice.selection)}`,
|
|
3728
|
+
});
|
|
3706
3729
|
console.error(`[omb-failover] thread=${turn.threadId} ${failure.kind}: ${moved.instanceId} -> ${choice.selection.instanceId} (attempt ${attempt})`);
|
|
3707
3730
|
// No turnId: the failed provider turn is retired, and its id would hide
|
|
3708
3731
|
// this record from every window.
|
|
@@ -3803,14 +3826,19 @@ function failoverPick(turn, profile, projected, candidates, failedCandidate, res
|
|
|
3803
3826
|
const computer = projected ? turnComputer(projected, turn.threadId, attempt.runOn) : undefined;
|
|
3804
3827
|
// A subscription bot fails over only to another account of its own
|
|
3805
3828
|
// provider on its side of the personal line; with none left it stops with
|
|
3806
|
-
// its one line
|
|
3829
|
+
// its one line, or a company bot's own conversation takes its session fallback (server/session-fallback.ts).
|
|
3807
3830
|
const subscriptionMove = isSubscriptionBot(profile)
|
|
3808
3831
|
? tried.size >= MAX_ENGINES_PER_TURN || !projected
|
|
3809
3832
|
? { line: subscriptionLine(profile, candidates) }
|
|
3810
3833
|
: subscriptionPlacement({ profile, projected, candidates, tried: new Set([...tried, turn.instanceId]), auto: attempt.persist, modelClass: neededClass })
|
|
3811
3834
|
: undefined;
|
|
3812
|
-
|
|
3835
|
+
const fallback = subscriptionMove && !subscriptionMove.choice && projected && !isPersonalBot(profile) && tried.size < MAX_ENGINES_PER_TURN && store.taskByThread(profile.id, turn.threadId);
|
|
3836
|
+
if (fallback && !enterSessionFallback(turn.threadId, false))
|
|
3837
|
+
return { line: sessionFallbackSpentLine(subscriptionMove.line) };
|
|
3838
|
+
if (subscriptionMove && !subscriptionMove.choice && !fallback)
|
|
3813
3839
|
return { line: subscriptionMove.line };
|
|
3840
|
+
if (fallback)
|
|
3841
|
+
[profile, projected] = [withoutSubscription(profile), withoutSubscription(projected)];
|
|
3814
3842
|
const personalTurn = isPersonalBot(profile) && !subscriptionMove;
|
|
3815
3843
|
// The Private Bot prefers personal engines and opted-in local compute,
|
|
3816
3844
|
// then company compute. startTurn rechecks company cost on every attempt.
|
|
@@ -3830,7 +3858,7 @@ function failoverPick(turn, profile, projected, candidates, failedCandidate, res
|
|
|
3830
3858
|
classLadder: (modelClass) => classLadder(classedModels(), modelClass),
|
|
3831
3859
|
failed: attempt.selection,
|
|
3832
3860
|
context: { candidates, needsDelegation: needsDelegation(tier, profile), computer, refuse: engineTierRefusal(projected), resting: engineResting,
|
|
3833
|
-
keyBandPaused: keyBandPausedFor(turn.threadId) },
|
|
3861
|
+
keyBandPaused: Boolean(fallback) || keyBandPausedFor(turn.threadId) },
|
|
3834
3862
|
tried,
|
|
3835
3863
|
modelClass: neededClass,
|
|
3836
3864
|
mayDrop: true,
|
|
@@ -3846,6 +3874,8 @@ function failoverPick(turn, profile, projected, candidates, failedCandidate, res
|
|
|
3846
3874
|
context: { candidates, needsDelegation: needsDelegation(tier, profile), computer, refuse: engineTierRefusal(projected), resting: engineResting } })
|
|
3847
3875
|
: { ...resolution, local: false };
|
|
3848
3876
|
const choice = placed.choice && placed.local ? { ...placed.choice, modelClass: "Añejo" } : placed.choice;
|
|
3877
|
+
if (!choice && fallback)
|
|
3878
|
+
return { line: subscriptionMove.line };
|
|
3849
3879
|
if (!choice && turn.failure?.kind === "provider_error")
|
|
3850
3880
|
return { passthrough: true };
|
|
3851
3881
|
if (!choice) {
|
|
@@ -3855,11 +3885,11 @@ function failoverPick(turn, profile, projected, candidates, failedCandidate, res
|
|
|
3855
3885
|
const resting = resolution.skipped.filter((skip) => skip.reason === "resting" || skip.reason === "tried").map((skip) => skip.instanceId);
|
|
3856
3886
|
const named = restingNamed([turn.instanceId, ...attempt.tried, ...resting]);
|
|
3857
3887
|
return {
|
|
3858
|
-
line: restingLine(named, restTime, cannotDriveNamed(candidates, resolution.skipped.map((skip) => skip.instanceId), computer)),
|
|
3888
|
+
line: restingLine(named, restTime, cannotDriveNamed(candidates, resolution.skipped.map((skip) => skip.instanceId), computer), { capped: tried.size >= MAX_ENGINES_PER_TURN }),
|
|
3859
3889
|
category: named.some((engine) => engine.needsSignIn || engine.keyExpired) ? "account" : "failover_exhausted",
|
|
3860
3890
|
};
|
|
3861
3891
|
}
|
|
3862
|
-
if (subscriptionMove) { /* subscriptions only: no pay-per-use episode */ }
|
|
3892
|
+
if (subscriptionMove || fallback) { /* subscriptions only: no pay-per-use episode */ }
|
|
3863
3893
|
else if (personalMove)
|
|
3864
3894
|
notePersonalPayPerUse(personalMove);
|
|
3865
3895
|
else
|
|
@@ -5013,67 +5043,21 @@ const publicBotQueuedMessages = () => queuedSteerSnapshot((botId, threadId) => B
|
|
|
5013
5043
|
const groupTurnOperations = new Map();
|
|
5014
5044
|
const groupGoalCoordinatorTurns = new Map();
|
|
5015
5045
|
const GROUP_GOAL_COORDINATOR_GUARD_MS = 5 * 60_000;
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
.some((turn) => turn.discard && !turn.turnId);
|
|
5032
|
-
}
|
|
5033
|
-
/** Match private coordinator output to one provider turn, never merely to a
|
|
5034
|
-
* reusable room thread. Most adapters emit turn.started before sendTurn
|
|
5035
|
-
* resolves, so the first stable event may bind an otherwise pending guard. */
|
|
5036
|
-
function groupGoalCoordinatorTurnForEvent(event) {
|
|
5037
|
-
const turns = groupGoalCoordinatorTurns.get(event.threadId);
|
|
5038
|
-
if (!turns?.size)
|
|
5039
|
-
return undefined;
|
|
5040
|
-
const candidates = [...turns];
|
|
5041
|
-
if (event.turnId) {
|
|
5042
|
-
const exact = candidates.find((turn) => turn.turnId === event.turnId);
|
|
5043
|
-
if (exact)
|
|
5044
|
-
return exact;
|
|
5045
|
-
// Until an interrupted handshake returns its own id, no new id can be
|
|
5046
|
-
// attributed safely. The stall fallback keeps this thread unavailable in
|
|
5047
|
-
// that narrow window; private text is suppressed below until sendTurn's
|
|
5048
|
-
// result binds the old guard or its bounded expiry releases ownership.
|
|
5049
|
-
const unboundDiscarded = candidates.filter((turn) => turn.discard && !turn.turnId);
|
|
5050
|
-
if (unboundDiscarded.length > 0) {
|
|
5051
|
-
// The ownership fallback below keeps a lone abandoned handshake's
|
|
5052
|
-
// thread closed, so its first eventual id can safely bind here. More
|
|
5053
|
-
// than one unbound candidate is genuinely ambiguous and stays gated.
|
|
5054
|
-
if (candidates.length === 1) {
|
|
5055
|
-
unboundDiscarded[0].turnId = event.turnId;
|
|
5056
|
-
// This event is the first stable identity for an already-abandoned
|
|
5057
|
-
// provider turn. Tombstone it immediately so this event and every
|
|
5058
|
-
// later completion/request cannot settle a replacement on the same
|
|
5059
|
-
// room thread.
|
|
5060
|
-
retireProviderTurn(event.turnId);
|
|
5061
|
-
return unboundDiscarded[0];
|
|
5062
|
-
}
|
|
5063
|
-
return undefined;
|
|
5064
|
-
}
|
|
5065
|
-
const pending = candidates.findLast((turn) => !turn.turnId && !turn.discard);
|
|
5066
|
-
if (pending && !pending.turnId) {
|
|
5067
|
-
pending.turnId = event.turnId;
|
|
5068
|
-
return pending;
|
|
5069
|
-
}
|
|
5070
|
-
return undefined;
|
|
5071
|
-
}
|
|
5072
|
-
// Turn-scoped events normally carry an id. If an adapter omits it, fail
|
|
5073
|
-
// closed for private text; with multiple overlapping guards there is no
|
|
5074
|
-
// safe way to attribute a completion, so leave cleanup to the bounded timer.
|
|
5075
|
-
return candidates.length === 1 ? candidates[0] : undefined;
|
|
5076
|
-
}
|
|
5046
|
+
const personalGoals = new PersonalGoalRuntime(join(DATA_DIR, "personal-goals.json"), {
|
|
5047
|
+
busy: (botId, threadId) => threadBusy(botId, threadId) || Boolean(activeGroupTurnForBot(botId)),
|
|
5048
|
+
interrupt: async (botId, threadId, goalId) => {
|
|
5049
|
+
const owns = [...(groupGoalCoordinatorTurns.get(threadId) ?? [])].some(guard => guard.personalGoalId === goalId && !guard.discard && (!guard.turnId || guard.turnId === liveTurnByThread.get(threadId)));
|
|
5050
|
+
if (owns)
|
|
5051
|
+
await interruptDirectThread(botId, threadId);
|
|
5052
|
+
},
|
|
5053
|
+
changed: (goal) => updatePersonalGoalReceipt(goal, store),
|
|
5054
|
+
run: (goal) => runPersonalGoalTurn(goal, { store, addGuard: addGroupGoalCoordinatorTurn, removeGuard: removeGroupGoalCoordinatorTurn,
|
|
5055
|
+
subscribe: (listener) => bus.subscribe(listener), ignore: shouldIgnoreProviderEvent, busy: threadBusy, start: startTurn }),
|
|
5056
|
+
});
|
|
5057
|
+
const addGroupGoalCoordinatorTurn = (threadId, turn) => addGoalGuard(groupGoalCoordinatorTurns, threadId, turn);
|
|
5058
|
+
const removeGroupGoalCoordinatorTurn = (threadId, turn) => removeGoalGuard(groupGoalCoordinatorTurns, threadId, turn);
|
|
5059
|
+
const hasUnboundDiscardedGroupGoalTurn = (threadId) => hasUnboundGoalGuard(groupGoalCoordinatorTurns, threadId);
|
|
5060
|
+
const groupGoalCoordinatorTurnForEvent = (event) => goalGuardForEvent(groupGoalCoordinatorTurns, retireProviderTurn, event);
|
|
5077
5061
|
function groupIsWorking(group) {
|
|
5078
5062
|
return Boolean(group.busyBotId) || Boolean(groupTurnOperations.get(group.id)?.size);
|
|
5079
5063
|
}
|
|
@@ -6024,6 +6008,9 @@ function messageWindow(threadId, messageId, limit) {
|
|
|
6024
6008
|
return { messages: all.slice(start, stop).map(slimMessage), hasMore: start > 0 };
|
|
6025
6009
|
}
|
|
6026
6010
|
const sseClients = new Set();
|
|
6011
|
+
// Approval device routing: docs/specs/remote-approval/spec.md.
|
|
6012
|
+
const deviceRoutes = new DeviceRouteRegistry();
|
|
6013
|
+
const removeSseClient = (client) => removeStream(sseClients, deviceRoutes, client);
|
|
6027
6014
|
/** The frame at which a bot's audience last changed (see the SSE route). */
|
|
6028
6015
|
let audienceChangedAt = 0;
|
|
6029
6016
|
/** A bot's audience changed: end every member stream. Each reconnects at
|
|
@@ -6035,27 +6022,15 @@ function audienceChanged() {
|
|
|
6035
6022
|
for (const client of sseClients) {
|
|
6036
6023
|
if (client.viewer.kind !== "member")
|
|
6037
6024
|
continue;
|
|
6038
|
-
|
|
6039
|
-
try {
|
|
6040
|
-
client.res.end();
|
|
6041
|
-
}
|
|
6042
|
-
catch {
|
|
6043
|
-
/* already gone */
|
|
6044
|
-
}
|
|
6025
|
+
removeSseClient(client);
|
|
6045
6026
|
}
|
|
6046
6027
|
}
|
|
6047
6028
|
function closeSessionStreams(sessionId) {
|
|
6048
6029
|
browserLive.closeForOwner(sessionId);
|
|
6049
|
-
for (const client of sseClients) {
|
|
6030
|
+
for (const client of Array.from(sseClients)) {
|
|
6050
6031
|
if (client.sessionId !== sessionId)
|
|
6051
6032
|
continue;
|
|
6052
|
-
|
|
6053
|
-
try {
|
|
6054
|
-
client.res.end();
|
|
6055
|
-
}
|
|
6056
|
-
catch {
|
|
6057
|
-
/* already gone */
|
|
6058
|
-
}
|
|
6033
|
+
removeSseClient(client);
|
|
6059
6034
|
}
|
|
6060
6035
|
}
|
|
6061
6036
|
sessions.onSessionRevoked((sessionId) => {
|
|
@@ -6253,7 +6228,7 @@ function broadcast(payload, options = {}) {
|
|
|
6253
6228
|
// Screen frames are replaceable and durable events are not: see
|
|
6254
6229
|
// ./sse-fanout.ts for the backpressure/bound decision this makes.
|
|
6255
6230
|
if (deliverSseFrame(client, kind, out) === "disconnected") {
|
|
6256
|
-
|
|
6231
|
+
removeSseClient(client);
|
|
6257
6232
|
}
|
|
6258
6233
|
}
|
|
6259
6234
|
}
|
|
@@ -7019,15 +6994,15 @@ function localVmSetUp() {
|
|
|
7019
6994
|
}
|
|
7020
6995
|
/** The one gate for starting the container engine (App Settings →
|
|
7021
6996
|
* Computers): the person's switch, the organisation's policy, and a Local VM
|
|
7022
|
-
* actually set up here. Returns what it started (
|
|
7023
|
-
* Desktop
|
|
6997
|
+
* actually set up here. Returns what it started (Colima, the only engine
|
|
6998
|
+
* Atomic starts; never Docker Desktop) once it answers, or null. */
|
|
7024
6999
|
async function startLocalVmRuntime() {
|
|
7025
7000
|
if (!localVmAutoStartRuntime(cfg) || !managedPolicy.computerAllowed("localVm") || !localVmSetUp())
|
|
7026
7001
|
return null;
|
|
7027
7002
|
try {
|
|
7028
|
-
const { status, started } = await ensureContainerRuntime();
|
|
7029
|
-
if (started)
|
|
7030
|
-
console
|
|
7003
|
+
const { status, started, error } = await ensureContainerRuntime();
|
|
7004
|
+
if (started || error)
|
|
7005
|
+
console[error ? "error" : "log"](`[local-vm] ${started ? `started ${started}: ${status.daemonUp ? "ready" : "still not answering"}` : "not started"}${error ? `. ${error}` : ""}`);
|
|
7031
7006
|
return started && status.daemonUp ? started : null;
|
|
7032
7007
|
}
|
|
7033
7008
|
catch (error) {
|
|
@@ -7539,7 +7514,7 @@ async function unusableAutoPin(bot, threadId, plan, runOn) {
|
|
|
7539
7514
|
if (pinned === "vm" && !choice.available && !choice.starting && await startLocalVmRuntime()) {
|
|
7540
7515
|
choice = await computerChoice(bot, pinned);
|
|
7541
7516
|
}
|
|
7542
|
-
return choice.available || choice.starting ? null : choice.reason ?? `${surfaceLabel(pinned)} is unavailable`;
|
|
7517
|
+
return choice.available || choice.starting ? null : (pinned === "vm" ? containerRuntimeError() : null) ?? choice.reason ?? `${surfaceLabel(pinned)} is unavailable`;
|
|
7543
7518
|
}
|
|
7544
7519
|
function continueComputerSelection(threadId, generation, succeeded) {
|
|
7545
7520
|
const selection = computerSelectionTurns.get(threadId);
|
|
@@ -8271,7 +8246,7 @@ bus.subscribe((event) => {
|
|
|
8271
8246
|
if (!event.ok && event.stopReason !== "interrupted" && !lazyClaimAlreadyReported && !routines?.runForThread(event.threadId)) {
|
|
8272
8247
|
const broken = bot ?? (speaker ? store.bot(speaker.botId) : undefined);
|
|
8273
8248
|
if (broken)
|
|
8274
|
-
reportIncident({ kind: "failed", bot: broken, threadId: event.threadId, detail: failedRunDetail(event.stopReason, turnError) });
|
|
8249
|
+
reportIncident({ kind: "failed", bot: broken, threadId: event.threadId, detail: failedRunDetail(event.stopReason, turnError), notifyChief: event.stopReason === "auth_required" });
|
|
8275
8250
|
}
|
|
8276
8251
|
const lastReported = turnUsage.get(event.threadId);
|
|
8277
8252
|
turnUsage.delete(event.threadId);
|
|
@@ -8637,7 +8612,7 @@ function reportIncident(input) {
|
|
|
8637
8612
|
const task = store.taskByThread(bot.id, threadId);
|
|
8638
8613
|
// A thread another bot opened and is watching is that bot's to handle:
|
|
8639
8614
|
// the delegator is woken with the failure already (wakeDelegationSource).
|
|
8640
|
-
if (task?.openedBy?.delegationId || delegationWatch.has(threadId) || roomHandoffs.activeDirect(threadId))
|
|
8615
|
+
if (!input.notifyChief && (task?.openedBy?.delegationId || delegationWatch.has(threadId) || roomHandoffs.activeDirect(threadId)))
|
|
8641
8616
|
return;
|
|
8642
8617
|
const group = store.groupByThread(threadId);
|
|
8643
8618
|
const incident = {
|
|
@@ -9678,7 +9653,8 @@ async function startTurn(botId, text, opts) {
|
|
|
9678
9653
|
: threadBusy(botId, threadId) ? undefined : turnEngineChoice(botId, threadId, { tools: true, vision: turnHasImages(opts?.userMessage) }, opts?.runOn);
|
|
9679
9654
|
const tierSelection = engineChoice?.persist ? engineChoice.selection : undefined;
|
|
9680
9655
|
const projectedBot = store.projectBotForTask(botId, threadId);
|
|
9681
|
-
const
|
|
9656
|
+
const fallbackBot = projectedBot && !opts?.engine && onSessionFallback(threadId) ? withoutSubscription(projectedBot) : projectedBot;
|
|
9657
|
+
const bot = fallbackBot && engineChoice ? { ...fallbackBot, modelSelection: engineChoice.selection } : fallbackBot;
|
|
9682
9658
|
if (!bot)
|
|
9683
9659
|
throw Object.assign(new Error("no such task"), { status: 404 });
|
|
9684
9660
|
// Routines and legacy peer delivery already have their own completion
|
|
@@ -10760,6 +10736,7 @@ async function startTurn(botId, text, opts) {
|
|
|
10760
10736
|
? await nexusMemory.readBack({ bot: liveBot ?? bot, threadId, query: providerText })
|
|
10761
10737
|
: "";
|
|
10762
10738
|
const prompt = buildSystemPrompt(persona, liveBot?.soul ?? bot.soul ?? "", roomPromptSections(roomTurn, [
|
|
10739
|
+
{ id: "personal-goal", label: "Active goal", text: personalGoals.goals.get(threadId)?.status === "working" ? personalGoalInstructions(personalGoals.goals.get(threadId)) : "" },
|
|
10763
10740
|
{ id: "user-profile", label: "About the user", text: userProfileSystemPrompt(cfg.profile) },
|
|
10764
10741
|
// the ICM folder is on this machine; a cloud run cannot reach it
|
|
10765
10742
|
// first after the soul: the block names agent tools, so it only goes
|
|
@@ -14356,6 +14333,8 @@ async function readyLocalVmForTurn(botId, target, isCurrent = () => true, onNote
|
|
|
14356
14333
|
onNote?.(`Started ${started} for the Local VM`);
|
|
14357
14334
|
status = await containerComputerStatus(undefined, undefined, target);
|
|
14358
14335
|
}
|
|
14336
|
+
else if (containerRuntimeError() && isCurrent())
|
|
14337
|
+
onNote?.(containerRuntimeError());
|
|
14359
14338
|
}
|
|
14360
14339
|
noteLocalVmSeen(target, status);
|
|
14361
14340
|
if (!isCurrent())
|
|
@@ -16129,7 +16108,7 @@ ROUTES.push(createSetupRoutes({
|
|
|
16129
16108
|
}
|
|
16130
16109
|
},
|
|
16131
16110
|
startOllama: async (context) => {
|
|
16132
|
-
|
|
16111
|
+
startOllamaApp();
|
|
16133
16112
|
for (let attempt = 0; attempt < 30 && !context.signal.aborted; attempt++) {
|
|
16134
16113
|
if ((await setupOllamaState()) === "running")
|
|
16135
16114
|
return;
|
|
@@ -19062,6 +19041,7 @@ const handleRequest = async (req, res) => {
|
|
|
19062
19041
|
admin: auth.scopes.includes("admin"),
|
|
19063
19042
|
screens: url.searchParams.get("screens") !== "off",
|
|
19064
19043
|
backpressured: false,
|
|
19044
|
+
authKind: auth.kind,
|
|
19065
19045
|
viewer,
|
|
19066
19046
|
// What this person's hydration (GET /api/bots, narrowed the same way) showed them.
|
|
19067
19047
|
seen: viewer.kind === "all" && viewer.privateOwner ? { bots: new Set(), groups: new Set() } : {
|
|
@@ -19105,6 +19085,7 @@ const handleRequest = async (req, res) => {
|
|
|
19105
19085
|
// A client that offered no cursor gets false too, which is exactly
|
|
19106
19086
|
// what a cold start should do.
|
|
19107
19087
|
resumed,
|
|
19088
|
+
approvalTarget: deviceRoutes.route(),
|
|
19108
19089
|
})}\n\n`);
|
|
19109
19090
|
if (resumed) {
|
|
19110
19091
|
for (const buffered of replayBuffer) {
|
|
@@ -19120,6 +19101,7 @@ const handleRequest = async (req, res) => {
|
|
|
19120
19101
|
}
|
|
19121
19102
|
}
|
|
19122
19103
|
sseClients.add(client);
|
|
19104
|
+
deviceRoutes.streamOpened(client.authKind);
|
|
19123
19105
|
// Keep this long-lived response out of socket idle-timeout handling
|
|
19124
19106
|
// without weakening timeouts for every other API request.
|
|
19125
19107
|
req.socket.setTimeout(0);
|
|
@@ -19139,7 +19121,7 @@ const handleRequest = async (req, res) => {
|
|
|
19139
19121
|
}, SSE_HEARTBEAT_MS);
|
|
19140
19122
|
req.on("close", () => {
|
|
19141
19123
|
clearInterval(keepalive);
|
|
19142
|
-
|
|
19124
|
+
removeSseClient(client);
|
|
19143
19125
|
});
|
|
19144
19126
|
return;
|
|
19145
19127
|
}
|
|
@@ -20584,11 +20566,11 @@ const handleRequest = async (req, res) => {
|
|
|
20584
20566
|
});
|
|
20585
20567
|
}
|
|
20586
20568
|
if (method === "POST" && path === "/api/bot-defaults/skills/preview") {
|
|
20587
|
-
|
|
20569
|
+
// Drafts accept the same uploaded bytes as existing bots, without installing them.
|
|
20570
|
+
const checked = skillImportSchema.safeParse(await readBody(req));
|
|
20588
20571
|
if (!checked.success)
|
|
20589
20572
|
return json(res, 400, { error: checked.error.message });
|
|
20590
|
-
const
|
|
20591
|
-
const fetched = await fetchSkillFromSource(input.source);
|
|
20573
|
+
const fetched = await resolveSkillImport(checked.data);
|
|
20592
20574
|
if ("error" in fetched)
|
|
20593
20575
|
return json(res, 422, { error: fetched.error });
|
|
20594
20576
|
const skills = [];
|
|
@@ -21701,10 +21683,10 @@ const handleRequest = async (req, res) => {
|
|
|
21701
21683
|
if (m && method === "POST") {
|
|
21702
21684
|
if (!store.bot(m[1]))
|
|
21703
21685
|
return json(res, 404, { error: "no such bot" });
|
|
21704
|
-
const parsed =
|
|
21686
|
+
const parsed = skillImportSchema.safeParse(await readBody(req));
|
|
21705
21687
|
if (!parsed.success)
|
|
21706
|
-
return json(res, 400, { error: "
|
|
21707
|
-
const fetched = await
|
|
21688
|
+
return json(res, 400, { error: "Choose a Markdown skill file or enter a GitHub source (maximum 256 KiB per file)." });
|
|
21689
|
+
const fetched = await resolveSkillImport(parsed.data);
|
|
21708
21690
|
if ("error" in fetched)
|
|
21709
21691
|
return json(res, 422, { error: fetched.error });
|
|
21710
21692
|
const results = fetched.skills.map((skill) => installSkill(m[1], skill.source, skill.files));
|
|
@@ -22233,10 +22215,20 @@ const handleRequest = async (req, res) => {
|
|
|
22233
22215
|
userAgent: typeof req.headers["user-agent"] === "string" ? req.headers["user-agent"] : undefined,
|
|
22234
22216
|
relayed: Boolean(onBehalfOf),
|
|
22235
22217
|
}));
|
|
22218
|
+
let goalCommand;
|
|
22219
|
+
try {
|
|
22220
|
+
goalCommand = parseGoalCommand(text);
|
|
22221
|
+
}
|
|
22222
|
+
catch (error) {
|
|
22223
|
+
return json(res, 400, { error: error instanceof Error ? error.message : "Invalid goal command" });
|
|
22224
|
+
}
|
|
22225
|
+
if (guarded && goalCommand)
|
|
22226
|
+
return json(res, 400, { error: "Goal commands require the conversation command interface." });
|
|
22236
22227
|
// The send is acknowledged before the turn starts, so a workspace at its
|
|
22237
22228
|
// spend limit is refused here, where the person can see it.
|
|
22238
22229
|
try {
|
|
22239
|
-
|
|
22230
|
+
if (!goalCommand || goalCommand.action === "start" || goalCommand.action === "resume")
|
|
22231
|
+
assertWithinBudget(cfg, DATA_DIR);
|
|
22240
22232
|
}
|
|
22241
22233
|
catch (error) {
|
|
22242
22234
|
return json(res, 409, { error: error instanceof Error ? error.message : String(error), code: "spend_cap" });
|
|
@@ -22305,6 +22297,12 @@ const handleRequest = async (req, res) => {
|
|
|
22305
22297
|
const message = await startTurn(bot.id, text, { threadId, replyTo, sendId, sender: messageSender(auth), trigger });
|
|
22306
22298
|
return { ok: true, threadId, message };
|
|
22307
22299
|
}
|
|
22300
|
+
if (goalCommand) {
|
|
22301
|
+
if (auth.kind === "assistant")
|
|
22302
|
+
throw Object.assign(new Error("Only the person controls goals."), { status: 403 });
|
|
22303
|
+
return receiveGoalCommand(personalGoals, store, bot.id, threadId, goalCommand, text, sendId, messageSender(auth), trigger);
|
|
22304
|
+
}
|
|
22305
|
+
personalGoals.steer(threadId);
|
|
22308
22306
|
// Claude can accept the message inside its live turn. If the write
|
|
22309
22307
|
// loses a race with turn settlement, or the engine cannot steer, the
|
|
22310
22308
|
// existing server-side queue records it atomically for the next turn.
|
|
@@ -23733,6 +23731,8 @@ const handleRequest = async (req, res) => {
|
|
|
23733
23731
|
if (method === "GET" && authStatus) {
|
|
23734
23732
|
res.setHeader("cache-control", "no-store");
|
|
23735
23733
|
const state = await providerAuthSessions.status(authStatus[1], auth.kind === "session" ? auth.session.id : "loopback", url.searchParams.get("flowId") ?? "");
|
|
23734
|
+
if (state.phase === "succeeded")
|
|
23735
|
+
engineRests.clear(authStatus[1], "signed-in");
|
|
23736
23736
|
return json(res, 200, { auth: state });
|
|
23737
23737
|
}
|
|
23738
23738
|
const instanceAction = /^\/api\/instances\/([\w.-]+)\/(refresh-models|install|auth\/start|auth\/complete|auth\/cancel|auth\/sign-out)$/.exec(path);
|
|
@@ -23774,6 +23774,8 @@ const handleRequest = async (req, res) => {
|
|
|
23774
23774
|
providerAuthSessions.revokeOwner(owner);
|
|
23775
23775
|
return json(res, 401, { error: "Your session ended. Start a new sign-in." });
|
|
23776
23776
|
}
|
|
23777
|
+
if (started.phase === "succeeded")
|
|
23778
|
+
engineRests.clear(instanceId, "signed-in");
|
|
23777
23779
|
return json(res, 200, { auth: started });
|
|
23778
23780
|
}
|
|
23779
23781
|
if (action === "auth/sign-out") {
|
|
@@ -23791,6 +23793,8 @@ const handleRequest = async (req, res) => {
|
|
|
23791
23793
|
if (!flowId || !callbackUrl)
|
|
23792
23794
|
return json(res, 400, { error: "flowId and a code or callbackUrl are required" });
|
|
23793
23795
|
await providerAuthSessions.complete(instanceId, owner, flowId, callbackUrl);
|
|
23796
|
+
engineRests.clear(instanceId, "signed-in");
|
|
23797
|
+
await describeEngines();
|
|
23794
23798
|
return json(res, 200, { ok: true });
|
|
23795
23799
|
}
|
|
23796
23800
|
const body = await readBody(req, 4096);
|