@vibedeckx/darwin-arm64 0.2.15 → 0.2.17
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/bin.js +1690 -267
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -13356,12 +13356,12 @@ var require_semver = __commonJS({
|
|
|
13356
13356
|
}
|
|
13357
13357
|
function replaceHyphen(comp, options) {
|
|
13358
13358
|
const r = HYPHENRANGE_REGEXP;
|
|
13359
|
-
return comp.replace(r, (_3, from, fM, fm,
|
|
13359
|
+
return comp.replace(r, (_3, from, fM, fm, fp34, fpr, fb, to, tM, tm, tp, tpr) => {
|
|
13360
13360
|
if (isX(fM)) {
|
|
13361
13361
|
from = "";
|
|
13362
13362
|
} else if (isX(fm)) {
|
|
13363
13363
|
from = `>=${fM}.0.0${options?.includePrerelease ? "-0" : ""}`;
|
|
13364
|
-
} else if (isX(
|
|
13364
|
+
} else if (isX(fp34)) {
|
|
13365
13365
|
from = `>=${fM}.${fm}.0${options?.includePrerelease ? "-0" : ""}`;
|
|
13366
13366
|
} else if (fpr) {
|
|
13367
13367
|
from = `>=${from}`;
|
|
@@ -87525,12 +87525,12 @@ var require_range = __commonJS({
|
|
|
87525
87525
|
debug("replaceGTE0", comp, options);
|
|
87526
87526
|
return comp.trim().replace(re2[options.includePrerelease ? t.GTE0PRE : t.GTE0], "");
|
|
87527
87527
|
};
|
|
87528
|
-
var hyphenReplace = (incPr) => ($0, from, fM, fm,
|
|
87528
|
+
var hyphenReplace = (incPr) => ($0, from, fM, fm, fp34, fpr, fb, to, tM, tm, tp, tpr) => {
|
|
87529
87529
|
if (isX(fM)) {
|
|
87530
87530
|
from = "";
|
|
87531
87531
|
} else if (isX(fm)) {
|
|
87532
87532
|
from = `>=${fM}.0.0${incPr ? "-0" : ""}`;
|
|
87533
|
-
} else if (isX(
|
|
87533
|
+
} else if (isX(fp34)) {
|
|
87534
87534
|
from = `>=${fM}.${fm}.0${incPr ? "-0" : ""}`;
|
|
87535
87535
|
} else if (fpr) {
|
|
87536
87536
|
from = `>=${from}`;
|
|
@@ -90111,8 +90111,8 @@ var require_pretty_print = __commonJS({
|
|
|
90111
90111
|
serializedRoute += serializeMetaData(route.metaData);
|
|
90112
90112
|
return serializedRoute;
|
|
90113
90113
|
}
|
|
90114
|
-
function mergeSimilarRoutes(
|
|
90115
|
-
return
|
|
90114
|
+
function mergeSimilarRoutes(routes33) {
|
|
90115
|
+
return routes33.reduce((mergedRoutes, route) => {
|
|
90116
90116
|
for (const nodeRoute of mergedRoutes) {
|
|
90117
90117
|
if (deepEqual(route.opts.constraints, nodeRoute.opts.constraints) && deepEqual(route.metaData, nodeRoute.metaData)) {
|
|
90118
90118
|
nodeRoute.method += ", " + route.method;
|
|
@@ -90124,18 +90124,18 @@ var require_pretty_print = __commonJS({
|
|
|
90124
90124
|
}, []);
|
|
90125
90125
|
}
|
|
90126
90126
|
function serializeNode(node, prefix, options) {
|
|
90127
|
-
let
|
|
90127
|
+
let routes33 = node.routes;
|
|
90128
90128
|
if (options.method === void 0) {
|
|
90129
|
-
|
|
90129
|
+
routes33 = routes33.map(normalizeRoute);
|
|
90130
90130
|
}
|
|
90131
|
-
|
|
90131
|
+
routes33 = routes33.map((route) => {
|
|
90132
90132
|
route.metaData = getRouteMetaData(route, options);
|
|
90133
90133
|
return route;
|
|
90134
90134
|
});
|
|
90135
90135
|
if (options.method === void 0) {
|
|
90136
|
-
|
|
90136
|
+
routes33 = mergeSimilarRoutes(routes33);
|
|
90137
90137
|
}
|
|
90138
|
-
return
|
|
90138
|
+
return routes33.map(serializeRoute).join(`
|
|
90139
90139
|
${prefix}`);
|
|
90140
90140
|
}
|
|
90141
90141
|
function buildObjectTree(node, tree, prefix, options) {
|
|
@@ -91402,9 +91402,9 @@ var require_find_my_way = __commonJS({
|
|
|
91402
91402
|
pathIndex = paramEndIndex;
|
|
91403
91403
|
}
|
|
91404
91404
|
};
|
|
91405
|
-
Router.prototype._rebuild = function(
|
|
91405
|
+
Router.prototype._rebuild = function(routes33) {
|
|
91406
91406
|
this.reset();
|
|
91407
|
-
for (const route of
|
|
91407
|
+
for (const route of routes33) {
|
|
91408
91408
|
const { method, path: path22, opts, handler, store } = route;
|
|
91409
91409
|
this._on(method, path22, opts, handler, store);
|
|
91410
91410
|
}
|
|
@@ -100612,8 +100612,8 @@ var require_commonjs6 = __commonJS({
|
|
|
100612
100612
|
return this.#fullpath = this.name;
|
|
100613
100613
|
}
|
|
100614
100614
|
const pv = p2.fullpath();
|
|
100615
|
-
const
|
|
100616
|
-
return this.#fullpath =
|
|
100615
|
+
const fp34 = pv + (!p2.parent ? "" : this.sep) + name25;
|
|
100616
|
+
return this.#fullpath = fp34;
|
|
100617
100617
|
}
|
|
100618
100618
|
/**
|
|
100619
100619
|
* On platforms other than windows, this is identical to fullpath.
|
|
@@ -105329,7 +105329,7 @@ var require_static = __commonJS({
|
|
|
105329
105329
|
var { fileURLToPath: fileURLToPath4 } = __require("node:url");
|
|
105330
105330
|
var { statSync: statSync2 } = __require("node:fs");
|
|
105331
105331
|
var { glob } = require_commonjs7();
|
|
105332
|
-
var
|
|
105332
|
+
var fp34 = require_plugin2();
|
|
105333
105333
|
var send = require_send2();
|
|
105334
105334
|
var encodingNegotiator = require_accept_negotiator();
|
|
105335
105335
|
var contentDisposition = require_content_disposition();
|
|
@@ -105433,7 +105433,7 @@ var require_static = __commonJS({
|
|
|
105433
105433
|
} else {
|
|
105434
105434
|
const indexes = new Set(opts.index === void 0 ? ["index.html"] : [].concat(opts.index));
|
|
105435
105435
|
const indexDirs = /* @__PURE__ */ new Map();
|
|
105436
|
-
const
|
|
105436
|
+
const routes33 = /* @__PURE__ */ new Set();
|
|
105437
105437
|
const roots = Array.isArray(sendOptions.root) ? sendOptions.root : [sendOptions.root];
|
|
105438
105438
|
for (let rootPath of roots) {
|
|
105439
105439
|
rootPath = rootPath.split(path22.win32.sep).join(path22.posix.sep);
|
|
@@ -105449,10 +105449,10 @@ var require_static = __commonJS({
|
|
|
105449
105449
|
for (let file2 of files) {
|
|
105450
105450
|
file2 = file2.split(path22.win32.sep).join(path22.posix.sep);
|
|
105451
105451
|
const route = prefix + file2;
|
|
105452
|
-
if (
|
|
105452
|
+
if (routes33.has(route)) {
|
|
105453
105453
|
continue;
|
|
105454
105454
|
}
|
|
105455
|
-
|
|
105455
|
+
routes33.add(route);
|
|
105456
105456
|
setUpHeadAndGet(routeOpts, route, `/${file2}`, rootPath);
|
|
105457
105457
|
const key = path22.posix.basename(route);
|
|
105458
105458
|
if (indexes.has(key) && !indexDirs.has(key)) {
|
|
@@ -105747,7 +105747,7 @@ var require_static = __commonJS({
|
|
|
105747
105747
|
throw err;
|
|
105748
105748
|
}
|
|
105749
105749
|
}
|
|
105750
|
-
module.exports =
|
|
105750
|
+
module.exports = fp34(fastifyStatic2, {
|
|
105751
105751
|
fastify: "5.x",
|
|
105752
105752
|
name: "@fastify/static"
|
|
105753
105753
|
});
|
|
@@ -112390,7 +112390,7 @@ var require_websocket2 = __commonJS({
|
|
|
112390
112390
|
var { ServerResponse } = __require("node:http");
|
|
112391
112391
|
var { PassThrough } = __require("node:stream");
|
|
112392
112392
|
var { randomBytes: randomBytes3 } = __require("node:crypto");
|
|
112393
|
-
var
|
|
112393
|
+
var fp34 = require_plugin2();
|
|
112394
112394
|
var WebSocket2 = require_ws();
|
|
112395
112395
|
var Duplexify = require_duplexify();
|
|
112396
112396
|
var kWs = /* @__PURE__ */ Symbol("ws-socket");
|
|
@@ -112576,7 +112576,7 @@ var require_websocket2 = __commonJS({
|
|
|
112576
112576
|
}
|
|
112577
112577
|
next();
|
|
112578
112578
|
}
|
|
112579
|
-
module.exports =
|
|
112579
|
+
module.exports = fp34(fastifyWebsocket2, {
|
|
112580
112580
|
fastify: "5.x",
|
|
112581
112581
|
name: "@fastify/websocket"
|
|
112582
112582
|
});
|
|
@@ -115020,7 +115020,7 @@ var require_multipart2 = __commonJS({
|
|
|
115020
115020
|
"use strict";
|
|
115021
115021
|
var Busboy = require_main();
|
|
115022
115022
|
var os2 = __require("node:os");
|
|
115023
|
-
var
|
|
115023
|
+
var fp34 = require_plugin2();
|
|
115024
115024
|
var { createWriteStream: createWriteStream2 } = __require("node:fs");
|
|
115025
115025
|
var { unlink: unlink2 } = __require("node:fs/promises");
|
|
115026
115026
|
var path22 = __require("node:path");
|
|
@@ -115469,7 +115469,7 @@ var require_multipart2 = __commonJS({
|
|
|
115469
115469
|
}
|
|
115470
115470
|
});
|
|
115471
115471
|
}
|
|
115472
|
-
module.exports =
|
|
115472
|
+
module.exports = fp34(fastifyMultipart2, {
|
|
115473
115473
|
fastify: "5.x",
|
|
115474
115474
|
name: "@fastify/multipart"
|
|
115475
115475
|
});
|
|
@@ -169019,11 +169019,11 @@ function buildCommand(builderArgs) {
|
|
|
169019
169019
|
}
|
|
169020
169020
|
};
|
|
169021
169021
|
}
|
|
169022
|
-
function* generateRouteMapHelpLines(
|
|
169022
|
+
function* generateRouteMapHelpLines(routes33, docs, args) {
|
|
169023
169023
|
const { brief, fullDescription, hideRoute } = docs;
|
|
169024
169024
|
const { headers } = args.text;
|
|
169025
169025
|
yield args.ansiColor ? `\x1B[1m${headers.usage}\x1B[22m` : headers.usage;
|
|
169026
|
-
for (const [name25, route] of Object.entries(
|
|
169026
|
+
for (const [name25, route] of Object.entries(routes33)) {
|
|
169027
169027
|
if (!hideRoute || !hideRoute[name25] || args.includeHidden) {
|
|
169028
169028
|
const externalRouteName = args.config.caseStyle === "convert-camel-to-kebab" ? convertCamelCaseToKebabCase(name25) : name25;
|
|
169029
169029
|
yield ` ${route.formatUsageLine({
|
|
@@ -169053,7 +169053,7 @@ function* generateRouteMapHelpLines(routes31, docs, args) {
|
|
|
169053
169053
|
}
|
|
169054
169054
|
yield "";
|
|
169055
169055
|
yield args.ansiColor ? `\x1B[1m${headers.commands}\x1B[22m` : headers.commands;
|
|
169056
|
-
const visibleRoutes = Object.entries(
|
|
169056
|
+
const visibleRoutes = Object.entries(routes33).filter(
|
|
169057
169057
|
([name25]) => !hideRoute || !hideRoute[name25] || args.includeHidden
|
|
169058
169058
|
);
|
|
169059
169059
|
const rows = visibleRoutes.map(([internalRouteName, route]) => {
|
|
@@ -169081,24 +169081,24 @@ function* generateRouteMapHelpLines(routes31, docs, args) {
|
|
|
169081
169081
|
}
|
|
169082
169082
|
}
|
|
169083
169083
|
function buildRouteMap({
|
|
169084
|
-
routes:
|
|
169084
|
+
routes: routes33,
|
|
169085
169085
|
defaultCommand: defaultCommandRoute,
|
|
169086
169086
|
docs,
|
|
169087
169087
|
aliases
|
|
169088
169088
|
}) {
|
|
169089
|
-
if (Object.entries(
|
|
169089
|
+
if (Object.entries(routes33).length === 0) {
|
|
169090
169090
|
throw new InternalError("Route map must contain at least one route");
|
|
169091
169091
|
}
|
|
169092
169092
|
const activeAliases = aliases ?? {};
|
|
169093
169093
|
const aliasesByRoute = /* @__PURE__ */ new Map();
|
|
169094
169094
|
for (const [alias, routeName] of Object.entries(activeAliases)) {
|
|
169095
|
-
if (alias in
|
|
169095
|
+
if (alias in routes33) {
|
|
169096
169096
|
throw new InternalError(`Cannot use "${alias}" as an alias when a route with that name already exists`);
|
|
169097
169097
|
}
|
|
169098
169098
|
const routeAliases = aliasesByRoute.get(routeName) ?? [];
|
|
169099
169099
|
aliasesByRoute.set(routeName, [...routeAliases, alias]);
|
|
169100
169100
|
}
|
|
169101
|
-
const defaultCommand = defaultCommandRoute ?
|
|
169101
|
+
const defaultCommand = defaultCommandRoute ? routes33[defaultCommandRoute] : void 0;
|
|
169102
169102
|
if (defaultCommand && defaultCommand.kind === RouteMapSymbol) {
|
|
169103
169103
|
throw new InternalError(
|
|
169104
169104
|
`Cannot use "${defaultCommandRoute}" as the default command because it is not a Command`
|
|
@@ -169107,7 +169107,7 @@ function buildRouteMap({
|
|
|
169107
169107
|
const resolveRouteName = (input) => {
|
|
169108
169108
|
if (input in activeAliases) {
|
|
169109
169109
|
return activeAliases[input];
|
|
169110
|
-
} else if (input in
|
|
169110
|
+
} else if (input in routes33) {
|
|
169111
169111
|
return input;
|
|
169112
169112
|
}
|
|
169113
169113
|
};
|
|
@@ -169125,7 +169125,7 @@ function buildRouteMap({
|
|
|
169125
169125
|
return `${args.prefix.join(" ")} ${routeNames.join("|")} ...`;
|
|
169126
169126
|
},
|
|
169127
169127
|
formatHelp: (config2) => {
|
|
169128
|
-
const lines = [...generateRouteMapHelpLines(
|
|
169128
|
+
const lines = [...generateRouteMapHelpLines(routes33, docs, config2)];
|
|
169129
169129
|
const text2 = lines.join("\n");
|
|
169130
169130
|
return text2 + "\n";
|
|
169131
169131
|
},
|
|
@@ -169168,11 +169168,11 @@ function buildRouteMap({
|
|
|
169168
169168
|
},
|
|
169169
169169
|
getRoutingTargetForInput: (input) => {
|
|
169170
169170
|
const routeName = input in activeAliases ? activeAliases[input] : input;
|
|
169171
|
-
return
|
|
169171
|
+
return routes33[routeName];
|
|
169172
169172
|
},
|
|
169173
169173
|
getAllEntries() {
|
|
169174
169174
|
const hiddenRoutes = docs.hideRoute;
|
|
169175
|
-
return Object.entries(
|
|
169175
|
+
return Object.entries(routes33).map(([originalRouteName, target]) => {
|
|
169176
169176
|
return {
|
|
169177
169177
|
name: {
|
|
169178
169178
|
original: originalRouteName,
|
|
@@ -185055,6 +185055,14 @@ var createCoreRepos = (kdb, h) => ({
|
|
|
185055
185055
|
let query = kdb.deleteFrom("projects").where("id", "=", id);
|
|
185056
185056
|
if (userId) query = query.where("user_id", "=", userId);
|
|
185057
185057
|
await query.execute();
|
|
185058
|
+
},
|
|
185059
|
+
// Deliberately unscoped: the notification importer runs without a request
|
|
185060
|
+
// context and must DERIVE ownership from the mapped local project rather
|
|
185061
|
+
// than trust any worker-supplied tenant id. Returns "" for solo-mode rows
|
|
185062
|
+
// (the column's default), which callers map to the "local" sentinel.
|
|
185063
|
+
getOwnerId: async (projectId) => {
|
|
185064
|
+
const row = await kdb.selectFrom("projects").select("user_id").where("id", "=", projectId).executeTakeFirst();
|
|
185065
|
+
return row?.user_id;
|
|
185058
185066
|
}
|
|
185059
185067
|
},
|
|
185060
185068
|
settings: {
|
|
@@ -185584,6 +185592,15 @@ var createExecutorRepos = (kdb, h) => ({
|
|
|
185584
185592
|
});
|
|
185585
185593
|
|
|
185586
185594
|
// src/storage/repositories/agent-sessions.ts
|
|
185595
|
+
var mapRemoteSessionMapping = (row) => ({
|
|
185596
|
+
local_session_id: row.local_session_id,
|
|
185597
|
+
project_id: row.project_id,
|
|
185598
|
+
remote_server_id: row.remote_server_id,
|
|
185599
|
+
remote_session_id: row.remote_session_id,
|
|
185600
|
+
branch: row.branch,
|
|
185601
|
+
notification_sync_start: row.notification_sync_start,
|
|
185602
|
+
notification_watch_until: row.notification_watch_until
|
|
185603
|
+
});
|
|
185587
185604
|
var mapAgentSession = (row) => ({
|
|
185588
185605
|
id: row.id,
|
|
185589
185606
|
project_id: row.project_id,
|
|
@@ -185596,6 +185613,7 @@ var mapAgentSession = (row) => ({
|
|
|
185596
185613
|
permission_mode: row.permission_mode ?? void 0,
|
|
185597
185614
|
agent_type: row.agent_type ?? void 0,
|
|
185598
185615
|
title: row.title,
|
|
185616
|
+
model: row.model ?? null,
|
|
185599
185617
|
created_at: row.created_at,
|
|
185600
185618
|
updated_at: row.updated_at,
|
|
185601
185619
|
last_user_message_at: row.last_user_message_at,
|
|
@@ -185610,7 +185628,7 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
185610
185628
|
// writes — this is what lets getLatestByBranch break ties
|
|
185611
185629
|
// deterministically (see the schema.ts / sqlite.ts DDL comment on
|
|
185612
185630
|
// agent_sessions).
|
|
185613
|
-
create: async ({ id, project_id, branch, permission_mode, agent_type }) => {
|
|
185631
|
+
create: async ({ id, project_id, branch, permission_mode, agent_type, model }) => {
|
|
185614
185632
|
await kdb.insertInto("agent_sessions").values({
|
|
185615
185633
|
id,
|
|
185616
185634
|
project_id,
|
|
@@ -185618,6 +185636,7 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
185618
185636
|
status: "running",
|
|
185619
185637
|
permission_mode: permission_mode ?? "edit",
|
|
185620
185638
|
agent_type: agent_type ?? "claude-code",
|
|
185639
|
+
model: model ?? null,
|
|
185621
185640
|
created_at: h.nowMs(),
|
|
185622
185641
|
updated_at: h.nowMs()
|
|
185623
185642
|
}).execute();
|
|
@@ -185660,6 +185679,9 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
185660
185679
|
updateAgentType: async (id, agent_type) => {
|
|
185661
185680
|
await kdb.updateTable("agent_sessions").set({ agent_type, updated_at: h.nowMs() }).where("id", "=", id).execute();
|
|
185662
185681
|
},
|
|
185682
|
+
updateModel: async (id, model) => {
|
|
185683
|
+
await kdb.updateTable("agent_sessions").set({ model, updated_at: h.nowMs() }).where("id", "=", id).execute();
|
|
185684
|
+
},
|
|
185663
185685
|
updateTitle: async (id, title) => {
|
|
185664
185686
|
await kdb.updateTable("agent_sessions").set({ title, updated_at: h.nowMs() }).where("id", "=", id).execute();
|
|
185665
185687
|
},
|
|
@@ -185690,6 +185712,17 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
185690
185712
|
upsertEntry: async (sessionId, entryIndex, data) => {
|
|
185691
185713
|
await kdb.insertInto("agent_session_entries").values({ session_id: sessionId, entry_index: entryIndex, data }).onConflict((oc) => oc.columns(["session_id", "entry_index"]).doUpdateSet({ data })).execute();
|
|
185692
185714
|
},
|
|
185715
|
+
// One transaction, both writes idempotent — see the contract note in
|
|
185716
|
+
// types.ts for why the milestone must ride with the turn_end rather than
|
|
185717
|
+
// being written next to it.
|
|
185718
|
+
upsertTurnEndWithOutbox: async ({ sessionId, entryIndex, entryData, outbox }) => {
|
|
185719
|
+
await kdb.transaction().execute(async (trx) => {
|
|
185720
|
+
await trx.insertInto("agent_session_entries").values({ session_id: sessionId, entry_index: entryIndex, data: entryData }).onConflict((oc) => oc.columns(["session_id", "entry_index"]).doUpdateSet({ data: entryData })).execute();
|
|
185721
|
+
if (outbox) {
|
|
185722
|
+
await trx.insertInto("notification_outbox").values(outbox).onConflict((oc) => oc.column("id").doNothing()).execute();
|
|
185723
|
+
}
|
|
185724
|
+
});
|
|
185725
|
+
},
|
|
185693
185726
|
getEntries: async (sessionId) => {
|
|
185694
185727
|
return kdb.selectFrom("agent_session_entries").select(["entry_index", "data"]).where("session_id", "=", sessionId).orderBy("entry_index", "asc").execute();
|
|
185695
185728
|
},
|
|
@@ -185703,14 +185736,21 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
185703
185736
|
remoteSessionMappings: {
|
|
185704
185737
|
// ON CONFLICT SET deliberately omits title_resolved — a re-upsert (e.g.
|
|
185705
185738
|
// the remote session getting re-mapped after a reconnect) must not reset
|
|
185706
|
-
// the "AI title already generated" flag back to false.
|
|
185707
|
-
|
|
185739
|
+
// the "AI title already generated" flag back to false. It omits
|
|
185740
|
+
// notification_sync_start and notification_watch_until for the same reason:
|
|
185741
|
+
// re-mapping a session (reconnect) or reusing a reviewer for a second
|
|
185742
|
+
// review must not downgrade an established from_start policy, and must not
|
|
185743
|
+
// rewind the watch window mid-flight. The sync cursor lives in its own
|
|
185744
|
+
// table and is likewise untouched — a reused reviewer continues from the
|
|
185745
|
+
// boundary it already imported instead of replaying its whole history.
|
|
185746
|
+
upsert: async (localSessionId, projectId, remoteServerId, remoteSessionId, branch, notificationSyncStart) => {
|
|
185708
185747
|
await kdb.insertInto("remote_session_mappings").values({
|
|
185709
185748
|
local_session_id: localSessionId,
|
|
185710
185749
|
project_id: projectId,
|
|
185711
185750
|
remote_server_id: remoteServerId,
|
|
185712
185751
|
remote_session_id: remoteSessionId,
|
|
185713
|
-
branch
|
|
185752
|
+
branch,
|
|
185753
|
+
notification_sync_start: notificationSyncStart ?? "from_now"
|
|
185714
185754
|
}).onConflict((oc) => oc.column("local_session_id").doUpdateSet({
|
|
185715
185755
|
project_id: projectId,
|
|
185716
185756
|
remote_server_id: remoteServerId,
|
|
@@ -185719,10 +185759,43 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
185719
185759
|
})).execute();
|
|
185720
185760
|
},
|
|
185721
185761
|
getAll: async () => {
|
|
185722
|
-
|
|
185762
|
+
const rows = await kdb.selectFrom("remote_session_mappings").selectAll().execute();
|
|
185763
|
+
return rows.map(mapRemoteSessionMapping);
|
|
185764
|
+
},
|
|
185765
|
+
getByLocal: async (localSessionId) => {
|
|
185766
|
+
const row = await kdb.selectFrom("remote_session_mappings").selectAll().where("local_session_id", "=", localSessionId).executeTakeFirst();
|
|
185767
|
+
return row ? mapRemoteSessionMapping(row) : void 0;
|
|
185768
|
+
},
|
|
185769
|
+
getByRemote: async (remoteServerId, remoteSessionId) => {
|
|
185770
|
+
const row = await kdb.selectFrom("remote_session_mappings").selectAll().where("remote_server_id", "=", remoteServerId).where("remote_session_id", "=", remoteSessionId).executeTakeFirst();
|
|
185771
|
+
return row ? mapRemoteSessionMapping(row) : void 0;
|
|
185723
185772
|
},
|
|
185773
|
+
// MAX(existing, incoming): concurrent extenders (a new turn plus live
|
|
185774
|
+
// stream activity) must not shorten a longer window one of them already set.
|
|
185775
|
+
extendNotificationWatch: async (localSessionId, until) => {
|
|
185776
|
+
await kdb.updateTable("remote_session_mappings").set({
|
|
185777
|
+
notification_watch_until: sql`MAX(COALESCE(notification_watch_until, 0), ${until})`
|
|
185778
|
+
}).where("local_session_id", "=", localSessionId).execute();
|
|
185779
|
+
},
|
|
185780
|
+
getNotificationSyncCandidates: async ({ now: now2, includeExpired }) => {
|
|
185781
|
+
let query = kdb.selectFrom("remote_session_mappings").selectAll();
|
|
185782
|
+
if (!includeExpired) {
|
|
185783
|
+
query = query.where("notification_watch_until", ">", now2);
|
|
185784
|
+
}
|
|
185785
|
+
const rows = await query.orderBy("local_session_id", "asc").execute();
|
|
185786
|
+
return rows.map(mapRemoteSessionMapping);
|
|
185787
|
+
},
|
|
185788
|
+
// The cursor is deleted with the mapping: without a mapping the front has
|
|
185789
|
+
// no local target for the worker's events, so a stale cursor would only
|
|
185790
|
+
// suppress a legitimate re-mapping's from_start replay.
|
|
185724
185791
|
delete: async (localSessionId) => {
|
|
185725
|
-
await kdb.
|
|
185792
|
+
await kdb.transaction().execute(async (trx) => {
|
|
185793
|
+
const row = await trx.selectFrom("remote_session_mappings").select(["remote_server_id", "remote_session_id"]).where("local_session_id", "=", localSessionId).executeTakeFirst();
|
|
185794
|
+
await trx.deleteFrom("remote_session_mappings").where("local_session_id", "=", localSessionId).execute();
|
|
185795
|
+
if (row) {
|
|
185796
|
+
await trx.deleteFrom("notification_sync_cursors").where("remote_server_id", "=", row.remote_server_id).where("remote_session_id", "=", row.remote_session_id).execute();
|
|
185797
|
+
}
|
|
185798
|
+
});
|
|
185726
185799
|
},
|
|
185727
185800
|
isTitleResolved: async (localSessionId) => {
|
|
185728
185801
|
const row = await kdb.selectFrom("remote_session_mappings").select("title_resolved").where("local_session_id", "=", localSessionId).executeTakeFirst();
|
|
@@ -186248,6 +186321,18 @@ var createWorkflowRunRepos = (kdb) => ({
|
|
|
186248
186321
|
transition: async (id, from, to, patch) => {
|
|
186249
186322
|
const result = await kdb.updateTable("workflow_runs").set({ ...patch ?? {}, status: to, updated_at: sql`datetime('now')` }).where("id", "=", id).where("status", "=", from).executeTakeFirst();
|
|
186250
186323
|
return (result.numUpdatedRows ?? 0n) > 0n;
|
|
186324
|
+
},
|
|
186325
|
+
// Guarded update FIRST, then the outbox insert conditioned on it having
|
|
186326
|
+
// applied: that ordering is what makes a lost CAS silent. Both statements
|
|
186327
|
+
// share one transaction so a crash between them can't leave a notified
|
|
186328
|
+
// transition unpersisted (or vice versa).
|
|
186329
|
+
transitionWithOutbox: async (id, from, to, patch, outbox) => {
|
|
186330
|
+
return kdb.transaction().execute(async (trx) => {
|
|
186331
|
+
const result = await trx.updateTable("workflow_runs").set({ ...patch ?? {}, status: to, updated_at: sql`datetime('now')` }).where("id", "=", id).where("status", "=", from).executeTakeFirst();
|
|
186332
|
+
if ((result.numUpdatedRows ?? 0n) === 0n) return false;
|
|
186333
|
+
await trx.insertInto("notification_outbox").values(outbox).onConflict((oc) => oc.column("id").doNothing()).execute();
|
|
186334
|
+
return true;
|
|
186335
|
+
});
|
|
186251
186336
|
}
|
|
186252
186337
|
}
|
|
186253
186338
|
});
|
|
@@ -186277,6 +186362,157 @@ var createTurnSnapshotRepos = (kdb) => ({
|
|
|
186277
186362
|
}
|
|
186278
186363
|
});
|
|
186279
186364
|
|
|
186365
|
+
// src/storage/repositories/notifications.ts
|
|
186366
|
+
var mapOutbox = (row) => ({
|
|
186367
|
+
seq: row.seq,
|
|
186368
|
+
id: row.id,
|
|
186369
|
+
kind: row.kind,
|
|
186370
|
+
project_id: row.project_id,
|
|
186371
|
+
branch: row.branch,
|
|
186372
|
+
session_id: row.session_id,
|
|
186373
|
+
workflow_run_id: row.workflow_run_id,
|
|
186374
|
+
created_at: row.created_at
|
|
186375
|
+
});
|
|
186376
|
+
var mapNotification = (row) => ({
|
|
186377
|
+
id: row.id,
|
|
186378
|
+
user_id: row.user_id,
|
|
186379
|
+
kind: row.kind,
|
|
186380
|
+
project_id: row.project_id,
|
|
186381
|
+
branch: row.branch,
|
|
186382
|
+
session_id: row.session_id,
|
|
186383
|
+
workflow_run_id: row.workflow_run_id,
|
|
186384
|
+
title: row.title,
|
|
186385
|
+
body: row.body,
|
|
186386
|
+
created_at: row.created_at,
|
|
186387
|
+
read_at: row.read_at
|
|
186388
|
+
});
|
|
186389
|
+
var createNotificationRepos = (kdb) => ({
|
|
186390
|
+
notificationOutbox: {
|
|
186391
|
+
// ON CONFLICT(id) DO NOTHING is what makes retrying the *business*
|
|
186392
|
+
// transition safe: the milestone id is derived from durable state
|
|
186393
|
+
// (session + turn_end index, or workflow run id), so a retried transaction
|
|
186394
|
+
// re-derives the same id and the second insert is a no-op rather than a
|
|
186395
|
+
// second ding. `inserted: false` still returns the existing seq so callers
|
|
186396
|
+
// that want to trigger a drain can do so either way.
|
|
186397
|
+
insert: async (event) => {
|
|
186398
|
+
const result = await kdb.insertInto("notification_outbox").values(event).onConflict((oc) => oc.column("id").doNothing()).executeTakeFirst();
|
|
186399
|
+
const inserted = (result.numInsertedOrUpdatedRows ?? 0n) > 0n;
|
|
186400
|
+
const row = await kdb.selectFrom("notification_outbox").select("seq").where("id", "=", event.id).executeTakeFirst();
|
|
186401
|
+
return { inserted, seq: row?.seq ?? null };
|
|
186402
|
+
},
|
|
186403
|
+
listAfter: async (afterSeq, limit) => {
|
|
186404
|
+
const rows = await kdb.selectFrom("notification_outbox").selectAll().where("seq", ">", afterSeq).orderBy("seq", "asc").limit(limit).execute();
|
|
186405
|
+
return rows.map(mapOutbox);
|
|
186406
|
+
},
|
|
186407
|
+
listBySessionAfter: async (sessionId, afterSeq, limit) => {
|
|
186408
|
+
const rows = await kdb.selectFrom("notification_outbox").selectAll().where("session_id", "=", sessionId).where("seq", ">", afterSeq).orderBy("seq", "asc").limit(limit).execute();
|
|
186409
|
+
return rows.map(mapOutbox);
|
|
186410
|
+
},
|
|
186411
|
+
headBySession: async (sessionId) => {
|
|
186412
|
+
const row = await kdb.selectFrom("notification_outbox").select(kdb.fn.max("seq").as("head")).where("session_id", "=", sessionId).executeTakeFirst();
|
|
186413
|
+
return row?.head ?? 0;
|
|
186414
|
+
},
|
|
186415
|
+
head: async () => {
|
|
186416
|
+
const row = await kdb.selectFrom("notification_outbox").select(kdb.fn.max("seq").as("head")).executeTakeFirst();
|
|
186417
|
+
return row?.head ?? 0;
|
|
186418
|
+
},
|
|
186419
|
+
pruneOlderThan: async (cutoffMs) => {
|
|
186420
|
+
await kdb.deleteFrom("notification_outbox").where("created_at", "<", cutoffMs).execute();
|
|
186421
|
+
}
|
|
186422
|
+
},
|
|
186423
|
+
notifications: {
|
|
186424
|
+
insert: async (notification) => {
|
|
186425
|
+
const result = await kdb.insertInto("notifications").values(notification).onConflict((oc) => oc.column("id").doNothing()).executeTakeFirst();
|
|
186426
|
+
return (result.numInsertedOrUpdatedRows ?? 0n) > 0n;
|
|
186427
|
+
},
|
|
186428
|
+
// One transaction on purpose. Insert-then-crash-then-replay is safe (the id
|
|
186429
|
+
// unique constraint absorbs it), but cursor-advance-then-crash would lose
|
|
186430
|
+
// the notification permanently — so the two writes must commit together.
|
|
186431
|
+
importRemote: async ({ notification, remoteServerId, remoteSessionId, seq }) => {
|
|
186432
|
+
return kdb.transaction().execute(async (trx) => {
|
|
186433
|
+
const result = await trx.insertInto("notifications").values(notification).onConflict((oc) => oc.column("id").doNothing()).executeTakeFirst();
|
|
186434
|
+
await trx.insertInto("notification_sync_cursors").values({
|
|
186435
|
+
remote_server_id: remoteServerId,
|
|
186436
|
+
remote_session_id: remoteSessionId,
|
|
186437
|
+
last_seq: seq,
|
|
186438
|
+
updated_at: Date.now()
|
|
186439
|
+
}).onConflict((oc) => oc.columns(["remote_server_id", "remote_session_id"]).doUpdateSet({
|
|
186440
|
+
// MAX(existing, incoming) — pages can be retried out of order after
|
|
186441
|
+
// a transient remote error, and a cursor that moved backward would
|
|
186442
|
+
// re-import (and re-ding) everything after it.
|
|
186443
|
+
last_seq: sql`MAX(notification_sync_cursors.last_seq, excluded.last_seq)`,
|
|
186444
|
+
updated_at: Date.now()
|
|
186445
|
+
})).execute();
|
|
186446
|
+
return { inserted: (result.numInsertedOrUpdatedRows ?? 0n) > 0n };
|
|
186447
|
+
});
|
|
186448
|
+
},
|
|
186449
|
+
listForUser: async (userId, opts) => {
|
|
186450
|
+
let query = kdb.selectFrom("notifications").selectAll().where("user_id", "=", userId);
|
|
186451
|
+
if (opts.unreadOnly) query = query.where("read_at", "is", null);
|
|
186452
|
+
const rows = await query.orderBy("created_at", "desc").orderBy("id", "desc").limit(opts.limit).execute();
|
|
186453
|
+
return rows.map(mapNotification);
|
|
186454
|
+
},
|
|
186455
|
+
// The user_id predicate is load-bearing authorization, not a filter: a
|
|
186456
|
+
// caller must not be able to mutate — or learn the existence of — another
|
|
186457
|
+
// tenant's notification by guessing its (deterministic, guessable) id.
|
|
186458
|
+
markRead: async (id, userId) => {
|
|
186459
|
+
const result = await kdb.updateTable("notifications").set({ read_at: Date.now() }).where("id", "=", id).where("user_id", "=", userId).where("read_at", "is", null).executeTakeFirst();
|
|
186460
|
+
if ((result.numUpdatedRows ?? 0n) > 0n) return true;
|
|
186461
|
+
const existing = await kdb.selectFrom("notifications").select("id").where("id", "=", id).where("user_id", "=", userId).executeTakeFirst();
|
|
186462
|
+
return existing !== void 0;
|
|
186463
|
+
},
|
|
186464
|
+
markAllRead: async (userId) => {
|
|
186465
|
+
await kdb.updateTable("notifications").set({ read_at: Date.now() }).where("user_id", "=", userId).where("read_at", "is", null).execute();
|
|
186466
|
+
},
|
|
186467
|
+
// Unread rows are never deleted — an unread milestone is the whole point of
|
|
186468
|
+
// the feature. Read history is capped per user so it can't grow forever.
|
|
186469
|
+
cleanup: async (keepRead) => {
|
|
186470
|
+
const users = await kdb.selectFrom("notifications").select("user_id").where("read_at", "is not", null).groupBy("user_id").execute();
|
|
186471
|
+
for (const { user_id } of users) {
|
|
186472
|
+
const keep = await kdb.selectFrom("notifications").select("id").where("user_id", "=", user_id).where("read_at", "is not", null).orderBy("created_at", "desc").orderBy("id", "desc").limit(keepRead).execute();
|
|
186473
|
+
let query = kdb.deleteFrom("notifications").where("user_id", "=", user_id).where("read_at", "is not", null);
|
|
186474
|
+
if (keep.length > 0) {
|
|
186475
|
+
query = query.where("id", "not in", keep.map((r) => r.id));
|
|
186476
|
+
}
|
|
186477
|
+
await query.execute();
|
|
186478
|
+
}
|
|
186479
|
+
}
|
|
186480
|
+
},
|
|
186481
|
+
notificationSyncCursors: {
|
|
186482
|
+
get: async (remoteServerId, remoteSessionId) => {
|
|
186483
|
+
const row = await kdb.selectFrom("notification_sync_cursors").select("last_seq").where("remote_server_id", "=", remoteServerId).where("remote_session_id", "=", remoteSessionId).executeTakeFirst();
|
|
186484
|
+
return row?.last_seq;
|
|
186485
|
+
},
|
|
186486
|
+
getMany: async (remoteServerId, remoteSessionIds) => {
|
|
186487
|
+
if (remoteSessionIds.length === 0) return /* @__PURE__ */ new Map();
|
|
186488
|
+
const rows = await kdb.selectFrom("notification_sync_cursors").select(["remote_session_id", "last_seq"]).where("remote_server_id", "=", remoteServerId).where("remote_session_id", "in", remoteSessionIds).execute();
|
|
186489
|
+
return new Map(rows.map((r) => [r.remote_session_id, r.last_seq]));
|
|
186490
|
+
},
|
|
186491
|
+
set: async (remoteServerId, remoteSessionId, lastSeq) => {
|
|
186492
|
+
await kdb.insertInto("notification_sync_cursors").values({
|
|
186493
|
+
remote_server_id: remoteServerId,
|
|
186494
|
+
remote_session_id: remoteSessionId,
|
|
186495
|
+
last_seq: lastSeq,
|
|
186496
|
+
updated_at: Date.now()
|
|
186497
|
+
}).onConflict((oc) => oc.columns(["remote_server_id", "remote_session_id"]).doUpdateSet({
|
|
186498
|
+
last_seq: sql`MAX(notification_sync_cursors.last_seq, excluded.last_seq)`,
|
|
186499
|
+
updated_at: Date.now()
|
|
186500
|
+
})).execute();
|
|
186501
|
+
},
|
|
186502
|
+
// DO NOTHING, not DO UPDATE: see the contract note in types.ts for why a
|
|
186503
|
+
// baseline must never overwrite an existing cursor.
|
|
186504
|
+
initializeIfAbsent: async (remoteServerId, remoteSessionId, lastSeq) => {
|
|
186505
|
+
const result = await kdb.insertInto("notification_sync_cursors").values({
|
|
186506
|
+
remote_server_id: remoteServerId,
|
|
186507
|
+
remote_session_id: remoteSessionId,
|
|
186508
|
+
last_seq: lastSeq,
|
|
186509
|
+
updated_at: Date.now()
|
|
186510
|
+
}).onConflict((oc) => oc.columns(["remote_server_id", "remote_session_id"]).doNothing()).executeTakeFirst();
|
|
186511
|
+
return (result.numInsertedOrUpdatedRows ?? 0n) > 0n;
|
|
186512
|
+
}
|
|
186513
|
+
}
|
|
186514
|
+
});
|
|
186515
|
+
|
|
186280
186516
|
// src/storage/sqlite.ts
|
|
186281
186517
|
var createDatabase = (dbPath) => {
|
|
186282
186518
|
const db = new Database(dbPath);
|
|
@@ -186360,6 +186596,10 @@ var createDatabase = (dbPath) => {
|
|
|
186360
186596
|
branch TEXT NOT NULL DEFAULT '',
|
|
186361
186597
|
status TEXT NOT NULL DEFAULT 'running',
|
|
186362
186598
|
title TEXT DEFAULT NULL,
|
|
186599
|
+
-- Per-session agent model, e.g. 'opus' or 'gpt-5.6-sol'. NULL = use the
|
|
186600
|
+
-- CLI's own default (no flag is passed). Never validated: an unknown
|
|
186601
|
+
-- name is passed to the CLI and fails there.
|
|
186602
|
+
model TEXT DEFAULT NULL,
|
|
186363
186603
|
-- Millisecond-precision timestamps. CURRENT_TIMESTAMP is seconds-only,
|
|
186364
186604
|
-- which lets two sessions tie on updated_at within the same second and
|
|
186365
186605
|
-- corrupts the ordering used by getLatestByBranch. The 'YYYY-MM-DD
|
|
@@ -186632,6 +186872,10 @@ var createDatabase = (dbPath) => {
|
|
|
186632
186872
|
if (!sessionInfoV6.some((col) => col.name === "favorited_at")) {
|
|
186633
186873
|
db.exec("ALTER TABLE agent_sessions ADD COLUMN favorited_at INTEGER DEFAULT NULL");
|
|
186634
186874
|
}
|
|
186875
|
+
const sessionInfoV7 = db.prepare("PRAGMA table_info(agent_sessions)").all();
|
|
186876
|
+
if (!sessionInfoV7.some((col) => col.name === "model")) {
|
|
186877
|
+
db.exec("ALTER TABLE agent_sessions ADD COLUMN model TEXT DEFAULT NULL");
|
|
186878
|
+
}
|
|
186635
186879
|
db.exec(`
|
|
186636
186880
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_project_branch
|
|
186637
186881
|
ON agent_sessions(project_id, branch);
|
|
@@ -186975,7 +187219,66 @@ var createDatabase = (dbPath) => {
|
|
|
186975
187219
|
PRIMARY KEY (session_id, turn_end_index),
|
|
186976
187220
|
FOREIGN KEY (session_id) REFERENCES agent_sessions(id) ON DELETE CASCADE
|
|
186977
187221
|
);
|
|
187222
|
+
|
|
187223
|
+
-- Durable attention-milestone outbox of THIS server. Written in the same
|
|
187224
|
+
-- transaction as the state that proves the milestone (a turn_end entry, a
|
|
187225
|
+
-- workflow status transition), so a crash can never leave a completed turn
|
|
187226
|
+
-- without its notification. Deliberately carries no title/body: the
|
|
187227
|
+
-- user-facing server generates copy at import time from its own session
|
|
187228
|
+
-- mapping, so a stale worker-side title never enters the wire protocol.
|
|
187229
|
+
-- No FK to agent_sessions: an outbox row must outlive deletion of the
|
|
187230
|
+
-- session it describes (deleting a session mid-sync must not silently drop
|
|
187231
|
+
-- an already-produced milestone). seq is the cursor, not created_at.
|
|
187232
|
+
CREATE TABLE IF NOT EXISTS notification_outbox (
|
|
187233
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
187234
|
+
id TEXT NOT NULL UNIQUE,
|
|
187235
|
+
kind TEXT NOT NULL,
|
|
187236
|
+
project_id TEXT NOT NULL,
|
|
187237
|
+
branch TEXT,
|
|
187238
|
+
session_id TEXT NOT NULL,
|
|
187239
|
+
workflow_run_id TEXT,
|
|
187240
|
+
created_at INTEGER NOT NULL
|
|
187241
|
+
);
|
|
187242
|
+
|
|
187243
|
+
CREATE INDEX IF NOT EXISTS idx_notification_outbox_session_seq
|
|
187244
|
+
ON notification_outbox(session_id, seq);
|
|
187245
|
+
|
|
187246
|
+
-- User-scoped notification inbox of the user-facing server. The id is the
|
|
187247
|
+
-- deterministic milestone id (remote rows namespaced
|
|
187248
|
+
-- remote:{serverId}:{outboxEventId}), which is what makes replay after a
|
|
187249
|
+
-- crash-before-cursor-advance a no-op instead of a duplicate ding.
|
|
187250
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
187251
|
+
id TEXT PRIMARY KEY,
|
|
187252
|
+
user_id TEXT NOT NULL,
|
|
187253
|
+
kind TEXT NOT NULL,
|
|
187254
|
+
project_id TEXT NOT NULL,
|
|
187255
|
+
branch TEXT,
|
|
187256
|
+
session_id TEXT,
|
|
187257
|
+
workflow_run_id TEXT,
|
|
187258
|
+
title TEXT NOT NULL,
|
|
187259
|
+
body TEXT,
|
|
187260
|
+
created_at INTEGER NOT NULL,
|
|
187261
|
+
read_at INTEGER
|
|
187262
|
+
);
|
|
187263
|
+
|
|
187264
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_user_created
|
|
187265
|
+
ON notifications(user_id, created_at DESC);
|
|
187266
|
+
|
|
187267
|
+
CREATE TABLE IF NOT EXISTS notification_sync_cursors (
|
|
187268
|
+
remote_server_id TEXT NOT NULL,
|
|
187269
|
+
remote_session_id TEXT NOT NULL,
|
|
187270
|
+
last_seq INTEGER NOT NULL,
|
|
187271
|
+
updated_at INTEGER NOT NULL,
|
|
187272
|
+
PRIMARY KEY (remote_server_id, remote_session_id)
|
|
187273
|
+
);
|
|
186978
187274
|
`);
|
|
187275
|
+
const remoteMappingNotifyInfo = db.prepare("PRAGMA table_info(remote_session_mappings)").all();
|
|
187276
|
+
if (!remoteMappingNotifyInfo.some((col) => col.name === "notification_sync_start")) {
|
|
187277
|
+
db.exec("ALTER TABLE remote_session_mappings ADD COLUMN notification_sync_start TEXT NOT NULL DEFAULT 'from_now'");
|
|
187278
|
+
}
|
|
187279
|
+
if (!remoteMappingNotifyInfo.some((col) => col.name === "notification_watch_until")) {
|
|
187280
|
+
db.exec("ALTER TABLE remote_session_mappings ADD COLUMN notification_watch_until INTEGER");
|
|
187281
|
+
}
|
|
186979
187282
|
const sessionSearchCacheInfo = db.prepare("PRAGMA table_info(session_search_cache)").all();
|
|
186980
187283
|
if (!sessionSearchCacheInfo.some((col) => col.name === "written_at")) {
|
|
186981
187284
|
db.exec("ALTER TABLE session_search_cache ADD COLUMN written_at INTEGER");
|
|
@@ -187022,6 +187325,7 @@ var createSqliteStorage = async (dbPath) => {
|
|
|
187022
187325
|
...createSearchCacheRepos(kdb, h),
|
|
187023
187326
|
...createWorkflowRunRepos(kdb),
|
|
187024
187327
|
...createTurnSnapshotRepos(kdb),
|
|
187328
|
+
...createNotificationRepos(kdb),
|
|
187025
187329
|
close: async () => {
|
|
187026
187330
|
await kdb.destroy();
|
|
187027
187331
|
}
|
|
@@ -187038,7 +187342,7 @@ import path17 from "path";
|
|
|
187038
187342
|
import { fileURLToPath } from "url";
|
|
187039
187343
|
|
|
187040
187344
|
// src/plugins/shared-services.ts
|
|
187041
|
-
var
|
|
187345
|
+
var import_fastify_plugin2 = __toESM(require_plugin2(), 1);
|
|
187042
187346
|
|
|
187043
187347
|
// src/process-manager.ts
|
|
187044
187348
|
import { spawn } from "child_process";
|
|
@@ -201019,8 +201323,11 @@ var UserInputParamsSchema = external_exports.looseObject({
|
|
|
201019
201323
|
|
|
201020
201324
|
// src/protocol/codex/cli.ts
|
|
201021
201325
|
var CROSS_REMOTE_MCP_TOKEN_ENV = "VIBEDECKX_CROSS_REMOTE_MCP_TOKEN";
|
|
201022
|
-
function buildCodexAppServerSpawnConfig(nativeBinary, crossRemoteMcp) {
|
|
201326
|
+
function buildCodexAppServerSpawnConfig(nativeBinary, crossRemoteMcp, model) {
|
|
201023
201327
|
const args = ["app-server"];
|
|
201328
|
+
if (model && model.trim()) {
|
|
201329
|
+
args.push("-c", `model=${JSON.stringify(model.trim())}`);
|
|
201330
|
+
}
|
|
201024
201331
|
if (crossRemoteMcp) {
|
|
201025
201332
|
args.push(
|
|
201026
201333
|
"-c",
|
|
@@ -201105,11 +201412,16 @@ function withNpxFallback(nativeBinary, args) {
|
|
|
201105
201412
|
}
|
|
201106
201413
|
return { command: "npx", args: ["-y", CLAUDE_NPM_PACKAGE, ...args] };
|
|
201107
201414
|
}
|
|
201108
|
-
function buildClaudeSessionSpawnConfig(nativeBinary, permissionMode, mcpConfigArg) {
|
|
201415
|
+
function buildClaudeSessionSpawnConfig(nativeBinary, permissionMode, mcpConfigArg, model) {
|
|
201109
201416
|
const permissionFlag = permissionMode === "plan" ? "--permission-mode=plan" : "--dangerously-skip-permissions";
|
|
201110
201417
|
const args = [
|
|
201111
201418
|
...STREAM_JSON_ARGS,
|
|
201112
|
-
permissionFlag
|
|
201419
|
+
permissionFlag
|
|
201420
|
+
];
|
|
201421
|
+
if (model && model.trim()) {
|
|
201422
|
+
args.push("--model", model.trim());
|
|
201423
|
+
}
|
|
201424
|
+
args.push(
|
|
201113
201425
|
// AskUserQuestion can't work over piped (non-TTY) stdin: claude resolves it
|
|
201114
201426
|
// internally as "dismissed" before we can present a picker and wait for the
|
|
201115
201427
|
// user. Disable it so the agent falls back to asking in plain text, which the
|
|
@@ -201117,7 +201429,7 @@ function buildClaudeSessionSpawnConfig(nativeBinary, permissionMode, mcpConfigAr
|
|
|
201117
201429
|
"--disallowedTools",
|
|
201118
201430
|
"AskUserQuestion",
|
|
201119
201431
|
"--verbose"
|
|
201120
|
-
|
|
201432
|
+
);
|
|
201121
201433
|
if (mcpConfigArg) {
|
|
201122
201434
|
args.push("--mcp-config", mcpConfigArg);
|
|
201123
201435
|
}
|
|
@@ -202016,6 +202328,57 @@ import { spawn as spawn3 } from "child_process";
|
|
|
202016
202328
|
import { randomUUID } from "crypto";
|
|
202017
202329
|
import { existsSync as existsSync3 } from "fs";
|
|
202018
202330
|
|
|
202331
|
+
// src/notification-milestones.ts
|
|
202332
|
+
var sessionResultReadyId = (sessionId, turnEndEntryIndex) => `session:${sessionId}:turn:${turnEndEntryIndex}:result-ready`;
|
|
202333
|
+
var sessionFailedId = (sessionId, turnEndEntryIndex) => `session:${sessionId}:turn:${turnEndEntryIndex}:failed`;
|
|
202334
|
+
var reviewReadyId = (workflowRunId) => `workflow:${workflowRunId}:review-ready`;
|
|
202335
|
+
var workflowFailedId = (workflowRunId, stateVersion) => `workflow:${workflowRunId}:failed:${stateVersion}`;
|
|
202336
|
+
function findTurnOpeningUserEntry(entries, beforeIndex) {
|
|
202337
|
+
let opening;
|
|
202338
|
+
for (let i = Math.min(beforeIndex, entries.length) - 1; i >= 0; i--) {
|
|
202339
|
+
const entry = entries[i];
|
|
202340
|
+
if (!entry) continue;
|
|
202341
|
+
if (entry.type === "turn_end") break;
|
|
202342
|
+
if (entry.type === "user") opening = entry;
|
|
202343
|
+
}
|
|
202344
|
+
return opening;
|
|
202345
|
+
}
|
|
202346
|
+
function resolveNotificationDisposition(openingUserEntry) {
|
|
202347
|
+
if (openingUserEntry?.type !== "user") return "result";
|
|
202348
|
+
if (openingUserEntry.notificationDisposition) return openingUserEntry.notificationDisposition;
|
|
202349
|
+
return openingUserEntry.origin === "workflow" ? "internal" : "result";
|
|
202350
|
+
}
|
|
202351
|
+
var FAILURE_OUTCOMES = /* @__PURE__ */ new Set([
|
|
202352
|
+
"failed",
|
|
202353
|
+
"process_exit",
|
|
202354
|
+
"server_restart"
|
|
202355
|
+
]);
|
|
202356
|
+
function sessionMilestoneForTurnEnd(opts) {
|
|
202357
|
+
if (opts.disposition !== "result") return void 0;
|
|
202358
|
+
const base = {
|
|
202359
|
+
project_id: opts.projectId,
|
|
202360
|
+
branch: opts.branch,
|
|
202361
|
+
session_id: opts.sessionId,
|
|
202362
|
+
workflow_run_id: null,
|
|
202363
|
+
created_at: opts.createdAt
|
|
202364
|
+
};
|
|
202365
|
+
if (opts.outcome === "completed") {
|
|
202366
|
+
return {
|
|
202367
|
+
...base,
|
|
202368
|
+
id: sessionResultReadyId(opts.sessionId, opts.entryIndex),
|
|
202369
|
+
kind: "session_result_ready"
|
|
202370
|
+
};
|
|
202371
|
+
}
|
|
202372
|
+
if (FAILURE_OUTCOMES.has(opts.outcome)) {
|
|
202373
|
+
return {
|
|
202374
|
+
...base,
|
|
202375
|
+
id: sessionFailedId(opts.sessionId, opts.entryIndex),
|
|
202376
|
+
kind: "session_failed"
|
|
202377
|
+
};
|
|
202378
|
+
}
|
|
202379
|
+
return void 0;
|
|
202380
|
+
}
|
|
202381
|
+
|
|
202019
202382
|
// src/utils/cross-remote-token.ts
|
|
202020
202383
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
202021
202384
|
var CROSS_REMOTE_SECRET_SETTING = "cross_remote_token_secret";
|
|
@@ -202159,11 +202522,12 @@ var ClaudeCodeProvider = class {
|
|
|
202159
202522
|
isAvailable() {
|
|
202160
202523
|
return true;
|
|
202161
202524
|
}
|
|
202162
|
-
buildSpawnConfig(_cwd, permissionMode, crossRemoteMcp) {
|
|
202525
|
+
buildSpawnConfig(_cwd, permissionMode, crossRemoteMcp, model) {
|
|
202163
202526
|
return buildClaudeSessionSpawnConfig(
|
|
202164
202527
|
this.detectBinary(),
|
|
202165
202528
|
permissionMode,
|
|
202166
|
-
crossRemoteMcp ? buildMcpConfigArg(crossRemoteMcp) : void 0
|
|
202529
|
+
crossRemoteMcp ? buildMcpConfigArg(crossRemoteMcp) : void 0,
|
|
202530
|
+
model
|
|
202167
202531
|
);
|
|
202168
202532
|
}
|
|
202169
202533
|
parseStdoutLine(line, _sessionId) {
|
|
@@ -202353,9 +202717,9 @@ var CodexProvider = class _CodexProvider {
|
|
|
202353
202717
|
isAvailable() {
|
|
202354
202718
|
return true;
|
|
202355
202719
|
}
|
|
202356
|
-
buildSpawnConfig(_cwd, permissionMode, crossRemoteMcp) {
|
|
202720
|
+
buildSpawnConfig(_cwd, permissionMode, crossRemoteMcp, model) {
|
|
202357
202721
|
this.lastPermissionMode = permissionMode;
|
|
202358
|
-
return buildCodexAppServerSpawnConfig(this.detectBinary(), crossRemoteMcp);
|
|
202722
|
+
return buildCodexAppServerSpawnConfig(this.detectBinary(), crossRemoteMcp, model);
|
|
202359
202723
|
}
|
|
202360
202724
|
// ============ Task 5.5: parseStdoutLine — JSON-RPC message routing ============
|
|
202361
202725
|
parseStdoutLine(line, sessionId) {
|
|
@@ -224253,6 +224617,21 @@ var TurnCompletionLedger = class {
|
|
|
224253
224617
|
};
|
|
224254
224618
|
|
|
224255
224619
|
// src/agent-session-manager.ts
|
|
224620
|
+
function buildStartupFailureMessage(agentType, stderrTail, stdoutTail) {
|
|
224621
|
+
const provider = getProvider(agentType);
|
|
224622
|
+
const name25 = provider.getDisplayName();
|
|
224623
|
+
const details = [stdoutTail.trim(), stderrTail.trim()].filter(Boolean).join("\n");
|
|
224624
|
+
const hint = stdoutTail.trim() ? void 0 : provider.getInstallHint?.();
|
|
224625
|
+
let msg = `Couldn't start ${name25}.`;
|
|
224626
|
+
if (hint) msg += `
|
|
224627
|
+
|
|
224628
|
+
${hint}`;
|
|
224629
|
+
if (details) msg += `
|
|
224630
|
+
|
|
224631
|
+
Details:
|
|
224632
|
+
${details}`;
|
|
224633
|
+
return msg;
|
|
224634
|
+
}
|
|
224256
224635
|
var SUMMARY_TEXT_CAP = 1500;
|
|
224257
224636
|
function extractLastAssistantText(entries) {
|
|
224258
224637
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
@@ -224295,6 +224674,36 @@ var AgentSessionManager = class {
|
|
|
224295
224674
|
setEventBus(eventBus) {
|
|
224296
224675
|
this.eventBus = eventBus;
|
|
224297
224676
|
}
|
|
224677
|
+
/**
|
|
224678
|
+
* Injected by shared-services: nudges NotificationService to drain the local
|
|
224679
|
+
* outbox right after a milestone lands, so the bell is fast in the common
|
|
224680
|
+
* case. Purely a latency optimization — correctness comes from the periodic
|
|
224681
|
+
* and startup drains, because this hook can't fire if the process dies
|
|
224682
|
+
* between the commit and the import.
|
|
224683
|
+
*/
|
|
224684
|
+
setMilestoneListener(listener) {
|
|
224685
|
+
this.onMilestoneCreated = listener;
|
|
224686
|
+
}
|
|
224687
|
+
onMilestoneCreated = null;
|
|
224688
|
+
/**
|
|
224689
|
+
* Decide the disposition to persist on an outgoing user turn.
|
|
224690
|
+
*
|
|
224691
|
+
* Ordinary user input defaults to `result`. Workflow callers must be explicit:
|
|
224692
|
+
* `origin: "workflow"` alone is NOT enough to infer `internal`, because
|
|
224693
|
+
* approved review feedback is also a workflow-authored turn yet starts a
|
|
224694
|
+
* user-visible source result. An omission is therefore a caller bug — warn
|
|
224695
|
+
* loudly rather than silently picking a disposition that suppresses a real
|
|
224696
|
+
* notification.
|
|
224697
|
+
*/
|
|
224698
|
+
resolveOutgoingDisposition(sessionId, opts) {
|
|
224699
|
+
if (opts?.notificationDisposition) return opts.notificationDisposition;
|
|
224700
|
+
if (opts?.origin === "workflow") {
|
|
224701
|
+
console.warn(
|
|
224702
|
+
`[AgentSession] workflow-origin turn for ${sessionId} carries no explicit notificationDisposition; defaulting to "result"`
|
|
224703
|
+
);
|
|
224704
|
+
}
|
|
224705
|
+
return "result";
|
|
224706
|
+
}
|
|
224298
224707
|
/**
|
|
224299
224708
|
* Injected by shared-services: lets commitCompletion mark taskCompleted WS
|
|
224300
224709
|
* frames whose completion the local WorkflowEngine claims (reviewer
|
|
@@ -224529,6 +224938,7 @@ var AgentSessionManager = class {
|
|
|
224529
224938
|
await this.ensureResidentCapacity({ projectId, branch }, { force });
|
|
224530
224939
|
const sessionId = opts.sessionId ?? randomUUID();
|
|
224531
224940
|
const branchKey = branch ?? "";
|
|
224941
|
+
const model = opts.model?.trim() ? opts.model.trim() : null;
|
|
224532
224942
|
const absoluteWorktreePath = resolveWorktreePath(projectPath, branch);
|
|
224533
224943
|
if (!skipDb) {
|
|
224534
224944
|
await this.storage.agentSessions.create({
|
|
@@ -224536,7 +224946,8 @@ var AgentSessionManager = class {
|
|
|
224536
224946
|
project_id: projectId,
|
|
224537
224947
|
branch: branchKey,
|
|
224538
224948
|
permission_mode: permissionMode,
|
|
224539
|
-
agent_type: agentType
|
|
224949
|
+
agent_type: agentType,
|
|
224950
|
+
model
|
|
224540
224951
|
});
|
|
224541
224952
|
}
|
|
224542
224953
|
if (!skipDb) {
|
|
@@ -224564,13 +224975,15 @@ var AgentSessionManager = class {
|
|
|
224564
224975
|
permissionMode,
|
|
224565
224976
|
crossRemoteMcp: opts.crossRemoteMcp,
|
|
224566
224977
|
agentType,
|
|
224978
|
+
model,
|
|
224567
224979
|
completion: new TurnCompletionLedger(),
|
|
224568
224980
|
graceTimer: null,
|
|
224569
224981
|
eventChain: Promise.resolve(),
|
|
224570
224982
|
bgSpawnHintsThisTurn: 0,
|
|
224571
224983
|
taskStartedThisTurn: 0,
|
|
224572
224984
|
lastActiveAt: Date.now(),
|
|
224573
|
-
turnOpenSince: null
|
|
224985
|
+
turnOpenSince: null,
|
|
224986
|
+
turnDisposition: null
|
|
224574
224987
|
};
|
|
224575
224988
|
this.sessions.set(sessionId, session);
|
|
224576
224989
|
const provider = getProvider(agentType);
|
|
@@ -224653,7 +225066,7 @@ var AgentSessionManager = class {
|
|
|
224653
225066
|
this.broadcastRaw(session.id, { finished: true });
|
|
224654
225067
|
return;
|
|
224655
225068
|
}
|
|
224656
|
-
const config2 = provider.buildSpawnConfig(cwd, session.permissionMode, session.crossRemoteMcp);
|
|
225069
|
+
const config2 = provider.buildSpawnConfig(cwd, session.permissionMode, session.crossRemoteMcp, session.model);
|
|
224657
225070
|
if (config2.command !== "npx") {
|
|
224658
225071
|
const agentVersion = getBinaryVersion(config2.command);
|
|
224659
225072
|
console.log(`[AgentSession] ${provider.getDisplayName()} version: ${agentVersion ?? "unknown (--version probe failed)"}`);
|
|
@@ -224661,6 +225074,7 @@ var AgentSessionManager = class {
|
|
|
224661
225074
|
console.log(`[AgentSession] ${provider.getDisplayName()} running via npx (version resolved at spawn by npm)`);
|
|
224662
225075
|
}
|
|
224663
225076
|
session.producedOutput = false;
|
|
225077
|
+
session.unparsedStdoutTail = "";
|
|
224664
225078
|
this.resetCompletion(session);
|
|
224665
225079
|
let stderrTail = "";
|
|
224666
225080
|
let spawnFailed = false;
|
|
@@ -224709,7 +225123,7 @@ var AgentSessionManager = class {
|
|
|
224709
225123
|
try {
|
|
224710
225124
|
await this.pushEntry(session.id, {
|
|
224711
225125
|
type: "error",
|
|
224712
|
-
message:
|
|
225126
|
+
message: buildStartupFailureMessage(session.agentType, stderrTail, session.unparsedStdoutTail ?? ""),
|
|
224713
225127
|
timestamp: Date.now()
|
|
224714
225128
|
}, true);
|
|
224715
225129
|
} catch (err) {
|
|
@@ -224742,7 +225156,7 @@ var AgentSessionManager = class {
|
|
|
224742
225156
|
const isNotFound = error48.code === "ENOENT";
|
|
224743
225157
|
this.pushEntry(session.id, {
|
|
224744
225158
|
type: "error",
|
|
224745
|
-
message: isNotFound ?
|
|
225159
|
+
message: isNotFound ? buildStartupFailureMessage(session.agentType, stderrTail, session.unparsedStdoutTail ?? "") : error48.message,
|
|
224746
225160
|
timestamp: Date.now()
|
|
224747
225161
|
}, true).catch((err) => {
|
|
224748
225162
|
console.error(`[AgentSession] Failed to push spawn-error entry for ${session.id}:`, err);
|
|
@@ -224867,6 +225281,8 @@ var AgentSessionManager = class {
|
|
|
224867
225281
|
const events = provider.parseStdoutLine(line, session.id);
|
|
224868
225282
|
if (events.length > 0) {
|
|
224869
225283
|
session.producedOutput = true;
|
|
225284
|
+
} else {
|
|
225285
|
+
session.unparsedStdoutTail = ((session.unparsedStdoutTail ?? "") + line + "\n").slice(-4e3);
|
|
224870
225286
|
}
|
|
224871
225287
|
for (const event of events) {
|
|
224872
225288
|
await this.processAgentEvent(session.id, event);
|
|
@@ -225121,32 +225537,21 @@ var AgentSessionManager = class {
|
|
|
225121
225537
|
* Push a new entry with ADD patch
|
|
225122
225538
|
*/
|
|
225123
225539
|
/**
|
|
225124
|
-
*
|
|
225125
|
-
*
|
|
225540
|
+
* Allocate the next entry index, record the message in the in-memory store,
|
|
225541
|
+
* and build its ADD replay patch. Shared by `pushEntry` and `pushTurnEnd` so
|
|
225542
|
+
* the two persistence paths can't drift on index allocation or patch shape.
|
|
225126
225543
|
*/
|
|
225127
|
-
|
|
225128
|
-
const
|
|
225129
|
-
|
|
225130
|
-
const
|
|
225131
|
-
|
|
225132
|
-
|
|
225133
|
-
|
|
225134
|
-
${hint}`;
|
|
225135
|
-
const details = stderrTail.trim();
|
|
225136
|
-
if (details) msg += `
|
|
225137
|
-
|
|
225138
|
-
Details:
|
|
225139
|
-
${details}`;
|
|
225140
|
-
return msg;
|
|
225544
|
+
stageEntry(session, message) {
|
|
225545
|
+
const index = session.store.indexProvider.next();
|
|
225546
|
+
session.store.entries[index] = message;
|
|
225547
|
+
const patch = ConversationPatch.addEntry(index, message);
|
|
225548
|
+
session.store.patches.push(patch);
|
|
225549
|
+
return { index, patch };
|
|
225141
225550
|
}
|
|
225142
225551
|
async pushEntry(sessionId, message, broadcast = true, userId = "local") {
|
|
225143
225552
|
const session = this.sessions.get(sessionId);
|
|
225144
225553
|
if (!session) return -1;
|
|
225145
|
-
const {
|
|
225146
|
-
const index = store.indexProvider.next();
|
|
225147
|
-
store.entries[index] = message;
|
|
225148
|
-
const patch = ConversationPatch.addEntry(index, message);
|
|
225149
|
-
store.patches.push(patch);
|
|
225554
|
+
const { index, patch } = this.stageEntry(session, message);
|
|
225150
225555
|
if (!session.skipDb && message.type !== "assistant") {
|
|
225151
225556
|
await this.persistEntry(session, index, message, userId);
|
|
225152
225557
|
}
|
|
@@ -225155,6 +225560,60 @@ ${details}`;
|
|
|
225155
225560
|
}
|
|
225156
225561
|
return index;
|
|
225157
225562
|
}
|
|
225563
|
+
/**
|
|
225564
|
+
* Persist a `turn_end` together with the attention milestone it earns.
|
|
225565
|
+
*
|
|
225566
|
+
* Deliberately NOT routed through `pushEntry`: the milestone id embeds the
|
|
225567
|
+
* turn_end's entry index, so the index has to be allocated before the write,
|
|
225568
|
+
* and the write itself has to be the atomic turn-end/outbox operation rather
|
|
225569
|
+
* than a plain entry upsert. Ordering matches `pushEntry` (persist, then
|
|
225570
|
+
* broadcast) so the existing turn_end-before-status contract still holds.
|
|
225571
|
+
*
|
|
225572
|
+
* `entryIndexOverride` re-persists an already-staged boundary (crash-repair
|
|
225573
|
+
* replay); without it a fresh index is allocated.
|
|
225574
|
+
*/
|
|
225575
|
+
async pushTurnEnd(session, outcome, disposition, endedAt, durationMs, entryIndexOverride) {
|
|
225576
|
+
const message = {
|
|
225577
|
+
type: "turn_end",
|
|
225578
|
+
timestamp: endedAt,
|
|
225579
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
225580
|
+
outcome,
|
|
225581
|
+
notificationDisposition: disposition
|
|
225582
|
+
};
|
|
225583
|
+
let index;
|
|
225584
|
+
let patch;
|
|
225585
|
+
if (entryIndexOverride !== void 0) {
|
|
225586
|
+
index = entryIndexOverride;
|
|
225587
|
+
patch = ConversationPatch.addEntry(index, message);
|
|
225588
|
+
} else {
|
|
225589
|
+
({ index, patch } = this.stageEntry(session, message));
|
|
225590
|
+
}
|
|
225591
|
+
if (!session.skipDb) {
|
|
225592
|
+
const outbox = sessionMilestoneForTurnEnd({
|
|
225593
|
+
sessionId: session.id,
|
|
225594
|
+
projectId: session.projectId,
|
|
225595
|
+
branch: session.branch,
|
|
225596
|
+
entryIndex: index,
|
|
225597
|
+
outcome,
|
|
225598
|
+
disposition,
|
|
225599
|
+
createdAt: endedAt
|
|
225600
|
+
});
|
|
225601
|
+
try {
|
|
225602
|
+
await this.storage.agentSessions.upsertTurnEndWithOutbox({
|
|
225603
|
+
sessionId: session.id,
|
|
225604
|
+
entryIndex: index,
|
|
225605
|
+
entryData: JSON.stringify(message),
|
|
225606
|
+
outbox
|
|
225607
|
+
});
|
|
225608
|
+
await this.storage.agentSessions.touchUpdatedAt(session.id);
|
|
225609
|
+
if (outbox) this.onMilestoneCreated?.();
|
|
225610
|
+
} catch (error48) {
|
|
225611
|
+
console.error(`[AgentSession] Failed to persist turn_end ${index} for ${session.id}:`, error48);
|
|
225612
|
+
}
|
|
225613
|
+
}
|
|
225614
|
+
if (entryIndexOverride === void 0) this.broadcastPatch(session.id, patch);
|
|
225615
|
+
return index;
|
|
225616
|
+
}
|
|
225158
225617
|
/**
|
|
225159
225618
|
* Persist a single entry to the database
|
|
225160
225619
|
*/
|
|
@@ -225193,18 +225652,24 @@ ${details}`;
|
|
|
225193
225652
|
}
|
|
225194
225653
|
}
|
|
225195
225654
|
/**
|
|
225196
|
-
* Close the open turn with a persisted turn_end stop-point entry
|
|
225655
|
+
* Close the open turn with a persisted turn_end stop-point entry, plus the
|
|
225656
|
+
* attention milestone the turn's disposition earns (written in the same
|
|
225657
|
+
* storage transaction — see pushTurnEnd).
|
|
225658
|
+
*
|
|
225197
225659
|
* turn_end entries are constructed ONLY here and in repairInterruptedTurn
|
|
225198
225660
|
* (restore path). Wall clock only — see design doc for why the CLI's
|
|
225199
|
-
* payload.duration_ms is not used.
|
|
225200
|
-
* persistence on purpose (no strict path — design decision).
|
|
225661
|
+
* payload.duration_ms is not used.
|
|
225201
225662
|
*/
|
|
225202
225663
|
async endActiveTurn(session, outcome) {
|
|
225203
225664
|
if (session.turnOpenSince === null) return null;
|
|
225204
225665
|
const endedAt = Date.now();
|
|
225205
225666
|
const durationMs = endedAt - session.turnOpenSince;
|
|
225206
|
-
const
|
|
225667
|
+
const disposition = session.turnDisposition ?? resolveNotificationDisposition(
|
|
225668
|
+
findTurnOpeningUserEntry(session.store.entries, session.store.entries.length)
|
|
225669
|
+
);
|
|
225670
|
+
const index = await this.pushTurnEnd(session, outcome, disposition, endedAt, durationMs);
|
|
225207
225671
|
session.turnOpenSince = null;
|
|
225672
|
+
session.turnDisposition = null;
|
|
225208
225673
|
if (!session.skipDb && index >= 0) {
|
|
225209
225674
|
try {
|
|
225210
225675
|
const project = await this.storage.projects.getById(session.projectId);
|
|
@@ -225223,12 +225688,13 @@ ${details}`;
|
|
|
225223
225688
|
async sendUserMessage(sessionId, content, projectPath, userId = "local", opts) {
|
|
225224
225689
|
const session = this.sessions.get(sessionId);
|
|
225225
225690
|
if (!session) return false;
|
|
225691
|
+
const disposition = this.resolveOutgoingDisposition(sessionId, opts);
|
|
225226
225692
|
if (session.dormant) {
|
|
225227
225693
|
if (!projectPath) {
|
|
225228
225694
|
console.error(`[AgentSession] Cannot wake dormant session ${sessionId} without projectPath`);
|
|
225229
225695
|
return false;
|
|
225230
225696
|
}
|
|
225231
|
-
await this.wakeDormantSession(session, projectPath, content, userId, opts?.origin);
|
|
225697
|
+
await this.wakeDormantSession(session, projectPath, content, userId, opts?.origin, disposition);
|
|
225232
225698
|
return true;
|
|
225233
225699
|
}
|
|
225234
225700
|
if (!session.process?.stdin) {
|
|
@@ -225248,7 +225714,8 @@ ${details}`;
|
|
|
225248
225714
|
type: "user",
|
|
225249
225715
|
content,
|
|
225250
225716
|
timestamp: Date.now(),
|
|
225251
|
-
...opts?.origin ? { origin: opts.origin } : {}
|
|
225717
|
+
...opts?.origin ? { origin: opts.origin } : {},
|
|
225718
|
+
notificationDisposition: disposition
|
|
225252
225719
|
}, true, userId);
|
|
225253
225720
|
try {
|
|
225254
225721
|
const provider = getProvider(session.agentType);
|
|
@@ -225257,14 +225724,20 @@ ${details}`;
|
|
|
225257
225724
|
console.warn(
|
|
225258
225725
|
`[AgentSession] sendUserMessage: provider buffered the input for ${session.agentType} session ${sessionId} \u2014 no stdin payload yet`
|
|
225259
225726
|
);
|
|
225260
|
-
if (session.turnOpenSince === null)
|
|
225727
|
+
if (session.turnOpenSince === null) {
|
|
225728
|
+
session.turnOpenSince = Date.now();
|
|
225729
|
+
session.turnDisposition = disposition;
|
|
225730
|
+
}
|
|
225261
225731
|
return true;
|
|
225262
225732
|
}
|
|
225263
225733
|
console.log(
|
|
225264
225734
|
`[AgentSession] sendUserMessage: wrote ${formatted.length}B to ${session.agentType} stdin (session=${sessionId})`
|
|
225265
225735
|
);
|
|
225266
225736
|
session.process.stdin.write(formatted);
|
|
225267
|
-
if (session.turnOpenSince === null)
|
|
225737
|
+
if (session.turnOpenSince === null) {
|
|
225738
|
+
session.turnOpenSince = Date.now();
|
|
225739
|
+
session.turnDisposition = disposition;
|
|
225740
|
+
}
|
|
225268
225741
|
return true;
|
|
225269
225742
|
} catch (error48) {
|
|
225270
225743
|
console.error(`[AgentSession] Failed to send message:`, error48);
|
|
@@ -225554,12 +226027,19 @@ ${details}`;
|
|
|
225554
226027
|
session.store.currentAssistantIndex = null;
|
|
225555
226028
|
session.buffer = "";
|
|
225556
226029
|
this.resetCompletion(session);
|
|
226030
|
+
const previousAgentType = session.agentType;
|
|
225557
226031
|
getProvider(session.agentType).onSessionDestroyed?.(sessionId);
|
|
225558
226032
|
session.agentType = agentType;
|
|
225559
226033
|
if (!session.skipDb) await this.storage.agentSessions.updateAgentType(sessionId, agentType);
|
|
226034
|
+
const clearedModel = session.model;
|
|
226035
|
+
if (clearedModel !== null) {
|
|
226036
|
+
session.model = null;
|
|
226037
|
+
if (!session.skipDb) await this.storage.agentSessions.updateModel(sessionId, null);
|
|
226038
|
+
}
|
|
226039
|
+
const agentDisplayName = (t) => t === "codex" ? "Codex" : "Claude Code";
|
|
225560
226040
|
await this.pushEntry(sessionId, {
|
|
225561
226041
|
type: "system",
|
|
225562
|
-
content: `Coding agent switched to ${agentType
|
|
226042
|
+
content: `Coding agent switched to ${agentDisplayName(agentType)}.` + (clearedModel !== null ? ` Model reset to the default (\`${clearedModel}\` was set for ${agentDisplayName(previousAgentType)}).` : ""),
|
|
225563
226043
|
timestamp: Date.now()
|
|
225564
226044
|
});
|
|
225565
226045
|
session.dormant = true;
|
|
@@ -225691,7 +226171,7 @@ ${details}`;
|
|
|
225691
226171
|
/**
|
|
225692
226172
|
* Wake a dormant session: spawn process, send full context + user message
|
|
225693
226173
|
*/
|
|
225694
|
-
async wakeDormantSession(session, projectPath, userMessage, userId = "local", origin) {
|
|
226174
|
+
async wakeDormantSession(session, projectPath, userMessage, userId = "local", origin, notificationDisposition = "result") {
|
|
225695
226175
|
console.log(`[AgentSession] Waking dormant session ${session.id}`);
|
|
225696
226176
|
await this.ensureResidentCapacity(
|
|
225697
226177
|
{ projectId: session.projectId, branch: session.branch },
|
|
@@ -225712,9 +226192,11 @@ ${details}`;
|
|
|
225712
226192
|
type: "user",
|
|
225713
226193
|
content: userMessage,
|
|
225714
226194
|
timestamp: Date.now(),
|
|
225715
|
-
...origin ? { origin } : {}
|
|
226195
|
+
...origin ? { origin } : {},
|
|
226196
|
+
notificationDisposition
|
|
225716
226197
|
}, true, userId);
|
|
225717
226198
|
session.turnOpenSince = Date.now();
|
|
226199
|
+
session.turnDisposition = notificationDisposition;
|
|
225718
226200
|
setTimeout(() => {
|
|
225719
226201
|
const context2 = this.buildFullConversationContext(session.store.entries);
|
|
225720
226202
|
if (context2) {
|
|
@@ -225781,7 +226263,8 @@ ${details}`;
|
|
|
225781
226263
|
* this runs on server boot for every restored session and must never
|
|
225782
226264
|
* throw into the restore path.
|
|
225783
226265
|
*/
|
|
225784
|
-
async repairInterruptedTurn(
|
|
226266
|
+
async repairInterruptedTurn(dbSession, rows) {
|
|
226267
|
+
const sessionId = dbSession.id;
|
|
225785
226268
|
let landingType = null;
|
|
225786
226269
|
for (let i = rows.length - 1; i >= 0; i--) {
|
|
225787
226270
|
try {
|
|
@@ -225796,17 +226279,43 @@ ${details}`;
|
|
|
225796
226279
|
if (landingType === null || landingType === "turn_end") return rows;
|
|
225797
226280
|
const maxIndex = rows.reduce((m2, r) => Math.max(m2, r.entry_index), -1);
|
|
225798
226281
|
const repairIndex = maxIndex + 1;
|
|
225799
|
-
const
|
|
226282
|
+
const entries = [];
|
|
226283
|
+
for (const row of rows) {
|
|
226284
|
+
try {
|
|
226285
|
+
entries[row.entry_index] = JSON.parse(row.data);
|
|
226286
|
+
} catch {
|
|
226287
|
+
}
|
|
226288
|
+
}
|
|
226289
|
+
const disposition = resolveNotificationDisposition(
|
|
226290
|
+
findTurnOpeningUserEntry(entries, repairIndex)
|
|
226291
|
+
);
|
|
226292
|
+
const repair = {
|
|
226293
|
+
type: "turn_end",
|
|
226294
|
+
timestamp: Date.now(),
|
|
226295
|
+
outcome: "server_restart",
|
|
226296
|
+
notificationDisposition: disposition
|
|
226297
|
+
};
|
|
225800
226298
|
const data = JSON.stringify(repair);
|
|
225801
|
-
|
|
225802
|
-
|
|
226299
|
+
const outbox = sessionMilestoneForTurnEnd({
|
|
226300
|
+
sessionId,
|
|
226301
|
+
projectId: dbSession.project_id,
|
|
226302
|
+
branch: dbSession.branch || null,
|
|
226303
|
+
entryIndex: repairIndex,
|
|
226304
|
+
outcome: "server_restart",
|
|
226305
|
+
disposition,
|
|
226306
|
+
createdAt: repair.timestamp
|
|
226307
|
+
});
|
|
226308
|
+
await this.storage.agentSessions.upsertTurnEndWithOutbox({
|
|
226309
|
+
sessionId,
|
|
226310
|
+
entryIndex: repairIndex,
|
|
226311
|
+
entryData: data,
|
|
226312
|
+
outbox
|
|
226313
|
+
});
|
|
226314
|
+
console.log(`[AgentSession] Repaired interrupted turn for ${sessionId} (server_restart turn_end at ${repairIndex}, disposition=${disposition})`);
|
|
225803
226315
|
try {
|
|
225804
|
-
const
|
|
225805
|
-
if (
|
|
225806
|
-
|
|
225807
|
-
if (project?.path) {
|
|
225808
|
-
await recordTurnSnapshot(this.storage, sessionId, repairIndex, resolveWorktreePath(project.path, dbSession.branch));
|
|
225809
|
-
}
|
|
226316
|
+
const project = await this.storage.projects.getById(dbSession.project_id);
|
|
226317
|
+
if (project?.path) {
|
|
226318
|
+
await recordTurnSnapshot(this.storage, sessionId, repairIndex, resolveWorktreePath(project.path, dbSession.branch));
|
|
225810
226319
|
}
|
|
225811
226320
|
} catch (error48) {
|
|
225812
226321
|
console.warn(`[AgentSession] Turn snapshot lookup failed for ${sessionId}@${repairIndex}:`, error48);
|
|
@@ -225825,7 +226334,7 @@ ${details}`;
|
|
|
225825
226334
|
let entries = await this.storage.agentSessions.getEntries(dbSession.id);
|
|
225826
226335
|
if (entries.length === 0) continue;
|
|
225827
226336
|
if (dbSession.status === "running") {
|
|
225828
|
-
entries = await this.repairInterruptedTurn(dbSession
|
|
226337
|
+
entries = await this.repairInterruptedTurn(dbSession, entries);
|
|
225829
226338
|
}
|
|
225830
226339
|
const store = this.rebuildStoreFromRows(entries, dbSession.id);
|
|
225831
226340
|
const permissionMode = dbSession.permission_mode === "plan" ? "plan" : "edit";
|
|
@@ -225842,13 +226351,15 @@ ${details}`;
|
|
|
225842
226351
|
skipDb: false,
|
|
225843
226352
|
permissionMode,
|
|
225844
226353
|
agentType: dbSession.agent_type || "claude-code",
|
|
226354
|
+
model: dbSession.model ?? null,
|
|
225845
226355
|
completion: new TurnCompletionLedger(),
|
|
225846
226356
|
graceTimer: null,
|
|
225847
226357
|
eventChain: Promise.resolve(),
|
|
225848
226358
|
bgSpawnHintsThisTurn: 0,
|
|
225849
226359
|
taskStartedThisTurn: 0,
|
|
225850
226360
|
lastActiveAt: Date.now(),
|
|
225851
|
-
turnOpenSince: null
|
|
226361
|
+
turnOpenSince: null,
|
|
226362
|
+
turnDisposition: null
|
|
225852
226363
|
};
|
|
225853
226364
|
this.sessions.set(dbSession.id, runningSession);
|
|
225854
226365
|
await this.storage.agentSessions.updateStatusPreservingTimestamp(dbSession.id, "stopped");
|
|
@@ -225902,13 +226413,16 @@ ${details}`;
|
|
|
225902
226413
|
const branch = source?.branch ?? (sourceRow.branch || null);
|
|
225903
226414
|
const permissionMode = source?.permissionMode ?? (sourceRow?.permission_mode === "plan" ? "plan" : "edit");
|
|
225904
226415
|
const agentType = agentTypeOverride ?? source?.agentType ?? (sourceRow?.agent_type || "claude-code");
|
|
226416
|
+
const sourceAgentType = source?.agentType ?? (sourceRow?.agent_type || "claude-code");
|
|
226417
|
+
const model = agentType === sourceAgentType ? source?.model ?? sourceRow?.model ?? null : null;
|
|
225905
226418
|
const newId = opts.sessionId ?? randomUUID();
|
|
225906
226419
|
await this.storage.agentSessions.create({
|
|
225907
226420
|
id: newId,
|
|
225908
226421
|
project_id: projectId,
|
|
225909
226422
|
branch: branch ?? "",
|
|
225910
226423
|
permission_mode: permissionMode,
|
|
225911
|
-
agent_type: agentType
|
|
226424
|
+
agent_type: agentType,
|
|
226425
|
+
model
|
|
225912
226426
|
});
|
|
225913
226427
|
await this.storage.agentSessions.updateStatusPreservingTimestamp(newId, "stopped");
|
|
225914
226428
|
for (const row of entryRows) {
|
|
@@ -225943,6 +226457,7 @@ ${details}`;
|
|
|
225943
226457
|
skipDb: false,
|
|
225944
226458
|
permissionMode,
|
|
225945
226459
|
agentType,
|
|
226460
|
+
model,
|
|
225946
226461
|
completion: new TurnCompletionLedger(),
|
|
225947
226462
|
graceTimer: null,
|
|
225948
226463
|
eventChain: Promise.resolve(),
|
|
@@ -225950,6 +226465,7 @@ ${details}`;
|
|
|
225950
226465
|
taskStartedThisTurn: 0,
|
|
225951
226466
|
lastActiveAt: Date.now(),
|
|
225952
226467
|
turnOpenSince: null,
|
|
226468
|
+
turnDisposition: null,
|
|
225953
226469
|
crossRemoteMcp: opts.crossRemoteMcp
|
|
225954
226470
|
};
|
|
225955
226471
|
this.sessions.set(newId, branched);
|
|
@@ -226296,9 +226812,397 @@ function runUpdatedEventFromRemoteFrame(parsed, sessionId, remoteInfo) {
|
|
|
226296
226812
|
return { type: "workflow:run-updated", projectId, branch: run2.branch, run: run2 };
|
|
226297
226813
|
}
|
|
226298
226814
|
|
|
226815
|
+
// src/routes/notification-outbox-routes.ts
|
|
226816
|
+
var import_fastify_plugin = __toESM(require_plugin2(), 1);
|
|
226817
|
+
var MAX_SESSIONS_PER_REQUEST = 100;
|
|
226818
|
+
var MAX_EVENTS_PER_SESSION = 100;
|
|
226819
|
+
function validateOutboxQuery(body) {
|
|
226820
|
+
const raw = body?.sessions;
|
|
226821
|
+
if (!Array.isArray(raw)) return { ok: false, error: "sessions must be an array" };
|
|
226822
|
+
if (raw.length === 0) return { ok: false, error: "sessions must not be empty" };
|
|
226823
|
+
if (raw.length > MAX_SESSIONS_PER_REQUEST) {
|
|
226824
|
+
return { ok: false, error: `sessions must contain at most ${MAX_SESSIONS_PER_REQUEST} entries` };
|
|
226825
|
+
}
|
|
226826
|
+
const seen = /* @__PURE__ */ new Set();
|
|
226827
|
+
const sessions = [];
|
|
226828
|
+
for (const entry of raw) {
|
|
226829
|
+
if (typeof entry !== "object" || entry === null) return { ok: false, error: "each session must be an object" };
|
|
226830
|
+
const { sessionId, after, headOnly } = entry;
|
|
226831
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
226832
|
+
return { ok: false, error: "sessionId must be a non-empty string" };
|
|
226833
|
+
}
|
|
226834
|
+
if (typeof after !== "number" || !Number.isInteger(after) || after < 0) {
|
|
226835
|
+
return { ok: false, error: "after must be a non-negative integer" };
|
|
226836
|
+
}
|
|
226837
|
+
if (headOnly !== void 0 && typeof headOnly !== "boolean") {
|
|
226838
|
+
return { ok: false, error: "headOnly must be a boolean" };
|
|
226839
|
+
}
|
|
226840
|
+
if (seen.has(sessionId)) return { ok: false, error: `duplicate sessionId: ${sessionId}` };
|
|
226841
|
+
seen.add(sessionId);
|
|
226842
|
+
sessions.push({ sessionId, after, ...headOnly ? { headOnly: true } : {} });
|
|
226843
|
+
}
|
|
226844
|
+
const rawLimit = body?.limitPerSession;
|
|
226845
|
+
if (rawLimit !== void 0) {
|
|
226846
|
+
if (typeof rawLimit !== "number" || !Number.isInteger(rawLimit) || rawLimit <= 0) {
|
|
226847
|
+
return { ok: false, error: "limitPerSession must be a positive integer" };
|
|
226848
|
+
}
|
|
226849
|
+
if (rawLimit > MAX_EVENTS_PER_SESSION) {
|
|
226850
|
+
return { ok: false, error: `limitPerSession must be at most ${MAX_EVENTS_PER_SESSION}` };
|
|
226851
|
+
}
|
|
226852
|
+
}
|
|
226853
|
+
return {
|
|
226854
|
+
ok: true,
|
|
226855
|
+
sessions,
|
|
226856
|
+
limit: typeof rawLimit === "number" ? rawLimit : MAX_EVENTS_PER_SESSION
|
|
226857
|
+
};
|
|
226858
|
+
}
|
|
226859
|
+
var routes = async (fastify2) => {
|
|
226860
|
+
fastify2.post("/api/notification-outbox/query", async (req, reply) => {
|
|
226861
|
+
const authResult = requireAuth(req, reply);
|
|
226862
|
+
if (authResult === null) return;
|
|
226863
|
+
const validated = validateOutboxQuery(req.body ?? {});
|
|
226864
|
+
if (!validated.ok) return reply.code(400).send({ error: validated.error });
|
|
226865
|
+
const sessions = [];
|
|
226866
|
+
for (const request of validated.sessions) {
|
|
226867
|
+
const headCursor = await fastify2.storage.notificationOutbox.headBySession(request.sessionId);
|
|
226868
|
+
if (request.headOnly) {
|
|
226869
|
+
sessions.push({
|
|
226870
|
+
sessionId: request.sessionId,
|
|
226871
|
+
events: [],
|
|
226872
|
+
headCursor,
|
|
226873
|
+
nextCursor: headCursor,
|
|
226874
|
+
hasMore: false
|
|
226875
|
+
});
|
|
226876
|
+
continue;
|
|
226877
|
+
}
|
|
226878
|
+
const events = await fastify2.storage.notificationOutbox.listBySessionAfter(
|
|
226879
|
+
request.sessionId,
|
|
226880
|
+
request.after,
|
|
226881
|
+
validated.limit
|
|
226882
|
+
);
|
|
226883
|
+
const nextCursor = events.length > 0 ? events[events.length - 1].seq : request.after;
|
|
226884
|
+
sessions.push({
|
|
226885
|
+
sessionId: request.sessionId,
|
|
226886
|
+
events,
|
|
226887
|
+
headCursor,
|
|
226888
|
+
nextCursor,
|
|
226889
|
+
hasMore: nextCursor < headCursor
|
|
226890
|
+
});
|
|
226891
|
+
}
|
|
226892
|
+
return reply.code(200).send({ sessions });
|
|
226893
|
+
});
|
|
226894
|
+
};
|
|
226895
|
+
var notification_outbox_routes_default = (0, import_fastify_plugin.default)(routes, { name: "notification-outbox-routes" });
|
|
226896
|
+
|
|
226897
|
+
// src/remote-notification-sync.ts
|
|
226898
|
+
var OUTBOX_QUERY_PATH = "/api/notification-outbox/query";
|
|
226899
|
+
var QUERY_TIMEOUT_MS = 15e3;
|
|
226900
|
+
var WATCH_WINDOW_MS = 30 * 60 * 1e3;
|
|
226901
|
+
var WATCH_EXTEND_THROTTLE_MS = 5 * 60 * 1e3;
|
|
226902
|
+
var MAX_TRACKED_SESSIONS = 5e3;
|
|
226903
|
+
var SYNC_INTERVAL_MS = 6e4;
|
|
226904
|
+
var MAX_PAGES_PER_SYNC = 50;
|
|
226905
|
+
function localWorkflowRunId(remoteServerId, projectId, remoteRunId) {
|
|
226906
|
+
return `remote-${remoteServerId}-${projectId}-${remoteRunId}`;
|
|
226907
|
+
}
|
|
226908
|
+
function localNotificationId(remoteServerId, outboxEventId) {
|
|
226909
|
+
return `remote:${remoteServerId}:${outboxEventId}`;
|
|
226910
|
+
}
|
|
226911
|
+
var RemoteNotificationSync = class {
|
|
226912
|
+
storage;
|
|
226913
|
+
notificationService;
|
|
226914
|
+
reverseConnectManager;
|
|
226915
|
+
proxy;
|
|
226916
|
+
timer = null;
|
|
226917
|
+
stopped = false;
|
|
226918
|
+
/** Serializes sweeps so a came-online sweep can't interleave with the tick. */
|
|
226919
|
+
chain = Promise.resolve();
|
|
226920
|
+
/**
|
|
226921
|
+
* Remote sessions this front currently believes are RUNNING. They are polled
|
|
226922
|
+
* regardless of their persisted watch window: an agent turn can easily run
|
|
226923
|
+
* longer than WATCH_WINDOW_MS while emitting nothing the bus can see, and a
|
|
226924
|
+
* mapping that lapsed mid-turn would not be polled when the turn finally
|
|
226925
|
+
* produces its milestone.
|
|
226926
|
+
*/
|
|
226927
|
+
activeRemoteSessions = /* @__PURE__ */ new Set();
|
|
226928
|
+
/** sessionId → last activity-driven watch extension (see WATCH_EXTEND_THROTTLE_MS). */
|
|
226929
|
+
lastWatchExtend = /* @__PURE__ */ new Map();
|
|
226930
|
+
constructor(deps) {
|
|
226931
|
+
this.storage = deps.storage;
|
|
226932
|
+
this.notificationService = deps.notificationService;
|
|
226933
|
+
this.reverseConnectManager = deps.reverseConnectManager;
|
|
226934
|
+
this.proxy = deps.proxy ?? proxyToRemoteAuto;
|
|
226935
|
+
}
|
|
226936
|
+
start() {
|
|
226937
|
+
if (this.timer || this.stopped) return;
|
|
226938
|
+
this.timer = setInterval(() => {
|
|
226939
|
+
this.enqueue(() => this.syncAll({ includeExpired: false }));
|
|
226940
|
+
}, SYNC_INTERVAL_MS);
|
|
226941
|
+
this.timer.unref?.();
|
|
226942
|
+
}
|
|
226943
|
+
shutdown() {
|
|
226944
|
+
this.stopped = true;
|
|
226945
|
+
if (this.timer) clearInterval(this.timer);
|
|
226946
|
+
this.timer = null;
|
|
226947
|
+
this.activeRemoteSessions.clear();
|
|
226948
|
+
this.lastWatchExtend.clear();
|
|
226949
|
+
}
|
|
226950
|
+
/**
|
|
226951
|
+
* Watch live remote activity (design §Mapping initialization and polling).
|
|
226952
|
+
*
|
|
226953
|
+
* The bus carries the remote stream's bridged `session:status` /
|
|
226954
|
+
* `session:taskCompleted` frames plus this front's own emits for remote
|
|
226955
|
+
* sessions, so it is the one place that sees a remote session start and stop
|
|
226956
|
+
* without threading a callback through the WebSocket plumbing.
|
|
226957
|
+
*/
|
|
226958
|
+
setEventBus(bus) {
|
|
226959
|
+
bus.subscribe((event) => this.observeActivity(event));
|
|
226960
|
+
}
|
|
226961
|
+
observeActivity(event) {
|
|
226962
|
+
const sessionId = event.sessionId;
|
|
226963
|
+
if (!sessionId || !sessionId.startsWith("remote-")) return;
|
|
226964
|
+
if (event.type === "session:status") {
|
|
226965
|
+
if (event.status === "running") this.activeRemoteSessions.add(sessionId);
|
|
226966
|
+
else this.activeRemoteSessions.delete(sessionId);
|
|
226967
|
+
} else if (event.type === "session:taskCompleted" || event.type === "session:finished") {
|
|
226968
|
+
this.activeRemoteSessions.delete(sessionId);
|
|
226969
|
+
}
|
|
226970
|
+
const now2 = Date.now();
|
|
226971
|
+
const last = this.lastWatchExtend.get(sessionId) ?? 0;
|
|
226972
|
+
if (now2 - last < WATCH_EXTEND_THROTTLE_MS) return;
|
|
226973
|
+
if (this.lastWatchExtend.size >= MAX_TRACKED_SESSIONS) this.pruneWatchBookkeeping(now2);
|
|
226974
|
+
this.lastWatchExtend.set(sessionId, now2);
|
|
226975
|
+
void this.extendWatch(sessionId);
|
|
226976
|
+
}
|
|
226977
|
+
/** Drop throttle entries older than a full window — they can only re-extend. */
|
|
226978
|
+
pruneWatchBookkeeping(now2) {
|
|
226979
|
+
for (const [sessionId, at] of this.lastWatchExtend) {
|
|
226980
|
+
if (now2 - at > WATCH_WINDOW_MS) this.lastWatchExtend.delete(sessionId);
|
|
226981
|
+
}
|
|
226982
|
+
}
|
|
226983
|
+
/** Fire-and-forget sweep, queued behind any in-flight one. */
|
|
226984
|
+
enqueue(work) {
|
|
226985
|
+
if (this.stopped) return;
|
|
226986
|
+
this.chain = this.chain.then(work).catch((err) => console.warn("[RemoteNotifications] sync failed:", err));
|
|
226987
|
+
}
|
|
226988
|
+
/** Extend a mapping's periodic-poll window (create / send / workflow start / live activity). */
|
|
226989
|
+
async extendWatch(localSessionId) {
|
|
226990
|
+
await this.storage.remoteSessionMappings.extendNotificationWatch(localSessionId, Date.now() + WATCH_WINDOW_MS).catch((err) => console.warn(`[RemoteNotifications] extendWatch(${localSessionId}) failed:`, err));
|
|
226991
|
+
}
|
|
226992
|
+
/**
|
|
226993
|
+
* Called before this front starts a new turn on a remote session.
|
|
226994
|
+
*
|
|
226995
|
+
* For a `from_now` mapping that has never been initialized, this records the
|
|
226996
|
+
* worker's current head FIRST — otherwise the turn we are about to start could
|
|
226997
|
+
* finish, be seen as "at or below the head we later record", and be suppressed
|
|
226998
|
+
* as history. Returns false when the baseline could not be recorded, in which
|
|
226999
|
+
* case the caller must NOT start the turn.
|
|
227000
|
+
*/
|
|
227001
|
+
async prepareForNewTurn(localSessionId) {
|
|
227002
|
+
const mapping = await this.storage.remoteSessionMappings.getByLocal(localSessionId);
|
|
227003
|
+
if (!mapping) return true;
|
|
227004
|
+
await this.extendWatch(localSessionId);
|
|
227005
|
+
if (mapping.notification_sync_start !== "from_now") return true;
|
|
227006
|
+
const existing = await this.storage.notificationSyncCursors.get(
|
|
227007
|
+
mapping.remote_server_id,
|
|
227008
|
+
mapping.remote_session_id
|
|
227009
|
+
);
|
|
227010
|
+
if (existing !== void 0) return true;
|
|
227011
|
+
const target = await this.resolveTarget(mapping);
|
|
227012
|
+
if (!target) {
|
|
227013
|
+
console.warn(`[RemoteNotifications] no remote link for ${localSessionId}; skipping baseline`);
|
|
227014
|
+
return true;
|
|
227015
|
+
}
|
|
227016
|
+
const result = await this.query(target, [{ sessionId: mapping.remote_session_id, after: 0, headOnly: true }]);
|
|
227017
|
+
if (!result.ok) {
|
|
227018
|
+
console.warn(
|
|
227019
|
+
`[RemoteNotifications] baseline for ${localSessionId} failed (status ${result.status}); refusing to start the turn`
|
|
227020
|
+
);
|
|
227021
|
+
return false;
|
|
227022
|
+
}
|
|
227023
|
+
const sessions = this.parseSessions(result);
|
|
227024
|
+
const head = sessions.find((s3) => s3.sessionId === mapping.remote_session_id)?.headCursor;
|
|
227025
|
+
if (head === void 0) {
|
|
227026
|
+
console.warn(`[RemoteNotifications] baseline response for ${localSessionId} omitted the session`);
|
|
227027
|
+
return false;
|
|
227028
|
+
}
|
|
227029
|
+
await this.storage.notificationSyncCursors.initializeIfAbsent(
|
|
227030
|
+
mapping.remote_server_id,
|
|
227031
|
+
mapping.remote_session_id,
|
|
227032
|
+
head
|
|
227033
|
+
);
|
|
227034
|
+
return true;
|
|
227035
|
+
}
|
|
227036
|
+
/** Sweep every mapped remote server. */
|
|
227037
|
+
async syncAll(opts) {
|
|
227038
|
+
const mappings = await this.candidates(opts);
|
|
227039
|
+
for (const [serverId, group2] of groupByServer(mappings)) {
|
|
227040
|
+
await this.syncMappings(serverId, group2);
|
|
227041
|
+
}
|
|
227042
|
+
}
|
|
227043
|
+
/** Sweep one server — used when a reverse connection comes online. */
|
|
227044
|
+
async syncServer(remoteServerId, opts) {
|
|
227045
|
+
const mappings = (await this.candidates(opts)).filter((m2) => m2.remote_server_id === remoteServerId);
|
|
227046
|
+
if (mappings.length > 0) await this.syncMappings(remoteServerId, mappings);
|
|
227047
|
+
}
|
|
227048
|
+
/**
|
|
227049
|
+
* Mappings this sweep should poll: the persisted watch set, plus any session
|
|
227050
|
+
* currently believed to be running.
|
|
227051
|
+
*
|
|
227052
|
+
* The union is what keeps a turn longer than WATCH_WINDOW_MS covered. Its
|
|
227053
|
+
* window is extended when the turn starts, but a long turn can go silent for
|
|
227054
|
+
* hours; without this the mapping would lapse out of the periodic set and its
|
|
227055
|
+
* eventual milestone would wait for a server restart or a remote reconnect.
|
|
227056
|
+
*/
|
|
227057
|
+
async candidates(opts) {
|
|
227058
|
+
const mappings = await this.storage.remoteSessionMappings.getNotificationSyncCandidates({
|
|
227059
|
+
now: Date.now(),
|
|
227060
|
+
includeExpired: opts.includeExpired
|
|
227061
|
+
});
|
|
227062
|
+
if (opts.includeExpired || this.activeRemoteSessions.size === 0) return mappings;
|
|
227063
|
+
const present = new Set(mappings.map((m2) => m2.local_session_id));
|
|
227064
|
+
for (const localSessionId of this.activeRemoteSessions) {
|
|
227065
|
+
if (present.has(localSessionId)) continue;
|
|
227066
|
+
const mapping = await this.storage.remoteSessionMappings.getByLocal(localSessionId);
|
|
227067
|
+
if (mapping) mappings.push(mapping);
|
|
227068
|
+
}
|
|
227069
|
+
return mappings;
|
|
227070
|
+
}
|
|
227071
|
+
async syncMappings(remoteServerId, mappings) {
|
|
227072
|
+
for (let i = 0; i < mappings.length; i += MAX_SESSIONS_PER_REQUEST) {
|
|
227073
|
+
const chunk = mappings.slice(i, i + MAX_SESSIONS_PER_REQUEST);
|
|
227074
|
+
await this.syncChunk(remoteServerId, chunk).catch(
|
|
227075
|
+
(err) => console.warn(`[RemoteNotifications] chunk sync for ${remoteServerId} failed:`, err)
|
|
227076
|
+
);
|
|
227077
|
+
}
|
|
227078
|
+
}
|
|
227079
|
+
async syncChunk(remoteServerId, chunk) {
|
|
227080
|
+
const target = await this.resolveTarget(chunk[0]);
|
|
227081
|
+
if (!target) {
|
|
227082
|
+
console.warn(`[RemoteNotifications] no remote link for server ${remoteServerId}; skipping ${chunk.length} mapping(s)`);
|
|
227083
|
+
return;
|
|
227084
|
+
}
|
|
227085
|
+
let pending = new Map(chunk.map((m2) => [m2.remote_session_id, m2]));
|
|
227086
|
+
for (let page = 0; page < MAX_PAGES_PER_SYNC && pending.size > 0; page++) {
|
|
227087
|
+
const cursors = await this.storage.notificationSyncCursors.getMany(
|
|
227088
|
+
remoteServerId,
|
|
227089
|
+
[...pending.keys()]
|
|
227090
|
+
);
|
|
227091
|
+
const requests = [...pending.values()].map((mapping) => {
|
|
227092
|
+
const cursor = cursors.get(mapping.remote_session_id);
|
|
227093
|
+
if (mapping.notification_sync_start === "from_now" && cursor === void 0) {
|
|
227094
|
+
return { sessionId: mapping.remote_session_id, after: 0, headOnly: true };
|
|
227095
|
+
}
|
|
227096
|
+
return { sessionId: mapping.remote_session_id, after: cursor ?? 0 };
|
|
227097
|
+
});
|
|
227098
|
+
const result = await this.query(target, requests);
|
|
227099
|
+
if (!result.ok) {
|
|
227100
|
+
console.warn(`[RemoteNotifications] outbox query to ${remoteServerId} failed (status ${result.status})`);
|
|
227101
|
+
return;
|
|
227102
|
+
}
|
|
227103
|
+
const byId = new Map(this.parseSessions(result).map((s3) => [s3.sessionId, s3]));
|
|
227104
|
+
const next = /* @__PURE__ */ new Map();
|
|
227105
|
+
for (const request of requests) {
|
|
227106
|
+
const mapping = pending.get(request.sessionId);
|
|
227107
|
+
const session = byId.get(request.sessionId);
|
|
227108
|
+
if (!session) continue;
|
|
227109
|
+
if ("headOnly" in request && request.headOnly) {
|
|
227110
|
+
await this.storage.notificationSyncCursors.initializeIfAbsent(
|
|
227111
|
+
remoteServerId,
|
|
227112
|
+
mapping.remote_session_id,
|
|
227113
|
+
session.headCursor
|
|
227114
|
+
);
|
|
227115
|
+
continue;
|
|
227116
|
+
}
|
|
227117
|
+
const imported = await this.importSession(remoteServerId, mapping, session);
|
|
227118
|
+
if (imported && session.hasMore) next.set(request.sessionId, mapping);
|
|
227119
|
+
}
|
|
227120
|
+
pending = next;
|
|
227121
|
+
}
|
|
227122
|
+
}
|
|
227123
|
+
/**
|
|
227124
|
+
* Import one session's page. Returns false when the batch was rejected, in
|
|
227125
|
+
* which case its cursor is untouched.
|
|
227126
|
+
*/
|
|
227127
|
+
async importSession(remoteServerId, mapping, session) {
|
|
227128
|
+
for (const event of session.events) {
|
|
227129
|
+
if (event.session_id !== mapping.remote_session_id) {
|
|
227130
|
+
console.warn(
|
|
227131
|
+
`[RemoteNotifications] ${remoteServerId}: event ${event.id} claims session ${event.session_id}, expected ${mapping.remote_session_id}; rejecting batch`
|
|
227132
|
+
);
|
|
227133
|
+
return false;
|
|
227134
|
+
}
|
|
227135
|
+
}
|
|
227136
|
+
const ownerId = await this.storage.projects.getOwnerId(mapping.project_id);
|
|
227137
|
+
if (ownerId === void 0) {
|
|
227138
|
+
console.warn(`[RemoteNotifications] project ${mapping.project_id} not found; skipping ${mapping.local_session_id}`);
|
|
227139
|
+
return false;
|
|
227140
|
+
}
|
|
227141
|
+
for (const event of session.events) {
|
|
227142
|
+
const notification = await this.notificationService.buildNotification(event, {
|
|
227143
|
+
id: localNotificationId(remoteServerId, event.id),
|
|
227144
|
+
userId: ownerId || "local",
|
|
227145
|
+
projectId: mapping.project_id,
|
|
227146
|
+
sessionId: mapping.local_session_id,
|
|
227147
|
+
workflowRunId: event.workflow_run_id ? localWorkflowRunId(remoteServerId, mapping.project_id, event.workflow_run_id) : null
|
|
227148
|
+
});
|
|
227149
|
+
const { inserted } = await this.storage.notifications.importRemote({
|
|
227150
|
+
notification,
|
|
227151
|
+
remoteServerId,
|
|
227152
|
+
remoteSessionId: mapping.remote_session_id,
|
|
227153
|
+
seq: event.seq
|
|
227154
|
+
});
|
|
227155
|
+
if (inserted) this.notificationService.emitCreated(notification);
|
|
227156
|
+
}
|
|
227157
|
+
if (session.events.length > 0) await this.extendWatch(mapping.local_session_id);
|
|
227158
|
+
return true;
|
|
227159
|
+
}
|
|
227160
|
+
async query(target, sessions) {
|
|
227161
|
+
return this.proxy(
|
|
227162
|
+
target.serverId,
|
|
227163
|
+
target.url,
|
|
227164
|
+
target.apiKey,
|
|
227165
|
+
"POST",
|
|
227166
|
+
OUTBOX_QUERY_PATH,
|
|
227167
|
+
{ sessions, limitPerSession: MAX_EVENTS_PER_SESSION },
|
|
227168
|
+
{ timeoutMs: QUERY_TIMEOUT_MS, reverseConnectManager: this.reverseConnectManager }
|
|
227169
|
+
);
|
|
227170
|
+
}
|
|
227171
|
+
parseSessions(result) {
|
|
227172
|
+
const sessions = result.data?.sessions;
|
|
227173
|
+
return Array.isArray(sessions) ? sessions : [];
|
|
227174
|
+
}
|
|
227175
|
+
/**
|
|
227176
|
+
* URL/apiKey come from project_remotes — the authoritative source, same as the
|
|
227177
|
+
* remoteSessionMap hydration in shared-services. Reverse-connect rows have an
|
|
227178
|
+
* empty URL and are routed by server id instead.
|
|
227179
|
+
*/
|
|
227180
|
+
async resolveTarget(mapping) {
|
|
227181
|
+
const remote = await this.storage.projectRemotes.getByProjectAndServer(
|
|
227182
|
+
mapping.project_id,
|
|
227183
|
+
mapping.remote_server_id
|
|
227184
|
+
);
|
|
227185
|
+
if (!remote) return null;
|
|
227186
|
+
return {
|
|
227187
|
+
serverId: mapping.remote_server_id,
|
|
227188
|
+
url: remote.server_url ?? "",
|
|
227189
|
+
apiKey: remote.server_api_key || ""
|
|
227190
|
+
};
|
|
227191
|
+
}
|
|
227192
|
+
};
|
|
227193
|
+
function groupByServer(mappings) {
|
|
227194
|
+
const groups = /* @__PURE__ */ new Map();
|
|
227195
|
+
for (const mapping of mappings) {
|
|
227196
|
+
const existing = groups.get(mapping.remote_server_id);
|
|
227197
|
+
if (existing) existing.push(mapping);
|
|
227198
|
+
else groups.set(mapping.remote_server_id, [mapping]);
|
|
227199
|
+
}
|
|
227200
|
+
return groups;
|
|
227201
|
+
}
|
|
227202
|
+
|
|
226299
227203
|
// src/remote-agent-sessions.ts
|
|
226300
227204
|
async function createRemoteAgentSession(deps, params) {
|
|
226301
|
-
const { projectId, agentMode, remoteConfig, branch, permissionMode, agentType, force, userId } = params;
|
|
227205
|
+
const { projectId, agentMode, remoteConfig, branch, permissionMode, agentType, model, force, userId } = params;
|
|
226302
227206
|
const remoteSessionId = randomUUID3();
|
|
226303
227207
|
const localSessionId = `remote-${agentMode}-${projectId}-${remoteSessionId}`;
|
|
226304
227208
|
const crossRemoteMcp = await mintCrossRemoteMcpConfig(
|
|
@@ -226320,7 +227224,7 @@ async function createRemoteAgentSession(deps, params) {
|
|
|
226320
227224
|
remoteConfig.server_api_key || "",
|
|
226321
227225
|
"POST",
|
|
226322
227226
|
`/api/path/agent-sessions/new`,
|
|
226323
|
-
{ path: remoteConfig.remote_path, branch, permissionMode, agentType, force, sessionId: remoteSessionId, crossRemoteMcp },
|
|
227227
|
+
{ path: remoteConfig.remote_path, branch, permissionMode, agentType, force, sessionId: remoteSessionId, crossRemoteMcp, model },
|
|
226324
227228
|
{ reverseConnectManager: deps.reverseConnectManager ?? void 0 }
|
|
226325
227229
|
);
|
|
226326
227230
|
if (!result.ok) {
|
|
@@ -226332,7 +227236,8 @@ async function createRemoteAgentSession(deps, params) {
|
|
|
226332
227236
|
deps.remoteSessionMap.delete(localSessionId);
|
|
226333
227237
|
return { ok: false, status: 409, data: { error: "Remote returned an unexpected session id; upgrade the remote" } };
|
|
226334
227238
|
}
|
|
226335
|
-
await deps.remoteSessionMappings.upsert(localSessionId, projectId, agentMode, remoteSessionId, branch ?? null);
|
|
227239
|
+
await deps.remoteSessionMappings.upsert(localSessionId, projectId, agentMode, remoteSessionId, branch ?? null, "from_start");
|
|
227240
|
+
await deps.remoteSessionMappings.extendNotificationWatch(localSessionId, Date.now() + WATCH_WINDOW_MS).catch((err) => console.warn("[RemoteSession] notification watch extend failed:", err));
|
|
226336
227241
|
await deps.storage.searchCache.noteSessionCreated({
|
|
226337
227242
|
localSessionId,
|
|
226338
227243
|
projectId,
|
|
@@ -228975,6 +229880,15 @@ var WorkflowError = class extends Error {
|
|
|
228975
229880
|
}
|
|
228976
229881
|
code;
|
|
228977
229882
|
};
|
|
229883
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "failed"]);
|
|
229884
|
+
var REVIEWER_TURN = {
|
|
229885
|
+
origin: "workflow",
|
|
229886
|
+
notificationDisposition: "milestone-managed"
|
|
229887
|
+
};
|
|
229888
|
+
var FEEDBACK_TURN = {
|
|
229889
|
+
origin: "workflow",
|
|
229890
|
+
notificationDisposition: "result"
|
|
229891
|
+
};
|
|
228978
229892
|
var MAX_CONTEXT_CHARS = 2e3;
|
|
228979
229893
|
var MAX_SELF_REPORT_CHARS = 4e3;
|
|
228980
229894
|
var SELF_REPORT_MIN_CHARS = 80;
|
|
@@ -229170,10 +230084,57 @@ var WorkflowEngine = class {
|
|
|
229170
230084
|
if (participant.runId === runId) this.participants.delete(sid);
|
|
229171
230085
|
}
|
|
229172
230086
|
}
|
|
230087
|
+
/**
|
|
230088
|
+
* The single place a run becomes `failed`. Goes through a guarded transition
|
|
230089
|
+
* out of the run's CURRENT status rather than an unconditional status write,
|
|
230090
|
+
* so the failure milestone can only be created by the caller that actually
|
|
230091
|
+
* performed the transition — a run that concurrently completed or was
|
|
230092
|
+
* cancelled fails the CAS and writes nothing.
|
|
230093
|
+
*
|
|
230094
|
+
* The milestone targets the participant the user should inspect: the reviewer
|
|
230095
|
+
* when one exists, otherwise the source session (a run can fail before its
|
|
230096
|
+
* reviewer is ever created).
|
|
230097
|
+
*/
|
|
229173
230098
|
async failRun(run2, error48) {
|
|
229174
|
-
|
|
230099
|
+
if (TERMINAL_STATUSES.has(run2.status)) return;
|
|
230100
|
+
const from = run2.status;
|
|
230101
|
+
const ok = await this.storage.workflowRuns.transitionWithOutbox(
|
|
230102
|
+
run2.id,
|
|
230103
|
+
from,
|
|
230104
|
+
"failed",
|
|
230105
|
+
{ error: error48 },
|
|
230106
|
+
{
|
|
230107
|
+
// The state the run failed OUT OF: two distinct failures of one run are
|
|
230108
|
+
// distinct attention milestones, while a retried transition out of the
|
|
230109
|
+
// same state is not.
|
|
230110
|
+
id: workflowFailedId(run2.id, from),
|
|
230111
|
+
kind: "workflow_failed",
|
|
230112
|
+
project_id: run2.project_id,
|
|
230113
|
+
branch: run2.branch,
|
|
230114
|
+
session_id: run2.reviewer_session_id ?? run2.source_session_id,
|
|
230115
|
+
workflow_run_id: run2.id,
|
|
230116
|
+
created_at: Date.now()
|
|
230117
|
+
}
|
|
230118
|
+
);
|
|
230119
|
+
if (!ok) return;
|
|
230120
|
+
const failed = await this.storage.workflowRuns.getById(run2.id);
|
|
229175
230121
|
if (failed) this.untrackRun(failed);
|
|
230122
|
+
this.onMilestoneCreated?.();
|
|
230123
|
+
}
|
|
230124
|
+
/** Test seam for the failure path (see failRun). */
|
|
230125
|
+
async failRunForTest(runId, error48) {
|
|
230126
|
+
const run2 = await this.storage.workflowRuns.getById(runId);
|
|
230127
|
+
if (run2) await this.failRun(run2, error48);
|
|
229176
230128
|
}
|
|
230129
|
+
/**
|
|
230130
|
+
* Injected by shared-services: nudges NotificationService to drain the local
|
|
230131
|
+
* outbox promptly. Latency only — correctness rests on the periodic/startup
|
|
230132
|
+
* drains, since this can't fire if the process dies right after the commit.
|
|
230133
|
+
*/
|
|
230134
|
+
setMilestoneListener(listener) {
|
|
230135
|
+
this.onMilestoneCreated = listener;
|
|
230136
|
+
}
|
|
230137
|
+
onMilestoneCreated = null;
|
|
229177
230138
|
/** Sync check used by ChatSessionManager before waking the commander model. */
|
|
229178
230139
|
shouldSuppressAgentEvent(sessionId) {
|
|
229179
230140
|
return this.participants.get(sessionId)?.role === "reviewer";
|
|
@@ -229299,7 +230260,7 @@ var WorkflowEngine = class {
|
|
|
229299
230260
|
reviewFocus: opts.reviewFocus ?? null,
|
|
229300
230261
|
target
|
|
229301
230262
|
});
|
|
229302
|
-
const sent = await this.agentOps.sendUserMessage(opts.reviewerSessionId, prompt, opts.project.path, void 0,
|
|
230263
|
+
const sent = await this.agentOps.sendUserMessage(opts.reviewerSessionId, prompt, opts.project.path, void 0, REVIEWER_TURN).catch(() => false);
|
|
229303
230264
|
if (!sent) {
|
|
229304
230265
|
await this.failRun(run2, "\u5411\u4E0A\u6B21 reviewer \u6295\u9012\u590D\u5BA1\u4EFB\u52A1\u5931\u8D25");
|
|
229305
230266
|
throw new WorkflowError("send-failed", "\u5411\u4E0A\u6B21 reviewer \u6295\u9012\u590D\u5BA1\u4EFB\u52A1\u5931\u8D25");
|
|
@@ -229344,13 +230305,9 @@ var WorkflowEngine = class {
|
|
|
229344
230305
|
target,
|
|
229345
230306
|
scope
|
|
229346
230307
|
});
|
|
229347
|
-
const sent = await this.agentOps.sendUserMessage(reviewerId, prompt, opts.project.path, void 0,
|
|
230308
|
+
const sent = await this.agentOps.sendUserMessage(reviewerId, prompt, opts.project.path, void 0, REVIEWER_TURN);
|
|
229348
230309
|
if (!sent) {
|
|
229349
|
-
|
|
229350
|
-
status: "failed",
|
|
229351
|
-
error: "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25"
|
|
229352
|
-
});
|
|
229353
|
-
if (failed) this.untrackRun(failed);
|
|
230310
|
+
await this.failRun({ ...run2, reviewer_session_id: reviewerId }, "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
229354
230311
|
throw new WorkflowError("spawn-failed", "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
229355
230312
|
}
|
|
229356
230313
|
const updated = await this.storage.workflowRuns.update(run2.id, { reviewer_session_id: reviewerId });
|
|
@@ -229359,11 +230316,7 @@ var WorkflowEngine = class {
|
|
|
229359
230316
|
return updated;
|
|
229360
230317
|
} catch (err) {
|
|
229361
230318
|
if (err instanceof WorkflowError && err.code === "spawn-failed") throw err;
|
|
229362
|
-
|
|
229363
|
-
status: "failed",
|
|
229364
|
-
error: `\u521B\u5EFA reviewer \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`
|
|
229365
|
-
});
|
|
229366
|
-
if (failed) this.untrackRun(failed);
|
|
230319
|
+
await this.failRun(run2, `\u521B\u5EFA reviewer \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
229367
230320
|
throw new WorkflowError("spawn-failed", "\u521B\u5EFA reviewer session \u5931\u8D25");
|
|
229368
230321
|
}
|
|
229369
230322
|
} catch (err) {
|
|
@@ -229388,11 +230341,28 @@ var WorkflowEngine = class {
|
|
|
229388
230341
|
}
|
|
229389
230342
|
} catch {
|
|
229390
230343
|
}
|
|
229391
|
-
const ok = await this.storage.workflowRuns.
|
|
229392
|
-
|
|
229393
|
-
|
|
229394
|
-
|
|
230344
|
+
const ok = await this.storage.workflowRuns.transitionWithOutbox(
|
|
230345
|
+
run2.id,
|
|
230346
|
+
"waiting_reviewer",
|
|
230347
|
+
"waiting_feedback",
|
|
230348
|
+
{
|
|
230349
|
+
feedback_snapshot: feedback,
|
|
230350
|
+
...driftNote ? { error: driftNote } : {}
|
|
230351
|
+
},
|
|
230352
|
+
{
|
|
230353
|
+
id: reviewReadyId(run2.id),
|
|
230354
|
+
kind: "review_ready",
|
|
230355
|
+
project_id: run2.project_id,
|
|
230356
|
+
branch: run2.branch,
|
|
230357
|
+
// Target the reviewer: that's where the feedback and the
|
|
230358
|
+
// approve/discard controls are.
|
|
230359
|
+
session_id: event.sessionId,
|
|
230360
|
+
workflow_run_id: run2.id,
|
|
230361
|
+
created_at: Date.now()
|
|
230362
|
+
}
|
|
230363
|
+
);
|
|
229395
230364
|
if (!ok) return;
|
|
230365
|
+
this.onMilestoneCreated?.();
|
|
229396
230366
|
const updated = await this.storage.workflowRuns.getById(run2.id);
|
|
229397
230367
|
if (updated) this.emitRunUpdated(updated);
|
|
229398
230368
|
}
|
|
@@ -229409,7 +230379,7 @@ var WorkflowEngine = class {
|
|
|
229409
230379
|
if (!claimed) throw new WorkflowError("bad-state", "run \u72B6\u6001\u5DF2\u53D8\u5316\uFF08\u53EF\u80FD\u5DF2\u88AB\u5904\u7406\uFF09");
|
|
229410
230380
|
const feedback = editedPayload ?? run2.feedback_snapshot ?? "";
|
|
229411
230381
|
const project = await this.storage.projects.getById(run2.project_id);
|
|
229412
|
-
const ok = await this.agentOps.sendUserMessage(run2.source_session_id, buildFeedbackMessage(feedback), project?.path ?? void 0, void 0,
|
|
230382
|
+
const ok = await this.agentOps.sendUserMessage(run2.source_session_id, buildFeedbackMessage(feedback), project?.path ?? void 0, void 0, FEEDBACK_TURN).catch(() => false);
|
|
229413
230383
|
if (!ok) {
|
|
229414
230384
|
await this.storage.workflowRuns.transition(runId, "sending_feedback", "waiting_feedback", {
|
|
229415
230385
|
error: "\u53D1\u9001\u5931\u8D25\uFF1A\u76EE\u6807 session \u53EF\u80FD\u672A\u8FD0\u884C\u3002\u8BF7\u5728\u5176\u7A97\u53E3\u4E2D\u5524\u9192\u540E\u91CD\u8BD5\uFF0C\u6216\u7ED3\u675F\u672C\u6B21 review\u3002"
|
|
@@ -229430,7 +230400,7 @@ var WorkflowEngine = class {
|
|
|
229430
230400
|
async cancelRun(runId, reason) {
|
|
229431
230401
|
const run2 = await this.storage.workflowRuns.getById(runId);
|
|
229432
230402
|
if (!run2) return void 0;
|
|
229433
|
-
if (
|
|
230403
|
+
if (TERMINAL_STATUSES.has(run2.status)) return run2;
|
|
229434
230404
|
const patch = reason ? { error: reason } : void 0;
|
|
229435
230405
|
const cancelled = await this.storage.workflowRuns.transition(runId, "waiting_reviewer", "cancelled", patch) || await this.storage.workflowRuns.transition(runId, "waiting_feedback", "cancelled", patch);
|
|
229436
230406
|
if (!cancelled) {
|
|
@@ -231244,6 +232214,154 @@ var SchedulerService = class {
|
|
|
231244
232214
|
}
|
|
231245
232215
|
};
|
|
231246
232216
|
|
|
232217
|
+
// src/notification-service.ts
|
|
232218
|
+
var TITLE_BY_KIND = {
|
|
232219
|
+
review_ready: "Review feedback is ready",
|
|
232220
|
+
session_result_ready: "Session result is ready",
|
|
232221
|
+
session_failed: "Session failed",
|
|
232222
|
+
workflow_failed: "Workflow needs attention"
|
|
232223
|
+
};
|
|
232224
|
+
var PLACEHOLDER_TITLES = /* @__PURE__ */ new Set(["New Session", "Generating title\u2026", "Generating title..."]);
|
|
232225
|
+
var LOCAL_CURSOR_KEY = "notification_local_cursor";
|
|
232226
|
+
var OUTBOX_PAGE_SIZE = 100;
|
|
232227
|
+
var READ_HISTORY_PER_USER = 500;
|
|
232228
|
+
var OUTBOX_RETENTION_DAYS = 90;
|
|
232229
|
+
var DRAIN_INTERVAL_MS = 3e4;
|
|
232230
|
+
var CLEANUP_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
232231
|
+
function notificationBody(opts) {
|
|
232232
|
+
const title = opts.sessionTitle?.trim();
|
|
232233
|
+
if (title && !PLACEHOLDER_TITLES.has(title)) return title;
|
|
232234
|
+
if (opts.branch) return opts.branch;
|
|
232235
|
+
return opts.projectName?.trim() || null;
|
|
232236
|
+
}
|
|
232237
|
+
function notificationTitle(kind) {
|
|
232238
|
+
return TITLE_BY_KIND[kind] ?? "Update";
|
|
232239
|
+
}
|
|
232240
|
+
var NotificationService = class {
|
|
232241
|
+
constructor(storage, eventBus) {
|
|
232242
|
+
this.storage = storage;
|
|
232243
|
+
this.eventBus = eventBus;
|
|
232244
|
+
}
|
|
232245
|
+
storage;
|
|
232246
|
+
eventBus;
|
|
232247
|
+
drainTimer = null;
|
|
232248
|
+
cleanupTimer = null;
|
|
232249
|
+
/** Serializes drains so a nudge can't interleave with the periodic tick. */
|
|
232250
|
+
drainChain = Promise.resolve();
|
|
232251
|
+
stopped = false;
|
|
232252
|
+
/**
|
|
232253
|
+
* Start the background timers. Unref'd: a pending drain must never be the
|
|
232254
|
+
* reason a CLI process refuses to exit.
|
|
232255
|
+
*/
|
|
232256
|
+
start() {
|
|
232257
|
+
if (this.drainTimer || this.stopped) return;
|
|
232258
|
+
this.drainTimer = setInterval(() => this.requestDrain(), DRAIN_INTERVAL_MS);
|
|
232259
|
+
this.drainTimer.unref?.();
|
|
232260
|
+
this.cleanupTimer = setInterval(() => {
|
|
232261
|
+
void this.cleanup().catch((err) => console.warn("[Notifications] cleanup failed:", err));
|
|
232262
|
+
}, CLEANUP_INTERVAL_MS);
|
|
232263
|
+
this.cleanupTimer.unref?.();
|
|
232264
|
+
}
|
|
232265
|
+
shutdown() {
|
|
232266
|
+
this.stopped = true;
|
|
232267
|
+
if (this.drainTimer) clearInterval(this.drainTimer);
|
|
232268
|
+
if (this.cleanupTimer) clearInterval(this.cleanupTimer);
|
|
232269
|
+
this.drainTimer = null;
|
|
232270
|
+
this.cleanupTimer = null;
|
|
232271
|
+
}
|
|
232272
|
+
/**
|
|
232273
|
+
* Fire-and-forget nudge used right after a milestone lands, so the bell is
|
|
232274
|
+
* fast in the common case. Queued behind any in-flight drain.
|
|
232275
|
+
*/
|
|
232276
|
+
requestDrain() {
|
|
232277
|
+
if (this.stopped) return;
|
|
232278
|
+
this.drainChain = this.drainChain.then(() => this.drainLocal()).catch((err) => console.warn("[Notifications] local drain failed:", err));
|
|
232279
|
+
}
|
|
232280
|
+
/**
|
|
232281
|
+
* Import every local outbox event after the durable cursor.
|
|
232282
|
+
*
|
|
232283
|
+
* The cursor advances per page AFTER its rows are inserted, so a crash
|
|
232284
|
+
* mid-drain replays the page — harmless, because insertion is idempotent on
|
|
232285
|
+
* the milestone id and `notification:created` is only emitted for rows this
|
|
232286
|
+
* call actually inserted.
|
|
232287
|
+
*/
|
|
232288
|
+
async drainLocal() {
|
|
232289
|
+
for (; ; ) {
|
|
232290
|
+
const cursor = await this.readLocalCursor();
|
|
232291
|
+
const events = await this.storage.notificationOutbox.listAfter(cursor, OUTBOX_PAGE_SIZE);
|
|
232292
|
+
if (events.length === 0) return;
|
|
232293
|
+
for (const event of events) {
|
|
232294
|
+
await this.importLocalEvent(event);
|
|
232295
|
+
}
|
|
232296
|
+
await this.storage.settings.set(LOCAL_CURSOR_KEY, String(events[events.length - 1].seq));
|
|
232297
|
+
if (events.length < OUTBOX_PAGE_SIZE) return;
|
|
232298
|
+
}
|
|
232299
|
+
}
|
|
232300
|
+
async importLocalEvent(event) {
|
|
232301
|
+
const ownerId = await this.storage.projects.getOwnerId(event.project_id);
|
|
232302
|
+
if (ownerId === void 0) {
|
|
232303
|
+
console.warn(`[Notifications] dropping outbox event ${event.id}: project ${event.project_id} not found`);
|
|
232304
|
+
return;
|
|
232305
|
+
}
|
|
232306
|
+
const notification = await this.buildNotification(event, {
|
|
232307
|
+
id: event.id,
|
|
232308
|
+
// "" is the solo-mode column default; the browser routes read the
|
|
232309
|
+
// "local" sentinel, so normalize here rather than in every query.
|
|
232310
|
+
userId: ownerId || "local",
|
|
232311
|
+
sessionId: event.session_id,
|
|
232312
|
+
projectId: event.project_id,
|
|
232313
|
+
workflowRunId: event.workflow_run_id
|
|
232314
|
+
});
|
|
232315
|
+
if (await this.storage.notifications.insert(notification)) {
|
|
232316
|
+
this.eventBus.emit({ type: "notification:created", projectId: notification.project_id, notification });
|
|
232317
|
+
}
|
|
232318
|
+
}
|
|
232319
|
+
/**
|
|
232320
|
+
* Assemble the persisted row. `ids` is separate from the event because remote
|
|
232321
|
+
* imports substitute front-local identities (see RemoteNotificationSync) while
|
|
232322
|
+
* reusing this exact copy generation.
|
|
232323
|
+
*/
|
|
232324
|
+
async buildNotification(event, ids) {
|
|
232325
|
+
const session = ids.sessionId ? await this.storage.agentSessions.getById(ids.sessionId) : void 0;
|
|
232326
|
+
const project = await this.storage.projects.getById(ids.projectId);
|
|
232327
|
+
return {
|
|
232328
|
+
id: ids.id,
|
|
232329
|
+
user_id: ids.userId,
|
|
232330
|
+
kind: event.kind,
|
|
232331
|
+
project_id: ids.projectId,
|
|
232332
|
+
branch: event.branch,
|
|
232333
|
+
session_id: ids.sessionId,
|
|
232334
|
+
workflow_run_id: ids.workflowRunId,
|
|
232335
|
+
title: notificationTitle(event.kind),
|
|
232336
|
+
body: notificationBody({
|
|
232337
|
+
sessionTitle: session?.title,
|
|
232338
|
+
branch: event.branch,
|
|
232339
|
+
projectName: project?.name
|
|
232340
|
+
}),
|
|
232341
|
+
created_at: event.created_at,
|
|
232342
|
+
read_at: null
|
|
232343
|
+
};
|
|
232344
|
+
}
|
|
232345
|
+
/** Emit for a row a remote import just inserted. */
|
|
232346
|
+
emitCreated(notification) {
|
|
232347
|
+
this.eventBus.emit({ type: "notification:created", projectId: notification.project_id, notification });
|
|
232348
|
+
}
|
|
232349
|
+
/** Retention: cap read history per user, prune aged worker outbox rows. */
|
|
232350
|
+
async cleanup() {
|
|
232351
|
+
await this.storage.notifications.cleanup(READ_HISTORY_PER_USER);
|
|
232352
|
+
await this.storage.notificationOutbox.pruneOlderThan(Date.now() - OUTBOX_RETENTION_DAYS * 864e5);
|
|
232353
|
+
}
|
|
232354
|
+
async readLocalCursor() {
|
|
232355
|
+
const raw = await this.storage.settings.get(LOCAL_CURSOR_KEY);
|
|
232356
|
+
const parsed = raw === void 0 ? 0 : Number(raw);
|
|
232357
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
232358
|
+
}
|
|
232359
|
+
/** Simulates a crash between inbox insertion and cursor commit. */
|
|
232360
|
+
async resetLocalCursorForTest() {
|
|
232361
|
+
await this.storage.settings.set(LOCAL_CURSOR_KEY, "0");
|
|
232362
|
+
}
|
|
232363
|
+
};
|
|
232364
|
+
|
|
231247
232365
|
// src/plugins/shared-services.ts
|
|
231248
232366
|
var sharedServices = async (fastify2, opts) => {
|
|
231249
232367
|
const processManager = new ProcessManager(opts.storage);
|
|
@@ -231382,8 +232500,25 @@ var sharedServices = async (fastify2, opts) => {
|
|
|
231382
232500
|
fastify2.decorate("browserManager", browserManager);
|
|
231383
232501
|
fastify2.decorate("scheduler", scheduler);
|
|
231384
232502
|
agentSessionManager.setEventBus(eventBus);
|
|
232503
|
+
const notificationService = new NotificationService(opts.storage, eventBus);
|
|
232504
|
+
fastify2.decorate("notificationService", notificationService);
|
|
232505
|
+
agentSessionManager.setMilestoneListener(() => notificationService.requestDrain());
|
|
232506
|
+
const remoteNotificationSync = new RemoteNotificationSync({
|
|
232507
|
+
storage: opts.storage,
|
|
232508
|
+
notificationService,
|
|
232509
|
+
reverseConnectManager
|
|
232510
|
+
});
|
|
232511
|
+
fastify2.decorate("remoteNotificationSync", remoteNotificationSync);
|
|
232512
|
+
remoteNotificationSync.setEventBus(eventBus);
|
|
232513
|
+
reverseConnectManager.setStatusChangeHandler((remoteServerId, status) => {
|
|
232514
|
+
if (status !== "online") return;
|
|
232515
|
+
remoteNotificationSync.enqueue(
|
|
232516
|
+
() => remoteNotificationSync.syncServer(remoteServerId, { includeExpired: true })
|
|
232517
|
+
);
|
|
232518
|
+
});
|
|
231385
232519
|
const workflowEngine = new WorkflowEngine(opts.storage, agentSessionManager);
|
|
231386
232520
|
workflowEngine.setEventBus(eventBus);
|
|
232521
|
+
workflowEngine.setMilestoneListener(() => notificationService.requestDrain());
|
|
231387
232522
|
await workflowEngine.init();
|
|
231388
232523
|
fastify2.decorate("workflowEngine", workflowEngine);
|
|
231389
232524
|
chatSessionManager.setWorkflowEngine(workflowEngine);
|
|
@@ -231393,6 +232528,10 @@ var sharedServices = async (fastify2, opts) => {
|
|
|
231393
232528
|
processManager.setEventBus(eventBus);
|
|
231394
232529
|
scheduler.setEventBus(eventBus);
|
|
231395
232530
|
await scheduler.start();
|
|
232531
|
+
notificationService.start();
|
|
232532
|
+
notificationService.requestDrain();
|
|
232533
|
+
remoteNotificationSync.start();
|
|
232534
|
+
remoteNotificationSync.enqueue(() => remoteNotificationSync.syncAll({ includeExpired: true }));
|
|
231396
232535
|
void (async () => {
|
|
231397
232536
|
const savedRows = await opts.storage.remoteExecutorProcesses.getRunning();
|
|
231398
232537
|
const directUrlRows = savedRows.filter((r) => r.remote_url);
|
|
@@ -231408,6 +232547,8 @@ var sharedServices = async (fastify2, opts) => {
|
|
|
231408
232547
|
});
|
|
231409
232548
|
fastify2.addHook("onClose", async () => {
|
|
231410
232549
|
scheduler.shutdown();
|
|
232550
|
+
notificationService.shutdown();
|
|
232551
|
+
remoteNotificationSync.shutdown();
|
|
231411
232552
|
agentSessionManager.shutdown();
|
|
231412
232553
|
processManager.shutdown();
|
|
231413
232554
|
remotePatchCache.shutdown();
|
|
@@ -231416,10 +232557,10 @@ var sharedServices = async (fastify2, opts) => {
|
|
|
231416
232557
|
await browserManager.shutdown();
|
|
231417
232558
|
});
|
|
231418
232559
|
};
|
|
231419
|
-
var shared_services_default = (0,
|
|
232560
|
+
var shared_services_default = (0, import_fastify_plugin2.default)(sharedServices, { name: "shared-services" });
|
|
231420
232561
|
|
|
231421
232562
|
// src/routes/project-routes.ts
|
|
231422
|
-
var
|
|
232563
|
+
var import_fastify_plugin3 = __toESM(require_plugin2(), 1);
|
|
231423
232564
|
import path8 from "path";
|
|
231424
232565
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
231425
232566
|
import { exec as exec2 } from "child_process";
|
|
@@ -231472,7 +232613,7 @@ function sanitizeProject(project) {
|
|
|
231472
232613
|
const { remote_api_key, ...safe } = project;
|
|
231473
232614
|
return { ...safe, has_remote_api_key: !!remote_api_key };
|
|
231474
232615
|
}
|
|
231475
|
-
var
|
|
232616
|
+
var routes2 = async (fastify2) => {
|
|
231476
232617
|
fastify2.get("/api/projects", async (req, reply) => {
|
|
231477
232618
|
const userId = requireAuth(req, reply);
|
|
231478
232619
|
if (userId === null) return;
|
|
@@ -231734,12 +232875,12 @@ var routes = async (fastify2) => {
|
|
|
231734
232875
|
}
|
|
231735
232876
|
});
|
|
231736
232877
|
};
|
|
231737
|
-
var project_routes_default = (0,
|
|
232878
|
+
var project_routes_default = (0, import_fastify_plugin3.default)(routes2, { name: "project-routes" });
|
|
231738
232879
|
|
|
231739
232880
|
// src/routes/remote-routes.ts
|
|
231740
|
-
var
|
|
232881
|
+
var import_fastify_plugin4 = __toESM(require_plugin2(), 1);
|
|
231741
232882
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
231742
|
-
var
|
|
232883
|
+
var routes3 = async (fastify2) => {
|
|
231743
232884
|
fastify2.post("/api/remote/test-connection", async (req, reply) => {
|
|
231744
232885
|
const { url: url2, apiKey } = req.body;
|
|
231745
232886
|
if (!url2 || !apiKey) {
|
|
@@ -231791,17 +232932,17 @@ var routes2 = async (fastify2) => {
|
|
|
231791
232932
|
return reply.code(201).send({ project: safeProject });
|
|
231792
232933
|
});
|
|
231793
232934
|
};
|
|
231794
|
-
var remote_routes_default = (0,
|
|
232935
|
+
var remote_routes_default = (0, import_fastify_plugin4.default)(routes3, { name: "remote-routes" });
|
|
231795
232936
|
|
|
231796
232937
|
// src/routes/remote-server-routes.ts
|
|
231797
|
-
var
|
|
232938
|
+
var import_fastify_plugin5 = __toESM(require_plugin2(), 1);
|
|
231798
232939
|
function sanitizeServer(server) {
|
|
231799
232940
|
const { api_key: _3, connect_token: _t, ...safe } = server;
|
|
231800
232941
|
return safe;
|
|
231801
232942
|
}
|
|
231802
232943
|
var CROSS_REMOTE_ACCESS_VALUES = ["off", "read", "exec"];
|
|
231803
232944
|
var isCrossRemoteAccess = (value) => typeof value === "string" && CROSS_REMOTE_ACCESS_VALUES.includes(value);
|
|
231804
|
-
var
|
|
232945
|
+
var routes4 = async (fastify2) => {
|
|
231805
232946
|
fastify2.get("/api/remote-servers", async (request, reply) => {
|
|
231806
232947
|
const userId = requireAuth(request, reply);
|
|
231807
232948
|
if (userId === null) return;
|
|
@@ -231986,15 +233127,15 @@ var routes3 = async (fastify2) => {
|
|
|
231986
233127
|
}
|
|
231987
233128
|
);
|
|
231988
233129
|
};
|
|
231989
|
-
var remote_server_routes_default = (0,
|
|
233130
|
+
var remote_server_routes_default = (0, import_fastify_plugin5.default)(routes4, { name: "remote-server-routes" });
|
|
231990
233131
|
|
|
231991
233132
|
// src/routes/project-remote-routes.ts
|
|
231992
|
-
var
|
|
233133
|
+
var import_fastify_plugin6 = __toESM(require_plugin2(), 1);
|
|
231993
233134
|
function sanitizeProjectRemote(pr) {
|
|
231994
233135
|
const { server_api_key: _3, ...safe } = pr;
|
|
231995
233136
|
return safe;
|
|
231996
233137
|
}
|
|
231997
|
-
var
|
|
233138
|
+
var routes5 = async (fastify2) => {
|
|
231998
233139
|
fastify2.get(
|
|
231999
233140
|
"/api/projects/:id/remotes",
|
|
232000
233141
|
async (request, reply) => {
|
|
@@ -232088,12 +233229,12 @@ var routes4 = async (fastify2) => {
|
|
|
232088
233229
|
}
|
|
232089
233230
|
);
|
|
232090
233231
|
};
|
|
232091
|
-
var project_remote_routes_default = (0,
|
|
233232
|
+
var project_remote_routes_default = (0, import_fastify_plugin6.default)(routes5, { name: "project-remote-routes" });
|
|
232092
233233
|
|
|
232093
233234
|
// src/routes/executor-group-routes.ts
|
|
232094
|
-
var
|
|
233235
|
+
var import_fastify_plugin7 = __toESM(require_plugin2(), 1);
|
|
232095
233236
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
232096
|
-
var
|
|
233237
|
+
var routes6 = async (fastify2) => {
|
|
232097
233238
|
fastify2.get(
|
|
232098
233239
|
"/api/projects/:projectId/executor-groups",
|
|
232099
233240
|
async (req, reply) => {
|
|
@@ -232164,17 +233305,17 @@ var routes5 = async (fastify2) => {
|
|
|
232164
233305
|
}
|
|
232165
233306
|
);
|
|
232166
233307
|
};
|
|
232167
|
-
var executor_group_routes_default = (0,
|
|
233308
|
+
var executor_group_routes_default = (0, import_fastify_plugin7.default)(routes6, { name: "executor-group-routes" });
|
|
232168
233309
|
|
|
232169
233310
|
// src/routes/executor-routes.ts
|
|
232170
|
-
var
|
|
233311
|
+
var import_fastify_plugin8 = __toESM(require_plugin2(), 1);
|
|
232171
233312
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
232172
233313
|
function normalizeSqlTimestamp(value) {
|
|
232173
233314
|
if (!value) return null;
|
|
232174
233315
|
if (value.includes("T") && /(Z|[+-]\d{2}:?\d{2})$/.test(value)) return value;
|
|
232175
233316
|
return value.replace(" ", "T") + "Z";
|
|
232176
233317
|
}
|
|
232177
|
-
var
|
|
233318
|
+
var routes7 = async (fastify2) => {
|
|
232178
233319
|
fastify2.get(
|
|
232179
233320
|
"/api/projects/:projectId/executors",
|
|
232180
233321
|
async (req, reply) => {
|
|
@@ -232313,13 +233454,13 @@ var routes6 = async (fastify2) => {
|
|
|
232313
233454
|
return reply.code(200).send({ success: true });
|
|
232314
233455
|
});
|
|
232315
233456
|
};
|
|
232316
|
-
var executor_routes_default = (0,
|
|
233457
|
+
var executor_routes_default = (0, import_fastify_plugin8.default)(routes7, { name: "executor-routes" });
|
|
232317
233458
|
|
|
232318
233459
|
// src/routes/process-routes.ts
|
|
232319
|
-
var
|
|
233460
|
+
var import_fastify_plugin9 = __toESM(require_plugin2(), 1);
|
|
232320
233461
|
import path9 from "path";
|
|
232321
233462
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
232322
|
-
var
|
|
233463
|
+
var routes8 = async (fastify2) => {
|
|
232323
233464
|
fastify2.post("/api/path/execute", async (req, reply) => {
|
|
232324
233465
|
const { path: projectPath, command, executor_type, prompt_provider, cwd, branch, pty: pty2 } = req.body;
|
|
232325
233466
|
if (!projectPath || !command) {
|
|
@@ -232529,10 +233670,10 @@ var routes7 = async (fastify2) => {
|
|
|
232529
233670
|
return reply.code(200).send({ processes });
|
|
232530
233671
|
});
|
|
232531
233672
|
};
|
|
232532
|
-
var process_routes_default = (0,
|
|
233673
|
+
var process_routes_default = (0, import_fastify_plugin9.default)(routes8, { name: "process-routes" });
|
|
232533
233674
|
|
|
232534
233675
|
// src/routes/worktree-routes.ts
|
|
232535
|
-
var
|
|
233676
|
+
var import_fastify_plugin10 = __toESM(require_plugin2(), 1);
|
|
232536
233677
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
232537
233678
|
async function getAllRemoteConfigs(fastify2, project) {
|
|
232538
233679
|
const remotes = await fastify2.storage.projectRemotes.getByProject(project.id);
|
|
@@ -232558,7 +233699,7 @@ async function getRemoteConfig(fastify2, project) {
|
|
|
232558
233699
|
const all = await getAllRemoteConfigs(fastify2, project);
|
|
232559
233700
|
return all.length > 0 ? all[0] : null;
|
|
232560
233701
|
}
|
|
232561
|
-
var
|
|
233702
|
+
var routes9 = async (fastify2) => {
|
|
232562
233703
|
fastify2.get("/api/path/branches", async (req, reply) => {
|
|
232563
233704
|
const projectPath = req.query.path;
|
|
232564
233705
|
if (!projectPath) {
|
|
@@ -233096,10 +234237,10 @@ var routes8 = async (fastify2) => {
|
|
|
233096
234237
|
});
|
|
233097
234238
|
});
|
|
233098
234239
|
};
|
|
233099
|
-
var worktree_routes_default = (0,
|
|
234240
|
+
var worktree_routes_default = (0, import_fastify_plugin10.default)(routes9, { name: "worktree-routes" });
|
|
233100
234241
|
|
|
233101
234242
|
// src/routes/diff-routes.ts
|
|
233102
|
-
var
|
|
234243
|
+
var import_fastify_plugin11 = __toESM(require_plugin2(), 1);
|
|
233103
234244
|
import path10 from "path";
|
|
233104
234245
|
import { readFileSync as readFileSync4 } from "fs";
|
|
233105
234246
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
@@ -233419,7 +234560,7 @@ async function getUntrackedFiles(cwd) {
|
|
|
233419
234560
|
}
|
|
233420
234561
|
return files;
|
|
233421
234562
|
}
|
|
233422
|
-
var
|
|
234563
|
+
var routes10 = async (fastify2) => {
|
|
233423
234564
|
fastify2.get("/api/path/diff", async (req, reply) => {
|
|
233424
234565
|
const projectPath = req.query.path;
|
|
233425
234566
|
if (!projectPath) {
|
|
@@ -233614,10 +234755,10 @@ var routes9 = async (fastify2) => {
|
|
|
233614
234755
|
}
|
|
233615
234756
|
});
|
|
233616
234757
|
};
|
|
233617
|
-
var diff_routes_default = (0,
|
|
234758
|
+
var diff_routes_default = (0, import_fastify_plugin11.default)(routes10, { name: "diff-routes" });
|
|
233618
234759
|
|
|
233619
234760
|
// src/routes/file-routes.ts
|
|
233620
|
-
var
|
|
234761
|
+
var import_fastify_plugin12 = __toESM(require_plugin2(), 1);
|
|
233621
234762
|
import path11 from "path";
|
|
233622
234763
|
import fs2 from "fs/promises";
|
|
233623
234764
|
import { createReadStream, constants as fsConstants } from "fs";
|
|
@@ -233853,7 +234994,7 @@ async function searchSymbol(basePath33, symbol26) {
|
|
|
233853
234994
|
}
|
|
233854
234995
|
return { hits, truncated };
|
|
233855
234996
|
}
|
|
233856
|
-
var
|
|
234997
|
+
var routes11 = async (fastify2) => {
|
|
233857
234998
|
fastify2.get("/api/path/browse", async (req, reply) => {
|
|
233858
234999
|
const projectPath = req.query.path;
|
|
233859
235000
|
if (!projectPath) {
|
|
@@ -234447,10 +235588,10 @@ var routes10 = async (fastify2) => {
|
|
|
234447
235588
|
}
|
|
234448
235589
|
});
|
|
234449
235590
|
};
|
|
234450
|
-
var file_routes_default = (0,
|
|
235591
|
+
var file_routes_default = (0, import_fastify_plugin12.default)(routes11, { name: "file-routes" });
|
|
234451
235592
|
|
|
234452
235593
|
// src/routes/agent-session-routes.ts
|
|
234453
|
-
var
|
|
235594
|
+
var import_fastify_plugin13 = __toESM(require_plugin2(), 1);
|
|
234454
235595
|
|
|
234455
235596
|
// src/utils/paste-file.ts
|
|
234456
235597
|
import { chmod, mkdir as mkdir4, open } from "node:fs/promises";
|
|
@@ -234482,6 +235623,14 @@ function resolveUserId(authResult) {
|
|
|
234482
235623
|
|
|
234483
235624
|
// src/routes/agent-session-routes.ts
|
|
234484
235625
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
235626
|
+
|
|
235627
|
+
// src/protocol/model-suggestions.ts
|
|
235628
|
+
var MODEL_SUGGESTIONS = {
|
|
235629
|
+
"claude-code": ["opus", "sonnet", "haiku"],
|
|
235630
|
+
codex: ["gpt-5.6-sol", "gpt-5.6-codex", "o3"]
|
|
235631
|
+
};
|
|
235632
|
+
|
|
235633
|
+
// src/routes/agent-session-routes.ts
|
|
234485
235634
|
async function resolveProjectPath(projectId, storage) {
|
|
234486
235635
|
if (projectId.startsWith("path:")) {
|
|
234487
235636
|
return projectId.slice(5);
|
|
@@ -234498,7 +235647,7 @@ function messageTextLength(content) {
|
|
|
234498
235647
|
}
|
|
234499
235648
|
return n;
|
|
234500
235649
|
}
|
|
234501
|
-
var
|
|
235650
|
+
var routes12 = async (fastify2) => {
|
|
234502
235651
|
function proxyAuto(remoteServerId, remoteUrl, remoteApiKey, method, apiPath, body) {
|
|
234503
235652
|
return proxyToRemoteAuto(remoteServerId, remoteUrl, remoteApiKey, method, apiPath, body, {
|
|
234504
235653
|
reverseConnectManager: fastify2.reverseConnectManager
|
|
@@ -234545,6 +235694,7 @@ var routes11 = async (fastify2) => {
|
|
|
234545
235694
|
status: session?.status || "stopped",
|
|
234546
235695
|
permissionMode: session?.permissionMode || "edit",
|
|
234547
235696
|
agentType: session?.agentType || "claude-code",
|
|
235697
|
+
model: session?.model ?? null,
|
|
234548
235698
|
title: dbRow?.title ?? null
|
|
234549
235699
|
},
|
|
234550
235700
|
messages
|
|
@@ -234555,15 +235705,23 @@ var routes11 = async (fastify2) => {
|
|
|
234555
235705
|
fastify2.agentSessionManager.emitSessionTitle(projectId, branch, sessionId, title);
|
|
234556
235706
|
}
|
|
234557
235707
|
fastify2.get("/api/agent-providers", async (_req, reply) => {
|
|
234558
|
-
const providers2 = getAllProviders().map((provider) =>
|
|
234559
|
-
type
|
|
234560
|
-
|
|
234561
|
-
|
|
234562
|
-
|
|
235708
|
+
const providers2 = getAllProviders().map((provider) => {
|
|
235709
|
+
const type = provider.getAgentType();
|
|
235710
|
+
return {
|
|
235711
|
+
type,
|
|
235712
|
+
displayName: provider.getDisplayName(),
|
|
235713
|
+
available: provider.isAvailable?.() ?? provider.detectBinary() !== null,
|
|
235714
|
+
// Suggestions only. The picker also accepts free text, and nothing
|
|
235715
|
+
// validates against this list — the session's real capabilities depend
|
|
235716
|
+
// on the CLI version and account tier of whichever machine spawns it,
|
|
235717
|
+
// which this server cannot know.
|
|
235718
|
+
models: MODEL_SUGGESTIONS[type] ?? []
|
|
235719
|
+
};
|
|
235720
|
+
});
|
|
234563
235721
|
return reply.code(200).send({ providers: providers2 });
|
|
234564
235722
|
});
|
|
234565
235723
|
fastify2.post("/api/path/agent-sessions", async (req, reply) => {
|
|
234566
|
-
const { path: projectPath, branch, permissionMode, agentType, force } = req.body;
|
|
235724
|
+
const { path: projectPath, branch, permissionMode, agentType, force, model } = req.body;
|
|
234567
235725
|
if (!projectPath) {
|
|
234568
235726
|
return reply.code(400).send({ error: "Path is required" });
|
|
234569
235727
|
}
|
|
@@ -234611,6 +235769,7 @@ var routes11 = async (fastify2) => {
|
|
|
234611
235769
|
status: effectiveStatus,
|
|
234612
235770
|
permissionMode: session?.permissionMode || "edit",
|
|
234613
235771
|
agentType: session?.agentType || "claude-code",
|
|
235772
|
+
model: session?.model ?? null,
|
|
234614
235773
|
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false
|
|
234615
235774
|
},
|
|
234616
235775
|
messages
|
|
@@ -234655,7 +235814,7 @@ var routes11 = async (fastify2) => {
|
|
|
234655
235814
|
}
|
|
234656
235815
|
);
|
|
234657
235816
|
fastify2.post("/api/path/agent-sessions/new", async (req, reply) => {
|
|
234658
|
-
const { path: projectPath, branch, permissionMode, agentType, force, sessionId, crossRemoteMcp } = req.body;
|
|
235817
|
+
const { path: projectPath, branch, permissionMode, agentType, force, sessionId, crossRemoteMcp, model } = req.body;
|
|
234659
235818
|
if (!projectPath) {
|
|
234660
235819
|
return reply.code(400).send({ error: "Path is required" });
|
|
234661
235820
|
}
|
|
@@ -234683,7 +235842,7 @@ var routes11 = async (fastify2) => {
|
|
|
234683
235842
|
agentType || "claude-code",
|
|
234684
235843
|
false,
|
|
234685
235844
|
force === true,
|
|
234686
|
-
{ sessionId, crossRemoteMcp }
|
|
235845
|
+
{ sessionId, crossRemoteMcp, model }
|
|
234687
235846
|
);
|
|
234688
235847
|
const session = fastify2.agentSessionManager.getSession(createdSessionId);
|
|
234689
235848
|
return reply.code(200).send({
|
|
@@ -234694,6 +235853,7 @@ var routes11 = async (fastify2) => {
|
|
|
234694
235853
|
status: session?.status || "running",
|
|
234695
235854
|
permissionMode: session?.permissionMode || "edit",
|
|
234696
235855
|
agentType: session?.agentType || "claude-code",
|
|
235856
|
+
model: session?.model ?? null,
|
|
234697
235857
|
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(session.id) : false
|
|
234698
235858
|
},
|
|
234699
235859
|
messages: []
|
|
@@ -234756,7 +235916,8 @@ var routes11 = async (fastify2) => {
|
|
|
234756
235916
|
project.id,
|
|
234757
235917
|
project.agent_mode,
|
|
234758
235918
|
s3.id,
|
|
234759
|
-
s3.branch ?? null
|
|
235919
|
+
s3.branch ?? null,
|
|
235920
|
+
"from_now"
|
|
234760
235921
|
);
|
|
234761
235922
|
return { ...s3, id: localSessionId, processAlive: s3.processAlive ?? false, entry_count: s3.entry_count ?? 0 };
|
|
234762
235923
|
}));
|
|
@@ -234795,7 +235956,7 @@ var routes11 = async (fastify2) => {
|
|
|
234795
235956
|
if (!project) {
|
|
234796
235957
|
return reply.code(404).send({ error: "Project not found" });
|
|
234797
235958
|
}
|
|
234798
|
-
const { branch, permissionMode, agentType } = req.body;
|
|
235959
|
+
const { branch, permissionMode, agentType, model } = req.body;
|
|
234799
235960
|
let agentMode = project.agent_mode;
|
|
234800
235961
|
let useRemoteAgent = agentMode !== "local";
|
|
234801
235962
|
if (useRemoteAgent && agentMode === "remote") {
|
|
@@ -234830,7 +235991,7 @@ var routes11 = async (fastify2) => {
|
|
|
234830
235991
|
remoteConfig.server_api_key || "",
|
|
234831
235992
|
"POST",
|
|
234832
235993
|
`/api/path/agent-sessions`,
|
|
234833
|
-
{ path: remoteConfig.remote_path, branch, permissionMode, agentType }
|
|
235994
|
+
{ path: remoteConfig.remote_path, branch, permissionMode, agentType, model }
|
|
234834
235995
|
);
|
|
234835
235996
|
console.log(`[API] Remote proxy result: ok=${result.ok}, status=${result.status}, data=${JSON.stringify(result.data).substring(0, 500)}`);
|
|
234836
235997
|
if (result.ok) {
|
|
@@ -234851,7 +236012,8 @@ var routes11 = async (fastify2) => {
|
|
|
234851
236012
|
project.id,
|
|
234852
236013
|
agentMode,
|
|
234853
236014
|
remoteData.session.id,
|
|
234854
|
-
branch ?? null
|
|
236015
|
+
branch ?? null,
|
|
236016
|
+
"from_now"
|
|
234855
236017
|
);
|
|
234856
236018
|
if (remoteData.messages && remoteData.messages.length > 0) {
|
|
234857
236019
|
const cacheEntry = fastify2.remotePatchCache.getOrCreate(localSessionId);
|
|
@@ -234907,6 +236069,7 @@ var routes11 = async (fastify2) => {
|
|
|
234907
236069
|
status: effectiveStatus,
|
|
234908
236070
|
permissionMode: session?.permissionMode || "edit",
|
|
234909
236071
|
agentType: session?.agentType || "claude-code",
|
|
236072
|
+
model: session?.model ?? null,
|
|
234910
236073
|
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false
|
|
234911
236074
|
},
|
|
234912
236075
|
messages
|
|
@@ -234923,7 +236086,7 @@ var routes11 = async (fastify2) => {
|
|
|
234923
236086
|
if (!project) {
|
|
234924
236087
|
return reply.code(404).send({ error: "Project not found" });
|
|
234925
236088
|
}
|
|
234926
|
-
const { branch, permissionMode, agentType, force } = req.body;
|
|
236089
|
+
const { branch, permissionMode, agentType, force, model } = req.body;
|
|
234927
236090
|
const agentMode = project.agent_mode;
|
|
234928
236091
|
const useRemoteAgent = agentMode !== "local";
|
|
234929
236092
|
if (useRemoteAgent) {
|
|
@@ -234949,7 +236112,8 @@ var routes11 = async (fastify2) => {
|
|
|
234949
236112
|
permissionMode: permissionMode || "edit",
|
|
234950
236113
|
agentType,
|
|
234951
236114
|
force: force === true,
|
|
234952
|
-
userId
|
|
236115
|
+
userId,
|
|
236116
|
+
model
|
|
234953
236117
|
}
|
|
234954
236118
|
);
|
|
234955
236119
|
if (created.ok) {
|
|
@@ -234982,7 +236146,7 @@ var routes11 = async (fastify2) => {
|
|
|
234982
236146
|
agentType || "claude-code",
|
|
234983
236147
|
false,
|
|
234984
236148
|
force === true,
|
|
234985
|
-
{ sessionId: preSessionId, crossRemoteMcp }
|
|
236149
|
+
{ sessionId: preSessionId, crossRemoteMcp, model }
|
|
234986
236150
|
);
|
|
234987
236151
|
const session = fastify2.agentSessionManager.getSession(sessionId);
|
|
234988
236152
|
return reply.code(200).send({
|
|
@@ -234993,6 +236157,7 @@ var routes11 = async (fastify2) => {
|
|
|
234993
236157
|
status: session?.status || "running",
|
|
234994
236158
|
permissionMode: session?.permissionMode || "edit",
|
|
234995
236159
|
agentType: session?.agentType || "claude-code",
|
|
236160
|
+
model: session?.model ?? null,
|
|
234996
236161
|
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false
|
|
234997
236162
|
},
|
|
234998
236163
|
messages: []
|
|
@@ -235049,6 +236214,7 @@ var routes11 = async (fastify2) => {
|
|
|
235049
236214
|
status: session.status,
|
|
235050
236215
|
permissionMode: session.permissionMode,
|
|
235051
236216
|
agentType: session.agentType || "claude-code",
|
|
236217
|
+
model: session.model ?? null,
|
|
235052
236218
|
processAlive: fastify2.agentSessionManager.getSessionProcessAlive(session.id)
|
|
235053
236219
|
},
|
|
235054
236220
|
messages
|
|
@@ -235078,6 +236244,12 @@ var routes11 = async (fastify2) => {
|
|
|
235078
236244
|
console.log(`[API] /message 404: remote session not found. Known keys: [${[...fastify2.remoteSessionMap.keys()].join(", ")}]`);
|
|
235079
236245
|
return reply.code(404).send({ error: "Remote session not found" });
|
|
235080
236246
|
}
|
|
236247
|
+
if (!await fastify2.remoteNotificationSync.prepareForNewTurn(req.params.sessionId)) {
|
|
236248
|
+
return reply.code(502).send({
|
|
236249
|
+
error: "Could not reach the remote server to prepare notification delivery",
|
|
236250
|
+
errorCode: "notification_baseline_failed"
|
|
236251
|
+
});
|
|
236252
|
+
}
|
|
235081
236253
|
const result = await proxyAuto(
|
|
235082
236254
|
remoteInfo.remoteServerId,
|
|
235083
236255
|
remoteInfo.remoteUrl,
|
|
@@ -235332,7 +236504,8 @@ var routes11 = async (fastify2) => {
|
|
|
235332
236504
|
projectId,
|
|
235333
236505
|
remoteInfo.remoteServerId,
|
|
235334
236506
|
newRemoteSessionId,
|
|
235335
|
-
remoteInfo.branch ?? null
|
|
236507
|
+
remoteInfo.branch ?? null,
|
|
236508
|
+
"from_start"
|
|
235336
236509
|
);
|
|
235337
236510
|
await fastify2.storage.remoteSessionMappings.markTitleResolved(localSessionId);
|
|
235338
236511
|
fastify2.agentSessionManager.markTitleResolved(localSessionId);
|
|
@@ -235627,10 +236800,10 @@ var routes11 = async (fastify2) => {
|
|
|
235627
236800
|
return reply.code(200).send({ success: true, favorited });
|
|
235628
236801
|
});
|
|
235629
236802
|
};
|
|
235630
|
-
var agent_session_routes_default = (0,
|
|
236803
|
+
var agent_session_routes_default = (0, import_fastify_plugin13.default)(routes12, { name: "agent-session-routes" });
|
|
235631
236804
|
|
|
235632
236805
|
// src/routes/branch-activity-routes.ts
|
|
235633
|
-
var
|
|
236806
|
+
var import_fastify_plugin14 = __toESM(require_plugin2(), 1);
|
|
235634
236807
|
function toResponse(map2) {
|
|
235635
236808
|
return {
|
|
235636
236809
|
branches: Array.from(map2.entries()).map(([branch, state]) => ({
|
|
@@ -235645,7 +236818,7 @@ function fromResponse(resp) {
|
|
|
235645
236818
|
resp.branches.map((b2) => [b2.branch ?? "", { activity: b2.activity, since: b2.since }])
|
|
235646
236819
|
);
|
|
235647
236820
|
}
|
|
235648
|
-
var
|
|
236821
|
+
var routes13 = async (fastify2) => {
|
|
235649
236822
|
function proxyAuto(remoteServerId, remoteUrl, remoteApiKey, method, apiPath, body) {
|
|
235650
236823
|
return proxyToRemoteAuto(remoteServerId, remoteUrl, remoteApiKey, method, apiPath, body, {
|
|
235651
236824
|
reverseConnectManager: fastify2.reverseConnectManager
|
|
@@ -235712,10 +236885,10 @@ var routes12 = async (fastify2) => {
|
|
|
235712
236885
|
}
|
|
235713
236886
|
);
|
|
235714
236887
|
};
|
|
235715
|
-
var branch_activity_routes_default = (0,
|
|
236888
|
+
var branch_activity_routes_default = (0, import_fastify_plugin14.default)(routes13, { name: "branch-activity-routes" });
|
|
235716
236889
|
|
|
235717
236890
|
// src/routes/merge-status-routes.ts
|
|
235718
|
-
var
|
|
236891
|
+
var import_fastify_plugin15 = __toESM(require_plugin2(), 1);
|
|
235719
236892
|
function resolveTargets(comparisons, storedTargets) {
|
|
235720
236893
|
const sources = [];
|
|
235721
236894
|
const effective = comparisons.map((comparison) => {
|
|
@@ -235849,7 +237022,7 @@ function sendComputed(reply, repoPath, comparisons, repository) {
|
|
|
235849
237022
|
return reply.code(statusCode).send({ error: message });
|
|
235850
237023
|
}
|
|
235851
237024
|
}
|
|
235852
|
-
var
|
|
237025
|
+
var routes14 = async (fastify2) => {
|
|
235853
237026
|
fastify2.post(
|
|
235854
237027
|
"/api/path/branches/merge-status",
|
|
235855
237028
|
async (req, reply) => {
|
|
@@ -235973,11 +237146,11 @@ var routes13 = async (fastify2) => {
|
|
|
235973
237146
|
}
|
|
235974
237147
|
);
|
|
235975
237148
|
};
|
|
235976
|
-
var merge_status_routes_default = (0,
|
|
237149
|
+
var merge_status_routes_default = (0, import_fastify_plugin15.default)(routes14, { name: "merge-status-routes" });
|
|
235977
237150
|
|
|
235978
237151
|
// src/routes/chat-session-routes.ts
|
|
235979
|
-
var
|
|
235980
|
-
var
|
|
237152
|
+
var import_fastify_plugin16 = __toESM(require_plugin2(), 1);
|
|
237153
|
+
var routes15 = async (fastify2) => {
|
|
235981
237154
|
const getAuthorizedSession = (req, reply, sessionId) => {
|
|
235982
237155
|
const authResult = requireAuth(req, reply);
|
|
235983
237156
|
if (authResult === null) return null;
|
|
@@ -236082,12 +237255,12 @@ var routes14 = async (fastify2) => {
|
|
|
236082
237255
|
return reply.send({ ok: true });
|
|
236083
237256
|
});
|
|
236084
237257
|
};
|
|
236085
|
-
var chat_session_routes_default = (0,
|
|
237258
|
+
var chat_session_routes_default = (0, import_fastify_plugin16.default)(routes15, { name: "chat-session-routes" });
|
|
236086
237259
|
|
|
236087
237260
|
// src/routes/task-routes.ts
|
|
236088
|
-
var
|
|
237261
|
+
var import_fastify_plugin17 = __toESM(require_plugin2(), 1);
|
|
236089
237262
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
236090
|
-
var
|
|
237263
|
+
var routes16 = async (fastify2) => {
|
|
236091
237264
|
fastify2.get(
|
|
236092
237265
|
"/api/projects/:projectId/tasks",
|
|
236093
237266
|
async (req, reply) => {
|
|
@@ -236233,12 +237406,12 @@ Description: ${description}`,
|
|
|
236233
237406
|
return reply.code(200).send({ success: true });
|
|
236234
237407
|
});
|
|
236235
237408
|
};
|
|
236236
|
-
var task_routes_default = (0,
|
|
237409
|
+
var task_routes_default = (0, import_fastify_plugin17.default)(routes16, { name: "task-routes" });
|
|
236237
237410
|
|
|
236238
237411
|
// src/routes/rule-routes.ts
|
|
236239
|
-
var
|
|
237412
|
+
var import_fastify_plugin18 = __toESM(require_plugin2(), 1);
|
|
236240
237413
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
236241
|
-
var
|
|
237414
|
+
var routes17 = async (fastify2) => {
|
|
236242
237415
|
fastify2.get(
|
|
236243
237416
|
"/api/projects/:projectId/rules",
|
|
236244
237417
|
async (req, reply) => {
|
|
@@ -236324,12 +237497,12 @@ var routes16 = async (fastify2) => {
|
|
|
236324
237497
|
return reply.code(200).send({ success: true });
|
|
236325
237498
|
});
|
|
236326
237499
|
};
|
|
236327
|
-
var rule_routes_default = (0,
|
|
237500
|
+
var rule_routes_default = (0, import_fastify_plugin18.default)(routes17, { name: "rule-routes" });
|
|
236328
237501
|
|
|
236329
237502
|
// src/routes/command-routes.ts
|
|
236330
|
-
var
|
|
237503
|
+
var import_fastify_plugin19 = __toESM(require_plugin2(), 1);
|
|
236331
237504
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
236332
|
-
var
|
|
237505
|
+
var routes18 = async (fastify2) => {
|
|
236333
237506
|
fastify2.get(
|
|
236334
237507
|
"/api/projects/:projectId/commands",
|
|
236335
237508
|
async (req, reply) => {
|
|
@@ -236398,83 +237571,274 @@ var routes17 = async (fastify2) => {
|
|
|
236398
237571
|
return reply.code(200).send({ success: true });
|
|
236399
237572
|
});
|
|
236400
237573
|
};
|
|
236401
|
-
var command_routes_default = (0,
|
|
237574
|
+
var command_routes_default = (0, import_fastify_plugin19.default)(routes18, { name: "command-routes" });
|
|
236402
237575
|
|
|
236403
237576
|
// src/routes/workflow-run-routes.ts
|
|
236404
|
-
var
|
|
237577
|
+
var import_fastify_plugin20 = __toESM(require_plugin2(), 1);
|
|
236405
237578
|
|
|
236406
237579
|
// src/utils/review-brief.ts
|
|
236407
|
-
var
|
|
236408
|
-
var
|
|
236409
|
-
var
|
|
236410
|
-
var
|
|
236411
|
-
var
|
|
237580
|
+
var BASE_TIMEOUT_MS = 15e3;
|
|
237581
|
+
var TIMEOUT_MS_PER_1K_CHARS = 120;
|
|
237582
|
+
var MAX_TIMEOUT_MS = 6e4;
|
|
237583
|
+
var PER_MESSAGE_MAX_CHARS = 6e3;
|
|
237584
|
+
var COMPACT_TRIGGER_CHARS = 12e4;
|
|
237585
|
+
var COMPACT_SLICE_BUDGETS = [96e3, 24e3, 6e3];
|
|
237586
|
+
var RECENT_VERBATIM_RATIO = 0.3;
|
|
237587
|
+
var MIN_RECENT_VERBATIM_CHARS = 2e3;
|
|
237588
|
+
var MAX_FOLD_CALLS = 8;
|
|
237589
|
+
var COMPACT_SUMMARY_MAX_CHARS = 6e3;
|
|
237590
|
+
var COMPACT_SUMMARY_MAX_TOKENS = 1500;
|
|
237591
|
+
var REVERSALS_MAX_CHARS = 2e3;
|
|
237592
|
+
var REVERSALS_MAX_TOKENS = 500;
|
|
236412
237593
|
var BRIEF_MAX_CHARS = 4e3;
|
|
237594
|
+
var BRIEF_MAX_TOKENS = 1e3;
|
|
237595
|
+
var REVERSAL_MIN_CHARS = 6e3;
|
|
237596
|
+
var SEP = "\n\n";
|
|
237597
|
+
var COMPACTION_NOTE = "_Note: the source conversation was compressed before distillation \u2014 older turns were summarized rather than read verbatim._";
|
|
237598
|
+
var BRIEF_TRUNCATION_NOTE = "\n\n_[brief truncated at the length limit \u2014 treat it as incomplete]_";
|
|
236413
237599
|
var SYSTEM_PROMPT2 = [
|
|
236414
237600
|
"You distill a coding-agent conversation into an intent brief for an independent code reviewer.",
|
|
236415
237601
|
"The reviewer will NOT see the conversation \u2014 only your brief plus the actual code, so capture what the code alone cannot show:",
|
|
236416
237602
|
"1. The original request and its goal.",
|
|
236417
|
-
"2. Constraints and
|
|
237603
|
+
"2. Constraints and decisions, including approaches that were rejected and why. A rejection counts whether the user made it or the agent made it under a principle the user set.",
|
|
236418
237604
|
"3. Trade-offs or limitations that were acknowledged and accepted.",
|
|
236419
|
-
"
|
|
236420
|
-
"
|
|
237605
|
+
"4. Behaviour established by actually running something \u2014 observed output, exit codes, error text, which stream an error arrived on. Report what was observed; that is evidence the reviewer cannot recover by reading code.",
|
|
237606
|
+
"Later statements supersede earlier ones on the same topic. An option explored at length early is often rejected later in a single sentence, and the rejection wins. Where the conversation ends in a decision table or summary, that is authoritative wherever it conflicts with earlier prose.",
|
|
237607
|
+
"The input may arrive as numbered compressed slices followed by verbatim recent turns. Slice numbers are chronological: a later slice overrules an earlier one.",
|
|
237608
|
+
"Decisions the agent made that the user did not contradict are binding \u2014 silence is agreement.",
|
|
237609
|
+
"Do NOT include the agent's self-assessment or any claim that the work is correct or complete \u2014 the reviewer must judge that independently.",
|
|
237610
|
+
"Every statement must trace to something in the conversation. If a heading has nothing real to report, say so rather than inventing plausible-sounding content.",
|
|
237611
|
+
"Write concise markdown bullets under those numbered headings, under 500 words total, in the same language as the conversation. Reply with the brief only."
|
|
236421
237612
|
].join("\n");
|
|
236422
|
-
|
|
236423
|
-
|
|
237613
|
+
var REVERSAL_SYSTEM_PROMPT = [
|
|
237614
|
+
"You read a coding-agent conversation and report ONLY the conclusions that were later reversed, dropped, or replaced.",
|
|
237615
|
+
"A design conversation converges by killing its own hypotheses: an option explored in depth early is often rejected later in one sentence, so weigh finality over how much was written.",
|
|
237616
|
+
"A reversal counts whether the user or the agent made it. Ignore rewording that leaves the decision intact.",
|
|
237617
|
+
"Output one line per reversal: SUPERSEDED: <what was concluded earlier> -> <what replaced it> (<why>)",
|
|
237618
|
+
"Report the most decisive reversals first and stay under 200 words \u2014 a list that runs long will be discarded whole, so prioritise rather than pad.",
|
|
237619
|
+
"If nothing was reversed, reply with exactly: NONE"
|
|
237620
|
+
].join("\n");
|
|
237621
|
+
var COMPACT_SYSTEM_PROMPT = [
|
|
237622
|
+
"You compress one slice of a coding-agent conversation. A later pass will distill the whole thing into an intent brief for a code reviewer.",
|
|
237623
|
+
"You are told which slice of how many this is. Other slices are compressed separately and you cannot see them, so never present a conclusion here as final \u2014 a later slice may overturn it.",
|
|
237624
|
+
"Preserve, in this order of priority:",
|
|
237625
|
+
"1. Reversals and corrections \u2014 any conclusion that was dropped, replaced, or corrected. Quote the deciding sentence verbatim.",
|
|
237626
|
+
"2. Constraints and decisions the user stated, and approaches that were rejected, with the reason.",
|
|
237627
|
+
"3. Behaviour established by actually running something: observed output, exit codes, error text, which stream an error arrived on.",
|
|
237628
|
+
"4. Trade-offs that were acknowledged and accepted.",
|
|
237629
|
+
"Drop: exploration narrative, restated background, tool mechanics, and anything a reviewer could recover by reading the code.",
|
|
237630
|
+
"Stay under 800 words. An over-long slice is discarded whole rather than cut short, so drop lower-priority material instead of running over.",
|
|
237631
|
+
"Keep the conversation's own language. Reply with the compressed slice only, no preamble."
|
|
237632
|
+
].join("\n");
|
|
237633
|
+
function timeoutForInput(chars) {
|
|
237634
|
+
return Math.min(MAX_TIMEOUT_MS, BASE_TIMEOUT_MS + Math.floor(chars / 1e3) * TIMEOUT_MS_PER_1K_CHARS);
|
|
237635
|
+
}
|
|
237636
|
+
function isSizeRelatedFailure(error48) {
|
|
237637
|
+
const err = error48;
|
|
237638
|
+
if (err?.name === "AbortError" || err?.name === "TimeoutError") return true;
|
|
237639
|
+
const message = err?.message ?? "";
|
|
237640
|
+
return /context[ _-]?length|maximum context|too many tokens|token limit|prompt is too long|request too large|timed? ?out/i.test(
|
|
237641
|
+
message
|
|
237642
|
+
);
|
|
236424
237643
|
}
|
|
236425
|
-
function
|
|
236426
|
-
|
|
237644
|
+
function withinLimit(text2, max, label) {
|
|
237645
|
+
if (text2.length <= max) return true;
|
|
237646
|
+
console.warn(`[ReviewBrief] ${label} ran to ${text2.length} chars (limit ${max}) \u2014 discarding rather than truncating`);
|
|
237647
|
+
return false;
|
|
237648
|
+
}
|
|
237649
|
+
function capMessage(text2) {
|
|
237650
|
+
return text2.length > PER_MESSAGE_MAX_CHARS ? text2.slice(0, PER_MESSAGE_MAX_CHARS) + "\u2026" : text2;
|
|
237651
|
+
}
|
|
237652
|
+
function toEntries(messages) {
|
|
237653
|
+
const entries = [];
|
|
236427
237654
|
for (const msg of messages) {
|
|
236428
237655
|
if (!msg) continue;
|
|
236429
237656
|
if (msg.type === "user" && !msg.event) {
|
|
236430
237657
|
const text2 = extractUserText(msg.content).trim();
|
|
236431
|
-
if (text2)
|
|
237658
|
+
if (text2) entries.push({ role: "User", text: capMessage(text2) });
|
|
236432
237659
|
} else if (msg.type === "assistant" && typeof msg.content === "string") {
|
|
236433
237660
|
const text2 = msg.content.trim();
|
|
236434
|
-
if (text2)
|
|
237661
|
+
if (text2) entries.push({ role: "Agent", text: capMessage(text2) });
|
|
236435
237662
|
}
|
|
236436
237663
|
}
|
|
236437
|
-
|
|
236438
|
-
|
|
236439
|
-
|
|
237664
|
+
return entries;
|
|
237665
|
+
}
|
|
237666
|
+
function render(entry) {
|
|
237667
|
+
return `${entry.role}: ${entry.text}`;
|
|
237668
|
+
}
|
|
237669
|
+
function renderAll(entries) {
|
|
237670
|
+
return entries.map(render).join(SEP);
|
|
237671
|
+
}
|
|
237672
|
+
function serializeConversationForBrief(messages) {
|
|
237673
|
+
return renderAll(toEntries(messages));
|
|
237674
|
+
}
|
|
237675
|
+
function splitRecent(entries, maxChars) {
|
|
237676
|
+
let size = 0;
|
|
237677
|
+
let i = entries.length;
|
|
237678
|
+
while (i > 0) {
|
|
237679
|
+
const len = render(entries[i - 1]).length + SEP.length;
|
|
237680
|
+
if (size + len > maxChars) break;
|
|
237681
|
+
size += len;
|
|
237682
|
+
i--;
|
|
237683
|
+
}
|
|
237684
|
+
return { older: entries.slice(0, i), recent: entries.slice(i) };
|
|
237685
|
+
}
|
|
237686
|
+
function chunkEntries(entries, maxChars) {
|
|
237687
|
+
const chunks = [];
|
|
237688
|
+
let current = [];
|
|
237689
|
+
let size = 0;
|
|
237690
|
+
for (const entry of entries) {
|
|
237691
|
+
const len = render(entry).length + SEP.length;
|
|
237692
|
+
if (current.length > 0 && size + len > maxChars) {
|
|
237693
|
+
chunks.push(current);
|
|
237694
|
+
current = [];
|
|
237695
|
+
size = 0;
|
|
237696
|
+
}
|
|
237697
|
+
current.push(entry);
|
|
237698
|
+
size += len;
|
|
237699
|
+
}
|
|
237700
|
+
if (current.length > 0) chunks.push(current);
|
|
237701
|
+
return chunks;
|
|
237702
|
+
}
|
|
237703
|
+
function telemetryFor(functionId, userId) {
|
|
237704
|
+
return userId ? {
|
|
237705
|
+
isEnabled: true,
|
|
237706
|
+
functionId,
|
|
237707
|
+
metadata: { userId, tags: ["vibedeckx", functionId] }
|
|
237708
|
+
} : void 0;
|
|
237709
|
+
}
|
|
237710
|
+
async function compactChunk(model, chunk, part, total, userId) {
|
|
237711
|
+
try {
|
|
237712
|
+
const result = await generateText({
|
|
237713
|
+
model,
|
|
237714
|
+
system: COMPACT_SYSTEM_PROMPT,
|
|
237715
|
+
prompt: `Slice ${part} of ${total}.
|
|
237716
|
+
|
|
237717
|
+
${chunk}`,
|
|
237718
|
+
timeout: timeoutForInput(chunk.length),
|
|
237719
|
+
maxOutputTokens: COMPACT_SUMMARY_MAX_TOKENS,
|
|
237720
|
+
experimental_telemetry: telemetryFor("review-intent-brief-compact", userId)
|
|
237721
|
+
});
|
|
237722
|
+
const text2 = (result.text ?? "").trim();
|
|
237723
|
+
if (text2.length === 0) return null;
|
|
237724
|
+
return withinLimit(text2, COMPACT_SUMMARY_MAX_CHARS, `slice ${part}/${total} summary`) ? text2 : null;
|
|
237725
|
+
} catch (error48) {
|
|
237726
|
+
if (isSizeRelatedFailure(error48)) throw error48;
|
|
237727
|
+
console.warn(`[ReviewBrief] compaction of slice ${part}/${total} failed:`, error48.message);
|
|
237728
|
+
return null;
|
|
237729
|
+
}
|
|
237730
|
+
}
|
|
237731
|
+
async function compactConversation(model, messages, options = {}) {
|
|
237732
|
+
const sliceChars = options.sliceChars ?? COMPACT_SLICE_BUDGETS[0];
|
|
237733
|
+
const recentChars = Math.max(MIN_RECENT_VERBATIM_CHARS, Math.floor(sliceChars * RECENT_VERBATIM_RATIO));
|
|
237734
|
+
const entries = toEntries(messages);
|
|
237735
|
+
if (entries.length === 0) return null;
|
|
237736
|
+
const { older, recent } = splitRecent(entries, recentChars);
|
|
237737
|
+
if (older.length === 0) return null;
|
|
237738
|
+
const chunks = chunkEntries(older, sliceChars);
|
|
237739
|
+
if (chunks.length > MAX_FOLD_CALLS) {
|
|
237740
|
+
console.warn(
|
|
237741
|
+
`[ReviewBrief] conversation needs ${chunks.length} slices of ${sliceChars} chars (max ${MAX_FOLD_CALLS}) \u2014 falling back to tier 2`
|
|
237742
|
+
);
|
|
237743
|
+
return null;
|
|
237744
|
+
}
|
|
237745
|
+
const settled = await Promise.allSettled(
|
|
237746
|
+
chunks.map((chunk, i) => compactChunk(model, renderAll(chunk), i + 1, chunks.length, options.userId))
|
|
237747
|
+
);
|
|
237748
|
+
const sizeRejection = settled.find((r) => r.status === "rejected" && isSizeRelatedFailure(r.reason));
|
|
237749
|
+
if (sizeRejection) throw sizeRejection.reason;
|
|
237750
|
+
if (settled.some((r) => r.status === "rejected" || r.value === null)) return null;
|
|
237751
|
+
const parts = settled.map(
|
|
237752
|
+
(r, i) => `[Compressed slice ${i + 1} of ${chunks.length}]
|
|
237753
|
+
${r.value}`
|
|
237754
|
+
);
|
|
237755
|
+
if (recent.length > 0) parts.push(`[Recent turns, verbatim]
|
|
237756
|
+
${renderAll(recent)}`);
|
|
237757
|
+
return parts.join(SEP);
|
|
237758
|
+
}
|
|
237759
|
+
function buildBriefPrompt(conversation, reversals) {
|
|
237760
|
+
const preamble = reversals ? "Conclusions this conversation reversed. The right-hand side is the current state \u2014 never report the left-hand side as what was decided:\n" + reversals + SEP : "";
|
|
237761
|
+
return `${preamble}Distill this conversation into an intent brief:
|
|
236440
237762
|
|
|
236441
|
-
|
|
237763
|
+
${conversation}`;
|
|
237764
|
+
}
|
|
237765
|
+
function appendCompactionNote(brief, compacted) {
|
|
237766
|
+
return compacted ? `${brief}${SEP}${COMPACTION_NOTE}` : brief;
|
|
237767
|
+
}
|
|
237768
|
+
async function extractReversalsWithModel(model, conversation, options = {}) {
|
|
237769
|
+
if (conversation.trim().length < REVERSAL_MIN_CHARS) return null;
|
|
237770
|
+
try {
|
|
237771
|
+
const result = await generateText({
|
|
237772
|
+
model,
|
|
237773
|
+
system: REVERSAL_SYSTEM_PROMPT,
|
|
237774
|
+
prompt: `Report the reversals in this conversation:
|
|
236442
237775
|
|
|
236443
|
-
${
|
|
237776
|
+
${conversation}`,
|
|
237777
|
+
timeout: timeoutForInput(conversation.length),
|
|
237778
|
+
maxOutputTokens: REVERSALS_MAX_TOKENS,
|
|
237779
|
+
experimental_telemetry: telemetryFor("review-intent-brief-reversals", options.userId)
|
|
237780
|
+
});
|
|
237781
|
+
const text2 = (result.text ?? "").trim();
|
|
237782
|
+
if (text2.length === 0 || /^none\b/i.test(text2)) return null;
|
|
237783
|
+
return withinLimit(text2, REVERSALS_MAX_CHARS, "reversal list") ? text2 : null;
|
|
237784
|
+
} catch (error48) {
|
|
237785
|
+
if (!isSizeRelatedFailure(error48)) throw error48;
|
|
237786
|
+
console.warn("[ReviewBrief] reversal pass hit a size limit \u2014 skipping it:", error48.message);
|
|
237787
|
+
return null;
|
|
237788
|
+
}
|
|
236444
237789
|
}
|
|
236445
237790
|
async function generateIntentBriefWithModel(model, conversation, options = {}) {
|
|
236446
237791
|
if (conversation.trim().length === 0) return null;
|
|
236447
|
-
const telemetry = options.userId ? {
|
|
236448
|
-
isEnabled: true,
|
|
236449
|
-
functionId: "review-intent-brief",
|
|
236450
|
-
metadata: {
|
|
236451
|
-
userId: options.userId,
|
|
236452
|
-
tags: ["vibedeckx", "review-intent-brief"]
|
|
236453
|
-
}
|
|
236454
|
-
} : void 0;
|
|
236455
237792
|
try {
|
|
236456
237793
|
const result = await generateText({
|
|
236457
237794
|
model,
|
|
236458
237795
|
system: SYSTEM_PROMPT2,
|
|
236459
|
-
prompt:
|
|
236460
|
-
|
|
236461
|
-
|
|
236462
|
-
|
|
236463
|
-
experimental_telemetry: telemetry
|
|
237796
|
+
prompt: buildBriefPrompt(conversation, options.reversals ?? null),
|
|
237797
|
+
timeout: timeoutForInput(conversation.length),
|
|
237798
|
+
maxOutputTokens: BRIEF_MAX_TOKENS,
|
|
237799
|
+
experimental_telemetry: telemetryFor("review-intent-brief", options.userId)
|
|
236464
237800
|
});
|
|
236465
237801
|
const text2 = (result.text ?? "").trim();
|
|
236466
|
-
|
|
237802
|
+
if (text2.length === 0) return null;
|
|
237803
|
+
return text2.length <= BRIEF_MAX_CHARS ? text2 : text2.slice(0, BRIEF_MAX_CHARS) + BRIEF_TRUNCATION_NOTE;
|
|
236467
237804
|
} catch (error48) {
|
|
237805
|
+
if (options.rethrowSizeFailures && isSizeRelatedFailure(error48)) throw error48;
|
|
236468
237806
|
console.warn("[ReviewBrief] AI generation failed:", error48.message);
|
|
236469
237807
|
return null;
|
|
236470
237808
|
}
|
|
236471
237809
|
}
|
|
237810
|
+
async function distill(model, conversation, userId, rethrowSizeFailures) {
|
|
237811
|
+
const reversals = await extractReversalsWithModel(model, conversation, { userId });
|
|
237812
|
+
return generateIntentBriefWithModel(model, conversation, { userId, reversals, rethrowSizeFailures });
|
|
237813
|
+
}
|
|
236472
237814
|
async function generateIntentBrief(storage, userId, messages) {
|
|
236473
237815
|
try {
|
|
236474
237816
|
if (!await isChatModelConfigured(storage, userId)) return null;
|
|
236475
237817
|
const conversation = serializeConversationForBrief(messages);
|
|
236476
237818
|
if (!conversation) return null;
|
|
236477
|
-
|
|
237819
|
+
const model = await resolveFastChatModel(storage, userId);
|
|
237820
|
+
if (conversation.length <= COMPACT_TRIGGER_CHARS) {
|
|
237821
|
+
try {
|
|
237822
|
+
const brief = await distill(model, conversation, userId, true);
|
|
237823
|
+
return brief === null ? null : appendCompactionNote(brief, false);
|
|
237824
|
+
} catch (error48) {
|
|
237825
|
+
if (!isSizeRelatedFailure(error48)) throw error48;
|
|
237826
|
+
console.warn("[ReviewBrief] full conversation rejected as too large \u2014 compacting and retrying");
|
|
237827
|
+
}
|
|
237828
|
+
}
|
|
237829
|
+
for (const sliceChars of COMPACT_SLICE_BUDGETS) {
|
|
237830
|
+
try {
|
|
237831
|
+
const compacted = await compactConversation(model, messages, { userId, sliceChars });
|
|
237832
|
+
if (compacted === null) return null;
|
|
237833
|
+
const brief = await distill(model, compacted, userId, true);
|
|
237834
|
+
return brief === null ? null : appendCompactionNote(brief, true);
|
|
237835
|
+
} catch (error48) {
|
|
237836
|
+
if (!isSizeRelatedFailure(error48)) throw error48;
|
|
237837
|
+
console.warn(`[ReviewBrief] ${sliceChars}-char slices still too large \u2014 retrying smaller`);
|
|
237838
|
+
}
|
|
237839
|
+
}
|
|
237840
|
+
console.warn("[ReviewBrief] could not compact the conversation small enough \u2014 falling back to tier 2");
|
|
237841
|
+
return null;
|
|
236478
237842
|
} catch (error48) {
|
|
236479
237843
|
console.warn("[ReviewBrief] generation failed:", error48.message);
|
|
236480
237844
|
return null;
|
|
@@ -236515,7 +237879,7 @@ function errStatus(err) {
|
|
|
236515
237879
|
return 500;
|
|
236516
237880
|
}
|
|
236517
237881
|
}
|
|
236518
|
-
async function
|
|
237882
|
+
async function routes19(fastify2) {
|
|
236519
237883
|
const remoteRunMap = /* @__PURE__ */ new Map();
|
|
236520
237884
|
const TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "failed"]);
|
|
236521
237885
|
const trackRemoteRun = (localRun, info) => {
|
|
@@ -236599,6 +237963,12 @@ async function routes18(fastify2) {
|
|
|
236599
237963
|
if (!clientProvidedBrief && !bareReviewerSessionId) {
|
|
236600
237964
|
intentBrief2 = await distillIntentBrief(userId, sourceSessionId);
|
|
236601
237965
|
}
|
|
237966
|
+
if (reviewerSessionId && !await fastify2.remoteNotificationSync.prepareForNewTurn(reviewerSessionId)) {
|
|
237967
|
+
return reply.code(502).send({
|
|
237968
|
+
error: "Could not reach the remote server to prepare notification delivery",
|
|
237969
|
+
errorCode: "notification_baseline_failed"
|
|
237970
|
+
});
|
|
237971
|
+
}
|
|
236602
237972
|
const result = await proxyAuto(remoteInfo, "POST", "/api/path/workflow-runs", {
|
|
236603
237973
|
sourceSessionId: remoteInfo.remoteSessionId,
|
|
236604
237974
|
reviewFocus,
|
|
@@ -236632,7 +238002,12 @@ async function routes18(fastify2) {
|
|
|
236632
238002
|
projectId,
|
|
236633
238003
|
remoteInfo.remoteServerId,
|
|
236634
238004
|
bareRun.reviewer_session_id,
|
|
236635
|
-
bareRun.branch
|
|
238005
|
+
bareRun.branch,
|
|
238006
|
+
"from_start"
|
|
238007
|
+
);
|
|
238008
|
+
await fastify2.remoteNotificationSync.extendWatch(localRun.reviewer_session_id);
|
|
238009
|
+
fastify2.remoteNotificationSync.enqueue(
|
|
238010
|
+
() => fastify2.remoteNotificationSync.syncServer(remoteInfo.remoteServerId, { includeExpired: true })
|
|
236636
238011
|
);
|
|
236637
238012
|
ensureRemoteAgentStream(localRun.reviewer_session_id, {
|
|
236638
238013
|
remoteSessionMap: fastify2.remoteSessionMap,
|
|
@@ -236641,6 +238016,11 @@ async function routes18(fastify2) {
|
|
|
236641
238016
|
eventBus: fastify2.eventBus,
|
|
236642
238017
|
agentSessionManager: fastify2.agentSessionManager
|
|
236643
238018
|
});
|
|
238019
|
+
fastify2.agentSessionManager.emitBranchActivityIfChanged(projectId, bareRun.branch, {
|
|
238020
|
+
activity: "working",
|
|
238021
|
+
since: Date.now(),
|
|
238022
|
+
sessionId: localRun.reviewer_session_id
|
|
238023
|
+
});
|
|
236644
238024
|
fastify2.eventBus.emit({
|
|
236645
238025
|
type: "session:process",
|
|
236646
238026
|
projectId,
|
|
@@ -236755,7 +238135,8 @@ async function routes18(fastify2) {
|
|
|
236755
238135
|
projectId,
|
|
236756
238136
|
remoteInfo.remoteServerId,
|
|
236757
238137
|
bareCandidate.sessionId,
|
|
236758
|
-
remoteInfo.branch ?? null
|
|
238138
|
+
remoteInfo.branch ?? null,
|
|
238139
|
+
"from_now"
|
|
236759
238140
|
);
|
|
236760
238141
|
}
|
|
236761
238142
|
return reply.send({ candidate: candidate2 });
|
|
@@ -236949,10 +238330,10 @@ async function routes18(fastify2) {
|
|
|
236949
238330
|
return reply.send({ runs });
|
|
236950
238331
|
});
|
|
236951
238332
|
}
|
|
236952
|
-
var workflow_run_routes_default = (0,
|
|
238333
|
+
var workflow_run_routes_default = (0, import_fastify_plugin20.default)(routes19, { name: "workflow-run-routes" });
|
|
236953
238334
|
|
|
236954
238335
|
// src/routes/settings-routes.ts
|
|
236955
|
-
var
|
|
238336
|
+
var import_fastify_plugin21 = __toESM(require_plugin2(), 1);
|
|
236956
238337
|
var SettingsValidationError = class extends Error {
|
|
236957
238338
|
};
|
|
236958
238339
|
var DEFAULT_PROXY_CONFIG = { type: "none", host: "", port: 0 };
|
|
@@ -237016,7 +238397,7 @@ function requireSettingsUser(req, reply) {
|
|
|
237016
238397
|
if (auth === null) return null;
|
|
237017
238398
|
return resolveUserId(auth);
|
|
237018
238399
|
}
|
|
237019
|
-
var
|
|
238400
|
+
var routes20 = async (fastify2) => {
|
|
237020
238401
|
fastify2.get("/api/settings/agent-processes", async (req, reply) => {
|
|
237021
238402
|
if (requireAuth(req, reply) === null) return;
|
|
237022
238403
|
const saved = await fastify2.storage.settings.get("agentProcesses");
|
|
@@ -237290,11 +238671,11 @@ var routes19 = async (fastify2) => {
|
|
|
237290
238671
|
return reply.code(200).send(updated);
|
|
237291
238672
|
});
|
|
237292
238673
|
};
|
|
237293
|
-
var settings_routes_default = (0,
|
|
238674
|
+
var settings_routes_default = (0, import_fastify_plugin21.default)(routes20, { name: "settings-routes" });
|
|
237294
238675
|
|
|
237295
238676
|
// src/routes/translate-routes.ts
|
|
237296
|
-
var
|
|
237297
|
-
var
|
|
238677
|
+
var import_fastify_plugin22 = __toESM(require_plugin2(), 1);
|
|
238678
|
+
var routes21 = async (fastify2) => {
|
|
237298
238679
|
fastify2.post(
|
|
237299
238680
|
"/api/translate",
|
|
237300
238681
|
async (req, reply) => {
|
|
@@ -237335,10 +238716,10 @@ ${text2}`,
|
|
|
237335
238716
|
}
|
|
237336
238717
|
);
|
|
237337
238718
|
};
|
|
237338
|
-
var translate_routes_default = (0,
|
|
238719
|
+
var translate_routes_default = (0, import_fastify_plugin22.default)(routes21, { name: "translate-routes" });
|
|
237339
238720
|
|
|
237340
238721
|
// src/routes/websocket-routes.ts
|
|
237341
|
-
var
|
|
238722
|
+
var import_fastify_plugin23 = __toESM(require_plugin2(), 1);
|
|
237342
238723
|
|
|
237343
238724
|
// src/routes/executor-stream-handlers.ts
|
|
237344
238725
|
import { randomUUID as randomUUID19 } from "crypto";
|
|
@@ -237616,7 +238997,7 @@ async function userOwnsSession(fastify2, sessionId, userId) {
|
|
|
237616
238997
|
}
|
|
237617
238998
|
|
|
237618
238999
|
// src/routes/websocket-routes.ts
|
|
237619
|
-
var
|
|
239000
|
+
var routes22 = async (fastify2) => {
|
|
237620
239001
|
fastify2.reverseConnectManager.setStatusChangeHandler((remoteServerId, status) => {
|
|
237621
239002
|
if (status !== "online") return;
|
|
237622
239003
|
const cache2 = fastify2.remotePatchCache;
|
|
@@ -238003,13 +239384,13 @@ var routes21 = async (fastify2) => {
|
|
|
238003
239384
|
);
|
|
238004
239385
|
});
|
|
238005
239386
|
};
|
|
238006
|
-
var websocket_routes_default = (0,
|
|
239387
|
+
var websocket_routes_default = (0, import_fastify_plugin23.default)(routes22, { name: "websocket-routes" });
|
|
238007
239388
|
|
|
238008
239389
|
// src/routes/reverse-connect-routes.ts
|
|
238009
|
-
var
|
|
239390
|
+
var import_fastify_plugin24 = __toESM(require_plugin2(), 1);
|
|
238010
239391
|
import { randomBytes as randomBytes2, createHash as createHash3, verify as cryptoVerify } from "crypto";
|
|
238011
239392
|
var MACHINE_HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
238012
|
-
var
|
|
239393
|
+
var routes23 = async (fastify2) => {
|
|
238013
239394
|
fastify2.get("/api/reverse-connect/identity", async (req, reply) => {
|
|
238014
239395
|
const token = req.headers["x-vibedeckx-connect-token"];
|
|
238015
239396
|
if (typeof token !== "string" || token.length === 0) {
|
|
@@ -238119,10 +239500,10 @@ var routes22 = async (fastify2) => {
|
|
|
238119
239500
|
);
|
|
238120
239501
|
});
|
|
238121
239502
|
};
|
|
238122
|
-
var reverse_connect_routes_default = (0,
|
|
239503
|
+
var reverse_connect_routes_default = (0, import_fastify_plugin24.default)(routes23, { name: "reverse-connect-routes" });
|
|
238123
239504
|
|
|
238124
239505
|
// src/routes/event-routes.ts
|
|
238125
|
-
var
|
|
239506
|
+
var import_fastify_plugin25 = __toESM(require_plugin2(), 1);
|
|
238126
239507
|
function toWireEvent(event) {
|
|
238127
239508
|
if (event.type === "executor:stopped" && (event.tailOutput !== void 0 || event.finalResult !== void 0)) {
|
|
238128
239509
|
const { tailOutput: _tailOutput, finalResult: _finalResult, ...rest } = event;
|
|
@@ -238130,7 +239511,7 @@ function toWireEvent(event) {
|
|
|
238130
239511
|
}
|
|
238131
239512
|
return event;
|
|
238132
239513
|
}
|
|
238133
|
-
var
|
|
239514
|
+
var routes24 = async (fastify2) => {
|
|
238134
239515
|
fastify2.get("/api/events", async (req, reply) => {
|
|
238135
239516
|
let userId = null;
|
|
238136
239517
|
if (fastify2.authEnabled) {
|
|
@@ -238188,10 +239569,10 @@ var routes23 = async (fastify2) => {
|
|
|
238188
239569
|
await reply;
|
|
238189
239570
|
});
|
|
238190
239571
|
};
|
|
238191
|
-
var event_routes_default = (0,
|
|
239572
|
+
var event_routes_default = (0, import_fastify_plugin25.default)(routes24, { name: "event-routes" });
|
|
238192
239573
|
|
|
238193
239574
|
// src/routes/terminal-routes.ts
|
|
238194
|
-
var
|
|
239575
|
+
var import_fastify_plugin26 = __toESM(require_plugin2(), 1);
|
|
238195
239576
|
import path13 from "path";
|
|
238196
239577
|
async function getRemoteConfig5(fastify2, project, remoteServerId) {
|
|
238197
239578
|
const remotes = await fastify2.storage.projectRemotes.getByProject(project.id);
|
|
@@ -238216,7 +239597,7 @@ async function getRemoteConfig5(fastify2, project, remoteServerId) {
|
|
|
238216
239597
|
}
|
|
238217
239598
|
return null;
|
|
238218
239599
|
}
|
|
238219
|
-
var
|
|
239600
|
+
var routes25 = async (fastify2) => {
|
|
238220
239601
|
fastify2.post("/api/path/terminals", async (req, reply) => {
|
|
238221
239602
|
const { path: projectPath, branch } = req.body;
|
|
238222
239603
|
if (!projectPath) {
|
|
@@ -238399,11 +239780,11 @@ var routes24 = async (fastify2) => {
|
|
|
238399
239780
|
}
|
|
238400
239781
|
);
|
|
238401
239782
|
};
|
|
238402
|
-
var terminal_routes_default = (0,
|
|
239783
|
+
var terminal_routes_default = (0, import_fastify_plugin26.default)(routes25, { name: "terminal-routes" });
|
|
238403
239784
|
|
|
238404
239785
|
// src/routes/browser-routes.ts
|
|
238405
|
-
var
|
|
238406
|
-
var
|
|
239786
|
+
var import_fastify_plugin27 = __toESM(require_plugin2(), 1);
|
|
239787
|
+
var routes26 = async (fastify2) => {
|
|
238407
239788
|
const ensureProjectAccess = async (req, reply) => {
|
|
238408
239789
|
const userId = requireAuth(req, reply);
|
|
238409
239790
|
if (userId === null) return null;
|
|
@@ -238474,10 +239855,10 @@ var routes25 = async (fastify2) => {
|
|
|
238474
239855
|
return reply.code(200).send({ ok: true });
|
|
238475
239856
|
});
|
|
238476
239857
|
};
|
|
238477
|
-
var browser_routes_default = (0,
|
|
239858
|
+
var browser_routes_default = (0, import_fastify_plugin27.default)(routes26, { name: "browser-routes" });
|
|
238478
239859
|
|
|
238479
239860
|
// src/routes/browser-proxy-routes.ts
|
|
238480
|
-
var
|
|
239861
|
+
var import_fastify_plugin28 = __toESM(require_plugin2(), 1);
|
|
238481
239862
|
import { randomUUID as randomUUID20 } from "crypto";
|
|
238482
239863
|
|
|
238483
239864
|
// src/utils/ssrf-guard.ts
|
|
@@ -238973,7 +240354,7 @@ function generateInjectedScript(projectId, targetOrigin, proxyWsPrefix) {
|
|
|
238973
240354
|
})();
|
|
238974
240355
|
</script>`;
|
|
238975
240356
|
}
|
|
238976
|
-
var
|
|
240357
|
+
var routes27 = async (fastify2) => {
|
|
238977
240358
|
fastify2.get("/api/projects/:id/browser/proxy/*", async (req, reply) => {
|
|
238978
240359
|
const userId = requireAuth(req, reply);
|
|
238979
240360
|
if (userId === null) return;
|
|
@@ -239144,10 +240525,10 @@ var routes26 = async (fastify2) => {
|
|
|
239144
240525
|
});
|
|
239145
240526
|
});
|
|
239146
240527
|
};
|
|
239147
|
-
var browser_proxy_routes_default = (0,
|
|
240528
|
+
var browser_proxy_routes_default = (0, import_fastify_plugin28.default)(routes27, { name: "browser-proxy-routes" });
|
|
239148
240529
|
|
|
239149
240530
|
// src/routes/cross-remote-target-routes.ts
|
|
239150
|
-
var
|
|
240531
|
+
var import_fastify_plugin29 = __toESM(require_plugin2(), 1);
|
|
239151
240532
|
import path14 from "path";
|
|
239152
240533
|
import { promises as fs3 } from "fs";
|
|
239153
240534
|
|
|
@@ -239227,7 +240608,7 @@ var clampTimeoutMs = (timeoutSec) => {
|
|
|
239227
240608
|
const requested = typeof timeoutSec === "number" && Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : DEFAULT_TIMEOUT_SEC;
|
|
239228
240609
|
return Math.min(requested, MAX_TIMEOUT_SEC) * 1e3;
|
|
239229
240610
|
};
|
|
239230
|
-
var
|
|
240611
|
+
var routes28 = async (fastify2) => {
|
|
239231
240612
|
fastify2.post(
|
|
239232
240613
|
"/api/path/cross-remote/exec",
|
|
239233
240614
|
async (request, reply) => {
|
|
@@ -239335,10 +240716,10 @@ var routes27 = async (fastify2) => {
|
|
|
239335
240716
|
return reply.send(result);
|
|
239336
240717
|
});
|
|
239337
240718
|
};
|
|
239338
|
-
var cross_remote_target_routes_default = (0,
|
|
240719
|
+
var cross_remote_target_routes_default = (0, import_fastify_plugin29.default)(routes28, { name: "cross-remote-target-routes" });
|
|
239339
240720
|
|
|
239340
240721
|
// src/routes/cross-remote-mcp-routes.ts
|
|
239341
|
-
var
|
|
240722
|
+
var import_fastify_plugin30 = __toESM(require_plugin2(), 1);
|
|
239342
240723
|
var PROTOCOL_VERSION = "2024-11-05";
|
|
239343
240724
|
var AUDIT_ARGS_MAX = 1024;
|
|
239344
240725
|
var NOT_ACCESSIBLE = "remote not found or not accessible";
|
|
@@ -239441,7 +240822,7 @@ var textResult = (text2, isError = false) => ({
|
|
|
239441
240822
|
content: [{ type: "text", text: text2 }],
|
|
239442
240823
|
...isError ? { isError: true } : {}
|
|
239443
240824
|
});
|
|
239444
|
-
var
|
|
240825
|
+
var routes29 = async (fastify2) => {
|
|
239445
240826
|
const guard = new SessionConcurrencyGuard();
|
|
239446
240827
|
let cachedSecret;
|
|
239447
240828
|
const getSecret = () => {
|
|
@@ -239564,10 +240945,10 @@ var routes28 = async (fastify2) => {
|
|
|
239564
240945
|
}
|
|
239565
240946
|
});
|
|
239566
240947
|
};
|
|
239567
|
-
var cross_remote_mcp_routes_default = (0,
|
|
240948
|
+
var cross_remote_mcp_routes_default = (0, import_fastify_plugin30.default)(routes29, { name: "cross-remote-mcp-routes" });
|
|
239568
240949
|
|
|
239569
240950
|
// src/routes/schedule-routes.ts
|
|
239570
|
-
var
|
|
240951
|
+
var import_fastify_plugin31 = __toESM(require_plugin2(), 1);
|
|
239571
240952
|
import { randomUUID as randomUUID21 } from "crypto";
|
|
239572
240953
|
import path15 from "path";
|
|
239573
240954
|
var RUN_TYPES = ["command", "prompt"];
|
|
@@ -239583,7 +240964,7 @@ function validateResolved(b2) {
|
|
|
239583
240964
|
if (cronError) return `Invalid cron expression: ${cronError}`;
|
|
239584
240965
|
return null;
|
|
239585
240966
|
}
|
|
239586
|
-
var
|
|
240967
|
+
var routes30 = async (fastify2) => {
|
|
239587
240968
|
const getAuthorizedSchedule = async (id, userId, reply) => {
|
|
239588
240969
|
const schedule = await fastify2.storage.scheduledTasks.getById(id);
|
|
239589
240970
|
if (!schedule) {
|
|
@@ -239755,7 +241136,7 @@ var routes29 = async (fastify2) => {
|
|
|
239755
241136
|
}
|
|
239756
241137
|
);
|
|
239757
241138
|
};
|
|
239758
|
-
var schedule_routes_default = (0,
|
|
241139
|
+
var schedule_routes_default = (0, import_fastify_plugin31.default)(routes30, { name: "schedule-routes" });
|
|
239759
241140
|
|
|
239760
241141
|
// src/search/catalog.ts
|
|
239761
241142
|
var parseDbTimestamp3 = (ts) => {
|
|
@@ -239959,7 +241340,8 @@ var searchRoutes = async (fastify2) => {
|
|
|
239959
241340
|
target.projectId,
|
|
239960
241341
|
target.targetId,
|
|
239961
241342
|
s3.id,
|
|
239962
|
-
mapBranch
|
|
241343
|
+
mapBranch,
|
|
241344
|
+
"from_now"
|
|
239963
241345
|
);
|
|
239964
241346
|
return { ...s3, id: localSessionId };
|
|
239965
241347
|
}));
|
|
@@ -239996,6 +241378,44 @@ var searchRoutes = async (fastify2) => {
|
|
|
239996
241378
|
};
|
|
239997
241379
|
var search_routes_default = searchRoutes;
|
|
239998
241380
|
|
|
241381
|
+
// src/routes/notification-routes.ts
|
|
241382
|
+
var import_fastify_plugin32 = __toESM(require_plugin2(), 1);
|
|
241383
|
+
var MAX_LIMIT = 500;
|
|
241384
|
+
var DEFAULT_LIMIT = 100;
|
|
241385
|
+
var routes31 = async (fastify2) => {
|
|
241386
|
+
fastify2.get(
|
|
241387
|
+
"/api/notifications",
|
|
241388
|
+
async (req, reply) => {
|
|
241389
|
+
const authResult = requireAuth(req, reply);
|
|
241390
|
+
if (authResult === null) return;
|
|
241391
|
+
const userId = resolveUserId(authResult);
|
|
241392
|
+
const requested = Number(req.query.limit);
|
|
241393
|
+
const limit = Number.isFinite(requested) && requested > 0 ? Math.min(Math.floor(requested), MAX_LIMIT) : DEFAULT_LIMIT;
|
|
241394
|
+
const notifications = await fastify2.storage.notifications.listForUser(userId, {
|
|
241395
|
+
limit,
|
|
241396
|
+
unreadOnly: req.query.unread === "true"
|
|
241397
|
+
});
|
|
241398
|
+
return reply.code(200).send({ notifications });
|
|
241399
|
+
}
|
|
241400
|
+
);
|
|
241401
|
+
fastify2.patch("/api/notifications/:id/read", async (req, reply) => {
|
|
241402
|
+
const authResult = requireAuth(req, reply);
|
|
241403
|
+
if (authResult === null) return;
|
|
241404
|
+
const userId = resolveUserId(authResult);
|
|
241405
|
+
const ok = await fastify2.storage.notifications.markRead(req.params.id, userId);
|
|
241406
|
+
if (!ok) return reply.code(404).send({ error: "Not found" });
|
|
241407
|
+
return reply.code(200).send({ ok: true });
|
|
241408
|
+
});
|
|
241409
|
+
fastify2.post("/api/notifications/read-all", async (req, reply) => {
|
|
241410
|
+
const authResult = requireAuth(req, reply);
|
|
241411
|
+
if (authResult === null) return;
|
|
241412
|
+
const userId = resolveUserId(authResult);
|
|
241413
|
+
await fastify2.storage.notifications.markAllRead(userId);
|
|
241414
|
+
return reply.code(200).send({ ok: true });
|
|
241415
|
+
});
|
|
241416
|
+
};
|
|
241417
|
+
var notification_routes_default = (0, import_fastify_plugin32.default)(routes31, { name: "notification-routes" });
|
|
241418
|
+
|
|
239999
241419
|
// ../../node_modules/.pnpm/@clerk+fastify@3.1.2_fastify@5.7.2_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@clerk/fastify/dist/chunk-RY4T2JMQ.mjs
|
|
240000
241420
|
import { Readable as Readable3 } from "stream";
|
|
240001
241421
|
var fastifyRequestToRequest = (req) => {
|
|
@@ -240046,7 +241466,7 @@ var requestToProxyRequest = (req) => {
|
|
|
240046
241466
|
|
|
240047
241467
|
// ../../node_modules/.pnpm/@clerk+fastify@3.1.2_fastify@5.7.2_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@clerk/fastify/dist/index.mjs
|
|
240048
241468
|
init_dist();
|
|
240049
|
-
var
|
|
241469
|
+
var import_fastify_plugin33 = __toESM(require_plugin2(), 1);
|
|
240050
241470
|
|
|
240051
241471
|
// ../../node_modules/.pnpm/@clerk+backend@3.2.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@clerk/backend/dist/internal.mjs
|
|
240052
241472
|
init_chunk_JW73PKED();
|
|
@@ -240334,7 +241754,7 @@ var plugin = (instance, opts, done) => {
|
|
|
240334
241754
|
instance.addHook(hookName, withClerkMiddleware(opts));
|
|
240335
241755
|
done();
|
|
240336
241756
|
};
|
|
240337
|
-
var clerkPlugin = (0,
|
|
241757
|
+
var clerkPlugin = (0, import_fastify_plugin33.default)(plugin, {
|
|
240338
241758
|
name: "@clerk/fastify",
|
|
240339
241759
|
fastify: "5.x"
|
|
240340
241760
|
});
|
|
@@ -241223,6 +242643,7 @@ var createServer = async (opts) => {
|
|
|
241223
242643
|
authEnabled,
|
|
241224
242644
|
clerkPublishableKey: authEnabled ? process.env.CLERK_PUBLISHABLE_KEY : void 0,
|
|
241225
242645
|
localProjectsEnabled: !noLocalProjects,
|
|
242646
|
+
discordInviteUrl: process.env.VIBEDECKX_DISCORD_URL || void 0,
|
|
241226
242647
|
// Capability flag for the reverse-connect identity preflight. Workers
|
|
241227
242648
|
// check this before calling /api/reverse-connect/identity so an auth
|
|
241228
242649
|
// middleware 401 from an older hub (which lacks the endpoint AND its
|
|
@@ -241282,6 +242703,8 @@ var createServer = async (opts) => {
|
|
|
241282
242703
|
server.register(settings_routes_default);
|
|
241283
242704
|
server.register(translate_routes_default);
|
|
241284
242705
|
server.register(event_routes_default);
|
|
242706
|
+
server.register(notification_routes_default);
|
|
242707
|
+
server.register(notification_outbox_routes_default);
|
|
241285
242708
|
server.register(terminal_routes_default);
|
|
241286
242709
|
server.register(browser_routes_default);
|
|
241287
242710
|
server.register(browser_proxy_routes_default);
|
|
@@ -243623,7 +245046,7 @@ var connectRoutes = buildRouteMap({
|
|
|
243623
245046
|
defaultCommand: "run",
|
|
243624
245047
|
docs: { brief: "Connect to a Vibedeckx server as an inbound node" }
|
|
243625
245048
|
});
|
|
243626
|
-
var
|
|
245049
|
+
var routes32 = buildRouteMap({
|
|
243627
245050
|
routes: {
|
|
243628
245051
|
start: startCommand,
|
|
243629
245052
|
connect: connectRoutes
|
|
@@ -243633,7 +245056,7 @@ var routes30 = buildRouteMap({
|
|
|
243633
245056
|
brief: "Vibedeckx - AI-powered app generator"
|
|
243634
245057
|
}
|
|
243635
245058
|
});
|
|
243636
|
-
var program = buildApplication(
|
|
245059
|
+
var program = buildApplication(routes32, {
|
|
243637
245060
|
name: "vibedeckx",
|
|
243638
245061
|
versionInfo: {
|
|
243639
245062
|
currentVersion: readPackageVersion()
|