openmausbot 0.1.81 → 0.1.82
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/{index-oLMljotU.js → index-5HVwp5m2.js} +1 -1
- package/dist/assets/{index-peLFiSH8.js → index-BLLsDK2F.js} +145 -143
- package/dist/index.html +1 -1
- package/dist-server/index.js +93 -19
- package/dist-server/openmausbot.js +23 -1
- package/dist-server/pair-cli.js +23 -1
- package/dist-server/server/drivers/acp/core.js +1 -1
- package/dist-server/server/drivers/acp/opencode-go.js +13 -0
- package/dist-server/server/index.js +63 -14
- package/dist-server/server/notify.js +8 -3
- package/dist-server/server/routines.js +19 -1
- package/dist-server/server/turn-resources.js +7 -2
- package/package.json +1 -1
package/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/app-icon.svg" />
|
|
7
7
|
<title>OpenMausBot</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-BLLsDK2F.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-CKysBq-V.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/dist-server/index.js
CHANGED
|
@@ -40642,7 +40642,7 @@ function buildNotification(kind, bot, threadId, detail, extra) {
|
|
|
40642
40642
|
if (bot.notifications === false) return null;
|
|
40643
40643
|
const body = summarize(detail);
|
|
40644
40644
|
const who = extra?.group ? `${bot.name} in ${extra.group.name}` : bot.name;
|
|
40645
|
-
const title = kind === "approval" ? `${who} needs approval` : kind === "question" ? `${who} has a question` : kind === "takeover" ? `${who} needs your hands` : kind === "routine-failed" ? `${who}'s routine failed` : kind === "turn-failed" ? `${who} couldn't start` : `${who} finished`;
|
|
40645
|
+
const title = kind === "approval" ? `${who} needs approval` : kind === "question" ? `${who} has a question` : kind === "takeover" ? `${who} needs your hands` : kind === "routine-failed" ? `${who}'s routine failed` : kind === "routine-deferred" ? `${who}'s routine is waiting` : kind === "turn-failed" ? `${who} couldn't start` : `${who} finished`;
|
|
40646
40646
|
if (kind === "done" && !body) return null;
|
|
40647
40647
|
const notification = { kind, botId: bot.id, botName: bot.name, threadId, title, body };
|
|
40648
40648
|
if (extra?.avatarUrl) notification.avatarUrl = extra.avatarUrl;
|
|
@@ -41468,7 +41468,7 @@ function createAcpDriver(support11) {
|
|
|
41468
41468
|
return { turnId };
|
|
41469
41469
|
}
|
|
41470
41470
|
const resolvedModel = support11.resolveTurnModel?.(turn.model, env);
|
|
41471
|
-
support11.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model });
|
|
41471
|
+
support11.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model, fullAuto: turnConfig.fullAuto === true });
|
|
41472
41472
|
const cliTurn = resolvedModel !== void 0 && resolvedModel !== turn.model ? { ...turn, model: resolvedModel } : turn;
|
|
41473
41473
|
const mcpServers = acpMcpServers(turn);
|
|
41474
41474
|
let launch;
|
|
@@ -47714,6 +47714,28 @@ var support7 = (loadCatalog2) => ({
|
|
|
47714
47714
|
modelVariants: true,
|
|
47715
47715
|
resolveTurnModel: (model2, env) => model2 ? ensureOpenCodeInjectModel(normalizeLegacyOpenCodeModel(model2, env), env) : model2,
|
|
47716
47716
|
transformEnv: stripForeignProviderKeys,
|
|
47717
|
+
applyTurnEnv: (env, { fullAuto }) => {
|
|
47718
|
+
if (!fullAuto) return;
|
|
47719
|
+
env.OPENCODE_PERMISSION = JSON.stringify(Object.fromEntries([
|
|
47720
|
+
"*",
|
|
47721
|
+
"external_directory",
|
|
47722
|
+
"read",
|
|
47723
|
+
"edit",
|
|
47724
|
+
"bash",
|
|
47725
|
+
"glob",
|
|
47726
|
+
"grep",
|
|
47727
|
+
"list",
|
|
47728
|
+
"task",
|
|
47729
|
+
"lsp",
|
|
47730
|
+
"skill",
|
|
47731
|
+
"webfetch",
|
|
47732
|
+
"websearch",
|
|
47733
|
+
"codesearch",
|
|
47734
|
+
"todoread",
|
|
47735
|
+
"todowrite",
|
|
47736
|
+
"doom_loop"
|
|
47737
|
+
].map((permission) => [permission, "allow"])));
|
|
47738
|
+
},
|
|
47717
47739
|
pickAuthMethod: () => null,
|
|
47718
47740
|
authFailure: "continue",
|
|
47719
47741
|
isAuthenticated: async (env, config2) => Boolean(env.OPENCODE_API_KEY) || hasStoredOpenCodeAuth(env) || await canListOpenCodeModels(env, config2.cli),
|
|
@@ -60755,10 +60777,14 @@ import { realpathSync as realpathSync6 } from "node:fs";
|
|
|
60755
60777
|
import { relative as relative3, resolve as resolve12, sep as sep2 } from "node:path";
|
|
60756
60778
|
var TurnResources = class {
|
|
60757
60779
|
owners = /* @__PURE__ */ new Map();
|
|
60758
|
-
|
|
60780
|
+
blocker(resource, owner2) {
|
|
60759
60781
|
for (const [key3, current2] of this.owners) {
|
|
60760
|
-
if (overlaps(key3, resource) && !sameOwner(current2, owner2)) return
|
|
60782
|
+
if (overlaps(key3, resource) && !sameOwner(current2, owner2)) return current2;
|
|
60761
60783
|
}
|
|
60784
|
+
return void 0;
|
|
60785
|
+
}
|
|
60786
|
+
claim(resource, owner2) {
|
|
60787
|
+
if (this.blocker(resource, owner2)) return false;
|
|
60762
60788
|
this.owners.set(resource, owner2);
|
|
60763
60789
|
return true;
|
|
60764
60790
|
}
|
|
@@ -64739,6 +64765,7 @@ function routineRequestOwnerKey(owner2) {
|
|
|
64739
64765
|
var ALL_DAYS = [0, 1, 2, 3, 4, 5, 6];
|
|
64740
64766
|
var CATCH_UP_MS = 12 * 60 * 6e4;
|
|
64741
64767
|
var WAKE_HORIZON_MS = 60 * 6e4;
|
|
64768
|
+
var ROUTINE_DEFERRAL_NOTICE_MS = 30 * 6e4;
|
|
64742
64769
|
var MAX_DATE_MS = 864e13;
|
|
64743
64770
|
var LOCAL_DAY_MS = 24 * 60 * 6e4;
|
|
64744
64771
|
var INTERVAL_RESTRICTION_SEARCH_MS = 9 * LOCAL_DAY_MS;
|
|
@@ -65713,7 +65740,20 @@ var RoutineManager = class {
|
|
|
65713
65740
|
}
|
|
65714
65741
|
}
|
|
65715
65742
|
const state = this.targetState(run3);
|
|
65716
|
-
if (state === "busy")
|
|
65743
|
+
if (state === "busy") {
|
|
65744
|
+
if (run3.deferredAt == null) {
|
|
65745
|
+
run3.deferredAt = now;
|
|
65746
|
+
this.save();
|
|
65747
|
+
this.emitRun(run3);
|
|
65748
|
+
}
|
|
65749
|
+
if (run3.deferredNoticeAt == null && now - run3.deferredAt >= ROUTINE_DEFERRAL_NOTICE_MS) {
|
|
65750
|
+
run3.deferredNoticeAt = now;
|
|
65751
|
+
this.save();
|
|
65752
|
+
this.emitRun(run3);
|
|
65753
|
+
this.options.onRunDeferred?.(cloneRun(run3));
|
|
65754
|
+
}
|
|
65755
|
+
continue;
|
|
65756
|
+
}
|
|
65717
65757
|
if (state === "missing") {
|
|
65718
65758
|
this.failRun(run3, this.missingTargetMessage(run3.target));
|
|
65719
65759
|
continue;
|
|
@@ -81627,9 +81667,33 @@ function releaseTurnResources(owner2) {
|
|
|
81627
81667
|
teamComputerTurns.delete(owner2.threadId);
|
|
81628
81668
|
}
|
|
81629
81669
|
}
|
|
81630
|
-
function bindTurnComputer(owner2, resource, exclusive = false) {
|
|
81631
|
-
|
|
81632
|
-
|
|
81670
|
+
async function bindTurnComputer(owner2, resource, exclusive = false) {
|
|
81671
|
+
const active = () => activeInternalGenerationByThread.get(owner2.threadId) === owner2.generation && turnResourceOwners.get(owner2.threadId)?.generation === owner2.generation;
|
|
81672
|
+
let waitingMessage;
|
|
81673
|
+
const deadline = Date.now() + GROUP_GOAL_WAIT_MAX_MS;
|
|
81674
|
+
try {
|
|
81675
|
+
while (true) {
|
|
81676
|
+
if (!active()) throw new DirectTurnSetupCancelled("Computer wait cancelled");
|
|
81677
|
+
if (!exclusive || claimTurnResource(owner2, resource)) break;
|
|
81678
|
+
if (!waitingMessage) {
|
|
81679
|
+
const blocker = turnResources.blocker(resource, owner2);
|
|
81680
|
+
const holderBot = blocker && store.botByThread(blocker.threadId);
|
|
81681
|
+
const holderTask = holderBot && blocker && store.taskByThread(holderBot.id, blocker.threadId);
|
|
81682
|
+
const holder = holderBot ? `${holderBot.name}${holderTask?.title ? ` / ${holderTask.title}` : ""}` : blocker && store.groupByThread(blocker.threadId)?.name;
|
|
81683
|
+
waitingMessage = store.appendMessage(owner2.threadId, {
|
|
81684
|
+
role: "bot",
|
|
81685
|
+
kind: "activity",
|
|
81686
|
+
tool: { name: `Waiting for computer${holder ? ` \u2014 ${holder} is using it` : ""}; will continue automatically` },
|
|
81687
|
+
...holderBot && holderTask ? { threadRef: { botId: holderBot.id, threadId: holderTask.threadId, title: holderTask.title } } : {}
|
|
81688
|
+
});
|
|
81689
|
+
}
|
|
81690
|
+
if (Date.now() >= deadline) throw new Error("Computer is still busy. Stop the turn using it, then retry.");
|
|
81691
|
+
await new Promise((resolve16) => setTimeout(resolve16, 100));
|
|
81692
|
+
}
|
|
81693
|
+
} finally {
|
|
81694
|
+
if (waitingMessage) store.patchMessage(owner2.threadId, waitingMessage.id, {
|
|
81695
|
+
tool: { name: active() && turnResources.owns(resource, owner2) ? "Computer available \u2014 continuing" : "Computer wait ended", ok: true }
|
|
81696
|
+
});
|
|
81633
81697
|
}
|
|
81634
81698
|
turnResourceOwners.set(owner2.threadId, owner2);
|
|
81635
81699
|
turnComputerResources.set(owner2.threadId, { owner: owner2, resource });
|
|
@@ -83426,10 +83490,10 @@ var watchdog = new TurnWatchdog({
|
|
|
83426
83490
|
groupSpeakers.delete(turn.threadId);
|
|
83427
83491
|
store.patchGroup(group.id, { busyBotId: null, unread: true });
|
|
83428
83492
|
}
|
|
83493
|
+
releaseTurnResources(stalledResourceOwner);
|
|
83429
83494
|
const currentBot = store.bot(turn.botId);
|
|
83430
83495
|
if (currentBot?.busy) {
|
|
83431
83496
|
stopScreenPoller(currentBot.id, turn.threadId);
|
|
83432
|
-
releaseTurnResources(stalledResourceOwner);
|
|
83433
83497
|
if (activeVpsThreads.get(currentBot.id) === turn.threadId) activeVpsThreads.delete(currentBot.id);
|
|
83434
83498
|
if (store.taskByThread(currentBot.id, turn.threadId)) store.setTaskActivity(currentBot.id, turn.threadId, "idle");
|
|
83435
83499
|
else store.setActivity(currentBot.id, "idle");
|
|
@@ -83611,11 +83675,11 @@ async function attachTeamBox(computer, botId, owner2, canMount, remoteAgent) {
|
|
|
83611
83675
|
const ownerId = teamComputerOwner(computer.id);
|
|
83612
83676
|
if (boxLifecycleBusyBots.has(ownerId)) throw new Error("The team computer is being changed; wait for it to finish");
|
|
83613
83677
|
if (computerControl.snapshot(ownerId).held) throw new Error("Release human control of the team computer before starting another turn");
|
|
83614
|
-
bindTurnComputer(owner2, `computer:box-bot:${ownerId}`, true);
|
|
83678
|
+
await bindTurnComputer(owner2, `computer:box-bot:${ownerId}`, true);
|
|
83615
83679
|
teamComputerTurns.set(owner2.threadId, { owner: owner2, computerId: computer.id, botId, remoteAgent });
|
|
83616
83680
|
let machine = await findBox(cfg, ownerId);
|
|
83617
83681
|
if (!machine) throw new Error("The team's Box computer is missing; explicitly create or retry it from the Team map");
|
|
83618
|
-
bindTurnComputer(owner2, `computer:box:${machine.id}`, true);
|
|
83682
|
+
await bindTurnComputer(owner2, `computer:box:${machine.id}`, true);
|
|
83619
83683
|
const action = boxTurnLifecycleAction({ explicitCloud: true, canMount: true, state: typeof machine.state === "string" ? machine.state : null });
|
|
83620
83684
|
if (action === "wake") machine = await readyBox(cfg, ownerId);
|
|
83621
83685
|
if (!machine || boxTurnLifecycleAction({ explicitCloud: true, canMount: true, state: typeof machine.state === "string" ? machine.state : null }) !== "attach") {
|
|
@@ -85013,7 +85077,7 @@ async function startTurn(botId, text8, opts) {
|
|
|
85013
85077
|
if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(localVmTarget.key)) return false;
|
|
85014
85078
|
}
|
|
85015
85079
|
try {
|
|
85016
|
-
bindTurnComputer(resourceOwner, `computer:vm:${localVmTarget.key}`, true);
|
|
85080
|
+
await bindTurnComputer(resourceOwner, `computer:vm:${localVmTarget.key}`, true);
|
|
85017
85081
|
if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(localVmTarget.key)) {
|
|
85018
85082
|
throw new Error("this Local VM is being started, stopped, or replaced \u2014 wait for setup to finish");
|
|
85019
85083
|
}
|
|
@@ -85058,7 +85122,7 @@ async function startTurn(botId, text8, opts) {
|
|
|
85058
85122
|
}
|
|
85059
85123
|
const cua = readCuaConnection();
|
|
85060
85124
|
if (!cua) throw new Error("CUA Driver is not ready for this computer \u2014 check permissions and restart OpenMausBot");
|
|
85061
|
-
bindTurnComputer(resourceOwner, "computer:host");
|
|
85125
|
+
await bindTurnComputer(resourceOwner, "computer:host");
|
|
85062
85126
|
integrations.localComputer = gatedLocalComputer(cua, controlIntegration(bot.id, threadId, dispatchClaimId));
|
|
85063
85127
|
computerKind = "local";
|
|
85064
85128
|
}
|
|
@@ -85067,7 +85131,7 @@ async function startTurn(botId, text8, opts) {
|
|
|
85067
85131
|
if (unsupported && wants2 === "cloud") throw new Error(unsupported);
|
|
85068
85132
|
if (unsupported && wants2 === void 0) autoVpsProblem = unsupported;
|
|
85069
85133
|
if (!unsupported) {
|
|
85070
|
-
bindTurnComputer(resourceOwner, `computer:vps:${vpsSshAlias(cfg)}:${bot.id}`, true);
|
|
85134
|
+
await bindTurnComputer(resourceOwner, `computer:vps:${vpsSshAlias(cfg)}:${bot.id}`, true);
|
|
85071
85135
|
activeVpsThreads.set(bot.id, threadId);
|
|
85072
85136
|
let remote;
|
|
85073
85137
|
remote = vpsStartsForTurn({ wants: wants2, autoStartVps: bot.autoStartVps, automationSource: opts?.automationSource }) ? await vpsComputerAction("provision", cfg, bot.id) : await inspectVpsForAuto(cfg, bot.id);
|
|
@@ -85097,7 +85161,7 @@ async function startTurn(botId, text8, opts) {
|
|
|
85097
85161
|
computerKind = "box";
|
|
85098
85162
|
}
|
|
85099
85163
|
if (!teamComputer && mountsCloudComputer && (wants2 === "cloud" || wants2 === void 0) && cloudBackend === "box" && boxConfigured(cfg)) {
|
|
85100
|
-
if (wants2 === "cloud") bindTurnComputer(resourceOwner, `computer:box-bot:${bot.id}`, true);
|
|
85164
|
+
if (wants2 === "cloud") await bindTurnComputer(resourceOwner, `computer:box-bot:${bot.id}`, true);
|
|
85101
85165
|
if (!mountsCloudComputer && wants2 === "cloud") {
|
|
85102
85166
|
throw new Error("this model engine cannot use computer tools \u2014 choose Claude, an ACP engine, or the Computer engine");
|
|
85103
85167
|
}
|
|
@@ -85133,7 +85197,7 @@ async function startTurn(botId, text8, opts) {
|
|
|
85133
85197
|
});
|
|
85134
85198
|
}
|
|
85135
85199
|
if (b3 && lifecycle === "attach") {
|
|
85136
|
-
bindTurnComputer(resourceOwner, `computer:box:${b3.id}`, instance.driverKind === "boxAgent");
|
|
85200
|
+
await bindTurnComputer(resourceOwner, `computer:box:${b3.id}`, instance.driverKind === "boxAgent");
|
|
85137
85201
|
previewCapture = () => screenshotBox(cfg, bot.id, b3.id);
|
|
85138
85202
|
if (mountsCloudComputer) {
|
|
85139
85203
|
integrations.computer = {
|
|
@@ -85161,7 +85225,7 @@ async function startTurn(botId, text8, opts) {
|
|
|
85161
85225
|
})) {
|
|
85162
85226
|
const cua = readCuaConnection();
|
|
85163
85227
|
if (cua) {
|
|
85164
|
-
bindTurnComputer(resourceOwner, "computer:host");
|
|
85228
|
+
await bindTurnComputer(resourceOwner, "computer:host");
|
|
85165
85229
|
integrations.localComputer = gatedLocalComputer(cua, controlIntegration(bot.id, threadId, dispatchClaimId));
|
|
85166
85230
|
computerKind = "local";
|
|
85167
85231
|
}
|
|
@@ -85415,6 +85479,7 @@ function routineRunCard(run3) {
|
|
|
85415
85479
|
status: run3.status
|
|
85416
85480
|
};
|
|
85417
85481
|
if (run3.goalStatus) card.goalStatus = run3.goalStatus;
|
|
85482
|
+
if (run3.deferredAt != null && run3.status === "queued") card.deferredAt = run3.deferredAt;
|
|
85418
85483
|
if (run3.threadId) card.executionThreadId = run3.threadId;
|
|
85419
85484
|
if (summary) card.summary = summary;
|
|
85420
85485
|
if (error51) card.error = error51;
|
|
@@ -85422,7 +85487,7 @@ function routineRunCard(run3) {
|
|
|
85422
85487
|
}
|
|
85423
85488
|
function routineRunFallbackText(card) {
|
|
85424
85489
|
const goalState = card.goalStatus === "needs-input" ? "needs your input" : card.goalStatus === "blocked" ? "was blocked" : card.goalStatus === "limit-reached" ? "reached its limit" : card.goalStatus === "stopped" ? "was stopped" : card.goalStatus === "failed" ? "failed" : void 0;
|
|
85425
|
-
const state = goalState ?? (card.status === "waiting" ? "needs your attention" : card.status === "completed" ? "completed" : card.status === "failed" ? "failed" : card.status === "cancelled" ? "was cancelled" : card.status === "missed" ? "was missed" : card.status);
|
|
85490
|
+
const state = goalState ?? (card.status === "waiting" ? "needs your attention" : card.status === "completed" ? "completed" : card.status === "failed" ? "failed" : card.status === "cancelled" ? "was cancelled" : card.status === "missed" ? "was missed" : card.status === "queued" && card.deferredAt != null ? "deferred: target busy" : card.status);
|
|
85426
85491
|
return `Routine \u201C${card.routineName}\u201D ${state}`;
|
|
85427
85492
|
}
|
|
85428
85493
|
function syncRoutineRunToSource(run3) {
|
|
@@ -85585,6 +85650,14 @@ routines = new RoutineManager({
|
|
|
85585
85650
|
const detail = run3.error ? `${run3.routineName}: ${run3.error}` : run3.routineName;
|
|
85586
85651
|
const notificationBot = routineSourceOwner(run3)?.bot ?? bot;
|
|
85587
85652
|
notify(buildNotification("routine-failed", notificationBot, routineSourceThread(run3) ?? run3.threadId ?? bot.threadId, detail));
|
|
85653
|
+
},
|
|
85654
|
+
onRunDeferred: (run3) => {
|
|
85655
|
+
const bot = store.bot(run3.botId);
|
|
85656
|
+
if (!bot) return;
|
|
85657
|
+
const minutes = run3.deferredAt != null && run3.deferredNoticeAt != null ? Math.max(1, Math.round((run3.deferredNoticeAt - run3.deferredAt) / 6e4)) : null;
|
|
85658
|
+
const detail = `${redactSecretsInText(run3.routineName)}: target busy${minutes != null ? ` for ${minutes} minutes` : ""}`;
|
|
85659
|
+
const notificationBot = routineSourceOwner(run3)?.bot ?? bot;
|
|
85660
|
+
notify(buildNotification("routine-deferred", notificationBot, routineSourceThread(run3) ?? bot.threadId, detail));
|
|
85588
85661
|
}
|
|
85589
85662
|
});
|
|
85590
85663
|
var recoveredRoutineGoalRuns = new Map(
|
|
@@ -86424,7 +86497,7 @@ async function runGroupMemberTurn(groupId, threadId, botId, hop, spoken = /* @__
|
|
|
86424
86497
|
throw new Error("this model engine cannot use the Local VM");
|
|
86425
86498
|
}
|
|
86426
86499
|
const target = { ...localVmTargetForBot(readyBot.id) };
|
|
86427
|
-
bindTurnComputer(resourceOwner, `computer:vm:${target.key}`, true);
|
|
86500
|
+
await bindTurnComputer(resourceOwner, `computer:vm:${target.key}`, true);
|
|
86428
86501
|
if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(target.key)) {
|
|
86429
86502
|
throw new Error("this Local VM is being started, stopped, or replaced");
|
|
86430
86503
|
}
|
|
@@ -86775,6 +86848,7 @@ ${e.text}`;
|
|
|
86775
86848
|
return true;
|
|
86776
86849
|
} catch (error51) {
|
|
86777
86850
|
if (providerDispatched) throw error51;
|
|
86851
|
+
if (error51 instanceof DirectTurnSetupCancelled) return false;
|
|
86778
86852
|
const isSpendCap = typeof error51 === "object" && error51 !== null && error51.code === "spend_cap";
|
|
86779
86853
|
const isVariantError = typeof error51 === "object" && error51 !== null && error51.code === "unsupported_model_variant";
|
|
86780
86854
|
if (!roomSpeaker && !isSpendCap && !isVariantError) throw error51;
|
|
@@ -20054,7 +20054,7 @@ function createAcpDriver(support11) {
|
|
|
20054
20054
|
return { turnId };
|
|
20055
20055
|
}
|
|
20056
20056
|
const resolvedModel = support11.resolveTurnModel?.(turn.model, env);
|
|
20057
|
-
support11.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model });
|
|
20057
|
+
support11.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model, fullAuto: turnConfig.fullAuto === true });
|
|
20058
20058
|
const cliTurn = resolvedModel !== void 0 && resolvedModel !== turn.model ? { ...turn, model: resolvedModel } : turn;
|
|
20059
20059
|
const mcpServers = acpMcpServers(turn);
|
|
20060
20060
|
let launch;
|
|
@@ -36256,6 +36256,28 @@ var init_opencode_go = __esm({
|
|
|
36256
36256
|
modelVariants: true,
|
|
36257
36257
|
resolveTurnModel: (model, env) => model ? ensureOpenCodeInjectModel(normalizeLegacyOpenCodeModel(model, env), env) : model,
|
|
36258
36258
|
transformEnv: stripForeignProviderKeys,
|
|
36259
|
+
applyTurnEnv: (env, { fullAuto }) => {
|
|
36260
|
+
if (!fullAuto) return;
|
|
36261
|
+
env.OPENCODE_PERMISSION = JSON.stringify(Object.fromEntries([
|
|
36262
|
+
"*",
|
|
36263
|
+
"external_directory",
|
|
36264
|
+
"read",
|
|
36265
|
+
"edit",
|
|
36266
|
+
"bash",
|
|
36267
|
+
"glob",
|
|
36268
|
+
"grep",
|
|
36269
|
+
"list",
|
|
36270
|
+
"task",
|
|
36271
|
+
"lsp",
|
|
36272
|
+
"skill",
|
|
36273
|
+
"webfetch",
|
|
36274
|
+
"websearch",
|
|
36275
|
+
"codesearch",
|
|
36276
|
+
"todoread",
|
|
36277
|
+
"todowrite",
|
|
36278
|
+
"doom_loop"
|
|
36279
|
+
].map((permission) => [permission, "allow"])));
|
|
36280
|
+
},
|
|
36259
36281
|
pickAuthMethod: () => null,
|
|
36260
36282
|
authFailure: "continue",
|
|
36261
36283
|
isAuthenticated: async (env, config2) => Boolean(env.OPENCODE_API_KEY) || hasStoredOpenCodeAuth(env) || await canListOpenCodeModels(env, config2.cli),
|
package/dist-server/pair-cli.js
CHANGED
|
@@ -20054,7 +20054,7 @@ function createAcpDriver(support11) {
|
|
|
20054
20054
|
return { turnId };
|
|
20055
20055
|
}
|
|
20056
20056
|
const resolvedModel = support11.resolveTurnModel?.(turn.model, env);
|
|
20057
|
-
support11.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model });
|
|
20057
|
+
support11.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model, fullAuto: turnConfig.fullAuto === true });
|
|
20058
20058
|
const cliTurn = resolvedModel !== void 0 && resolvedModel !== turn.model ? { ...turn, model: resolvedModel } : turn;
|
|
20059
20059
|
const mcpServers = acpMcpServers(turn);
|
|
20060
20060
|
let launch;
|
|
@@ -36256,6 +36256,28 @@ var init_opencode_go = __esm({
|
|
|
36256
36256
|
modelVariants: true,
|
|
36257
36257
|
resolveTurnModel: (model, env) => model ? ensureOpenCodeInjectModel(normalizeLegacyOpenCodeModel(model, env), env) : model,
|
|
36258
36258
|
transformEnv: stripForeignProviderKeys,
|
|
36259
|
+
applyTurnEnv: (env, { fullAuto }) => {
|
|
36260
|
+
if (!fullAuto) return;
|
|
36261
|
+
env.OPENCODE_PERMISSION = JSON.stringify(Object.fromEntries([
|
|
36262
|
+
"*",
|
|
36263
|
+
"external_directory",
|
|
36264
|
+
"read",
|
|
36265
|
+
"edit",
|
|
36266
|
+
"bash",
|
|
36267
|
+
"glob",
|
|
36268
|
+
"grep",
|
|
36269
|
+
"list",
|
|
36270
|
+
"task",
|
|
36271
|
+
"lsp",
|
|
36272
|
+
"skill",
|
|
36273
|
+
"webfetch",
|
|
36274
|
+
"websearch",
|
|
36275
|
+
"codesearch",
|
|
36276
|
+
"todoread",
|
|
36277
|
+
"todowrite",
|
|
36278
|
+
"doom_loop"
|
|
36279
|
+
].map((permission) => [permission, "allow"])));
|
|
36280
|
+
},
|
|
36259
36281
|
pickAuthMethod: () => null,
|
|
36260
36282
|
authFailure: "continue",
|
|
36261
36283
|
isAuthenticated: async (env, config2) => Boolean(env.OPENCODE_API_KEY) || hasStoredOpenCodeAuth(env) || await canListOpenCodeModels(env, config2.cli),
|
|
@@ -327,7 +327,7 @@ export function createAcpDriver(support) {
|
|
|
327
327
|
return { turnId };
|
|
328
328
|
}
|
|
329
329
|
const resolvedModel = support.resolveTurnModel?.(turn.model, env);
|
|
330
|
-
support.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model });
|
|
330
|
+
support.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model, fullAuto: turnConfig.fullAuto === true });
|
|
331
331
|
const cliTurn = resolvedModel !== undefined && resolvedModel !== turn.model
|
|
332
332
|
? { ...turn, model: resolvedModel }
|
|
333
333
|
: turn;
|
|
@@ -340,6 +340,19 @@ const support = (loadCatalog) => ({
|
|
|
340
340
|
? ensureOpenCodeInjectModel(normalizeLegacyOpenCodeModel(model, env), env)
|
|
341
341
|
: model,
|
|
342
342
|
transformEnv: stripForeignProviderKeys,
|
|
343
|
+
applyTurnEnv: (env, { fullAuto }) => {
|
|
344
|
+
if (!fullAuto)
|
|
345
|
+
return;
|
|
346
|
+
// Scope native permissions to this child, not the user's OpenCode config.
|
|
347
|
+
// A wildcard alone leaves OpenCode's more-specific external-directory and
|
|
348
|
+
// read rules in place. Replace the built-in rules as well, including path
|
|
349
|
+
// maps, so Full means the same thing inside the provider and in OMB.
|
|
350
|
+
env.OPENCODE_PERMISSION = JSON.stringify(Object.fromEntries([
|
|
351
|
+
"*", "external_directory", "read", "edit", "bash", "glob", "grep",
|
|
352
|
+
"list", "task", "lsp", "skill", "webfetch", "websearch", "codesearch",
|
|
353
|
+
"todoread", "todowrite", "doom_loop",
|
|
354
|
+
].map((permission) => [permission, "allow"])));
|
|
355
|
+
},
|
|
343
356
|
pickAuthMethod: () => null,
|
|
344
357
|
authFailure: "continue",
|
|
345
358
|
isAuthenticated: async (env, config) => (Boolean(env.OPENCODE_API_KEY)
|
|
@@ -616,9 +616,39 @@ function releaseTurnResources(owner) {
|
|
|
616
616
|
teamComputerTurns.delete(owner.threadId);
|
|
617
617
|
}
|
|
618
618
|
}
|
|
619
|
-
function bindTurnComputer(owner, resource, exclusive = false) {
|
|
620
|
-
|
|
621
|
-
|
|
619
|
+
async function bindTurnComputer(owner, resource, exclusive = false) {
|
|
620
|
+
const active = () => activeInternalGenerationByThread.get(owner.threadId) === owner.generation &&
|
|
621
|
+
turnResourceOwners.get(owner.threadId)?.generation === owner.generation;
|
|
622
|
+
let waitingMessage;
|
|
623
|
+
const deadline = Date.now() + GROUP_GOAL_WAIT_MAX_MS;
|
|
624
|
+
try {
|
|
625
|
+
while (true) {
|
|
626
|
+
if (!active())
|
|
627
|
+
throw new DirectTurnSetupCancelled("Computer wait cancelled");
|
|
628
|
+
if (!exclusive || claimTurnResource(owner, resource))
|
|
629
|
+
break;
|
|
630
|
+
if (!waitingMessage) {
|
|
631
|
+
const blocker = turnResources.blocker(resource, owner);
|
|
632
|
+
const holderBot = blocker && store.botByThread(blocker.threadId);
|
|
633
|
+
const holderTask = holderBot && blocker && store.taskByThread(holderBot.id, blocker.threadId);
|
|
634
|
+
const holder = holderBot ? `${holderBot.name}${holderTask?.title ? ` / ${holderTask.title}` : ""}`
|
|
635
|
+
: blocker && store.groupByThread(blocker.threadId)?.name;
|
|
636
|
+
waitingMessage = store.appendMessage(owner.threadId, {
|
|
637
|
+
role: "bot", kind: "activity",
|
|
638
|
+
tool: { name: `Waiting for computer${holder ? ` — ${holder} is using it` : ""}; will continue automatically` },
|
|
639
|
+
...(holderBot && holderTask ? { threadRef: { botId: holderBot.id, threadId: holderTask.threadId, title: holderTask.title } } : {}),
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
if (Date.now() >= deadline)
|
|
643
|
+
throw new Error("Computer is still busy. Stop the turn using it, then retry.");
|
|
644
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
finally {
|
|
648
|
+
if (waitingMessage)
|
|
649
|
+
store.patchMessage(owner.threadId, waitingMessage.id, {
|
|
650
|
+
tool: { name: active() && turnResources.owns(resource, owner) ? "Computer available — continuing" : "Computer wait ended", ok: true },
|
|
651
|
+
});
|
|
622
652
|
}
|
|
623
653
|
turnResourceOwners.set(owner.threadId, owner);
|
|
624
654
|
turnComputerResources.set(owner.threadId, { owner, resource });
|
|
@@ -2943,10 +2973,12 @@ const watchdog = new TurnWatchdog({
|
|
|
2943
2973
|
groupSpeakers.delete(turn.threadId);
|
|
2944
2974
|
store.patchGroup(group.id, { busyBotId: null, unread: true });
|
|
2945
2975
|
}
|
|
2976
|
+
// A cleared UI busy flag must not strand this finished generation's
|
|
2977
|
+
// computer claim. The generation checks above protect replacements.
|
|
2978
|
+
releaseTurnResources(stalledResourceOwner);
|
|
2946
2979
|
const currentBot = store.bot(turn.botId);
|
|
2947
2980
|
if (currentBot?.busy) {
|
|
2948
2981
|
stopScreenPoller(currentBot.id, turn.threadId);
|
|
2949
|
-
releaseTurnResources(stalledResourceOwner);
|
|
2950
2982
|
if (activeVpsThreads.get(currentBot.id) === turn.threadId)
|
|
2951
2983
|
activeVpsThreads.delete(currentBot.id);
|
|
2952
2984
|
if (store.taskByThread(currentBot.id, turn.threadId))
|
|
@@ -3223,12 +3255,12 @@ async function attachTeamBox(computer, botId, owner, canMount, remoteAgent) {
|
|
|
3223
3255
|
throw new Error("The team computer is being changed; wait for it to finish");
|
|
3224
3256
|
if (computerControl.snapshot(ownerId).held)
|
|
3225
3257
|
throw new Error("Release human control of the team computer before starting another turn");
|
|
3226
|
-
bindTurnComputer(owner, `computer:box-bot:${ownerId}`, true);
|
|
3258
|
+
await bindTurnComputer(owner, `computer:box-bot:${ownerId}`, true);
|
|
3227
3259
|
teamComputerTurns.set(owner.threadId, { owner, computerId: computer.id, botId, remoteAgent });
|
|
3228
3260
|
let machine = await box.findBox(cfg, ownerId);
|
|
3229
3261
|
if (!machine)
|
|
3230
3262
|
throw new Error("The team's Box computer is missing; explicitly create or retry it from the Team map");
|
|
3231
|
-
bindTurnComputer(owner, `computer:box:${machine.id}`, true);
|
|
3263
|
+
await bindTurnComputer(owner, `computer:box:${machine.id}`, true);
|
|
3232
3264
|
const action = box.boxTurnLifecycleAction({ explicitCloud: true, canMount: true, state: typeof machine.state === "string" ? machine.state : null });
|
|
3233
3265
|
if (action === "wake")
|
|
3234
3266
|
machine = await box.readyBox(cfg, ownerId);
|
|
@@ -5209,7 +5241,7 @@ async function startTurn(botId, text, opts) {
|
|
|
5209
5241
|
return false;
|
|
5210
5242
|
}
|
|
5211
5243
|
try {
|
|
5212
|
-
bindTurnComputer(resourceOwner, `computer:vm:${localVmTarget.key}`, true);
|
|
5244
|
+
await bindTurnComputer(resourceOwner, `computer:vm:${localVmTarget.key}`, true);
|
|
5213
5245
|
if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(localVmTarget.key)) {
|
|
5214
5246
|
throw new Error("this Local VM is being started, stopped, or replaced — wait for setup to finish");
|
|
5215
5247
|
}
|
|
@@ -5268,7 +5300,7 @@ async function startTurn(botId, text, opts) {
|
|
|
5268
5300
|
const cua = readCuaConnection();
|
|
5269
5301
|
if (!cua)
|
|
5270
5302
|
throw new Error("CUA Driver is not ready for this computer — check permissions and restart OpenMausBot");
|
|
5271
|
-
bindTurnComputer(resourceOwner, "computer:host");
|
|
5303
|
+
await bindTurnComputer(resourceOwner, "computer:host");
|
|
5272
5304
|
integrations.localComputer = gatedLocalComputer(cua, controlIntegration(bot.id, threadId, dispatchClaimId));
|
|
5273
5305
|
computerKind = "local";
|
|
5274
5306
|
}
|
|
@@ -5284,7 +5316,7 @@ async function startTurn(botId, text, opts) {
|
|
|
5284
5316
|
if (!unsupported) {
|
|
5285
5317
|
// The remote lifecycle and container are shared by this bot. Keep
|
|
5286
5318
|
// its explicit computer turns serialized; ordinary threads still run.
|
|
5287
|
-
bindTurnComputer(resourceOwner, `computer:vps:${vpsSshAlias(cfg)}:${bot.id}`, true);
|
|
5319
|
+
await bindTurnComputer(resourceOwner, `computer:vps:${vpsSshAlias(cfg)}:${bot.id}`, true);
|
|
5288
5320
|
activeVpsThreads.set(bot.id, threadId);
|
|
5289
5321
|
let remote;
|
|
5290
5322
|
remote = vps.vpsStartsForTurn({ wants, autoStartVps: bot.autoStartVps, automationSource: opts?.automationSource })
|
|
@@ -5322,7 +5354,7 @@ async function startTurn(botId, text, opts) {
|
|
|
5322
5354
|
// Explicit cloud turns can provision/wake the same bot's Box. Claim
|
|
5323
5355
|
// before any network await so setup itself cannot race another turn.
|
|
5324
5356
|
if (wants === "cloud")
|
|
5325
|
-
bindTurnComputer(resourceOwner, `computer:box-bot:${bot.id}`, true);
|
|
5357
|
+
await bindTurnComputer(resourceOwner, `computer:box-bot:${bot.id}`, true);
|
|
5326
5358
|
if (!mountsCloudComputer && wants === "cloud") {
|
|
5327
5359
|
throw new Error("this model engine cannot use computer tools — choose Claude, an ACP engine, or the Computer engine");
|
|
5328
5360
|
}
|
|
@@ -5366,7 +5398,7 @@ async function startTurn(botId, text, opts) {
|
|
|
5366
5398
|
});
|
|
5367
5399
|
}
|
|
5368
5400
|
if (b && lifecycle === "attach") {
|
|
5369
|
-
bindTurnComputer(resourceOwner, `computer:box:${b.id}`, instance.driverKind === "boxAgent");
|
|
5401
|
+
await bindTurnComputer(resourceOwner, `computer:box:${b.id}`, instance.driverKind === "boxAgent");
|
|
5370
5402
|
previewCapture = () => box.screenshotBox(cfg, bot.id, b.id);
|
|
5371
5403
|
if (mountsCloudComputer) {
|
|
5372
5404
|
integrations.computer = {
|
|
@@ -5407,7 +5439,7 @@ async function startTurn(botId, text, opts) {
|
|
|
5407
5439
|
})) {
|
|
5408
5440
|
const cua = readCuaConnection();
|
|
5409
5441
|
if (cua) {
|
|
5410
|
-
bindTurnComputer(resourceOwner, "computer:host");
|
|
5442
|
+
await bindTurnComputer(resourceOwner, "computer:host");
|
|
5411
5443
|
integrations.localComputer = gatedLocalComputer(cua, controlIntegration(bot.id, threadId, dispatchClaimId));
|
|
5412
5444
|
computerKind = "local";
|
|
5413
5445
|
}
|
|
@@ -5771,6 +5803,8 @@ function routineRunCard(run) {
|
|
|
5771
5803
|
};
|
|
5772
5804
|
if (run.goalStatus)
|
|
5773
5805
|
card.goalStatus = run.goalStatus;
|
|
5806
|
+
if (run.deferredAt != null && run.status === "queued")
|
|
5807
|
+
card.deferredAt = run.deferredAt;
|
|
5774
5808
|
if (run.threadId)
|
|
5775
5809
|
card.executionThreadId = run.threadId;
|
|
5776
5810
|
if (summary)
|
|
@@ -5801,7 +5835,9 @@ function routineRunFallbackText(card) {
|
|
|
5801
5835
|
? "was cancelled"
|
|
5802
5836
|
: card.status === "missed"
|
|
5803
5837
|
? "was missed"
|
|
5804
|
-
: card.status
|
|
5838
|
+
: card.status === "queued" && card.deferredAt != null
|
|
5839
|
+
? "deferred: target busy"
|
|
5840
|
+
: card.status);
|
|
5805
5841
|
return `Routine “${card.routineName}” ${state}`;
|
|
5806
5842
|
}
|
|
5807
5843
|
/** Upsert one durable lifecycle card per run. Replaying the same transition,
|
|
@@ -6005,6 +6041,17 @@ routines = new RoutineManager({
|
|
|
6005
6041
|
const notificationBot = routineSourceOwner(run)?.bot ?? bot;
|
|
6006
6042
|
notify(buildNotification("routine-failed", notificationBot, routineSourceThread(run) ?? run.threadId ?? bot.threadId, detail));
|
|
6007
6043
|
},
|
|
6044
|
+
onRunDeferred: (run) => {
|
|
6045
|
+
const bot = store.bot(run.botId);
|
|
6046
|
+
if (!bot)
|
|
6047
|
+
return;
|
|
6048
|
+
const minutes = run.deferredAt != null && run.deferredNoticeAt != null
|
|
6049
|
+
? Math.max(1, Math.round((run.deferredNoticeAt - run.deferredAt) / 60_000))
|
|
6050
|
+
: null;
|
|
6051
|
+
const detail = `${redactSecretsInText(run.routineName)}: target busy${minutes != null ? ` for ${minutes} minutes` : ""}`;
|
|
6052
|
+
const notificationBot = routineSourceOwner(run)?.bot ?? bot;
|
|
6053
|
+
notify(buildNotification("routine-deferred", notificationBot, routineSourceThread(run) ?? bot.threadId, detail));
|
|
6054
|
+
},
|
|
6008
6055
|
});
|
|
6009
6056
|
// The scheduler receipt and room transcript live in separate durable stores.
|
|
6010
6057
|
// If the process exited between those two writes, prefer the correlated
|
|
@@ -7009,7 +7056,7 @@ setupRetry = 0) {
|
|
|
7009
7056
|
}
|
|
7010
7057
|
// A distinct identity fences cleanup even in shared mode on the same room thread.
|
|
7011
7058
|
const target = { ...localVmTargetForBot(readyBot.id) };
|
|
7012
|
-
bindTurnComputer(resourceOwner, `computer:vm:${target.key}`, true);
|
|
7059
|
+
await bindTurnComputer(resourceOwner, `computer:vm:${target.key}`, true);
|
|
7013
7060
|
if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(target.key)) {
|
|
7014
7061
|
throw new Error("this Local VM is being started, stopped, or replaced");
|
|
7015
7062
|
}
|
|
@@ -7449,6 +7496,8 @@ setupRetry = 0) {
|
|
|
7449
7496
|
catch (error) {
|
|
7450
7497
|
if (providerDispatched)
|
|
7451
7498
|
throw error;
|
|
7499
|
+
if (error instanceof DirectTurnSetupCancelled)
|
|
7500
|
+
return false;
|
|
7452
7501
|
const isSpendCap = typeof error === "object" && error !== null && error.code === "spend_cap";
|
|
7453
7502
|
const isVariantError = typeof error === "object" && error !== null && error.code === "unsupported_model_variant";
|
|
7454
7503
|
if (!roomSpeaker && !isSpendCap && !isVariantError)
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
// bot is not working, and the fix is usually a setting only a person can
|
|
11
11
|
// change, so a retry cannot clear it. A routine failure already buzzed;
|
|
12
12
|
// this makes an interactive turn behave the same way.
|
|
13
|
+
// A routine parked behind a busy target earns one notice after half an
|
|
14
|
+
// hour: from outside the schedule looks broken, and only a person can
|
|
15
|
+
// decide whether the busy work or the routine matters more.
|
|
13
16
|
//
|
|
14
17
|
// Delivery is a separate concern. The harness emits a frame; whoever is
|
|
15
18
|
// listening decides what to do with it — desktop and paired-phone local
|
|
@@ -53,9 +56,11 @@ export function buildNotification(kind, bot, threadId, detail, extra) {
|
|
|
53
56
|
? `${who} needs your hands`
|
|
54
57
|
: kind === "routine-failed"
|
|
55
58
|
? `${who}'s routine failed`
|
|
56
|
-
: kind === "
|
|
57
|
-
? `${who}
|
|
58
|
-
:
|
|
59
|
+
: kind === "routine-deferred"
|
|
60
|
+
? `${who}'s routine is waiting`
|
|
61
|
+
: kind === "turn-failed"
|
|
62
|
+
? `${who} couldn't start`
|
|
63
|
+
: `${who} finished`;
|
|
59
64
|
// A "finished" with nothing to say is not worth a notification — the
|
|
60
65
|
// badge in the sidebar already carries that much.
|
|
61
66
|
if (kind === "done" && !body)
|
|
@@ -22,6 +22,9 @@ const ALL_DAYS = [0, 1, 2, 3, 4, 5, 6];
|
|
|
22
22
|
const CATCH_UP_MS = 12 * 60 * 60_000;
|
|
23
23
|
/** How long before a due routine the computer is asked to stay awake. */
|
|
24
24
|
const WAKE_HORIZON_MS = 60 * 60_000;
|
|
25
|
+
/** How long a run may sit deferred behind a busy target before the user
|
|
26
|
+
* hears about it once. Surfacing only; dispatch semantics stay unchanged. */
|
|
27
|
+
export const ROUTINE_DEFERRAL_NOTICE_MS = 30 * 60_000;
|
|
25
28
|
const MAX_DATE_MS = 8_640_000_000_000_000;
|
|
26
29
|
const LOCAL_DAY_MS = 24 * 60 * 60_000;
|
|
27
30
|
const INTERVAL_RESTRICTION_SEARCH_MS = 9 * LOCAL_DAY_MS;
|
|
@@ -1168,8 +1171,23 @@ export class RoutineManager {
|
|
|
1168
1171
|
}
|
|
1169
1172
|
}
|
|
1170
1173
|
const state = this.targetState(run);
|
|
1171
|
-
if (state === "busy")
|
|
1174
|
+
if (state === "busy") {
|
|
1175
|
+
// A queued run behind a busy target is deferred, not silent. Stamp
|
|
1176
|
+
// the wait once so receipts and cards can say how long it has been
|
|
1177
|
+
// held; the run still dispatches the moment the target frees.
|
|
1178
|
+
if (run.deferredAt == null) {
|
|
1179
|
+
run.deferredAt = now;
|
|
1180
|
+
this.save();
|
|
1181
|
+
this.emitRun(run);
|
|
1182
|
+
}
|
|
1183
|
+
if (run.deferredNoticeAt == null && now - run.deferredAt >= ROUTINE_DEFERRAL_NOTICE_MS) {
|
|
1184
|
+
run.deferredNoticeAt = now;
|
|
1185
|
+
this.save();
|
|
1186
|
+
this.emitRun(run);
|
|
1187
|
+
this.options.onRunDeferred?.(cloneRun(run));
|
|
1188
|
+
}
|
|
1172
1189
|
continue;
|
|
1190
|
+
}
|
|
1173
1191
|
if (state === "missing") {
|
|
1174
1192
|
this.failRun(run, this.missingTargetMessage(run.target));
|
|
1175
1193
|
continue;
|
|
@@ -6,11 +6,16 @@ import { relative, resolve, sep } from "node:path";
|
|
|
6
6
|
* are not a sandbox for arbitrary shell commands. */
|
|
7
7
|
export class TurnResources {
|
|
8
8
|
owners = new Map();
|
|
9
|
-
|
|
9
|
+
blocker(resource, owner) {
|
|
10
10
|
for (const [key, current] of this.owners) {
|
|
11
11
|
if (overlaps(key, resource) && !sameOwner(current, owner))
|
|
12
|
-
return
|
|
12
|
+
return current;
|
|
13
13
|
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
claim(resource, owner) {
|
|
17
|
+
if (this.blocker(resource, owner))
|
|
18
|
+
return false;
|
|
14
19
|
this.owners.set(resource, owner);
|
|
15
20
|
return true;
|
|
16
21
|
}
|