@workerdeck/server 2.11.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 +138 -25
- 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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { WebSocketServer } from "ws";
|
|
3
|
-
import { BrowserBridgeExecutor, PEER_MESSAGE_MAX_CHARS, PEER_RECENT_DEFAULT, PEER_RECENT_MAX, SessionRunner, attachmentKind, checkClaudeAuth, createEngineSession, getEngineAdapter, installPeerDirectory, normalizeMediaType, peerDirectoryHandle, peerSummary, recentLines } from "@workerdeck/core";
|
|
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, PROTOCOL_VERSION, SHELL_COMMAND_MAX, costOfByModel, imagePartRef, isJobRun, mergeByModel, 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
|
}
|
|
@@ -1854,13 +1924,15 @@ async function handleCommand(ctx, frame, runner, access) {
|
|
|
1854
1924
|
const { attachmentStore, bridge } = ctx;
|
|
1855
1925
|
switch (frame.type) {
|
|
1856
1926
|
case "user_message": {
|
|
1927
|
+
const mentions = ctx.peers && !isSlashCommand(frame.text) ? await ctx.peers.mentions(runner.id, frame.text).catch(() => []) : [];
|
|
1928
|
+
const options = mentions.length > 0 ? { mentions } : void 0;
|
|
1857
1929
|
if (!frame.attachmentIds?.length) {
|
|
1858
|
-
runner.sendMessage(frame.text);
|
|
1930
|
+
runner.sendMessage(frame.text, void 0, options);
|
|
1859
1931
|
return;
|
|
1860
1932
|
}
|
|
1861
1933
|
const resolved = attachmentStore.resolve(runner.id, frame.attachmentIds);
|
|
1862
1934
|
if (!resolved.ok) throw new Error(`unknown attachment(s): ${resolved.missing.join(", ")}`);
|
|
1863
|
-
runner.sendMessage(frame.text, resolved.attachments);
|
|
1935
|
+
runner.sendMessage(frame.text, resolved.attachments, options);
|
|
1864
1936
|
return;
|
|
1865
1937
|
}
|
|
1866
1938
|
case "permission_decision":
|
|
@@ -2740,7 +2812,6 @@ var ProfileService = class {
|
|
|
2740
2812
|
if (!hasEngineRunnerFactory) return `profile '${p.name}' uses engine 'provider' but no \`createEngineRunner\` was provided to build one`;
|
|
2741
2813
|
} else if (p.engine === "codex") {
|
|
2742
2814
|
if (p.codexHome && !existsSync(p.codexHome)) return `profile '${p.name}' codexHome does not exist: ${p.codexHome}`;
|
|
2743
|
-
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`;
|
|
2744
2815
|
} else if (!p.configDir || !existsSync(p.configDir)) return `profile '${p.name}' configDir does not exist: ${p.configDir}`;
|
|
2745
2816
|
if (disableBypassPermissions && p.defaults?.permissionMode === "bypassPermissions") return `profile '${p.name}' defaults to bypassPermissions but disableBypassPermissions is set`;
|
|
2746
2817
|
const fallbackMode = p.defaults?.permissionMode;
|
|
@@ -3164,6 +3235,44 @@ function createPeerService(deps) {
|
|
|
3164
3235
|
queued: before === "running" || before === "awaiting_approval"
|
|
3165
3236
|
};
|
|
3166
3237
|
};
|
|
3238
|
+
const mentions = async (from, text) => {
|
|
3239
|
+
const tokens = scanPeerMentions(text);
|
|
3240
|
+
if (tokens.length === 0) return [];
|
|
3241
|
+
const rows = await list(from);
|
|
3242
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
3243
|
+
const add = (key, row) => {
|
|
3244
|
+
const held = byKey.get(key);
|
|
3245
|
+
if (held) held.push(row);
|
|
3246
|
+
else byKey.set(key, [row]);
|
|
3247
|
+
};
|
|
3248
|
+
for (const row of rows) {
|
|
3249
|
+
add(peerMentionKey(peerMentionSlug(row.title, row.id)), row);
|
|
3250
|
+
add(peerMentionKey(row.id), row);
|
|
3251
|
+
}
|
|
3252
|
+
const resolved = [];
|
|
3253
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3254
|
+
for (const token of tokens) {
|
|
3255
|
+
const key = peerMentionKey(token.body);
|
|
3256
|
+
const exact = byKey.get(key);
|
|
3257
|
+
const matches = exact ?? (key.length >= 4 ? rows.filter((row) => row.id.startsWith(key)) : []);
|
|
3258
|
+
const target = matches[0];
|
|
3259
|
+
if (!target || matches.length > 1 && !exact) continue;
|
|
3260
|
+
if (seen.has(target.id)) continue;
|
|
3261
|
+
seen.add(target.id);
|
|
3262
|
+
const others = matches.slice(1, 4).map((row) => row.id);
|
|
3263
|
+
resolved.push({
|
|
3264
|
+
typed: token.body,
|
|
3265
|
+
id: target.id,
|
|
3266
|
+
name: target.title,
|
|
3267
|
+
engine: target.engine,
|
|
3268
|
+
status: target.status,
|
|
3269
|
+
cwd: target.cwd,
|
|
3270
|
+
...others.length ? { ambiguousWith: others } : {}
|
|
3271
|
+
});
|
|
3272
|
+
if (resolved.length >= PEER_MENTION_MAX) break;
|
|
3273
|
+
}
|
|
3274
|
+
return resolved;
|
|
3275
|
+
};
|
|
3167
3276
|
const watch = (runner) => runner.subscribe((event) => {
|
|
3168
3277
|
if (event.type === "user_message" && !event.origin && !event.synthetic && event.parentToolUseId == null && !event.replay) inbound.delete(runner.id);
|
|
3169
3278
|
}, runner.info().lastSeq);
|
|
@@ -3171,6 +3280,7 @@ function createPeerService(deps) {
|
|
|
3171
3280
|
list,
|
|
3172
3281
|
peek,
|
|
3173
3282
|
send,
|
|
3283
|
+
mentions,
|
|
3174
3284
|
watch
|
|
3175
3285
|
};
|
|
3176
3286
|
}
|
|
@@ -3362,6 +3472,8 @@ function createSessionFactory(deps) {
|
|
|
3362
3472
|
const name = config.profile;
|
|
3363
3473
|
const profile = name !== void 0 ? profiles.get(name) : void 0;
|
|
3364
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`);
|
|
3365
3477
|
const runner = profile && isProviderProfile(profile) ? await deps.createEngineRunner({
|
|
3366
3478
|
config,
|
|
3367
3479
|
profile,
|
|
@@ -3664,6 +3776,7 @@ function createWorkerServer(options = {}) {
|
|
|
3664
3776
|
factory,
|
|
3665
3777
|
registry,
|
|
3666
3778
|
parking,
|
|
3779
|
+
peers,
|
|
3667
3780
|
bridge,
|
|
3668
3781
|
projects,
|
|
3669
3782
|
queue,
|