@workerdeck/server 2.9.0 → 2.11.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 +49 -3
- package/build/index.mjs +338 -12
- package/build/index.mjs.map +1 -1
- package/package.json +5 -5
package/build/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
2
|
-
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, 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, SessionRunnerConfig, ToolExecutionCall, ToolExecutionResult, ToolExecutor, ToolSet } from "@workerdeck/core";
|
|
3
3
|
import { JobQueue, QueueAdapter } from "@workerdeck/queue";
|
|
4
|
-
import { CreateSessionRequest, JobEvent, MessageAttachment, ProfileEngine, ProfileInfo, ProfileUsage, ProviderConfig, SdkSessionSummary, ServerFrame, SessionCapability, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
|
|
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
|
|
6
6
|
type BridgeHubOptions = {
|
|
7
7
|
timeoutMs?: number;
|
|
@@ -61,6 +61,7 @@ type ParkedSessionRecord = {
|
|
|
61
61
|
config: SessionRunnerConfig;
|
|
62
62
|
snapshot: RunnerSnapshot;
|
|
63
63
|
executions: ParkedExecution[];
|
|
64
|
+
cost?: CostLedgerState;
|
|
64
65
|
parkedAt: number;
|
|
65
66
|
};
|
|
66
67
|
type DormantSessionRecord = {
|
|
@@ -70,6 +71,7 @@ type DormantSessionRecord = {
|
|
|
70
71
|
profile?: string;
|
|
71
72
|
config: SessionRunnerConfig;
|
|
72
73
|
sdkSessionId: string;
|
|
74
|
+
cost?: CostLedgerState;
|
|
73
75
|
savedAt: number;
|
|
74
76
|
};
|
|
75
77
|
type StoredSessionRecord = ParkedSessionRecord | DormantSessionRecord;
|
|
@@ -137,6 +139,14 @@ declare class SessionParkManager {
|
|
|
137
139
|
close(): void;
|
|
138
140
|
}
|
|
139
141
|
//#endregion
|
|
142
|
+
//#region src/services/peers.d.ts
|
|
143
|
+
type PeerServiceOptions = {
|
|
144
|
+
enabled?: boolean;
|
|
145
|
+
maxMessageChars?: number;
|
|
146
|
+
perMinute?: number;
|
|
147
|
+
maxHops?: number;
|
|
148
|
+
};
|
|
149
|
+
//#endregion
|
|
140
150
|
//#region src/services/profile-store.d.ts
|
|
141
151
|
type ProfileStore = {
|
|
142
152
|
list(): ProfileInfo[] | Promise<ProfileInfo[]>;
|
|
@@ -146,6 +156,33 @@ type ProfileStore = {
|
|
|
146
156
|
declare function createMemoryProfileStore(seed?: ProfileInfo[]): ProfileStore;
|
|
147
157
|
declare function createFileProfileStore(path?: string): ProfileStore;
|
|
148
158
|
//#endregion
|
|
159
|
+
//#region src/services/spend-ledger.d.ts
|
|
160
|
+
type SpendRecord = {
|
|
161
|
+
days: Record<string, Record<string, ByModel>>;
|
|
162
|
+
};
|
|
163
|
+
type SpendStore = {
|
|
164
|
+
load(): Promise<SpendRecord | undefined>;
|
|
165
|
+
save(record: SpendRecord): Promise<void>;
|
|
166
|
+
};
|
|
167
|
+
type SpendLedgerOptions = {
|
|
168
|
+
store?: SpendStore;
|
|
169
|
+
monthlySubscriptionUsd?: (profile: string) => number | undefined;
|
|
170
|
+
retentionDays?: number;
|
|
171
|
+
saveDebounceMs?: number;
|
|
172
|
+
onError?: (error: unknown) => void;
|
|
173
|
+
};
|
|
174
|
+
declare function dayKey(at: number): string;
|
|
175
|
+
declare class SpendLedger {
|
|
176
|
+
#private;
|
|
177
|
+
constructor(options?: SpendLedgerOptions);
|
|
178
|
+
load(): Promise<void>;
|
|
179
|
+
watch(runner: Runner): () => void;
|
|
180
|
+
spend(profile: string, now?: number): ProfileSpend | undefined;
|
|
181
|
+
flush(): Promise<void>;
|
|
182
|
+
close(): void;
|
|
183
|
+
}
|
|
184
|
+
declare function createFileSpendStore(path: string, onError?: (error: unknown) => void): SpendStore;
|
|
185
|
+
//#endregion
|
|
149
186
|
//#region src/options.d.ts
|
|
150
187
|
type SdkSessionLister = (options: {
|
|
151
188
|
dir?: string;
|
|
@@ -194,6 +231,7 @@ type WorkerServerOptions = {
|
|
|
194
231
|
requireAvailableProfile?: boolean;
|
|
195
232
|
listSdkSessions?: SdkSessionLister;
|
|
196
233
|
queue?: QueueServerOptions;
|
|
234
|
+
peers?: PeerServiceOptions;
|
|
197
235
|
notifications?: SessionNotificationOptions;
|
|
198
236
|
bridge?: BridgeHubOptions;
|
|
199
237
|
parking?: {
|
|
@@ -203,6 +241,14 @@ type WorkerServerOptions = {
|
|
|
203
241
|
persistLive?: boolean;
|
|
204
242
|
onError?: (error: unknown, context: ParkErrorContext) => void;
|
|
205
243
|
};
|
|
244
|
+
pricing?: {
|
|
245
|
+
overrides?: Record<string, ModelRate>;
|
|
246
|
+
};
|
|
247
|
+
spend?: {
|
|
248
|
+
store?: SpendStore;
|
|
249
|
+
monthlySubscriptionUsd?: Record<string, number>;
|
|
250
|
+
onError?: (error: unknown) => void;
|
|
251
|
+
};
|
|
206
252
|
createEngineRunner?: (context: EngineRunnerContext) => Runner | Promise<Runner>;
|
|
207
253
|
engines?: Partial<Record<ProfileEngine, EngineAdapter>>;
|
|
208
254
|
};
|
|
@@ -360,5 +406,5 @@ declare class ProfileUsageTracker {
|
|
|
360
406
|
usage(profile: string, now?: number): ProfileUsage | undefined;
|
|
361
407
|
}
|
|
362
408
|
//#endregion
|
|
363
|
-
export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type CarriedSession, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProducedFile, ProducedFileStore, type ProfileStore, ProfileUsageTracker, type ProviderRunnerOptions, type QueueServerOptions, type ReloadPlan, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, type StoredSessionRecord, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, isDormant, reloadPlan, sandboxedProviderProfile, toDurableRecord };
|
|
409
|
+
export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type CarriedSession, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProducedFile, ProducedFileStore, type ProfileStore, ProfileUsageTracker, type ProviderRunnerOptions, type QueueServerOptions, type ReloadPlan, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, SpendLedger, type SpendRecord, type SpendStore, type StoredSessionRecord, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, dayKey, isDormant, reloadPlan, sandboxedProviderProfile, toDurableRecord };
|
|
364
410
|
//# sourceMappingURL=index.d.mts.map
|
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, SessionRunner, attachmentKind, checkClaudeAuth, createEngineSession, getEngineAdapter, normalizeMediaType } from "@workerdeck/core";
|
|
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";
|
|
4
4
|
import { JobQueue } from "@workerdeck/queue";
|
|
5
|
-
import { ENGINE_CAPABILITIES, PROTOCOL_VERSION, SHELL_COMMAND_MAX, imagePartRef, isJobRun, sessionState, supportsPermissionMode } from "@workerdeck/protocol";
|
|
5
|
+
import { ENGINE_CAPABILITIES, PROTOCOL_VERSION, SHELL_COMMAND_MAX, costOfByModel, imagePartRef, isJobRun, mergeByModel, 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";
|
|
@@ -535,7 +535,7 @@ function subsequenceScore(haystack, needle) {
|
|
|
535
535
|
return score - haystack.length / 100;
|
|
536
536
|
}
|
|
537
537
|
//#endregion
|
|
538
|
-
//#region src/routes/
|
|
538
|
+
//#region src/routes/fs.ts
|
|
539
539
|
function kindRank(type) {
|
|
540
540
|
return type === "dir" ? 0 : 1;
|
|
541
541
|
}
|
|
@@ -1027,7 +1027,8 @@ const EPHEMERAL_CONFIG_KEYS = [
|
|
|
1027
1027
|
"queryFn",
|
|
1028
1028
|
"historyFn",
|
|
1029
1029
|
"extraOptions",
|
|
1030
|
-
"env"
|
|
1030
|
+
"env",
|
|
1031
|
+
"peers"
|
|
1031
1032
|
];
|
|
1032
1033
|
function toDurableRecord(record) {
|
|
1033
1034
|
const config = { ...record.config };
|
|
@@ -1290,6 +1291,7 @@ const PROJECT_FILE = ".workerdeck.json";
|
|
|
1290
1291
|
const MAX_PROJECT_FILE_BYTES = 65536;
|
|
1291
1292
|
const MAX_NAME_CHARS = 80;
|
|
1292
1293
|
const GLYPH_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
1294
|
+
const SHORTCODE_RE = /^[A-Z0-9]{2,5}$/;
|
|
1293
1295
|
const DEFAULT_TTL_MS = 3e4;
|
|
1294
1296
|
const SWEEP_ABOVE = 256;
|
|
1295
1297
|
var ProjectInfoService = class {
|
|
@@ -1361,15 +1363,20 @@ function tryLoad(file, root) {
|
|
|
1361
1363
|
const raw = parsed;
|
|
1362
1364
|
const name = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim().slice(0, MAX_NAME_CHARS) : basename(root);
|
|
1363
1365
|
const icon = classifyIcon(raw.icon, root);
|
|
1366
|
+
const shortcode = classifyShortcode(raw.shortcode);
|
|
1364
1367
|
return {
|
|
1365
1368
|
project: {
|
|
1366
1369
|
name,
|
|
1367
1370
|
root,
|
|
1368
|
-
...icon ? { icon: icon.wire } : {}
|
|
1371
|
+
...icon ? { icon: icon.wire } : {},
|
|
1372
|
+
...shortcode ? { shortcode } : {}
|
|
1369
1373
|
},
|
|
1370
1374
|
...icon?.resolved ? { icon: icon.resolved } : {}
|
|
1371
1375
|
};
|
|
1372
1376
|
}
|
|
1377
|
+
function classifyShortcode(value) {
|
|
1378
|
+
return typeof value === "string" && SHORTCODE_RE.test(value) ? value : void 0;
|
|
1379
|
+
}
|
|
1373
1380
|
function classifyIcon(value, root) {
|
|
1374
1381
|
if (typeof value !== "string") return;
|
|
1375
1382
|
const declared = value.trim();
|
|
@@ -1806,7 +1813,8 @@ function attachClient(ctx, ws, runner, req, access) {
|
|
|
1806
1813
|
protocolVersion: PROTOCOL_VERSION,
|
|
1807
1814
|
session: ctx.projects.withProject(runner.info()),
|
|
1808
1815
|
replayingFrom: afterSeq,
|
|
1809
|
-
...shellPermitted(ctx.shell, runner, access.operator) ? { shell: true } : {}
|
|
1816
|
+
...shellPermitted(ctx.shell, runner, access.operator) ? { shell: true } : {},
|
|
1817
|
+
...ctx.pricingOverrides ? { pricingOverrides: ctx.pricingOverrides } : {}
|
|
1810
1818
|
});
|
|
1811
1819
|
const unsubscribe = runner.subscribe((event) => send({
|
|
1812
1820
|
type: "event",
|
|
@@ -2493,6 +2501,7 @@ var SessionParkManager = class {
|
|
|
2493
2501
|
meta: info.meta
|
|
2494
2502
|
},
|
|
2495
2503
|
sdkSessionId,
|
|
2504
|
+
cost: runner.costState?.(),
|
|
2496
2505
|
savedAt: Date.now()
|
|
2497
2506
|
};
|
|
2498
2507
|
try {
|
|
@@ -2540,6 +2549,7 @@ var SessionParkManager = class {
|
|
|
2540
2549
|
meta: info.meta
|
|
2541
2550
|
},
|
|
2542
2551
|
snapshot,
|
|
2552
|
+
cost: runner.costState?.(),
|
|
2543
2553
|
executions: snapshot.parked,
|
|
2544
2554
|
parkedAt: Date.now()
|
|
2545
2555
|
};
|
|
@@ -2563,6 +2573,7 @@ var SessionParkManager = class {
|
|
|
2563
2573
|
const config = this.#configs.get(id);
|
|
2564
2574
|
if (!config) return;
|
|
2565
2575
|
if (this.#options.onParking && !this.#options.onParking(id, executions[0])) return;
|
|
2576
|
+
const cost = runner.costState?.();
|
|
2566
2577
|
const snapshot = runner.park();
|
|
2567
2578
|
if (!snapshot) return;
|
|
2568
2579
|
const info = {
|
|
@@ -2578,6 +2589,7 @@ var SessionParkManager = class {
|
|
|
2578
2589
|
config,
|
|
2579
2590
|
snapshot,
|
|
2580
2591
|
executions: snapshot.parked,
|
|
2592
|
+
cost,
|
|
2581
2593
|
parkedAt: Date.now()
|
|
2582
2594
|
};
|
|
2583
2595
|
for (const execution of snapshot.parked) this.#track(id, execution);
|
|
@@ -2623,6 +2635,10 @@ var SessionParkManager = class {
|
|
|
2623
2635
|
});
|
|
2624
2636
|
throw error;
|
|
2625
2637
|
}
|
|
2638
|
+
runner.carryCost?.(record.cost ?? {
|
|
2639
|
+
byModel: record.info.usageByModel,
|
|
2640
|
+
reportedCostUsd: record.info.totalCostUsd
|
|
2641
|
+
});
|
|
2626
2642
|
this.#options.registry.register(runner);
|
|
2627
2643
|
this.remember(id, {
|
|
2628
2644
|
...record.config,
|
|
@@ -2765,6 +2781,8 @@ var ProfileService = class {
|
|
|
2765
2781
|
}
|
|
2766
2782
|
const usage = decorate.usage(p.name);
|
|
2767
2783
|
if (usage) base.usage = usage;
|
|
2784
|
+
const spend = decorate.spend?.(p.name);
|
|
2785
|
+
if (spend) base.spend = spend;
|
|
2768
2786
|
return base;
|
|
2769
2787
|
}
|
|
2770
2788
|
all() {
|
|
@@ -2872,6 +2890,291 @@ function serveWindow(held, now) {
|
|
|
2872
2890
|
};
|
|
2873
2891
|
}
|
|
2874
2892
|
//#endregion
|
|
2893
|
+
//#region src/services/spend-ledger.ts
|
|
2894
|
+
const DEFAULT_RETENTION_DAYS = 92;
|
|
2895
|
+
const DEFAULT_SAVE_DEBOUNCE_MS = 2e3;
|
|
2896
|
+
function dayKey(at) {
|
|
2897
|
+
const d = new Date(at);
|
|
2898
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
2899
|
+
}
|
|
2900
|
+
function daysBack(now, count) {
|
|
2901
|
+
const out = [];
|
|
2902
|
+
for (let i = 0; i < count; i += 1) out.push(dayKey(now - i * 864e5));
|
|
2903
|
+
return out;
|
|
2904
|
+
}
|
|
2905
|
+
function subtract(current, previous) {
|
|
2906
|
+
const delta = {};
|
|
2907
|
+
for (const [model, usage] of Object.entries(current)) {
|
|
2908
|
+
const was = previous[model];
|
|
2909
|
+
const row = {
|
|
2910
|
+
input: usage.input - (was?.input ?? 0),
|
|
2911
|
+
output: usage.output - (was?.output ?? 0),
|
|
2912
|
+
cacheWrite5m: usage.cacheWrite5m - (was?.cacheWrite5m ?? 0),
|
|
2913
|
+
cacheWrite1h: usage.cacheWrite1h - (was?.cacheWrite1h ?? 0),
|
|
2914
|
+
cacheRead: usage.cacheRead - (was?.cacheRead ?? 0)
|
|
2915
|
+
};
|
|
2916
|
+
if (Object.values(row).some((value) => value < 0)) continue;
|
|
2917
|
+
if (Object.values(row).some((value) => value > 0)) delta[model] = row;
|
|
2918
|
+
}
|
|
2919
|
+
return delta;
|
|
2920
|
+
}
|
|
2921
|
+
var SpendLedger = class {
|
|
2922
|
+
#days = {};
|
|
2923
|
+
#lastBanked = /* @__PURE__ */ new Map();
|
|
2924
|
+
#options;
|
|
2925
|
+
#saveTimer;
|
|
2926
|
+
#loaded;
|
|
2927
|
+
constructor(options = {}) {
|
|
2928
|
+
this.#options = options;
|
|
2929
|
+
}
|
|
2930
|
+
load() {
|
|
2931
|
+
this.#loaded ??= this.#load();
|
|
2932
|
+
return this.#loaded;
|
|
2933
|
+
}
|
|
2934
|
+
async #load() {
|
|
2935
|
+
const store = this.#options.store;
|
|
2936
|
+
if (!store) return;
|
|
2937
|
+
try {
|
|
2938
|
+
const record = await store.load();
|
|
2939
|
+
if (record?.days) this.#days = record.days;
|
|
2940
|
+
} catch (error) {
|
|
2941
|
+
this.#options.onError?.(error);
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
watch(runner) {
|
|
2945
|
+
const info = runner.info();
|
|
2946
|
+
const profile = info.profile;
|
|
2947
|
+
if (!profile) return () => {};
|
|
2948
|
+
this.#lastBanked.set(runner.id, info.usageByModel ?? {});
|
|
2949
|
+
const unsubscribe = runner.subscribe((event) => {
|
|
2950
|
+
if (event.type !== "turn_result" || !event.usageByModel) return;
|
|
2951
|
+
this.#bank(profile, event.usageByModel, runner.id, event.ts);
|
|
2952
|
+
});
|
|
2953
|
+
return () => {
|
|
2954
|
+
unsubscribe();
|
|
2955
|
+
this.#lastBanked.delete(runner.id);
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
#bank(profile, cumulative, sessionId, at) {
|
|
2959
|
+
const delta = subtract(cumulative, this.#lastBanked.get(sessionId) ?? {});
|
|
2960
|
+
this.#lastBanked.set(sessionId, cumulative);
|
|
2961
|
+
if (Object.keys(delta).length === 0) return;
|
|
2962
|
+
const key = dayKey(at);
|
|
2963
|
+
const day = this.#days[key] ??= {};
|
|
2964
|
+
day[profile] = mergeByModel(day[profile] ?? {}, delta);
|
|
2965
|
+
this.#scheduleSave();
|
|
2966
|
+
}
|
|
2967
|
+
spend(profile, now = Date.now()) {
|
|
2968
|
+
const week = this.#window(profile, daysBack(now, 7));
|
|
2969
|
+
const month = this.#window(profile, daysBack(now, 30));
|
|
2970
|
+
if (Object.keys(month).length === 0) return;
|
|
2971
|
+
const weekCost = costOfByModel(week);
|
|
2972
|
+
const monthlySubscriptionUsd = this.#options.monthlySubscriptionUsd?.(profile);
|
|
2973
|
+
return {
|
|
2974
|
+
weekUsd: weekCost.total,
|
|
2975
|
+
monthUsd: costOfByModel(month).total,
|
|
2976
|
+
weekByModel: week,
|
|
2977
|
+
unpricedShare: weekCost.unpricedShare,
|
|
2978
|
+
monthlySubscriptionUsd,
|
|
2979
|
+
subscription: monthlySubscriptionUsd ? subscriptionComparison(weekCost.total, monthlySubscriptionUsd) : void 0
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2982
|
+
#window(profile, keys) {
|
|
2983
|
+
let out = {};
|
|
2984
|
+
for (const key of keys) {
|
|
2985
|
+
const day = this.#days[key]?.[profile];
|
|
2986
|
+
if (day) out = mergeByModel(out, day);
|
|
2987
|
+
}
|
|
2988
|
+
return out;
|
|
2989
|
+
}
|
|
2990
|
+
#scheduleSave() {
|
|
2991
|
+
if (!this.#options.store || this.#saveTimer) return;
|
|
2992
|
+
this.#saveTimer = setTimeout(() => {
|
|
2993
|
+
this.#saveTimer = void 0;
|
|
2994
|
+
this.flush();
|
|
2995
|
+
}, this.#options.saveDebounceMs ?? DEFAULT_SAVE_DEBOUNCE_MS);
|
|
2996
|
+
this.#saveTimer.unref?.();
|
|
2997
|
+
}
|
|
2998
|
+
async flush() {
|
|
2999
|
+
const store = this.#options.store;
|
|
3000
|
+
if (!store) return;
|
|
3001
|
+
this.#prune();
|
|
3002
|
+
try {
|
|
3003
|
+
await store.save({ days: this.#days });
|
|
3004
|
+
} catch (error) {
|
|
3005
|
+
this.#options.onError?.(error);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
#prune() {
|
|
3009
|
+
const keep = new Set(daysBack(Date.now(), this.#options.retentionDays ?? DEFAULT_RETENTION_DAYS));
|
|
3010
|
+
for (const key of Object.keys(this.#days)) if (!keep.has(key)) delete this.#days[key];
|
|
3011
|
+
}
|
|
3012
|
+
close() {
|
|
3013
|
+
clearTimeout(this.#saveTimer);
|
|
3014
|
+
this.#saveTimer = void 0;
|
|
3015
|
+
}
|
|
3016
|
+
};
|
|
3017
|
+
function createFileSpendStore(path, onError) {
|
|
3018
|
+
return {
|
|
3019
|
+
async load() {
|
|
3020
|
+
try {
|
|
3021
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
3022
|
+
if (parsed === null || typeof parsed !== "object" || typeof parsed.days !== "object") return;
|
|
3023
|
+
return parsed;
|
|
3024
|
+
} catch {
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
},
|
|
3028
|
+
async save(record) {
|
|
3029
|
+
try {
|
|
3030
|
+
await mkdir(dirname(path), { recursive: true });
|
|
3031
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
3032
|
+
await writeFile(temp, JSON.stringify(record));
|
|
3033
|
+
await rename(temp, path);
|
|
3034
|
+
} catch (error) {
|
|
3035
|
+
onError?.(error);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
//#endregion
|
|
3041
|
+
//#region src/services/peers.ts
|
|
3042
|
+
const DEFAULT_PER_MINUTE = 10;
|
|
3043
|
+
const DEFAULT_MAX_HOPS = 12;
|
|
3044
|
+
const WINDOW_MS = 6e4;
|
|
3045
|
+
function visible(from, to) {
|
|
3046
|
+
return to.id !== from.id && scopeMatches(from.scope, to.scope);
|
|
3047
|
+
}
|
|
3048
|
+
function createPeerService(deps) {
|
|
3049
|
+
const perMinute = deps.options?.perMinute ?? DEFAULT_PER_MINUTE;
|
|
3050
|
+
const maxChars = deps.options?.maxMessageChars ?? PEER_MESSAGE_MAX_CHARS;
|
|
3051
|
+
const maxHops = deps.options?.maxHops ?? DEFAULT_MAX_HOPS;
|
|
3052
|
+
const sent = /* @__PURE__ */ new Map();
|
|
3053
|
+
const inbound = /* @__PURE__ */ new Map();
|
|
3054
|
+
const registry = () => {
|
|
3055
|
+
const found = deps.refs.registry;
|
|
3056
|
+
if (!found) throw new Error("peer messaging is not ready");
|
|
3057
|
+
return found;
|
|
3058
|
+
};
|
|
3059
|
+
const allSessions = async () => {
|
|
3060
|
+
const live = registry().list();
|
|
3061
|
+
const dormant = await deps.refs.parking?.listInfo() ?? [];
|
|
3062
|
+
return [...live, ...dormant].map((info) => deps.projects.withProject(info));
|
|
3063
|
+
};
|
|
3064
|
+
const infoOf = async (id) => {
|
|
3065
|
+
const live = registry().get(id)?.info();
|
|
3066
|
+
if (live) return deps.projects.withProject(live);
|
|
3067
|
+
const record = await deps.refs.parking?.get(id);
|
|
3068
|
+
return record ? deps.projects.withProject(record.info) : void 0;
|
|
3069
|
+
};
|
|
3070
|
+
const sender = async (from) => {
|
|
3071
|
+
const info = await infoOf(from);
|
|
3072
|
+
if (!info) throw new Error(`unknown sender session: ${from}`);
|
|
3073
|
+
return info;
|
|
3074
|
+
};
|
|
3075
|
+
const underRateLimit = (from, to) => {
|
|
3076
|
+
const key = `${from}->${to}`;
|
|
3077
|
+
const now = Date.now();
|
|
3078
|
+
const recent = (sent.get(key) ?? []).filter((at) => now - at < WINDOW_MS);
|
|
3079
|
+
if (recent.length >= perMinute) {
|
|
3080
|
+
sent.set(key, recent);
|
|
3081
|
+
return false;
|
|
3082
|
+
}
|
|
3083
|
+
recent.push(now);
|
|
3084
|
+
sent.set(key, recent);
|
|
3085
|
+
return true;
|
|
3086
|
+
};
|
|
3087
|
+
const list = async (from) => {
|
|
3088
|
+
const me = await sender(from);
|
|
3089
|
+
return (await allSessions()).filter((info) => visible(me, info)).sort((a, b) => (b.lastActivityAt ?? b.createdAt) - (a.lastActivityAt ?? a.createdAt)).map(peerSummary);
|
|
3090
|
+
};
|
|
3091
|
+
const peek = async (from, sessionId, options) => {
|
|
3092
|
+
const me = await sender(from);
|
|
3093
|
+
const info = await infoOf(sessionId);
|
|
3094
|
+
if (!info || !visible(me, info)) return;
|
|
3095
|
+
const limit = Math.min(Math.max(options?.recent ?? PEER_RECENT_DEFAULT, 0), PEER_RECENT_MAX);
|
|
3096
|
+
const runner = registry().get(sessionId);
|
|
3097
|
+
const events = [];
|
|
3098
|
+
if (runner && limit > 0) runner.subscribe((event) => events.push(event), 0, {
|
|
3099
|
+
truncateResults: true,
|
|
3100
|
+
imageRefs: true
|
|
3101
|
+
})();
|
|
3102
|
+
return {
|
|
3103
|
+
...peerSummary(info),
|
|
3104
|
+
live: runner !== void 0,
|
|
3105
|
+
checklist: info.checklist,
|
|
3106
|
+
pendingApprovals: runner ? runner.pendingApprovals.map((request) => request.title ?? request.toolName) : [],
|
|
3107
|
+
recent: recentLines(events, limit)
|
|
3108
|
+
};
|
|
3109
|
+
};
|
|
3110
|
+
const send = async (from, sessionId, text, options) => {
|
|
3111
|
+
const me = await sender(from);
|
|
3112
|
+
if (sessionId === from) return {
|
|
3113
|
+
delivered: false,
|
|
3114
|
+
reason: "that is this session"
|
|
3115
|
+
};
|
|
3116
|
+
if (text.length > maxChars) return {
|
|
3117
|
+
delivered: false,
|
|
3118
|
+
reason: `message is ${text.length} characters; the limit is ${maxChars}. Write it to a file and send the path.`
|
|
3119
|
+
};
|
|
3120
|
+
const target = await infoOf(sessionId);
|
|
3121
|
+
if (!target || !visible(me, target)) return {
|
|
3122
|
+
delivered: false,
|
|
3123
|
+
reason: `no such session: ${sessionId}`
|
|
3124
|
+
};
|
|
3125
|
+
if (target.status === "closed" || target.status === "failed") return {
|
|
3126
|
+
delivered: false,
|
|
3127
|
+
reason: `session ${sessionId} is ${target.status}`
|
|
3128
|
+
};
|
|
3129
|
+
const hops = [...options?.hops ?? inbound.get(from) ?? [], from];
|
|
3130
|
+
if (hops.length > maxHops) return {
|
|
3131
|
+
delivered: false,
|
|
3132
|
+
reason: `${hops.length} messages have passed between sessions without a person speaking; stop and ask your user before continuing.`
|
|
3133
|
+
};
|
|
3134
|
+
if (!underRateLimit(from, sessionId)) return {
|
|
3135
|
+
delivered: false,
|
|
3136
|
+
reason: `rate limit: at most ${perMinute} messages a minute to one session. Batch what you have to say.`
|
|
3137
|
+
};
|
|
3138
|
+
const runner = await deps.refs.parking?.ensureLive(sessionId) ?? registry().get(sessionId);
|
|
3139
|
+
if (!runner) return {
|
|
3140
|
+
delivered: false,
|
|
3141
|
+
reason: `session ${sessionId} could not be woken`
|
|
3142
|
+
};
|
|
3143
|
+
const before = runner.info().status;
|
|
3144
|
+
const origin = {
|
|
3145
|
+
kind: "peer",
|
|
3146
|
+
sessionId: from,
|
|
3147
|
+
name: me.title,
|
|
3148
|
+
engine: me.engine,
|
|
3149
|
+
hops
|
|
3150
|
+
};
|
|
3151
|
+
try {
|
|
3152
|
+
runner.sendMessage(text, void 0, { origin });
|
|
3153
|
+
} catch (error) {
|
|
3154
|
+
return {
|
|
3155
|
+
delivered: false,
|
|
3156
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
3159
|
+
inbound.set(sessionId, hops);
|
|
3160
|
+
return {
|
|
3161
|
+
delivered: true,
|
|
3162
|
+
sessionId,
|
|
3163
|
+
name: target.title,
|
|
3164
|
+
queued: before === "running" || before === "awaiting_approval"
|
|
3165
|
+
};
|
|
3166
|
+
};
|
|
3167
|
+
const watch = (runner) => runner.subscribe((event) => {
|
|
3168
|
+
if (event.type === "user_message" && !event.origin && !event.synthetic && event.parentToolUseId == null && !event.replay) inbound.delete(runner.id);
|
|
3169
|
+
}, runner.info().lastSeq);
|
|
3170
|
+
return {
|
|
3171
|
+
list,
|
|
3172
|
+
peek,
|
|
3173
|
+
send,
|
|
3174
|
+
watch
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
//#endregion
|
|
2875
3178
|
//#region src/services/registry.ts
|
|
2876
3179
|
var SessionRegistry = class {
|
|
2877
3180
|
#sessions = /* @__PURE__ */ new Map();
|
|
@@ -3051,7 +3354,11 @@ function createSessionFactory(deps) {
|
|
|
3051
3354
|
return engineOf(profile) === "claude" ? claudeSessionEnv(profile, process.env) : process.env;
|
|
3052
3355
|
}
|
|
3053
3356
|
};
|
|
3054
|
-
const buildRunner = async (
|
|
3357
|
+
const buildRunner = async (built, restore, id) => {
|
|
3358
|
+
const config = deps.peers ? {
|
|
3359
|
+
...built,
|
|
3360
|
+
peers: deps.peers
|
|
3361
|
+
} : built;
|
|
3055
3362
|
const name = config.profile;
|
|
3056
3363
|
const profile = name !== void 0 ? profiles.get(name) : void 0;
|
|
3057
3364
|
if (name !== void 0 && !profile) throw new Error(`unknown profile: ${name}`);
|
|
@@ -3172,8 +3479,16 @@ function createWorkerServer(options = {}) {
|
|
|
3172
3479
|
const corsOrigins = options.cors?.origins.length ? new Set(options.cors.origins) : void 0;
|
|
3173
3480
|
const maxBodyBytes = options.maxBodyBytes ?? 1048576;
|
|
3174
3481
|
const adapterFor = (engine) => options.engines?.[engine ?? "claude"] ?? getEngineAdapter(engine);
|
|
3482
|
+
const pricing = setPricingOverrides(options.pricing?.overrides);
|
|
3483
|
+
if (pricing.dropped.length > 0) console.warn(`[workerdeck] Ignoring pricing.overrides for ${pricing.dropped.join(", ")}: each entry needs input, output, cacheWrite5m, cacheWrite1h and cacheRead in USD per million tokens`);
|
|
3175
3484
|
const profileDefaultModels = /* @__PURE__ */ new Map();
|
|
3176
3485
|
const profileUsage = new ProfileUsageTracker();
|
|
3486
|
+
const spendLedger = new SpendLedger({
|
|
3487
|
+
store: options.spend?.store,
|
|
3488
|
+
monthlySubscriptionUsd: (name) => options.spend?.monthlySubscriptionUsd?.[name] ?? options.spend?.monthlySubscriptionUsd?.["*"],
|
|
3489
|
+
onError: (error) => options.spend?.onError?.(error)
|
|
3490
|
+
});
|
|
3491
|
+
spendLedger.load();
|
|
3177
3492
|
const refreshStaleUsage = (name) => {
|
|
3178
3493
|
const held = profileUsage.usage(name);
|
|
3179
3494
|
const newest = Math.max(0, ...Object.values(held ?? {}).map((window) => window.updatedAt ?? 0));
|
|
@@ -3202,7 +3517,8 @@ function createWorkerServer(options = {}) {
|
|
|
3202
3517
|
usage: (name) => {
|
|
3203
3518
|
refreshStaleUsage(name);
|
|
3204
3519
|
return profileUsage.usage(name);
|
|
3205
|
-
}
|
|
3520
|
+
},
|
|
3521
|
+
spend: (name) => spendLedger.spend(name)
|
|
3206
3522
|
}
|
|
3207
3523
|
});
|
|
3208
3524
|
for (const p of options.profiles ?? []) {
|
|
@@ -3210,6 +3526,13 @@ function createWorkerServer(options = {}) {
|
|
|
3210
3526
|
if (invalid) throw new Error(`createWorkerServer: ${invalid}`);
|
|
3211
3527
|
}
|
|
3212
3528
|
const refs = {};
|
|
3529
|
+
const projects = new ProjectInfoService();
|
|
3530
|
+
const peers = options.peers?.enabled === false ? void 0 : createPeerService({
|
|
3531
|
+
refs,
|
|
3532
|
+
projects,
|
|
3533
|
+
options: options.peers
|
|
3534
|
+
});
|
|
3535
|
+
if (peers) installPeerDirectory(peers);
|
|
3213
3536
|
const factory = createSessionFactory({
|
|
3214
3537
|
adapterFor,
|
|
3215
3538
|
profiles,
|
|
@@ -3219,6 +3542,7 @@ function createWorkerServer(options = {}) {
|
|
|
3219
3542
|
disableBypassPermissions: options.disableBypassPermissions,
|
|
3220
3543
|
approvalTimeoutMs: options.approvalTimeoutMs,
|
|
3221
3544
|
requireApiKey: options.requireApiKey,
|
|
3545
|
+
peers: peers ? peerDirectoryHandle() : void 0,
|
|
3222
3546
|
refs
|
|
3223
3547
|
});
|
|
3224
3548
|
const availability = new AvailabilityTracker({
|
|
@@ -3227,7 +3551,6 @@ function createWorkerServer(options = {}) {
|
|
|
3227
3551
|
adapterFor,
|
|
3228
3552
|
sessionEnvFor: factory.sessionEnvFor
|
|
3229
3553
|
});
|
|
3230
|
-
const projects = new ProjectInfoService();
|
|
3231
3554
|
const notifier = new SessionNotifier({
|
|
3232
3555
|
...options.notifications,
|
|
3233
3556
|
decorateInfo: (info) => projects.withProject(info)
|
|
@@ -3239,7 +3562,9 @@ function createWorkerServer(options = {}) {
|
|
|
3239
3562
|
notifier.watch(runner),
|
|
3240
3563
|
producedFiles.watch(runner),
|
|
3241
3564
|
profileUsage.watch(runner),
|
|
3242
|
-
|
|
3565
|
+
spendLedger.watch(runner),
|
|
3566
|
+
shell?.watch(runner),
|
|
3567
|
+
peers?.watch(runner)
|
|
3243
3568
|
];
|
|
3244
3569
|
const profile = runner.info().profile;
|
|
3245
3570
|
if (profile) detachers.push(runner.subscribe((event) => {
|
|
@@ -3348,7 +3673,8 @@ function createWorkerServer(options = {}) {
|
|
|
3348
3673
|
hostFilesWritable: options.hostFiles?.write === true,
|
|
3349
3674
|
maxHostFileBytes: options.hostFiles?.maxFileBytes ?? 1048576,
|
|
3350
3675
|
maxHostDirEntries: options.hostFiles?.maxEntries ?? 5e3,
|
|
3351
|
-
shell
|
|
3676
|
+
shell,
|
|
3677
|
+
pricingOverrides: Object.keys(pricing.overrides).length > 0 ? pricing.overrides : void 0
|
|
3352
3678
|
};
|
|
3353
3679
|
const handleRequest = async (req, res) => {
|
|
3354
3680
|
const pathname = new URL(req.url ?? "/", "http://internal").pathname;
|
|
@@ -3684,6 +4010,6 @@ function createFileProfileStore(path = join(process.cwd(), ".workerdeck", "profi
|
|
|
3684
4010
|
};
|
|
3685
4011
|
}
|
|
3686
4012
|
//#endregion
|
|
3687
|
-
export { AttachmentStore, BridgeHub, MemorySessionStore, ProducedFileStore, ProfileUsageTracker, SessionNotifier, SessionParkManager, SessionRegistry, createFileProfileStore, createFileSessionStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, isDormant, reloadPlan, sandboxedProviderProfile, toDurableRecord };
|
|
4013
|
+
export { AttachmentStore, BridgeHub, MemorySessionStore, ProducedFileStore, ProfileUsageTracker, SessionNotifier, SessionParkManager, SessionRegistry, SpendLedger, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, dayKey, isDormant, reloadPlan, sandboxedProviderProfile, toDurableRecord };
|
|
3688
4014
|
|
|
3689
4015
|
//# sourceMappingURL=index.mjs.map
|