@goodfish/jarvy 0.3.0 → 0.4.0
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/CHANGELOG.md +17 -0
- package/README.md +4 -2
- package/README.zh-CN.md +4 -2
- package/dist/server/cli.js +525 -84
- package/dist/web/assets/figure-sTOSJn2I.js +1 -0
- package/dist/web/assets/index-DL8kubrV.js +534 -0
- package/dist/web/assets/index-Dpj-oCnv.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/figure-DBBkOPNl.js +0 -1
- package/dist/web/assets/index-C1Aq7FIn.js +0 -505
- package/dist/web/assets/index-CDOMm3pH.css +0 -1
package/dist/server/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createServer } from "node:http";
|
|
|
11
11
|
import { getRequestListener } from "@hono/node-server";
|
|
12
12
|
|
|
13
13
|
// src/server/api/app.ts
|
|
14
|
-
import { Hono as
|
|
14
|
+
import { Hono as Hono10 } from "hono";
|
|
15
15
|
import { HTTPException as HTTPException9 } from "hono/http-exception";
|
|
16
16
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
17
17
|
|
|
@@ -432,6 +432,26 @@ function botRoutes(bots) {
|
|
|
432
432
|
// src/server/api/routes/chats.ts
|
|
433
433
|
import { Hono as Hono5 } from "hono";
|
|
434
434
|
import { HTTPException as HTTPException7 } from "hono/http-exception";
|
|
435
|
+
|
|
436
|
+
// src/server/api/views/download.ts
|
|
437
|
+
var MEDIA_TYPES = /* @__PURE__ */ new Set(["video/webm", "video/mp4", "video/quicktime", "audio/mpeg", "audio/wav", "audio/x-wav", "audio/ogg", "audio/webm", "audio/mp4"]);
|
|
438
|
+
function downloadType(mimeType) {
|
|
439
|
+
const type = mimeType.split(";")[0].trim().toLowerCase();
|
|
440
|
+
return MEDIA_TYPES.has(type) ? type : "application/octet-stream";
|
|
441
|
+
}
|
|
442
|
+
function byteRange(header, size) {
|
|
443
|
+
const match = header?.trim().match(/^bytes=(\d*)-(\d*)$/);
|
|
444
|
+
if (!match || !match[1] && !match[2]) return null;
|
|
445
|
+
if (!match[1]) {
|
|
446
|
+
const suffix = Number(match[2]);
|
|
447
|
+
return suffix === 0 ? "unsatisfiable" : { start: Math.max(0, size - suffix), end: size - 1 };
|
|
448
|
+
}
|
|
449
|
+
const start = Number(match[1]), end = match[2] ? Number(match[2]) : Infinity;
|
|
450
|
+
if (end < start) return null;
|
|
451
|
+
return start >= size ? "unsatisfiable" : { start, end: Math.min(end, size - 1) };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/server/api/routes/chats.ts
|
|
435
455
|
function chatRoutes({ chats, bots }) {
|
|
436
456
|
const routes = new Hono5();
|
|
437
457
|
const view = (chat) => chatView(chat, (id) => chats.member(id));
|
|
@@ -484,11 +504,19 @@ function attachmentRoutes(chats) {
|
|
|
484
504
|
routes.get("/:id", (c) => {
|
|
485
505
|
const attachment = chats.download(c.get("actorId"), c.req.param("id"));
|
|
486
506
|
if (!attachment) throw new HTTPException7(404, { message: "Attachment not found" });
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
"Content-
|
|
507
|
+
const size = attachment.data.length, range = byteRange(c.req.header("Range"), size);
|
|
508
|
+
const headers2 = {
|
|
509
|
+
"Content-Type": downloadType(attachment.file.mimeType),
|
|
510
|
+
"Accept-Ranges": "bytes",
|
|
490
511
|
"Content-Disposition": `attachment; filename*=UTF-8''${encodeURIComponent(attachment.file.name)}`
|
|
491
|
-
}
|
|
512
|
+
};
|
|
513
|
+
if (range === "unsatisfiable") return c.body(null, 416, { ...headers2, "Content-Range": `bytes */${size}` });
|
|
514
|
+
if (!range) return c.body(new Uint8Array(attachment.data), 200, { ...headers2, "Content-Length": String(size) });
|
|
515
|
+
return c.body(
|
|
516
|
+
new Uint8Array(attachment.data.subarray(range.start, range.end + 1)),
|
|
517
|
+
206,
|
|
518
|
+
{ ...headers2, "Content-Length": String(range.end - range.start + 1), "Content-Range": `bytes ${range.start}-${range.end}/${size}` }
|
|
519
|
+
);
|
|
492
520
|
});
|
|
493
521
|
routes.get("/:id/run", (c) => {
|
|
494
522
|
const attachment = chats.download(c.get("actorId"), c.req.param("id"));
|
|
@@ -685,12 +713,68 @@ function channelRoutes({ chats }) {
|
|
|
685
713
|
return routes;
|
|
686
714
|
}
|
|
687
715
|
|
|
716
|
+
// src/server/api/routes/live.ts
|
|
717
|
+
import { Hono as Hono9 } from "hono";
|
|
718
|
+
function liveRoutes({ account, bots }) {
|
|
719
|
+
const routes = new Hono9();
|
|
720
|
+
routes.get("/bots/:id/live", (c) => {
|
|
721
|
+
const token = c.get("sessionToken"), signal = c.req.raw.signal, live = bots.live(c.req.param("id"));
|
|
722
|
+
let disconnect;
|
|
723
|
+
const body = new ReadableStream({
|
|
724
|
+
start(controller) {
|
|
725
|
+
let ended = false;
|
|
726
|
+
const encoder = new TextEncoder();
|
|
727
|
+
const send = (event, data) => {
|
|
728
|
+
if (ended) return;
|
|
729
|
+
if ((controller.desiredSize ?? 0) <= 0) {
|
|
730
|
+
disconnect();
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
controller.enqueue(encoder.encode(`event: ${event}
|
|
734
|
+
data: ${JSON.stringify(data)}
|
|
735
|
+
|
|
736
|
+
`));
|
|
737
|
+
};
|
|
738
|
+
const { turn, unsubscribe } = live.subscribe((update) => {
|
|
739
|
+
if (!account.authenticated(token)) {
|
|
740
|
+
disconnect();
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
send(update.type, update);
|
|
744
|
+
}, () => disconnect());
|
|
745
|
+
const heartbeat = setInterval(() => {
|
|
746
|
+
if (!account.authenticated(token)) disconnect();
|
|
747
|
+
else send("heartbeat", {});
|
|
748
|
+
}, 15e3);
|
|
749
|
+
heartbeat.unref();
|
|
750
|
+
const aborted = () => disconnect();
|
|
751
|
+
disconnect = (close = true) => {
|
|
752
|
+
if (ended) return;
|
|
753
|
+
ended = true;
|
|
754
|
+
clearInterval(heartbeat);
|
|
755
|
+
unsubscribe();
|
|
756
|
+
signal.removeEventListener("abort", aborted);
|
|
757
|
+
if (close) controller.close();
|
|
758
|
+
};
|
|
759
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
760
|
+
if (signal.aborted) disconnect();
|
|
761
|
+
else send("snapshot", { turn, now: Date.now() });
|
|
762
|
+
},
|
|
763
|
+
cancel() {
|
|
764
|
+
disconnect(false);
|
|
765
|
+
}
|
|
766
|
+
}, new ByteLengthQueuingStrategy({ highWaterMark: c.env.outgoing.writableHighWaterMark }));
|
|
767
|
+
return c.body(body, 200, { "Content-Type": "text/event-stream", Connection: "keep-alive", "X-Accel-Buffering": "no" });
|
|
768
|
+
});
|
|
769
|
+
return routes;
|
|
770
|
+
}
|
|
771
|
+
|
|
688
772
|
// src/server/api/app.ts
|
|
689
773
|
function createHttpApp(services, webRoot) {
|
|
690
|
-
const app = new
|
|
774
|
+
const app = new Hono10();
|
|
691
775
|
app.use("*", headers);
|
|
692
776
|
app.onError(handleError);
|
|
693
|
-
const api = new
|
|
777
|
+
const api = new Hono10();
|
|
694
778
|
api.use("*", apiSecurity);
|
|
695
779
|
installAuthentication(api, services.account);
|
|
696
780
|
api.route("/bots", botRoutes(services.bots));
|
|
@@ -699,6 +783,7 @@ function createHttpApp(services, webRoot) {
|
|
|
699
783
|
api.route("/attachments", attachmentRoutes(services.chats));
|
|
700
784
|
api.route("/", modelRoutes(services.models));
|
|
701
785
|
api.route("/", eventRoutes(services));
|
|
786
|
+
api.route("/", liveRoutes(services));
|
|
702
787
|
api.all("*", () => {
|
|
703
788
|
throw new HTTPException9(404, { message: "API route not found" });
|
|
704
789
|
});
|
|
@@ -3092,14 +3177,14 @@ function messageTargets({ dm, sender, members, mentioned, replied, subscribers }
|
|
|
3092
3177
|
if (dm) return members.filter((member) => member.id !== sender.id).map((member) => ({ member, reason: "dm" }));
|
|
3093
3178
|
const targets = mentioned.filter((member) => member.id !== sender.id).map((member) => ({ member, reason: "mention" }));
|
|
3094
3179
|
const add = (member, reason) => {
|
|
3095
|
-
if (member.id !== sender.id && !targets.some((
|
|
3180
|
+
if (member.id !== sender.id && !targets.some((target2) => target2.member.id === member.id)) targets.push({ member, reason });
|
|
3096
3181
|
};
|
|
3097
3182
|
if (replied && members.some((member) => member.id === replied.id)) add(replied, "reply");
|
|
3098
3183
|
for (const member of members) if (subscribers.includes(member.id)) add(member, "subscription");
|
|
3099
3184
|
return targets;
|
|
3100
3185
|
}
|
|
3101
3186
|
function echoTargets({ sender, members, subscribers, reached }) {
|
|
3102
|
-
return members.filter((member) => member.id !== sender.id && subscribers.includes(member.id) && !reached.some((
|
|
3187
|
+
return members.filter((member) => member.id !== sender.id && subscribers.includes(member.id) && !reached.some((target2) => target2.member.id === member.id)).map((member) => ({ member, reason: "subscription" }));
|
|
3103
3188
|
}
|
|
3104
3189
|
function reactionTargets({ actorId, author }) {
|
|
3105
3190
|
return author && author.id !== actorId ? [{ member: author, reason: "author" }] : [];
|
|
@@ -3126,9 +3211,9 @@ function mutedUrgency(reason, urgency) {
|
|
|
3126
3211
|
|
|
3127
3212
|
// src/server/service/delivery/delivery.ts
|
|
3128
3213
|
function plan(kind, targets, muted = []) {
|
|
3129
|
-
return targets.flatMap((
|
|
3130
|
-
const urgency = urgencyOf(kind,
|
|
3131
|
-
return urgency ? [{ ...
|
|
3214
|
+
return targets.flatMap((target2) => {
|
|
3215
|
+
const urgency = urgencyOf(kind, target2.reason);
|
|
3216
|
+
return urgency ? [{ ...target2, kind, urgency: muted.includes(target2.member.id) ? mutedUrgency(target2.reason, urgency) : urgency }] : [];
|
|
3132
3217
|
});
|
|
3133
3218
|
}
|
|
3134
3219
|
var Dispatcher = class {
|
|
@@ -3650,9 +3735,9 @@ var ChatService = class {
|
|
|
3650
3735
|
/** A DM with another member of the actor's space (a Bot DMs its owner or another Bot); one per pair. */
|
|
3651
3736
|
openDirect(actorId, targetId) {
|
|
3652
3737
|
const owner = this.repository.channels.ownerOf(actorId), directory = this.repository.channels.directory(owner);
|
|
3653
|
-
const actor = directory.find((member) => member.id === actorId),
|
|
3654
|
-
if (!actor || !
|
|
3655
|
-
const chat = this.repository.openDirect(actorId, targetId, owner, `${actor.name} \xB7 ${
|
|
3738
|
+
const actor = directory.find((member) => member.id === actorId), target2 = directory.find((member) => member.id === targetId);
|
|
3739
|
+
if (!actor || !target2) throw new ChatError(404, "Member not found");
|
|
3740
|
+
const chat = this.repository.openDirect(actorId, targetId, owner, `${actor.name} \xB7 ${target2.name}`);
|
|
3656
3741
|
this.notifySaved(chat.id);
|
|
3657
3742
|
return chat;
|
|
3658
3743
|
}
|
|
@@ -3964,8 +4049,8 @@ var Changes = class {
|
|
|
3964
4049
|
// src/server/service/bot/activity.ts
|
|
3965
4050
|
function activityPage(messages, history, limit) {
|
|
3966
4051
|
const candidates2 = [
|
|
3967
|
-
...messages.map((
|
|
3968
|
-
...history.map((
|
|
4052
|
+
...messages.map((record2) => ({ kind: "chat", record: record2 })),
|
|
4053
|
+
...history.map((record2) => ({ kind: "history", record: record2 }))
|
|
3969
4054
|
];
|
|
3970
4055
|
candidates2.sort((a, b) => {
|
|
3971
4056
|
if (a.record.timestamp !== b.record.timestamp) return b.record.timestamp - a.record.timestamp;
|
|
@@ -4121,7 +4206,74 @@ function configureHttp(timeoutMs) {
|
|
|
4121
4206
|
|
|
4122
4207
|
// src/server/infra/pi/session.ts
|
|
4123
4208
|
import { Type } from "typebox";
|
|
4209
|
+
|
|
4210
|
+
// src/server/infra/pi/live.ts
|
|
4211
|
+
var LiveTranslator = class {
|
|
4212
|
+
message = 0;
|
|
4213
|
+
open = /* @__PURE__ */ new Set();
|
|
4214
|
+
translate(event) {
|
|
4215
|
+
switch (event.type) {
|
|
4216
|
+
case "message_start":
|
|
4217
|
+
if (event.message.role === "assistant") this.message++;
|
|
4218
|
+
return [];
|
|
4219
|
+
case "message_update": {
|
|
4220
|
+
const update = event.assistantMessageEvent;
|
|
4221
|
+
if (update.type !== "thinking_start" && update.type !== "thinking_delta" && update.type !== "thinking_end" && update.type !== "text_start" && update.type !== "text_delta" && update.type !== "text_end") return [];
|
|
4222
|
+
const key = `${this.message}:${update.contentIndex}`, kind = update.type.startsWith("thinking") ? "thinking" : "text";
|
|
4223
|
+
const events = [];
|
|
4224
|
+
if (!this.open.has(key) && !update.type.endsWith("_end")) {
|
|
4225
|
+
this.open.add(key);
|
|
4226
|
+
events.push({ type: "block_start", key, kind });
|
|
4227
|
+
}
|
|
4228
|
+
if (update.type.endsWith("_delta") && "delta" in update && update.delta) events.push({ type: "block_delta", key, kind, delta: update.delta });
|
|
4229
|
+
if (update.type.endsWith("_end") && this.open.delete(key)) events.push({ type: "block_end", key });
|
|
4230
|
+
return events;
|
|
4231
|
+
}
|
|
4232
|
+
case "message_end": {
|
|
4233
|
+
if (event.message.role !== "assistant") return [];
|
|
4234
|
+
const prefix = `${this.message}:`, closing = [...this.open].filter((key) => key.startsWith(prefix));
|
|
4235
|
+
for (const key of closing) this.open.delete(key);
|
|
4236
|
+
return closing.map((key) => ({ type: "block_end", key }));
|
|
4237
|
+
}
|
|
4238
|
+
case "tool_execution_start":
|
|
4239
|
+
return [{ type: "tool_start", id: event.toolCallId, parentId: event.parentToolCallId ?? null, name: event.toolName, args: event.args }];
|
|
4240
|
+
case "tool_execution_end":
|
|
4241
|
+
return [{ type: "tool_end", id: event.toolCallId, output: resultText(event.result), failed: event.isError }];
|
|
4242
|
+
case "compaction_start":
|
|
4243
|
+
return [{ type: "compaction_start" }];
|
|
4244
|
+
case "compaction_end":
|
|
4245
|
+
return [{
|
|
4246
|
+
type: "compaction_end",
|
|
4247
|
+
failed: !!event.errorMessage || event.aborted,
|
|
4248
|
+
error: event.errorMessage ?? null,
|
|
4249
|
+
tokensBefore: event.result?.tokensBefore ?? null,
|
|
4250
|
+
tokensAfter: event.result?.estimatedTokensAfter ?? null
|
|
4251
|
+
}];
|
|
4252
|
+
case "auto_retry_start":
|
|
4253
|
+
return [{ type: "retry_start", attempt: event.attempt, maxAttempts: event.maxAttempts, error: event.errorMessage }];
|
|
4254
|
+
case "auto_retry_end":
|
|
4255
|
+
return [{ type: "retry_end", failed: !event.success, error: event.finalError ?? null }];
|
|
4256
|
+
default:
|
|
4257
|
+
return [];
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
};
|
|
4261
|
+
function resultText(result) {
|
|
4262
|
+
const content2 = result?.content;
|
|
4263
|
+
if (Array.isArray(content2)) return content2.map((part) => part?.type === "text" ? String(part.text ?? "") : part?.type === "image" ? "[image]" : "").filter(Boolean).join("\n");
|
|
4264
|
+
if (typeof result === "string") return result;
|
|
4265
|
+
try {
|
|
4266
|
+
return JSON.stringify(result) ?? "";
|
|
4267
|
+
} catch {
|
|
4268
|
+
return String(result);
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
// src/server/infra/pi/session.ts
|
|
4124
4273
|
var HTTP_IDLE_TIMEOUT_MS = 9e5;
|
|
4274
|
+
function compactionReserve(contextWindow) {
|
|
4275
|
+
return Math.min(65536, Math.max(16384, Math.min(32768, Math.floor(contextWindow / 4)), Math.floor(contextWindow * 0.13)));
|
|
4276
|
+
}
|
|
4125
4277
|
var PiSession = class _PiSession {
|
|
4126
4278
|
constructor(options, models, credentials) {
|
|
4127
4279
|
this.options = options;
|
|
@@ -4129,6 +4281,7 @@ var PiSession = class _PiSession {
|
|
|
4129
4281
|
this.credentials = credentials;
|
|
4130
4282
|
}
|
|
4131
4283
|
session;
|
|
4284
|
+
settings;
|
|
4132
4285
|
unsubscribe;
|
|
4133
4286
|
unsubscribeHistory;
|
|
4134
4287
|
promptActive = false;
|
|
@@ -4140,6 +4293,7 @@ var PiSession = class _PiSession {
|
|
|
4140
4293
|
abortFailure;
|
|
4141
4294
|
result = { outcome: "completed" };
|
|
4142
4295
|
signature = "";
|
|
4296
|
+
translator = new LiveTranslator();
|
|
4143
4297
|
get context() {
|
|
4144
4298
|
const usage = this.session.getContextUsage();
|
|
4145
4299
|
return usage ? { tokens: usage.tokens, contextWindow: usage.contextWindow, percent: usage.percent } : void 0;
|
|
@@ -4185,7 +4339,10 @@ var PiSession = class _PiSession {
|
|
|
4185
4339
|
const sessionManager = SessionManager.open(reference.file, sessionsDir, bot.workspacePath);
|
|
4186
4340
|
const adapter = new _PiSession(options, models, credentials);
|
|
4187
4341
|
configureHttp(HTTP_IDLE_TIMEOUT_MS);
|
|
4188
|
-
const settingsManager = SettingsManager.inMemory({
|
|
4342
|
+
const settingsManager = SettingsManager.inMemory({
|
|
4343
|
+
httpIdleTimeoutMs: HTTP_IDLE_TIMEOUT_MS,
|
|
4344
|
+
compaction: { reserveTokens: compactionReserve(model.contextWindow) }
|
|
4345
|
+
});
|
|
4189
4346
|
const tools = ["bash", "read", "edit", "write", "codemode", ...options.tools.map((tool) => tool.name)];
|
|
4190
4347
|
const resourceLoader = new DefaultResourceLoader({
|
|
4191
4348
|
cwd: bot.workspacePath,
|
|
@@ -4225,6 +4382,7 @@ var PiSession = class _PiSession {
|
|
|
4225
4382
|
tools
|
|
4226
4383
|
});
|
|
4227
4384
|
adapter.session = result.session;
|
|
4385
|
+
adapter.settings = settingsManager;
|
|
4228
4386
|
if (result.extensionsResult.errors.length || !tools.every((name2) => adapter.session.getActiveToolNames().includes(name2))) {
|
|
4229
4387
|
adapter.session.dispose();
|
|
4230
4388
|
throw new RuntimeFault("tools_missing", "Couldn't load the run's tools.");
|
|
@@ -4238,8 +4396,8 @@ var PiSession = class _PiSession {
|
|
|
4238
4396
|
for (let index = entries.length - 1; index >= 0; index--) {
|
|
4239
4397
|
const entry = entries[index];
|
|
4240
4398
|
if (entry.type !== "message" || entry.message !== event.message) continue;
|
|
4241
|
-
const
|
|
4242
|
-
if (
|
|
4399
|
+
const record2 = history.convert(entry);
|
|
4400
|
+
if (record2) await options.onHistory(record2);
|
|
4243
4401
|
break;
|
|
4244
4402
|
}
|
|
4245
4403
|
});
|
|
@@ -4281,6 +4439,7 @@ var PiSession = class _PiSession {
|
|
|
4281
4439
|
}
|
|
4282
4440
|
);
|
|
4283
4441
|
if (event.type === "message_end" && event.message.role === "assistant") this.result = this.classify(event.message);
|
|
4442
|
+
if (this.options.live) for (const live of this.translator.translate(event)) this.options.live(live);
|
|
4284
4443
|
this.maybeAbort();
|
|
4285
4444
|
}
|
|
4286
4445
|
/** The last assistant message decides the prompt (a later retry success replaces an earlier error).
|
|
@@ -4324,17 +4483,24 @@ var PiSession = class _PiSession {
|
|
|
4324
4483
|
const settled = new Promise((resolve6) => {
|
|
4325
4484
|
this.settledResolve = resolve6;
|
|
4326
4485
|
});
|
|
4486
|
+
this.options.live?.({ type: "start" });
|
|
4487
|
+
let outcome = "failed";
|
|
4327
4488
|
try {
|
|
4328
|
-
|
|
4489
|
+
try {
|
|
4490
|
+
await this.session.prompt(xml, { expandPromptTemplates: false });
|
|
4491
|
+
} finally {
|
|
4492
|
+
await this.session.waitForIdle();
|
|
4493
|
+
if (this.started && !this.settled) await settled;
|
|
4494
|
+
this.promptActive = false;
|
|
4495
|
+
await this.abortTask;
|
|
4496
|
+
this.options.store.runtime.recordSession(this.options.bot.id, this.sessionId, existsSync(this.sessionFile));
|
|
4497
|
+
}
|
|
4498
|
+
if (this.abortFailure) throw this.abortFailure;
|
|
4499
|
+
outcome = this.result.outcome;
|
|
4500
|
+
return this.result;
|
|
4329
4501
|
} finally {
|
|
4330
|
-
|
|
4331
|
-
if (this.started && !this.settled) await settled;
|
|
4332
|
-
this.promptActive = false;
|
|
4333
|
-
await this.abortTask;
|
|
4334
|
-
this.options.store.runtime.recordSession(this.options.bot.id, this.sessionId, existsSync(this.sessionFile));
|
|
4502
|
+
this.options.live?.({ type: "end", outcome });
|
|
4335
4503
|
}
|
|
4336
|
-
if (this.abortFailure) throw this.abortFailure;
|
|
4337
|
-
return this.result;
|
|
4338
4504
|
}
|
|
4339
4505
|
async configure(selection) {
|
|
4340
4506
|
const connection = this.options.store.getModelConnection(selection.connectionId), configured2 = connection?.models.find((item) => item.id === selection.modelId);
|
|
@@ -4351,6 +4517,7 @@ var PiSession = class _PiSession {
|
|
|
4351
4517
|
this.credentials.current = next;
|
|
4352
4518
|
try {
|
|
4353
4519
|
await this.session.setModel(model, { persist: false });
|
|
4520
|
+
this.settings.applyOverrides({ compaction: { reserveTokens: compactionReserve(model.contextWindow) } });
|
|
4354
4521
|
this.session.setThinkingLevel(resolveThinking(model, selection.thinkingLevel), { persist: false });
|
|
4355
4522
|
this.signature = signature;
|
|
4356
4523
|
return this.session.thinkingLevel;
|
|
@@ -4440,17 +4607,17 @@ var HistoryRepository = class {
|
|
|
4440
4607
|
const row = this.db.prepare("SELECT * FROM history WHERE id=?").get(id);
|
|
4441
4608
|
return row ? this.decode(row) : null;
|
|
4442
4609
|
}
|
|
4443
|
-
links(
|
|
4610
|
+
links(record2) {
|
|
4444
4611
|
const links = [];
|
|
4445
|
-
for (const block of
|
|
4612
|
+
for (const block of record2.message.content) {
|
|
4446
4613
|
if (block.type !== "tool_call" && block.type !== "tool_result") continue;
|
|
4447
4614
|
const callId = block.tool_call_id;
|
|
4448
4615
|
if (block.type === "tool_result") {
|
|
4449
|
-
const call = this.db.prepare(`SELECT id,role FROM history WHERE id<? AND ${CALL} ORDER BY id DESC LIMIT 1`).get(
|
|
4616
|
+
const call = this.db.prepare(`SELECT id,role FROM history WHERE id<? AND ${CALL} ORDER BY id DESC LIMIT 1`).get(record2.id, "tool_call", callId);
|
|
4450
4617
|
if (call) links.push({ ...call, tool_call_id: callId });
|
|
4451
4618
|
} else {
|
|
4452
|
-
const next = this.db.prepare(`SELECT id FROM history WHERE id>? AND ${CALL} ORDER BY id LIMIT 1`).get(
|
|
4453
|
-
const results = this.db.prepare(`SELECT id,role FROM history WHERE id>? AND id<? AND ${CALL} ORDER BY id`).all(
|
|
4619
|
+
const next = this.db.prepare(`SELECT id FROM history WHERE id>? AND ${CALL} ORDER BY id LIMIT 1`).get(record2.id, "tool_call", callId);
|
|
4620
|
+
const results = this.db.prepare(`SELECT id,role FROM history WHERE id>? AND id<? AND ${CALL} ORDER BY id`).all(record2.id, next?.id ?? Number.MAX_SAFE_INTEGER, "tool_result", callId);
|
|
4454
4621
|
links.push(...results.map((result) => ({ ...result, tool_call_id: callId })));
|
|
4455
4622
|
}
|
|
4456
4623
|
}
|
|
@@ -4494,14 +4661,14 @@ var Memory = class {
|
|
|
4494
4661
|
return this.history().usage(since);
|
|
4495
4662
|
}
|
|
4496
4663
|
read(id) {
|
|
4497
|
-
const
|
|
4498
|
-
return
|
|
4664
|
+
const record2 = this.history().read(id);
|
|
4665
|
+
return record2 ? { record: record2, links: this.history().links(record2) } : null;
|
|
4499
4666
|
}
|
|
4500
4667
|
readMany(ids, mode) {
|
|
4501
4668
|
const found = mode === "summary" ? this.history().summaries(ids).map((item) => item && summaryLine(item)) : this.history().details(ids);
|
|
4502
4669
|
const result = { mode, records: [], missingIds: [] };
|
|
4503
|
-
found.forEach((
|
|
4504
|
-
if (
|
|
4670
|
+
found.forEach((record2, index) => {
|
|
4671
|
+
if (record2) result.records.push(record2);
|
|
4505
4672
|
else result.missingIds.push(ids[index]);
|
|
4506
4673
|
});
|
|
4507
4674
|
return result;
|
|
@@ -4629,17 +4796,17 @@ var Bot = class extends Member {
|
|
|
4629
4796
|
workspace: this.workspace.location(),
|
|
4630
4797
|
tools: this.tools.definitions(),
|
|
4631
4798
|
onStatus: (status) => this.updateState(status),
|
|
4632
|
-
onHistory: (
|
|
4799
|
+
onHistory: (record2) => this.recordHistory(record2)
|
|
4633
4800
|
};
|
|
4634
4801
|
}
|
|
4635
4802
|
updateState(status) {
|
|
4636
4803
|
this.state.update(status);
|
|
4637
4804
|
}
|
|
4638
|
-
async recordHistory(
|
|
4639
|
-
const historyId = await this.memory.append(
|
|
4640
|
-
if (
|
|
4805
|
+
async recordHistory(record2) {
|
|
4806
|
+
const historyId = await this.memory.append(record2);
|
|
4807
|
+
if (record2.role !== "assistant") return;
|
|
4641
4808
|
this.callHistory.clear();
|
|
4642
|
-
for (const block of
|
|
4809
|
+
for (const block of record2.message.content) {
|
|
4643
4810
|
if (block.type === "tool_call") this.callHistory.set(block.tool_call_id, historyId);
|
|
4644
4811
|
}
|
|
4645
4812
|
}
|
|
@@ -4798,20 +4965,20 @@ ${content(block.result.content, heading + "#")}`;
|
|
|
4798
4965
|
function formatHistory(result, format) {
|
|
4799
4966
|
if (format === "json") return JSON.stringify(result, null, 2);
|
|
4800
4967
|
const sections = [`# History (${result.mode})`];
|
|
4801
|
-
for (const
|
|
4802
|
-
sections.push(`## #${
|
|
4803
|
-
if (
|
|
4968
|
+
for (const record2 of result.records) {
|
|
4969
|
+
sections.push(`## #${record2.id} \xB7 ${record2.role}`, `Time: ${new Date(record2.timestamp).toISOString()}`);
|
|
4970
|
+
if (record2.model) sections.push(`Model:
|
|
4804
4971
|
|
|
4805
|
-
${jsonBlock(
|
|
4806
|
-
if ("preview" in
|
|
4807
|
-
sections.push(fence(
|
|
4972
|
+
${jsonBlock(record2.model)}`);
|
|
4973
|
+
if ("preview" in record2) {
|
|
4974
|
+
sections.push(fence(record2.preview));
|
|
4808
4975
|
continue;
|
|
4809
4976
|
}
|
|
4810
|
-
sections.push(content(
|
|
4811
|
-
if (
|
|
4812
|
-
if (
|
|
4813
|
-
if (
|
|
4814
|
-
if (
|
|
4977
|
+
sections.push(content(record2.message.content));
|
|
4978
|
+
if (record2.message.tools !== void 0) sections.push("### Tool definitions", jsonBlock(record2.message.tools));
|
|
4979
|
+
if (record2.message.tools_removed !== void 0) sections.push("### Tools removed", jsonBlock(record2.message.tools_removed));
|
|
4980
|
+
if (record2.message.stop_reason !== void 0) sections.push("### Stop reason", fence(record2.message.stop_reason));
|
|
4981
|
+
if (record2.message.error !== void 0) sections.push("### Error", fence(record2.message.error));
|
|
4815
4982
|
}
|
|
4816
4983
|
if (result.missingIds.length) sections.push(`## Missing IDs
|
|
4817
4984
|
|
|
@@ -5739,34 +5906,288 @@ When something will take a while, post a short progress note partway through, an
|
|
|
5739
5906
|
`;
|
|
5740
5907
|
}
|
|
5741
5908
|
|
|
5909
|
+
// src/server/shared/live.ts
|
|
5910
|
+
var LIVE_STEPS = 300;
|
|
5911
|
+
var LIVE_TEXT = 4e3;
|
|
5912
|
+
var LIVE_OUTPUT_END = 1200;
|
|
5913
|
+
var LIVE_INPUT = 300;
|
|
5914
|
+
var LIVE_MESSAGE = 2e3;
|
|
5915
|
+
|
|
5916
|
+
// src/server/service/bot/live-input.ts
|
|
5917
|
+
function clip(text3, length) {
|
|
5918
|
+
return text3.length > length ? `${text3.slice(0, length - 1)}\u2026` : text3;
|
|
5919
|
+
}
|
|
5920
|
+
var field = (value2, length = LIVE_INPUT) => typeof value2 === "string" ? clip(value2, length) : void 0;
|
|
5921
|
+
var record = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
|
|
5922
|
+
function liveInput(tool, args, chat) {
|
|
5923
|
+
const values = record(args);
|
|
5924
|
+
switch (tool) {
|
|
5925
|
+
case "bash":
|
|
5926
|
+
return { command: field(values.command) };
|
|
5927
|
+
case "read":
|
|
5928
|
+
case "edit":
|
|
5929
|
+
case "write":
|
|
5930
|
+
return { path: field(values.path) };
|
|
5931
|
+
case "codemode":
|
|
5932
|
+
return { command: field(values.code) };
|
|
5933
|
+
case "chat":
|
|
5934
|
+
return gateway(values, chat);
|
|
5935
|
+
default: {
|
|
5936
|
+
let json2 = "";
|
|
5937
|
+
try {
|
|
5938
|
+
json2 = JSON.stringify(args) ?? "";
|
|
5939
|
+
} catch {
|
|
5940
|
+
}
|
|
5941
|
+
return json2 ? { args: clip(json2, LIVE_INPUT) } : {};
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5944
|
+
}
|
|
5945
|
+
function gateway(values, chat) {
|
|
5946
|
+
const action = values.action === "call" && typeof values.tool === "string" ? values.tool : typeof values.action === "string" ? values.action : "";
|
|
5947
|
+
const inner = record(values.args), where = target(inner, chat);
|
|
5948
|
+
if (action === "messages.send") return { action, chat: where, text: field(inner.message, LIVE_MESSAGE) };
|
|
5949
|
+
if (action === "messages.ask") return {
|
|
5950
|
+
action,
|
|
5951
|
+
chat: where,
|
|
5952
|
+
text: field(inner.question, LIVE_MESSAGE),
|
|
5953
|
+
options: Array.isArray(inner.options) ? inner.options.map((option2) => field(record(option2).label) ?? "").filter(Boolean) : void 0
|
|
5954
|
+
};
|
|
5955
|
+
return where ? { action, chat: where } : { action };
|
|
5956
|
+
}
|
|
5957
|
+
function target(inner, chat) {
|
|
5958
|
+
if (typeof inner.to === "string") return { id: null, type: "dm", title: clip(inner.to, 80) };
|
|
5959
|
+
return typeof inner.chatId === "string" ? chat(inner.chatId) ?? void 0 : void 0;
|
|
5960
|
+
}
|
|
5961
|
+
function sentChat(output, chat) {
|
|
5962
|
+
try {
|
|
5963
|
+
const chatId = JSON.parse(output).chatId;
|
|
5964
|
+
return typeof chatId === "string" ? chat(chatId) ?? void 0 : void 0;
|
|
5965
|
+
} catch {
|
|
5966
|
+
return void 0;
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
|
|
5970
|
+
// src/server/service/bot/live.ts
|
|
5971
|
+
var FLUSH_MS = 100;
|
|
5972
|
+
function liveOutput(text3) {
|
|
5973
|
+
if (!text3) return null;
|
|
5974
|
+
if (text3.length <= 2 * LIVE_OUTPUT_END) return { head: text3, tail: "", omitted: 0 };
|
|
5975
|
+
return { head: text3.slice(0, LIVE_OUTPUT_END), tail: text3.slice(-LIVE_OUTPUT_END), omitted: text3.length - 2 * LIVE_OUTPUT_END };
|
|
5976
|
+
}
|
|
5977
|
+
var LiveTurns = class {
|
|
5978
|
+
constructor(chat) {
|
|
5979
|
+
this.chat = chat;
|
|
5980
|
+
}
|
|
5981
|
+
turn = null;
|
|
5982
|
+
steps = /* @__PURE__ */ new Map();
|
|
5983
|
+
blocks = /* @__PURE__ */ new Map();
|
|
5984
|
+
compaction = null;
|
|
5985
|
+
retry = null;
|
|
5986
|
+
counter = 0;
|
|
5987
|
+
pending = [];
|
|
5988
|
+
timer = null;
|
|
5989
|
+
listeners = /* @__PURE__ */ new Map();
|
|
5990
|
+
/** Listens from now on and returns the turn so far (null when the Bot is not running one). Pending changes go to
|
|
5991
|
+
* the earlier listeners first, so the snapshot and the next patch never overlap. */
|
|
5992
|
+
subscribe(listener, close) {
|
|
5993
|
+
this.flush();
|
|
5994
|
+
this.listeners.set(listener, close);
|
|
5995
|
+
return { turn: this.turn ? structuredClone(this.turn) : null, unsubscribe: () => {
|
|
5996
|
+
this.listeners.delete(listener);
|
|
5997
|
+
} };
|
|
5998
|
+
}
|
|
5999
|
+
/** Ends every listener's stream (the Bot was deleted, or the server is stopping). */
|
|
6000
|
+
close() {
|
|
6001
|
+
for (const close of [...this.listeners.values()]) close();
|
|
6002
|
+
this.listeners.clear();
|
|
6003
|
+
}
|
|
6004
|
+
event(event) {
|
|
6005
|
+
if (event.type === "start") {
|
|
6006
|
+
this.flush();
|
|
6007
|
+
this.reset();
|
|
6008
|
+
this.turn = { startedAt: Date.now(), steps: [], dropped: 0 };
|
|
6009
|
+
this.emit({ type: "start", turn: structuredClone(this.turn) });
|
|
6010
|
+
return;
|
|
6011
|
+
}
|
|
6012
|
+
if (!this.turn) return;
|
|
6013
|
+
switch (event.type) {
|
|
6014
|
+
case "end": {
|
|
6015
|
+
for (const step of this.turn.steps) if (step.state === "running") this.finish(step, event.outcome !== "completed");
|
|
6016
|
+
this.flush();
|
|
6017
|
+
this.emit({ type: "end", outcome: event.outcome, endedAt: Date.now() });
|
|
6018
|
+
this.reset();
|
|
6019
|
+
return;
|
|
6020
|
+
}
|
|
6021
|
+
case "block_start":
|
|
6022
|
+
this.blocks.set(event.key, this.add({ kind: event.kind, text: "", omitted: 0 }).id);
|
|
6023
|
+
return;
|
|
6024
|
+
case "block_delta": {
|
|
6025
|
+
let id = this.blocks.get(event.key);
|
|
6026
|
+
if (!id) {
|
|
6027
|
+
id = this.add({ kind: event.kind, text: "", omitted: 0 }).id;
|
|
6028
|
+
this.blocks.set(event.key, id);
|
|
6029
|
+
}
|
|
6030
|
+
const step = this.steps.get(id);
|
|
6031
|
+
if (!step) return;
|
|
6032
|
+
const text3 = (step.text ?? "") + event.delta, over = Math.max(0, text3.length - LIVE_TEXT);
|
|
6033
|
+
step.text = text3.slice(over);
|
|
6034
|
+
step.omitted = (step.omitted ?? 0) + over;
|
|
6035
|
+
this.queue({ op: "text", id, append: event.delta });
|
|
6036
|
+
return;
|
|
6037
|
+
}
|
|
6038
|
+
case "block_end": {
|
|
6039
|
+
const step = this.steps.get(this.blocks.get(event.key) ?? "");
|
|
6040
|
+
if (step) this.finish(step, false);
|
|
6041
|
+
this.blocks.delete(event.key);
|
|
6042
|
+
return;
|
|
6043
|
+
}
|
|
6044
|
+
case "tool_start":
|
|
6045
|
+
this.add({ id: event.id, kind: "tool", parentId: event.parentId, tool: event.name, input: liveInput(event.name, event.args, this.chat) });
|
|
6046
|
+
return;
|
|
6047
|
+
case "tool_end": {
|
|
6048
|
+
const step = this.steps.get(event.id);
|
|
6049
|
+
if (!step) return;
|
|
6050
|
+
step.output = liveOutput(event.output);
|
|
6051
|
+
if (step.input?.action === "messages.send" && !event.failed) step.input.chat = sentChat(event.output, this.chat) ?? step.input.chat;
|
|
6052
|
+
this.finish(step, event.failed);
|
|
6053
|
+
return;
|
|
6054
|
+
}
|
|
6055
|
+
case "compaction_start":
|
|
6056
|
+
this.compaction = this.add({ kind: "compaction" }).id;
|
|
6057
|
+
return;
|
|
6058
|
+
case "compaction_end": {
|
|
6059
|
+
const step = this.steps.get(this.compaction ?? "");
|
|
6060
|
+
this.compaction = null;
|
|
6061
|
+
if (!step) return;
|
|
6062
|
+
step.tokensBefore = event.tokensBefore;
|
|
6063
|
+
step.tokensAfter = event.tokensAfter;
|
|
6064
|
+
step.output = event.error ? liveOutput(event.error) : null;
|
|
6065
|
+
this.finish(step, event.failed);
|
|
6066
|
+
return;
|
|
6067
|
+
}
|
|
6068
|
+
case "retry_start": {
|
|
6069
|
+
const previous = this.steps.get(this.retry ?? "");
|
|
6070
|
+
if (previous?.state === "running") this.finish(previous, true);
|
|
6071
|
+
this.retry = this.add({ kind: "retry", attempt: event.attempt, maxAttempts: event.maxAttempts, output: liveOutput(event.error) }).id;
|
|
6072
|
+
return;
|
|
6073
|
+
}
|
|
6074
|
+
case "retry_end": {
|
|
6075
|
+
const step = this.steps.get(this.retry ?? "");
|
|
6076
|
+
this.retry = null;
|
|
6077
|
+
if (step) {
|
|
6078
|
+
if (event.error) step.output = liveOutput(event.error);
|
|
6079
|
+
this.finish(step, event.failed);
|
|
6080
|
+
}
|
|
6081
|
+
return;
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
add(fields) {
|
|
6086
|
+
const turn = this.turn;
|
|
6087
|
+
if (turn.steps.length >= LIVE_STEPS) {
|
|
6088
|
+
const index = Math.max(0, turn.steps.findIndex((step2) => step2.state !== "running")), [gone] = turn.steps.splice(index, 1);
|
|
6089
|
+
this.steps.delete(gone.id);
|
|
6090
|
+
turn.dropped++;
|
|
6091
|
+
this.queue({ op: "drop", id: gone.id });
|
|
6092
|
+
}
|
|
6093
|
+
const step = { id: `s${++this.counter}`, state: "running", startedAt: Date.now(), endedAt: null, parentId: null, ...fields };
|
|
6094
|
+
turn.steps.push(step);
|
|
6095
|
+
this.steps.set(step.id, step);
|
|
6096
|
+
this.queue({ op: "step", step: structuredClone(step) });
|
|
6097
|
+
return step;
|
|
6098
|
+
}
|
|
6099
|
+
finish(step, failed) {
|
|
6100
|
+
step.state = failed ? "failed" : "ok";
|
|
6101
|
+
step.endedAt = Date.now();
|
|
6102
|
+
this.queue({ op: "step", step: structuredClone(step) });
|
|
6103
|
+
}
|
|
6104
|
+
queue(patch) {
|
|
6105
|
+
this.pending.push(patch);
|
|
6106
|
+
this.timer ??= setTimeout(() => this.flush(), FLUSH_MS);
|
|
6107
|
+
}
|
|
6108
|
+
/** Sends the pending changes as one patch: text streamed into the same step is joined, and a step sent whole
|
|
6109
|
+
* replaces what came before it for that step. A step is copied when it is queued, so text streamed after that
|
|
6110
|
+
* follows it as its own patch. */
|
|
6111
|
+
flush() {
|
|
6112
|
+
if (this.timer) {
|
|
6113
|
+
clearTimeout(this.timer);
|
|
6114
|
+
this.timer = null;
|
|
6115
|
+
}
|
|
6116
|
+
if (!this.pending.length) return;
|
|
6117
|
+
const patches = [];
|
|
6118
|
+
for (const patch of this.pending) {
|
|
6119
|
+
const last = patches.at(-1);
|
|
6120
|
+
if (patch.op === "text" && last?.op === "text" && last.id === patch.id) {
|
|
6121
|
+
last.append += patch.append;
|
|
6122
|
+
continue;
|
|
6123
|
+
}
|
|
6124
|
+
if (patch.op === "step") {
|
|
6125
|
+
for (let index = patches.length - 1; index >= 0; index--) {
|
|
6126
|
+
const item = patches[index];
|
|
6127
|
+
if ((item.op === "step" ? item.step.id : item.id) === patch.step.id) patches.splice(index, 1);
|
|
6128
|
+
}
|
|
6129
|
+
patches.push(patch);
|
|
6130
|
+
continue;
|
|
6131
|
+
}
|
|
6132
|
+
patches.push({ ...patch });
|
|
6133
|
+
}
|
|
6134
|
+
this.pending = [];
|
|
6135
|
+
this.emit({ type: "patch", patches });
|
|
6136
|
+
}
|
|
6137
|
+
emit(update) {
|
|
6138
|
+
for (const listener of [...this.listeners.keys()]) {
|
|
6139
|
+
try {
|
|
6140
|
+
listener(update);
|
|
6141
|
+
} catch (error) {
|
|
6142
|
+
console.error("[live] listener failed", { error });
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
}
|
|
6146
|
+
reset() {
|
|
6147
|
+
this.turn = null;
|
|
6148
|
+
this.steps.clear();
|
|
6149
|
+
this.blocks.clear();
|
|
6150
|
+
this.compaction = null;
|
|
6151
|
+
this.retry = null;
|
|
6152
|
+
this.counter = 0;
|
|
6153
|
+
this.pending = [];
|
|
6154
|
+
if (this.timer) {
|
|
6155
|
+
clearTimeout(this.timer);
|
|
6156
|
+
this.timer = null;
|
|
6157
|
+
}
|
|
6158
|
+
}
|
|
6159
|
+
};
|
|
6160
|
+
|
|
5742
6161
|
// src/server/service/bot/service.ts
|
|
5743
6162
|
var BotService = class {
|
|
5744
6163
|
constructor(deps) {
|
|
5745
6164
|
this.deps = deps;
|
|
5746
6165
|
}
|
|
5747
6166
|
bots = /* @__PURE__ */ new Map();
|
|
6167
|
+
lives = /* @__PURE__ */ new Map();
|
|
5748
6168
|
closed = false;
|
|
5749
6169
|
started = false;
|
|
5750
6170
|
get(id) {
|
|
5751
6171
|
if (this.closed) throw new RuntimeFault("stopping", "Shutting down.", 503);
|
|
5752
|
-
const
|
|
5753
|
-
if (!
|
|
6172
|
+
const record2 = this.deps.store.getBot(id);
|
|
6173
|
+
if (!record2) throw new RuntimeFault("bot_missing", "Bot not found", 404);
|
|
5754
6174
|
let bot = this.bots.get(id);
|
|
5755
6175
|
if (bot) return bot;
|
|
5756
|
-
const { store, chats, files, credentials } = this.deps;
|
|
6176
|
+
const { store, chats, files, credentials } = this.deps, live = this.live(id);
|
|
5757
6177
|
const runtime = new AgentRuntime(new BotInbox(id, store.runtime, chats, files), (setup, model, reference, activity) => (this.deps.factory ?? PiSession.create)({
|
|
5758
6178
|
store,
|
|
5759
|
-
bot:
|
|
6179
|
+
bot: record2,
|
|
5760
6180
|
selection: model.selection,
|
|
5761
6181
|
reference,
|
|
5762
6182
|
credentials,
|
|
5763
6183
|
tools: setup.tools,
|
|
5764
6184
|
systemPrompt: () => botPrompt(setup),
|
|
5765
6185
|
onHistory: setup.onHistory,
|
|
5766
|
-
activity
|
|
6186
|
+
activity,
|
|
6187
|
+
live: (event) => live.event(event)
|
|
5767
6188
|
}));
|
|
5768
|
-
const workspace = new Workspace(
|
|
5769
|
-
const memory = new Memory(join6(dirname5(
|
|
6189
|
+
const workspace = new Workspace(record2.workspacePath);
|
|
6190
|
+
const memory = new Memory(join6(dirname5(record2.workspacePath), "history.db"));
|
|
5770
6191
|
bot = new Bot(id, {
|
|
5771
6192
|
identity: new Identity(id, store),
|
|
5772
6193
|
model: new Model(id, store, resolveThinking),
|
|
@@ -5776,7 +6197,7 @@ var BotService = class {
|
|
|
5776
6197
|
agentRuntime: runtime,
|
|
5777
6198
|
state: new BotState(() => this.deps.changed(id))
|
|
5778
6199
|
}, { deliver: async (message) => {
|
|
5779
|
-
const outgoing = await files.readOutgoing(
|
|
6200
|
+
const outgoing = await files.readOutgoing(record2.workspacePath, message.attachments ?? [], message.signal);
|
|
5780
6201
|
message.signal?.throwIfAborted();
|
|
5781
6202
|
return chats.deliver(id, message.chatId, {
|
|
5782
6203
|
content: message.content,
|
|
@@ -5792,23 +6213,40 @@ var BotService = class {
|
|
|
5792
6213
|
this.bots.set(id, bot);
|
|
5793
6214
|
return bot;
|
|
5794
6215
|
}
|
|
5795
|
-
details(
|
|
5796
|
-
const model =
|
|
5797
|
-
return { ...
|
|
6216
|
+
details(record2) {
|
|
6217
|
+
const model = record2.connectionId ? this.deps.store.getModelConnection(record2.connectionId)?.models.find((model2) => model2.id === record2.modelId) : void 0;
|
|
6218
|
+
return { ...record2, effectiveThinkingLevel: model ? resolveThinking(model, record2.thinkingLevel) : null };
|
|
5798
6219
|
}
|
|
5799
6220
|
owns(id) {
|
|
5800
6221
|
return this.deps.store.getBot(id) !== null;
|
|
5801
6222
|
}
|
|
6223
|
+
/** The turn a Bot is running, live (A47); kept for a Bot whether or not it has started. */
|
|
6224
|
+
live(id) {
|
|
6225
|
+
if (!this.deps.store.getBot(id)) throw new RuntimeFault("bot_missing", "Bot not found", 404);
|
|
6226
|
+
let live = this.lives.get(id);
|
|
6227
|
+
if (!live) {
|
|
6228
|
+
live = new LiveTurns((chatId) => this.liveChat(id, chatId));
|
|
6229
|
+
this.lives.set(id, live);
|
|
6230
|
+
}
|
|
6231
|
+
return live;
|
|
6232
|
+
}
|
|
6233
|
+
/** A chat as the Bot sees it, for the live turn: a DM is named after the other member. */
|
|
6234
|
+
liveChat(botId, chatId) {
|
|
6235
|
+
const chat = this.deps.chats.get(botId, chatId);
|
|
6236
|
+
if (!chat) return null;
|
|
6237
|
+
const other = chat.type === "dm" ? chat.memberIds.find((member) => member !== botId) : void 0;
|
|
6238
|
+
return { id: chat.id, type: chat.type, title: other ? this.deps.chats.member(other)?.name ?? chat.title : chat.title };
|
|
6239
|
+
}
|
|
5802
6240
|
list() {
|
|
5803
|
-
return this.deps.store.listBots().map((
|
|
6241
|
+
return this.deps.store.listBots().map((record2) => this.details(record2));
|
|
5804
6242
|
}
|
|
5805
6243
|
create(name2) {
|
|
5806
6244
|
return this.details(this.deps.store.createBot(name2));
|
|
5807
6245
|
}
|
|
5808
6246
|
describe(id) {
|
|
5809
|
-
const
|
|
5810
|
-
if (!
|
|
5811
|
-
return this.details(
|
|
6247
|
+
const record2 = this.deps.store.getBot(id);
|
|
6248
|
+
if (!record2) throw new RuntimeFault("bot_missing", "Bot not found", 404);
|
|
6249
|
+
return this.details(record2);
|
|
5812
6250
|
}
|
|
5813
6251
|
runs(id) {
|
|
5814
6252
|
this.describe(id);
|
|
@@ -5856,15 +6294,15 @@ var BotService = class {
|
|
|
5856
6294
|
/** Every Bot's run state, for `bots.list` (A43). A Bot not loaded in this process is stopped. */
|
|
5857
6295
|
roster() {
|
|
5858
6296
|
const { store } = this.deps;
|
|
5859
|
-
return store.listBots().map((
|
|
5860
|
-
const bot = this.bots.get(
|
|
6297
|
+
return store.listBots().map((record2) => {
|
|
6298
|
+
const bot = this.bots.get(record2.id), runId = bot?.agentRuntime.activeRunId ?? null;
|
|
5861
6299
|
const status = bot?.status() ?? { status: "stop", activity: null, context: null, error: null };
|
|
5862
6300
|
return {
|
|
5863
|
-
id:
|
|
5864
|
-
name:
|
|
6301
|
+
id: record2.id,
|
|
6302
|
+
name: record2.name,
|
|
5865
6303
|
status,
|
|
5866
6304
|
runChatIds: status.status === "running" && runId ? store.runtime.inputs(runId).map((input) => input.chatId) : [],
|
|
5867
|
-
errorCode: store.runtime.latestErrorCode(
|
|
6305
|
+
errorCode: store.runtime.latestErrorCode(record2.id)
|
|
5868
6306
|
};
|
|
5869
6307
|
});
|
|
5870
6308
|
}
|
|
@@ -5895,6 +6333,8 @@ var BotService = class {
|
|
|
5895
6333
|
const bot = this.get(id), workspacePath = bot.workspace.location().cwd;
|
|
5896
6334
|
await bot.stop();
|
|
5897
6335
|
this.bots.delete(id);
|
|
6336
|
+
this.lives.get(id)?.close();
|
|
6337
|
+
this.lives.delete(id);
|
|
5898
6338
|
if (!this.deps.store.deleteBot(id)) throw new RuntimeFault("bot_missing", "Bot not found", 404);
|
|
5899
6339
|
if (!keepFiles) await rm2(dirname5(workspacePath), { recursive: true, force: true });
|
|
5900
6340
|
}
|
|
@@ -5902,6 +6342,7 @@ var BotService = class {
|
|
|
5902
6342
|
if (this.closed) return;
|
|
5903
6343
|
this.closed = true;
|
|
5904
6344
|
await Promise.all([...this.bots.values()].map((bot) => bot.stop()));
|
|
6345
|
+
for (const live of this.lives.values()) live.close();
|
|
5905
6346
|
}
|
|
5906
6347
|
};
|
|
5907
6348
|
|
|
@@ -6003,8 +6444,8 @@ import { join as join8, resolve as resolve5 } from "node:path";
|
|
|
6003
6444
|
var pidPath = (dir) => join8(resolve5(dir), "jarvy.pid");
|
|
6004
6445
|
var logPath = (dir) => join8(resolve5(dir), "jarvy.log");
|
|
6005
6446
|
function writePidFile(dir, url) {
|
|
6006
|
-
const
|
|
6007
|
-
writeFileSync(pidPath(dir), JSON.stringify(
|
|
6447
|
+
const record2 = { pid: process.pid, startToken: startToken(process.pid), url, startedAt: Date.now() };
|
|
6448
|
+
writeFileSync(pidPath(dir), JSON.stringify(record2), { mode: 384 });
|
|
6008
6449
|
}
|
|
6009
6450
|
function removePidFile(dir) {
|
|
6010
6451
|
if (readRecord(dir)?.pid === process.pid) rmSync(pidPath(dir), { force: true });
|
|
@@ -6032,14 +6473,14 @@ function alive(pid) {
|
|
|
6032
6473
|
}
|
|
6033
6474
|
}
|
|
6034
6475
|
function runningServer(dir) {
|
|
6035
|
-
const
|
|
6036
|
-
if (!
|
|
6037
|
-
const token = alive(
|
|
6038
|
-
if (token === null || token &&
|
|
6476
|
+
const record2 = readRecord(dir);
|
|
6477
|
+
if (!record2) return null;
|
|
6478
|
+
const token = alive(record2.pid) ? startToken(record2.pid) : null;
|
|
6479
|
+
if (token === null || token && record2.startToken && token !== record2.startToken) {
|
|
6039
6480
|
rmSync(pidPath(dir), { force: true });
|
|
6040
6481
|
return null;
|
|
6041
6482
|
}
|
|
6042
|
-
return
|
|
6483
|
+
return record2;
|
|
6043
6484
|
}
|
|
6044
6485
|
function startBackground(script, runArgs, dir) {
|
|
6045
6486
|
mkdirSync3(resolve5(dir), { recursive: true, mode: 448 });
|
|
@@ -6072,11 +6513,11 @@ function signalReady(url) {
|
|
|
6072
6513
|
process.disconnect();
|
|
6073
6514
|
}
|
|
6074
6515
|
async function stopServer(dir, waitMs = 3e4) {
|
|
6075
|
-
const
|
|
6076
|
-
if (!
|
|
6077
|
-
process.kill(
|
|
6516
|
+
const record2 = runningServer(dir);
|
|
6517
|
+
if (!record2) return "not_running";
|
|
6518
|
+
process.kill(record2.pid, "SIGTERM");
|
|
6078
6519
|
for (const deadline = Date.now() + waitMs; Date.now() < deadline; ) {
|
|
6079
|
-
if (!alive(
|
|
6520
|
+
if (!alive(record2.pid)) {
|
|
6080
6521
|
rmSync(pidPath(dir), { force: true });
|
|
6081
6522
|
return "stopped";
|
|
6082
6523
|
}
|