@standardagents/builder 0.27.12 → 0.28.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/dist/built-in-routes.js +273 -21
- package/dist/built-in-routes.js.map +1 -1
- package/dist/{index-DReIS8eP.d.ts → index-BzXyIJPA.d.ts} +163 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +909 -38
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +24 -2
- package/dist/plugin.js.map +1 -1
- package/dist/runtime.d.ts +127 -5
- package/dist/runtime.js +855 -36
- package/dist/runtime.js.map +1 -1
- package/dist/test.d.ts +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -37,6 +37,55 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
37
37
|
};
|
|
38
38
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
39
39
|
|
|
40
|
+
// src/agents/client-forwarding.ts
|
|
41
|
+
function asForwarding(state) {
|
|
42
|
+
return state;
|
|
43
|
+
}
|
|
44
|
+
function supportsClientForwarding(state) {
|
|
45
|
+
const s = asForwarding(state);
|
|
46
|
+
return typeof s.callClient === "function" && typeof s.callClientStream === "function";
|
|
47
|
+
}
|
|
48
|
+
function callClient(state, request) {
|
|
49
|
+
const s = asForwarding(state);
|
|
50
|
+
if (typeof s.callClient !== "function") {
|
|
51
|
+
return Promise.resolve({
|
|
52
|
+
ok: false,
|
|
53
|
+
error: "This thread runtime cannot forward tools to a client. Client forwarding requires the DurableThread bridge."
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return s.callClient(request);
|
|
57
|
+
}
|
|
58
|
+
function callClientStream(state, request) {
|
|
59
|
+
const s = asForwarding(state);
|
|
60
|
+
if (typeof s.callClientStream !== "function") {
|
|
61
|
+
return Promise.resolve({
|
|
62
|
+
ok: false,
|
|
63
|
+
error: "This thread runtime cannot open client streams. Client streaming requires the DurableThread bridge."
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return s.callClientStream(request);
|
|
67
|
+
}
|
|
68
|
+
function encodeStreamChunk(bytes) {
|
|
69
|
+
let binary = "";
|
|
70
|
+
const step = 32768;
|
|
71
|
+
for (let i = 0; i < bytes.length; i += step) {
|
|
72
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + step));
|
|
73
|
+
}
|
|
74
|
+
return btoa(binary);
|
|
75
|
+
}
|
|
76
|
+
function decodeStreamChunk(data) {
|
|
77
|
+
const binary = atob(data);
|
|
78
|
+
const bytes = new Uint8Array(binary.length);
|
|
79
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
80
|
+
return bytes;
|
|
81
|
+
}
|
|
82
|
+
var CLIENT_STREAM_CHUNK_BYTES;
|
|
83
|
+
var init_client_forwarding = __esm({
|
|
84
|
+
"src/agents/client-forwarding.ts"() {
|
|
85
|
+
CLIENT_STREAM_CHUNK_BYTES = 256 * 1024;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
40
89
|
// src/agents/types.ts
|
|
41
90
|
var SUBAGENT_INITIAL_ARGUMENTS_THREAD_PROPERTY, MAX_STEPS, MAX_RETRIES_PER_MODEL, TIMESTAMP_MULTIPLIER, STREAM_COOLDOWN, BACKOFF_BASE;
|
|
42
91
|
var init_types = __esm({
|
|
@@ -929,6 +978,20 @@ var init_ProviderRegistry = __esm({
|
|
|
929
978
|
const threadEnv = state.thread.metadata?.env ?? state.thread.metadata?.tenvs ?? null;
|
|
930
979
|
const agentBuilderId = state.env.AGENT_BUILDER.idFromName("singleton");
|
|
931
980
|
const agentBuilder = state.env.AGENT_BUILDER.get(agentBuilderId);
|
|
981
|
+
let instanceEnv = null;
|
|
982
|
+
if (typeof agentBuilder.getInstanceEnvEntries === "function") {
|
|
983
|
+
try {
|
|
984
|
+
const entries = await agentBuilder.getInstanceEnvEntries();
|
|
985
|
+
if (Array.isArray(entries) && entries.length > 0) {
|
|
986
|
+
instanceEnv = {};
|
|
987
|
+
for (const entry of entries) {
|
|
988
|
+
if (entry?.name) instanceEnv[entry.name] = entry.value;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
} catch (error) {
|
|
992
|
+
console.warn("buildThreadScopedEnv: instance env lookup failed:", error);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
932
995
|
let userId = state.thread.metadata?.user_id ?? null;
|
|
933
996
|
let parent = state.thread.metadata?.parent ?? null;
|
|
934
997
|
let hops = 0;
|
|
@@ -965,11 +1028,12 @@ var init_ProviderRegistry = __esm({
|
|
|
965
1028
|
console.warn("buildThreadScopedEnv:", diagnostic);
|
|
966
1029
|
}
|
|
967
1030
|
}
|
|
968
|
-
if (!userEnv && !threadEnv) {
|
|
1031
|
+
if (!instanceEnv && !userEnv && !threadEnv) {
|
|
969
1032
|
this.scopeDiagnostics.set(state.env, diagnostic);
|
|
970
1033
|
return state.env;
|
|
971
1034
|
}
|
|
972
|
-
|
|
1035
|
+
if (instanceEnv) diagnostic = `${diagnostic ? diagnostic + "; " : ""}instance env: ${Object.keys(instanceEnv).length} entries`;
|
|
1036
|
+
const merged = { ...state.env, ...instanceEnv ?? {}, ...userEnv ?? {}, ...threadEnv ?? {} };
|
|
973
1037
|
this.scopeDiagnostics.set(merged, diagnostic);
|
|
974
1038
|
return merged;
|
|
975
1039
|
}
|
|
@@ -983,7 +1047,7 @@ var init_ProviderRegistry = __esm({
|
|
|
983
1047
|
* @param thread - Thread instance for loading model from virtual modules
|
|
984
1048
|
* @returns Provider and model definition
|
|
985
1049
|
*/
|
|
986
|
-
async getProvider(modelName, env, thread) {
|
|
1050
|
+
async getProvider(modelName, env, thread, getState) {
|
|
987
1051
|
if (!thread) {
|
|
988
1052
|
throw new Error("Thread instance required for model lookup");
|
|
989
1053
|
}
|
|
@@ -1016,6 +1080,18 @@ var init_ProviderRegistry = __esm({
|
|
|
1016
1080
|
);
|
|
1017
1081
|
}
|
|
1018
1082
|
const config = resolved.providerConfig;
|
|
1083
|
+
if (typeof providerDefinition.createFetch === "function") {
|
|
1084
|
+
const state = getState ? await getState() : void 0;
|
|
1085
|
+
const configWithFetch = {
|
|
1086
|
+
...config,
|
|
1087
|
+
fetch: providerDefinition.createFetch({
|
|
1088
|
+
providerName,
|
|
1089
|
+
config,
|
|
1090
|
+
state
|
|
1091
|
+
})
|
|
1092
|
+
};
|
|
1093
|
+
return this.buildResult(providerFactory(configWithFetch), modelDef, false);
|
|
1094
|
+
}
|
|
1019
1095
|
const fingerprint = this.getProviderFingerprint(providerName, modelDef, config);
|
|
1020
1096
|
const cached = this.providerCache.get(modelName);
|
|
1021
1097
|
if (cached?.fingerprint === fingerprint) {
|
|
@@ -1888,7 +1964,16 @@ var init_LLMRequest = __esm({
|
|
|
1888
1964
|
static async callModel(modelId, context, state, logId) {
|
|
1889
1965
|
const { ProviderRegistry: ProviderRegistry2 } = await Promise.resolve().then(() => (init_providers(), providers_exports));
|
|
1890
1966
|
const providerEnv2 = await ProviderRegistry2.buildThreadScopedEnv(state);
|
|
1891
|
-
const
|
|
1967
|
+
const getThreadState = async () => {
|
|
1968
|
+
const { ThreadStateImpl: ThreadStateImpl2 } = await Promise.resolve().then(() => (init_ThreadStateImpl(), ThreadStateImpl_exports));
|
|
1969
|
+
return ThreadStateImpl2.fromFlowState(state);
|
|
1970
|
+
};
|
|
1971
|
+
const { provider, modelName, modelDef, viaPlatform, platformProxyOrigin } = await ProviderRegistry2.getProvider(
|
|
1972
|
+
modelId,
|
|
1973
|
+
providerEnv2,
|
|
1974
|
+
state.thread.instance,
|
|
1975
|
+
getThreadState
|
|
1976
|
+
);
|
|
1892
1977
|
const streamAbort = new AbortController();
|
|
1893
1978
|
const parentSignal = state.abortController?.signal;
|
|
1894
1979
|
let onParentAbort;
|
|
@@ -4338,8 +4423,17 @@ ${errorLines.join("\n")}`);
|
|
|
4338
4423
|
responseParts.push(`Other tools run: ${successfulToolNames.join(", ")}`);
|
|
4339
4424
|
}
|
|
4340
4425
|
} else {
|
|
4341
|
-
if (toolResponseOptions.include_text_response
|
|
4342
|
-
|
|
4426
|
+
if (toolResponseOptions.include_text_response) {
|
|
4427
|
+
let responseText = textContent;
|
|
4428
|
+
if (!responseText) {
|
|
4429
|
+
const recovered = await state.storage.sql.exec(
|
|
4430
|
+
`SELECT content FROM messages WHERE parent_id = ? AND role = 'assistant' AND content IS NOT NULL AND content != '' AND (tool_calls IS NULL OR tool_calls = '') ORDER BY created_at DESC LIMIT 1`,
|
|
4431
|
+
toolMessageId
|
|
4432
|
+
);
|
|
4433
|
+
const recoveredRow = recovered.toArray()[0];
|
|
4434
|
+
if (recoveredRow?.content) responseText = recoveredRow.content;
|
|
4435
|
+
}
|
|
4436
|
+
if (responseText) responseParts.push(responseText);
|
|
4343
4437
|
}
|
|
4344
4438
|
if (toolResponseOptions.include_tool_calls || toolResponseOptions.include_errors) {
|
|
4345
4439
|
const childAssistantMessagesResult = await state.storage.sql.exec(
|
|
@@ -6433,12 +6527,14 @@ var init_FlowEngine = __esm({
|
|
|
6433
6527
|
} catch (error) {
|
|
6434
6528
|
console.error("Fatal error in executeAsync:", error);
|
|
6435
6529
|
await this.logError(state, error, "fatal_execution_error", "FlowEngine.executeAsync");
|
|
6530
|
+
await this.settleTerminalFailure(state, error);
|
|
6436
6531
|
throw error;
|
|
6437
6532
|
}
|
|
6438
6533
|
} else {
|
|
6439
6534
|
this.executeAsync(state, state.threadId).catch(async (error) => {
|
|
6440
6535
|
console.error("Fatal error in executeAsync:", error);
|
|
6441
6536
|
await this.logError(state, error, "fatal_execution_error", "FlowEngine.executeAsync");
|
|
6537
|
+
await this.settleTerminalFailure(state, error);
|
|
6442
6538
|
});
|
|
6443
6539
|
}
|
|
6444
6540
|
return {
|
|
@@ -6492,10 +6588,27 @@ var init_FlowEngine = __esm({
|
|
|
6492
6588
|
console.warn(
|
|
6493
6589
|
`[FlowEngine] Finalized ${rows.length} orphaned incomplete log(s) for thread ${state.threadId}`
|
|
6494
6590
|
);
|
|
6591
|
+
await state.thread.instance.failPendingMessagesAndBroadcast?.();
|
|
6495
6592
|
} catch (err) {
|
|
6496
6593
|
console.error("[FlowEngine] Failed to finalize orphaned logs (non-fatal):", err);
|
|
6497
6594
|
}
|
|
6498
6595
|
}
|
|
6596
|
+
/**
|
|
6597
|
+
* A terminal execution failure ended the run without a normal assistant
|
|
6598
|
+
* reply. Persist a durable failed assistant message + settle pending
|
|
6599
|
+
* messages so every connected head lands on a visible end state instead of
|
|
6600
|
+
* a stuck "Working" spinner. Best-effort — never masks the original error.
|
|
6601
|
+
*/
|
|
6602
|
+
static async settleTerminalFailure(state, error) {
|
|
6603
|
+
try {
|
|
6604
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
6605
|
+
await state.thread.instance.settleTerminalFailure?.(
|
|
6606
|
+
`The agent run failed unexpectedly: ${detail}`
|
|
6607
|
+
);
|
|
6608
|
+
} catch (settleError) {
|
|
6609
|
+
console.error("[FlowEngine] Failed to settle terminal failure (non-fatal):", settleError);
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6499
6612
|
static async executeAsync(state, threadId) {
|
|
6500
6613
|
const effectiveMaxSteps = Math.max(
|
|
6501
6614
|
MAX_STEPS,
|
|
@@ -12770,6 +12883,7 @@ function runCodeViaThreadRpc(threadInstance, threadId, source, options) {
|
|
|
12770
12883
|
var ThreadStateImpl, ExecutionStateImpl;
|
|
12771
12884
|
var init_ThreadStateImpl = __esm({
|
|
12772
12885
|
"src/agents/ThreadStateImpl.ts"() {
|
|
12886
|
+
init_client_forwarding();
|
|
12773
12887
|
init_types();
|
|
12774
12888
|
init_utilities();
|
|
12775
12889
|
init_code_execution();
|
|
@@ -13354,9 +13468,10 @@ var init_ThreadStateImpl = __esm({
|
|
|
13354
13468
|
}
|
|
13355
13469
|
const instance = this._threadInstance;
|
|
13356
13470
|
const toolCallId = this._flowState?.currentToolCall?.id;
|
|
13357
|
-
const
|
|
13358
|
-
|
|
13359
|
-
|
|
13471
|
+
const canPark = !!(toolCallId && this._executionState && typeof instance.dispatchClientTool === "function");
|
|
13472
|
+
const side = this._flowState?.currentSide === "b" ? "b" : "a";
|
|
13473
|
+
const mustParkForApproval = request.requestPermission != null && await this.forwardedCallNeedsApproval(request);
|
|
13474
|
+
if (mustParkForApproval && canPark && toolCallId && typeof instance.hasBridge === "function" && await instance.hasBridge()) {
|
|
13360
13475
|
await instance.dispatchClientTool(request, toolCallId, side);
|
|
13361
13476
|
this._executionState.stop();
|
|
13362
13477
|
return {
|
|
@@ -13368,6 +13483,55 @@ var init_ThreadStateImpl = __esm({
|
|
|
13368
13483
|
const local = await this._threadInstance.callClientTool(request);
|
|
13369
13484
|
if (local.ok || !this.isNoClient(local)) return local;
|
|
13370
13485
|
const env = this._threadInstance.env;
|
|
13486
|
+
if (env?.AGENT_BUILDER_THREAD) {
|
|
13487
|
+
const seen = /* @__PURE__ */ new Set([this._metadata.id]);
|
|
13488
|
+
let parentId = await this.resolveParentThreadId(this._metadata.id);
|
|
13489
|
+
while (parentId && !seen.has(parentId)) {
|
|
13490
|
+
seen.add(parentId);
|
|
13491
|
+
try {
|
|
13492
|
+
const stub = env.AGENT_BUILDER_THREAD.get(env.AGENT_BUILDER_THREAD.idFromName(parentId));
|
|
13493
|
+
const res = await stub.callClientTool(request);
|
|
13494
|
+
if (res.ok || !this.isNoClient(res)) return res;
|
|
13495
|
+
} catch {
|
|
13496
|
+
}
|
|
13497
|
+
parentId = await this.resolveParentThreadId(parentId);
|
|
13498
|
+
}
|
|
13499
|
+
}
|
|
13500
|
+
if (canPark && toolCallId) {
|
|
13501
|
+
await instance.dispatchClientTool(request, toolCallId, side);
|
|
13502
|
+
if (typeof instance.notifyClientWaiting === "function") {
|
|
13503
|
+
try {
|
|
13504
|
+
await instance.notifyClientWaiting(toolCallId, this._metadata.id, this._metadata.user_id ?? null);
|
|
13505
|
+
} catch {
|
|
13506
|
+
}
|
|
13507
|
+
}
|
|
13508
|
+
this._executionState.stop();
|
|
13509
|
+
return {
|
|
13510
|
+
ok: true,
|
|
13511
|
+
result: `\u23F3 Waiting for your machine to reconnect to run ${request.tool}\u2026`,
|
|
13512
|
+
data: { deferred: true, parked: true, waitingForClient: true, toolCallId }
|
|
13513
|
+
};
|
|
13514
|
+
}
|
|
13515
|
+
return local;
|
|
13516
|
+
}
|
|
13517
|
+
/**
|
|
13518
|
+
* Open a bidirectional byte stream to the connected client (e.g. a raw TCP
|
|
13519
|
+
* tunnel for Worker-side TLS termination). Streams require a LIVE bridge on
|
|
13520
|
+
* this thread and never park — a stream is only meaningful while the client
|
|
13521
|
+
* is attached. Subagents must not call this directly; they proxy whole
|
|
13522
|
+
* requests up to the root thread instead (request-up / stream-down).
|
|
13523
|
+
*/
|
|
13524
|
+
async callClientStream(request) {
|
|
13525
|
+
const instance = this._threadInstance;
|
|
13526
|
+
if (typeof instance.openClientStream !== "function") {
|
|
13527
|
+
return {
|
|
13528
|
+
ok: false,
|
|
13529
|
+
error: "This thread runtime cannot open client streams. Client streaming requires the DurableThread bridge."
|
|
13530
|
+
};
|
|
13531
|
+
}
|
|
13532
|
+
const local = await instance.openClientStream(request);
|
|
13533
|
+
if (local.ok || !this.isNoClient(local)) return local;
|
|
13534
|
+
const env = this._threadInstance.env;
|
|
13371
13535
|
if (!env?.AGENT_BUILDER_THREAD) return local;
|
|
13372
13536
|
const seen = /* @__PURE__ */ new Set([this._metadata.id]);
|
|
13373
13537
|
let parentId = await this.resolveParentThreadId(this._metadata.id);
|
|
@@ -13375,14 +13539,86 @@ var init_ThreadStateImpl = __esm({
|
|
|
13375
13539
|
seen.add(parentId);
|
|
13376
13540
|
try {
|
|
13377
13541
|
const stub = env.AGENT_BUILDER_THREAD.get(env.AGENT_BUILDER_THREAD.idFromName(parentId));
|
|
13378
|
-
|
|
13379
|
-
|
|
13542
|
+
if (typeof stub.proxyStreamOpen === "function") {
|
|
13543
|
+
const opened = await stub.proxyStreamOpen(request);
|
|
13544
|
+
if (opened.ok && opened.id) {
|
|
13545
|
+
return { ok: true, duplex: this.buildProxyDuplex(stub, opened.id) };
|
|
13546
|
+
}
|
|
13547
|
+
if (opened.error && !opened.noClient) {
|
|
13548
|
+
return { ok: false, error: opened.error };
|
|
13549
|
+
}
|
|
13550
|
+
}
|
|
13380
13551
|
} catch {
|
|
13381
13552
|
}
|
|
13382
13553
|
parentId = await this.resolveParentThreadId(parentId);
|
|
13383
13554
|
}
|
|
13384
13555
|
return local;
|
|
13385
13556
|
}
|
|
13557
|
+
/**
|
|
13558
|
+
* Build a client duplex that bridges to a stream opened on an ancestor DO
|
|
13559
|
+
* (the bridge holder) via chunk-pull RPCs. `readable` polls `proxyStreamPull`;
|
|
13560
|
+
* `writable` forwards each chunk with `proxyStreamWrite`; teardown calls
|
|
13561
|
+
* `proxyStreamAbort`.
|
|
13562
|
+
*/
|
|
13563
|
+
buildProxyDuplex(stub, id) {
|
|
13564
|
+
let settleDone;
|
|
13565
|
+
const done = new Promise((r) => settleDone = r);
|
|
13566
|
+
let closed = false;
|
|
13567
|
+
const readable = new ReadableStream({
|
|
13568
|
+
async pull(controller) {
|
|
13569
|
+
while (!closed) {
|
|
13570
|
+
let res;
|
|
13571
|
+
try {
|
|
13572
|
+
res = await stub.proxyStreamPull(id);
|
|
13573
|
+
} catch (error) {
|
|
13574
|
+
controller.error(error instanceof Error ? error : new Error(String(error)));
|
|
13575
|
+
settleDone({ ok: false, error: "proxy stream pull failed" });
|
|
13576
|
+
return;
|
|
13577
|
+
}
|
|
13578
|
+
for (const chunk of res.chunks) {
|
|
13579
|
+
controller.enqueue(decodeStreamChunk(chunk));
|
|
13580
|
+
}
|
|
13581
|
+
if (res.done) {
|
|
13582
|
+
controller.close();
|
|
13583
|
+
settleDone(res.error ? { ok: false, error: res.error } : { ok: true });
|
|
13584
|
+
return;
|
|
13585
|
+
}
|
|
13586
|
+
if (res.chunks.length > 0) return;
|
|
13587
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
13588
|
+
}
|
|
13589
|
+
},
|
|
13590
|
+
cancel() {
|
|
13591
|
+
closed = true;
|
|
13592
|
+
void stub.proxyStreamAbort(id).catch(() => {
|
|
13593
|
+
});
|
|
13594
|
+
}
|
|
13595
|
+
});
|
|
13596
|
+
const writable = new WritableStream({
|
|
13597
|
+
async write(chunk) {
|
|
13598
|
+
await stub.proxyStreamWrite(id, encodeStreamChunk(chunk));
|
|
13599
|
+
},
|
|
13600
|
+
close() {
|
|
13601
|
+
void stub.proxyStreamEnd?.(id).catch?.(() => {
|
|
13602
|
+
});
|
|
13603
|
+
},
|
|
13604
|
+
abort() {
|
|
13605
|
+
closed = true;
|
|
13606
|
+
void stub.proxyStreamAbort(id).catch(() => {
|
|
13607
|
+
});
|
|
13608
|
+
}
|
|
13609
|
+
});
|
|
13610
|
+
return {
|
|
13611
|
+
readable,
|
|
13612
|
+
writable,
|
|
13613
|
+
close: (error) => {
|
|
13614
|
+
closed = true;
|
|
13615
|
+
void stub.proxyStreamAbort(id).catch(() => {
|
|
13616
|
+
});
|
|
13617
|
+
settleDone(error ? { ok: false, error } : { ok: true });
|
|
13618
|
+
},
|
|
13619
|
+
done
|
|
13620
|
+
};
|
|
13621
|
+
}
|
|
13386
13622
|
isNoClient(res) {
|
|
13387
13623
|
return res.ok === false && !!res.data && typeof res.data === "object" && res.data.noClient === true;
|
|
13388
13624
|
}
|
|
@@ -14231,6 +14467,7 @@ declare module 'virtual:@standardagents/builder' {
|
|
|
14231
14467
|
import type { DurableAgentBuilder as BaseDurableAgentBuilder } from '@standardagents/builder/runtime';
|
|
14232
14468
|
import type { CodeExecutionBridge as BaseCodeExecutionBridge } from '@standardagents/builder/runtime';
|
|
14233
14469
|
import type { ThreadWatchdog as BaseThreadWatchdog } from '@standardagents/builder/runtime';
|
|
14470
|
+
import type { DurableUserHub as BaseDurableUserHub } from '@standardagents/builder/runtime';
|
|
14234
14471
|
import type { ThreadEnv } from '@standardagents/builder/runtime';
|
|
14235
14472
|
|
|
14236
14473
|
// ============================================================
|
|
@@ -14715,6 +14952,12 @@ declare module 'virtual:@standardagents/builder' {
|
|
|
14715
14952
|
*/
|
|
14716
14953
|
export class ThreadWatchdog extends BaseThreadWatchdog {}
|
|
14717
14954
|
|
|
14955
|
+
/**
|
|
14956
|
+
* Per-user hub Durable Object (AGENT_BUILDER_USER_HUB binding). Holds the
|
|
14957
|
+
* persistent daemon socket, wake signal, and sharded account KV.
|
|
14958
|
+
*/
|
|
14959
|
+
export class DurableUserHub extends BaseDurableUserHub {}
|
|
14960
|
+
|
|
14718
14961
|
// ============================================================
|
|
14719
14962
|
// Router Function
|
|
14720
14963
|
// ============================================================
|
|
@@ -20886,6 +21129,7 @@ import { DurableThread as _BaseDurableThread } from '@standardagents/builder/run
|
|
|
20886
21129
|
import { DurableAgentBuilder as _BaseDurableAgentBuilder } from '@standardagents/builder/runtime';
|
|
20887
21130
|
import { CodeExecutionBridge as _BaseCodeExecutionBridge } from '@standardagents/builder/runtime';
|
|
20888
21131
|
import { ThreadWatchdog as _BaseThreadWatchdog } from '@standardagents/builder/runtime';
|
|
21132
|
+
import { DurableUserHub as _BaseDurableUserHub } from '@standardagents/builder/runtime';
|
|
20889
21133
|
|
|
20890
21134
|
// Import sip WASM module and readiness helper
|
|
20891
21135
|
// Static import allows workerd to pre-compile the WASM at bundle time
|
|
@@ -21011,6 +21255,12 @@ export class CodeExecutionBridge extends _BaseCodeExecutionBridge {}
|
|
|
21011
21255
|
* probes its DurableThread over RPC.
|
|
21012
21256
|
*/
|
|
21013
21257
|
export class ThreadWatchdog extends _BaseThreadWatchdog {}
|
|
21258
|
+
|
|
21259
|
+
/**
|
|
21260
|
+
* Per-user hub companion. Coordination only (persistent daemon socket, wake
|
|
21261
|
+
* signal, sharded account KV); needs no virtual-module registries.
|
|
21262
|
+
*/
|
|
21263
|
+
export class DurableUserHub extends _BaseDurableUserHub {}
|
|
21014
21264
|
`;
|
|
21015
21265
|
}
|
|
21016
21266
|
async function loadRegistryVirtualModule(context) {
|
|
@@ -21986,6 +22236,10 @@ function createAgentBuilderWorkerConfig(options) {
|
|
|
21986
22236
|
{
|
|
21987
22237
|
name: "AGENT_BUILDER_WATCHDOG",
|
|
21988
22238
|
class_name: "ThreadWatchdog"
|
|
22239
|
+
},
|
|
22240
|
+
{
|
|
22241
|
+
name: "AGENT_BUILDER_USER_HUB",
|
|
22242
|
+
class_name: "DurableUserHub"
|
|
21989
22243
|
}
|
|
21990
22244
|
]
|
|
21991
22245
|
},
|
|
@@ -22003,6 +22257,10 @@ function createAgentBuilderWorkerConfig(options) {
|
|
|
22003
22257
|
{
|
|
22004
22258
|
tag: "v3",
|
|
22005
22259
|
new_sqlite_classes: ["ThreadWatchdog"]
|
|
22260
|
+
},
|
|
22261
|
+
{
|
|
22262
|
+
tag: "v4",
|
|
22263
|
+
new_sqlite_classes: ["DurableUserHub"]
|
|
22006
22264
|
}
|
|
22007
22265
|
]
|
|
22008
22266
|
};
|
|
@@ -22090,7 +22348,7 @@ function agentbuilder(options = {}) {
|
|
|
22090
22348
|
function writeGeneratedWorkerEntry() {
|
|
22091
22349
|
const content = `// Generated by @standardagents/builder.
|
|
22092
22350
|
// Future agents: update builder/src/plugin.ts when changing this entrypoint.
|
|
22093
|
-
import { CodeExecutionBridge, DurableAgentBuilder, DurableThread, ThreadWatchdog, router } from "virtual:@standardagents/builder";
|
|
22351
|
+
import { CodeExecutionBridge, DurableAgentBuilder, DurableThread, ThreadWatchdog, DurableUserHub, router } from "virtual:@standardagents/builder";
|
|
22094
22352
|
|
|
22095
22353
|
export default {
|
|
22096
22354
|
async fetch(request: Request, env: Parameters<typeof router>[1]) {
|
|
@@ -22099,7 +22357,7 @@ export default {
|
|
|
22099
22357
|
},
|
|
22100
22358
|
};
|
|
22101
22359
|
|
|
22102
|
-
export { CodeExecutionBridge, DurableAgentBuilder, DurableThread, ThreadWatchdog };
|
|
22360
|
+
export { CodeExecutionBridge, DurableAgentBuilder, DurableThread, ThreadWatchdog, DurableUserHub };
|
|
22103
22361
|
`;
|
|
22104
22362
|
fs8__default.mkdirSync(outputDir, { recursive: true });
|
|
22105
22363
|
if (!fs8__default.existsSync(generatedWorkerEntry) || fs8__default.readFileSync(generatedWorkerEntry, "utf-8") !== content) {
|
|
@@ -22566,6 +22824,9 @@ async function requireAdmin(request, env) {
|
|
|
22566
22824
|
return result;
|
|
22567
22825
|
}
|
|
22568
22826
|
|
|
22827
|
+
// src/index.ts
|
|
22828
|
+
init_client_forwarding();
|
|
22829
|
+
|
|
22569
22830
|
// src/durable-objects/migrations/001_initial.ts
|
|
22570
22831
|
var migration = {
|
|
22571
22832
|
version: 1,
|
|
@@ -24241,6 +24502,7 @@ function isQualifiedName(name) {
|
|
|
24241
24502
|
}
|
|
24242
24503
|
|
|
24243
24504
|
// src/durable-objects/DurableThread.ts
|
|
24505
|
+
init_client_forwarding();
|
|
24244
24506
|
init_sessionTools();
|
|
24245
24507
|
|
|
24246
24508
|
// src/durable-objects/bridge-owner.ts
|
|
@@ -24471,6 +24733,11 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
24471
24733
|
// setValue() so external KV writes (release/reassign) invalidate it too.
|
|
24472
24734
|
executionOwnerCache = void 0;
|
|
24473
24735
|
pendingClientCalls = /* @__PURE__ */ new Map();
|
|
24736
|
+
// Live bidirectional client byte streams (e.g. TCP tunnels for Worker-side
|
|
24737
|
+
// TLS). Keyed by stream id; each stream is bound to the bridge socket it was
|
|
24738
|
+
// opened on and dies with it. Streams are in-memory only — they exist solely
|
|
24739
|
+
// while the client is attached and never park durably.
|
|
24740
|
+
clientStreams = /* @__PURE__ */ new Map();
|
|
24474
24741
|
alarmQueue;
|
|
24475
24742
|
alarmQueueRearmed = false;
|
|
24476
24743
|
currentAbortController = null;
|
|
@@ -24530,6 +24797,9 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
24530
24797
|
case "watchdogCheck":
|
|
24531
24798
|
await this.watchdogCheck(args.threadId);
|
|
24532
24799
|
break;
|
|
24800
|
+
case "clientParkTimeout":
|
|
24801
|
+
await this.failParkedClientCall(args.toolCallId, args.threadId);
|
|
24802
|
+
break;
|
|
24533
24803
|
default:
|
|
24534
24804
|
throw new Error(`Unknown method: ${method}`);
|
|
24535
24805
|
}
|
|
@@ -26513,6 +26783,69 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26513
26783
|
* Stop the currently executing thread (RPC method)
|
|
26514
26784
|
* Simple "off" switch - stops turns, only cleared by user messages
|
|
26515
26785
|
*/
|
|
26786
|
+
/**
|
|
26787
|
+
* Flip every still-`pending` message to `failed` and broadcast the change to
|
|
26788
|
+
* all connected heads. This is THE settle path for any terminal condition
|
|
26789
|
+
* that leaves a turn half-open — user stop, lost execution, terminal
|
|
26790
|
+
* execution failure, and expired parked forwarded calls — so no head is
|
|
26791
|
+
* left rendering a stuck "Working" state. Returns the failure timestamp.
|
|
26792
|
+
*/
|
|
26793
|
+
async failPendingMessagesAndBroadcast() {
|
|
26794
|
+
const now = Date.now() * TIMESTAMP_MULTIPLIER;
|
|
26795
|
+
await this.ctx.storage.sql.exec(
|
|
26796
|
+
`UPDATE messages SET status = 'failed', response_completed_at = ? WHERE status = 'pending'`,
|
|
26797
|
+
now
|
|
26798
|
+
);
|
|
26799
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
26800
|
+
`SELECT id, role, content, tool_calls, tool_call_id, tool_status, created_at, log_id, status, request_sent_at, response_completed_at FROM messages WHERE response_completed_at = ?`,
|
|
26801
|
+
now
|
|
26802
|
+
);
|
|
26803
|
+
for (const row of cursor) {
|
|
26804
|
+
this.broadcastMessage({
|
|
26805
|
+
id: row.id,
|
|
26806
|
+
role: row.role,
|
|
26807
|
+
content: row.content,
|
|
26808
|
+
tool_calls: row.tool_calls,
|
|
26809
|
+
tool_call_id: row.tool_call_id,
|
|
26810
|
+
tool_status: row.tool_status,
|
|
26811
|
+
created_at: row.created_at,
|
|
26812
|
+
log_id: row.log_id,
|
|
26813
|
+
status: "failed",
|
|
26814
|
+
request_sent_at: row.request_sent_at,
|
|
26815
|
+
response_completed_at: row.response_completed_at
|
|
26816
|
+
});
|
|
26817
|
+
}
|
|
26818
|
+
return now;
|
|
26819
|
+
}
|
|
26820
|
+
/**
|
|
26821
|
+
* A terminal failure ended a turn without a normal assistant reply: settle
|
|
26822
|
+
* every pending message and persist a durable `failed` assistant message so
|
|
26823
|
+
* all connected heads land on a real, visible end state — never a stuck
|
|
26824
|
+
* "Working" spinner. Used by the FlowEngine's terminal catch and the parked
|
|
26825
|
+
* forwarded-call TTL.
|
|
26826
|
+
*/
|
|
26827
|
+
async settleTerminalFailure(errorMessage) {
|
|
26828
|
+
await this.ensureMigrated();
|
|
26829
|
+
await this.failPendingMessagesAndBroadcast();
|
|
26830
|
+
const messageId = crypto.randomUUID();
|
|
26831
|
+
const timestamp = Date.now() * TIMESTAMP_MULTIPLIER;
|
|
26832
|
+
await this.ctx.storage.sql.exec(
|
|
26833
|
+
`INSERT INTO messages (id, role, content, created_at, status, prompt) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
26834
|
+
messageId,
|
|
26835
|
+
"assistant",
|
|
26836
|
+
errorMessage,
|
|
26837
|
+
timestamp,
|
|
26838
|
+
"failed",
|
|
26839
|
+
null
|
|
26840
|
+
);
|
|
26841
|
+
this.broadcastMessage({
|
|
26842
|
+
id: messageId,
|
|
26843
|
+
role: "assistant",
|
|
26844
|
+
content: errorMessage,
|
|
26845
|
+
created_at: timestamp,
|
|
26846
|
+
status: "failed"
|
|
26847
|
+
});
|
|
26848
|
+
}
|
|
26516
26849
|
async stop() {
|
|
26517
26850
|
await this.ensureMigrated();
|
|
26518
26851
|
try {
|
|
@@ -26522,30 +26855,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26522
26855
|
if (this.currentAbortController) {
|
|
26523
26856
|
this.currentAbortController.abort();
|
|
26524
26857
|
}
|
|
26525
|
-
const now =
|
|
26526
|
-
const updateCursor = await this.ctx.storage.sql.exec(
|
|
26527
|
-
`UPDATE messages SET status = 'failed', response_completed_at = ? WHERE status = 'pending'`,
|
|
26528
|
-
now
|
|
26529
|
-
);
|
|
26530
|
-
const cursor = await this.ctx.storage.sql.exec(
|
|
26531
|
-
`SELECT id, role, content, tool_calls, tool_call_id, tool_status, created_at, log_id, status, request_sent_at, response_completed_at FROM messages WHERE response_completed_at = ?`,
|
|
26532
|
-
now
|
|
26533
|
-
);
|
|
26534
|
-
for (const row of cursor) {
|
|
26535
|
-
this.broadcastMessage({
|
|
26536
|
-
id: row.id,
|
|
26537
|
-
role: row.role,
|
|
26538
|
-
content: row.content,
|
|
26539
|
-
tool_calls: row.tool_calls,
|
|
26540
|
-
tool_call_id: row.tool_call_id,
|
|
26541
|
-
tool_status: row.tool_status,
|
|
26542
|
-
created_at: row.created_at,
|
|
26543
|
-
log_id: row.log_id,
|
|
26544
|
-
status: "failed",
|
|
26545
|
-
request_sent_at: row.request_sent_at,
|
|
26546
|
-
response_completed_at: row.response_completed_at
|
|
26547
|
-
});
|
|
26548
|
-
}
|
|
26858
|
+
const now = await this.failPendingMessagesAndBroadcast();
|
|
26549
26859
|
const messagesWithToolsCursor = await this.ctx.storage.sql.exec(
|
|
26550
26860
|
`SELECT id, tool_calls, depth FROM messages WHERE tool_calls IS NOT NULL AND tool_calls != 'null' AND (depth IS NULL OR depth = 0)`
|
|
26551
26861
|
);
|
|
@@ -28022,6 +28332,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
28022
28332
|
socket.close();
|
|
28023
28333
|
} catch {
|
|
28024
28334
|
}
|
|
28335
|
+
this.failClientStreamsForSocket(socket);
|
|
28025
28336
|
this.failPendingClientCallsIfNoBridge();
|
|
28026
28337
|
}
|
|
28027
28338
|
/**
|
|
@@ -28277,6 +28588,352 @@ ${resultContent}${attachmentSummary}`;
|
|
|
28277
28588
|
}
|
|
28278
28589
|
return { side };
|
|
28279
28590
|
}
|
|
28591
|
+
// ─── Client byte streams (Worker-side TLS tunnels) ─────────────────────────
|
|
28592
|
+
// A stream is a bidirectional byte pipe over the bridge WebSocket: the
|
|
28593
|
+
// client opens a raw TCP socket per the stream's `meta` and shuttles bytes
|
|
28594
|
+
// both ways; the Worker terminates TLS over those bytes (node:tls over the
|
|
28595
|
+
// duplex), so the client only ever relays ciphertext. Streams are bound to a
|
|
28596
|
+
// live socket and never park.
|
|
28597
|
+
/** Default time to wait for the client to accept a stream open. */
|
|
28598
|
+
static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
|
|
28599
|
+
/** Open a bidirectional byte stream to the connected owner client. */
|
|
28600
|
+
async openClientStream(request) {
|
|
28601
|
+
const owner = await this.getExecutionOwner();
|
|
28602
|
+
const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
|
|
28603
|
+
if (!socket) {
|
|
28604
|
+
return {
|
|
28605
|
+
ok: false,
|
|
28606
|
+
error: "The workspace is not available right now. Try again in a moment.",
|
|
28607
|
+
data: { noClient: true }
|
|
28608
|
+
};
|
|
28609
|
+
}
|
|
28610
|
+
const id = crypto.randomUUID();
|
|
28611
|
+
let controller = null;
|
|
28612
|
+
const readable = new ReadableStream({
|
|
28613
|
+
start: (c) => {
|
|
28614
|
+
controller = c;
|
|
28615
|
+
},
|
|
28616
|
+
cancel: () => {
|
|
28617
|
+
this.abortClientStream(id, "The stream reader was canceled.");
|
|
28618
|
+
}
|
|
28619
|
+
});
|
|
28620
|
+
let settleDone;
|
|
28621
|
+
const done = new Promise((resolve2) => {
|
|
28622
|
+
settleDone = resolve2;
|
|
28623
|
+
});
|
|
28624
|
+
const state = {
|
|
28625
|
+
socket,
|
|
28626
|
+
controller,
|
|
28627
|
+
opened: null,
|
|
28628
|
+
openTimer: null,
|
|
28629
|
+
settleDone,
|
|
28630
|
+
settled: false
|
|
28631
|
+
};
|
|
28632
|
+
this.clientStreams.set(id, state);
|
|
28633
|
+
this.bridgeLastSeen.set(socket, Date.now());
|
|
28634
|
+
const openResult = await new Promise((resolve2) => {
|
|
28635
|
+
state.opened = resolve2;
|
|
28636
|
+
const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
|
|
28637
|
+
state.openTimer = setTimeout(() => {
|
|
28638
|
+
resolve2({
|
|
28639
|
+
ok: false,
|
|
28640
|
+
error: `The client did not accept the ${request.tool} stream within ${Math.round(timeoutMs / 1e3)}s.`
|
|
28641
|
+
});
|
|
28642
|
+
}, timeoutMs);
|
|
28643
|
+
try {
|
|
28644
|
+
socket.send(
|
|
28645
|
+
JSON.stringify({
|
|
28646
|
+
type: "stream_open",
|
|
28647
|
+
id,
|
|
28648
|
+
tool: request.tool,
|
|
28649
|
+
meta: request.meta ?? {}
|
|
28650
|
+
})
|
|
28651
|
+
);
|
|
28652
|
+
} catch (error) {
|
|
28653
|
+
resolve2({
|
|
28654
|
+
ok: false,
|
|
28655
|
+
error: `Failed to reach the client: ${error instanceof Error ? error.message : String(error)}`
|
|
28656
|
+
});
|
|
28657
|
+
}
|
|
28658
|
+
});
|
|
28659
|
+
state.opened = null;
|
|
28660
|
+
if (state.openTimer) {
|
|
28661
|
+
clearTimeout(state.openTimer);
|
|
28662
|
+
state.openTimer = null;
|
|
28663
|
+
}
|
|
28664
|
+
if (!openResult.ok) {
|
|
28665
|
+
this.clientStreams.delete(id);
|
|
28666
|
+
state.settled = true;
|
|
28667
|
+
settleDone({ ok: false, error: openResult.error });
|
|
28668
|
+
return { ok: false, error: openResult.error ?? "Stream open failed." };
|
|
28669
|
+
}
|
|
28670
|
+
const sendChunked = (bytes) => {
|
|
28671
|
+
for (let i = 0; i < bytes.length; i += CLIENT_STREAM_CHUNK_BYTES) {
|
|
28672
|
+
const chunk = bytes.subarray(i, i + CLIENT_STREAM_CHUNK_BYTES);
|
|
28673
|
+
socket.send(JSON.stringify({ type: "stream_data", id, data: encodeStreamChunk(chunk) }));
|
|
28674
|
+
}
|
|
28675
|
+
};
|
|
28676
|
+
const writable = new WritableStream({
|
|
28677
|
+
write: (chunk) => {
|
|
28678
|
+
if (!this.clientStreams.has(id)) {
|
|
28679
|
+
throw new Error("The client stream is closed.");
|
|
28680
|
+
}
|
|
28681
|
+
sendChunked(chunk);
|
|
28682
|
+
},
|
|
28683
|
+
close: () => {
|
|
28684
|
+
try {
|
|
28685
|
+
if (this.clientStreams.has(id)) {
|
|
28686
|
+
socket.send(JSON.stringify({ type: "stream_end", id }));
|
|
28687
|
+
}
|
|
28688
|
+
} catch {
|
|
28689
|
+
}
|
|
28690
|
+
},
|
|
28691
|
+
abort: (reason) => {
|
|
28692
|
+
this.abortClientStream(id, reason instanceof Error ? reason.message : String(reason));
|
|
28693
|
+
}
|
|
28694
|
+
});
|
|
28695
|
+
const duplex = {
|
|
28696
|
+
readable,
|
|
28697
|
+
writable,
|
|
28698
|
+
close: (error) => this.abortClientStream(id, error),
|
|
28699
|
+
done
|
|
28700
|
+
};
|
|
28701
|
+
return { ok: true, duplex };
|
|
28702
|
+
}
|
|
28703
|
+
// ─── Subagent stream proxy-up (chunk-pull, no cross-DO ReadableStream) ─────
|
|
28704
|
+
// A subagent (child thread, no bridge) proxies its whole stream request UP to
|
|
28705
|
+
// this bridge-holding root. We open the client stream locally, then buffer
|
|
28706
|
+
// incoming bytes so the child can drain them over plain request/response RPCs
|
|
28707
|
+
// — sidestepping the unproven DO-to-DO ReadableStream return.
|
|
28708
|
+
proxyStreams = /* @__PURE__ */ new Map();
|
|
28709
|
+
async proxyStreamOpen(request) {
|
|
28710
|
+
const opened = await this.openClientStream(request);
|
|
28711
|
+
if (!opened.ok) {
|
|
28712
|
+
const noClient = !!(opened.data && opened.data.noClient);
|
|
28713
|
+
return { ok: false, error: opened.error, noClient };
|
|
28714
|
+
}
|
|
28715
|
+
const id = crypto.randomUUID();
|
|
28716
|
+
const duplex = opened.duplex;
|
|
28717
|
+
const reader = duplex.readable.getReader();
|
|
28718
|
+
const writer = duplex.writable.getWriter();
|
|
28719
|
+
const entry = {
|
|
28720
|
+
writer,
|
|
28721
|
+
reader,
|
|
28722
|
+
buffer: [],
|
|
28723
|
+
done: false,
|
|
28724
|
+
error: void 0,
|
|
28725
|
+
pump: Promise.resolve(),
|
|
28726
|
+
close: (error) => duplex.close(error)
|
|
28727
|
+
};
|
|
28728
|
+
entry.pump = (async () => {
|
|
28729
|
+
try {
|
|
28730
|
+
while (true) {
|
|
28731
|
+
const { value, done } = await reader.read();
|
|
28732
|
+
if (done) break;
|
|
28733
|
+
if (value) entry.buffer.push(value);
|
|
28734
|
+
}
|
|
28735
|
+
} catch (error) {
|
|
28736
|
+
entry.error = error instanceof Error ? error.message : String(error);
|
|
28737
|
+
} finally {
|
|
28738
|
+
entry.done = true;
|
|
28739
|
+
}
|
|
28740
|
+
})();
|
|
28741
|
+
void duplex.done.then((d) => {
|
|
28742
|
+
entry.done = true;
|
|
28743
|
+
if (!d.ok && !entry.error) entry.error = d.error;
|
|
28744
|
+
});
|
|
28745
|
+
this.proxyStreams.set(id, entry);
|
|
28746
|
+
return { ok: true, id };
|
|
28747
|
+
}
|
|
28748
|
+
async proxyStreamWrite(id, dataB64) {
|
|
28749
|
+
const entry = this.proxyStreams.get(id);
|
|
28750
|
+
if (!entry) return;
|
|
28751
|
+
try {
|
|
28752
|
+
await entry.writer.write(decodeStreamChunk(dataB64));
|
|
28753
|
+
} catch {
|
|
28754
|
+
}
|
|
28755
|
+
}
|
|
28756
|
+
async proxyStreamPull(id) {
|
|
28757
|
+
const entry = this.proxyStreams.get(id);
|
|
28758
|
+
if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
|
|
28759
|
+
const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
|
|
28760
|
+
if (entry.done && entry.buffer.length === 0) {
|
|
28761
|
+
this.proxyStreams.delete(id);
|
|
28762
|
+
return { chunks, done: true, error: entry.error };
|
|
28763
|
+
}
|
|
28764
|
+
return { chunks, done: false };
|
|
28765
|
+
}
|
|
28766
|
+
async proxyStreamEnd(id) {
|
|
28767
|
+
const entry = this.proxyStreams.get(id);
|
|
28768
|
+
if (!entry) return;
|
|
28769
|
+
try {
|
|
28770
|
+
await entry.writer.close();
|
|
28771
|
+
} catch {
|
|
28772
|
+
}
|
|
28773
|
+
}
|
|
28774
|
+
async proxyStreamAbort(id) {
|
|
28775
|
+
const entry = this.proxyStreams.get(id);
|
|
28776
|
+
if (!entry) return;
|
|
28777
|
+
this.proxyStreams.delete(id);
|
|
28778
|
+
try {
|
|
28779
|
+
entry.close("aborted");
|
|
28780
|
+
} catch {
|
|
28781
|
+
}
|
|
28782
|
+
}
|
|
28783
|
+
/** Settle and remove a client stream exactly once. */
|
|
28784
|
+
settleClientStream(id, result) {
|
|
28785
|
+
const state = this.clientStreams.get(id);
|
|
28786
|
+
if (!state) return;
|
|
28787
|
+
this.clientStreams.delete(id);
|
|
28788
|
+
if (state.openTimer) clearTimeout(state.openTimer);
|
|
28789
|
+
if (state.opened) state.opened({ ok: false, error: result.error ?? "Stream closed." });
|
|
28790
|
+
if (!state.settled) {
|
|
28791
|
+
state.settled = true;
|
|
28792
|
+
try {
|
|
28793
|
+
if (result.ok) state.controller?.close();
|
|
28794
|
+
else state.controller?.error(new Error(result.error ?? "Client stream aborted."));
|
|
28795
|
+
} catch {
|
|
28796
|
+
}
|
|
28797
|
+
state.settleDone(result);
|
|
28798
|
+
}
|
|
28799
|
+
}
|
|
28800
|
+
/** Abort a stream from the Worker side, telling the client to tear down. */
|
|
28801
|
+
abortClientStream(id, error) {
|
|
28802
|
+
const state = this.clientStreams.get(id);
|
|
28803
|
+
if (!state) return;
|
|
28804
|
+
try {
|
|
28805
|
+
if (state.socket.readyState === WebSocket.OPEN) {
|
|
28806
|
+
state.socket.send(JSON.stringify({ type: "stream_abort", id, error: error ?? null }));
|
|
28807
|
+
}
|
|
28808
|
+
} catch {
|
|
28809
|
+
}
|
|
28810
|
+
this.settleClientStream(id, error ? { ok: false, error } : { ok: true });
|
|
28811
|
+
}
|
|
28812
|
+
/** Handle an incoming stream_* frame from a bridge client. */
|
|
28813
|
+
handleClientStreamFrame(ws, parsed) {
|
|
28814
|
+
const id = typeof parsed.id === "string" ? parsed.id : null;
|
|
28815
|
+
if (!id) return;
|
|
28816
|
+
const state = this.clientStreams.get(id);
|
|
28817
|
+
if (!state || state.socket !== ws) return;
|
|
28818
|
+
switch (parsed.type) {
|
|
28819
|
+
case "stream_opened": {
|
|
28820
|
+
if (state.opened) {
|
|
28821
|
+
const resolve2 = state.opened;
|
|
28822
|
+
state.opened = null;
|
|
28823
|
+
if (state.openTimer) {
|
|
28824
|
+
clearTimeout(state.openTimer);
|
|
28825
|
+
state.openTimer = null;
|
|
28826
|
+
}
|
|
28827
|
+
resolve2({ ok: true });
|
|
28828
|
+
}
|
|
28829
|
+
return;
|
|
28830
|
+
}
|
|
28831
|
+
case "stream_error": {
|
|
28832
|
+
const error = typeof parsed.error === "string" ? parsed.error : "Client stream error.";
|
|
28833
|
+
this.settleClientStream(id, { ok: false, error });
|
|
28834
|
+
return;
|
|
28835
|
+
}
|
|
28836
|
+
case "stream_data": {
|
|
28837
|
+
if (typeof parsed.data !== "string") return;
|
|
28838
|
+
try {
|
|
28839
|
+
state.controller?.enqueue(decodeStreamChunk(parsed.data));
|
|
28840
|
+
} catch {
|
|
28841
|
+
this.abortClientStream(id, "Failed to deliver stream data.");
|
|
28842
|
+
}
|
|
28843
|
+
return;
|
|
28844
|
+
}
|
|
28845
|
+
case "stream_end": {
|
|
28846
|
+
this.settleClientStream(id, { ok: true });
|
|
28847
|
+
return;
|
|
28848
|
+
}
|
|
28849
|
+
case "stream_abort": {
|
|
28850
|
+
const error = typeof parsed.error === "string" ? parsed.error : void 0;
|
|
28851
|
+
this.settleClientStream(id, { ok: false, error: error ?? "Client aborted the stream." });
|
|
28852
|
+
return;
|
|
28853
|
+
}
|
|
28854
|
+
}
|
|
28855
|
+
}
|
|
28856
|
+
/** Fail every stream bound to a socket that closed or died. */
|
|
28857
|
+
failClientStreamsForSocket(ws) {
|
|
28858
|
+
for (const [id, state] of Array.from(this.clientStreams.entries())) {
|
|
28859
|
+
if (state.socket === ws) {
|
|
28860
|
+
this.settleClientStream(id, { ok: false, error: "The client connection was lost." });
|
|
28861
|
+
}
|
|
28862
|
+
}
|
|
28863
|
+
}
|
|
28864
|
+
// ─── Parked-call wake + bounded TTL ────────────────────────────────────────
|
|
28865
|
+
/** How long a parked forwarded call waits for a client before failing visibly. */
|
|
28866
|
+
static CLIENT_PARK_TTL_MS = 30 * 60 * 1e3;
|
|
28867
|
+
/**
|
|
28868
|
+
* A forwarded call parked with NO client attached: ask the user's hub to
|
|
28869
|
+
* summon the owning machine's daemon to this thread, and arm the bounded
|
|
28870
|
+
* park TTL that turns an unreachable host into a visible durable failure
|
|
28871
|
+
* instead of a silent hang.
|
|
28872
|
+
*/
|
|
28873
|
+
async notifyClientWaiting(toolCallId, threadId, userId) {
|
|
28874
|
+
try {
|
|
28875
|
+
await this.alarmQueue.enqueue(
|
|
28876
|
+
"clientParkTimeout",
|
|
28877
|
+
{ toolCallId, threadId },
|
|
28878
|
+
_DurableThread.CLIENT_PARK_TTL_MS
|
|
28879
|
+
);
|
|
28880
|
+
} catch (error) {
|
|
28881
|
+
console.error("[DurableThread] Failed to arm park TTL (non-fatal):", error);
|
|
28882
|
+
}
|
|
28883
|
+
if (!userId) return;
|
|
28884
|
+
try {
|
|
28885
|
+
const namespace = this.env.AGENT_BUILDER_USER_HUB;
|
|
28886
|
+
if (!namespace) return;
|
|
28887
|
+
const hub = namespace.get(namespace.idFromName(userId));
|
|
28888
|
+
await hub.requestWake(threadId);
|
|
28889
|
+
} catch (error) {
|
|
28890
|
+
console.error("[DurableThread] Hub wake failed (non-fatal):", error);
|
|
28891
|
+
}
|
|
28892
|
+
}
|
|
28893
|
+
/**
|
|
28894
|
+
* Park TTL expired: if the call is still pending, settle it as a durable,
|
|
28895
|
+
* VISIBLE failure — rewrite the waiting placeholder, mark the pending row
|
|
28896
|
+
* failed, fail any still-pending assistant messages, and persist a durable
|
|
28897
|
+
* failed assistant message so every connected head settles instead of
|
|
28898
|
+
* showing "Working" forever.
|
|
28899
|
+
*/
|
|
28900
|
+
async failParkedClientCall(toolCallId, threadId) {
|
|
28901
|
+
await this.ensureMigrated();
|
|
28902
|
+
await this.ensurePendingClientTable();
|
|
28903
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
28904
|
+
`SELECT status, tool FROM pending_client_calls WHERE tool_call_id = ? LIMIT 1`,
|
|
28905
|
+
toolCallId
|
|
28906
|
+
);
|
|
28907
|
+
const row = cursor.toArray()[0];
|
|
28908
|
+
if (!row || row.status !== "pending") return;
|
|
28909
|
+
const errorText = "Could not reach your machine to run this operation. Reconnect it and retry.";
|
|
28910
|
+
await this.ctx.storage.sql.exec(
|
|
28911
|
+
`UPDATE pending_client_calls SET status = 'failed' WHERE tool_call_id = ?`,
|
|
28912
|
+
toolCallId
|
|
28913
|
+
);
|
|
28914
|
+
await this.ctx.storage.sql.exec(
|
|
28915
|
+
`UPDATE messages SET content = ?, tool_status = 'error' WHERE tool_call_id = ? AND role = 'tool'`,
|
|
28916
|
+
`Error: ${errorText}`,
|
|
28917
|
+
toolCallId
|
|
28918
|
+
);
|
|
28919
|
+
try {
|
|
28920
|
+
const msgCursor = await this.ctx.storage.sql.exec(
|
|
28921
|
+
`SELECT id, role, content, tool_call_id, tool_status, created_at, depth
|
|
28922
|
+
FROM messages WHERE tool_call_id = ? AND role = 'tool' LIMIT 1`,
|
|
28923
|
+
toolCallId
|
|
28924
|
+
);
|
|
28925
|
+
const msg = msgCursor.toArray()[0];
|
|
28926
|
+
if (msg) this.broadcastMessage(msg);
|
|
28927
|
+
} catch {
|
|
28928
|
+
}
|
|
28929
|
+
await this.settleTerminalFailure(errorText);
|
|
28930
|
+
if (threadId) {
|
|
28931
|
+
try {
|
|
28932
|
+
await this.disarmExecutionWatchdog(threadId);
|
|
28933
|
+
} catch {
|
|
28934
|
+
}
|
|
28935
|
+
}
|
|
28936
|
+
}
|
|
28280
28937
|
/**
|
|
28281
28938
|
* Broadcast a log record to all connected WebSocket clients
|
|
28282
28939
|
*/
|
|
@@ -28493,6 +29150,10 @@ ${resultContent}${attachmentSummary}`;
|
|
|
28493
29150
|
this.resolveClientCall(ws, parsed);
|
|
28494
29151
|
return;
|
|
28495
29152
|
}
|
|
29153
|
+
if (parsed && typeof parsed.type === "string" && parsed.type.startsWith("stream_") && parsed.type !== "stream_ping" && parsed.type !== "stream_pong") {
|
|
29154
|
+
this.handleClientStreamFrame(ws, parsed);
|
|
29155
|
+
return;
|
|
29156
|
+
}
|
|
28496
29157
|
}
|
|
28497
29158
|
} catch (error) {
|
|
28498
29159
|
console.error("[DurableThread] Error handling WebSocket message:", error);
|
|
@@ -28513,6 +29174,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
28513
29174
|
this.bridgeSockets.delete(ws);
|
|
28514
29175
|
this.bridgeLastSeen.delete(ws);
|
|
28515
29176
|
this.bridgeClientInfo.delete(ws);
|
|
29177
|
+
this.failClientStreamsForSocket(ws);
|
|
28516
29178
|
this.failPendingClientCallsIfNoBridge();
|
|
28517
29179
|
}
|
|
28518
29180
|
}
|
|
@@ -28534,6 +29196,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
28534
29196
|
this.bridgeSockets.delete(ws);
|
|
28535
29197
|
this.bridgeLastSeen.delete(ws);
|
|
28536
29198
|
this.bridgeClientInfo.delete(ws);
|
|
29199
|
+
this.failClientStreamsForSocket(ws);
|
|
28537
29200
|
this.failPendingClientCallsIfNoBridge();
|
|
28538
29201
|
}
|
|
28539
29202
|
}
|
|
@@ -28851,6 +29514,11 @@ ${resultContent}${attachmentSummary}`;
|
|
|
28851
29514
|
data: { id, is_complete: true, error, error_type: errorType }
|
|
28852
29515
|
});
|
|
28853
29516
|
}
|
|
29517
|
+
try {
|
|
29518
|
+
await this.failPendingMessagesAndBroadcast();
|
|
29519
|
+
} catch (error2) {
|
|
29520
|
+
console.error("[DurableThread] Failed to settle pending messages (non-fatal):", error2);
|
|
29521
|
+
}
|
|
28854
29522
|
}
|
|
28855
29523
|
/**
|
|
28856
29524
|
* A subagent whose execution the watchdog gave up on leaves its parent
|
|
@@ -31842,11 +32510,24 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
31842
32510
|
throw new Error("User value user_id must be a non-empty string");
|
|
31843
32511
|
}
|
|
31844
32512
|
}
|
|
32513
|
+
/**
|
|
32514
|
+
* The per-user hub is the sharded home for account-wide KV. When the binding
|
|
32515
|
+
* exists, the singleton delegates so the global object is no longer a
|
|
32516
|
+
* per-user bottleneck; without it (pre-hub deploys, unit tests) it keeps the
|
|
32517
|
+
* local `user_values` table.
|
|
32518
|
+
*/
|
|
32519
|
+
userHub(userId) {
|
|
32520
|
+
const namespace = this.env.AGENT_BUILDER_USER_HUB;
|
|
32521
|
+
if (!namespace) return null;
|
|
32522
|
+
return namespace.get(namespace.idFromName(userId));
|
|
32523
|
+
}
|
|
31845
32524
|
/** Read one JSON value from a user's account-wide key-value store. */
|
|
31846
32525
|
async getUserValue(userId, key) {
|
|
31847
32526
|
await this.ensureMigrated();
|
|
31848
32527
|
this.assertValidUserValueUserId(userId);
|
|
31849
32528
|
this.assertValidUserValueKey(key);
|
|
32529
|
+
const hub = this.userHub(userId);
|
|
32530
|
+
if (hub) return hub.getUserValue(userId, key);
|
|
31850
32531
|
const cursor = await this.ctx.storage.sql.exec(
|
|
31851
32532
|
`SELECT value FROM user_values WHERE user_id = ? AND key = ? LIMIT 1`,
|
|
31852
32533
|
userId,
|
|
@@ -31867,6 +32548,8 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
31867
32548
|
await this.ensureMigrated();
|
|
31868
32549
|
this.assertValidUserValueUserId(userId);
|
|
31869
32550
|
this.assertValidUserValueKey(key);
|
|
32551
|
+
const hub = this.userHub(userId);
|
|
32552
|
+
if (hub) return hub.setUserValue(userId, key, value);
|
|
31870
32553
|
if (value === null || value === void 0) {
|
|
31871
32554
|
await this.ctx.storage.sql.exec(
|
|
31872
32555
|
`DELETE FROM user_values WHERE user_id = ? AND key = ?`,
|
|
@@ -31903,6 +32586,8 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
31903
32586
|
async listUserValues(userId, options = {}) {
|
|
31904
32587
|
await this.ensureMigrated();
|
|
31905
32588
|
this.assertValidUserValueUserId(userId);
|
|
32589
|
+
const hub = this.userHub(userId);
|
|
32590
|
+
if (hub) return hub.listUserValues(userId, options);
|
|
31906
32591
|
const limit = Math.min(Math.max(Math.trunc(options.limit ?? 100), 1), 500);
|
|
31907
32592
|
const offset = Math.max(Math.trunc(options.offset ?? 0), 0);
|
|
31908
32593
|
const prefix = typeof options.prefix === "string" && options.prefix.length > 0 ? options.prefix : null;
|
|
@@ -33651,6 +34336,192 @@ function formatMs(ms) {
|
|
|
33651
34336
|
const minutes = Math.floor(seconds / 60);
|
|
33652
34337
|
return `${minutes}m${String(seconds % 60).padStart(2, "0")}s`;
|
|
33653
34338
|
}
|
|
34339
|
+
var DurableUserHub = class extends DurableObject {
|
|
34340
|
+
/** Persistent client (daemon) sockets, restored across hibernation. */
|
|
34341
|
+
clientSockets = /* @__PURE__ */ new Set();
|
|
34342
|
+
clientInfo = /* @__PURE__ */ new Map();
|
|
34343
|
+
migrated = false;
|
|
34344
|
+
constructor(ctx, env) {
|
|
34345
|
+
super(ctx, env);
|
|
34346
|
+
if (typeof WebSocketRequestResponsePair !== "undefined") {
|
|
34347
|
+
ctx.setWebSocketAutoResponse(
|
|
34348
|
+
new WebSocketRequestResponsePair("stream_ping", "stream_pong")
|
|
34349
|
+
);
|
|
34350
|
+
}
|
|
34351
|
+
for (const ws of ctx.getWebSockets()) {
|
|
34352
|
+
const attachment = ws.deserializeAttachment();
|
|
34353
|
+
if (attachment?.type === "hub_client") {
|
|
34354
|
+
this.clientSockets.add(ws);
|
|
34355
|
+
this.clientInfo.set(ws, {
|
|
34356
|
+
clientId: typeof attachment.clientId === "string" ? attachment.clientId : null,
|
|
34357
|
+
clientName: typeof attachment.clientName === "string" ? attachment.clientName : null,
|
|
34358
|
+
clientKind: typeof attachment.clientKind === "string" ? attachment.clientKind : null
|
|
34359
|
+
});
|
|
34360
|
+
}
|
|
34361
|
+
}
|
|
34362
|
+
}
|
|
34363
|
+
ensureMigrated() {
|
|
34364
|
+
if (this.migrated) return;
|
|
34365
|
+
this.ctx.storage.sql.exec(`
|
|
34366
|
+
CREATE TABLE IF NOT EXISTS user_values (
|
|
34367
|
+
user_id TEXT NOT NULL,
|
|
34368
|
+
key TEXT NOT NULL,
|
|
34369
|
+
value TEXT NOT NULL,
|
|
34370
|
+
created_at INTEGER NOT NULL,
|
|
34371
|
+
updated_at INTEGER NOT NULL,
|
|
34372
|
+
PRIMARY KEY (user_id, key)
|
|
34373
|
+
)
|
|
34374
|
+
`);
|
|
34375
|
+
this.ctx.storage.sql.exec(
|
|
34376
|
+
`CREATE INDEX IF NOT EXISTS idx_user_values_user ON user_values(user_id)`
|
|
34377
|
+
);
|
|
34378
|
+
this.migrated = true;
|
|
34379
|
+
}
|
|
34380
|
+
// ─── Persistent client socket ──────────────────────────────────────────────
|
|
34381
|
+
/**
|
|
34382
|
+
* Accept the user's persistent client (daemon) WebSocket. The daemon holds
|
|
34383
|
+
* this ONE socket for its whole lifetime; per-thread bridge sockets are
|
|
34384
|
+
* opened separately and only while a thread is active.
|
|
34385
|
+
*/
|
|
34386
|
+
async fetch(request) {
|
|
34387
|
+
const url = new URL(request.url);
|
|
34388
|
+
if (request.headers.get("Upgrade") === "websocket") {
|
|
34389
|
+
const pair = new WebSocketPair();
|
|
34390
|
+
const [client, server] = [pair[0], pair[1]];
|
|
34391
|
+
const info = {
|
|
34392
|
+
clientId: url.searchParams.get("client_id"),
|
|
34393
|
+
clientName: url.searchParams.get("client_name"),
|
|
34394
|
+
clientKind: url.searchParams.get("client_kind")
|
|
34395
|
+
};
|
|
34396
|
+
this.ctx.acceptWebSocket(server);
|
|
34397
|
+
server.serializeAttachment({ type: "hub_client", ...info });
|
|
34398
|
+
this.clientSockets.add(server);
|
|
34399
|
+
this.clientInfo.set(server, info);
|
|
34400
|
+
try {
|
|
34401
|
+
server.send(JSON.stringify({ type: "hub_ready" }));
|
|
34402
|
+
} catch {
|
|
34403
|
+
}
|
|
34404
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
34405
|
+
}
|
|
34406
|
+
return new Response("Not found", { status: 404 });
|
|
34407
|
+
}
|
|
34408
|
+
async webSocketMessage(ws, _message) {
|
|
34409
|
+
if (!this.clientSockets.has(ws)) this.clientSockets.add(ws);
|
|
34410
|
+
}
|
|
34411
|
+
async webSocketClose(ws) {
|
|
34412
|
+
this.clientSockets.delete(ws);
|
|
34413
|
+
this.clientInfo.delete(ws);
|
|
34414
|
+
}
|
|
34415
|
+
async webSocketError(ws) {
|
|
34416
|
+
this.clientSockets.delete(ws);
|
|
34417
|
+
this.clientInfo.delete(ws);
|
|
34418
|
+
}
|
|
34419
|
+
/** Whether a persistent client (daemon) is connected right now. */
|
|
34420
|
+
async hasClient() {
|
|
34421
|
+
for (const ws of this.clientSockets) {
|
|
34422
|
+
if (ws.readyState === WebSocket.OPEN) return true;
|
|
34423
|
+
}
|
|
34424
|
+
return false;
|
|
34425
|
+
}
|
|
34426
|
+
// ─── Wake orchestration (the load-bearing RPC) ─────────────────────────────
|
|
34427
|
+
/**
|
|
34428
|
+
* Signal the connected daemon to attach a bridge to `threadId`. Called by a
|
|
34429
|
+
* cold thread DO that parked a forwarded call with no client attached. The
|
|
34430
|
+
* daemon reacts by opening a per-thread bridge socket, which triggers the
|
|
34431
|
+
* thread's drain-on-attach. Returns whether a daemon was reachable.
|
|
34432
|
+
*/
|
|
34433
|
+
async requestWake(threadId) {
|
|
34434
|
+
const frame = { type: "wake", threadId };
|
|
34435
|
+
const payload = JSON.stringify(frame);
|
|
34436
|
+
let delivered = false;
|
|
34437
|
+
for (const ws of Array.from(this.clientSockets)) {
|
|
34438
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
34439
|
+
this.clientSockets.delete(ws);
|
|
34440
|
+
this.clientInfo.delete(ws);
|
|
34441
|
+
continue;
|
|
34442
|
+
}
|
|
34443
|
+
try {
|
|
34444
|
+
ws.send(payload);
|
|
34445
|
+
delivered = true;
|
|
34446
|
+
} catch {
|
|
34447
|
+
this.clientSockets.delete(ws);
|
|
34448
|
+
this.clientInfo.delete(ws);
|
|
34449
|
+
}
|
|
34450
|
+
}
|
|
34451
|
+
return { delivered };
|
|
34452
|
+
}
|
|
34453
|
+
// ─── Account-wide KV (sharded off the singleton) ───────────────────────────
|
|
34454
|
+
async getUserValue(userId, key) {
|
|
34455
|
+
this.ensureMigrated();
|
|
34456
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
34457
|
+
`SELECT value FROM user_values WHERE user_id = ? AND key = ? LIMIT 1`,
|
|
34458
|
+
userId,
|
|
34459
|
+
key
|
|
34460
|
+
);
|
|
34461
|
+
const raw = cursor.toArray()[0]?.value;
|
|
34462
|
+
if (raw === void 0) return null;
|
|
34463
|
+
try {
|
|
34464
|
+
return JSON.parse(raw);
|
|
34465
|
+
} catch {
|
|
34466
|
+
return null;
|
|
34467
|
+
}
|
|
34468
|
+
}
|
|
34469
|
+
async setUserValue(userId, key, value) {
|
|
34470
|
+
this.ensureMigrated();
|
|
34471
|
+
if (value === null || value === void 0) {
|
|
34472
|
+
await this.ctx.storage.sql.exec(
|
|
34473
|
+
`DELETE FROM user_values WHERE user_id = ? AND key = ?`,
|
|
34474
|
+
userId,
|
|
34475
|
+
key
|
|
34476
|
+
);
|
|
34477
|
+
return;
|
|
34478
|
+
}
|
|
34479
|
+
const serialized = JSON.stringify(value);
|
|
34480
|
+
const now = Date.now();
|
|
34481
|
+
await this.ctx.storage.sql.exec(
|
|
34482
|
+
`INSERT INTO user_values (user_id, key, value, created_at, updated_at)
|
|
34483
|
+
VALUES (?1, ?2, ?3, ?4, ?4)
|
|
34484
|
+
ON CONFLICT(user_id, key) DO UPDATE SET
|
|
34485
|
+
value = excluded.value,
|
|
34486
|
+
updated_at = excluded.updated_at`,
|
|
34487
|
+
userId,
|
|
34488
|
+
key,
|
|
34489
|
+
serialized,
|
|
34490
|
+
now
|
|
34491
|
+
);
|
|
34492
|
+
}
|
|
34493
|
+
async listUserValues(userId, options = {}) {
|
|
34494
|
+
this.ensureMigrated();
|
|
34495
|
+
const limit = Math.min(Math.max(Math.trunc(options.limit ?? 100), 1), 500);
|
|
34496
|
+
const offset = Math.max(Math.trunc(options.offset ?? 0), 0);
|
|
34497
|
+
const prefix = typeof options.prefix === "string" && options.prefix.length > 0 ? options.prefix : null;
|
|
34498
|
+
const escapedPrefix = prefix ? `${prefix.replace(/[\\%_]/g, (m) => `\\${m}`)}%` : null;
|
|
34499
|
+
const where = escapedPrefix ? `WHERE user_id = ? AND key LIKE ? ESCAPE '\\'` : `WHERE user_id = ?`;
|
|
34500
|
+
const whereArgs = escapedPrefix ? [userId, escapedPrefix] : [userId];
|
|
34501
|
+
const totalCursor = await this.ctx.storage.sql.exec(
|
|
34502
|
+
`SELECT COUNT(*) AS total FROM user_values ${where}`,
|
|
34503
|
+
...whereArgs
|
|
34504
|
+
);
|
|
34505
|
+
const total = Number(totalCursor.toArray()[0]?.total ?? 0);
|
|
34506
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
34507
|
+
`SELECT key, value, updated_at FROM user_values ${where}
|
|
34508
|
+
ORDER BY key ASC LIMIT ? OFFSET ?`,
|
|
34509
|
+
...whereArgs,
|
|
34510
|
+
limit,
|
|
34511
|
+
offset
|
|
34512
|
+
);
|
|
34513
|
+
const entries = cursor.toArray().map((row) => {
|
|
34514
|
+
let value = null;
|
|
34515
|
+
try {
|
|
34516
|
+
value = JSON.parse(row.value);
|
|
34517
|
+
} catch {
|
|
34518
|
+
value = null;
|
|
34519
|
+
}
|
|
34520
|
+
return { key: row.key, value, updated_at: Number(row.updated_at) };
|
|
34521
|
+
});
|
|
34522
|
+
return { entries, total, limit, offset };
|
|
34523
|
+
}
|
|
34524
|
+
};
|
|
33654
34525
|
|
|
33655
34526
|
// src/index.ts
|
|
33656
34527
|
init_utilities();
|
|
@@ -34013,6 +34884,6 @@ init_context();
|
|
|
34013
34884
|
init_ProviderRegistry();
|
|
34014
34885
|
init_types2();
|
|
34015
34886
|
|
|
34016
|
-
export { CodeExecutionBridge, DurableAgentBuilder, DurableThread, FlowStateSdk, NamespaceResolutionError, PackageDiscoveryService, ProviderRegistry, ThreadWatchdog, agentbuilder, authenticate, buildImageDescription, builder, cat, createNamespaceContext, createThreadEndpointHandler, defineController2 as defineController, discoverPackages, emitThreadEvent, enhanceFlowState, exists, find, forceTurn, generateAgentFile, generateImageDescription, generateModelFile, generatePromptFile, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isQualifiedName, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, stat, tail, unlink, updateThread, writeFile, writeImage };
|
|
34887
|
+
export { CLIENT_STREAM_CHUNK_BYTES, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, NamespaceResolutionError, PackageDiscoveryService, ProviderRegistry, ThreadWatchdog, agentbuilder, authenticate, buildImageDescription, builder, callClient, callClientStream, cat, createNamespaceContext, createThreadEndpointHandler, decodeStreamChunk, defineController2 as defineController, discoverPackages, emitThreadEvent, encodeStreamChunk, enhanceFlowState, exists, find, forceTurn, generateAgentFile, generateImageDescription, generateModelFile, generatePromptFile, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isQualifiedName, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, stat, supportsClientForwarding, tail, unlink, updateThread, writeFile, writeImage };
|
|
34017
34888
|
//# sourceMappingURL=index.js.map
|
|
34018
34889
|
//# sourceMappingURL=index.js.map
|