openmausbot 0.1.69 → 0.1.70
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-B2F__IRS.css +1 -0
- package/dist/assets/index-CLj3t5Ba.js +280 -0
- package/dist/assets/{index-BmHupgVJ.js → index-D1RGhLUK.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +2 -0
- package/dist-server/drivers/agents-proxy.js +103 -0
- package/dist-server/index.js +1169 -576
- package/dist-server/local-computer-proxy.js +2 -0
- package/dist-server/openmausbot.js +456 -230
- package/dist-server/pair-cli.js +456 -230
- package/dist-server/prepare-cloudflared.js +20 -3
- package/dist-server/server/delegations.js +132 -74
- package/dist-server/server/drivers/agents-proxy.js +129 -0
- package/dist-server/server/drivers/claude-login-auth.js +102 -9
- package/dist-server/server/drivers/claude.js +11 -5
- package/dist-server/server/engine-install.js +137 -0
- package/dist-server/server/env-path.js +12 -0
- package/dist-server/server/harness/registry.js +31 -6
- package/dist-server/server/index.js +340 -22
- package/dist-server/server/peer-provenance.js +6 -2
- package/dist-server/server/routines.js +116 -55
- package/dist-server/server/steer-queue.js +20 -1
- package/dist-server/server/store.js +22 -4
- package/dist-server/server/system-prompt.js +1 -0
- package/dist-server/server/team-backup.js +18 -4
- package/dist-server/shared/team-backup.js +1 -0
- package/dist-server/vps-container-mcp.js +2 -0
- package/package.json +1 -1
- package/dist/assets/index-COp5lMnv.js +0 -279
- package/dist/assets/index-Jk7TIeyW.css +0 -1
|
@@ -449,6 +449,7 @@ export class RoutineManager {
|
|
|
449
449
|
timeoutMinutes: loadTimeoutMinutes(routine.timeoutMinutes),
|
|
450
450
|
attachments: loadAttachments(routine.attachments),
|
|
451
451
|
sourceThreadId: persistedSourceThreadId.parse(routine.sourceThreadId),
|
|
452
|
+
resultsThreadId: persistedSourceThreadId.parse(routine.resultsThreadId),
|
|
452
453
|
};
|
|
453
454
|
if (loaded.timeoutMinutes === undefined)
|
|
454
455
|
delete loaded.timeoutMinutes;
|
|
@@ -467,6 +468,7 @@ export class RoutineManager {
|
|
|
467
468
|
timeoutMinutes: loadTimeoutMinutes(run.timeoutMinutes),
|
|
468
469
|
attachments: loadAttachments(run.attachments),
|
|
469
470
|
sourceThreadId: persistedSourceThreadId.parse(run.sourceThreadId),
|
|
471
|
+
resultsThreadId: persistedSourceThreadId.parse(run.resultsThreadId),
|
|
470
472
|
};
|
|
471
473
|
if (loaded.timeoutMinutes === undefined)
|
|
472
474
|
delete loaded.timeoutMinutes;
|
|
@@ -619,11 +621,12 @@ export class RoutineManager {
|
|
|
619
621
|
createdAt: at,
|
|
620
622
|
updatedAt: at,
|
|
621
623
|
};
|
|
624
|
+
const discardResults = this.applyResultsInput(routine, input.resultsThreadId);
|
|
622
625
|
this.commitMutation(() => {
|
|
623
626
|
this.routines.unshift(routine);
|
|
624
627
|
if (request)
|
|
625
628
|
this.rememberRoutineRequest(request, routine.id, at);
|
|
626
|
-
});
|
|
629
|
+
}, discardResults);
|
|
627
630
|
this.emitRoutine(routine);
|
|
628
631
|
return cloneRoutine(routine);
|
|
629
632
|
}
|
|
@@ -664,9 +667,14 @@ export class RoutineManager {
|
|
|
664
667
|
if (clean.schedule.type === "interval" && clean.enabled && nextRunAt === null) {
|
|
665
668
|
throw new Error("This interval has no future runs. Choose a later end date or turn it off.");
|
|
666
669
|
}
|
|
670
|
+
const destination = { ...routine, ...clean };
|
|
671
|
+
if (destination.botId !== routine.botId)
|
|
672
|
+
delete destination.resultsThreadId;
|
|
673
|
+
const discardResults = this.applyResultsInput(destination, patch.resultsThreadId);
|
|
667
674
|
const cancelledRuns = [];
|
|
668
675
|
this.commitMutation(() => {
|
|
669
676
|
Object.assign(routine, clean, {
|
|
677
|
+
resultsThreadId: destination.resultsThreadId,
|
|
670
678
|
nextRunAt,
|
|
671
679
|
// `updatedAt` doubles as the optimistic revision on durable routine
|
|
672
680
|
// confirmation cards. Keep it monotonic even for two writes in one ms.
|
|
@@ -692,7 +700,7 @@ export class RoutineManager {
|
|
|
692
700
|
}
|
|
693
701
|
if (request)
|
|
694
702
|
this.rememberRoutineRequest(request, routine.id, now);
|
|
695
|
-
});
|
|
703
|
+
}, discardResults);
|
|
696
704
|
for (const run of cancelledRuns)
|
|
697
705
|
this.emitRun(run);
|
|
698
706
|
this.emitRoutine(routine);
|
|
@@ -803,15 +811,19 @@ export class RoutineManager {
|
|
|
803
811
|
if (!routine)
|
|
804
812
|
return null;
|
|
805
813
|
let run;
|
|
814
|
+
const allocations = [];
|
|
815
|
+
const previousDestination = routine.resultsThreadId;
|
|
806
816
|
this.commitMutation(() => {
|
|
807
|
-
run = this.newRun(routine, this.now(), true);
|
|
808
|
-
//
|
|
809
|
-
//
|
|
817
|
+
run = this.newRun(routine, this.now(), true, allocations, request?.threadId ?? routine.sourceThreadId);
|
|
818
|
+
// Preserve the invoking chat as provenance/fallback for this run.
|
|
819
|
+
// An explicitly configured results destination continues to win.
|
|
810
820
|
if (request)
|
|
811
821
|
run.sourceThreadId = request.threadId;
|
|
812
822
|
if (request)
|
|
813
823
|
this.rememberRoutineRequest(request, run.id, this.now());
|
|
814
|
-
});
|
|
824
|
+
}, () => this.discardResultsThreads(allocations));
|
|
825
|
+
if (routine.resultsThreadId !== previousDestination)
|
|
826
|
+
this.emitRoutine(routine);
|
|
815
827
|
this.emitRun(run);
|
|
816
828
|
queueMicrotask(() => void this.tick());
|
|
817
829
|
return cloneRun(run);
|
|
@@ -978,55 +990,57 @@ export class RoutineManager {
|
|
|
978
990
|
await this.options.interruptTurn?.(run.botId, threadId, run.runOn ?? "maus").catch(() => { });
|
|
979
991
|
}
|
|
980
992
|
}
|
|
981
|
-
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
993
|
+
const dueRoutines = this.routines.filter((routine) => routine.enabled && routine.nextRunAt != null && routine.nextRunAt <= now);
|
|
994
|
+
const scheduledRuns = [];
|
|
995
|
+
const allocations = [];
|
|
996
|
+
if (dueRoutines.length > 0) {
|
|
997
|
+
this.commitMutation(() => {
|
|
998
|
+
for (const routine of dueRoutines) {
|
|
999
|
+
const pendingAt = routine.nextRunAt;
|
|
1000
|
+
const late = now - pendingAt;
|
|
1001
|
+
const scheduledFor = routine.schedule.type === "interval" && late <= CATCH_UP_MS
|
|
1002
|
+
? latestIntervalOccurrence(routine.schedule, now) ?? pendingAt
|
|
1003
|
+
: pendingAt;
|
|
1004
|
+
// One slow interval run must not build an unbounded queue of stale
|
|
1005
|
+
// copies behind it. The series still advances on its original phase.
|
|
1006
|
+
const overlapping = routine.schedule.type === "interval" && this.runs.some((run) => run.routineId === routine.id && ["queued", "running", "waiting"].includes(run.status));
|
|
1007
|
+
if (!overlapping) {
|
|
1008
|
+
const run = this.newRun(routine, scheduledFor, false, allocations);
|
|
1009
|
+
if (late > CATCH_UP_MS) {
|
|
1010
|
+
run.status = "missed";
|
|
1011
|
+
run.finishedAt = now;
|
|
1012
|
+
run.error = "This computer was offline for more than 12 hours after the scheduled time";
|
|
1013
|
+
}
|
|
1014
|
+
scheduledRuns.push(run);
|
|
1015
|
+
}
|
|
1016
|
+
routine.nextRunAt =
|
|
1017
|
+
routine.schedule.type === "once" ? null : nextOccurrence(routine.schedule, Math.max(now, scheduledFor));
|
|
1018
|
+
// `updatedAt` is the optimistic definition revision carried by
|
|
1019
|
+
// routine confirmation cards. Moving the scheduler cursor is runtime
|
|
1020
|
+
// progress, not a definition edit, so recurring ticks must not make a
|
|
1021
|
+
// still-accurate pending confirmation stale. A one-time routine does
|
|
1022
|
+
// mutate its definition by auto-disabling after its occurrence.
|
|
1023
|
+
if (routine.schedule.type === "once") {
|
|
1024
|
+
routine.enabled = false;
|
|
1025
|
+
routine.updatedAt = Math.max(now, routine.updatedAt + 1);
|
|
1026
|
+
}
|
|
1027
|
+
else if (routine.schedule.type === "interval" && routine.nextRunAt === null) {
|
|
1028
|
+
routine.enabled = false;
|
|
1029
|
+
routine.updatedAt = Math.max(now, routine.updatedAt + 1);
|
|
1030
|
+
}
|
|
1006
1031
|
}
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
// progress, not a definition edit, so recurring ticks must not make a
|
|
1013
|
-
// still-accurate pending confirmation stale. A one-time routine does
|
|
1014
|
-
// mutate its definition by auto-disabling after its occurrence.
|
|
1015
|
-
if (routine.schedule.type === "once") {
|
|
1016
|
-
routine.enabled = false;
|
|
1017
|
-
routine.updatedAt = Math.max(now, routine.updatedAt + 1);
|
|
1018
|
-
}
|
|
1019
|
-
else if (routine.schedule.type === "interval" && routine.nextRunAt === null) {
|
|
1020
|
-
routine.enabled = false;
|
|
1021
|
-
routine.updatedAt = Math.max(now, routine.updatedAt + 1);
|
|
1022
|
-
}
|
|
1032
|
+
}, () => this.discardResultsThreads(allocations));
|
|
1033
|
+
}
|
|
1034
|
+
// Reporting may persist transcript cards, so publish only after the
|
|
1035
|
+
// scheduler batch (including each destination) is durable.
|
|
1036
|
+
for (const routine of dueRoutines)
|
|
1023
1037
|
this.emitRoutine(routine);
|
|
1024
|
-
|
|
1038
|
+
for (const run of scheduledRuns)
|
|
1039
|
+
this.emitRun(run);
|
|
1040
|
+
for (const run of scheduledRuns) {
|
|
1041
|
+
if (run.status === "missed")
|
|
1042
|
+
this.options.onRunFailed?.(cloneRun(run));
|
|
1025
1043
|
}
|
|
1026
|
-
if (changed)
|
|
1027
|
-
this.save();
|
|
1028
|
-
for (const missed of missedRuns)
|
|
1029
|
-
this.options.onRunFailed?.(missed);
|
|
1030
1044
|
// Oldest queued requests have priority. New manual/webhook arrivals
|
|
1031
1045
|
// must not continually overtake work that has already waited. Snapshot
|
|
1032
1046
|
// the queue because each dispatch can asynchronously add/cancel work.
|
|
@@ -1262,7 +1276,16 @@ export class RoutineManager {
|
|
|
1262
1276
|
return schedule.at;
|
|
1263
1277
|
return nextOccurrence(schedule, now);
|
|
1264
1278
|
}
|
|
1265
|
-
newRun(routine, scheduledFor, manual) {
|
|
1279
|
+
newRun(routine, scheduledFor, manual, allocations, sourceThreadId = routine.sourceThreadId) {
|
|
1280
|
+
if (routine.target === "bot" && this.options.resolveResultsThread) {
|
|
1281
|
+
const destination = this.options.resolveResultsThread({ ...routine, sourceThreadId }, false);
|
|
1282
|
+
if (destination !== routine.resultsThreadId) {
|
|
1283
|
+
if (destination)
|
|
1284
|
+
allocations.push({ botId: routine.botId, threadId: destination });
|
|
1285
|
+
routine.resultsThreadId = destination;
|
|
1286
|
+
routine.updatedAt = Math.max(this.now(), routine.updatedAt + 1);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1266
1289
|
const run = {
|
|
1267
1290
|
id: randomUUID(),
|
|
1268
1291
|
routineId: routine.id,
|
|
@@ -1279,12 +1302,44 @@ export class RoutineManager {
|
|
|
1279
1302
|
status: "queued",
|
|
1280
1303
|
manual,
|
|
1281
1304
|
triggerSource: manual ? "manual" : "schedule",
|
|
1282
|
-
sourceThreadId
|
|
1305
|
+
sourceThreadId,
|
|
1306
|
+
resultsThreadId: routine.resultsThreadId,
|
|
1283
1307
|
createdAt: this.now(),
|
|
1284
1308
|
};
|
|
1285
1309
|
this.runs.push(run);
|
|
1286
1310
|
return run;
|
|
1287
1311
|
}
|
|
1312
|
+
discardResultsThreads(allocations) {
|
|
1313
|
+
for (const { botId, threadId } of allocations) {
|
|
1314
|
+
try {
|
|
1315
|
+
this.options.discardResultsThread?.(botId, threadId);
|
|
1316
|
+
}
|
|
1317
|
+
catch (error) {
|
|
1318
|
+
console.error("routine: could not discard uncommitted results thread", error);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
applyResultsInput(routine, value) {
|
|
1323
|
+
if (routine.target !== "bot") {
|
|
1324
|
+
if (value != null)
|
|
1325
|
+
throw Object.assign(new Error("Results threads are only available for bot routines"), { status: 400 });
|
|
1326
|
+
delete routine.resultsThreadId;
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
if (value === undefined)
|
|
1330
|
+
return;
|
|
1331
|
+
if (value === null) {
|
|
1332
|
+
const destination = this.options.resolveResultsThread?.(routine, true);
|
|
1333
|
+
if (!destination)
|
|
1334
|
+
throw new Error("Could not create a results thread for this routine");
|
|
1335
|
+
routine.resultsThreadId = destination;
|
|
1336
|
+
return () => this.options.discardResultsThread?.(routine.botId, destination);
|
|
1337
|
+
}
|
|
1338
|
+
if (typeof value !== "string" || !value.trim() || !this.options.isResultsThread?.(routine.botId, value.trim())) {
|
|
1339
|
+
throw Object.assign(new Error("Choose a visible results thread belonging to this bot"), { status: 400 });
|
|
1340
|
+
}
|
|
1341
|
+
routine.resultsThreadId = value.trim();
|
|
1342
|
+
}
|
|
1288
1343
|
emitRoutine(routine) {
|
|
1289
1344
|
this.options.emit?.({ kind: "routine", routine: cloneRoutine(routine) });
|
|
1290
1345
|
}
|
|
@@ -1331,7 +1386,7 @@ export class RoutineManager {
|
|
|
1331
1386
|
* state if writing or renaming that file fails so a retry cannot mistake an
|
|
1332
1387
|
* uncommitted action for a durable one.
|
|
1333
1388
|
*/
|
|
1334
|
-
commitMutation(mutate) {
|
|
1389
|
+
commitMutation(mutate, rollback) {
|
|
1335
1390
|
const before = {
|
|
1336
1391
|
routines: this.routines.map(cloneRoutine),
|
|
1337
1392
|
runs: this.runs.map(cloneRun),
|
|
@@ -1345,6 +1400,12 @@ export class RoutineManager {
|
|
|
1345
1400
|
this.routines = before.routines;
|
|
1346
1401
|
this.runs = before.runs;
|
|
1347
1402
|
this.routineRequestReceipts = before.receipts;
|
|
1403
|
+
try {
|
|
1404
|
+
rollback?.();
|
|
1405
|
+
}
|
|
1406
|
+
catch (cleanupError) {
|
|
1407
|
+
console.error("routine: could not discard uncommitted results thread", cleanupError);
|
|
1408
|
+
}
|
|
1348
1409
|
throw error;
|
|
1349
1410
|
}
|
|
1350
1411
|
}
|
|
@@ -57,11 +57,27 @@ export function queueSteeredMessage(botId, threadId, text, options = {}) {
|
|
|
57
57
|
replyToId: options.replyToId,
|
|
58
58
|
sendId: options.sendId,
|
|
59
59
|
reason: options.reason,
|
|
60
|
+
unattended: options.unattended,
|
|
60
61
|
});
|
|
61
62
|
queues.set(threadId, entry);
|
|
62
63
|
changed();
|
|
63
64
|
return { id };
|
|
64
65
|
}
|
|
66
|
+
/** Where a thread stands among this bot's threads waiting for a free slot:
|
|
67
|
+
* 1 for the next to start. The drain visits queues in insertion order, so
|
|
68
|
+
* insertion order is the line. Null when nothing of this bot's is waiting
|
|
69
|
+
* on that thread. */
|
|
70
|
+
export function queuedThreadPosition(botId, threadId) {
|
|
71
|
+
let position = 0;
|
|
72
|
+
for (const [candidate, entry] of queues) {
|
|
73
|
+
if (entry.botId !== botId || !entry.items.some((item) => item.reason === "capacity"))
|
|
74
|
+
continue;
|
|
75
|
+
position += 1;
|
|
76
|
+
if (candidate === threadId)
|
|
77
|
+
return position;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
65
81
|
/** Drain every queue whose task is idle: append the held lines (leaf is now
|
|
66
82
|
* the finished turn's last item), then one run per thread whose prompt is
|
|
67
83
|
* the texts separated by a blank line. `userMessage` is the last appended line
|
|
@@ -108,7 +124,10 @@ export function drainSteeredMessages(store, run, isBlocked) {
|
|
|
108
124
|
// message into one HTML block, which makes that attachment stop being a
|
|
109
125
|
// native image when the combined follow-up is dispatched.
|
|
110
126
|
const prompt = entry.items.map((item) => item.prompt).join("\n\n");
|
|
111
|
-
void run(entry.botId, threadId, prompt, last, appended.map((message) => message.id)
|
|
127
|
+
void run(entry.botId, threadId, prompt, last, appended.map((message) => message.id),
|
|
128
|
+
// one unattended line makes the whole drained turn unattended: a
|
|
129
|
+
// person's words in the same queue cannot re-attend a bot's own
|
|
130
|
+
entry.items.some((item) => item.unattended === true));
|
|
112
131
|
}
|
|
113
132
|
}
|
|
114
133
|
/** Find the receipt for a retry whose message is still waiting to drain. */
|
|
@@ -26,6 +26,7 @@ export function isProjectEmoji(value) {
|
|
|
26
26
|
const TASK_PATCH_FIELDS = [
|
|
27
27
|
"title", "projectId", "modelSelection", "approvalMode", "autoApprove", "alwaysAllow",
|
|
28
28
|
"unread", "rewound", "pinnedMessageId", "resumeCursors", "lastInstanceId", "cwd",
|
|
29
|
+
"routineRunId",
|
|
29
30
|
];
|
|
30
31
|
/** Everything the BOT authored is scrubbed of content-shaped secrets before
|
|
31
32
|
* it is stored: its reply text, a tool title (an ACP engine's title can be
|
|
@@ -1444,7 +1445,7 @@ export class Store {
|
|
|
1444
1445
|
}
|
|
1445
1446
|
/** A fresh context on the same bot: new thread, new session, same
|
|
1446
1447
|
* persona/tools/computer. Becomes the active task. */
|
|
1447
|
-
createTask(botId, title, activate = true, projectId) {
|
|
1448
|
+
createTask(botId, title, activate = true, projectId, openedBy) {
|
|
1448
1449
|
const bot = this.bot(botId);
|
|
1449
1450
|
if (!bot)
|
|
1450
1451
|
return null;
|
|
@@ -1455,6 +1456,7 @@ export class Store {
|
|
|
1455
1456
|
title: title?.trim().slice(0, 80) || UNTITLED_THREAD,
|
|
1456
1457
|
createdAt: Date.now(),
|
|
1457
1458
|
...(projectId ? { projectId } : {}),
|
|
1459
|
+
...(openedBy ? { openedBy: structuredClone(openedBy) } : {}),
|
|
1458
1460
|
resumeCursors: {},
|
|
1459
1461
|
modelSelection: structuredClone(bot.modelSelection),
|
|
1460
1462
|
approvalMode: approvalModeFor(bot),
|
|
@@ -1472,6 +1474,20 @@ export class Store {
|
|
|
1472
1474
|
this.emit({ type: "bot", botId });
|
|
1473
1475
|
return task;
|
|
1474
1476
|
}
|
|
1477
|
+
/** Attach (or complete) the opener record after the thread exists — the
|
|
1478
|
+
* handoff id is only known once the thread it targets has an id, so a
|
|
1479
|
+
* peer-opened thread is created first and stamped second. Never reachable
|
|
1480
|
+
* from the HTTP task PATCH: openedBy is not a TASK_PATCH_FIELD. */
|
|
1481
|
+
setTaskOpenedBy(botId, threadId, openedBy) {
|
|
1482
|
+
const bot = this.bot(botId);
|
|
1483
|
+
const task = this.taskByThread(botId, threadId);
|
|
1484
|
+
if (!bot || !task)
|
|
1485
|
+
return null;
|
|
1486
|
+
task.openedBy = structuredClone(openedBy);
|
|
1487
|
+
this.saveBots();
|
|
1488
|
+
this.emit({ type: "bot", botId });
|
|
1489
|
+
return task;
|
|
1490
|
+
}
|
|
1475
1491
|
switchTask(botId, threadId) {
|
|
1476
1492
|
const bot = this.bot(botId);
|
|
1477
1493
|
const task = bot?.tasks?.find((t) => t.threadId === threadId);
|
|
@@ -1503,10 +1519,12 @@ export class Store {
|
|
|
1503
1519
|
if (!bot.tasks.some((t) => t.threadId === threadId))
|
|
1504
1520
|
return null;
|
|
1505
1521
|
bot.tasks = bot.tasks.filter((t) => t.threadId !== threadId);
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1522
|
+
const visible = bot.tasks.find((task) => !task.routineRunId)
|
|
1523
|
+
?? this.createTask(botId, undefined, bot.threadId === threadId);
|
|
1524
|
+
if (bot.threadId === threadId || this.taskByThread(botId, bot.threadId)?.routineRunId) {
|
|
1525
|
+
this.mirrorActiveTask(bot, visible);
|
|
1509
1526
|
}
|
|
1527
|
+
this.deleteThreadRecord(threadId);
|
|
1510
1528
|
bot.unread = bot.tasks.some((task) => task.unread);
|
|
1511
1529
|
this.refreshBotActivity(bot);
|
|
1512
1530
|
this.saveBots();
|
|
@@ -36,6 +36,7 @@ export function computerPrompt(kind) {
|
|
|
36
36
|
}
|
|
37
37
|
export const COMPOSIO_PROMPT = " The user's connected apps (Gmail, Calendar, Slack, Notion, and the rest) are reachable through the composio tools — find the right one with COMPOSIO_SEARCH_TOOLS, read its arguments with COMPOSIO_GET_TOOL_SCHEMAS, then run it with COMPOSIO_MULTI_EXECUTE_TOOL. Reach for them before telling the user you have no access to a service.";
|
|
38
38
|
export const CREDENTIAL_PROMPT = " If a supported API key is missing, use request_credential to create a secure credential request. A freshly QR-paired mobile app or the desktop app can show the secure entry card. Never claim it opened unless the request succeeded, and never ask the user to paste credentials into chat.";
|
|
39
|
+
export const THREADS_PROMPT = " A thread is one conversation with its own history and its own run; a bot can have several running at once, and the person sees them as rows under that bot. Use start_thread to open one on yourself for separate work, or on a teammate to hand them a job that should run on its own. Use list_threads to see how the ones you opened are going. When you mention a thread to the person, write its title as #Title so it links. Do not use a ticket comment, a note, or a room post as a stand-in for a thread.";
|
|
39
40
|
export const ROUTINE_PROMPT = " If the user explicitly asks to list or review, schedule, run, or change routines, use list_routines and propose_routine or propose_routine_action. A proposal is not applied until the user confirms its in-app card, so never claim the action completed before that confirmation.";
|
|
40
41
|
export const ROUTINE_EXECUTION_PROMPT = " Execute this routine now: use available peer tools for required handoffs rather than merely announcing that you will wait; after an accepted delegation, end this turn for automatic resumption, and report a concrete blocker if no handoff is possible.";
|
|
41
42
|
export const LEARN_PROMPT = " If the user sends /learn or asks you to save a reusable procedure from this work, use skills_list and skill_manage. Create new skills; update an existing learned skill only when the user explicitly asks to revise that exact name. Include source provenance and wait for the review card decision.";
|
|
@@ -29,6 +29,11 @@ export function createTeamBackup(store, routines, name) {
|
|
|
29
29
|
const tasks = record.tasks?.length ? record.tasks : [{ threadId: record.threadId, title: "Conversation", createdAt: record.createdAt }];
|
|
30
30
|
return tasks.map((task) => ({
|
|
31
31
|
key: task.threadId, title: task.title, createdAt: task.createdAt,
|
|
32
|
+
// Who opened the thread travels; the handoff id does not — the
|
|
33
|
+
// delegation ledger is process-local and never part of a backup.
|
|
34
|
+
openedBy: "openedBy" in task && task.openedBy
|
|
35
|
+
? { botId: task.openedBy.botId, name: task.openedBy.name, at: task.openedBy.at }
|
|
36
|
+
: undefined,
|
|
32
37
|
activeLeafId: store.activeLeaf(task.threadId),
|
|
33
38
|
messages: store.messagesFor(task.threadId).map((message) => ({
|
|
34
39
|
id: message.id, role: message.role, text: messageText(message), at: message.at,
|
|
@@ -131,10 +136,19 @@ export function importTeamBackup(store, routines, input, selection) {
|
|
|
131
136
|
}
|
|
132
137
|
for (const source of backup.bots) {
|
|
133
138
|
const bot = store.bot(botIds.get(source.key));
|
|
134
|
-
const tasks = source.tasks.map((task, i) =>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
const tasks = source.tasks.map((task, i) => {
|
|
140
|
+
const record = {
|
|
141
|
+
threadId: i === 0 ? bot.threadId : newId(), title: task.title, createdAt: task.createdAt, resumeCursors: {},
|
|
142
|
+
modelSelection: structuredClone(selection), activity: "idle", busy: false, unread: false,
|
|
143
|
+
};
|
|
144
|
+
// Same rule as a message's `from`: the opener is remapped to its
|
|
145
|
+
// imported twin, and an opener outside this backup leaves no record
|
|
146
|
+
// rather than a bot id that resolves to a stranger.
|
|
147
|
+
const opener = task.openedBy && botIds.get(task.openedBy.botId);
|
|
148
|
+
if (task.openedBy && opener)
|
|
149
|
+
record.openedBy = { botId: opener, name: task.openedBy.name, at: task.openedBy.at };
|
|
150
|
+
return record;
|
|
151
|
+
});
|
|
138
152
|
// Own the task IDs before writing their transcripts, so rollback also
|
|
139
153
|
// removes partially imported history if persistence fails midway.
|
|
140
154
|
store.patchBot(bot.id, { tasks, threadId: tasks[source.tasks.findIndex((task) => task.key === source.activeTask)].threadId });
|
|
@@ -141,9 +141,11 @@ function windowsKnownDirs() {
|
|
|
141
141
|
var cached = null;
|
|
142
142
|
var probed = false;
|
|
143
143
|
var loginShellPath = null;
|
|
144
|
+
var registeredDirs = [];
|
|
144
145
|
function augmentedPath() {
|
|
145
146
|
if (cached === null) {
|
|
146
147
|
cached = mergePaths([
|
|
148
|
+
...registeredDirs.filter((d) => existsSync(d)),
|
|
147
149
|
...process.env.OMB_EXTRA_PATH ? process.env.OMB_EXTRA_PATH.split(delimiter) : [],
|
|
148
150
|
...process.env.PATH ? process.env.PATH.split(delimiter) : [],
|
|
149
151
|
// Keep the last successful login-shell result while a rescan starts a
|