@wrongstack/webui-server 0.308.1 → 0.308.4
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/index.js +353 -25
- package/dist/protocol/index.js +1 -0
- package/dist/protocol/registry.d.ts +1 -1
- package/dist/protocol/server-workspace.d.ts +1 -1
- package/dist/server/connections/auto-healer.d.ts +85 -0
- package/dist/server/embedded-message-router.d.ts +8 -0
- package/dist/server/entry.js +337 -25
- package/dist/server/start-webui-shutdown.d.ts +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -12010,6 +12010,7 @@ ${metrics}` : "",
|
|
|
12010
12010
|
task.labels?.length ? `Labels: ${task.labels.join(", ")}` : "",
|
|
12011
12011
|
"",
|
|
12012
12012
|
"Work the task end-to-end. Use the kanban tool, not direct file edits, to update this task.",
|
|
12013
|
+
"Stay inside this task: its description and success criteria define the boundary. If you notice problems outside this task while working, do not fix them \u2014 note them in lastResult instead.",
|
|
12013
12014
|
...leaseId ? [
|
|
12014
12015
|
"LEASE CONTRACT:",
|
|
12015
12016
|
`- Your leaseId is "${leaseId}". Include expectedLeaseId "${leaseId}" in every mark_assignment and heartbeat_assignment call.`,
|
|
@@ -12018,7 +12019,7 @@ ${metrics}` : "",
|
|
|
12018
12019
|
"On failure the host may call recover_stale; respect its decisions and do not duplicate work."
|
|
12019
12020
|
] : [],
|
|
12020
12021
|
`When you start or finish, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", and assignmentStatus "running", "completed", or "failed"${leaseId ? `, and expectedLeaseId "${leaseId}"` : ""}. Include lastResult or error when you finish. On managed lifecycle boards, mark_assignment(completed) will automatically advance your card from Running to Review \u2014 a separate verification step is required before the card reaches Done.`,
|
|
12021
|
-
"When finished, report what changed, what you verified, and any
|
|
12022
|
+
"When finished, report what changed, what you verified, any remaining blockers, and any issues you noticed outside this task's scope."
|
|
12022
12023
|
].filter(Boolean).join("\n");
|
|
12023
12024
|
}
|
|
12024
12025
|
function findTask(tasks, taskId) {
|
|
@@ -16042,9 +16043,19 @@ async function handleBrainAsk(ctx, ws, question) {
|
|
|
16042
16043
|
risk: "medium",
|
|
16043
16044
|
fallback: "ask_human"
|
|
16044
16045
|
});
|
|
16046
|
+
const answerSessionId = ctx.getSessionId?.();
|
|
16045
16047
|
ctx.send(ws, {
|
|
16046
16048
|
type: "brain.answer",
|
|
16047
|
-
|
|
16049
|
+
// Omit sessionId when there is no session: this is a direct reply to
|
|
16050
|
+
// the asker, not a broadcast, but the client's session gate
|
|
16051
|
+
// (isActiveSessionMessage) is fail-closed on a present-but-empty
|
|
16052
|
+
// sessionId — stamping '' would hide the answer from its own asker
|
|
16053
|
+
// in an embedded host with an unbound agent context.
|
|
16054
|
+
payload: {
|
|
16055
|
+
...answerSessionId ? { sessionId: answerSessionId } : {},
|
|
16056
|
+
question: q,
|
|
16057
|
+
decision
|
|
16058
|
+
}
|
|
16048
16059
|
});
|
|
16049
16060
|
} catch (err) {
|
|
16050
16061
|
sendResult6(ctx, ws, false, `Brain consultation failed: ${toErrorMessage6(err)}`);
|
|
@@ -18530,7 +18541,12 @@ function createProviderHandlers(deps2) {
|
|
|
18530
18541
|
|
|
18531
18542
|
// src/server/session-handlers.ts
|
|
18532
18543
|
import { loadTodosCheckpoint } from "@wrongstack/core/storage";
|
|
18533
|
-
import {
|
|
18544
|
+
import {
|
|
18545
|
+
CONTEXT_WINDOW_MODE_PINNED_META_KEY,
|
|
18546
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
18547
|
+
isContextWindowModeId,
|
|
18548
|
+
resolveContextWindowPolicy
|
|
18549
|
+
} from "@wrongstack/core/types";
|
|
18534
18550
|
import { repairToolUseAdjacency as repairToolUseAdjacency2, sessionScopedPath } from "@wrongstack/core/utils";
|
|
18535
18551
|
|
|
18536
18552
|
// src/protocol/connection-fsm.ts
|
|
@@ -19115,6 +19131,7 @@ var SERVER_WORKSPACE_MESSAGE_TYPES = [
|
|
|
19115
19131
|
var SERVER_CONFIGURATION_MESSAGE_TYPES = [
|
|
19116
19132
|
"auth.oauth.status",
|
|
19117
19133
|
"codebase.index.server.shutdown_result",
|
|
19134
|
+
"connections.auto_heal_status",
|
|
19118
19135
|
"connections.health_error",
|
|
19119
19136
|
"connections.health_result",
|
|
19120
19137
|
"connections.service_action_result",
|
|
@@ -20084,8 +20101,8 @@ function createSessionHandlers(ctx) {
|
|
|
20084
20101
|
return;
|
|
20085
20102
|
}
|
|
20086
20103
|
const { id } = parsed.value;
|
|
20087
|
-
let policy = resolveContextWindowPolicy({}, id);
|
|
20088
|
-
if (policy.id !== id) {
|
|
20104
|
+
let policy = resolveContextWindowPolicy({}, id, readSessionWindowTokens(ctx.context));
|
|
20105
|
+
if (!isContextWindowModeId(id) && policy.id !== id) {
|
|
20089
20106
|
const customModes = (await modeStore()).list().filter((m) => m.custom === true);
|
|
20090
20107
|
const custom = customModes.find((m) => m.id === id);
|
|
20091
20108
|
if (!custom) {
|
|
@@ -20096,6 +20113,7 @@ function createSessionHandlers(ctx) {
|
|
|
20096
20113
|
}
|
|
20097
20114
|
ctx.context.meta["contextWindowMode"] = policy.id;
|
|
20098
20115
|
ctx.context.meta["contextWindowPolicy"] = policy;
|
|
20116
|
+
ctx.context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY] = true;
|
|
20099
20117
|
result(ws, true, `Context mode switched to ${policy.id}`);
|
|
20100
20118
|
broadcastToAll({
|
|
20101
20119
|
type: "context.mode.changed",
|
|
@@ -20159,11 +20177,14 @@ function createSessionHandlers(ctx) {
|
|
|
20159
20177
|
}
|
|
20160
20178
|
const { id } = parsed.value;
|
|
20161
20179
|
if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
|
|
20162
|
-
|
|
20163
|
-
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy(
|
|
20180
|
+
const policy = resolveContextWindowPolicy(
|
|
20164
20181
|
{},
|
|
20165
|
-
DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
20182
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
20183
|
+
readSessionWindowTokens(ctx.context)
|
|
20166
20184
|
);
|
|
20185
|
+
ctx.context.meta["contextWindowMode"] = policy.id;
|
|
20186
|
+
ctx.context.meta["contextWindowPolicy"] = policy;
|
|
20187
|
+
delete ctx.context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY];
|
|
20167
20188
|
}
|
|
20168
20189
|
const store = await modeStore();
|
|
20169
20190
|
const operation = store.remove(id);
|
|
@@ -20370,6 +20391,12 @@ function createSessionHandlers(ctx) {
|
|
|
20370
20391
|
}
|
|
20371
20392
|
};
|
|
20372
20393
|
}
|
|
20394
|
+
function readSessionWindowTokens(context) {
|
|
20395
|
+
const meta = context.meta?.["effectiveMaxContext"];
|
|
20396
|
+
if (typeof meta === "number" && Number.isFinite(meta) && meta > 0) return meta;
|
|
20397
|
+
const cap = context.provider?.capabilities?.maxContext;
|
|
20398
|
+
return typeof cap === "number" && Number.isFinite(cap) && cap > 0 ? cap : 0;
|
|
20399
|
+
}
|
|
20373
20400
|
|
|
20374
20401
|
// src/server/embedded-host-adapters.ts
|
|
20375
20402
|
async function applyEmbeddedModelSwitch(ctx, providerId, modelId) {
|
|
@@ -21032,6 +21059,259 @@ async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
|
21032
21059
|
return true;
|
|
21033
21060
|
}
|
|
21034
21061
|
|
|
21062
|
+
// src/server/connections/auto-healer.ts
|
|
21063
|
+
var AUTO_HEAL_ENV_FLAG = "WRONGSTACK_AUTO_HEAL_SERVICES";
|
|
21064
|
+
var AUTO_HEAL_DEFAULT_INTERVAL_MS = 3e4;
|
|
21065
|
+
var AUTO_HEAL_DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
21066
|
+
var AUTO_HEAL_DEFAULT_MAX_ATTEMPTS = 3;
|
|
21067
|
+
var RESTARTABLE_SERVICE_IDS = /* @__PURE__ */ new Set([
|
|
21068
|
+
"kanban",
|
|
21069
|
+
"sage",
|
|
21070
|
+
"chronicle",
|
|
21071
|
+
"codebase-index",
|
|
21072
|
+
"mailbox"
|
|
21073
|
+
]);
|
|
21074
|
+
function isAutoHealEnabled() {
|
|
21075
|
+
return process.env[AUTO_HEAL_ENV_FLAG] === "1";
|
|
21076
|
+
}
|
|
21077
|
+
function createAutoHealer(options) {
|
|
21078
|
+
const enabled = options.enabled ?? isAutoHealEnabled();
|
|
21079
|
+
const intervalMs = options.intervalMs ?? AUTO_HEAL_DEFAULT_INTERVAL_MS;
|
|
21080
|
+
const cooldownMs = options.cooldownMs ?? AUTO_HEAL_DEFAULT_COOLDOWN_MS;
|
|
21081
|
+
const maxAttempts = options.maxAttempts ?? AUTO_HEAL_DEFAULT_MAX_ATTEMPTS;
|
|
21082
|
+
const collect = options.collect ?? (() => collectConnectionsHealth({
|
|
21083
|
+
projectRoot: options.projectRoot(),
|
|
21084
|
+
indexDir: options.indexDir(),
|
|
21085
|
+
backend: "standalone"
|
|
21086
|
+
}));
|
|
21087
|
+
const execute = options.execute ?? executeServiceAction;
|
|
21088
|
+
const services = /* @__PURE__ */ new Map();
|
|
21089
|
+
let timer = null;
|
|
21090
|
+
let running = false;
|
|
21091
|
+
let ticking = false;
|
|
21092
|
+
let disposed = false;
|
|
21093
|
+
let inFlightTick = null;
|
|
21094
|
+
let lastTickAt = null;
|
|
21095
|
+
let warnedNoBoundary = false;
|
|
21096
|
+
function stateFor(serviceId) {
|
|
21097
|
+
let state = services.get(serviceId);
|
|
21098
|
+
if (!state) {
|
|
21099
|
+
state = {
|
|
21100
|
+
lastAttemptAt: null,
|
|
21101
|
+
consecutiveFailures: 0,
|
|
21102
|
+
lastSuccess: null,
|
|
21103
|
+
lastMessage: null,
|
|
21104
|
+
inFlight: false,
|
|
21105
|
+
escalated: false
|
|
21106
|
+
};
|
|
21107
|
+
services.set(serviceId, state);
|
|
21108
|
+
}
|
|
21109
|
+
return state;
|
|
21110
|
+
}
|
|
21111
|
+
function snapshot() {
|
|
21112
|
+
return {
|
|
21113
|
+
enabled,
|
|
21114
|
+
running,
|
|
21115
|
+
lastTickAt,
|
|
21116
|
+
services: Object.fromEntries(services)
|
|
21117
|
+
};
|
|
21118
|
+
}
|
|
21119
|
+
function emitStatus(event) {
|
|
21120
|
+
try {
|
|
21121
|
+
options.onStatus?.({ ...event, at: Date.now() });
|
|
21122
|
+
} catch (error2) {
|
|
21123
|
+
options.logger?.warn?.(
|
|
21124
|
+
`[AutoHeal] onStatus hook threw: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21125
|
+
);
|
|
21126
|
+
}
|
|
21127
|
+
}
|
|
21128
|
+
async function tick() {
|
|
21129
|
+
if (!enabled || disposed) return snapshot();
|
|
21130
|
+
if (!options.trustBoundary) {
|
|
21131
|
+
if (!warnedNoBoundary) {
|
|
21132
|
+
warnedNoBoundary = true;
|
|
21133
|
+
options.logger?.warn?.(
|
|
21134
|
+
"[AutoHeal] Disabled: no policy authority (trust boundary) is configured."
|
|
21135
|
+
);
|
|
21136
|
+
}
|
|
21137
|
+
return snapshot();
|
|
21138
|
+
}
|
|
21139
|
+
if (ticking) return snapshot();
|
|
21140
|
+
ticking = true;
|
|
21141
|
+
try {
|
|
21142
|
+
const report = await collect();
|
|
21143
|
+
const now = Date.now();
|
|
21144
|
+
const projectRoot = options.projectRoot();
|
|
21145
|
+
const indexDir = options.indexDir();
|
|
21146
|
+
for (const service of report.services) {
|
|
21147
|
+
if (disposed) break;
|
|
21148
|
+
const state = stateFor(service.id);
|
|
21149
|
+
if (service.status !== "error") {
|
|
21150
|
+
state.consecutiveFailures = 0;
|
|
21151
|
+
state.escalated = false;
|
|
21152
|
+
continue;
|
|
21153
|
+
}
|
|
21154
|
+
if (!RESTARTABLE_SERVICE_IDS.has(service.id) || service.control === "none") {
|
|
21155
|
+
continue;
|
|
21156
|
+
}
|
|
21157
|
+
if (state.lastAttemptAt !== null && now - state.lastAttemptAt < cooldownMs) continue;
|
|
21158
|
+
if (state.consecutiveFailures >= maxAttempts) {
|
|
21159
|
+
state.escalated = true;
|
|
21160
|
+
options.logger?.warn?.(
|
|
21161
|
+
`[AutoHeal] ${service.id} left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage ?? "unknown"}`
|
|
21162
|
+
);
|
|
21163
|
+
continue;
|
|
21164
|
+
}
|
|
21165
|
+
if (state.inFlight) continue;
|
|
21166
|
+
const authorization = await authorizeWebUIAction(
|
|
21167
|
+
options.trustBoundary,
|
|
21168
|
+
{
|
|
21169
|
+
capability: "connections.service.restart",
|
|
21170
|
+
subject: { kind: "process", id: `${service.id}@${projectRoot}` },
|
|
21171
|
+
risk: "elevated",
|
|
21172
|
+
cwd: projectRoot,
|
|
21173
|
+
metadata: { transport: "auto-heal", serviceId: service.id, action: "restart" }
|
|
21174
|
+
},
|
|
21175
|
+
options.logger
|
|
21176
|
+
);
|
|
21177
|
+
if (disposed) break;
|
|
21178
|
+
if (!authorization.allowed) {
|
|
21179
|
+
state.lastAttemptAt = now;
|
|
21180
|
+
state.lastMessage = `refused by policy: ${authorization.reason}`;
|
|
21181
|
+
emitStatus({
|
|
21182
|
+
serviceId: service.id,
|
|
21183
|
+
phase: "refused",
|
|
21184
|
+
message: `refused by policy: ${authorization.reason}`,
|
|
21185
|
+
attempt: state.consecutiveFailures + 1
|
|
21186
|
+
});
|
|
21187
|
+
options.logger?.warn?.(
|
|
21188
|
+
`[AutoHeal] ${service.id} restart refused by policy: ${authorization.reason}`
|
|
21189
|
+
);
|
|
21190
|
+
continue;
|
|
21191
|
+
}
|
|
21192
|
+
state.inFlight = true;
|
|
21193
|
+
const attempt = state.consecutiveFailures + 1;
|
|
21194
|
+
emitStatus({
|
|
21195
|
+
serviceId: service.id,
|
|
21196
|
+
phase: "restarting",
|
|
21197
|
+
message: `Auto-restarting ${service.id}`,
|
|
21198
|
+
attempt
|
|
21199
|
+
});
|
|
21200
|
+
try {
|
|
21201
|
+
const result = await execute(service.id, "restart", projectRoot, indexDir);
|
|
21202
|
+
state.consecutiveFailures = result.success ? 0 : state.consecutiveFailures + 1;
|
|
21203
|
+
state.lastSuccess = result.success;
|
|
21204
|
+
state.lastMessage = result.message;
|
|
21205
|
+
emitStatus({
|
|
21206
|
+
serviceId: service.id,
|
|
21207
|
+
phase: result.success ? "restarted" : "failed",
|
|
21208
|
+
message: result.message,
|
|
21209
|
+
attempt
|
|
21210
|
+
});
|
|
21211
|
+
if (!result.success && state.consecutiveFailures >= maxAttempts) {
|
|
21212
|
+
state.escalated = true;
|
|
21213
|
+
emitStatus({
|
|
21214
|
+
serviceId: service.id,
|
|
21215
|
+
phase: "escalated",
|
|
21216
|
+
message: `left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${result.message}`,
|
|
21217
|
+
attempt
|
|
21218
|
+
});
|
|
21219
|
+
options.logger?.warn?.(
|
|
21220
|
+
`[AutoHeal] ${service.id} escalated after ${state.consecutiveFailures} failed auto-restart(s): ${result.message}`
|
|
21221
|
+
);
|
|
21222
|
+
}
|
|
21223
|
+
options.logger?.[result.success ? "info" : "warn"]?.(
|
|
21224
|
+
`[AutoHeal] ${service.id} auto-restart ${result.success ? "succeeded" : "failed"}: ${result.message}`
|
|
21225
|
+
);
|
|
21226
|
+
} catch (error2) {
|
|
21227
|
+
state.consecutiveFailures += 1;
|
|
21228
|
+
state.lastSuccess = false;
|
|
21229
|
+
state.lastMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
21230
|
+
emitStatus({
|
|
21231
|
+
serviceId: service.id,
|
|
21232
|
+
phase: "failed",
|
|
21233
|
+
message: state.lastMessage,
|
|
21234
|
+
attempt
|
|
21235
|
+
});
|
|
21236
|
+
if (state.consecutiveFailures >= maxAttempts) {
|
|
21237
|
+
state.escalated = true;
|
|
21238
|
+
emitStatus({
|
|
21239
|
+
serviceId: service.id,
|
|
21240
|
+
phase: "escalated",
|
|
21241
|
+
message: `left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage}`,
|
|
21242
|
+
attempt
|
|
21243
|
+
});
|
|
21244
|
+
options.logger?.warn?.(
|
|
21245
|
+
`[AutoHeal] ${service.id} escalated after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage}`
|
|
21246
|
+
);
|
|
21247
|
+
}
|
|
21248
|
+
options.logger?.warn?.(
|
|
21249
|
+
`[AutoHeal] ${service.id} auto-restart threw: ${state.lastMessage}`
|
|
21250
|
+
);
|
|
21251
|
+
} finally {
|
|
21252
|
+
state.lastAttemptAt = Date.now();
|
|
21253
|
+
state.inFlight = false;
|
|
21254
|
+
}
|
|
21255
|
+
}
|
|
21256
|
+
lastTickAt = Date.now();
|
|
21257
|
+
} catch (error2) {
|
|
21258
|
+
options.logger?.warn?.(
|
|
21259
|
+
`[AutoHeal] health collect failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21260
|
+
);
|
|
21261
|
+
} finally {
|
|
21262
|
+
ticking = false;
|
|
21263
|
+
}
|
|
21264
|
+
return snapshot();
|
|
21265
|
+
}
|
|
21266
|
+
function runTick() {
|
|
21267
|
+
if (!enabled || disposed || running === false || ticking) return;
|
|
21268
|
+
const pending = tick();
|
|
21269
|
+
const tracked = pending.then(
|
|
21270
|
+
() => void 0,
|
|
21271
|
+
() => void 0
|
|
21272
|
+
);
|
|
21273
|
+
inFlightTick = tracked;
|
|
21274
|
+
void tracked.finally(() => {
|
|
21275
|
+
if (inFlightTick === tracked) inFlightTick = null;
|
|
21276
|
+
});
|
|
21277
|
+
}
|
|
21278
|
+
function stopInternal() {
|
|
21279
|
+
if (timer) {
|
|
21280
|
+
clearInterval(timer);
|
|
21281
|
+
timer = null;
|
|
21282
|
+
}
|
|
21283
|
+
running = false;
|
|
21284
|
+
}
|
|
21285
|
+
return {
|
|
21286
|
+
start() {
|
|
21287
|
+
if (!enabled || running || disposed) return;
|
|
21288
|
+
running = true;
|
|
21289
|
+
runTick();
|
|
21290
|
+
timer = setInterval(runTick, intervalMs);
|
|
21291
|
+
timer.unref?.();
|
|
21292
|
+
},
|
|
21293
|
+
stop: stopInternal,
|
|
21294
|
+
async dispose() {
|
|
21295
|
+
stopInternal();
|
|
21296
|
+
disposed = true;
|
|
21297
|
+
const pending = inFlightTick;
|
|
21298
|
+
if (pending) {
|
|
21299
|
+
await Promise.race([
|
|
21300
|
+
pending,
|
|
21301
|
+
new Promise((resolve20) => {
|
|
21302
|
+
const t = setTimeout(resolve20, 3e4);
|
|
21303
|
+
t.unref?.();
|
|
21304
|
+
})
|
|
21305
|
+
]);
|
|
21306
|
+
}
|
|
21307
|
+
disposed = true;
|
|
21308
|
+
},
|
|
21309
|
+
tick,
|
|
21310
|
+
getSnapshot: snapshot,
|
|
21311
|
+
isRunning: () => running
|
|
21312
|
+
};
|
|
21313
|
+
}
|
|
21314
|
+
|
|
21035
21315
|
// src/server/fallback-choice.ts
|
|
21036
21316
|
function emitFallbackChoice(events, msg) {
|
|
21037
21317
|
const parsed = validateModelFallbackChoicePayload(msg.payload);
|
|
@@ -21721,6 +22001,22 @@ async function handleShellOpen(req, logger, options) {
|
|
|
21721
22001
|
function createEmbeddedMessageRouter(deps2) {
|
|
21722
22002
|
const { opts, send: send2, sendResult: sendResult7 } = deps2;
|
|
21723
22003
|
const projectRoot = () => opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
22004
|
+
const autoHealer = createAutoHealer({
|
|
22005
|
+
projectRoot,
|
|
22006
|
+
indexDir: () => typeof opts.agent.ctx.meta["codebaseIndexDir"] === "string" ? opts.agent.ctx.meta["codebaseIndexDir"] : void 0,
|
|
22007
|
+
trustBoundary: deps2.trustBoundary,
|
|
22008
|
+
logger: deps2.logger,
|
|
22009
|
+
onStatus: (event) => deps2.providerCtx.broadcast({
|
|
22010
|
+
type: "connections.auto_heal_status",
|
|
22011
|
+
payload: event
|
|
22012
|
+
})
|
|
22013
|
+
});
|
|
22014
|
+
autoHealer.start();
|
|
22015
|
+
if (deps2.onDispose) {
|
|
22016
|
+
deps2.onDispose(async () => {
|
|
22017
|
+
await autoHealer.dispose();
|
|
22018
|
+
});
|
|
22019
|
+
}
|
|
21724
22020
|
const terminal = async (ws, message) => {
|
|
21725
22021
|
await deps2.terminalHandler.handleMessage(ws, message).catch((error2) => {
|
|
21726
22022
|
const text2 = error2 instanceof Error ? error2.message : String(error2);
|
|
@@ -24704,6 +25000,7 @@ import {
|
|
|
24704
25000
|
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
24705
25001
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
24706
25002
|
import {
|
|
25003
|
+
CONTEXT_WINDOW_MODE_PINNED_META_KEY as CONTEXT_WINDOW_MODE_PINNED_META_KEY2,
|
|
24707
25004
|
DEFAULT_TOOLS_CONFIG,
|
|
24708
25005
|
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
24709
25006
|
} from "@wrongstack/core/types";
|
|
@@ -25624,22 +25921,26 @@ async function createAgentServices(input) {
|
|
|
25624
25921
|
summarizerModel: config.context?.summarizerModel,
|
|
25625
25922
|
llmSelector: config.context?.llmSelector
|
|
25626
25923
|
});
|
|
25627
|
-
|
|
25924
|
+
let effectiveMaxContext = 0;
|
|
25925
|
+
try {
|
|
25926
|
+
const m = await resolveProviderModelMetadata(
|
|
25927
|
+
modelsRegistry,
|
|
25928
|
+
config.provider,
|
|
25929
|
+
context.model,
|
|
25930
|
+
config.providers?.[config.provider]
|
|
25931
|
+
);
|
|
25932
|
+
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
25933
|
+
} catch {
|
|
25934
|
+
}
|
|
25935
|
+
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
25936
|
+
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
25937
|
+
const initialContextPolicy = resolveContextWindowPolicy2(
|
|
25938
|
+
config.context,
|
|
25939
|
+
void 0,
|
|
25940
|
+
effectiveMaxContext
|
|
25941
|
+
);
|
|
25628
25942
|
let autoCompactor;
|
|
25629
25943
|
if (config.context?.autoCompact !== false) {
|
|
25630
|
-
let effectiveMaxContext = 0;
|
|
25631
|
-
try {
|
|
25632
|
-
const m = await resolveProviderModelMetadata(
|
|
25633
|
-
modelsRegistry,
|
|
25634
|
-
config.provider,
|
|
25635
|
-
context.model,
|
|
25636
|
-
config.providers?.[config.provider]
|
|
25637
|
-
);
|
|
25638
|
-
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
25639
|
-
} catch {
|
|
25640
|
-
}
|
|
25641
|
-
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
25642
|
-
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
25643
25944
|
autoCompactor = new AutoCompactionMiddlewareCtor(
|
|
25644
25945
|
compactor,
|
|
25645
25946
|
effectiveMaxContext,
|
|
@@ -25694,6 +25995,15 @@ async function createAgentServices(input) {
|
|
|
25694
25995
|
context.meta["effectiveMaxContext"] = newMaxContext;
|
|
25695
25996
|
autoCompactor?.setMaxContext(newMaxContext);
|
|
25696
25997
|
autoCompactor?.setEnabled(config.context?.autoCompact !== false);
|
|
25998
|
+
if (context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY2] !== true) {
|
|
25999
|
+
const policy = resolveContextWindowPolicy2(
|
|
26000
|
+
currentConfig.context ?? {},
|
|
26001
|
+
void 0,
|
|
26002
|
+
newMaxContext
|
|
26003
|
+
);
|
|
26004
|
+
context.meta["contextWindowMode"] = policy.id;
|
|
26005
|
+
context.meta["contextWindowPolicy"] = policy;
|
|
26006
|
+
}
|
|
25697
26007
|
} else {
|
|
25698
26008
|
delete context.meta["effectiveMaxContext"];
|
|
25699
26009
|
autoCompactor?.setEnabled(false);
|
|
@@ -26325,8 +26635,22 @@ function createMessageDispatcher(opts) {
|
|
|
26325
26635
|
broadcast: (message) => broadcast(state.getClients(), message),
|
|
26326
26636
|
log: (message) => deps2.logger.warn?.(`[KanbanSupervisor] ${message}`)
|
|
26327
26637
|
});
|
|
26638
|
+
const autoHealer = createAutoHealer({
|
|
26639
|
+
projectRoot: () => state.getProjectRoot(),
|
|
26640
|
+
indexDir: () => typeof deps2.context.meta["codebaseIndexDir"] === "string" ? deps2.context.meta["codebaseIndexDir"] : void 0,
|
|
26641
|
+
trustBoundary: deps2.trustBoundary,
|
|
26642
|
+
logger: deps2.logger,
|
|
26643
|
+
onStatus: (event) => broadcast(state.getClients(), {
|
|
26644
|
+
type: "connections.auto_heal_status",
|
|
26645
|
+
payload: event
|
|
26646
|
+
})
|
|
26647
|
+
});
|
|
26648
|
+
autoHealer.start();
|
|
26328
26649
|
if (opts.onDispose) {
|
|
26329
|
-
const dispose = () =>
|
|
26650
|
+
const dispose = async () => {
|
|
26651
|
+
kanbanSupervisor.dispose();
|
|
26652
|
+
await autoHealer.dispose();
|
|
26653
|
+
};
|
|
26330
26654
|
opts.onDispose(dispose);
|
|
26331
26655
|
}
|
|
26332
26656
|
const kanbanContext = () => ({
|
|
@@ -27190,7 +27514,11 @@ async function createPreContextServices(input) {
|
|
|
27190
27514
|
model: config.model
|
|
27191
27515
|
});
|
|
27192
27516
|
context.meta["promptOnlineAgents"] = onlineAgents;
|
|
27193
|
-
const initialContextPolicy = resolveContextWindowPolicy3(
|
|
27517
|
+
const initialContextPolicy = resolveContextWindowPolicy3(
|
|
27518
|
+
config.context,
|
|
27519
|
+
void 0,
|
|
27520
|
+
provider.capabilities?.maxContext
|
|
27521
|
+
);
|
|
27194
27522
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
27195
27523
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
27196
27524
|
context.state.setMeta(
|
|
@@ -28035,7 +28363,7 @@ function setupWebuiShutdown(options) {
|
|
|
28035
28363
|
onPreShutdown: async () => {
|
|
28036
28364
|
await options.stopEmptySessionCleanup.dispose();
|
|
28037
28365
|
const disposeKanban = options.getKanbanSupervisorDispose();
|
|
28038
|
-
disposeKanban?.();
|
|
28366
|
+
await disposeKanban?.();
|
|
28039
28367
|
},
|
|
28040
28368
|
onShutdown: async () => {
|
|
28041
28369
|
unregister();
|
package/dist/protocol/index.js
CHANGED
|
@@ -580,6 +580,7 @@ var SERVER_WORKSPACE_MESSAGE_TYPES = [
|
|
|
580
580
|
var SERVER_CONFIGURATION_MESSAGE_TYPES = [
|
|
581
581
|
"auth.oauth.status",
|
|
582
582
|
"codebase.index.server.shutdown_result",
|
|
583
|
+
"connections.auto_heal_status",
|
|
583
584
|
"connections.health_error",
|
|
584
585
|
"connections.health_result",
|
|
585
586
|
"connections.service_action_result",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const CLIENT_MESSAGE_TYPES: readonly ["abort", "ping", "user_message", "tool.confirm_result", "topic.advice", "completion.request", "model.switch", "model.refine", "model.fallback_choice", "autonomy.switch", "context.clear", "context.compact", "context.debug", "context.editor.open", "context.editor.validate", "context.editor.apply", "context.mode.create", "context.mode.delete", "context.mode.switch", "context.mode.update", "context.modes.list", "context.repair", "mode.switch", "modes.list", "session.checkpoints", "session.delete", "session.inspect", "session.new", "session.rename", "session.resume", "session.rewind", "session.save", "sessions.list", "side_effects.list", "stats.get", "todo.update", "todos.clear", "todos.get", "todos.remove", "collab.join", "collab.leave", "collab.annotate", "collab.resolve", "collab.request_pause", "collab.resume", "collab.grant_control", "collab.inject_tool", "mailbox.action", "mailbox.agents", "mailbox.clear", "mailbox.compact", "mailbox.messages", "mailbox.purge", "mailbox.send", "files.create", "files.delete", "files.list", "files.move", "files.read", "files.rename", "files.skeleton", "files.tree", "files.write", "git.changes", "git.diff", "git.info", "projects.add", "projects.list", "projects.select", "working_dir.set", "worktree.cleanup", "worktree.diff", "worktree.merge", "worktree.remove", "worktree.scan", "shell.open", "process.kill", "process.killAll", "process.list", "terminal.close", "terminal.create", "terminal.input", "terminal.resize", "codebase.index.server.shutdown", "connections.health", "connections.service_action", "diag.get", "key.add", "key.delete", "key.set_active", "key.update", "prefs.get", "prefs.update", "provider.add", "provider.clear_models", "provider.custom_models.remove", "provider.custom_models.set", "provider.models", "provider.models.search", "provider.probe", "provider.remove", "provider.status.clear", "provider.status.get", "provider.status.retry", "provider.undo_clear", "provider.update", "providers.list", "providers.saved", "tool.disable", "tool.enable", "tools.list", "webui.shutdown", "goal-state.get", "goal.addTask", "goal.assess", "goal.assignTask", "goal.clear", "goal.get", "goal.list", "goal.load", "goal.moveTask", "goal.pause", "goal.resume", "goal.retryTask", "goal.revert", "goal.runTask", "goal.save", "goal.selectPhase", "goal.start", "goal.state", "goal.status", "goal.stop", "goal.taskStatus", "goal.toggleAutonomous", "plan.get", "plan.item.update", "plan.template_use", "task.update", "tasks.get", "sdd.board.cancel_task", "sdd.board.cleanup_worktrees", "sdd.board.delete_task", "sdd.board.destroy", "sdd.board.get", "sdd.board.list", "sdd.board.pause", "sdd.board.reassign", "sdd.board.resume", "sdd.board.retry", "sdd.board.retry_all_failed", "sdd.board.rollback", "sdd.board.set_task_fallbacks", "sdd.board.set_task_model", "sdd.board.set_task_verification", "sdd.board.split_task", "sdd.board.stop", "sdd.run.from_graph", "sdd.run.from_spec", "sdd.run.start", "sdd.spec.approve", "sdd.spec.discard", "sdd.spec.get", "sdd.spec.message", "sdd.spec.rewind", "sdd.spec.start", "specs.get", "specs.list", "specs.taskStatus", "brain.ask", "brain.config.get", "brain.config.set", "brain.risk", "brain.status", "chronicle.facet", "chronicle.facets", "chronicle.graph", "chronicle.metrics", "chronicle.query", "chronicle.status", "config.doctor", "design.list", "design.materialize", "design.set", "design.state", "design.swap", "design.tune", "design.use", "design.verify", "memory.list", "memory.sage.backfillRecoverable", "memory.sage.candidateResolve", "memory.sage.delete", "memory.sage.forFile", "memory.sage.get", "memory.sage.graph", "memory.sage.list", "memory.sage.listCandidates", "memory.sage.listPage", "memory.sage.recover", "memory.sage.remember", "memory.sage.searchBreakdown", "memory.sage.update", "auth.oauth.cancel", "auth.oauth.code", "auth.oauth.start", "mcp.add", "mcp.disable", "mcp.discover", "mcp.enable", "mcp.list", "mcp.prompt.get", "mcp.prompts", "mcp.remove", "mcp.resource.read", "mcp.resources", "mcp.restart", "mcp.sleep", "mcp.update", "mcp.wake", "prompts.content", "prompts.create", "prompts.favorite", "prompts.journal", "prompts.list", "prompts.recent", "prompts.search", "prompts.used", "skills.content", "skills.create", "skills.edit", "skills.export", "skills.install", "skills.list", "skills.uninstall", "skills.update"];
|
|
2
|
-
export declare const SERVER_MESSAGE_TYPES: readonly ["error", "log", "pong", "side_effects", "agent.status_changed", "agent.timeline.message", "client.status_update", "chimera.report_available", "compaction.failed", "completion.result", "context.compacted", "context.debug", "context.editor.snapshot", "context.editor.validation", "context.editor.applied", "context.mode.changed", "context.modes.list", "context.repaired", "ctx.max_context", "ctx.pct", "delegate.completed", "delegate.started", "iteration.completed", "iteration.limit_reached", "iteration.started", "model.refine_result", "modes.list", "provider.active_blocked", "provider.error", "provider.fallback", "provider.fallback_pending", "provider.response", "provider.retry", "provider.status_changed", "provider.stream_error", "provider.text_delta", "provider.thinking_delta", "run.result", "session.checkpoints", "session.damaged", "session.end", "session.inspect", "session.rewound", "session.start", "session.stats", "sessions.list", "sessions.status_update", "stats.get", "token.cost_estimate_unavailable", "token.threshold", "tool.confirm_needed", "tool.disabled", "tool.enabled", "tool.executed", "tool.loop_detected", "tool.progress", "tool.started", "topic.advice_result", "tools.list", "trust.persisted", "collab.annotation.added", "collab.annotation.resolved", "collab.event", "collab.injection.granted", "collab.participant.joined", "collab.participant.left", "collab.pause.granted", "collab.pause.released", "collab.state", "mailbox.action_result", "mailbox.agent_registered", "mailbox.agent_deregistered", "mailbox.agents", "mailbox.cleared", "mailbox.compacted", "mailbox.event", "mailbox.messages", "mailbox.sent", "mailbox.purged", "mailbox.received", "subagent.budget_extended", "subagent.event", "checkpoint.written", "codemap.file_event", "codemap.index_updated", "codemap.tool_executed", "codemap.tool_started", "file.saved", "files.created", "files.deleted", "files.list", "files.moved", "files.read", "files.renamed", "files.skeleton_result", "files.tree", "files.tree.changed", "files.written", "git.changes", "git.diff", "git.info", "process.list", "projects.added", "projects.list", "projects.selected", "terminal.exit", "terminal.output", "working_dir.changed", "worktree.cleanup_result", "worktree.diff_result", "worktree.event", "worktree.merge_result", "worktree.orphans", "worktree.state", "auth.oauth.status", "codebase.index.server.shutdown_result", "connections.health_error", "connections.health_result", "connections.service_action_result", "diag.get", "key.operation_result", "model.switch_result", "prefs.updated", "provider.catalog", "provider.models", "provider.models.search_result", "provider.probe", "provider.status.snapshot", "providers.saved", "budget.decision", "budget.threshold_reached", "coordinator.stats", "coordinator.status", "eternal.iteration", "fleet.concurrency_update", "goal-state.updated", "goal.assess.result", "goal.list", "goal.paused", "goal.resumed", "goal.saved", "goal.error", "goal.stopped", "goal.failed", "goal.completed", "goal.cleared", "goal.reverted", "goal.progress", "goal.state", "in_flight.ended", "in_flight.started", "plan.updated", "task.completed", "task.failed", "task.pending", "task.started", "tasks.updated", "todos.cleared", "todos.updated", "kanban.task.activity", "sdd.board.lifecycle_result", "sdd.board.list", "sdd.board.snapshot", "sdd.run.started", "sdd.spec.agent_text", "sdd.spec.error", "sdd.spec.snapshot", "specs.detail", "specs.list", "consensus.vote_cast", "consensus.vote_initiated", "consensus.vote_resolved", "cron.job_fired", "cron.snapshot", "techstack.job.cancelled", "techstack.job.failed", "techstack.job.progress", "techstack.job.started", "techstack.report.delivered", "techstack.report.ready", "techstack.snapshot.updated", "techstack.workspace.completed", "brain.answer", "brain.config", "brain.event", "brain.status", "chronicle.error", "chronicle.facet_result", "chronicle.facets_result", "chronicle.graph_result", "chronicle.metrics_result", "chronicle.query_result", "chronicle.status_result", "config.doctor.result", "design.list", "design.materialize", "design.set", "design.state", "design.swap", "design.tune", "design.use", "design.verify", "memory.event", "memory.list", "memory.sage.backfillRecoverable", "memory.sage.candidateResolve", "memory.sage.delete", "memory.sage.forFile", "memory.sage.get", "memory.sage.graph", "memory.sage.list", "memory.sage.listCandidates", "memory.sage.listPage", "memory.sage.recover", "memory.sage.remember", "memory.sage.searchBreakdown", "memory.sage.update", "mcp.content.error", "mcp.content.selected", "mcp.list", "mcp.operation_result", "mcp.prompts", "mcp.resources", "mcp.server.added", "mcp.server.connected", "mcp.server.disconnected", "mcp.server.discovered", "mcp.server.error", "mcp.server.reconnected", "mcp.server.removed", "mcp.server.sleeping", "mcp.server.updated", "mcp.server.waking", "prompts.content", "prompts.created", "prompts.favorite", "prompts.journal", "prompts.list", "prompts.recent", "prompts.search", "prompts.used", "skills.content", "skills.created", "skills.edited", "skills.exported", "skills.installed", "skills.list", "skills.uninstalled", "skills.updated"];
|
|
2
|
+
export declare const SERVER_MESSAGE_TYPES: readonly ["error", "log", "pong", "side_effects", "agent.status_changed", "agent.timeline.message", "client.status_update", "chimera.report_available", "compaction.failed", "completion.result", "context.compacted", "context.debug", "context.editor.snapshot", "context.editor.validation", "context.editor.applied", "context.mode.changed", "context.modes.list", "context.repaired", "ctx.max_context", "ctx.pct", "delegate.completed", "delegate.started", "iteration.completed", "iteration.limit_reached", "iteration.started", "model.refine_result", "modes.list", "provider.active_blocked", "provider.error", "provider.fallback", "provider.fallback_pending", "provider.response", "provider.retry", "provider.status_changed", "provider.stream_error", "provider.text_delta", "provider.thinking_delta", "run.result", "session.checkpoints", "session.damaged", "session.end", "session.inspect", "session.rewound", "session.start", "session.stats", "sessions.list", "sessions.status_update", "stats.get", "token.cost_estimate_unavailable", "token.threshold", "tool.confirm_needed", "tool.disabled", "tool.enabled", "tool.executed", "tool.loop_detected", "tool.progress", "tool.started", "topic.advice_result", "tools.list", "trust.persisted", "collab.annotation.added", "collab.annotation.resolved", "collab.event", "collab.injection.granted", "collab.participant.joined", "collab.participant.left", "collab.pause.granted", "collab.pause.released", "collab.state", "mailbox.action_result", "mailbox.agent_registered", "mailbox.agent_deregistered", "mailbox.agents", "mailbox.cleared", "mailbox.compacted", "mailbox.event", "mailbox.messages", "mailbox.sent", "mailbox.purged", "mailbox.received", "subagent.budget_extended", "subagent.event", "checkpoint.written", "codemap.file_event", "codemap.index_updated", "codemap.tool_executed", "codemap.tool_started", "file.saved", "files.created", "files.deleted", "files.list", "files.moved", "files.read", "files.renamed", "files.skeleton_result", "files.tree", "files.tree.changed", "files.written", "git.changes", "git.diff", "git.info", "process.list", "projects.added", "projects.list", "projects.selected", "terminal.exit", "terminal.output", "working_dir.changed", "worktree.cleanup_result", "worktree.diff_result", "worktree.event", "worktree.merge_result", "worktree.orphans", "worktree.state", "auth.oauth.status", "codebase.index.server.shutdown_result", "connections.auto_heal_status", "connections.health_error", "connections.health_result", "connections.service_action_result", "diag.get", "key.operation_result", "model.switch_result", "prefs.updated", "provider.catalog", "provider.models", "provider.models.search_result", "provider.probe", "provider.status.snapshot", "providers.saved", "budget.decision", "budget.threshold_reached", "coordinator.stats", "coordinator.status", "eternal.iteration", "fleet.concurrency_update", "goal-state.updated", "goal.assess.result", "goal.list", "goal.paused", "goal.resumed", "goal.saved", "goal.error", "goal.stopped", "goal.failed", "goal.completed", "goal.cleared", "goal.reverted", "goal.progress", "goal.state", "in_flight.ended", "in_flight.started", "plan.updated", "task.completed", "task.failed", "task.pending", "task.started", "tasks.updated", "todos.cleared", "todos.updated", "kanban.task.activity", "sdd.board.lifecycle_result", "sdd.board.list", "sdd.board.snapshot", "sdd.run.started", "sdd.spec.agent_text", "sdd.spec.error", "sdd.spec.snapshot", "specs.detail", "specs.list", "consensus.vote_cast", "consensus.vote_initiated", "consensus.vote_resolved", "cron.job_fired", "cron.snapshot", "techstack.job.cancelled", "techstack.job.failed", "techstack.job.progress", "techstack.job.started", "techstack.report.delivered", "techstack.report.ready", "techstack.snapshot.updated", "techstack.workspace.completed", "brain.answer", "brain.config", "brain.event", "brain.status", "chronicle.error", "chronicle.facet_result", "chronicle.facets_result", "chronicle.graph_result", "chronicle.metrics_result", "chronicle.query_result", "chronicle.status_result", "config.doctor.result", "design.list", "design.materialize", "design.set", "design.state", "design.swap", "design.tune", "design.use", "design.verify", "memory.event", "memory.list", "memory.sage.backfillRecoverable", "memory.sage.candidateResolve", "memory.sage.delete", "memory.sage.forFile", "memory.sage.get", "memory.sage.graph", "memory.sage.list", "memory.sage.listCandidates", "memory.sage.listPage", "memory.sage.recover", "memory.sage.remember", "memory.sage.searchBreakdown", "memory.sage.update", "mcp.content.error", "mcp.content.selected", "mcp.list", "mcp.operation_result", "mcp.prompts", "mcp.resources", "mcp.server.added", "mcp.server.connected", "mcp.server.disconnected", "mcp.server.discovered", "mcp.server.error", "mcp.server.reconnected", "mcp.server.removed", "mcp.server.sleeping", "mcp.server.updated", "mcp.server.waking", "prompts.content", "prompts.created", "prompts.favorite", "prompts.journal", "prompts.list", "prompts.recent", "prompts.search", "prompts.used", "skills.content", "skills.created", "skills.edited", "skills.exported", "skills.installed", "skills.list", "skills.uninstalled", "skills.updated"];
|
|
3
3
|
export type ExactClientMessageType = (typeof CLIENT_MESSAGE_TYPES)[number];
|
|
4
4
|
export type ExactServerMessageType = (typeof SERVER_MESSAGE_TYPES)[number];
|
|
5
5
|
export declare function isRegisteredMessageType(type: string, direction: 'client' | 'server'): boolean;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const SERVER_WORKSPACE_MESSAGE_TYPES: readonly ['checkpoint.written', 'codemap.file_event', 'codemap.index_updated', 'codemap.tool_executed', 'codemap.tool_started', 'file.saved', 'files.created', 'files.deleted', 'files.list', 'files.moved', 'files.read', 'files.renamed', 'files.skeleton_result', 'files.tree', 'files.tree.changed', 'files.written', 'git.changes', 'git.diff', 'git.info', 'process.list', 'projects.added', 'projects.list', 'projects.selected', 'terminal.exit', 'terminal.output', 'working_dir.changed', 'worktree.cleanup_result', 'worktree.diff_result', 'worktree.event', 'worktree.merge_result', 'worktree.orphans', 'worktree.state'];
|
|
2
|
-
export declare const SERVER_CONFIGURATION_MESSAGE_TYPES: readonly ['auth.oauth.status', 'codebase.index.server.shutdown_result', 'connections.health_error', 'connections.health_result', 'connections.service_action_result', 'diag.get', 'key.operation_result', 'model.switch_result', 'prefs.updated', 'provider.catalog', 'provider.models', 'provider.models.search_result', 'provider.probe', 'provider.status.snapshot', 'providers.saved'];
|
|
2
|
+
export declare const SERVER_CONFIGURATION_MESSAGE_TYPES: readonly ['auth.oauth.status', 'codebase.index.server.shutdown_result', 'connections.auto_heal_status', 'connections.health_error', 'connections.health_result', 'connections.service_action_result', 'diag.get', 'key.operation_result', 'model.switch_result', 'prefs.updated', 'provider.catalog', 'provider.models', 'provider.models.search_result', 'provider.probe', 'provider.status.snapshot', 'providers.saved'];
|
|
3
3
|
//# sourceMappingURL=server-workspace.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Logger } from '@wrongstack/core/types';
|
|
2
|
+
import type { TrustBoundary } from '@wrongstack/core/security';
|
|
3
|
+
import type { ConnectionHealthService, ConnectionsHealthReport, ServiceActionResult } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Auto-heal watchdog for /connections IPC services.
|
|
6
|
+
*
|
|
7
|
+
* There is deliberately no retry loop in the /connections layer: the health
|
|
8
|
+
* report is a passive, read-only probe and the RotateCcw button drives the
|
|
9
|
+
* only restart path (`connections.service_action` → `executeServiceAction`).
|
|
10
|
+
* This module turns that same machinery into an opt-in server-side watchdog:
|
|
11
|
+
* on an interval it collects the health report and, for any restartable
|
|
12
|
+
* service stuck in `error`, re-runs the exact restart used by the button
|
|
13
|
+
* (shutdown → wait-for-dead → respawn → ping verification).
|
|
14
|
+
*
|
|
15
|
+
* Trigger contract — status `error` only. `offline` / `unavailable` are the
|
|
16
|
+
* normal sleeping states of on-demand daemons and must never be restarted;
|
|
17
|
+
* `degraded` means the service is serving (inline fallback, quarantine,
|
|
18
|
+
* damaged rows) and should also never trigger a restart.
|
|
19
|
+
*/
|
|
20
|
+
export declare const AUTO_HEAL_ENV_FLAG = "WRONGSTACK_AUTO_HEAL_SERVICES";
|
|
21
|
+
export declare const AUTO_HEAL_DEFAULT_INTERVAL_MS = 30000;
|
|
22
|
+
export declare const AUTO_HEAL_DEFAULT_COOLDOWN_MS: number;
|
|
23
|
+
export declare const AUTO_HEAL_DEFAULT_MAX_ATTEMPTS = 3;
|
|
24
|
+
export interface AutoHealServiceState {
|
|
25
|
+
lastAttemptAt: number | null;
|
|
26
|
+
consecutiveFailures: number;
|
|
27
|
+
lastSuccess: boolean | null;
|
|
28
|
+
lastMessage: string | null;
|
|
29
|
+
inFlight: boolean;
|
|
30
|
+
escalated: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** UI-facing event emitted at each healer decision point. */
|
|
33
|
+
export type AutoHealStatusPhase = 'restarting' | 'restarted' | 'failed' | 'escalated' | 'refused';
|
|
34
|
+
export interface AutoHealStatusEvent {
|
|
35
|
+
serviceId: ConnectionHealthService['id'] | null;
|
|
36
|
+
phase: AutoHealStatusPhase;
|
|
37
|
+
message: string;
|
|
38
|
+
at: number;
|
|
39
|
+
/** 1-based attempt number within the current failure streak. */
|
|
40
|
+
attempt: number;
|
|
41
|
+
}
|
|
42
|
+
export interface AutoHealSnapshot {
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
running: boolean;
|
|
45
|
+
lastTickAt: number | null;
|
|
46
|
+
services: Record<string, AutoHealServiceState>;
|
|
47
|
+
}
|
|
48
|
+
export interface AutoHealerOptions {
|
|
49
|
+
projectRoot: () => string;
|
|
50
|
+
indexDir: () => string | undefined;
|
|
51
|
+
/** Policy boundary. When absent, auto-heal refuses to act (same rule as the manual WS path). */
|
|
52
|
+
trustBoundary?: TrustBoundary | undefined;
|
|
53
|
+
logger?: Logger | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* UI visibility hook. The wiring layer forwards these to the WebSocket
|
|
56
|
+
* broadcast as `connections.auto_heal_status` so clients can render an
|
|
57
|
+
* "auto-restarting…" state instead of silently flipping statuses.
|
|
58
|
+
*/
|
|
59
|
+
onStatus?: ((event: AutoHealStatusEvent) => void) | undefined;
|
|
60
|
+
/** Health collector override (tests). Defaults to the real per-project collector. */
|
|
61
|
+
collect?: () => Promise<ConnectionsHealthReport>;
|
|
62
|
+
/** Restart executor override (tests). Defaults to the button's `executeServiceAction`. */
|
|
63
|
+
execute?: (serviceId: ConnectionHealthService['id'], action: 'restart', projectRoot: string, indexDir: string | undefined) => Promise<ServiceActionResult>;
|
|
64
|
+
/** Enable override. Defaults to the `WRONGSTACK_AUTO_HEAL_SERVICES` env flag. */
|
|
65
|
+
enabled?: boolean;
|
|
66
|
+
intervalMs?: number;
|
|
67
|
+
cooldownMs?: number;
|
|
68
|
+
maxAttempts?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface AutoHealer {
|
|
71
|
+
start(): void;
|
|
72
|
+
stop(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Idempotent teardown: stops the interval and waits for any in-flight tick
|
|
75
|
+
* (including a restart in progress) to finish. No new restart is started
|
|
76
|
+
* after disposal begins. Safe to call multiple times and when never started.
|
|
77
|
+
*/
|
|
78
|
+
dispose(): Promise<void>;
|
|
79
|
+
tick(): Promise<AutoHealSnapshot>;
|
|
80
|
+
getSnapshot(): AutoHealSnapshot;
|
|
81
|
+
isRunning(): boolean;
|
|
82
|
+
}
|
|
83
|
+
export declare function isAutoHealEnabled(): boolean;
|
|
84
|
+
export declare function createAutoHealer(options: AutoHealerOptions): AutoHealer;
|
|
85
|
+
//# sourceMappingURL=auto-healer.d.ts.map
|
|
@@ -40,6 +40,14 @@ export interface EmbeddedMessageRouterDeps {
|
|
|
40
40
|
};
|
|
41
41
|
currentSessionId: () => string;
|
|
42
42
|
shutdown: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Caller-supplied register hook for long-lived disposables created inside
|
|
45
|
+
* the router (the auto-heal watchdog). The router hands its disposer to the
|
|
46
|
+
* caller once during construction; the caller is responsible for invoking
|
|
47
|
+
* it during its own shutdown — the router itself never invokes it. Mirrors
|
|
48
|
+
* `MessageDispatcherOptions.onDispose` in the standalone dispatcher.
|
|
49
|
+
*/
|
|
50
|
+
onDispose?: ((disposer: () => void | Promise<void>) => void) | undefined;
|
|
43
51
|
providerCtx: EmbeddedProviderContext;
|
|
44
52
|
brainCtx: BrainHandlerContext;
|
|
45
53
|
introspectionCtx: IntrospectionRouteContext;
|
package/dist/server/entry.js
CHANGED
|
@@ -11827,6 +11827,7 @@ ${metrics}` : "",
|
|
|
11827
11827
|
task.labels?.length ? `Labels: ${task.labels.join(", ")}` : "",
|
|
11828
11828
|
"",
|
|
11829
11829
|
"Work the task end-to-end. Use the kanban tool, not direct file edits, to update this task.",
|
|
11830
|
+
"Stay inside this task: its description and success criteria define the boundary. If you notice problems outside this task while working, do not fix them \u2014 note them in lastResult instead.",
|
|
11830
11831
|
...leaseId ? [
|
|
11831
11832
|
"LEASE CONTRACT:",
|
|
11832
11833
|
`- Your leaseId is "${leaseId}". Include expectedLeaseId "${leaseId}" in every mark_assignment and heartbeat_assignment call.`,
|
|
@@ -11835,7 +11836,7 @@ ${metrics}` : "",
|
|
|
11835
11836
|
"On failure the host may call recover_stale; respect its decisions and do not duplicate work."
|
|
11836
11837
|
] : [],
|
|
11837
11838
|
`When you start or finish, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", and assignmentStatus "running", "completed", or "failed"${leaseId ? `, and expectedLeaseId "${leaseId}"` : ""}. Include lastResult or error when you finish. On managed lifecycle boards, mark_assignment(completed) will automatically advance your card from Running to Review \u2014 a separate verification step is required before the card reaches Done.`,
|
|
11838
|
-
"When finished, report what changed, what you verified, and any
|
|
11839
|
+
"When finished, report what changed, what you verified, any remaining blockers, and any issues you noticed outside this task's scope."
|
|
11839
11840
|
].filter(Boolean).join("\n");
|
|
11840
11841
|
}
|
|
11841
11842
|
function findTask(tasks, taskId) {
|
|
@@ -15217,9 +15218,19 @@ async function handleBrainAsk(ctx, ws, question) {
|
|
|
15217
15218
|
risk: "medium",
|
|
15218
15219
|
fallback: "ask_human"
|
|
15219
15220
|
});
|
|
15221
|
+
const answerSessionId = ctx.getSessionId?.();
|
|
15220
15222
|
ctx.send(ws, {
|
|
15221
15223
|
type: "brain.answer",
|
|
15222
|
-
|
|
15224
|
+
// Omit sessionId when there is no session: this is a direct reply to
|
|
15225
|
+
// the asker, not a broadcast, but the client's session gate
|
|
15226
|
+
// (isActiveSessionMessage) is fail-closed on a present-but-empty
|
|
15227
|
+
// sessionId — stamping '' would hide the answer from its own asker
|
|
15228
|
+
// in an embedded host with an unbound agent context.
|
|
15229
|
+
payload: {
|
|
15230
|
+
...answerSessionId ? { sessionId: answerSessionId } : {},
|
|
15231
|
+
question: q,
|
|
15232
|
+
decision
|
|
15233
|
+
}
|
|
15223
15234
|
});
|
|
15224
15235
|
} catch (err) {
|
|
15225
15236
|
sendResult6(ctx, ws, false, `Brain consultation failed: ${toErrorMessage6(err)}`);
|
|
@@ -17303,7 +17314,12 @@ function createProviderHandlers(deps2) {
|
|
|
17303
17314
|
|
|
17304
17315
|
// src/server/session-handlers.ts
|
|
17305
17316
|
import { loadTodosCheckpoint } from "@wrongstack/core/storage";
|
|
17306
|
-
import {
|
|
17317
|
+
import {
|
|
17318
|
+
CONTEXT_WINDOW_MODE_PINNED_META_KEY,
|
|
17319
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
17320
|
+
isContextWindowModeId,
|
|
17321
|
+
resolveContextWindowPolicy
|
|
17322
|
+
} from "@wrongstack/core/types";
|
|
17307
17323
|
import { repairToolUseAdjacency as repairToolUseAdjacency2, sessionScopedPath } from "@wrongstack/core/utils";
|
|
17308
17324
|
|
|
17309
17325
|
// src/protocol/client-conversation.ts
|
|
@@ -17825,6 +17841,7 @@ var SERVER_WORKSPACE_MESSAGE_TYPES = [
|
|
|
17825
17841
|
var SERVER_CONFIGURATION_MESSAGE_TYPES = [
|
|
17826
17842
|
"auth.oauth.status",
|
|
17827
17843
|
"codebase.index.server.shutdown_result",
|
|
17844
|
+
"connections.auto_heal_status",
|
|
17828
17845
|
"connections.health_error",
|
|
17829
17846
|
"connections.health_result",
|
|
17830
17847
|
"connections.service_action_result",
|
|
@@ -18603,8 +18620,8 @@ function createSessionHandlers(ctx) {
|
|
|
18603
18620
|
return;
|
|
18604
18621
|
}
|
|
18605
18622
|
const { id } = parsed.value;
|
|
18606
|
-
let policy = resolveContextWindowPolicy({}, id);
|
|
18607
|
-
if (policy.id !== id) {
|
|
18623
|
+
let policy = resolveContextWindowPolicy({}, id, readSessionWindowTokens(ctx.context));
|
|
18624
|
+
if (!isContextWindowModeId(id) && policy.id !== id) {
|
|
18608
18625
|
const customModes = (await modeStore()).list().filter((m) => m.custom === true);
|
|
18609
18626
|
const custom = customModes.find((m) => m.id === id);
|
|
18610
18627
|
if (!custom) {
|
|
@@ -18615,6 +18632,7 @@ function createSessionHandlers(ctx) {
|
|
|
18615
18632
|
}
|
|
18616
18633
|
ctx.context.meta["contextWindowMode"] = policy.id;
|
|
18617
18634
|
ctx.context.meta["contextWindowPolicy"] = policy;
|
|
18635
|
+
ctx.context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY] = true;
|
|
18618
18636
|
result(ws, true, `Context mode switched to ${policy.id}`);
|
|
18619
18637
|
broadcastToAll({
|
|
18620
18638
|
type: "context.mode.changed",
|
|
@@ -18678,11 +18696,14 @@ function createSessionHandlers(ctx) {
|
|
|
18678
18696
|
}
|
|
18679
18697
|
const { id } = parsed.value;
|
|
18680
18698
|
if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
|
|
18681
|
-
|
|
18682
|
-
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy(
|
|
18699
|
+
const policy = resolveContextWindowPolicy(
|
|
18683
18700
|
{},
|
|
18684
|
-
DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
18701
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
18702
|
+
readSessionWindowTokens(ctx.context)
|
|
18685
18703
|
);
|
|
18704
|
+
ctx.context.meta["contextWindowMode"] = policy.id;
|
|
18705
|
+
ctx.context.meta["contextWindowPolicy"] = policy;
|
|
18706
|
+
delete ctx.context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY];
|
|
18686
18707
|
}
|
|
18687
18708
|
const store = await modeStore();
|
|
18688
18709
|
const operation = store.remove(id);
|
|
@@ -18889,6 +18910,12 @@ function createSessionHandlers(ctx) {
|
|
|
18889
18910
|
}
|
|
18890
18911
|
};
|
|
18891
18912
|
}
|
|
18913
|
+
function readSessionWindowTokens(context) {
|
|
18914
|
+
const meta = context.meta?.["effectiveMaxContext"];
|
|
18915
|
+
if (typeof meta === "number" && Number.isFinite(meta) && meta > 0) return meta;
|
|
18916
|
+
const cap = context.provider?.capabilities?.maxContext;
|
|
18917
|
+
return typeof cap === "number" && Number.isFinite(cap) && cap > 0 ? cap : 0;
|
|
18918
|
+
}
|
|
18892
18919
|
|
|
18893
18920
|
// src/server/agent-roster-handlers.ts
|
|
18894
18921
|
import {
|
|
@@ -19387,6 +19414,259 @@ async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
|
19387
19414
|
return true;
|
|
19388
19415
|
}
|
|
19389
19416
|
|
|
19417
|
+
// src/server/connections/auto-healer.ts
|
|
19418
|
+
var AUTO_HEAL_ENV_FLAG = "WRONGSTACK_AUTO_HEAL_SERVICES";
|
|
19419
|
+
var AUTO_HEAL_DEFAULT_INTERVAL_MS = 3e4;
|
|
19420
|
+
var AUTO_HEAL_DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
19421
|
+
var AUTO_HEAL_DEFAULT_MAX_ATTEMPTS = 3;
|
|
19422
|
+
var RESTARTABLE_SERVICE_IDS = /* @__PURE__ */ new Set([
|
|
19423
|
+
"kanban",
|
|
19424
|
+
"sage",
|
|
19425
|
+
"chronicle",
|
|
19426
|
+
"codebase-index",
|
|
19427
|
+
"mailbox"
|
|
19428
|
+
]);
|
|
19429
|
+
function isAutoHealEnabled() {
|
|
19430
|
+
return process.env[AUTO_HEAL_ENV_FLAG] === "1";
|
|
19431
|
+
}
|
|
19432
|
+
function createAutoHealer(options) {
|
|
19433
|
+
const enabled = options.enabled ?? isAutoHealEnabled();
|
|
19434
|
+
const intervalMs = options.intervalMs ?? AUTO_HEAL_DEFAULT_INTERVAL_MS;
|
|
19435
|
+
const cooldownMs = options.cooldownMs ?? AUTO_HEAL_DEFAULT_COOLDOWN_MS;
|
|
19436
|
+
const maxAttempts = options.maxAttempts ?? AUTO_HEAL_DEFAULT_MAX_ATTEMPTS;
|
|
19437
|
+
const collect = options.collect ?? (() => collectConnectionsHealth({
|
|
19438
|
+
projectRoot: options.projectRoot(),
|
|
19439
|
+
indexDir: options.indexDir(),
|
|
19440
|
+
backend: "standalone"
|
|
19441
|
+
}));
|
|
19442
|
+
const execute = options.execute ?? executeServiceAction;
|
|
19443
|
+
const services = /* @__PURE__ */ new Map();
|
|
19444
|
+
let timer = null;
|
|
19445
|
+
let running = false;
|
|
19446
|
+
let ticking = false;
|
|
19447
|
+
let disposed = false;
|
|
19448
|
+
let inFlightTick = null;
|
|
19449
|
+
let lastTickAt = null;
|
|
19450
|
+
let warnedNoBoundary = false;
|
|
19451
|
+
function stateFor(serviceId) {
|
|
19452
|
+
let state = services.get(serviceId);
|
|
19453
|
+
if (!state) {
|
|
19454
|
+
state = {
|
|
19455
|
+
lastAttemptAt: null,
|
|
19456
|
+
consecutiveFailures: 0,
|
|
19457
|
+
lastSuccess: null,
|
|
19458
|
+
lastMessage: null,
|
|
19459
|
+
inFlight: false,
|
|
19460
|
+
escalated: false
|
|
19461
|
+
};
|
|
19462
|
+
services.set(serviceId, state);
|
|
19463
|
+
}
|
|
19464
|
+
return state;
|
|
19465
|
+
}
|
|
19466
|
+
function snapshot() {
|
|
19467
|
+
return {
|
|
19468
|
+
enabled,
|
|
19469
|
+
running,
|
|
19470
|
+
lastTickAt,
|
|
19471
|
+
services: Object.fromEntries(services)
|
|
19472
|
+
};
|
|
19473
|
+
}
|
|
19474
|
+
function emitStatus(event) {
|
|
19475
|
+
try {
|
|
19476
|
+
options.onStatus?.({ ...event, at: Date.now() });
|
|
19477
|
+
} catch (error2) {
|
|
19478
|
+
options.logger?.warn?.(
|
|
19479
|
+
`[AutoHeal] onStatus hook threw: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
19480
|
+
);
|
|
19481
|
+
}
|
|
19482
|
+
}
|
|
19483
|
+
async function tick() {
|
|
19484
|
+
if (!enabled || disposed) return snapshot();
|
|
19485
|
+
if (!options.trustBoundary) {
|
|
19486
|
+
if (!warnedNoBoundary) {
|
|
19487
|
+
warnedNoBoundary = true;
|
|
19488
|
+
options.logger?.warn?.(
|
|
19489
|
+
"[AutoHeal] Disabled: no policy authority (trust boundary) is configured."
|
|
19490
|
+
);
|
|
19491
|
+
}
|
|
19492
|
+
return snapshot();
|
|
19493
|
+
}
|
|
19494
|
+
if (ticking) return snapshot();
|
|
19495
|
+
ticking = true;
|
|
19496
|
+
try {
|
|
19497
|
+
const report = await collect();
|
|
19498
|
+
const now = Date.now();
|
|
19499
|
+
const projectRoot = options.projectRoot();
|
|
19500
|
+
const indexDir = options.indexDir();
|
|
19501
|
+
for (const service of report.services) {
|
|
19502
|
+
if (disposed) break;
|
|
19503
|
+
const state = stateFor(service.id);
|
|
19504
|
+
if (service.status !== "error") {
|
|
19505
|
+
state.consecutiveFailures = 0;
|
|
19506
|
+
state.escalated = false;
|
|
19507
|
+
continue;
|
|
19508
|
+
}
|
|
19509
|
+
if (!RESTARTABLE_SERVICE_IDS.has(service.id) || service.control === "none") {
|
|
19510
|
+
continue;
|
|
19511
|
+
}
|
|
19512
|
+
if (state.lastAttemptAt !== null && now - state.lastAttemptAt < cooldownMs) continue;
|
|
19513
|
+
if (state.consecutiveFailures >= maxAttempts) {
|
|
19514
|
+
state.escalated = true;
|
|
19515
|
+
options.logger?.warn?.(
|
|
19516
|
+
`[AutoHeal] ${service.id} left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage ?? "unknown"}`
|
|
19517
|
+
);
|
|
19518
|
+
continue;
|
|
19519
|
+
}
|
|
19520
|
+
if (state.inFlight) continue;
|
|
19521
|
+
const authorization = await authorizeWebUIAction(
|
|
19522
|
+
options.trustBoundary,
|
|
19523
|
+
{
|
|
19524
|
+
capability: "connections.service.restart",
|
|
19525
|
+
subject: { kind: "process", id: `${service.id}@${projectRoot}` },
|
|
19526
|
+
risk: "elevated",
|
|
19527
|
+
cwd: projectRoot,
|
|
19528
|
+
metadata: { transport: "auto-heal", serviceId: service.id, action: "restart" }
|
|
19529
|
+
},
|
|
19530
|
+
options.logger
|
|
19531
|
+
);
|
|
19532
|
+
if (disposed) break;
|
|
19533
|
+
if (!authorization.allowed) {
|
|
19534
|
+
state.lastAttemptAt = now;
|
|
19535
|
+
state.lastMessage = `refused by policy: ${authorization.reason}`;
|
|
19536
|
+
emitStatus({
|
|
19537
|
+
serviceId: service.id,
|
|
19538
|
+
phase: "refused",
|
|
19539
|
+
message: `refused by policy: ${authorization.reason}`,
|
|
19540
|
+
attempt: state.consecutiveFailures + 1
|
|
19541
|
+
});
|
|
19542
|
+
options.logger?.warn?.(
|
|
19543
|
+
`[AutoHeal] ${service.id} restart refused by policy: ${authorization.reason}`
|
|
19544
|
+
);
|
|
19545
|
+
continue;
|
|
19546
|
+
}
|
|
19547
|
+
state.inFlight = true;
|
|
19548
|
+
const attempt = state.consecutiveFailures + 1;
|
|
19549
|
+
emitStatus({
|
|
19550
|
+
serviceId: service.id,
|
|
19551
|
+
phase: "restarting",
|
|
19552
|
+
message: `Auto-restarting ${service.id}`,
|
|
19553
|
+
attempt
|
|
19554
|
+
});
|
|
19555
|
+
try {
|
|
19556
|
+
const result = await execute(service.id, "restart", projectRoot, indexDir);
|
|
19557
|
+
state.consecutiveFailures = result.success ? 0 : state.consecutiveFailures + 1;
|
|
19558
|
+
state.lastSuccess = result.success;
|
|
19559
|
+
state.lastMessage = result.message;
|
|
19560
|
+
emitStatus({
|
|
19561
|
+
serviceId: service.id,
|
|
19562
|
+
phase: result.success ? "restarted" : "failed",
|
|
19563
|
+
message: result.message,
|
|
19564
|
+
attempt
|
|
19565
|
+
});
|
|
19566
|
+
if (!result.success && state.consecutiveFailures >= maxAttempts) {
|
|
19567
|
+
state.escalated = true;
|
|
19568
|
+
emitStatus({
|
|
19569
|
+
serviceId: service.id,
|
|
19570
|
+
phase: "escalated",
|
|
19571
|
+
message: `left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${result.message}`,
|
|
19572
|
+
attempt
|
|
19573
|
+
});
|
|
19574
|
+
options.logger?.warn?.(
|
|
19575
|
+
`[AutoHeal] ${service.id} escalated after ${state.consecutiveFailures} failed auto-restart(s): ${result.message}`
|
|
19576
|
+
);
|
|
19577
|
+
}
|
|
19578
|
+
options.logger?.[result.success ? "info" : "warn"]?.(
|
|
19579
|
+
`[AutoHeal] ${service.id} auto-restart ${result.success ? "succeeded" : "failed"}: ${result.message}`
|
|
19580
|
+
);
|
|
19581
|
+
} catch (error2) {
|
|
19582
|
+
state.consecutiveFailures += 1;
|
|
19583
|
+
state.lastSuccess = false;
|
|
19584
|
+
state.lastMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
19585
|
+
emitStatus({
|
|
19586
|
+
serviceId: service.id,
|
|
19587
|
+
phase: "failed",
|
|
19588
|
+
message: state.lastMessage,
|
|
19589
|
+
attempt
|
|
19590
|
+
});
|
|
19591
|
+
if (state.consecutiveFailures >= maxAttempts) {
|
|
19592
|
+
state.escalated = true;
|
|
19593
|
+
emitStatus({
|
|
19594
|
+
serviceId: service.id,
|
|
19595
|
+
phase: "escalated",
|
|
19596
|
+
message: `left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage}`,
|
|
19597
|
+
attempt
|
|
19598
|
+
});
|
|
19599
|
+
options.logger?.warn?.(
|
|
19600
|
+
`[AutoHeal] ${service.id} escalated after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage}`
|
|
19601
|
+
);
|
|
19602
|
+
}
|
|
19603
|
+
options.logger?.warn?.(
|
|
19604
|
+
`[AutoHeal] ${service.id} auto-restart threw: ${state.lastMessage}`
|
|
19605
|
+
);
|
|
19606
|
+
} finally {
|
|
19607
|
+
state.lastAttemptAt = Date.now();
|
|
19608
|
+
state.inFlight = false;
|
|
19609
|
+
}
|
|
19610
|
+
}
|
|
19611
|
+
lastTickAt = Date.now();
|
|
19612
|
+
} catch (error2) {
|
|
19613
|
+
options.logger?.warn?.(
|
|
19614
|
+
`[AutoHeal] health collect failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
19615
|
+
);
|
|
19616
|
+
} finally {
|
|
19617
|
+
ticking = false;
|
|
19618
|
+
}
|
|
19619
|
+
return snapshot();
|
|
19620
|
+
}
|
|
19621
|
+
function runTick() {
|
|
19622
|
+
if (!enabled || disposed || running === false || ticking) return;
|
|
19623
|
+
const pending = tick();
|
|
19624
|
+
const tracked = pending.then(
|
|
19625
|
+
() => void 0,
|
|
19626
|
+
() => void 0
|
|
19627
|
+
);
|
|
19628
|
+
inFlightTick = tracked;
|
|
19629
|
+
void tracked.finally(() => {
|
|
19630
|
+
if (inFlightTick === tracked) inFlightTick = null;
|
|
19631
|
+
});
|
|
19632
|
+
}
|
|
19633
|
+
function stopInternal() {
|
|
19634
|
+
if (timer) {
|
|
19635
|
+
clearInterval(timer);
|
|
19636
|
+
timer = null;
|
|
19637
|
+
}
|
|
19638
|
+
running = false;
|
|
19639
|
+
}
|
|
19640
|
+
return {
|
|
19641
|
+
start() {
|
|
19642
|
+
if (!enabled || running || disposed) return;
|
|
19643
|
+
running = true;
|
|
19644
|
+
runTick();
|
|
19645
|
+
timer = setInterval(runTick, intervalMs);
|
|
19646
|
+
timer.unref?.();
|
|
19647
|
+
},
|
|
19648
|
+
stop: stopInternal,
|
|
19649
|
+
async dispose() {
|
|
19650
|
+
stopInternal();
|
|
19651
|
+
disposed = true;
|
|
19652
|
+
const pending = inFlightTick;
|
|
19653
|
+
if (pending) {
|
|
19654
|
+
await Promise.race([
|
|
19655
|
+
pending,
|
|
19656
|
+
new Promise((resolve19) => {
|
|
19657
|
+
const t = setTimeout(resolve19, 3e4);
|
|
19658
|
+
t.unref?.();
|
|
19659
|
+
})
|
|
19660
|
+
]);
|
|
19661
|
+
}
|
|
19662
|
+
disposed = true;
|
|
19663
|
+
},
|
|
19664
|
+
tick,
|
|
19665
|
+
getSnapshot: snapshot,
|
|
19666
|
+
isRunning: () => running
|
|
19667
|
+
};
|
|
19668
|
+
}
|
|
19669
|
+
|
|
19390
19670
|
// src/server/fallback-choice.ts
|
|
19391
19671
|
function emitFallbackChoice(events, msg) {
|
|
19392
19672
|
const parsed = validateModelFallbackChoicePayload(msg.payload);
|
|
@@ -22572,6 +22852,7 @@ import {
|
|
|
22572
22852
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
22573
22853
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
22574
22854
|
import {
|
|
22855
|
+
CONTEXT_WINDOW_MODE_PINNED_META_KEY as CONTEXT_WINDOW_MODE_PINNED_META_KEY2,
|
|
22575
22856
|
DEFAULT_TOOLS_CONFIG,
|
|
22576
22857
|
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
22577
22858
|
} from "@wrongstack/core/types";
|
|
@@ -23492,22 +23773,26 @@ async function createAgentServices(input) {
|
|
|
23492
23773
|
summarizerModel: config.context?.summarizerModel,
|
|
23493
23774
|
llmSelector: config.context?.llmSelector
|
|
23494
23775
|
});
|
|
23495
|
-
|
|
23776
|
+
let effectiveMaxContext = 0;
|
|
23777
|
+
try {
|
|
23778
|
+
const m = await resolveProviderModelMetadata(
|
|
23779
|
+
modelsRegistry,
|
|
23780
|
+
config.provider,
|
|
23781
|
+
context.model,
|
|
23782
|
+
config.providers?.[config.provider]
|
|
23783
|
+
);
|
|
23784
|
+
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
23785
|
+
} catch {
|
|
23786
|
+
}
|
|
23787
|
+
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
23788
|
+
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
23789
|
+
const initialContextPolicy = resolveContextWindowPolicy2(
|
|
23790
|
+
config.context,
|
|
23791
|
+
void 0,
|
|
23792
|
+
effectiveMaxContext
|
|
23793
|
+
);
|
|
23496
23794
|
let autoCompactor;
|
|
23497
23795
|
if (config.context?.autoCompact !== false) {
|
|
23498
|
-
let effectiveMaxContext = 0;
|
|
23499
|
-
try {
|
|
23500
|
-
const m = await resolveProviderModelMetadata(
|
|
23501
|
-
modelsRegistry,
|
|
23502
|
-
config.provider,
|
|
23503
|
-
context.model,
|
|
23504
|
-
config.providers?.[config.provider]
|
|
23505
|
-
);
|
|
23506
|
-
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
23507
|
-
} catch {
|
|
23508
|
-
}
|
|
23509
|
-
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
23510
|
-
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
23511
23796
|
autoCompactor = new AutoCompactionMiddlewareCtor(
|
|
23512
23797
|
compactor,
|
|
23513
23798
|
effectiveMaxContext,
|
|
@@ -23562,6 +23847,15 @@ async function createAgentServices(input) {
|
|
|
23562
23847
|
context.meta["effectiveMaxContext"] = newMaxContext;
|
|
23563
23848
|
autoCompactor?.setMaxContext(newMaxContext);
|
|
23564
23849
|
autoCompactor?.setEnabled(config.context?.autoCompact !== false);
|
|
23850
|
+
if (context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY2] !== true) {
|
|
23851
|
+
const policy = resolveContextWindowPolicy2(
|
|
23852
|
+
currentConfig.context ?? {},
|
|
23853
|
+
void 0,
|
|
23854
|
+
newMaxContext
|
|
23855
|
+
);
|
|
23856
|
+
context.meta["contextWindowMode"] = policy.id;
|
|
23857
|
+
context.meta["contextWindowPolicy"] = policy;
|
|
23858
|
+
}
|
|
23565
23859
|
} else {
|
|
23566
23860
|
delete context.meta["effectiveMaxContext"];
|
|
23567
23861
|
autoCompactor?.setEnabled(false);
|
|
@@ -24193,8 +24487,22 @@ function createMessageDispatcher(opts) {
|
|
|
24193
24487
|
broadcast: (message) => broadcast(state.getClients(), message),
|
|
24194
24488
|
log: (message) => deps2.logger.warn?.(`[KanbanSupervisor] ${message}`)
|
|
24195
24489
|
});
|
|
24490
|
+
const autoHealer = createAutoHealer({
|
|
24491
|
+
projectRoot: () => state.getProjectRoot(),
|
|
24492
|
+
indexDir: () => typeof deps2.context.meta["codebaseIndexDir"] === "string" ? deps2.context.meta["codebaseIndexDir"] : void 0,
|
|
24493
|
+
trustBoundary: deps2.trustBoundary,
|
|
24494
|
+
logger: deps2.logger,
|
|
24495
|
+
onStatus: (event) => broadcast(state.getClients(), {
|
|
24496
|
+
type: "connections.auto_heal_status",
|
|
24497
|
+
payload: event
|
|
24498
|
+
})
|
|
24499
|
+
});
|
|
24500
|
+
autoHealer.start();
|
|
24196
24501
|
if (opts.onDispose) {
|
|
24197
|
-
const dispose = () =>
|
|
24502
|
+
const dispose = async () => {
|
|
24503
|
+
kanbanSupervisor.dispose();
|
|
24504
|
+
await autoHealer.dispose();
|
|
24505
|
+
};
|
|
24198
24506
|
opts.onDispose(dispose);
|
|
24199
24507
|
}
|
|
24200
24508
|
const kanbanContext = () => ({
|
|
@@ -25058,7 +25366,11 @@ async function createPreContextServices(input) {
|
|
|
25058
25366
|
model: config.model
|
|
25059
25367
|
});
|
|
25060
25368
|
context.meta["promptOnlineAgents"] = onlineAgents;
|
|
25061
|
-
const initialContextPolicy = resolveContextWindowPolicy3(
|
|
25369
|
+
const initialContextPolicy = resolveContextWindowPolicy3(
|
|
25370
|
+
config.context,
|
|
25371
|
+
void 0,
|
|
25372
|
+
provider.capabilities?.maxContext
|
|
25373
|
+
);
|
|
25062
25374
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
25063
25375
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
25064
25376
|
context.state.setMeta(
|
|
@@ -25903,7 +26215,7 @@ function setupWebuiShutdown(options) {
|
|
|
25903
26215
|
onPreShutdown: async () => {
|
|
25904
26216
|
await options.stopEmptySessionCleanup.dispose();
|
|
25905
26217
|
const disposeKanban = options.getKanbanSupervisorDispose();
|
|
25906
|
-
disposeKanban?.();
|
|
26218
|
+
await disposeKanban?.();
|
|
25907
26219
|
},
|
|
25908
26220
|
onShutdown: async () => {
|
|
25909
26221
|
unregister();
|
|
@@ -12,7 +12,7 @@ export declare function setupWebuiShutdown(options: {
|
|
|
12
12
|
stopEmptySessionCleanup: {
|
|
13
13
|
dispose: () => Promise<void>;
|
|
14
14
|
};
|
|
15
|
-
getKanbanSupervisorDispose: () => (() => void) | null;
|
|
15
|
+
getKanbanSupervisorDispose: () => (() => void | Promise<void>) | null;
|
|
16
16
|
todosCheckpoint: {
|
|
17
17
|
detach: () => Promise<void>;
|
|
18
18
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/webui-server",
|
|
3
|
-
"version": "0.308.
|
|
3
|
+
"version": "0.308.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack WebUI HTTP/WebSocket server module — extracted from @wrongstack/webui in PR #243/244 to remove the CLI -> @wrongstack/webui/server cross-package edge (audit §3.1.1). Pure backend: HTTP routes, WebSocket handlers, MCP tool wrappers, HTML serving. The web frontend lives in @wrongstack/webui; this package is the standalone server it can run on.",
|
|
6
6
|
"keywords": [
|
|
@@ -40,17 +40,17 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"ws": "^8.21.3",
|
|
43
|
-
"@wrongstack/
|
|
44
|
-
"@wrongstack/
|
|
45
|
-
"@wrongstack/
|
|
46
|
-
"@wrongstack/
|
|
47
|
-
"@wrongstack/
|
|
48
|
-
"@wrongstack/
|
|
49
|
-
"@wrongstack/
|
|
50
|
-
"@wrongstack/
|
|
51
|
-
"@wrongstack/
|
|
52
|
-
"@wrongstack/tools": "0.308.
|
|
53
|
-
"@wrongstack/
|
|
43
|
+
"@wrongstack/requirement-intake": "0.308.4",
|
|
44
|
+
"@wrongstack/core": "0.308.4",
|
|
45
|
+
"@wrongstack/mcp": "0.308.4",
|
|
46
|
+
"@wrongstack/kanban": "0.308.4",
|
|
47
|
+
"@wrongstack/techstack": "0.308.4",
|
|
48
|
+
"@wrongstack/sage": "0.308.4",
|
|
49
|
+
"@wrongstack/sdd": "0.308.4",
|
|
50
|
+
"@wrongstack/runtime": "0.308.4",
|
|
51
|
+
"@wrongstack/vector-memory": "0.308.4",
|
|
52
|
+
"@wrongstack/tools": "0.308.4",
|
|
53
|
+
"@wrongstack/providers": "0.308.4"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/node": "^26.2.0",
|