@standardagents/builder 0.28.1 → 0.29.1
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 +66 -12
- package/dist/built-in-routes.js.map +1 -1
- package/dist/{index-BzXyIJPA.d.ts → index-BNLDsLsL.d.ts} +22 -15
- package/dist/index.d.ts +1 -1
- package/dist/index.js +151 -28
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +18 -2
- package/dist/runtime.js +151 -28
- package/dist/runtime.js.map +1 -1
- package/dist/test.d.ts +1 -1
- package/package.json +5 -5
package/dist/runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { e as ThreadEnv, A as Agent, r as ThreadMetadata, t as ToolResult, M as Message, k as ClientToolRequest, l as ClientToolResponse, m as ClientStreamRequest, n as ClientStreamResponse, w as FileRecord, F as FlowState, y as FileStats, G as GrepResult, x as AttachmentRef, J as MessageContent, E as Env, q as ThreadInstance } from './index-
|
|
2
|
-
export { f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, B as Controller, a as ControllerContext, u as FlowResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as callClient, h as callClientStream, c as createThreadEndpointHandler, j as decodeStreamChunk, d as defineController, i as encodeStreamChunk, s as supportsClientForwarding } from './index-
|
|
1
|
+
import { e as ThreadEnv, A as Agent, r as ThreadMetadata, t as ToolResult, M as Message, k as ClientToolRequest, l as ClientToolResponse, m as ClientStreamRequest, n as ClientStreamResponse, w as FileRecord, F as FlowState, y as FileStats, G as GrepResult, x as AttachmentRef, J as MessageContent, E as Env, q as ThreadInstance } from './index-BNLDsLsL.js';
|
|
2
|
+
export { f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, B as Controller, a as ControllerContext, u as FlowResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as callClient, h as callClientStream, c as createThreadEndpointHandler, j as decodeStreamChunk, d as defineController, i as encodeStreamChunk, s as supportsClientForwarding } from './index-BNLDsLsL.js';
|
|
3
3
|
import { CodeExecutionOptions, CodeExecutionResult, SubagentRegistryEntry, InjectMessageInput, QueueMessageInput, SkillMetadata, SkillFileInput, ModelDefinition as ModelDefinition$1, ToolArgs, PromptTextPart, PromptEnvPart, VariableDefinition, SubpromptConfig as SubpromptConfig$1, PromptToolConfig as PromptToolConfig$1, SubagentToolConfig as SubagentToolConfig$1, ReasoningConfig, SideConfig as SideConfig$1, LLMProviderInterface, ContentPart, TextPart, ImagePart, FilePart, ThreadState, NamespaceContext, DefinitionLoader } from '@standardagents/spec';
|
|
4
4
|
export { AgentType, DefinitionLoader, GlobalNamespaceContext, HookSignatures, ImageContent, ModelCapabilities, ModelProvider, NamespaceContext, PackageSignature, PackedExports, PackedMeta, PackedMetadata, PackedNamespaceContext, PromptInput, PromptTextPart, ProviderAssistantMessage, ProviderError, ProviderErrorCode, ProviderFactory, ProviderFactoryConfig, ProviderFinishReason, ProviderGeneratedImage, ProviderMessage, ProviderMessageContent, ModelCapabilities as ProviderModelCapabilities, ProviderReasoningDetail, ProviderRequest, ProviderResponse, ProviderStreamChunk, ProviderSystemMessage, ProviderTool, ProviderToolCallPart, ProviderToolMessage, ProviderToolResultContent, ProviderUsage, ProviderUserMessage, ReasoningConfig, StructuredPrompt, TextContent, Tool, ToolArgs, ToolArgsNode, ToolArgsRawShape, ToolContent, belongsToPackage, defineAgent, defineHook, defineModel, definePrompt, defineTool, isPacked, isVisibleInNamespace, mapReasoningLevel } from '@standardagents/spec';
|
|
5
5
|
import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
|
|
@@ -1112,6 +1112,18 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
|
|
|
1112
1112
|
} | null>;
|
|
1113
1113
|
/** Default time to wait for the client to accept a stream open. */
|
|
1114
1114
|
private static readonly CLIENT_STREAM_OPEN_TIMEOUT_MS;
|
|
1115
|
+
/** Poll cadence while waiting for a disconnected owner's bridge to return. */
|
|
1116
|
+
private static readonly OWNER_REATTACH_POLL_MS;
|
|
1117
|
+
/**
|
|
1118
|
+
* The thread HAS an elected execution owner, but its bridge socket is gone
|
|
1119
|
+
* (daemon restart, network blip, DO handoff). Summon the owning machine
|
|
1120
|
+
* through the per-user hub and wait for a bridge to re-attach instead of
|
|
1121
|
+
* failing the stream open instantly — that instant failure is what ended
|
|
1122
|
+
* Sama One turns with "workspace unavailable" whenever a slow tool/approval
|
|
1123
|
+
* outlived the socket. Threads with NO recorded owner (subagent children)
|
|
1124
|
+
* still fail fast so ancestor walk-up routing stays immediate.
|
|
1125
|
+
*/
|
|
1126
|
+
private awaitOwnerBridgeSocket;
|
|
1115
1127
|
/** Open a bidirectional byte stream to the connected owner client. */
|
|
1116
1128
|
openClientStream(request: ClientStreamRequest): Promise<ClientStreamResponse>;
|
|
1117
1129
|
private proxyStreams;
|
|
@@ -1120,6 +1132,7 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
|
|
|
1120
1132
|
id?: string;
|
|
1121
1133
|
error?: string;
|
|
1122
1134
|
noClient?: boolean;
|
|
1135
|
+
code?: 'unsupported' | 'unavailable' | 'refused' | 'timeout';
|
|
1123
1136
|
}>;
|
|
1124
1137
|
proxyStreamWrite(id: string, dataB64: string): Promise<void>;
|
|
1125
1138
|
proxyStreamPull(id: string): Promise<{
|
|
@@ -1146,6 +1159,9 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
|
|
|
1146
1159
|
* instead of a silent hang.
|
|
1147
1160
|
*/
|
|
1148
1161
|
notifyClientWaiting(toolCallId: string, threadId: string, userId: string | null): Promise<void>;
|
|
1162
|
+
/** Ask the user's hub to summon the owning machine's daemon to a thread.
|
|
1163
|
+
* Best-effort: wake failure must never fail the caller's own path. */
|
|
1164
|
+
private requestHubWake;
|
|
1149
1165
|
/**
|
|
1150
1166
|
* Park TTL expired: if the call is still pending, settle it as a durable,
|
|
1151
1167
|
* VISIBLE failure — rewrite the waiting placeholder, mark the pending row
|
package/dist/runtime.js
CHANGED
|
@@ -47,6 +47,30 @@ function callClient(state, request) {
|
|
|
47
47
|
return s.callClient(request);
|
|
48
48
|
}
|
|
49
49
|
function callClientStream(state, request) {
|
|
50
|
+
if (state.runtimeStreams) {
|
|
51
|
+
return state.runtimeStreams.open({
|
|
52
|
+
capability: request.tool,
|
|
53
|
+
metadata: request.meta,
|
|
54
|
+
timeoutMs: request.timeoutMs
|
|
55
|
+
}).then(
|
|
56
|
+
(opened) => opened.ok ? {
|
|
57
|
+
ok: true,
|
|
58
|
+
duplex: {
|
|
59
|
+
readable: opened.stream.readable,
|
|
60
|
+
writable: opened.stream.writable,
|
|
61
|
+
close: (reason) => opened.stream.close(reason),
|
|
62
|
+
done: opened.stream.closed
|
|
63
|
+
}
|
|
64
|
+
} : {
|
|
65
|
+
ok: false,
|
|
66
|
+
error: opened.error,
|
|
67
|
+
data: opened.code ? {
|
|
68
|
+
code: opened.code,
|
|
69
|
+
noClient: opened.code === "unavailable" ? true : void 0
|
|
70
|
+
} : void 0
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
50
74
|
const s = asForwarding(state);
|
|
51
75
|
if (typeof s.callClientStream !== "function") {
|
|
52
76
|
return Promise.resolve({
|
|
@@ -56,6 +80,39 @@ function callClientStream(state, request) {
|
|
|
56
80
|
}
|
|
57
81
|
return s.callClientStream(request);
|
|
58
82
|
}
|
|
83
|
+
function createRuntimeStreams(openClientStream) {
|
|
84
|
+
return {
|
|
85
|
+
async open(request) {
|
|
86
|
+
const opened = await openClientStream({
|
|
87
|
+
tool: request.capability,
|
|
88
|
+
meta: request.metadata,
|
|
89
|
+
timeoutMs: request.timeoutMs
|
|
90
|
+
});
|
|
91
|
+
if (!opened.ok) {
|
|
92
|
+
const failureData = opened.data;
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: opened.error,
|
|
96
|
+
code: failureData?.code
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
let closed = false;
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
stream: {
|
|
103
|
+
readable: opened.duplex.readable,
|
|
104
|
+
writable: opened.duplex.writable,
|
|
105
|
+
closed: opened.duplex.done,
|
|
106
|
+
close: (reason) => {
|
|
107
|
+
if (closed) return;
|
|
108
|
+
closed = true;
|
|
109
|
+
opened.duplex.close(reason);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
59
116
|
function encodeStreamChunk(bytes) {
|
|
60
117
|
let binary = "";
|
|
61
118
|
const step = 32768;
|
|
@@ -12930,6 +12987,14 @@ var init_ThreadStateImpl = __esm({
|
|
|
12930
12987
|
}
|
|
12931
12988
|
return this.getMetadataArguments();
|
|
12932
12989
|
}
|
|
12990
|
+
/**
|
|
12991
|
+
* Optional spec byte streams backed by the execution owner's existing
|
|
12992
|
+
* bridge. Streams are live resources: they proxy through ancestors when a
|
|
12993
|
+
* subagent opens one, but they never park or outlive a bridge disconnect.
|
|
12994
|
+
*/
|
|
12995
|
+
get runtimeStreams() {
|
|
12996
|
+
return createRuntimeStreams((request) => this.callClientStream(request));
|
|
12997
|
+
}
|
|
12933
12998
|
getMetadataArguments() {
|
|
12934
12999
|
const value = this._metadata.properties?.[SUBAGENT_INITIAL_ARGUMENTS_THREAD_PROPERTY];
|
|
12935
13000
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -13506,11 +13571,9 @@ var init_ThreadStateImpl = __esm({
|
|
|
13506
13571
|
return local;
|
|
13507
13572
|
}
|
|
13508
13573
|
/**
|
|
13509
|
-
*
|
|
13510
|
-
*
|
|
13511
|
-
*
|
|
13512
|
-
* is attached. Subagents must not call this directly; they proxy whole
|
|
13513
|
-
* requests up to the root thread instead (request-up / stream-down).
|
|
13574
|
+
* Legacy bridge implementation used by `runtimeStreams`. Streams require a
|
|
13575
|
+
* live execution owner and never park. A child thread proxies the complete
|
|
13576
|
+
* request to the nearest bridge-holding ancestor (request-up/stream-down).
|
|
13514
13577
|
*/
|
|
13515
13578
|
async callClientStream(request) {
|
|
13516
13579
|
const instance = this._threadInstance;
|
|
@@ -13520,7 +13583,9 @@ var init_ThreadStateImpl = __esm({
|
|
|
13520
13583
|
error: "This thread runtime cannot open client streams. Client streaming requires the DurableThread bridge."
|
|
13521
13584
|
};
|
|
13522
13585
|
}
|
|
13523
|
-
const
|
|
13586
|
+
const wakeUserId = this._metadata.user_id ?? null;
|
|
13587
|
+
const withWake = (threadId) => wakeUserId ? { ...request, wake: { threadId, userId: wakeUserId } } : request;
|
|
13588
|
+
const local = await instance.openClientStream(withWake(this._metadata.id));
|
|
13524
13589
|
if (local.ok || !this.isNoClient(local)) return local;
|
|
13525
13590
|
const env = this._threadInstance.env;
|
|
13526
13591
|
if (!env?.AGENT_BUILDER_THREAD) return local;
|
|
@@ -13531,12 +13596,16 @@ var init_ThreadStateImpl = __esm({
|
|
|
13531
13596
|
try {
|
|
13532
13597
|
const stub = env.AGENT_BUILDER_THREAD.get(env.AGENT_BUILDER_THREAD.idFromName(parentId));
|
|
13533
13598
|
if (typeof stub.proxyStreamOpen === "function") {
|
|
13534
|
-
const opened = await stub.proxyStreamOpen(
|
|
13599
|
+
const opened = await stub.proxyStreamOpen(withWake(parentId));
|
|
13535
13600
|
if (opened.ok && opened.id) {
|
|
13536
13601
|
return { ok: true, duplex: this.buildProxyDuplex(stub, opened.id) };
|
|
13537
13602
|
}
|
|
13538
13603
|
if (opened.error && !opened.noClient) {
|
|
13539
|
-
return {
|
|
13604
|
+
return {
|
|
13605
|
+
ok: false,
|
|
13606
|
+
error: opened.error,
|
|
13607
|
+
data: opened.code ? { code: opened.code } : void 0
|
|
13608
|
+
};
|
|
13540
13609
|
}
|
|
13541
13610
|
}
|
|
13542
13611
|
} catch {
|
|
@@ -13555,6 +13624,12 @@ var init_ThreadStateImpl = __esm({
|
|
|
13555
13624
|
let settleDone;
|
|
13556
13625
|
const done = new Promise((r) => settleDone = r);
|
|
13557
13626
|
let closed = false;
|
|
13627
|
+
let settled = false;
|
|
13628
|
+
const settle = (result) => {
|
|
13629
|
+
if (settled) return;
|
|
13630
|
+
settled = true;
|
|
13631
|
+
settleDone(result);
|
|
13632
|
+
};
|
|
13558
13633
|
const readable = new ReadableStream({
|
|
13559
13634
|
async pull(controller) {
|
|
13560
13635
|
while (!closed) {
|
|
@@ -13563,7 +13638,7 @@ var init_ThreadStateImpl = __esm({
|
|
|
13563
13638
|
res = await stub.proxyStreamPull(id);
|
|
13564
13639
|
} catch (error) {
|
|
13565
13640
|
controller.error(error instanceof Error ? error : new Error(String(error)));
|
|
13566
|
-
|
|
13641
|
+
settle({ ok: false, error: "proxy stream pull failed" });
|
|
13567
13642
|
return;
|
|
13568
13643
|
}
|
|
13569
13644
|
for (const chunk of res.chunks) {
|
|
@@ -13571,7 +13646,7 @@ var init_ThreadStateImpl = __esm({
|
|
|
13571
13646
|
}
|
|
13572
13647
|
if (res.done) {
|
|
13573
13648
|
controller.close();
|
|
13574
|
-
|
|
13649
|
+
settle(res.error ? { ok: false, error: res.error } : { ok: true });
|
|
13575
13650
|
return;
|
|
13576
13651
|
}
|
|
13577
13652
|
if (res.chunks.length > 0) return;
|
|
@@ -13582,6 +13657,7 @@ var init_ThreadStateImpl = __esm({
|
|
|
13582
13657
|
closed = true;
|
|
13583
13658
|
void stub.proxyStreamAbort(id).catch(() => {
|
|
13584
13659
|
});
|
|
13660
|
+
settle({ ok: false, error: "proxy stream reader canceled" });
|
|
13585
13661
|
}
|
|
13586
13662
|
});
|
|
13587
13663
|
const writable = new WritableStream({
|
|
@@ -13596,16 +13672,18 @@ var init_ThreadStateImpl = __esm({
|
|
|
13596
13672
|
closed = true;
|
|
13597
13673
|
void stub.proxyStreamAbort(id).catch(() => {
|
|
13598
13674
|
});
|
|
13675
|
+
settle({ ok: false, error: "proxy stream writer aborted" });
|
|
13599
13676
|
}
|
|
13600
13677
|
});
|
|
13601
13678
|
return {
|
|
13602
13679
|
readable,
|
|
13603
13680
|
writable,
|
|
13604
13681
|
close: (error) => {
|
|
13682
|
+
if (closed) return;
|
|
13605
13683
|
closed = true;
|
|
13606
13684
|
void stub.proxyStreamAbort(id).catch(() => {
|
|
13607
13685
|
});
|
|
13608
|
-
|
|
13686
|
+
settle(error ? { ok: false, error } : { ok: true });
|
|
13609
13687
|
},
|
|
13610
13688
|
done
|
|
13611
13689
|
};
|
|
@@ -20247,23 +20325,51 @@ ${resultContent}${attachmentSummary}`;
|
|
|
20247
20325
|
}
|
|
20248
20326
|
return { side };
|
|
20249
20327
|
}
|
|
20250
|
-
// ───
|
|
20251
|
-
//
|
|
20252
|
-
//
|
|
20253
|
-
//
|
|
20254
|
-
// duplex), so the client only ever relays ciphertext. Streams are bound to a
|
|
20255
|
-
// live socket and never park.
|
|
20328
|
+
// ─── Runtime byte streams ──────────────────────────────────────────────────
|
|
20329
|
+
// The existing bridge frames carry opaque capability metadata and bytes
|
|
20330
|
+
// between the runtime and its execution owner. Streams are live resources:
|
|
20331
|
+
// they are bound to one bridge socket and never park.
|
|
20256
20332
|
/** Default time to wait for the client to accept a stream open. */
|
|
20257
20333
|
static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
|
|
20334
|
+
/** Poll cadence while waiting for a disconnected owner's bridge to return. */
|
|
20335
|
+
static OWNER_REATTACH_POLL_MS = 250;
|
|
20336
|
+
/**
|
|
20337
|
+
* The thread HAS an elected execution owner, but its bridge socket is gone
|
|
20338
|
+
* (daemon restart, network blip, DO handoff). Summon the owning machine
|
|
20339
|
+
* through the per-user hub and wait for a bridge to re-attach instead of
|
|
20340
|
+
* failing the stream open instantly — that instant failure is what ended
|
|
20341
|
+
* Sama One turns with "workspace unavailable" whenever a slow tool/approval
|
|
20342
|
+
* outlived the socket. Threads with NO recorded owner (subagent children)
|
|
20343
|
+
* still fail fast so ancestor walk-up routing stays immediate.
|
|
20344
|
+
*/
|
|
20345
|
+
async awaitOwnerBridgeSocket(request) {
|
|
20346
|
+
if (request.wake?.userId) {
|
|
20347
|
+
void this.requestHubWake(request.wake.userId, request.wake.threadId);
|
|
20348
|
+
}
|
|
20349
|
+
const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
|
|
20350
|
+
const deadline = Date.now() + timeoutMs;
|
|
20351
|
+
while (Date.now() < deadline) {
|
|
20352
|
+
await new Promise(
|
|
20353
|
+
(resolve) => setTimeout(resolve, _DurableThread.OWNER_REATTACH_POLL_MS)
|
|
20354
|
+
);
|
|
20355
|
+
const owner = await this.getExecutionOwner();
|
|
20356
|
+
const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
|
|
20357
|
+
if (socket) return socket;
|
|
20358
|
+
}
|
|
20359
|
+
return null;
|
|
20360
|
+
}
|
|
20258
20361
|
/** Open a bidirectional byte stream to the connected owner client. */
|
|
20259
20362
|
async openClientStream(request) {
|
|
20260
20363
|
const owner = await this.getExecutionOwner();
|
|
20261
|
-
|
|
20364
|
+
let socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
|
|
20365
|
+
if (!socket && owner) {
|
|
20366
|
+
socket = await this.awaitOwnerBridgeSocket(request);
|
|
20367
|
+
}
|
|
20262
20368
|
if (!socket) {
|
|
20263
20369
|
return {
|
|
20264
20370
|
ok: false,
|
|
20265
20371
|
error: "The workspace is not available right now. Try again in a moment.",
|
|
20266
|
-
data: { noClient: true }
|
|
20372
|
+
data: { noClient: true, code: "unavailable" }
|
|
20267
20373
|
};
|
|
20268
20374
|
}
|
|
20269
20375
|
const id = crypto.randomUUID();
|
|
@@ -20296,7 +20402,8 @@ ${resultContent}${attachmentSummary}`;
|
|
|
20296
20402
|
state.openTimer = setTimeout(() => {
|
|
20297
20403
|
resolve({
|
|
20298
20404
|
ok: false,
|
|
20299
|
-
error: `The client did not accept the ${request.tool} stream within ${Math.round(timeoutMs / 1e3)}s
|
|
20405
|
+
error: `The client did not accept the ${request.tool} stream within ${Math.round(timeoutMs / 1e3)}s.`,
|
|
20406
|
+
code: "timeout"
|
|
20300
20407
|
});
|
|
20301
20408
|
}, timeoutMs);
|
|
20302
20409
|
try {
|
|
@@ -20311,7 +20418,8 @@ ${resultContent}${attachmentSummary}`;
|
|
|
20311
20418
|
} catch (error) {
|
|
20312
20419
|
resolve({
|
|
20313
20420
|
ok: false,
|
|
20314
|
-
error: `Failed to reach the client: ${error instanceof Error ? error.message : String(error)}
|
|
20421
|
+
error: `Failed to reach the client: ${error instanceof Error ? error.message : String(error)}`,
|
|
20422
|
+
code: "unavailable"
|
|
20315
20423
|
});
|
|
20316
20424
|
}
|
|
20317
20425
|
});
|
|
@@ -20324,7 +20432,11 @@ ${resultContent}${attachmentSummary}`;
|
|
|
20324
20432
|
this.clientStreams.delete(id);
|
|
20325
20433
|
state.settled = true;
|
|
20326
20434
|
settleDone({ ok: false, error: openResult.error });
|
|
20327
|
-
return {
|
|
20435
|
+
return {
|
|
20436
|
+
ok: false,
|
|
20437
|
+
error: openResult.error ?? "Stream open failed.",
|
|
20438
|
+
data: openResult.code ? { code: openResult.code } : void 0
|
|
20439
|
+
};
|
|
20328
20440
|
}
|
|
20329
20441
|
const sendChunked = (bytes) => {
|
|
20330
20442
|
for (let i = 0; i < bytes.length; i += CLIENT_STREAM_CHUNK_BYTES) {
|
|
@@ -20368,8 +20480,14 @@ ${resultContent}${attachmentSummary}`;
|
|
|
20368
20480
|
async proxyStreamOpen(request) {
|
|
20369
20481
|
const opened = await this.openClientStream(request);
|
|
20370
20482
|
if (!opened.ok) {
|
|
20371
|
-
const
|
|
20372
|
-
|
|
20483
|
+
const failureData = opened.data;
|
|
20484
|
+
const noClient = failureData?.noClient === true;
|
|
20485
|
+
return {
|
|
20486
|
+
ok: false,
|
|
20487
|
+
error: opened.error,
|
|
20488
|
+
noClient,
|
|
20489
|
+
code: failureData?.code
|
|
20490
|
+
};
|
|
20373
20491
|
}
|
|
20374
20492
|
const id = crypto.randomUUID();
|
|
20375
20493
|
const duplex = opened.duplex;
|
|
@@ -20440,12 +20558,12 @@ ${resultContent}${attachmentSummary}`;
|
|
|
20440
20558
|
}
|
|
20441
20559
|
}
|
|
20442
20560
|
/** Settle and remove a client stream exactly once. */
|
|
20443
|
-
settleClientStream(id, result) {
|
|
20561
|
+
settleClientStream(id, result, openCode) {
|
|
20444
20562
|
const state = this.clientStreams.get(id);
|
|
20445
20563
|
if (!state) return;
|
|
20446
20564
|
this.clientStreams.delete(id);
|
|
20447
20565
|
if (state.openTimer) clearTimeout(state.openTimer);
|
|
20448
|
-
if (state.opened) state.opened({ ok: false, error: result.error ?? "Stream closed." });
|
|
20566
|
+
if (state.opened) state.opened({ ok: false, error: result.error ?? "Stream closed.", code: openCode });
|
|
20449
20567
|
if (!state.settled) {
|
|
20450
20568
|
state.settled = true;
|
|
20451
20569
|
try {
|
|
@@ -20489,7 +20607,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
20489
20607
|
}
|
|
20490
20608
|
case "stream_error": {
|
|
20491
20609
|
const error = typeof parsed.error === "string" ? parsed.error : "Client stream error.";
|
|
20492
|
-
this.settleClientStream(id, { ok: false, error });
|
|
20610
|
+
this.settleClientStream(id, { ok: false, error }, "refused");
|
|
20493
20611
|
return;
|
|
20494
20612
|
}
|
|
20495
20613
|
case "stream_data": {
|
|
@@ -20516,7 +20634,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
20516
20634
|
failClientStreamsForSocket(ws) {
|
|
20517
20635
|
for (const [id, state] of Array.from(this.clientStreams.entries())) {
|
|
20518
20636
|
if (state.socket === ws) {
|
|
20519
|
-
this.settleClientStream(id, { ok: false, error: "The client connection was lost." });
|
|
20637
|
+
this.settleClientStream(id, { ok: false, error: "The client connection was lost." }, "unavailable");
|
|
20520
20638
|
}
|
|
20521
20639
|
}
|
|
20522
20640
|
}
|
|
@@ -20540,6 +20658,11 @@ ${resultContent}${attachmentSummary}`;
|
|
|
20540
20658
|
console.error("[DurableThread] Failed to arm park TTL (non-fatal):", error);
|
|
20541
20659
|
}
|
|
20542
20660
|
if (!userId) return;
|
|
20661
|
+
await this.requestHubWake(userId, threadId);
|
|
20662
|
+
}
|
|
20663
|
+
/** Ask the user's hub to summon the owning machine's daemon to a thread.
|
|
20664
|
+
* Best-effort: wake failure must never fail the caller's own path. */
|
|
20665
|
+
async requestHubWake(userId, threadId) {
|
|
20543
20666
|
try {
|
|
20544
20667
|
const namespace = this.env.AGENT_BUILDER_USER_HUB;
|
|
20545
20668
|
if (!namespace) return;
|