@hachej/boring-workspace 0.1.81 → 0.1.83

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.
@@ -2903,15 +2903,23 @@ function resolvePaneStatusWorkspaceId(request) {
2903
2903
  function paneRenderStatusRoutes(app, opts = {}, done) {
2904
2904
  const store = opts.store ?? createPaneRenderStatusStore();
2905
2905
  const validateReport = createBodyValidator(reportBodySchema);
2906
- const getWorkspaceId = async (request) => {
2907
- return await opts.getWorkspaceId?.(request) ?? resolvePaneStatusWorkspaceId(request);
2906
+ const getWorkspaceId = async (request, presentedWorkspaceId) => {
2907
+ return await opts.getWorkspaceId?.(request, presentedWorkspaceId) ?? resolvePaneStatusWorkspaceId(request);
2908
+ };
2909
+ const admitMalformedScopedWorkspaceId = async (request) => {
2910
+ const scoped = request.requestScope !== void 0;
2911
+ const body = request.body;
2912
+ if (!scoped || !body || !Object.prototype.hasOwnProperty.call(body, "workspaceId")) return;
2913
+ if (body.workspaceId !== void 0 && typeof body.workspaceId !== "string") {
2914
+ await getWorkspaceId(request, body.workspaceId);
2915
+ }
2908
2916
  };
2909
2917
  app.put(
2910
2918
  "/api/v1/ui/panels/status",
2911
- { preHandler: validateReport },
2919
+ { preHandler: [admitMalformedScopedWorkspaceId, validateReport] },
2912
2920
  async (request, reply) => {
2913
2921
  const body = request.body;
2914
- const workspaceId = await getWorkspaceId(request) ?? body.workspaceId;
2922
+ const workspaceId = await getWorkspaceId(request, body.workspaceId) ?? body.workspaceId;
2915
2923
  const status = store.report({ ...body, workspaceId });
2916
2924
  return reply.code(200).send({ ok: true, status });
2917
2925
  }
@@ -2967,7 +2975,7 @@ function createBodyValidator2(schema) {
2967
2975
  function uiRoutes(app, opts, done) {
2968
2976
  const fallbackBridge = opts.bridge;
2969
2977
  const paneStatusStore = opts.paneStatusStore ?? createPaneRenderStatusStore();
2970
- const getPaneWorkspaceId = async (request) => await opts.getWorkspaceId?.(request) ?? resolvePaneStatusWorkspaceId(request);
2978
+ const getPaneWorkspaceId = async (request, presentedWorkspaceId) => await opts.getWorkspaceId?.(request, presentedWorkspaceId) ?? resolvePaneStatusWorkspaceId(request);
2971
2979
  const touchUi = async (request) => {
2972
2980
  paneStatusStore.touchUi(await getPaneWorkspaceId(request));
2973
2981
  };
@@ -3322,20 +3330,28 @@ function mintWorkspaceBridgeRuntimeRefreshToken(options) {
3322
3330
  });
3323
3331
  }
3324
3332
  function verifyWorkspaceBridgeRuntimeToken(token, options) {
3333
+ return authorizeWorkspaceBridgeRuntimeToken(
3334
+ verifyWorkspaceBridgeRuntimeTokenClaims(token, options),
3335
+ options.requiredCapabilities
3336
+ );
3337
+ }
3338
+ function verifyWorkspaceBridgeRuntimeTokenClaims(token, options) {
3325
3339
  assertUsableSecret(options.secret);
3326
3340
  const claims = parseAndVerifyToken(token, options.secret);
3327
3341
  const now = Math.floor((options.nowMs ?? Date.now()) / 1e3);
3328
3342
  ensureLiveTokenClaims(claims, now, WORKSPACE_BRIDGE_TOKEN_AUDIENCE, "Runtime bridge token");
3329
- const missingCapability = (options.requiredCapabilities ?? []).find(
3330
- (capability) => !claims.capabilities.includes(capability)
3343
+ return { claims };
3344
+ }
3345
+ function authorizeWorkspaceBridgeRuntimeToken(verified, requiredCapabilities = []) {
3346
+ const missingCapability = requiredCapabilities.find(
3347
+ (capability) => !verified.claims.capabilities.includes(capability)
3331
3348
  );
3332
3349
  if (missingCapability) {
3333
3350
  throw bridgeTokenError("BRIDGE_CAPABILITY_DENIED" /* CapabilityDenied */, "Runtime bridge token is missing a required capability");
3334
3351
  }
3335
- const runtimeClaims = claims;
3336
3352
  return {
3337
- claims: runtimeClaims,
3338
- authContext: runtimeClaimsToBridgeAuthContext(runtimeClaims)
3353
+ claims: verified.claims,
3354
+ authContext: runtimeClaimsToBridgeAuthContext(verified.claims)
3339
3355
  };
3340
3356
  }
3341
3357
  function verifyWorkspaceBridgeRuntimeRefreshToken(token, options) {
@@ -3557,14 +3573,25 @@ function workspaceBridgeHttpRoutes(app, opts, done) {
3557
3573
  return sendBridgeError(reply, 400, void 0, "BRIDGE_SCHEMA_INVALID" /* SchemaInvalid */, "WorkspaceBridge request body is invalid");
3558
3574
  }
3559
3575
  const body = { ...parsed.data, input: parsed.data.input ?? {} };
3576
+ const authHeader = firstHeader(request.headers.authorization);
3577
+ const runtimeToken = authHeader?.startsWith("Bearer ") ? authHeader.slice("Bearer ".length) : void 0;
3578
+ let verifiedRuntimeToken;
3579
+ if (runtimeToken !== void 0 && opts.assertRuntimeWorkspaceScope) {
3580
+ try {
3581
+ verifiedRuntimeToken = resolveRuntimeClaims(runtimeToken, opts);
3582
+ } catch (err) {
3583
+ const bridgeError = isBridgeError(err) ? err : createWorkspaceBridgeError("BRIDGE_HANDLER_FAILED" /* HandlerFailed */, "WorkspaceBridge transport failed");
3584
+ return sendBridgeError(reply, statusForBridgeError(bridgeError.code), body.requestId, bridgeError.code, bridgeError.message);
3585
+ }
3586
+ await opts.assertRuntimeWorkspaceScope(request, verifiedRuntimeToken.claims);
3587
+ }
3560
3588
  try {
3561
3589
  const registry = await resolveRegistry(request, body, opts);
3562
3590
  const definition = registry.getDefinition(body.op);
3563
3591
  if (!definition) {
3564
3592
  return sendBridgeError(reply, statusForBridgeError("BRIDGE_OP_NOT_FOUND" /* OpNotFound */), body.requestId, "BRIDGE_OP_NOT_FOUND" /* OpNotFound */, "WorkspaceBridge operation is not registered");
3565
3593
  }
3566
- const authHeader = firstHeader(request.headers.authorization);
3567
- const authContext = authHeader?.startsWith("Bearer ") ? resolveRuntimeContext(authHeader.slice("Bearer ".length), opts, definition) : await resolveBrowserContext(request, opts, definition, body);
3594
+ const authContext = runtimeToken !== void 0 ? resolveRuntimeContext(runtimeToken, opts, definition, verifiedRuntimeToken) : await resolveBrowserContext(request, opts, definition, body);
3568
3595
  const idempotencyStore = opts.getIdempotencyStore ? await opts.getIdempotencyStore(request, body) : opts.idempotencyStore;
3569
3596
  const expectedWorkspaceId = opts.getOwnerWorkspaceId ? await opts.getOwnerWorkspaceId(request, body, authContext) : opts.ownerWorkspaceId;
3570
3597
  const response = await runWithWorkspaceBridgeIdempotency(idempotencyStore, {
@@ -3587,12 +3614,21 @@ function workspaceBridgeHttpRoutes(app, opts, done) {
3587
3614
  if (!opts.runtimeTokenSecret || !opts.runtimeRefreshTokenSecret) {
3588
3615
  return sendBridgeError(reply, 401, void 0, "BRIDGE_AUTH_REQUIRED" /* AuthRequired */, "WorkspaceBridge token refresh is not configured");
3589
3616
  }
3617
+ const nowMs = Date.now();
3618
+ let verified;
3590
3619
  try {
3591
- const nowMs = Date.now();
3592
- const verified = verifyWorkspaceBridgeRuntimeRefreshToken(authHeader.slice("Bearer ".length), {
3620
+ verified = verifyWorkspaceBridgeRuntimeRefreshToken(authHeader.slice("Bearer ".length), {
3593
3621
  secret: opts.runtimeRefreshTokenSecret,
3594
3622
  nowMs
3595
3623
  });
3624
+ } catch (err) {
3625
+ const bridgeError = isBridgeError(err) ? err : createWorkspaceBridgeError("BRIDGE_INVALID_TOKEN" /* InvalidToken */, "WorkspaceBridge refresh token is invalid");
3626
+ return sendBridgeError(reply, statusForBridgeError(bridgeError.code), void 0, bridgeError.code, bridgeError.message);
3627
+ }
3628
+ if (opts.assertRuntimeWorkspaceScope) {
3629
+ await opts.assertRuntimeWorkspaceScope(request, verified.claims);
3630
+ }
3631
+ try {
3596
3632
  const store = opts.getRuntimeRefreshTokenStore ? await opts.getRuntimeRefreshTokenStore(request, verified.claims) ?? defaultRefreshTokenStore : defaultRefreshTokenStore;
3597
3633
  const refreshUse = await store.recordUse({
3598
3634
  jti: verified.claims.jti,
@@ -3635,7 +3671,10 @@ async function resolveRegistry(request, body, opts) {
3635
3671
  }
3636
3672
  return registry;
3637
3673
  }
3638
- function resolveRuntimeContext(token, opts, definition) {
3674
+ function resolveRuntimeContext(token, opts, definition, verified) {
3675
+ if (verified) {
3676
+ return authorizeWorkspaceBridgeRuntimeToken(verified, definition.requiredCapabilities).authContext;
3677
+ }
3639
3678
  if (!opts.runtimeTokenSecret) {
3640
3679
  throw createWorkspaceBridgeError("BRIDGE_AUTH_REQUIRED" /* AuthRequired */, "Runtime bridge token auth is not configured");
3641
3680
  }
@@ -3644,6 +3683,12 @@ function resolveRuntimeContext(token, opts, definition) {
3644
3683
  requiredCapabilities: definition.requiredCapabilities
3645
3684
  }).authContext;
3646
3685
  }
3686
+ function resolveRuntimeClaims(token, opts) {
3687
+ if (!opts.runtimeTokenSecret) {
3688
+ throw createWorkspaceBridgeError("BRIDGE_AUTH_REQUIRED" /* AuthRequired */, "Runtime bridge token auth is not configured");
3689
+ }
3690
+ return verifyWorkspaceBridgeRuntimeTokenClaims(token, { secret: opts.runtimeTokenSecret });
3691
+ }
3647
3692
  async function resolveBrowserContext(request, opts, definition, body) {
3648
3693
  if (!opts.browserAuthPolicy) {
3649
3694
  throw createWorkspaceBridgeError("BRIDGE_AUTH_REQUIRED" /* AuthRequired */, "Browser bridge auth is not configured");
package/dist/server.d.ts CHANGED
@@ -68,7 +68,7 @@ type PaneRenderStatusStore = ReturnType<typeof createPaneRenderStatusStore>;
68
68
  interface UiRoutesOptions {
69
69
  bridge?: UiBridge;
70
70
  getBridge?: (request: FastifyRequest) => UiBridge | Promise<UiBridge>;
71
- getWorkspaceId?: (request: FastifyRequest) => string | undefined | Promise<string | undefined>;
71
+ getWorkspaceId?: (request: FastifyRequest, presentedWorkspaceId?: unknown) => string | undefined | Promise<string | undefined>;
72
72
  /**
73
73
  * Server/plugin-owned state slots preserved across browser full-state PUTs.
74
74
  * Browser UI snapshots are replace-style for normal workspace state, but
@@ -232,6 +232,7 @@ interface VerifyWorkspaceBridgeRuntimeTokenOptions {
232
232
  nowMs?: number;
233
233
  requiredCapabilities?: readonly string[];
234
234
  }
235
+ type VerifyWorkspaceBridgeRuntimeTokenClaimsOptions = Omit<VerifyWorkspaceBridgeRuntimeTokenOptions, "requiredCapabilities">;
235
236
  interface VerifyWorkspaceBridgeRuntimeRefreshTokenOptions {
236
237
  secret: string;
237
238
  nowMs?: number;
@@ -240,12 +241,17 @@ interface VerifiedWorkspaceBridgeRuntimeToken {
240
241
  claims: WorkspaceBridgeRuntimeTokenClaims;
241
242
  authContext: BridgeAuthContext;
242
243
  }
244
+ interface VerifiedWorkspaceBridgeRuntimeTokenClaims {
245
+ claims: WorkspaceBridgeRuntimeTokenClaims;
246
+ }
243
247
  interface VerifiedWorkspaceBridgeRuntimeRefreshToken {
244
248
  claims: WorkspaceBridgeRuntimeRefreshTokenClaims;
245
249
  }
246
250
  declare function mintWorkspaceBridgeRuntimeToken(options: MintWorkspaceBridgeRuntimeTokenOptions): string;
247
251
  declare function mintWorkspaceBridgeRuntimeRefreshToken(options: MintWorkspaceBridgeRuntimeRefreshTokenOptions): string;
248
252
  declare function verifyWorkspaceBridgeRuntimeToken(token: string, options: VerifyWorkspaceBridgeRuntimeTokenOptions): VerifiedWorkspaceBridgeRuntimeToken;
253
+ declare function verifyWorkspaceBridgeRuntimeTokenClaims(token: string, options: VerifyWorkspaceBridgeRuntimeTokenClaimsOptions): VerifiedWorkspaceBridgeRuntimeTokenClaims;
254
+ declare function authorizeWorkspaceBridgeRuntimeToken(verified: VerifiedWorkspaceBridgeRuntimeTokenClaims, requiredCapabilities?: readonly string[]): VerifiedWorkspaceBridgeRuntimeToken;
249
255
  declare function verifyWorkspaceBridgeRuntimeRefreshToken(token: string, options: VerifyWorkspaceBridgeRuntimeRefreshTokenOptions): VerifiedWorkspaceBridgeRuntimeRefreshToken;
250
256
  declare function clampWorkspaceBridgeRuntimeTokenTtlMs(ttlMs: number | undefined): number | undefined;
251
257
  declare function runtimeClaimsToBridgeAuthContext(claims: WorkspaceBridgeRuntimeTokenClaims): BridgeAuthContext;
@@ -289,6 +295,7 @@ interface WorkspaceBridgeHttpRoutesOptions {
289
295
  runtimeRefreshTokenSecret?: string;
290
296
  runtimeRefreshTokenStore?: WorkspaceBridgeRuntimeRefreshTokenStore;
291
297
  getRuntimeRefreshTokenStore?: (request: FastifyRequest, claims: WorkspaceBridgeRuntimeRefreshTokenClaims) => WorkspaceBridgeRuntimeRefreshTokenStore | undefined | Promise<WorkspaceBridgeRuntimeRefreshTokenStore | undefined>;
298
+ assertRuntimeWorkspaceScope?: (request: FastifyRequest, claims: WorkspaceBridgeRuntimeTokenClaims | WorkspaceBridgeRuntimeRefreshTokenClaims) => void | Promise<void>;
292
299
  refreshTokenRateLimit?: {
293
300
  maxUses?: number;
294
301
  windowMs?: number;
@@ -622,4 +629,4 @@ interface RuntimeBackendGatewayOptions {
622
629
  }
623
630
  declare function runtimeBackendGateway(app: FastifyInstance, opts: RuntimeBackendGatewayOptions): Promise<void>;
624
631
 
625
- export { type BeginIdempotencyOptions, BoringPluginAssetManager, BoringPluginEvent, BoringPluginFrontTarget, BoringPluginFrontTargetResolver, BoringPluginListEntry, type BoringPluginScanResult, BoringPluginSource, BoringPluginSourceInput, BoringServerPluginManifest, BridgeAuthContext, BridgeAuthPolicy, BridgeCallerClass, BridgeIdempotencyPolicy, type CompleteIdempotencyOptions, DEFAULT_WORKSPACE_BRIDGE_RUNTIME_REFRESH_TOKEN_TTL_MS, DEFAULT_WORKSPACE_BRIDGE_RUNTIME_TOKEN_TTL_MS, type IdempotencyBeginResult, type IdempotencyRecordStatus, InMemoryWorkspaceBridgeIdempotencyStore, InMemoryWorkspaceBridgeRuntimeRefreshTokenStore, MAX_WORKSPACE_BRIDGE_RUNTIME_TOKEN_TTL_MS, type MintWorkspaceBridgeRuntimeRefreshTokenOptions, type MintWorkspaceBridgeRuntimeTokenOptions, type PluginRestartWarning, type RuntimeBackendDiagnostic, type RuntimeBackendDispatchRequest, type RuntimeBackendDispatchResponse, type RuntimeBackendDispatcher, RuntimeBackendError, type RuntimeBackendGatewayOptions, RuntimeBackendRegistry, type RuntimeBackendReloadResult, type TrustedDomainBridgeHandlerOptions, type TrustedDomainBridgeHandlerPolicy, type TrustedDomainBridgeHandlerRegistration, UiBridge, type UiRoutesOptions, type VerifiedWorkspaceBridgeRuntimeRefreshToken, type VerifiedWorkspaceBridgeRuntimeToken, type VerifyWorkspaceBridgeRuntimeRefreshTokenOptions, type VerifyWorkspaceBridgeRuntimeTokenOptions, WORKSPACE_BRIDGE_REFRESH_TOKEN_AUDIENCE, WORKSPACE_BRIDGE_TOKEN_AUDIENCE, WorkspaceBridge, WorkspaceBridgeCallRequest, WorkspaceBridgeCallResponse, WorkspaceBridgeError, WorkspaceBridgeHandler, type WorkspaceBridgeHttpRoutesOptions, type WorkspaceBridgeIdempotencyRecord, type WorkspaceBridgeIdempotencyStore, WorkspaceBridgeOperationDefinition, WorkspaceBridgeRegistry, type WorkspaceBridgeRuntimeCore, type WorkspaceBridgeRuntimeCoreOptions, type WorkspaceBridgeRuntimeRefreshTokenClaims, type WorkspaceBridgeRuntimeRefreshTokenStore, type WorkspaceBridgeRuntimeRefreshTokenUseOptions, type WorkspaceBridgeRuntimeRefreshTokenUseResult, type WorkspaceBridgeRuntimeTokenClaims, WorkspaceServerPluginAsset, aggregatePluginPrompts, boringPluginRoutes, buildBoringSystemPrompt, clampWorkspaceBridgeRuntimeTokenTtlMs, collectRestartWarnings, createExecUiTool, createGetUiStateTool, createWorkspaceBridgeRuntimeCore, createWorkspaceUiTools, definePluginAsset, defineTrustedDomainBridgeHandler, hashNormalizedInput, mintWorkspaceBridgeRuntimeRefreshToken, mintWorkspaceBridgeRuntimeToken, pluginFileSignature, preflightBoringPlugins, readBoringPlugins, readPluginSignatureCache, resolvePluginAssetPath, runWithWorkspaceBridgeIdempotency, runtimeBackendGateway, runtimeClaimsToBridgeAuthContext, scanBoringPlugins, stableStringify, uiRoutes, verifyWorkspaceBridgeRuntimeRefreshToken, verifyWorkspaceBridgeRuntimeToken, workspaceBridgeHttpRoutes, writePluginSignatureCache };
632
+ export { type BeginIdempotencyOptions, BoringPluginAssetManager, BoringPluginEvent, BoringPluginFrontTarget, BoringPluginFrontTargetResolver, BoringPluginListEntry, type BoringPluginScanResult, BoringPluginSource, BoringPluginSourceInput, BoringServerPluginManifest, BridgeAuthContext, BridgeAuthPolicy, BridgeCallerClass, BridgeIdempotencyPolicy, type CompleteIdempotencyOptions, DEFAULT_WORKSPACE_BRIDGE_RUNTIME_REFRESH_TOKEN_TTL_MS, DEFAULT_WORKSPACE_BRIDGE_RUNTIME_TOKEN_TTL_MS, type IdempotencyBeginResult, type IdempotencyRecordStatus, InMemoryWorkspaceBridgeIdempotencyStore, InMemoryWorkspaceBridgeRuntimeRefreshTokenStore, MAX_WORKSPACE_BRIDGE_RUNTIME_TOKEN_TTL_MS, type MintWorkspaceBridgeRuntimeRefreshTokenOptions, type MintWorkspaceBridgeRuntimeTokenOptions, type PluginRestartWarning, type RuntimeBackendDiagnostic, type RuntimeBackendDispatchRequest, type RuntimeBackendDispatchResponse, type RuntimeBackendDispatcher, RuntimeBackendError, type RuntimeBackendGatewayOptions, RuntimeBackendRegistry, type RuntimeBackendReloadResult, type TrustedDomainBridgeHandlerOptions, type TrustedDomainBridgeHandlerPolicy, type TrustedDomainBridgeHandlerRegistration, UiBridge, type UiRoutesOptions, type VerifiedWorkspaceBridgeRuntimeRefreshToken, type VerifiedWorkspaceBridgeRuntimeToken, type VerifiedWorkspaceBridgeRuntimeTokenClaims, type VerifyWorkspaceBridgeRuntimeRefreshTokenOptions, type VerifyWorkspaceBridgeRuntimeTokenClaimsOptions, type VerifyWorkspaceBridgeRuntimeTokenOptions, WORKSPACE_BRIDGE_REFRESH_TOKEN_AUDIENCE, WORKSPACE_BRIDGE_TOKEN_AUDIENCE, WorkspaceBridge, WorkspaceBridgeCallRequest, WorkspaceBridgeCallResponse, WorkspaceBridgeError, WorkspaceBridgeHandler, type WorkspaceBridgeHttpRoutesOptions, type WorkspaceBridgeIdempotencyRecord, type WorkspaceBridgeIdempotencyStore, WorkspaceBridgeOperationDefinition, WorkspaceBridgeRegistry, type WorkspaceBridgeRuntimeCore, type WorkspaceBridgeRuntimeCoreOptions, type WorkspaceBridgeRuntimeRefreshTokenClaims, type WorkspaceBridgeRuntimeRefreshTokenStore, type WorkspaceBridgeRuntimeRefreshTokenUseOptions, type WorkspaceBridgeRuntimeRefreshTokenUseResult, type WorkspaceBridgeRuntimeTokenClaims, WorkspaceServerPluginAsset, aggregatePluginPrompts, authorizeWorkspaceBridgeRuntimeToken, boringPluginRoutes, buildBoringSystemPrompt, clampWorkspaceBridgeRuntimeTokenTtlMs, collectRestartWarnings, createExecUiTool, createGetUiStateTool, createWorkspaceBridgeRuntimeCore, createWorkspaceUiTools, definePluginAsset, defineTrustedDomainBridgeHandler, hashNormalizedInput, mintWorkspaceBridgeRuntimeRefreshToken, mintWorkspaceBridgeRuntimeToken, pluginFileSignature, preflightBoringPlugins, readBoringPlugins, readPluginSignatureCache, resolvePluginAssetPath, runWithWorkspaceBridgeIdempotency, runtimeBackendGateway, runtimeClaimsToBridgeAuthContext, scanBoringPlugins, stableStringify, uiRoutes, verifyWorkspaceBridgeRuntimeRefreshToken, verifyWorkspaceBridgeRuntimeToken, verifyWorkspaceBridgeRuntimeTokenClaims, workspaceBridgeHttpRoutes, writePluginSignatureCache };
package/dist/server.js CHANGED
@@ -166,15 +166,23 @@ function resolvePaneStatusWorkspaceId(request) {
166
166
  function paneRenderStatusRoutes(app, opts = {}, done) {
167
167
  const store = opts.store ?? createPaneRenderStatusStore();
168
168
  const validateReport = createBodyValidator(reportBodySchema);
169
- const getWorkspaceId = async (request) => {
170
- return await opts.getWorkspaceId?.(request) ?? resolvePaneStatusWorkspaceId(request);
169
+ const getWorkspaceId = async (request, presentedWorkspaceId) => {
170
+ return await opts.getWorkspaceId?.(request, presentedWorkspaceId) ?? resolvePaneStatusWorkspaceId(request);
171
+ };
172
+ const admitMalformedScopedWorkspaceId = async (request) => {
173
+ const scoped = request.requestScope !== void 0;
174
+ const body = request.body;
175
+ if (!scoped || !body || !Object.prototype.hasOwnProperty.call(body, "workspaceId")) return;
176
+ if (body.workspaceId !== void 0 && typeof body.workspaceId !== "string") {
177
+ await getWorkspaceId(request, body.workspaceId);
178
+ }
171
179
  };
172
180
  app.put(
173
181
  "/api/v1/ui/panels/status",
174
- { preHandler: validateReport },
182
+ { preHandler: [admitMalformedScopedWorkspaceId, validateReport] },
175
183
  async (request, reply) => {
176
184
  const body = request.body;
177
- const workspaceId = await getWorkspaceId(request) ?? body.workspaceId;
185
+ const workspaceId = await getWorkspaceId(request, body.workspaceId) ?? body.workspaceId;
178
186
  const status = store.report({ ...body, workspaceId });
179
187
  return reply.code(200).send({ ok: true, status });
180
188
  }
@@ -230,7 +238,7 @@ function createBodyValidator2(schema) {
230
238
  function uiRoutes(app, opts, done) {
231
239
  const fallbackBridge = opts.bridge;
232
240
  const paneStatusStore = opts.paneStatusStore ?? createPaneRenderStatusStore();
233
- const getPaneWorkspaceId = async (request) => await opts.getWorkspaceId?.(request) ?? resolvePaneStatusWorkspaceId(request);
241
+ const getPaneWorkspaceId = async (request, presentedWorkspaceId) => await opts.getWorkspaceId?.(request, presentedWorkspaceId) ?? resolvePaneStatusWorkspaceId(request);
234
242
  const touchUi = async (request) => {
235
243
  paneStatusStore.touchUi(await getPaneWorkspaceId(request));
236
244
  };
@@ -1401,20 +1409,28 @@ function mintWorkspaceBridgeRuntimeRefreshToken(options) {
1401
1409
  });
1402
1410
  }
1403
1411
  function verifyWorkspaceBridgeRuntimeToken(token, options) {
1412
+ return authorizeWorkspaceBridgeRuntimeToken(
1413
+ verifyWorkspaceBridgeRuntimeTokenClaims(token, options),
1414
+ options.requiredCapabilities
1415
+ );
1416
+ }
1417
+ function verifyWorkspaceBridgeRuntimeTokenClaims(token, options) {
1404
1418
  assertUsableSecret(options.secret);
1405
1419
  const claims = parseAndVerifyToken(token, options.secret);
1406
1420
  const now = Math.floor((options.nowMs ?? Date.now()) / 1e3);
1407
1421
  ensureLiveTokenClaims(claims, now, WORKSPACE_BRIDGE_TOKEN_AUDIENCE, "Runtime bridge token");
1408
- const missingCapability = (options.requiredCapabilities ?? []).find(
1409
- (capability) => !claims.capabilities.includes(capability)
1422
+ return { claims };
1423
+ }
1424
+ function authorizeWorkspaceBridgeRuntimeToken(verified, requiredCapabilities = []) {
1425
+ const missingCapability = requiredCapabilities.find(
1426
+ (capability) => !verified.claims.capabilities.includes(capability)
1410
1427
  );
1411
1428
  if (missingCapability) {
1412
1429
  throw bridgeTokenError("BRIDGE_CAPABILITY_DENIED" /* CapabilityDenied */, "Runtime bridge token is missing a required capability");
1413
1430
  }
1414
- const runtimeClaims = claims;
1415
1431
  return {
1416
- claims: runtimeClaims,
1417
- authContext: runtimeClaimsToBridgeAuthContext(runtimeClaims)
1432
+ claims: verified.claims,
1433
+ authContext: runtimeClaimsToBridgeAuthContext(verified.claims)
1418
1434
  };
1419
1435
  }
1420
1436
  function verifyWorkspaceBridgeRuntimeRefreshToken(token, options) {
@@ -1636,14 +1652,25 @@ function workspaceBridgeHttpRoutes(app, opts, done) {
1636
1652
  return sendBridgeError(reply, 400, void 0, "BRIDGE_SCHEMA_INVALID" /* SchemaInvalid */, "WorkspaceBridge request body is invalid");
1637
1653
  }
1638
1654
  const body = { ...parsed.data, input: parsed.data.input ?? {} };
1655
+ const authHeader = firstHeader2(request.headers.authorization);
1656
+ const runtimeToken = authHeader?.startsWith("Bearer ") ? authHeader.slice("Bearer ".length) : void 0;
1657
+ let verifiedRuntimeToken;
1658
+ if (runtimeToken !== void 0 && opts.assertRuntimeWorkspaceScope) {
1659
+ try {
1660
+ verifiedRuntimeToken = resolveRuntimeClaims(runtimeToken, opts);
1661
+ } catch (err) {
1662
+ const bridgeError = isBridgeError(err) ? err : createWorkspaceBridgeError("BRIDGE_HANDLER_FAILED" /* HandlerFailed */, "WorkspaceBridge transport failed");
1663
+ return sendBridgeError(reply, statusForBridgeError(bridgeError.code), body.requestId, bridgeError.code, bridgeError.message);
1664
+ }
1665
+ await opts.assertRuntimeWorkspaceScope(request, verifiedRuntimeToken.claims);
1666
+ }
1639
1667
  try {
1640
1668
  const registry = await resolveRegistry(request, body, opts);
1641
1669
  const definition = registry.getDefinition(body.op);
1642
1670
  if (!definition) {
1643
1671
  return sendBridgeError(reply, statusForBridgeError("BRIDGE_OP_NOT_FOUND" /* OpNotFound */), body.requestId, "BRIDGE_OP_NOT_FOUND" /* OpNotFound */, "WorkspaceBridge operation is not registered");
1644
1672
  }
1645
- const authHeader = firstHeader2(request.headers.authorization);
1646
- const authContext = authHeader?.startsWith("Bearer ") ? resolveRuntimeContext(authHeader.slice("Bearer ".length), opts, definition) : await resolveBrowserContext(request, opts, definition, body);
1673
+ const authContext = runtimeToken !== void 0 ? resolveRuntimeContext(runtimeToken, opts, definition, verifiedRuntimeToken) : await resolveBrowserContext(request, opts, definition, body);
1647
1674
  const idempotencyStore = opts.getIdempotencyStore ? await opts.getIdempotencyStore(request, body) : opts.idempotencyStore;
1648
1675
  const expectedWorkspaceId = opts.getOwnerWorkspaceId ? await opts.getOwnerWorkspaceId(request, body, authContext) : opts.ownerWorkspaceId;
1649
1676
  const response = await runWithWorkspaceBridgeIdempotency(idempotencyStore, {
@@ -1666,12 +1693,21 @@ function workspaceBridgeHttpRoutes(app, opts, done) {
1666
1693
  if (!opts.runtimeTokenSecret || !opts.runtimeRefreshTokenSecret) {
1667
1694
  return sendBridgeError(reply, 401, void 0, "BRIDGE_AUTH_REQUIRED" /* AuthRequired */, "WorkspaceBridge token refresh is not configured");
1668
1695
  }
1696
+ const nowMs = Date.now();
1697
+ let verified;
1669
1698
  try {
1670
- const nowMs = Date.now();
1671
- const verified = verifyWorkspaceBridgeRuntimeRefreshToken(authHeader.slice("Bearer ".length), {
1699
+ verified = verifyWorkspaceBridgeRuntimeRefreshToken(authHeader.slice("Bearer ".length), {
1672
1700
  secret: opts.runtimeRefreshTokenSecret,
1673
1701
  nowMs
1674
1702
  });
1703
+ } catch (err) {
1704
+ const bridgeError = isBridgeError(err) ? err : createWorkspaceBridgeError("BRIDGE_INVALID_TOKEN" /* InvalidToken */, "WorkspaceBridge refresh token is invalid");
1705
+ return sendBridgeError(reply, statusForBridgeError(bridgeError.code), void 0, bridgeError.code, bridgeError.message);
1706
+ }
1707
+ if (opts.assertRuntimeWorkspaceScope) {
1708
+ await opts.assertRuntimeWorkspaceScope(request, verified.claims);
1709
+ }
1710
+ try {
1675
1711
  const store = opts.getRuntimeRefreshTokenStore ? await opts.getRuntimeRefreshTokenStore(request, verified.claims) ?? defaultRefreshTokenStore : defaultRefreshTokenStore;
1676
1712
  const refreshUse = await store.recordUse({
1677
1713
  jti: verified.claims.jti,
@@ -1714,7 +1750,10 @@ async function resolveRegistry(request, body, opts) {
1714
1750
  }
1715
1751
  return registry;
1716
1752
  }
1717
- function resolveRuntimeContext(token, opts, definition) {
1753
+ function resolveRuntimeContext(token, opts, definition, verified) {
1754
+ if (verified) {
1755
+ return authorizeWorkspaceBridgeRuntimeToken(verified, definition.requiredCapabilities).authContext;
1756
+ }
1718
1757
  if (!opts.runtimeTokenSecret) {
1719
1758
  throw createWorkspaceBridgeError("BRIDGE_AUTH_REQUIRED" /* AuthRequired */, "Runtime bridge token auth is not configured");
1720
1759
  }
@@ -1723,6 +1762,12 @@ function resolveRuntimeContext(token, opts, definition) {
1723
1762
  requiredCapabilities: definition.requiredCapabilities
1724
1763
  }).authContext;
1725
1764
  }
1765
+ function resolveRuntimeClaims(token, opts) {
1766
+ if (!opts.runtimeTokenSecret) {
1767
+ throw createWorkspaceBridgeError("BRIDGE_AUTH_REQUIRED" /* AuthRequired */, "Runtime bridge token auth is not configured");
1768
+ }
1769
+ return verifyWorkspaceBridgeRuntimeTokenClaims(token, { secret: opts.runtimeTokenSecret });
1770
+ }
1726
1771
  async function resolveBrowserContext(request, opts, definition, body) {
1727
1772
  if (!opts.browserAuthPolicy) {
1728
1773
  throw createWorkspaceBridgeError("BRIDGE_AUTH_REQUIRED" /* AuthRequired */, "Browser bridge auth is not configured");
@@ -3792,6 +3837,7 @@ export {
3792
3837
  WorkspaceBridgeErrorCode,
3793
3838
  WorkspaceBridgeRegistry,
3794
3839
  aggregatePluginPrompts,
3840
+ authorizeWorkspaceBridgeRuntimeToken,
3795
3841
  bootstrapServer,
3796
3842
  boringPluginRoutes,
3797
3843
  buildBoringSystemPrompt,
@@ -3832,6 +3878,7 @@ export {
3832
3878
  validateWorkspaceBridgeOperationDefinition,
3833
3879
  verifyWorkspaceBridgeRuntimeRefreshToken,
3834
3880
  verifyWorkspaceBridgeRuntimeToken,
3881
+ verifyWorkspaceBridgeRuntimeTokenClaims,
3835
3882
  workspaceBridgeHttpRoutes,
3836
3883
  writePluginSignatureCache
3837
3884
  };
@@ -6171,6 +6171,9 @@
6171
6171
  .grid {
6172
6172
  display: grid;
6173
6173
  }
6174
+ .inline {
6175
+ display: inline;
6176
+ }
6174
6177
  .inline-block {
6175
6178
  display: inline-block;
6176
6179
  }
@@ -6220,6 +6223,9 @@
6220
6223
  .h-1 {
6221
6224
  height: var(--spacing);
6222
6225
  }
6226
+ .h-2 {
6227
+ height: calc(var(--spacing) * 2);
6228
+ }
6223
6229
  .h-2\.5 {
6224
6230
  height: calc(var(--spacing) * 2.5);
6225
6231
  }
@@ -8424,6 +8430,64 @@
8424
8430
  height: calc(var(--spacing) * 4);
8425
8431
  }
8426
8432
  }
8433
+ .\[\&\:\:-moz-progress-bar\]\:rounded-full {
8434
+ &::-moz-progress-bar {
8435
+ border-radius: calc(infinity * 1px);
8436
+ }
8437
+ }
8438
+ .\[\&\:\:-moz-progress-bar\]\:bg-\[color\:var\(--boring-warning\,var\(--accent-foreground\)\)\] {
8439
+ &::-moz-progress-bar {
8440
+ background-color: var(--boring-warning,var(--accent-foreground));
8441
+ }
8442
+ }
8443
+ .\[\&\:\:-moz-progress-bar\]\:bg-destructive {
8444
+ &::-moz-progress-bar {
8445
+ background-color: var(--boring-destructive);
8446
+ }
8447
+ }
8448
+ .\[\&\:\:-moz-progress-bar\]\:bg-primary {
8449
+ &::-moz-progress-bar {
8450
+ background-color: var(--boring-primary);
8451
+ }
8452
+ }
8453
+ .\[\&\:\:-webkit-progress-bar\]\:bg-muted {
8454
+ &::-webkit-progress-bar {
8455
+ background-color: var(--boring-muted);
8456
+ }
8457
+ }
8458
+ .\[\&\:\:-webkit-progress-value\]\:rounded-full {
8459
+ &::-webkit-progress-value {
8460
+ border-radius: calc(infinity * 1px);
8461
+ }
8462
+ }
8463
+ .\[\&\:\:-webkit-progress-value\]\:bg-\[color\:var\(--boring-warning\,var\(--accent-foreground\)\)\] {
8464
+ &::-webkit-progress-value {
8465
+ background-color: var(--boring-warning,var(--accent-foreground));
8466
+ }
8467
+ }
8468
+ .\[\&\:\:-webkit-progress-value\]\:bg-destructive {
8469
+ &::-webkit-progress-value {
8470
+ background-color: var(--boring-destructive);
8471
+ }
8472
+ }
8473
+ .\[\&\:\:-webkit-progress-value\]\:bg-primary {
8474
+ &::-webkit-progress-value {
8475
+ background-color: var(--boring-primary);
8476
+ }
8477
+ }
8478
+ .\[\&\:\:-webkit-progress-value\]\:transition-all {
8479
+ &::-webkit-progress-value {
8480
+ transition-property: all;
8481
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
8482
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
8483
+ }
8484
+ }
8485
+ .\[\&\:\:-webkit-progress-value\]\:duration-300 {
8486
+ &::-webkit-progress-value {
8487
+ --tw-duration: 300ms;
8488
+ transition-duration: 300ms;
8489
+ }
8490
+ }
8427
8491
  .\[\&\:\:-webkit-scrollbar\]\:hidden {
8428
8492
  &::-webkit-scrollbar {
8429
8493
  display: none;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-workspace",
3
- "version": "0.1.81",
3
+ "version": "0.1.83",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Workspace UI, plugin, and bridge package for composing chat, files, catalogs, editors, and app-specific panes.",
@@ -139,9 +139,9 @@
139
139
  "tailwind-merge": "^2.0.0",
140
140
  "zod": "^3.23.0",
141
141
  "zustand": "^5.0.14",
142
- "@hachej/boring-agent": "0.1.81",
143
- "@hachej/boring-ui-kit": "0.1.81",
144
- "@hachej/boring-ui-plugin-cli": "0.1.81"
142
+ "@hachej/boring-agent": "0.1.83",
143
+ "@hachej/boring-ui-kit": "0.1.83",
144
+ "@hachej/boring-ui-plugin-cli": "0.1.83"
145
145
  },
146
146
  "devDependencies": {
147
147
  "@tailwindcss/postcss": "^4.3.1",