openmausbot 0.1.82 → 0.1.83
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-BLLsDK2F.js → index-DSvqfBOf.js} +71 -71
- package/dist/assets/{index-5HVwp5m2.js → index-nqTFZTxg.js} +1 -1
- package/dist/index.html +1 -1
- package/dist-server/container-mcp.js +20 -4
- package/dist-server/drivers/agents-proxy.js +4 -4
- package/dist-server/index.js +1063 -493
- package/dist-server/mcp-gate.js +2 -1
- package/dist-server/openmausbot.js +187 -55
- package/dist-server/pair-cli.js +187 -55
- package/dist-server/server/auto-approve.js +15 -1
- package/dist-server/server/auto-vm-claims.js +22 -0
- package/dist-server/server/browser-bundle-release.js +8 -7
- package/dist-server/server/browser-engine.js +1 -1
- package/dist-server/server/channel-queue.js +58 -0
- package/dist-server/server/chief-of-staff.js +2 -2
- package/dist-server/server/config.js +33 -2
- package/dist-server/server/contracts.js +0 -7
- package/dist-server/server/drivers/acp/gemini.js +11 -1
- package/dist-server/server/drivers/acp/qwen.js +16 -1
- package/dist-server/server/drivers/agents-proxy.js +4 -4
- package/dist-server/server/drivers/claude.js +4 -3
- package/dist-server/server/drivers/codex.js +66 -1
- package/dist-server/server/drivers/pi.js +2 -1
- package/dist-server/server/env-path.js +6 -1
- package/dist-server/server/index.js +534 -84
- package/dist-server/server/local-routing.js +10 -4
- package/dist-server/server/peer-roster.js +37 -1
- package/dist-server/server/recent-work.js +4 -1
- package/dist-server/server/redact.js +5 -48
- package/dist-server/server/room-handoffs.js +79 -16
- package/dist-server/server/skill-fetch.js +1 -1
- package/dist-server/server/steer-queue.js +29 -0
- package/dist-server/server/store.js +26 -3
- package/dist-server/server/thread-retention.js +50 -0
- package/dist-server/server/turn-context.js +7 -0
- package/dist-server/server/turn-resources.js +6 -0
- package/dist-server/shared/approval-mode.js +8 -5
- package/dist-server/shared/inspector.js +1 -0
- package/dist-server/shared/json.js +1 -0
- package/dist-server/shared/notification.js +1 -0
- package/dist-server/shared/redact.js +61 -0
- package/dist-server/shared/routines.js +1 -0
- package/dist-server/shared/runtime-events.js +1 -0
- package/dist-server/shared/webhooks.js +1 -0
- package/dist-server/shared/wire.js +8 -0
- package/dist-server/vps-container-mcp.js +20 -4
- package/package.json +1 -1
package/dist-server/pair-cli.js
CHANGED
|
@@ -17568,15 +17568,23 @@ var init_image_generation = __esm({
|
|
|
17568
17568
|
}
|
|
17569
17569
|
});
|
|
17570
17570
|
|
|
17571
|
+
// shared/wire.ts
|
|
17572
|
+
var EFFORT_LEVELS;
|
|
17573
|
+
var init_wire = __esm({
|
|
17574
|
+
"shared/wire.ts"() {
|
|
17575
|
+
"use strict";
|
|
17576
|
+
EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
|
|
17577
|
+
}
|
|
17578
|
+
});
|
|
17579
|
+
|
|
17571
17580
|
// server/contracts.ts
|
|
17572
17581
|
function isModelVariant(value) {
|
|
17573
17582
|
return typeof value === "string" && value.length > 0 && value.length <= 256 && value.trim() === value && !new RegExp("\\p{Cc}", "u").test(value);
|
|
17574
17583
|
}
|
|
17575
|
-
var
|
|
17584
|
+
var eventCounter, newEventId, newId;
|
|
17576
17585
|
var init_contracts = __esm({
|
|
17577
17586
|
"server/contracts.ts"() {
|
|
17578
17587
|
"use strict";
|
|
17579
|
-
EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
|
|
17580
17588
|
eventCounter = 0;
|
|
17581
17589
|
newEventId = () => `ev-${Date.now().toString(36)}-${(eventCounter++).toString(36)}`;
|
|
17582
17590
|
newId = () => crypto.randomUUID();
|
|
@@ -17937,13 +17945,14 @@ function instanceConfigs(cfg) {
|
|
|
17937
17945
|
}
|
|
17938
17946
|
return map3;
|
|
17939
17947
|
}
|
|
17940
|
-
var optionalText, SSH_ALIAS, LEGACY_BROWSER_PROFILE_ID, BROWSER_PROFILE_ID, MIN_ROOM_TURN_TIMEOUT_MINUTES, MAX_ROOM_TURN_TIMEOUT_MINUTES, MAX_CONCURRENT_BOT_THREADS, MIN_THREAD_EVENT_LOG_BYTES, MAX_THREAD_EVENT_LOG_BYTES, MIN_LOCAL_VM_MAX_INSTANCES, MAX_LOCAL_VM_MAX_INSTANCES, vpsConfigSchema, roomConfigSchema, localVmConfigSchema, browserProfileSchema, legacyBrowserProfileSchema, legacyBrowserProfilesSchema, storedBrowserProfilesSchema, browserProfilesSchema, featureConfigSchema, onboardingConfigSchema, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
|
|
17948
|
+
var optionalText, SSH_ALIAS, LEGACY_BROWSER_PROFILE_ID, BROWSER_PROFILE_ID, MIN_ROOM_TURN_TIMEOUT_MINUTES, MAX_ROOM_TURN_TIMEOUT_MINUTES, DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES, DEFAULT_ROOM_HANDOFF_MIN_RUNWAY_MINUTES, DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES, MAX_CONCURRENT_BOT_THREADS, MIN_THREAD_EVENT_LOG_BYTES, MAX_THREAD_EVENT_LOG_BYTES, MIN_LOCAL_VM_MAX_INSTANCES, MAX_LOCAL_VM_MAX_INSTANCES, vpsConfigSchema, roomConfigSchema, localVmConfigSchema, browserProfileSchema, legacyBrowserProfileSchema, legacyBrowserProfilesSchema, storedBrowserProfilesSchema, browserProfilesSchema, featureConfigSchema, onboardingConfigSchema, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
|
|
17941
17949
|
var init_config = __esm({
|
|
17942
17950
|
"server/config.ts"() {
|
|
17943
17951
|
"use strict";
|
|
17944
17952
|
init_zod();
|
|
17945
17953
|
init_image_generation();
|
|
17946
17954
|
init_atomic();
|
|
17955
|
+
init_wire();
|
|
17947
17956
|
init_contracts();
|
|
17948
17957
|
init_mcp_registry();
|
|
17949
17958
|
init_schema();
|
|
@@ -17953,6 +17962,9 @@ var init_config = __esm({
|
|
|
17953
17962
|
BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
|
|
17954
17963
|
MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
|
|
17955
17964
|
MAX_ROOM_TURN_TIMEOUT_MINUTES = 1440;
|
|
17965
|
+
DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES = 30;
|
|
17966
|
+
DEFAULT_ROOM_HANDOFF_MIN_RUNWAY_MINUTES = 10;
|
|
17967
|
+
DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES = 240;
|
|
17956
17968
|
MAX_CONCURRENT_BOT_THREADS = 10;
|
|
17957
17969
|
MIN_THREAD_EVENT_LOG_BYTES = 256 * 1024;
|
|
17958
17970
|
MAX_THREAD_EVENT_LOG_BYTES = 4 * 1024 * 1024 * 1024;
|
|
@@ -17964,8 +17976,16 @@ var init_config = __esm({
|
|
|
17964
17976
|
}).optional()
|
|
17965
17977
|
});
|
|
17966
17978
|
roomConfigSchema = external_exports.object({
|
|
17967
|
-
turnTimeoutMinutes: external_exports.number().int().min(MIN_ROOM_TURN_TIMEOUT_MINUTES).max(MAX_ROOM_TURN_TIMEOUT_MINUTES)
|
|
17968
|
-
|
|
17979
|
+
turnTimeoutMinutes: external_exports.number().int().min(MIN_ROOM_TURN_TIMEOUT_MINUTES).max(MAX_ROOM_TURN_TIMEOUT_MINUTES),
|
|
17980
|
+
/** Room handoff tree lifetime. Active execution pauses this clock; the
|
|
17981
|
+
* hard cap is wall-clock and bounds trees that never stop executing. */
|
|
17982
|
+
handoffLifetimeMinutes: external_exports.number().int().min(1).max(MAX_ROOM_TURN_TIMEOUT_MINUTES).optional(),
|
|
17983
|
+
handoffMinRunwayMinutes: external_exports.number().int().min(1).max(MAX_ROOM_TURN_TIMEOUT_MINUTES).optional(),
|
|
17984
|
+
handoffHardCapMinutes: external_exports.number().int().min(1).max(7 * MAX_ROOM_TURN_TIMEOUT_MINUTES).optional()
|
|
17985
|
+
}).refine(
|
|
17986
|
+
(rooms) => (rooms.handoffMinRunwayMinutes ?? DEFAULT_ROOM_HANDOFF_MIN_RUNWAY_MINUTES) <= (rooms.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) && (rooms.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) <= (rooms.handoffHardCapMinutes ?? DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES),
|
|
17987
|
+
{ message: "rooms handoff bounds must satisfy handoffMinRunwayMinutes <= handoffLifetimeMinutes <= handoffHardCapMinutes" }
|
|
17988
|
+
);
|
|
17969
17989
|
localVmConfigSchema = external_exports.object({
|
|
17970
17990
|
mode: external_exports.enum(["shared", "per-bot"]).optional(),
|
|
17971
17991
|
maxInstances: external_exports.number().int().min(MIN_LOCAL_VM_MAX_INSTANCES).max(MAX_LOCAL_VM_MAX_INSTANCES).optional()
|
|
@@ -18137,7 +18157,10 @@ var init_config = __esm({
|
|
|
18137
18157
|
maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
|
|
18138
18158
|
/** Cap each per-thread events/ and native/ NDJSON log at this many
|
|
18139
18159
|
* bytes; absent (the default) keeps today's unbounded growth (#1280). */
|
|
18140
|
-
eventLogMaxBytes: external_exports.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional()
|
|
18160
|
+
eventLogMaxBytes: external_exports.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional(),
|
|
18161
|
+
/** Days a closed or archived thread's event logs survive (#1280).
|
|
18162
|
+
* Absent keeps them forever. */
|
|
18163
|
+
eventLogRetentionDays: external_exports.number().int().min(1).max(3650).optional()
|
|
18141
18164
|
}).strict().optional(),
|
|
18142
18165
|
localVm: localVmConfigSchema.optional(),
|
|
18143
18166
|
features: featureConfigSchema.optional(),
|
|
@@ -18246,7 +18269,7 @@ function ownerPredatesThisBoot(owner) {
|
|
|
18246
18269
|
return false;
|
|
18247
18270
|
}
|
|
18248
18271
|
function ownerIsAlive(owner) {
|
|
18249
|
-
return !ownerPredatesThisBoot(owner) && processIsAlive2(owner.pid);
|
|
18272
|
+
return !ownerPredatesThisBoot(owner) && processIsAlive2(owner.pid) && ownerIdentityMatches(owner);
|
|
18250
18273
|
}
|
|
18251
18274
|
function isLeaseOwner(value) {
|
|
18252
18275
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
@@ -18296,6 +18319,51 @@ function processIsAlive2(pid) {
|
|
|
18296
18319
|
throw leaseError("OpenMausBot could not verify the data-directory lease owner; refusing to start.", error51);
|
|
18297
18320
|
}
|
|
18298
18321
|
}
|
|
18322
|
+
function parseWmiDateTime(value) {
|
|
18323
|
+
const raw = String(value).trim();
|
|
18324
|
+
const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{6})([+-]\d{3})$/.exec(raw);
|
|
18325
|
+
if (!match) return null;
|
|
18326
|
+
const [_, year, month, day, hour, minute, second, micro, offset] = match;
|
|
18327
|
+
const ms = Date.UTC(
|
|
18328
|
+
Number(year),
|
|
18329
|
+
Number(month) - 1,
|
|
18330
|
+
Number(day),
|
|
18331
|
+
Number(hour),
|
|
18332
|
+
Number(minute),
|
|
18333
|
+
Number(second),
|
|
18334
|
+
Math.floor(Number(micro) / 1e3)
|
|
18335
|
+
);
|
|
18336
|
+
const offsetMinutes = Number(offset);
|
|
18337
|
+
return ms - offsetMinutes * 60 * 1e3;
|
|
18338
|
+
}
|
|
18339
|
+
function processCreationTimeMs(pid) {
|
|
18340
|
+
if (process.platform !== "win32") return null;
|
|
18341
|
+
try {
|
|
18342
|
+
const output = execFileSync2(
|
|
18343
|
+
"wmic",
|
|
18344
|
+
["process", "where", `ProcessId=${pid}`, "get", "CreationDate", "/format:csv"],
|
|
18345
|
+
{ encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
|
|
18346
|
+
);
|
|
18347
|
+
for (const line of output.split(/\r?\n/)) {
|
|
18348
|
+
const trimmed = line.trim();
|
|
18349
|
+
if (!trimmed) continue;
|
|
18350
|
+
const lower = trimmed.toLowerCase();
|
|
18351
|
+
if (lower === "node,creationdate" || lower.startsWith("node,")) continue;
|
|
18352
|
+
const parts = trimmed.split(",");
|
|
18353
|
+
const raw = parts[parts.length - 1].trim();
|
|
18354
|
+
const parsed = parseWmiDateTime(raw);
|
|
18355
|
+
if (parsed !== null) return parsed;
|
|
18356
|
+
}
|
|
18357
|
+
} catch {
|
|
18358
|
+
}
|
|
18359
|
+
return null;
|
|
18360
|
+
}
|
|
18361
|
+
function ownerIdentityMatches(owner) {
|
|
18362
|
+
if (process.platform !== "win32") return true;
|
|
18363
|
+
const created = processCreationTimeMs(owner.pid);
|
|
18364
|
+
if (created === null) return true;
|
|
18365
|
+
return created < owner.createdAt;
|
|
18366
|
+
}
|
|
18299
18367
|
function unlinkExact(path3, message2) {
|
|
18300
18368
|
try {
|
|
18301
18369
|
unlinkSync2(path3);
|
|
@@ -18701,7 +18769,8 @@ function parseCmdShim(shim, env) {
|
|
|
18701
18769
|
return null;
|
|
18702
18770
|
}
|
|
18703
18771
|
const dir = dirname4(shim);
|
|
18704
|
-
const
|
|
18772
|
+
const npmEntry = /^SET "NP[MX]_CLI_JS=%~dp0\\([^"]+)"/im.exec(text4);
|
|
18773
|
+
const targets = [...npmEntry ? [npmEntry] : [], ...text4.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m) => join8(dir, m[1])).filter((p) => isFile(p) && basename2(p).toLowerCase() !== "node.exe");
|
|
18705
18774
|
const script = targets.find((p) => /\.[cm]?js$/i.test(p));
|
|
18706
18775
|
if (script) {
|
|
18707
18776
|
const node = nodeExe(dir, env);
|
|
@@ -19574,9 +19643,9 @@ var init_local_inject = __esm({
|
|
|
19574
19643
|
// shared/approval-mode.ts
|
|
19575
19644
|
function supportsApprovalMode(driverKind, mode) {
|
|
19576
19645
|
if (mode === "custom") return driverKind === "codex";
|
|
19577
|
-
if (mode === "edits") return ["claudeAgent", "grokAgent", "antigravityAgent"].includes(driverKind ?? "");
|
|
19646
|
+
if (mode === "edits") return ["claudeAgent", "grokAgent", "antigravityAgent", "qwenAgent", "geminiAgent"].includes(driverKind ?? "");
|
|
19578
19647
|
if (mode !== "full") return true;
|
|
19579
|
-
return ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo"].includes(driverKind ?? "");
|
|
19648
|
+
return ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo", "qwenAgent", "geminiAgent"].includes(driverKind ?? "");
|
|
19580
19649
|
}
|
|
19581
19650
|
var init_approval_mode = __esm({
|
|
19582
19651
|
"shared/approval-mode.ts"() {
|
|
@@ -19584,12 +19653,7 @@ var init_approval_mode = __esm({
|
|
|
19584
19653
|
}
|
|
19585
19654
|
});
|
|
19586
19655
|
|
|
19587
|
-
//
|
|
19588
|
-
function isSecretName(name) {
|
|
19589
|
-
const lower = name.toLowerCase();
|
|
19590
|
-
if (SECRET_KEY_PARTS.some((part) => lower.includes(part))) return true;
|
|
19591
|
-
return /(^|[_.-])keys?$/.test(lower);
|
|
19592
|
-
}
|
|
19656
|
+
// shared/redact.ts
|
|
19593
19657
|
function redactSecretsInText(text4) {
|
|
19594
19658
|
if (!text4 || text4.length < 8) return text4;
|
|
19595
19659
|
let out = text4;
|
|
@@ -19604,33 +19668,10 @@ ${close}`);
|
|
|
19604
19668
|
out = out.replace(URL_USERINFO, (_m, lead, secret, at) => `${lead}${mask(secret)}${at}`);
|
|
19605
19669
|
return out;
|
|
19606
19670
|
}
|
|
19607
|
-
|
|
19608
|
-
if (typeof input === "string") return redactSecretsInText(input);
|
|
19609
|
-
if (depth > 12 || input === null || typeof input !== "object") return input;
|
|
19610
|
-
if (Array.isArray(input)) {
|
|
19611
|
-
return input.map((item) => {
|
|
19612
|
-
if (item !== null && typeof item === "object" && !Array.isArray(item) && typeof item.name === "string" && typeof item.value === "string") {
|
|
19613
|
-
const entry = item;
|
|
19614
|
-
return isSecretName(entry.name) ? { ...entry, value: mask(entry.value) } : { ...entry, value: redactSecretsInText(entry.value) };
|
|
19615
|
-
}
|
|
19616
|
-
return redactSecrets(item, depth + 1);
|
|
19617
|
-
});
|
|
19618
|
-
}
|
|
19619
|
-
const out = {};
|
|
19620
|
-
for (const [key, value] of Object.entries(input)) {
|
|
19621
|
-
if (typeof value === "string" && isSecretName(key)) {
|
|
19622
|
-
out[key] = mask(value);
|
|
19623
|
-
continue;
|
|
19624
|
-
}
|
|
19625
|
-
out[key] = redactSecrets(value, depth + 1);
|
|
19626
|
-
}
|
|
19627
|
-
return out;
|
|
19628
|
-
}
|
|
19629
|
-
var SECRET_KEY_PARTS, REDACTION_MARKER, mask, KEY_PREFIXES, BEARER, PEM_BLOCK, KEY_VALUE, KEY_SUFFIX_ASSIGNMENT, SECRET_FLAG, URL_USERINFO;
|
|
19671
|
+
var REDACTION_MARKER, mask, KEY_PREFIXES, BEARER, PEM_BLOCK, KEY_VALUE, KEY_SUFFIX_ASSIGNMENT, SECRET_FLAG, URL_USERINFO;
|
|
19630
19672
|
var init_redact = __esm({
|
|
19631
|
-
"
|
|
19673
|
+
"shared/redact.ts"() {
|
|
19632
19674
|
"use strict";
|
|
19633
|
-
SECRET_KEY_PARTS = ["token", "secret", "password", "passwd", "apikey", "api_key", "authorization", "auth_token"];
|
|
19634
19675
|
REDACTION_MARKER = /^«redacted \d+ chars»$/;
|
|
19635
19676
|
mask = (value) => REDACTION_MARKER.test(value) ? value : `\xABredacted ${value.length} chars\xBB`;
|
|
19636
19677
|
KEY_PREFIXES = [
|
|
@@ -19668,6 +19709,44 @@ var init_redact = __esm({
|
|
|
19668
19709
|
}
|
|
19669
19710
|
});
|
|
19670
19711
|
|
|
19712
|
+
// server/redact.ts
|
|
19713
|
+
function isSecretName(name) {
|
|
19714
|
+
const lower = name.toLowerCase();
|
|
19715
|
+
if (SECRET_KEY_PARTS.some((part) => lower.includes(part))) return true;
|
|
19716
|
+
return /(^|[_.-])keys?$/.test(lower);
|
|
19717
|
+
}
|
|
19718
|
+
function redactSecrets(input, depth = 0) {
|
|
19719
|
+
if (typeof input === "string") return redactSecretsInText(input);
|
|
19720
|
+
if (depth > 12 || input === null || typeof input !== "object") return input;
|
|
19721
|
+
if (Array.isArray(input)) {
|
|
19722
|
+
return input.map((item) => {
|
|
19723
|
+
if (item !== null && typeof item === "object" && !Array.isArray(item) && typeof item.name === "string" && typeof item.value === "string") {
|
|
19724
|
+
const entry = item;
|
|
19725
|
+
return isSecretName(entry.name) ? { ...entry, value: mask(entry.value) } : { ...entry, value: redactSecretsInText(entry.value) };
|
|
19726
|
+
}
|
|
19727
|
+
return redactSecrets(item, depth + 1);
|
|
19728
|
+
});
|
|
19729
|
+
}
|
|
19730
|
+
const out = {};
|
|
19731
|
+
for (const [key, value] of Object.entries(input)) {
|
|
19732
|
+
if (typeof value === "string" && isSecretName(key)) {
|
|
19733
|
+
out[key] = mask(value);
|
|
19734
|
+
continue;
|
|
19735
|
+
}
|
|
19736
|
+
out[key] = redactSecrets(value, depth + 1);
|
|
19737
|
+
}
|
|
19738
|
+
return out;
|
|
19739
|
+
}
|
|
19740
|
+
var SECRET_KEY_PARTS;
|
|
19741
|
+
var init_redact2 = __esm({
|
|
19742
|
+
"server/redact.ts"() {
|
|
19743
|
+
"use strict";
|
|
19744
|
+
init_redact();
|
|
19745
|
+
init_redact();
|
|
19746
|
+
SECRET_KEY_PARTS = ["token", "secret", "password", "passwd", "apikey", "api_key", "authorization", "auth_token"];
|
|
19747
|
+
}
|
|
19748
|
+
});
|
|
19749
|
+
|
|
19671
19750
|
// server/thread-log-rotation.ts
|
|
19672
19751
|
import { closeSync as closeSync4, fstatSync, openSync as openSync4, readSync as readSync2, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync3, writeSync } from "node:fs";
|
|
19673
19752
|
function currentThreadLogCap() {
|
|
@@ -19753,7 +19832,7 @@ var init_native = __esm({
|
|
|
19753
19832
|
"server/drivers/native.ts"() {
|
|
19754
19833
|
"use strict";
|
|
19755
19834
|
init_config();
|
|
19756
|
-
|
|
19835
|
+
init_redact2();
|
|
19757
19836
|
init_thread_log_rotation();
|
|
19758
19837
|
}
|
|
19759
19838
|
});
|
|
@@ -19816,7 +19895,7 @@ var QUESTION_LIMIT, LIMIT, cut;
|
|
|
19816
19895
|
var init_tool_summary = __esm({
|
|
19817
19896
|
"server/tool-summary.ts"() {
|
|
19818
19897
|
"use strict";
|
|
19819
|
-
|
|
19898
|
+
init_redact2();
|
|
19820
19899
|
QUESTION_LIMIT = 300;
|
|
19821
19900
|
LIMIT = 200;
|
|
19822
19901
|
cut = (text4, limit) => redactSecretsInText(text4).trim().slice(0, limit);
|
|
@@ -23968,8 +24047,8 @@ var init_claude = __esm({
|
|
|
23968
24047
|
};
|
|
23969
24048
|
const steer = async (threadId, text4) => {
|
|
23970
24049
|
const s = sessions.get(threadId);
|
|
23971
|
-
if (!s || !s.turn || s.turn.settled || s.closing || s.child.exitCode !== null) return
|
|
23972
|
-
return writeUser(s, threadId, claudeUserMessage(text4, void 0));
|
|
24050
|
+
if (!s || !s.turn || s.turn.settled || s.closing || s.child.exitCode !== null) return "refused";
|
|
24051
|
+
return await writeUser(s, threadId, claudeUserMessage(text4, void 0)) ? "steered" : "refused";
|
|
23973
24052
|
};
|
|
23974
24053
|
const login = new ClaudeLoginController({ cli: config2.cli, environment, onAuthenticated: async () => {
|
|
23975
24054
|
await refreshModels();
|
|
@@ -25482,9 +25561,23 @@ var init_codex = __esm({
|
|
|
25482
25561
|
return stopped;
|
|
25483
25562
|
});
|
|
25484
25563
|
let completeStoppedTurn;
|
|
25564
|
+
let interruptRequested = false;
|
|
25485
25565
|
const stop = async () => {
|
|
25486
25566
|
stopRequested = true;
|
|
25487
25567
|
stopSignal.abort();
|
|
25568
|
+
if (!state.settled && !interruptRequested && codexThreadId && codexTurnId && child.exitCode === null && child.signalCode === null) {
|
|
25569
|
+
interruptRequested = true;
|
|
25570
|
+
const graceMs = Math.max(1, Number(process.env.FAKE_CODEX_INTERRUPT_GRACE_MS ?? 750) || 750);
|
|
25571
|
+
try {
|
|
25572
|
+
await request("turn/interrupt", { threadId: codexThreadId, turnId: codexTurnId }, graceMs);
|
|
25573
|
+
} catch {
|
|
25574
|
+
}
|
|
25575
|
+
const deadline = Date.now() + graceMs;
|
|
25576
|
+
while (!state.settled && Date.now() < deadline) {
|
|
25577
|
+
await new Promise((wake) => setTimeout(wake, 15));
|
|
25578
|
+
}
|
|
25579
|
+
if (state.settled) return true;
|
|
25580
|
+
}
|
|
25488
25581
|
const stopped = await terminate();
|
|
25489
25582
|
if (stopped) completeStoppedTurn?.();
|
|
25490
25583
|
return stopped;
|
|
@@ -25505,6 +25598,23 @@ var init_codex = __esm({
|
|
|
25505
25598
|
emit({ ...base2(threadId, turnId), type: "runtime.error", message: "codex did not shut down after termination was requested" });
|
|
25506
25599
|
}
|
|
25507
25600
|
};
|
|
25601
|
+
const steerActiveTurn = async (text4) => {
|
|
25602
|
+
if (state.settled || abandoned || stopRequested || !codexThreadId || !codexTurnId) return "refused";
|
|
25603
|
+
if (child.exitCode !== null || child.signalCode !== null) return "refused";
|
|
25604
|
+
try {
|
|
25605
|
+
const steerTimeoutMs = Math.max(1, Number(process.env.FAKE_CODEX_STEER_TIMEOUT_MS ?? 1e4) || 1e4);
|
|
25606
|
+
await request("turn/steer", {
|
|
25607
|
+
threadId: codexThreadId,
|
|
25608
|
+
input: [{ type: "text", text: text4 }],
|
|
25609
|
+
expectedTurnId: codexTurnId
|
|
25610
|
+
}, steerTimeoutMs);
|
|
25611
|
+
return "steered";
|
|
25612
|
+
} catch (error51) {
|
|
25613
|
+
const message2 = error51 instanceof Error ? error51.message : String(error51);
|
|
25614
|
+
if (state.settled || message2.includes("timed out")) return "indeterminate";
|
|
25615
|
+
return "refused";
|
|
25616
|
+
}
|
|
25617
|
+
};
|
|
25508
25618
|
const controlsHost = turn.integrations?.localComputer?.scope === "local-computer";
|
|
25509
25619
|
const handleServerRequest = (msg) => {
|
|
25510
25620
|
const method = msg.method;
|
|
@@ -25794,6 +25904,10 @@ var init_codex = __esm({
|
|
|
25794
25904
|
void stop();
|
|
25795
25905
|
return;
|
|
25796
25906
|
}
|
|
25907
|
+
if (stopRequested) {
|
|
25908
|
+
void settle(false, "interrupted");
|
|
25909
|
+
return;
|
|
25910
|
+
}
|
|
25797
25911
|
const recentStderr = stderrSinceOutput.trim();
|
|
25798
25912
|
const hadStreamedOutput = codexTurnId !== null || state.sawStreamDelta;
|
|
25799
25913
|
const verdict = signal !== null ? { transient: false, reason: "interrupted" } : classifyError({ exitCode: code, stderr: recentStderr });
|
|
@@ -25832,7 +25946,7 @@ var init_codex = __esm({
|
|
|
25832
25946
|
});
|
|
25833
25947
|
void settle(false, "exit_before_result");
|
|
25834
25948
|
});
|
|
25835
|
-
active.set(threadId, { stop, turnId, asks });
|
|
25949
|
+
active.set(threadId, { stop, turnId, asks, steer: steerActiveTurn });
|
|
25836
25950
|
if (attempt === 0) emit({ ...base2(threadId, turnId), type: "turn.started" });
|
|
25837
25951
|
try {
|
|
25838
25952
|
await request("initialize", {
|
|
@@ -26041,6 +26155,7 @@ ${stderr ?? ""}`))
|
|
|
26041
26155
|
provider: DRIVER_KIND3,
|
|
26042
26156
|
capabilities: {
|
|
26043
26157
|
sessionModelSwitch: "unsupported",
|
|
26158
|
+
queueing: true,
|
|
26044
26159
|
computerMcp: true,
|
|
26045
26160
|
localComputerMcp: true,
|
|
26046
26161
|
composioMcp: true,
|
|
@@ -26056,6 +26171,10 @@ ${stderr ?? ""}`))
|
|
|
26056
26171
|
interruptTurn: async (threadId) => {
|
|
26057
26172
|
await active.get(threadId)?.stop();
|
|
26058
26173
|
},
|
|
26174
|
+
steer: async (threadId, text4) => {
|
|
26175
|
+
const turn = active.get(threadId);
|
|
26176
|
+
return turn?.steer ? await turn.steer(text4) : "refused";
|
|
26177
|
+
},
|
|
26059
26178
|
respondToRequest: async (threadId, requestId, decision) => {
|
|
26060
26179
|
const turn = active.get(threadId);
|
|
26061
26180
|
const finish = turn?.asks.get(requestId);
|
|
@@ -34674,7 +34793,7 @@ var init_openai_chat = __esm({
|
|
|
34674
34793
|
"server/drivers/openai-chat.ts"() {
|
|
34675
34794
|
"use strict";
|
|
34676
34795
|
init_contracts();
|
|
34677
|
-
|
|
34796
|
+
init_redact2();
|
|
34678
34797
|
init_tool_summary();
|
|
34679
34798
|
init_chat_mcp_tools();
|
|
34680
34799
|
init_chat_tool_approval();
|
|
@@ -35013,6 +35132,11 @@ function liveOauthCredential() {
|
|
|
35013
35132
|
function geminiIsAuthenticated(env) {
|
|
35014
35133
|
return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync12(join24(homedir15(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
|
|
35015
35134
|
}
|
|
35135
|
+
function geminiApprovalArgs(fullAuto, approvalMode) {
|
|
35136
|
+
if (fullAuto) return ["--yolo"];
|
|
35137
|
+
if (approvalMode === "edits") return ["--approval-mode", "auto_edit"];
|
|
35138
|
+
return [];
|
|
35139
|
+
}
|
|
35016
35140
|
var AUTH_PREFERENCE, nonBlank, support3, GeminiAgentDriver;
|
|
35017
35141
|
var init_gemini = __esm({
|
|
35018
35142
|
"server/drivers/acp/gemini.ts"() {
|
|
@@ -35035,7 +35159,7 @@ var init_gemini = __esm({
|
|
|
35035
35159
|
loginNote: "Gemini CLI needs a GEMINI_API_KEY, a Vertex AI setup, or an enterprise Code Assist login \u2014 consumer Google logins stopped working on 2026-06-18 (use the Antigravity engine for those accounts)",
|
|
35036
35160
|
// --acp is the stable Gemini CLI surface. --experimental-acp remains an
|
|
35037
35161
|
// alias for older releases, but using it now emits a deprecation warning.
|
|
35038
|
-
spawnArgs: (
|
|
35162
|
+
spawnArgs: (config2, turn) => ["--acp", ...geminiApprovalArgs(config2.fullAuto, turn.approvalMode), ...turn.model ? ["-m", turn.model] : []],
|
|
35039
35163
|
credentialEnv: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
|
|
35040
35164
|
pickAuthMethod: (methods) => {
|
|
35041
35165
|
const ids = methods.map((m) => m.id).filter((id) => typeof id === "string");
|
|
@@ -36468,6 +36592,12 @@ function resolveQwenTurnModel(model, env) {
|
|
|
36468
36592
|
if (!matches.length) throw new Error("This Qwen model is no longer configured. Refresh models and select it again.");
|
|
36469
36593
|
return matches[0].id;
|
|
36470
36594
|
}
|
|
36595
|
+
function qwenApprovalArgs(fullAuto, approvalMode) {
|
|
36596
|
+
if (fullAuto) return ["--yolo"];
|
|
36597
|
+
if (approvalMode === "auto") return ["--approval-mode", "auto"];
|
|
36598
|
+
if (approvalMode === "edits") return ["--approval-mode", "auto-edit"];
|
|
36599
|
+
return [];
|
|
36600
|
+
}
|
|
36471
36601
|
var EMPTY, PROTOCOLS, support8, QwenAgentDriver;
|
|
36472
36602
|
var init_qwen = __esm({
|
|
36473
36603
|
"server/drivers/acp/qwen.ts"() {
|
|
@@ -36496,7 +36626,7 @@ var init_qwen = __esm({
|
|
|
36496
36626
|
},
|
|
36497
36627
|
// A raw -m only changes the model within the saved provider. ACP switches
|
|
36498
36628
|
// the complete route and confirms it before any prompt leaves OMB.
|
|
36499
|
-
spawnArgs: () => ["--acp"],
|
|
36629
|
+
spawnArgs: (config2, turn) => ["--acp", ...qwenApprovalArgs(config2.fullAuto, turn.approvalMode)],
|
|
36500
36630
|
selectModel: { configId: "model" },
|
|
36501
36631
|
pickAuthMethod: () => null,
|
|
36502
36632
|
authFailure: "continue",
|
|
@@ -44122,6 +44252,7 @@ var init_pi = __esm({
|
|
|
44122
44252
|
init_procs();
|
|
44123
44253
|
init_proxy_paths();
|
|
44124
44254
|
init_tool_summary();
|
|
44255
|
+
init_wire();
|
|
44125
44256
|
init_contracts();
|
|
44126
44257
|
init_local_inject();
|
|
44127
44258
|
init_native();
|
|
@@ -45205,12 +45336,13 @@ var init_browser_bundle_release = __esm({
|
|
|
45205
45336
|
"server/browser-bundle-release.ts"() {
|
|
45206
45337
|
"use strict";
|
|
45207
45338
|
init_browser_engine_release();
|
|
45208
|
-
CHROME_VERSION = "
|
|
45339
|
+
CHROME_VERSION = "153.0.8010.47";
|
|
45209
45340
|
CHROME_ASSETS = {
|
|
45210
|
-
"darwin-arm64": { platform: "mac-arm64", bytes:
|
|
45211
|
-
"darwin-x64": { platform: "mac-x64", bytes:
|
|
45212
|
-
"linux-
|
|
45213
|
-
"
|
|
45341
|
+
"darwin-arm64": { platform: "mac-arm64", bytes: 98668949, sha256: "6d28839675b6f22dbd7ba8775dbdabcae7a5be37b482380b27b12f05b748b955", executableSha256: "111ee425ea5b0a01dcb19fda16452a9be0319749cba374b1c987d2437d8dcedb" },
|
|
45342
|
+
"darwin-x64": { platform: "mac-x64", bytes: 103712919, sha256: "aa178547f9751fbcf413e0f57915ddb72e83064d4d1c4a29dc1719b169f9cc2f", executableSha256: "33452695b1b6f18db2f796a422eca69b69ca8aacce36d1ff8c6f5d69eac1a635" },
|
|
45343
|
+
"linux-arm64": { platform: "linux-arm64", bytes: 120245582, sha256: "af0931a58d6bab688112d5ca1f7abd6d95c0b8a296ef34637f272795787774d7", executableSha256: "b6a9f0483c976fddc72c34ce1c983a1275163cdfb95576874bfed5ff07c53807" },
|
|
45344
|
+
"linux-x64": { platform: "linux64", bytes: 119695587, sha256: "7728775cf4a35464cd81c8eea2d44d6d32ccc0bd1edfa75aea7f32d146963d63", executableSha256: "ea90dee9cbd7b17f197eb6c386bd9f1ff636734ee99753cd2408658c45358bf2" },
|
|
45345
|
+
"win32-x64": { platform: "win64", bytes: 120466147, sha256: "9f405cfaf7bc08bf9e046e653cd3086c0faa1d4e25907de857f7e7f093a20122", executableSha256: "01176f6928faeb37ff018bc8884bbca8a0197f51b659d467f2501ab2c64f9fcf" }
|
|
45214
45346
|
};
|
|
45215
45347
|
}
|
|
45216
45348
|
});
|
|
@@ -45592,7 +45724,7 @@ function agentBrowserIntegration(input) {
|
|
|
45592
45724
|
for (const name of ["PATH", "AGENT_BROWSER_EXECUTABLE_PATH"]) {
|
|
45593
45725
|
if (sourceEnv[name]) env[name] = sourceEnv[name];
|
|
45594
45726
|
}
|
|
45595
|
-
const bundle = packagedBrowser({ env: sourceEnv });
|
|
45727
|
+
const bundle = packagedBrowser({ env: sourceEnv, platform: input.platform, arch: input.arch });
|
|
45596
45728
|
if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve10(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync18)) {
|
|
45597
45729
|
env.AGENT_BROWSER_EXECUTABLE_PATH = bundle.chrome;
|
|
45598
45730
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// verdict the app synthesizes is Full access, because that level is the
|
|
8
8
|
// person's explicit, separately confirmed grant to answer every prompt.
|
|
9
9
|
// Questions never come through here: a bot's question always reaches a human.
|
|
10
|
+
import { supportsApprovalMode } from "../shared/approval-mode.js";
|
|
10
11
|
/** A failed delivery is a runtime error, not another permission decision.
|
|
11
12
|
* An expired ask must never become a fresh Allow/Deny card. */
|
|
12
13
|
export async function deliverFullAccessApproval(adapter, threadId, requestId, turnId, isCurrent = () => false) {
|
|
@@ -24,7 +25,9 @@ export async function deliverFullAccessApproval(adapter, threadId, requestId, tu
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
/** Full access is the person's explicit grant to this receiving bot, including
|
|
27
|
-
* delegated work. It never inherits the sender's mode or elevates another bot
|
|
28
|
+
* delegated work. It never inherits the sender's mode or elevates another bot
|
|
29
|
+
* — with the one exception below (delegationInheritsFullAccess), applied
|
|
30
|
+
* where a Chief's delegated thread is created rather than here.
|
|
28
31
|
* Custom is a provider-config choice rather than an app Full-access grant, so
|
|
29
32
|
* peer-started Custom turns use Auto. Provider support and grant confirmation
|
|
30
33
|
* are checked by the caller. */
|
|
@@ -33,6 +36,17 @@ export function approvalModeForOrigin(mode, origin) {
|
|
|
33
36
|
return "auto";
|
|
34
37
|
return mode;
|
|
35
38
|
}
|
|
39
|
+
/** Whether work a bot hands to a teammate runs with Full access. Only a Chief
|
|
40
|
+
* of Staff passes access on, and only the Full access the person gave it for
|
|
41
|
+
* the conversation it is delegating from: the Chief exists to get the team's
|
|
42
|
+
* work done without the person answering every card, and a teammate stopping
|
|
43
|
+
* that work to ask defeats the grant. The recipient's engine has to implement
|
|
44
|
+
* Full, or the work keeps the recipient's own level. A bot never elevates
|
|
45
|
+
* itself this way. */
|
|
46
|
+
export function delegationInheritsFullAccess(input) {
|
|
47
|
+
return input.senderIsChief && input.senderHasFullAccess && !input.sameBot
|
|
48
|
+
&& supportsApprovalMode(input.recipientDriverKind, "full");
|
|
49
|
+
}
|
|
36
50
|
// Tools that ask a PERSON something. A question exists so that a human
|
|
37
51
|
// decides; any mode answering one on their behalf defeats the only reason
|
|
38
52
|
// it was asked. They normally arrive typed as questions and never reach a
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Lazy exclusive claims for auto-resolved Local VM attaches (issue #1361).
|
|
2
|
+
* The table is keyed by thread and owned by the dispatch generation; the
|
|
3
|
+
* claim closure itself lives in index.ts next to the turn state it needs.
|
|
4
|
+
* Kept free of index.ts imports so the fire-once semantics stay unit-testable. */
|
|
5
|
+
/** Fire a thread's lazy claim exactly once, fenced by the dispatch
|
|
6
|
+
* generation. A rejected claim KEEPS the slot and marks it failed: the
|
|
7
|
+
* computer-control gate then keeps answering held for that generation, so
|
|
8
|
+
* the bridge refuses every later screen call instead of forwarding it onto
|
|
9
|
+
* a VM this turn never claimed. `begin` staying set means the claim never
|
|
10
|
+
* re-fires; turn settle (releaseTurnResources / releaseLocalVmThread in
|
|
11
|
+
* index.ts) clears the slot, fenced by owner generation. A rejection
|
|
12
|
+
* mutates only the slot it fired from, so a stale claim can never remove
|
|
13
|
+
* or mark a newer generation's slot on the same thread. */
|
|
14
|
+
export function startAutoVmClaim(table, threadId, generation) {
|
|
15
|
+
const slot = table.get(threadId);
|
|
16
|
+
if (!slot || slot.owner.threadId !== threadId || slot.owner.generation !== generation || slot.begin)
|
|
17
|
+
return;
|
|
18
|
+
slot.begin = slot.claim().then(() => { slot.claimed = true; }, (error) => {
|
|
19
|
+
slot.failed = true;
|
|
20
|
+
slot.failure = error instanceof Error ? error.message : String(error);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
// Reviewed vendor assets, downloaded and hashed on 2026-09-
|
|
1
|
+
// Reviewed vendor assets, downloaded and hashed on 2026-09-16. Update all
|
|
2
2
|
// pins together and run each platform's packaged, offline browser smoke test.
|
|
3
3
|
// This is Chromium's headless shell, not full Chrome (which includes Widevine).
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { agentBrowserReleaseUrl, agentBrowserReleaseVersion, resolveAgentBrowserReleaseAsset, } from "./browser-engine-release.js";
|
|
6
|
-
export const CHROME_VERSION = "
|
|
7
|
-
export const SUPPORTED_BROWSER_TARGETS = ["darwin-arm64", "darwin-x64", "linux-x64", "win32-x64"];
|
|
6
|
+
export const CHROME_VERSION = "153.0.8010.47";
|
|
7
|
+
export const SUPPORTED_BROWSER_TARGETS = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-x64"];
|
|
8
8
|
const CHROME_ASSETS = {
|
|
9
|
-
"darwin-arm64": { platform: "mac-arm64", bytes:
|
|
10
|
-
"darwin-x64": { platform: "mac-x64", bytes:
|
|
11
|
-
"linux-
|
|
12
|
-
"
|
|
9
|
+
"darwin-arm64": { platform: "mac-arm64", bytes: 98668949, sha256: "6d28839675b6f22dbd7ba8775dbdabcae7a5be37b482380b27b12f05b748b955", executableSha256: "111ee425ea5b0a01dcb19fda16452a9be0319749cba374b1c987d2437d8dcedb" },
|
|
10
|
+
"darwin-x64": { platform: "mac-x64", bytes: 103712919, sha256: "aa178547f9751fbcf413e0f57915ddb72e83064d4d1c4a29dc1719b169f9cc2f", executableSha256: "33452695b1b6f18db2f796a422eca69b69ca8aacce36d1ff8c6f5d69eac1a635" },
|
|
11
|
+
"linux-arm64": { platform: "linux-arm64", bytes: 120245582, sha256: "af0931a58d6bab688112d5ca1f7abd6d95c0b8a296ef34637f272795787774d7", executableSha256: "b6a9f0483c976fddc72c34ce1c983a1275163cdfb95576874bfed5ff07c53807" },
|
|
12
|
+
"linux-x64": { platform: "linux64", bytes: 119695587, sha256: "7728775cf4a35464cd81c8eea2d44d6d32ccc0bd1edfa75aea7f32d146963d63", executableSha256: "ea90dee9cbd7b17f197eb6c386bd9f1ff636734ee99753cd2408658c45358bf2" },
|
|
13
|
+
"win32-x64": { platform: "win64", bytes: 120466147, sha256: "9f405cfaf7bc08bf9e046e653cd3086c0faa1d4e25907de857f7e7f093a20122", executableSha256: "01176f6928faeb37ff018bc8884bbca8a0197f51b659d467f2501ab2c64f9fcf" },
|
|
13
14
|
};
|
|
14
15
|
export function browserBundleSpec(target) {
|
|
15
16
|
if (!Object.hasOwn(CHROME_ASSETS, target))
|
|
@@ -375,7 +375,7 @@ export function agentBrowserIntegration(input) {
|
|
|
375
375
|
if (sourceEnv[name])
|
|
376
376
|
env[name] = sourceEnv[name];
|
|
377
377
|
}
|
|
378
|
-
const bundle = packagedBrowser({ env: sourceEnv });
|
|
378
|
+
const bundle = packagedBrowser({ env: sourceEnv, platform: input.platform, arch: input.arch });
|
|
379
379
|
if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync)) {
|
|
380
380
|
env.AGENT_BROWSER_EXECUTABLE_PATH = bundle.chrome;
|
|
381
381
|
}
|
|
@@ -61,6 +61,64 @@ export function cancelChannelMessage(groupId, queueId) {
|
|
|
61
61
|
}
|
|
62
62
|
return false;
|
|
63
63
|
}
|
|
64
|
+
/** Atomically lift a channel thread's whole queue out for a live steer. The
|
|
65
|
+
* entry leaves first so a room that settles while the adapter is still
|
|
66
|
+
* thinking can never also drain the same words as a follow-up turn. Only
|
|
67
|
+
* the HEAD can be lifted: the success path steers and settles items[0], so
|
|
68
|
+
* a request naming a later item must not lift the queue at all (it would
|
|
69
|
+
* steer and delete a different message's words). The caller must either
|
|
70
|
+
* restore the held queue or settle its head. */
|
|
71
|
+
export function holdChannelQueue(groupId, threadId, queueId) {
|
|
72
|
+
const entry = queues.get(threadId);
|
|
73
|
+
if (!entry || entry.groupId !== groupId || entry.items[0]?.id !== queueId)
|
|
74
|
+
return null;
|
|
75
|
+
queues.delete(threadId);
|
|
76
|
+
return { groupId, threadId, items: entry.items };
|
|
77
|
+
}
|
|
78
|
+
/** Put a held queue back after the steer was refused. Words queued while the
|
|
79
|
+
* hold was open keep their place behind the restored items. */
|
|
80
|
+
export function restoreHeldChannelQueue(held) {
|
|
81
|
+
const existing = queues.get(held.threadId);
|
|
82
|
+
if (existing && existing.groupId !== held.groupId)
|
|
83
|
+
throw new Error("queued task belongs to another channel");
|
|
84
|
+
queues.set(held.threadId, {
|
|
85
|
+
groupId: held.groupId,
|
|
86
|
+
items: existing ? [...held.items, ...existing.items] : held.items,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** Resolve the held head's reply target through a caller-supplied resolver.
|
|
90
|
+
* The queue is already lifted out of the map here, so a target that cannot
|
|
91
|
+
* be resolved (missing, non-text, empty — state drift between queueing and
|
|
92
|
+
* the steer) must not strand the held words outside it until restart:
|
|
93
|
+
* restore first, then let the error propagate to the request. */
|
|
94
|
+
export function resolveHeldReplyTarget(held, resolve) {
|
|
95
|
+
const head = held.items[0];
|
|
96
|
+
if (!head?.replyToId)
|
|
97
|
+
return undefined;
|
|
98
|
+
try {
|
|
99
|
+
return resolve(held.threadId, head.replyToId);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
restoreHeldChannelQueue(held);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Mark a held queue's head delivered — its words were folded into the
|
|
107
|
+
* running turn — and re-queue the rest for the room's normal one-at-a-time
|
|
108
|
+
* drain. A restart must not replay the steered head as a fresh follow-up. */
|
|
109
|
+
export function settleHeldChannelQueueHead(held) {
|
|
110
|
+
const [head, ...rest] = held.items;
|
|
111
|
+
settleChatFollowups([head.id], null);
|
|
112
|
+
if (rest.length === 0)
|
|
113
|
+
return;
|
|
114
|
+
const existing = queues.get(held.threadId);
|
|
115
|
+
if (existing && existing.groupId !== held.groupId)
|
|
116
|
+
throw new Error("queued task belongs to another channel");
|
|
117
|
+
queues.set(held.threadId, {
|
|
118
|
+
groupId: held.groupId,
|
|
119
|
+
items: existing ? [...rest, ...existing.items] : rest,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
64
122
|
/**
|
|
65
123
|
* Start at most one follow-up per idle channel. Starting it synchronously
|
|
66
124
|
* marks the channel working again; its completion calls this drain for the
|
|
@@ -29,12 +29,12 @@ export function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate, trustedOpen
|
|
|
29
29
|
});
|
|
30
30
|
const delegation = canDelegate
|
|
31
31
|
? boundedCoordination
|
|
32
|
-
? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room, everything you send a teammate continues your one standing conversation with them, using their own model and permissions, so they still have the context of your earlier assignments. Busy teammates queue. Give self-contained briefs, then end your turn; you resume automatically after their results return. Leads can coordinate their own specialists. Do not poll, send acknowledgements as new work, or substitute native helpers for named bots. On return, verify the requested outcome, resolve decisions within the user's scope, request concrete corrections with rework=true when necessary, and return one consolidated answer. Consultations are advice, not proof that work or tests ran."
|
|
32
|
+
? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room, everything you send a teammate continues your one standing conversation with them, using their own model and permissions, so they still have the context of your earlier assignments. Busy teammates queue. Give self-contained briefs, then end your turn; you resume automatically after their results return. Leads can coordinate their own specialists. Do not poll, send acknowledgements as new work, or substitute native helpers for named bots. On return, verify the requested outcome, resolve decisions within the user's scope, request concrete corrections with rework=true when necessary, and return one consolidated answer. Consultations are advice, not proof that work or tests ran. A refusal from coordinate_bots means nothing was sent: fix what it names (usually the id — copy it from list_bots or your roster; a unique teammate name also works) and retry, and never describe a handoff the tool did not accept."
|
|
33
33
|
: [
|
|
34
34
|
"Use list_bots to confirm the live roster and IDs. When assigning work to a teammate, use delegate_bot: it returns immediately, keeps you available to the user, and delivers the teammate's outcome back into this conversation automatically — success or failure. When the result arrives you are woken with it: report it to the user and act. If the teammate fails or stalls, tell the user plainly and decide the next step yourself.",
|
|
35
35
|
"After delegate_bot accepts the task, acknowledge the handoff and continue with any independent work or end your turn. Do not call wait_delegation or repeatedly poll check_delegation in the same turn.",
|
|
36
36
|
"Use ask_bot only for a brief consultation whose answer you must have before writing your current response. Never use ask_bot for an assigned task, background work, or anything potentially long-running.",
|
|
37
|
-
"Delegate with a clear, self-contained brief. Say that the task is assigned, not completed; only claim completion after the teammate's result has actually arrived.",
|
|
37
|
+
"Delegate with a clear, self-contained brief. Say that the task is assigned, not completed; only claim completion after the teammate's result has actually arrived. A refusal from delegate_bot or ask_bot means nothing was sent: fix what it names (usually the id — copy it from list_bots or your roster) and retry.",
|
|
38
38
|
"You may assign work to more than one teammate when the request genuinely benefits. Stay responsive while they work, then combine their returned results when the user asks for a synthesis.",
|
|
39
39
|
].join(" ")
|
|
40
40
|
: "Your current engine cannot contact teammates. Be honest about that limitation and ask the user to choose a delegation-compatible engine before promising coordinated work.";
|