@songsid/agend 2.0.11-beta.3 → 2.0.11-beta.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.
@@ -66,6 +66,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
66
66
  private mirrorTimer;
67
67
  private sseClients;
68
68
  private webToken;
69
+ private viewToken;
69
70
  constructor(dataDir: string);
70
71
  lastActivityMs(name: string): number;
71
72
  private getInstanceIdle;
@@ -33,6 +33,7 @@ import { TopicArchiver } from "./topic-archiver.js";
33
33
  import { StatuslineWatcher } from "./statusline-watcher.js";
34
34
  import { outboundHandlers } from "./outbound-handlers.js";
35
35
  import { handleWebRequest, broadcastSseEvent } from "./web-api.js";
36
+ import { handleViewRequest, isViewPath } from "./view-api.js";
36
37
  import { handleAgentRequest } from "./agent-endpoint.js";
37
38
  import { ClassicChannelManager, classicInstanceName } from "./classic-channel-manager.js";
38
39
  import { getTmuxSession } from "./config.js";
@@ -115,6 +116,7 @@ export class FleetManager {
115
116
  // Web UI: SSE clients + auth token
116
117
  sseClients = new Set();
117
118
  webToken = null;
119
+ viewToken = null;
118
120
  constructor(dataDir) {
119
121
  this.dataDir = dataDir;
120
122
  this.lifecycle = new InstanceLifecycle(this);
@@ -3955,6 +3957,15 @@ When users create specialized instances, suggest these configurations:
3955
3957
  catch {
3956
3958
  // best-effort
3957
3959
  }
3960
+ // Separate read-only token for the /view page: grants terminal-view + profile
3961
+ // read, but never write (POSTs still require the full web token).
3962
+ this.viewToken = randomBytes(24).toString("hex");
3963
+ const viewTokenPath = join(this.dataDir, "view.token");
3964
+ writeFileSync(viewTokenPath, this.viewToken, { mode: 0o600 });
3965
+ try {
3966
+ chmodSync(viewTokenPath, 0o600);
3967
+ }
3968
+ catch { /* best-effort */ }
3958
3969
  this.healthServer = createServer((req, res) => {
3959
3970
  res.setHeader("Content-Type", "application/json");
3960
3971
  // Public health probe — no auth required.
@@ -3964,6 +3975,10 @@ When users create specialized instances, suggest these configurations:
3964
3975
  else if (req.method === "POST" && req.url === "/agent") {
3965
3976
  // /agent handles its own instance-level auth via X-Agend-Instance-Token
3966
3977
  }
3978
+ else if (isViewPath(new URL(req.url ?? "/", `http://localhost:${port}`).pathname)) {
3979
+ // /view routes accept the read-only view.token (or web.token) and do
3980
+ // their own per-method auth in view-api.ts — skip the web-token gate.
3981
+ }
3967
3982
  else {
3968
3983
  // All other endpoints require a valid token (query ?token= or X-Agend-Token header).
3969
3984
  // /ui/* will also re-check in web-api.ts, which is harmless.
@@ -4147,6 +4162,8 @@ When users create specialized instances, suggest these configurations:
4147
4162
  }
4148
4163
  // ── Web UI endpoints (delegated to web-api.ts) ─────
4149
4164
  const url = new URL(req.url ?? "/", `http://localhost:${port}`);
4165
+ if (handleViewRequest(req, res, url, this))
4166
+ return;
4150
4167
  if (handleWebRequest(req, res, url, this))
4151
4168
  return;
4152
4169
  res.writeHead(404);
@@ -4188,6 +4205,7 @@ When users create specialized instances, suggest these configurations:
4188
4205
  this.logger.info({ port }, "Health endpoint listening");
4189
4206
  });
4190
4207
  this.logger.info({ url: `http://localhost:${port}/ui?token=${this.webToken}` }, "Web UI available");
4208
+ this.logger.info({ url: `http://localhost:${port}/view?token=${this.viewToken}` }, "Web View available");
4191
4209
  }
4192
4210
  getUiStatus() {
4193
4211
  const instances = Object.keys(this.fleetConfig?.instances ?? {}).map(name => {