@junctionpanel/server 0.1.56 → 0.1.58

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.
@@ -109,6 +109,13 @@ function normalizeAgentRunOptions(runOptions) {
109
109
  ...(runOptions.extra ? { extra: runOptions.extra } : {}),
110
110
  };
111
111
  }
112
+ function isLoopbackAddress(remoteAddress) {
113
+ if (!remoteAddress) {
114
+ return false;
115
+ }
116
+ const normalized = remoteAddress.trim().toLowerCase();
117
+ return normalized === '127.0.0.1' || normalized === '::1' || normalized === '::ffff:127.0.0.1';
118
+ }
112
119
  /**
113
120
  * Session represents a single connected client session.
114
121
  * It owns all state management, orchestration logic, and message processing.
@@ -135,7 +142,7 @@ export class Session {
135
142
  this.checkoutDiffSubscriptions = new Map();
136
143
  this.checkoutDiffTargets = new Map();
137
144
  this.workspaceGitOperationStates = sharedWorkspaceGitOperationStates;
138
- const { clientId, userId, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, junctionHome, agentManager, agentStorage, createAgentMcpTransport, terminalManager, agentProviderRuntimeSettings, } = options;
145
+ const { clientId, userId, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, junctionHome, agentManager, agentStorage, createAgentMcpTransport, terminalManager, agentProviderRuntimeSettings, connectionContext, } = options;
139
146
  this.clientId = clientId;
140
147
  this.userId = userId;
141
148
  this.sessionId = uuidv4();
@@ -148,6 +155,14 @@ export class Session {
148
155
  this.agentStorage = agentStorage;
149
156
  this.createAgentMcpTransport = createAgentMcpTransport;
150
157
  this.terminalManager = terminalManager;
158
+ this.connectionContext = {
159
+ clientType: connectionContext?.clientType ?? 'browser',
160
+ transport: connectionContext?.transport ?? 'direct',
161
+ host: connectionContext?.host ?? null,
162
+ origin: connectionContext?.origin ?? null,
163
+ userAgent: connectionContext?.userAgent ?? null,
164
+ remoteAddress: connectionContext?.remoteAddress ?? null,
165
+ };
151
166
  if (this.terminalManager) {
152
167
  this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) => this.handleTerminalsChanged(event));
153
168
  }
@@ -157,6 +172,9 @@ export class Session {
157
172
  module: 'session',
158
173
  clientId: this.clientId,
159
174
  sessionId: this.sessionId,
175
+ transport: this.connectionContext.transport,
176
+ clientType: this.connectionContext.clientType,
177
+ remoteAddress: this.connectionContext.remoteAddress,
160
178
  });
161
179
  this.providerRegistry = buildProviderRegistry(this.sessionLogger, {
162
180
  runtimeSettings: this.agentProviderRuntimeSettings,
@@ -1119,6 +1137,13 @@ export class Session {
1119
1137
  this.sessionLogger.warn({ streamId: frame.streamId, messageType: frame.messageType }, 'Unhandled terminal binary frame');
1120
1138
  }
1121
1139
  async handleRestartServerRequest(requestId, reason) {
1140
+ this.assertLifecycleControlAllowed('restart', requestId, reason);
1141
+ this.requestServerRestart(requestId, reason, {
1142
+ logLevel: 'warn',
1143
+ logMessage: 'Restart requested via websocket',
1144
+ });
1145
+ }
1146
+ requestServerRestart(requestId, reason, options) {
1122
1147
  const payload = {
1123
1148
  status: 'restart_requested',
1124
1149
  clientId: this.clientId,
@@ -1127,7 +1152,9 @@ export class Session {
1127
1152
  payload.reason = reason;
1128
1153
  }
1129
1154
  payload.requestId = requestId;
1130
- this.sessionLogger.warn({ reason }, 'Restart requested via websocket');
1155
+ const logLevel = options?.logLevel ?? 'warn';
1156
+ const logMessage = options?.logMessage ?? 'Restart requested';
1157
+ this.sessionLogger[logLevel]({ reason, requestId }, logMessage);
1131
1158
  this.emit({
1132
1159
  type: 'status',
1133
1160
  payload,
@@ -1140,6 +1167,7 @@ export class Session {
1140
1167
  });
1141
1168
  }
1142
1169
  async handleShutdownServerRequest(requestId) {
1170
+ this.assertLifecycleControlAllowed('shutdown', requestId);
1143
1171
  this.sessionLogger.warn('Shutdown requested via websocket');
1144
1172
  this.emit({
1145
1173
  type: 'status',
@@ -1155,6 +1183,25 @@ export class Session {
1155
1183
  requestId,
1156
1184
  });
1157
1185
  }
1186
+ assertLifecycleControlAllowed(action, requestId, reason) {
1187
+ const allowed = this.connectionContext.clientType === 'cli'
1188
+ && this.connectionContext.transport === 'direct'
1189
+ && isLoopbackAddress(this.connectionContext.remoteAddress);
1190
+ if (allowed) {
1191
+ return;
1192
+ }
1193
+ this.sessionLogger.warn({
1194
+ action,
1195
+ requestId,
1196
+ reason: reason ?? null,
1197
+ clientType: this.connectionContext.clientType,
1198
+ transport: this.connectionContext.transport,
1199
+ remoteAddress: this.connectionContext.remoteAddress,
1200
+ host: this.connectionContext.host,
1201
+ origin: this.connectionContext.origin,
1202
+ }, 'Rejected lifecycle request from unauthorized client');
1203
+ throw new SessionRequestError('lifecycle_forbidden', `Only direct localhost CLI sessions can ${action} the daemon.`);
1204
+ }
1158
1205
  emitLifecycleIntent(intent) {
1159
1206
  if (!this.onLifecycleIntent) {
1160
1207
  return;
@@ -2119,7 +2166,10 @@ export class Session {
2119
2166
  requestId: msg.requestId,
2120
2167
  },
2121
2168
  });
2122
- await this.handleRestartServerRequest(msg.requestId, 'settings_update');
2169
+ this.requestServerRestart(msg.requestId, 'settings_update', {
2170
+ logLevel: 'info',
2171
+ logMessage: 'Restart requested after daemon provider settings update',
2172
+ });
2123
2173
  }
2124
2174
  catch (error) {
2125
2175
  this.sessionLogger.error({ err: error, provider: msg.provider }, 'Failed to update daemon provider settings');
@@ -3493,7 +3543,6 @@ export class Session {
3493
3543
  || includePr
3494
3544
  || (previousHasLoadedPullRequest && !gitIdentityChanged);
3495
3545
  target.latestPayloadHasFullGitData = !snapshot.git.isGit || fullGit;
3496
- this.updateWorkspaceStatusPrPolling(target);
3497
3546
  const fingerprint = this.workspaceStatusSnapshotFingerprint(snapshot);
3498
3547
  if (fingerprint !== target.latestFingerprint) {
3499
3548
  target.latestFingerprint = fingerprint;