@workerdeck/server 2.12.0 → 2.13.0
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/build/index.d.mts +2 -2
- package/build/index.mjs +93 -22
- package/build/index.mjs.map +1 -1
- package/package.json +5 -5
package/build/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
2
|
-
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, CostLedgerState, EngineAdapter, EngineSessionOptions, HostToolDefinition, LanguageModel, McpConnection, ParkedExecution, Runner, RunnerSnapshot, SessionRunnerConfig, ToolExecutionCall, ToolExecutionResult, ToolExecutor, ToolSet } from "@workerdeck/core";
|
|
2
|
+
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, CostLedgerState, EngineAdapter, EngineSessionOptions, HostToolDefinition, LanguageModel, McpConnection, ParkedExecution, Runner, RunnerSnapshot, SessionInstructions, SessionRunnerConfig, ToolExecutionCall, ToolExecutionResult, ToolExecutor, ToolSet } from "@workerdeck/core";
|
|
3
3
|
import { JobQueue, QueueAdapter } from "@workerdeck/queue";
|
|
4
4
|
import { ByModel, CreateSessionRequest, JobEvent, MessageAttachment, ModelRate, ProfileEngine, ProfileInfo, ProfileSpend, ProfileUsage, ProviderConfig, SdkSessionSummary, ServerFrame, SessionCapability, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
|
|
5
5
|
//#region src/services/bridge.d.ts
|
|
@@ -323,7 +323,7 @@ type ProviderRunnerOptions = {
|
|
|
323
323
|
tools?: Record<string, HostToolDefinition>;
|
|
324
324
|
mcp?: McpConnection;
|
|
325
325
|
mcpTools?: ToolSet;
|
|
326
|
-
instructions?:
|
|
326
|
+
instructions?: SessionInstructions;
|
|
327
327
|
executionLimits?: {
|
|
328
328
|
timeoutMs?: number;
|
|
329
329
|
memoryLimitBytes?: number;
|
package/build/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { createServer } from "node:http";
|
|
|
2
2
|
import { WebSocketServer } from "ws";
|
|
3
3
|
import { BrowserBridgeExecutor, PEER_MESSAGE_MAX_CHARS, PEER_RECENT_DEFAULT, PEER_RECENT_MAX, SessionRunner, attachmentKind, checkClaudeAuth, createEngineSession, getEngineAdapter, installPeerDirectory, isSlashCommand, normalizeMediaType, peerDirectoryHandle, peerSummary, recentLines } from "@workerdeck/core";
|
|
4
4
|
import { JobQueue } from "@workerdeck/queue";
|
|
5
|
-
import { ENGINE_CAPABILITIES, PEER_MENTION_MAX, PROTOCOL_VERSION, SHELL_COMMAND_MAX, costOfByModel, imagePartRef, isJobRun, mergeByModel, peerMentionKey, peerMentionSlug, scanPeerMentions, sessionState, setPricingOverrides, subscriptionComparison, supportsPermissionMode } from "@workerdeck/protocol";
|
|
5
|
+
import { ENGINE_CAPABILITIES, PEER_MENTION_MAX, PROTOCOL_VERSION, SHELL_COMMAND_MAX, costOfByModel, imagePartRef, isJobRun, mergeByModel, peerMentionKey, peerMentionSlug, pickCreateSessionRequest, scanPeerMentions, sessionState, setPricingOverrides, subscriptionComparison, supportsPermissionMode } from "@workerdeck/protocol";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { closeSync, constants, createReadStream, existsSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { constants as constants$1, homedir } from "node:os";
|
|
@@ -751,34 +751,89 @@ async function handleHostFiles(ctx, req, res, pathname) {
|
|
|
751
751
|
}
|
|
752
752
|
json(res, 404, { error: "not found" });
|
|
753
753
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
754
|
+
const HOST_ONLY_KEYS = new Set(Object.keys({
|
|
755
|
+
epoch: true,
|
|
756
|
+
queryFn: true,
|
|
757
|
+
env: true,
|
|
758
|
+
pathToClaudeCodeExecutable: true,
|
|
759
|
+
extraOptions: true,
|
|
760
|
+
instructions: true,
|
|
761
|
+
defaultApprovalTimeoutMs: true,
|
|
762
|
+
backfillHistory: true,
|
|
763
|
+
historyFn: true,
|
|
764
|
+
sessionInfoFn: true,
|
|
765
|
+
peers: true,
|
|
766
|
+
connectFn: true,
|
|
767
|
+
codexHome: true,
|
|
768
|
+
codexPathOverride: true,
|
|
769
|
+
languageModel: true,
|
|
770
|
+
tools: true,
|
|
771
|
+
maxSteps: true,
|
|
772
|
+
executor: true,
|
|
773
|
+
executableTools: true,
|
|
774
|
+
vfs: true,
|
|
775
|
+
executionLimits: true,
|
|
776
|
+
executionBackend: true,
|
|
777
|
+
toolTitles: true,
|
|
778
|
+
shouldApprove: true,
|
|
779
|
+
resolveModel: true,
|
|
780
|
+
reportMcpServers: true,
|
|
781
|
+
onClose: true,
|
|
782
|
+
restore: true
|
|
783
|
+
}));
|
|
784
|
+
function vetCreateRequest(ctx, body, auth) {
|
|
785
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return {
|
|
786
|
+
ok: false,
|
|
787
|
+
status: 400,
|
|
788
|
+
error: "request body must be a JSON object"
|
|
789
|
+
};
|
|
790
|
+
const hostOnly = Object.keys(body).filter((key) => HOST_ONLY_KEYS.has(key));
|
|
791
|
+
if (hostOnly.length > 0) return {
|
|
792
|
+
ok: false,
|
|
793
|
+
status: 400,
|
|
794
|
+
error: `host-only runner config is not accepted on a session request: ${hostOnly.join(", ")} (set it from the gateway's buildRunnerConfig hook)`
|
|
795
|
+
};
|
|
796
|
+
const req = pickCreateSessionRequest(body);
|
|
757
797
|
const { availability, factory } = ctx;
|
|
758
798
|
const refusedScope = factory.applyScope(req, auth);
|
|
759
|
-
if (refusedScope) return
|
|
799
|
+
if (refusedScope) return {
|
|
800
|
+
ok: false,
|
|
801
|
+
...refusedScope
|
|
802
|
+
};
|
|
760
803
|
const refused = factory.applyBypassPolicy(req);
|
|
761
804
|
if (refused) return {
|
|
805
|
+
ok: false,
|
|
762
806
|
status: 403,
|
|
763
807
|
error: refused
|
|
764
808
|
};
|
|
765
809
|
const resolved = factory.resolveProfile(req.profile, auth.allowedProfiles);
|
|
766
810
|
if (!resolved.ok) return {
|
|
811
|
+
ok: false,
|
|
767
812
|
status: resolved.status,
|
|
768
813
|
error: resolved.error
|
|
769
814
|
};
|
|
770
815
|
const unavailable = availability.checkAvailable(resolved.profile);
|
|
771
|
-
if (unavailable) return
|
|
816
|
+
if (unavailable) return {
|
|
817
|
+
ok: false,
|
|
818
|
+
...unavailable
|
|
819
|
+
};
|
|
772
820
|
const refusedCwd = factory.checkCwd(req, resolved.profile);
|
|
773
|
-
if (refusedCwd) return
|
|
821
|
+
if (refusedCwd) return {
|
|
822
|
+
ok: false,
|
|
823
|
+
...refusedCwd
|
|
824
|
+
};
|
|
774
825
|
const badRequest = factory.checkPermissionMode(req.permissionMode, resolved.profile) ?? factory.checkEngineGrants(req, resolved.profile);
|
|
775
826
|
if (badRequest) return {
|
|
827
|
+
ok: false,
|
|
776
828
|
status: 400,
|
|
777
829
|
error: badRequest
|
|
778
830
|
};
|
|
779
831
|
factory.stripInertFields(req, resolved.profile);
|
|
780
832
|
req.profile = resolved.profile?.name;
|
|
781
|
-
return
|
|
833
|
+
return {
|
|
834
|
+
ok: true,
|
|
835
|
+
request: req
|
|
836
|
+
};
|
|
782
837
|
}
|
|
783
838
|
//#endregion
|
|
784
839
|
//#region src/routes/jobs.ts
|
|
@@ -808,21 +863,28 @@ async function handleJobs(ctx, req, res, pathname, auth) {
|
|
|
808
863
|
}
|
|
809
864
|
if (req.method === "POST") {
|
|
810
865
|
const body = await readJsonBody(req, ctx.maxBodyBytes);
|
|
811
|
-
if (!body
|
|
866
|
+
if (!body?.session || typeof body.session !== "object") {
|
|
812
867
|
json(res, 400, { error: "session is required" });
|
|
813
868
|
return;
|
|
814
869
|
}
|
|
815
|
-
|
|
870
|
+
const prompt = body.session.prompt;
|
|
871
|
+
if (!prompt || typeof prompt !== "string") {
|
|
816
872
|
json(res, 400, { error: "session.prompt is required" });
|
|
817
873
|
return;
|
|
818
874
|
}
|
|
819
|
-
const
|
|
820
|
-
if (
|
|
821
|
-
json(res,
|
|
875
|
+
const vetted = vetCreateRequest(ctx, body.session, auth);
|
|
876
|
+
if (!vetted.ok) {
|
|
877
|
+
json(res, vetted.status, { error: vetted.error });
|
|
822
878
|
return;
|
|
823
879
|
}
|
|
824
880
|
try {
|
|
825
|
-
json(res, 201, { job: await queue.submit(
|
|
881
|
+
json(res, 201, { job: await queue.submit({
|
|
882
|
+
...body,
|
|
883
|
+
session: {
|
|
884
|
+
...vetted.request,
|
|
885
|
+
prompt
|
|
886
|
+
}
|
|
887
|
+
}) });
|
|
826
888
|
} catch (error) {
|
|
827
889
|
json(res, 400, { error: error instanceof Error ? error.message : "invalid job" });
|
|
828
890
|
}
|
|
@@ -1028,7 +1090,8 @@ const EPHEMERAL_CONFIG_KEYS = [
|
|
|
1028
1090
|
"historyFn",
|
|
1029
1091
|
"extraOptions",
|
|
1030
1092
|
"env",
|
|
1031
|
-
"peers"
|
|
1093
|
+
"peers",
|
|
1094
|
+
"instructions"
|
|
1032
1095
|
];
|
|
1033
1096
|
function toDurableRecord(record) {
|
|
1034
1097
|
const config = { ...record.config };
|
|
@@ -1517,13 +1580,12 @@ async function handleSessions(ctx, req, res, route, auth) {
|
|
|
1517
1580
|
return;
|
|
1518
1581
|
}
|
|
1519
1582
|
if (req.method === "POST") {
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
json(res, refusal.status, { error: refusal.error });
|
|
1583
|
+
const vetted = vetCreateRequest(ctx, await readJsonBody(req, ctx.maxBodyBytes), auth);
|
|
1584
|
+
if (!vetted.ok) {
|
|
1585
|
+
json(res, vetted.status, { error: vetted.error });
|
|
1524
1586
|
return;
|
|
1525
1587
|
}
|
|
1526
|
-
const runner = await factory.createRunner(factory.buildRunnerConfig(
|
|
1588
|
+
const runner = await factory.createRunner(factory.buildRunnerConfig(vetted.request));
|
|
1527
1589
|
factory.watchAuthSource(runner);
|
|
1528
1590
|
json(res, 201, { session: projects.withProject(runner.info()) });
|
|
1529
1591
|
return;
|
|
@@ -1611,7 +1673,15 @@ async function handleSessions(ctx, req, res, route, auth) {
|
|
|
1611
1673
|
json(res, 409, { error: "session is parked (it has no pending permission requests)" });
|
|
1612
1674
|
return;
|
|
1613
1675
|
}
|
|
1614
|
-
|
|
1676
|
+
const decision = body.behavior === "allow" ? {
|
|
1677
|
+
behavior: "allow",
|
|
1678
|
+
updatedInput: body.updatedInput
|
|
1679
|
+
} : {
|
|
1680
|
+
behavior: "deny",
|
|
1681
|
+
message: body.message,
|
|
1682
|
+
interrupt: body.interrupt
|
|
1683
|
+
};
|
|
1684
|
+
if (!runner.resolvePermission(route.permissionId, decision)) {
|
|
1615
1685
|
json(res, 404, { error: "permission request not found (already resolved or expired)" });
|
|
1616
1686
|
return;
|
|
1617
1687
|
}
|
|
@@ -2742,7 +2812,6 @@ var ProfileService = class {
|
|
|
2742
2812
|
if (!hasEngineRunnerFactory) return `profile '${p.name}' uses engine 'provider' but no \`createEngineRunner\` was provided to build one`;
|
|
2743
2813
|
} else if (p.engine === "codex") {
|
|
2744
2814
|
if (p.codexHome && !existsSync(p.codexHome)) return `profile '${p.name}' codexHome does not exist: ${p.codexHome}`;
|
|
2745
|
-
if (p.session?.instructions) return `profile '${p.name}' declares session.instructions, which the codex engine cannot deliver - put instructions in the target repo’s AGENTS.md instead`;
|
|
2746
2815
|
} else if (!p.configDir || !existsSync(p.configDir)) return `profile '${p.name}' configDir does not exist: ${p.configDir}`;
|
|
2747
2816
|
if (disableBypassPermissions && p.defaults?.permissionMode === "bypassPermissions") return `profile '${p.name}' defaults to bypassPermissions but disableBypassPermissions is set`;
|
|
2748
2817
|
const fallbackMode = p.defaults?.permissionMode;
|
|
@@ -3403,6 +3472,8 @@ function createSessionFactory(deps) {
|
|
|
3403
3472
|
const name = config.profile;
|
|
3404
3473
|
const profile = name !== void 0 ? profiles.get(name) : void 0;
|
|
3405
3474
|
if (name !== void 0 && !profile) throw new Error(`unknown profile: ${name}`);
|
|
3475
|
+
const capabilities = profile?.capabilities ?? adapterFor(profile?.engine).capabilities;
|
|
3476
|
+
if (config.instructions !== void 0 && capabilities.systemInstructions === false) throw new Error(`the ${engineOf(profile)} engine cannot deliver system instructions`);
|
|
3406
3477
|
const runner = profile && isProviderProfile(profile) ? await deps.createEngineRunner({
|
|
3407
3478
|
config,
|
|
3408
3479
|
profile,
|