om-memory-system 3.6.0-next.39 → 3.6.0-next.42

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.
@@ -152,6 +152,8 @@ export class WebServer {
152
152
  onTakeoverCallback = null;
153
153
  onPortsExhaustedCallback = null;
154
154
  onStepAsideCallback = null;
155
+ onPowerActionCallback = null;
156
+ powerActionRunning = false;
155
157
  stepAsideTimer = null;
156
158
  holdOffTimer = null;
157
159
  portsExhaustedNotified = false;
@@ -211,6 +213,42 @@ export class WebServer {
211
213
  setOnStepAside(callback) {
212
214
  this.onStepAsideCallback = callback;
213
215
  }
216
+ /** Standalone web apps register this to stop or restart from the page. */
217
+ setOnPowerAction(callback) {
218
+ this.onPowerActionCallback = callback;
219
+ }
220
+ handlePowerAction(action, remoteAddress) {
221
+ const record = (outcome) => log("Web server power request", { action, outcome, ownVersion: packageVersion() });
222
+ if (!isLoopbackAddress(remoteAddress)) {
223
+ record("refused_not_loopback");
224
+ return this.jsonResponse({ success: false, error: "Loopback caller required" }, 403);
225
+ }
226
+ const callback = this.onPowerActionCallback;
227
+ if (!callback) {
228
+ record("unsupported");
229
+ return this.jsonResponse({ success: false, error: "Not supported by this web app" }, 409);
230
+ }
231
+ // A repeated request must not start a second stop or restart while one runs.
232
+ if (this.powerActionRunning) {
233
+ record("already_running");
234
+ return this.jsonResponse({ success: true }, 202);
235
+ }
236
+ this.powerActionRunning = true;
237
+ record(action === "stop" ? "stopping" : "restarting");
238
+ // Let the 202 reply reach the caller before the web app goes down.
239
+ setTimeout(async () => {
240
+ try {
241
+ await callback(action);
242
+ }
243
+ catch (error) {
244
+ log("Power action callback error", { action, error: String(error) });
245
+ }
246
+ finally {
247
+ this.powerActionRunning = false;
248
+ }
249
+ }, STEP_ASIDE_REPLY_GRACE_MS).unref();
250
+ return this.jsonResponse({ success: true }, 202);
251
+ }
214
252
  scheduleStepAside() {
215
253
  if (this.stepAsideTimer)
216
254
  return;
@@ -281,6 +319,10 @@ export class WebServer {
281
319
  fetch: this.handleRequest.bind(this),
282
320
  });
283
321
  this.isOwner = true;
322
+ // A restart copy is named in the start lock until it owns the port.
323
+ void import("./web-ensure.js")
324
+ .then(({ removeStartLockFor }) => removeStartLockFor(process.pid))
325
+ .catch(() => { });
284
326
  // This process serves the Claude Code hooks, so it also retries their failed captures.
285
327
  void import("../importer/claude-hook-api.js")
286
328
  .then(({ startClaudeCodeWorker }) => startClaudeCodeWorker())
@@ -477,6 +519,13 @@ export class WebServer {
477
519
  callerVersion: "",
478
520
  });
479
521
  }
522
+ if (method === "POST" && (path === "/api/web/stop" || path === "/api/web/restart")) {
523
+ log("Web server power request", {
524
+ action: path === "/api/web/stop" ? "stop" : "restart",
525
+ outcome: "refused_auth",
526
+ ownVersion: packageVersion(),
527
+ });
528
+ }
480
529
  return (configuredTokenFailure ??
481
530
  this.jsonResponse({ success: false, error: "Unauthorized" }, 401));
482
531
  }
@@ -542,6 +591,15 @@ export class WebServer {
542
591
  ? controls.resume(host, cwd)
543
592
  : controls.runNow(host, cwd));
544
593
  }
594
+ if (path === "/api/web/status" && method === "GET") {
595
+ return this.jsonResponse({
596
+ version: packageVersion(),
597
+ canControl: isLoopbackAddress(remoteAddress) && this.onPowerActionCallback !== null,
598
+ });
599
+ }
600
+ if ((path === "/api/web/stop" || path === "/api/web/restart") && method === "POST") {
601
+ return this.handlePowerAction(path === "/api/web/stop" ? "stop" : "restart", remoteAddress);
602
+ }
545
603
  if (path === "/api/web/step-aside" && method === "POST") {
546
604
  const body = await req.json().catch(() => null);
547
605
  return this.handleStepAside(remoteAddress, body);