@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.
- package/dist/app-server.js +60 -15
- package/dist/server.d.ts +9 -2
- package/dist/server.js +62 -15
- package/dist/workspace.css +64 -0
- package/package.json +4 -4
package/dist/app-server.js
CHANGED
|
@@ -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
|
-
|
|
3330
|
-
|
|
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:
|
|
3338
|
-
authContext: runtimeClaimsToBridgeAuthContext(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1409
|
-
|
|
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:
|
|
1417
|
-
authContext: runtimeClaimsToBridgeAuthContext(
|
|
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
|
|
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
|
-
|
|
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
|
};
|
package/dist/workspace.css
CHANGED
|
@@ -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.
|
|
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.
|
|
143
|
-
"@hachej/boring-ui-kit": "0.1.
|
|
144
|
-
"@hachej/boring-ui-plugin-cli": "0.1.
|
|
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",
|