@standardagents/builder 0.37.3 → 0.37.5
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 +178 -81
- package/dist/built-in-routes.js.map +1 -1
- package/dist/client/{ApiKeysView-BhuT3pIF.js → ApiKeysView-Pk5oljVU.js} +1 -1
- package/dist/client/CenteredContentView-HGpMGAQj.js +1 -0
- package/dist/client/CompositionView-DN8dTOCO.js +32 -0
- package/dist/client/{ConfirmDialog.vue_vue_type_script_setup_true_lang-BqwwsXWJ.js → ConfirmDialog.vue_vue_type_script_setup_true_lang--j_hnMZ0.js} +1 -1
- package/dist/client/{CopyButton.vue_vue_type_script_setup_true_lang-Cqm3Vt3E.js → CopyButton.vue_vue_type_script_setup_true_lang-CpJDcbZx.js} +1 -1
- package/dist/client/{DataTable.vue_vue_type_script_setup_true_lang-CA5qgsty.js → DataTable.vue_vue_type_script_setup_true_lang-unLrY-PY.js} +1 -1
- package/dist/client/{JsonViewer-ByQKCShF.js → JsonViewer-BYj_yrGB.js} +1 -1
- package/dist/client/{LoginView-J4qtHbQK.js → LoginView-CfVqItZV.js} +1 -1
- package/dist/client/{MarketplaceView-CshdWDqp.js → MarketplaceView-Cidt0nLf.js} +1 -1
- package/dist/client/{Modal.vue_vue_type_script_setup_true_lang-DKdB9A8Z.js → Modal.vue_vue_type_script_setup_true_lang-C1dmm1HX.js} +1 -1
- package/dist/client/ModelModal.vue_vue_type_script_setup_true_lang-DIKLuPXT.js +1 -0
- package/dist/client/{ModelsView-B_n306yk.js → ModelsView-DH6wNe38.js} +1 -1
- package/dist/client/{PromptEditView-C85qRgsE.js → PromptEditView-D_VjoAa-.js} +1 -1
- package/dist/client/{PromptModal-PNpoJfqD.js → PromptModal-a9LTOWNt.js} +1 -1
- package/dist/client/{PromptsView-BKnfThIF.js → PromptsView-BCCCryKR.js} +1 -1
- package/dist/client/{ProvidersView-Df41VhHD.js → ProvidersView-CF4x5F6G.js} +2 -2
- package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-D5VDoQiK.js +22 -0
- package/dist/client/ThreadsView-BzscU1l2.js +1 -0
- package/dist/client/{ToolsView-C0MH9x3N.js → ToolsView-CeEtxDZV.js} +1 -1
- package/dist/client/{UiCodeBlock.vue_vue_type_script_setup_true_lang-DjDQOYjB.js → UiCodeBlock.vue_vue_type_script_setup_true_lang-BdyzSxZZ.js} +1 -1
- package/dist/client/{UsersView--TQiJYP7.js → UsersView-Di5cmUYL.js} +1 -1
- package/dist/client/{VariablesView-BO3UljZu.js → VariablesView-CXUr05jD.js} +1 -1
- package/dist/client/assets/{CompositionView-nJBKoNx4.css → CompositionView-0qQUWuc1.css} +1 -1
- package/dist/client/{check-DeHZQcv4.js → check-YKi1o6p6.js} +1 -1
- package/dist/client/{chevron-right-C8Mb0B59.js → chevron-right-T0zzq_GX.js} +1 -1
- package/dist/client/{copy-BxJsl-KZ.js → copy-C_gUGAj1.js} +1 -1
- package/dist/client/{formkit-0eyyCEP_.js → formkit-CE26dtFp.js} +1 -1
- package/dist/client/index-BMqXblce.js +6 -0
- package/dist/client/index.html +3 -3
- package/dist/client/{package-B9czwyIu.js → package-B70IFSDB.js} +1 -1
- package/dist/client/{plus-DqtuqJFm.js → plus-BN9IHYdt.js} +1 -1
- package/dist/client/{timeAgo-CDt-vAhI.js → timeAgo-G1ovbWPH.js} +1 -1
- package/dist/client/{useActionFeedback-B6UfBJ7l.js → useActionFeedback-7pfPjKMi.js} +1 -1
- package/dist/client/{vue-Dw7WLX4c.js → vue-CZ-ORfS3.js} +1 -1
- package/dist/{index-DceFbLG9.d.ts → index-Lfan8QMN.d.ts} +46 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +297 -25
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +6 -0
- package/dist/plugin.js.map +1 -1
- package/dist/runtime.d.ts +45 -5
- package/dist/runtime.js +291 -25
- package/dist/runtime.js.map +1 -1
- package/dist/test.d.ts +1 -1
- package/package.json +5 -5
- package/dist/client/CenteredContentView-CfgX3Ao3.js +0 -1
- package/dist/client/CompositionView-B9FfWMHd.js +0 -32
- package/dist/client/ModelModal.vue_vue_type_script_setup_true_lang-BAxaBZsx.js +0 -1
- package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-CtIBWHUR.js +0 -22
- package/dist/client/ThreadsView-sjo5Rrcm.js +0 -1
- package/dist/client/index-BgZnQr8X.js +0 -6
package/dist/runtime.js
CHANGED
|
@@ -10103,10 +10103,18 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
|
|
|
10103
10103
|
const mimeType = match[1];
|
|
10104
10104
|
const base64Data = match[2];
|
|
10105
10105
|
const ext = mimeType.split("/")[1] || "png";
|
|
10106
|
+
if (base64Data.length < 64) {
|
|
10107
|
+
console.error("[FlowEngine] Generated image payload is empty/truncated, skipping");
|
|
10108
|
+
continue;
|
|
10109
|
+
}
|
|
10106
10110
|
const attachmentId = crypto.randomUUID();
|
|
10107
10111
|
const path = `/attachments/${Date.now()}-${attachmentId.slice(0, 8)}.${ext}`;
|
|
10108
10112
|
try {
|
|
10109
|
-
await state.thread.instance.writeFile(path, base64Data, mimeType);
|
|
10113
|
+
const written = await state.thread.instance.writeFile(path, base64Data, mimeType);
|
|
10114
|
+
if (written && written.success === false) {
|
|
10115
|
+
console.error("[FlowEngine] Generated image write failed:", written.error);
|
|
10116
|
+
continue;
|
|
10117
|
+
}
|
|
10110
10118
|
if (image.id) {
|
|
10111
10119
|
imagePathMap.set(image.id, path);
|
|
10112
10120
|
}
|
|
@@ -10173,10 +10181,18 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
|
|
|
10173
10181
|
const mimeType = imageUrl.slice(dataPrefix.length, base64Index);
|
|
10174
10182
|
const base64Data = imageUrl.slice(base64Index + base64Marker.length);
|
|
10175
10183
|
const ext = mimeType.split("/")[1] || "png";
|
|
10184
|
+
if (base64Data.length < 64) {
|
|
10185
|
+
console.error(`[FlowEngine] ${toolName} image payload is empty/truncated, skipping`);
|
|
10186
|
+
continue;
|
|
10187
|
+
}
|
|
10176
10188
|
const attachmentId = crypto.randomUUID();
|
|
10177
10189
|
const path = `/attachments/${Date.now()}-${attachmentId.slice(0, 8)}.${ext}`;
|
|
10178
10190
|
try {
|
|
10179
|
-
await state.thread.instance.writeFile(path, base64Data, mimeType);
|
|
10191
|
+
const written = await state.thread.instance.writeFile(path, base64Data, mimeType);
|
|
10192
|
+
if (written && written.success === false) {
|
|
10193
|
+
console.error(`[FlowEngine] ${toolName} image write failed:`, written.error);
|
|
10194
|
+
continue;
|
|
10195
|
+
}
|
|
10180
10196
|
const attachmentRef = {
|
|
10181
10197
|
id: attachmentId,
|
|
10182
10198
|
type: "file",
|
|
@@ -13713,8 +13729,25 @@ var init_ThreadStateImpl = __esm({
|
|
|
13713
13729
|
const mustParkForApproval = await this.forwardedCallNeedsApproval(request);
|
|
13714
13730
|
if (mustParkForApproval && canPark && toolCallId && typeof instance.hasBridge === "function" && await instance.hasBridge()) {
|
|
13715
13731
|
await instance.dispatchClientTool(request, toolCallId, side);
|
|
13716
|
-
const
|
|
13717
|
-
if (typeof
|
|
13732
|
+
const withDelivery = instance;
|
|
13733
|
+
if (typeof withDelivery.awaitDeliveredClientToolResult === "function") {
|
|
13734
|
+
try {
|
|
13735
|
+
const delivered = await withDelivery.awaitDeliveredClientToolResult.call(
|
|
13736
|
+
instance,
|
|
13737
|
+
toolCallId,
|
|
13738
|
+
4e3
|
|
13739
|
+
);
|
|
13740
|
+
if (delivered) {
|
|
13741
|
+
return {
|
|
13742
|
+
ok: delivered.toolStatus !== "error",
|
|
13743
|
+
result: delivered.content,
|
|
13744
|
+
data: { toolCallId }
|
|
13745
|
+
};
|
|
13746
|
+
}
|
|
13747
|
+
} catch {
|
|
13748
|
+
}
|
|
13749
|
+
} else if (typeof withDelivery.getDeliveredClientToolResult === "function") {
|
|
13750
|
+
const getDelivered = withDelivery.getDeliveredClientToolResult;
|
|
13718
13751
|
const graceDeadline = Date.now() + 4e3;
|
|
13719
13752
|
while (Date.now() < graceDeadline) {
|
|
13720
13753
|
await new Promise((r) => setTimeout(r, 150));
|
|
@@ -13804,7 +13837,14 @@ var init_ThreadStateImpl = __esm({
|
|
|
13804
13837
|
if (typeof stub.proxyStreamOpen === "function") {
|
|
13805
13838
|
const opened = await stub.proxyStreamOpen(withWake(parentId));
|
|
13806
13839
|
if (opened.ok && opened.id) {
|
|
13807
|
-
return {
|
|
13840
|
+
return {
|
|
13841
|
+
ok: true,
|
|
13842
|
+
duplex: this.buildProxyDuplex(
|
|
13843
|
+
stub,
|
|
13844
|
+
opened.id,
|
|
13845
|
+
opened.parkedPull === true
|
|
13846
|
+
)
|
|
13847
|
+
};
|
|
13808
13848
|
}
|
|
13809
13849
|
if (opened.error && !opened.noClient) {
|
|
13810
13850
|
return {
|
|
@@ -13822,11 +13862,15 @@ var init_ThreadStateImpl = __esm({
|
|
|
13822
13862
|
}
|
|
13823
13863
|
/**
|
|
13824
13864
|
* Build a client duplex that bridges to a stream opened on an ancestor DO
|
|
13825
|
-
* (the bridge holder) via chunk-pull RPCs. `readable`
|
|
13826
|
-
*
|
|
13865
|
+
* (the bridge holder) via chunk-pull RPCs. `readable` issues PARKED pulls —
|
|
13866
|
+
* a current ancestor holds one `proxyStreamPull` until bytes, completion,
|
|
13867
|
+
* error, or abort, so an idle open stream performs zero repeated pulls.
|
|
13868
|
+
* Older ancestors use the bounded long-pull compatibility path. `writable`
|
|
13869
|
+
* forwards each chunk with `proxyStreamWrite`; teardown calls
|
|
13827
13870
|
* `proxyStreamAbort`.
|
|
13828
13871
|
*/
|
|
13829
|
-
|
|
13872
|
+
static PROXY_PULL_MAX_WAIT_MS = 2e4;
|
|
13873
|
+
buildProxyDuplex(stub, id, supportsParkedPull = false) {
|
|
13830
13874
|
let settleDone;
|
|
13831
13875
|
const done = new Promise((r) => settleDone = r);
|
|
13832
13876
|
let closed = false;
|
|
@@ -13839,9 +13883,13 @@ var init_ThreadStateImpl = __esm({
|
|
|
13839
13883
|
const readable = new ReadableStream({
|
|
13840
13884
|
async pull(controller) {
|
|
13841
13885
|
while (!closed) {
|
|
13886
|
+
const pullStartedAt = Date.now();
|
|
13842
13887
|
let res;
|
|
13843
13888
|
try {
|
|
13844
|
-
res = await stub.proxyStreamPull(
|
|
13889
|
+
res = await stub.proxyStreamPull(
|
|
13890
|
+
id,
|
|
13891
|
+
supportsParkedPull ? void 0 : _ThreadStateImpl.PROXY_PULL_MAX_WAIT_MS
|
|
13892
|
+
);
|
|
13845
13893
|
} catch (error) {
|
|
13846
13894
|
controller.error(error instanceof Error ? error : new Error(String(error)));
|
|
13847
13895
|
settle({ ok: false, error: "proxy stream pull failed" });
|
|
@@ -13856,7 +13904,14 @@ var init_ThreadStateImpl = __esm({
|
|
|
13856
13904
|
return;
|
|
13857
13905
|
}
|
|
13858
13906
|
if (res.chunks.length > 0) return;
|
|
13859
|
-
|
|
13907
|
+
if (supportsParkedPull) {
|
|
13908
|
+
controller.error(new Error("parked proxy pull returned without data"));
|
|
13909
|
+
settle({ ok: false, error: "parked proxy pull returned without data" });
|
|
13910
|
+
return;
|
|
13911
|
+
}
|
|
13912
|
+
if (Date.now() - pullStartedAt < 250) {
|
|
13913
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
13914
|
+
}
|
|
13860
13915
|
}
|
|
13861
13916
|
},
|
|
13862
13917
|
cancel() {
|
|
@@ -15975,6 +16030,9 @@ var AlarmQueue = class {
|
|
|
15975
16030
|
options.sourceLogId ?? null
|
|
15976
16031
|
);
|
|
15977
16032
|
await this.rescheduleAlarm();
|
|
16033
|
+
if (method === "runEffect") {
|
|
16034
|
+
await this.emitEffectRunChange(id);
|
|
16035
|
+
}
|
|
15978
16036
|
return id;
|
|
15979
16037
|
}
|
|
15980
16038
|
/**
|
|
@@ -16013,6 +16071,9 @@ var AlarmQueue = class {
|
|
|
16013
16071
|
`UPDATE alarm_queue SET status = 'processing', attempts = attempts + 1 WHERE id = ?`,
|
|
16014
16072
|
item.id
|
|
16015
16073
|
);
|
|
16074
|
+
if (item.method === "runEffect") {
|
|
16075
|
+
await this.emitEffectRunChange(item.id);
|
|
16076
|
+
}
|
|
16016
16077
|
try {
|
|
16017
16078
|
const args = JSON.parse(item.args);
|
|
16018
16079
|
await this.executeMethod(item.method, args, item.id);
|
|
@@ -16066,6 +16127,7 @@ var AlarmQueue = class {
|
|
|
16066
16127
|
now,
|
|
16067
16128
|
id
|
|
16068
16129
|
);
|
|
16130
|
+
await this.emitEffectRunChange(id);
|
|
16069
16131
|
}
|
|
16070
16132
|
/**
|
|
16071
16133
|
* Mark an item as failed but keep it for 24 hours for debugging
|
|
@@ -16085,6 +16147,7 @@ var AlarmQueue = class {
|
|
|
16085
16147
|
now,
|
|
16086
16148
|
id
|
|
16087
16149
|
);
|
|
16150
|
+
await this.emitEffectRunChange(id);
|
|
16088
16151
|
}
|
|
16089
16152
|
/**
|
|
16090
16153
|
* Clean up failed items older than 24 hours
|
|
@@ -16174,6 +16237,10 @@ var AlarmQueue = class {
|
|
|
16174
16237
|
*/
|
|
16175
16238
|
async recoverOrphanedProcessing(maxAttempts = 3) {
|
|
16176
16239
|
const now = Date.now() * 1e3;
|
|
16240
|
+
const orphanedEffectsCursor = await this.sql.exec(
|
|
16241
|
+
`SELECT id FROM alarm_queue WHERE status = 'processing' AND method = 'runEffect'`
|
|
16242
|
+
);
|
|
16243
|
+
const orphanedEffectIds = orphanedEffectsCursor.toArray().map((row) => row.id);
|
|
16177
16244
|
await this.sql.exec(
|
|
16178
16245
|
`UPDATE alarm_queue
|
|
16179
16246
|
SET status = 'failed',
|
|
@@ -16186,6 +16253,9 @@ var AlarmQueue = class {
|
|
|
16186
16253
|
await this.sql.exec(
|
|
16187
16254
|
`UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
|
|
16188
16255
|
);
|
|
16256
|
+
for (const id of orphanedEffectIds) {
|
|
16257
|
+
await this.emitEffectRunChange(id);
|
|
16258
|
+
}
|
|
16189
16259
|
}
|
|
16190
16260
|
// ============================================================
|
|
16191
16261
|
// Effect-specific Methods
|
|
@@ -16364,6 +16434,7 @@ var AlarmQueue = class {
|
|
|
16364
16434
|
const canceled = changesRows.length > 0 && changesRows[0].changes > 0;
|
|
16365
16435
|
if (canceled) {
|
|
16366
16436
|
await this.rescheduleAlarm();
|
|
16437
|
+
await this.emitEffectRunChange(id);
|
|
16367
16438
|
}
|
|
16368
16439
|
return canceled;
|
|
16369
16440
|
}
|
|
@@ -16438,6 +16509,7 @@ var AlarmQueue = class {
|
|
|
16438
16509
|
if (!changed) {
|
|
16439
16510
|
throw new Error(`Effect run ${item.id} is no longer pending`);
|
|
16440
16511
|
}
|
|
16512
|
+
await this.emitEffectRunChange(item.id);
|
|
16441
16513
|
try {
|
|
16442
16514
|
await this.executeMethod(item.method, JSON.parse(item.args), item.id);
|
|
16443
16515
|
await this.complete(item.id, item.method);
|
|
@@ -16449,6 +16521,45 @@ var AlarmQueue = class {
|
|
|
16449
16521
|
await this.rescheduleAlarm();
|
|
16450
16522
|
}
|
|
16451
16523
|
}
|
|
16524
|
+
/**
|
|
16525
|
+
* Read one effect row and hand its bounded projection (no args payload) to
|
|
16526
|
+
* the change listener. Self-filtering: operational (non-effect) rows and
|
|
16527
|
+
* already-deleted rows emit nothing, and a listener failure must never
|
|
16528
|
+
* disturb queue processing.
|
|
16529
|
+
*/
|
|
16530
|
+
async emitEffectRunChange(id) {
|
|
16531
|
+
const listener = this.options.onEffectRunChanged;
|
|
16532
|
+
if (!listener) return;
|
|
16533
|
+
try {
|
|
16534
|
+
const cursor = await this.sql.exec(
|
|
16535
|
+
`
|
|
16536
|
+
SELECT id, args, scheduled_at, created_at, status, attempts, error, completed_at, manual_run, manual_run_at, replayed_from
|
|
16537
|
+
FROM alarm_queue
|
|
16538
|
+
WHERE id = ? AND method = 'runEffect'
|
|
16539
|
+
LIMIT 1
|
|
16540
|
+
`,
|
|
16541
|
+
id
|
|
16542
|
+
);
|
|
16543
|
+
const row = cursor.toArray()[0];
|
|
16544
|
+
if (!row) return;
|
|
16545
|
+
const { effectName } = this.parseEffectPayload(row.args);
|
|
16546
|
+
listener({
|
|
16547
|
+
id: row.id,
|
|
16548
|
+
name: effectName,
|
|
16549
|
+
scheduledAt: row.scheduled_at,
|
|
16550
|
+
createdAt: row.created_at,
|
|
16551
|
+
status: row.status,
|
|
16552
|
+
attempts: row.attempts,
|
|
16553
|
+
error: row.error,
|
|
16554
|
+
completedAt: row.completed_at,
|
|
16555
|
+
manualRun: row.manual_run === 1,
|
|
16556
|
+
manualRunAt: row.manual_run_at,
|
|
16557
|
+
replayedFrom: row.replayed_from
|
|
16558
|
+
});
|
|
16559
|
+
} catch (error) {
|
|
16560
|
+
console.error(`[AlarmQueue] Failed to emit effect run change for ${id}:`, error);
|
|
16561
|
+
}
|
|
16562
|
+
}
|
|
16452
16563
|
normalizeOffset(value) {
|
|
16453
16564
|
const parsed = typeof value === "number" ? Math.trunc(value) : Number.NaN;
|
|
16454
16565
|
if (!Number.isFinite(parsed)) return 0;
|
|
@@ -20919,6 +21030,25 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
20919
21030
|
alarmQueueRearmed = false;
|
|
20920
21031
|
currentAbortController = null;
|
|
20921
21032
|
isExecuting = false;
|
|
21033
|
+
/**
|
|
21034
|
+
* In-memory waiters parked until the running flow finishes (AB-09): the
|
|
21035
|
+
* executeFlow busy-guard awaits this signal instead of re-checking
|
|
21036
|
+
* isExecuting every 500ms. Every isExecuting=false transition (execution
|
|
21037
|
+
* finallys and the watchdog takeover) notifies. In-memory only — a DO
|
|
21038
|
+
* restart drops waiters together with the busy caller.
|
|
21039
|
+
*/
|
|
21040
|
+
executionIdleWaiters = /* @__PURE__ */ new Set();
|
|
21041
|
+
notifyExecutionIdleWaiters() {
|
|
21042
|
+
if (!this.executionIdleWaiters || this.executionIdleWaiters.size === 0) return;
|
|
21043
|
+
const waiters = Array.from(this.executionIdleWaiters);
|
|
21044
|
+
this.executionIdleWaiters.clear();
|
|
21045
|
+
for (const wake of waiters) {
|
|
21046
|
+
try {
|
|
21047
|
+
wake();
|
|
21048
|
+
} catch {
|
|
21049
|
+
}
|
|
21050
|
+
}
|
|
21051
|
+
}
|
|
20922
21052
|
// ── Execution watchdog state (see watchdogCheck) ─────────────────────────
|
|
20923
21053
|
// In-memory heartbeat: bumped by noteExecutionProgress() on every streamed
|
|
20924
21054
|
// chunk and request attempt. Deliberately NOT persisted — after a DO restart
|
|
@@ -20943,7 +21073,11 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
20943
21073
|
this.alarmQueue = new AlarmQueue(ctx, {
|
|
20944
21074
|
retentionEnabled: env.AGENT_BUILDER_ALARM_RETENTION_ENABLED !== "false",
|
|
20945
21075
|
isForensicHold: async () => (await this.runtimeSafety.getStatus()).forensicHold,
|
|
20946
|
-
invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 }
|
|
21076
|
+
invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 },
|
|
21077
|
+
// Effect lifecycle transitions broadcast on the thread event channel so
|
|
21078
|
+
// inspectors stay live from the already-open message socket instead of
|
|
21079
|
+
// polling the effects list.
|
|
21080
|
+
onEffectRunChanged: (change) => this.broadcastEvent("effect_changed", change)
|
|
20947
21081
|
});
|
|
20948
21082
|
this.alarmQueue.setExecutor(
|
|
20949
21083
|
async (method, args, itemId) => {
|
|
@@ -21502,6 +21636,7 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
21502
21636
|
if (value === null || value === void 0) {
|
|
21503
21637
|
await this.ctx.storage.sql.exec(`DELETE FROM thread_values WHERE key = ?`, key);
|
|
21504
21638
|
this.broadcastLog({ type: "kv_data", key, deleted: true, updated_at: updatedAt });
|
|
21639
|
+
this.broadcastEvent("kv_changed", { key, deleted: true, updated_at: updatedAt });
|
|
21505
21640
|
return;
|
|
21506
21641
|
}
|
|
21507
21642
|
const serialized = this.serializeThreadValue(value);
|
|
@@ -21518,6 +21653,7 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
21518
21653
|
updatedAt
|
|
21519
21654
|
);
|
|
21520
21655
|
this.broadcastLog({ type: "kv_data", key, deleted: false, updated_at: updatedAt });
|
|
21656
|
+
this.broadcastEvent("kv_changed", { key, deleted: false, updated_at: updatedAt });
|
|
21521
21657
|
}
|
|
21522
21658
|
async listValues(options = {}) {
|
|
21523
21659
|
await this.ensureMigrated();
|
|
@@ -24538,6 +24674,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
24538
24674
|
if (isOwner) {
|
|
24539
24675
|
void this.resendPendingClientCalls(server);
|
|
24540
24676
|
}
|
|
24677
|
+
this.notifyOwnerBridgeWaiters();
|
|
24541
24678
|
return new Response(null, {
|
|
24542
24679
|
status: 101,
|
|
24543
24680
|
webSocket: client
|
|
@@ -24573,6 +24710,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
24573
24710
|
/** Persist + cache the execution-owner record (null releases ownership). */
|
|
24574
24711
|
async setExecutionOwner(owner) {
|
|
24575
24712
|
await this.setValue(EXECUTION_OWNER_KEY, owner);
|
|
24713
|
+
this.notifyOwnerBridgeWaiters();
|
|
24576
24714
|
}
|
|
24577
24715
|
/** Whether the given client id has an open bridge socket (excluding `except`).
|
|
24578
24716
|
* Idle bridge heartbeats are handled by Cloudflare without waking this object,
|
|
@@ -24930,6 +25068,55 @@ ${resultContent}${attachmentSummary}`;
|
|
|
24930
25068
|
* permission level covers the call) otherwise race the placeholder into
|
|
24931
25069
|
* stored history, leaving the model blind to its own tool outcomes.
|
|
24932
25070
|
*/
|
|
25071
|
+
/**
|
|
25072
|
+
* In-memory waiters parked on a forwarded call's result delivery (AB-09).
|
|
25073
|
+
* `deliverClientToolResult` resolves them so the approval grace-wait reads
|
|
25074
|
+
* delivered state exactly once instead of re-polling it every 150ms.
|
|
25075
|
+
* In-memory only: a DO restart drops waiters along with the caller that
|
|
25076
|
+
* registered them, and the durable park path still resumes the turn.
|
|
25077
|
+
*/
|
|
25078
|
+
deliveredResultWaiters = /* @__PURE__ */ new Map();
|
|
25079
|
+
notifyDeliveredResultWaiters(toolCallId) {
|
|
25080
|
+
const waiters = this.deliveredResultWaiters?.get(toolCallId);
|
|
25081
|
+
if (!waiters) return;
|
|
25082
|
+
this.deliveredResultWaiters.delete(toolCallId);
|
|
25083
|
+
for (const wake of waiters) {
|
|
25084
|
+
try {
|
|
25085
|
+
wake();
|
|
25086
|
+
} catch {
|
|
25087
|
+
}
|
|
25088
|
+
}
|
|
25089
|
+
}
|
|
25090
|
+
/**
|
|
25091
|
+
* Await a parked forwarded call's delivered result: one immediate read,
|
|
25092
|
+
* then a single parked wait resolved by result delivery, bounded by one
|
|
25093
|
+
* hard deadline, then one final read. Replaces the caller-side 150ms
|
|
25094
|
+
* re-poll of {@link getDeliveredClientToolResult}.
|
|
25095
|
+
*/
|
|
25096
|
+
async awaitDeliveredClientToolResult(toolCallId, maxWaitMs) {
|
|
25097
|
+
const immediate = await this.getDeliveredClientToolResult(toolCallId);
|
|
25098
|
+
if (immediate) return immediate;
|
|
25099
|
+
if (!(maxWaitMs > 0)) return null;
|
|
25100
|
+
await new Promise((resolve) => {
|
|
25101
|
+
let waiters = this.deliveredResultWaiters.get(toolCallId);
|
|
25102
|
+
if (!waiters) {
|
|
25103
|
+
waiters = /* @__PURE__ */ new Set();
|
|
25104
|
+
this.deliveredResultWaiters.set(toolCallId, waiters);
|
|
25105
|
+
}
|
|
25106
|
+
const registered = waiters;
|
|
25107
|
+
const deadline = setTimeout(() => {
|
|
25108
|
+
registered.delete(wake);
|
|
25109
|
+
if (registered.size === 0) this.deliveredResultWaiters.delete(toolCallId);
|
|
25110
|
+
resolve();
|
|
25111
|
+
}, maxWaitMs);
|
|
25112
|
+
const wake = () => {
|
|
25113
|
+
clearTimeout(deadline);
|
|
25114
|
+
resolve();
|
|
25115
|
+
};
|
|
25116
|
+
registered.add(wake);
|
|
25117
|
+
});
|
|
25118
|
+
return this.getDeliveredClientToolResult(toolCallId);
|
|
25119
|
+
}
|
|
24933
25120
|
async getDeliveredClientToolResult(toolCallId) {
|
|
24934
25121
|
await this.ensureMigrated();
|
|
24935
25122
|
await this.ensurePendingClientTable();
|
|
@@ -25065,6 +25252,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25065
25252
|
`UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
|
|
25066
25253
|
toolCallId
|
|
25067
25254
|
);
|
|
25255
|
+
this.notifyDeliveredResultWaiters(toolCallId);
|
|
25068
25256
|
try {
|
|
25069
25257
|
const msgCursor = await this.ctx.storage.sql.exec(
|
|
25070
25258
|
`SELECT id, role, content, name, tool_call_id, tool_status, created_at, depth
|
|
@@ -25083,8 +25271,24 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25083
25271
|
// they are bound to one bridge socket and never park.
|
|
25084
25272
|
/** Default time to wait for the client to accept a stream open. */
|
|
25085
25273
|
static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
|
|
25086
|
-
/**
|
|
25087
|
-
|
|
25274
|
+
/**
|
|
25275
|
+
* In-memory waiters parked on the owner's bridge socket returning (AB-09).
|
|
25276
|
+
* Resolved by bridge attach and by execution-owner changes; each waiter
|
|
25277
|
+
* re-evaluates against the CURRENT owner on wake. In-memory only: a DO
|
|
25278
|
+
* restart drops waiters with the stream-open call that registered them.
|
|
25279
|
+
*/
|
|
25280
|
+
ownerBridgeWaiters = /* @__PURE__ */ new Set();
|
|
25281
|
+
notifyOwnerBridgeWaiters() {
|
|
25282
|
+
if (!this.ownerBridgeWaiters || this.ownerBridgeWaiters.size === 0) return;
|
|
25283
|
+
const waiters = Array.from(this.ownerBridgeWaiters);
|
|
25284
|
+
this.ownerBridgeWaiters.clear();
|
|
25285
|
+
for (const wake of waiters) {
|
|
25286
|
+
try {
|
|
25287
|
+
wake();
|
|
25288
|
+
} catch {
|
|
25289
|
+
}
|
|
25290
|
+
}
|
|
25291
|
+
}
|
|
25088
25292
|
/**
|
|
25089
25293
|
* The thread HAS an elected execution owner, but its bridge socket is gone
|
|
25090
25294
|
* (daemon restart, network blip, DO handoff). Summon the owning machine
|
|
@@ -25093,6 +25297,11 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25093
25297
|
* Sama One turns with "workspace unavailable" whenever a slow tool/approval
|
|
25094
25298
|
* outlived the socket. Threads with NO recorded owner (subagent children)
|
|
25095
25299
|
* still fail fast so ancestor walk-up routing stays immediate.
|
|
25300
|
+
*
|
|
25301
|
+
* The wait parks on bridge-attach/owner-change signals under ONE hard
|
|
25302
|
+
* deadline — no re-poll cadence. Each wake re-reads the owner because the
|
|
25303
|
+
* returning daemon may reclaim with a new generation, and the socket must
|
|
25304
|
+
* match the CURRENT owner.
|
|
25096
25305
|
*/
|
|
25097
25306
|
async awaitOwnerBridgeSocket(request) {
|
|
25098
25307
|
if (request.wake?.userId) {
|
|
@@ -25100,15 +25309,25 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25100
25309
|
}
|
|
25101
25310
|
const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
|
|
25102
25311
|
const deadline = Date.now() + timeoutMs;
|
|
25103
|
-
|
|
25104
|
-
|
|
25105
|
-
(resolve) => setTimeout(resolve, _DurableThread.OWNER_REATTACH_POLL_MS)
|
|
25106
|
-
);
|
|
25312
|
+
this.ownerBridgeWaiters ??= /* @__PURE__ */ new Set();
|
|
25313
|
+
while (true) {
|
|
25107
25314
|
const owner = await this.getExecutionOwner();
|
|
25108
25315
|
const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
|
|
25109
25316
|
if (socket) return socket;
|
|
25317
|
+
const remainingMs = deadline - Date.now();
|
|
25318
|
+
if (remainingMs <= 0) return null;
|
|
25319
|
+
await new Promise((resolve) => {
|
|
25320
|
+
const timer = setTimeout(() => {
|
|
25321
|
+
this.ownerBridgeWaiters.delete(wake);
|
|
25322
|
+
resolve();
|
|
25323
|
+
}, remainingMs);
|
|
25324
|
+
const wake = () => {
|
|
25325
|
+
clearTimeout(timer);
|
|
25326
|
+
resolve();
|
|
25327
|
+
};
|
|
25328
|
+
this.ownerBridgeWaiters.add(wake);
|
|
25329
|
+
});
|
|
25110
25330
|
}
|
|
25111
|
-
return null;
|
|
25112
25331
|
}
|
|
25113
25332
|
/** Open a bidirectional byte stream to the connected owner client. */
|
|
25114
25333
|
async openClientStream(request) {
|
|
@@ -25252,27 +25471,38 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25252
25471
|
done: false,
|
|
25253
25472
|
error: void 0,
|
|
25254
25473
|
pump: Promise.resolve(),
|
|
25255
|
-
close: (error) => duplex.close(error)
|
|
25474
|
+
close: (error) => duplex.close(error),
|
|
25475
|
+
waiter: null,
|
|
25476
|
+
notify: () => {
|
|
25477
|
+
const waiter = entry.waiter;
|
|
25478
|
+
entry.waiter = null;
|
|
25479
|
+
waiter?.();
|
|
25480
|
+
}
|
|
25256
25481
|
};
|
|
25257
25482
|
entry.pump = (async () => {
|
|
25258
25483
|
try {
|
|
25259
25484
|
while (true) {
|
|
25260
25485
|
const { value, done } = await reader.read();
|
|
25261
25486
|
if (done) break;
|
|
25262
|
-
if (value)
|
|
25487
|
+
if (value) {
|
|
25488
|
+
entry.buffer.push(value);
|
|
25489
|
+
entry.notify();
|
|
25490
|
+
}
|
|
25263
25491
|
}
|
|
25264
25492
|
} catch (error) {
|
|
25265
25493
|
entry.error = error instanceof Error ? error.message : String(error);
|
|
25266
25494
|
} finally {
|
|
25267
25495
|
entry.done = true;
|
|
25496
|
+
entry.notify();
|
|
25268
25497
|
}
|
|
25269
25498
|
})();
|
|
25270
25499
|
void duplex.done.then((d) => {
|
|
25271
25500
|
entry.done = true;
|
|
25272
25501
|
if (!d.ok && !entry.error) entry.error = d.error;
|
|
25502
|
+
entry.notify();
|
|
25273
25503
|
});
|
|
25274
25504
|
this.proxyStreams.set(id, entry);
|
|
25275
|
-
return { ok: true, id };
|
|
25505
|
+
return { ok: true, id, parkedPull: true };
|
|
25276
25506
|
}
|
|
25277
25507
|
async proxyStreamWrite(id, dataB64) {
|
|
25278
25508
|
const entry = this.proxyStreams.get(id);
|
|
@@ -25282,9 +25512,22 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25282
25512
|
} catch {
|
|
25283
25513
|
}
|
|
25284
25514
|
}
|
|
25285
|
-
async proxyStreamPull(id) {
|
|
25515
|
+
async proxyStreamPull(id, maxWaitMs) {
|
|
25286
25516
|
const entry = this.proxyStreams.get(id);
|
|
25287
25517
|
if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
|
|
25518
|
+
if (entry.buffer.length === 0 && !entry.done) {
|
|
25519
|
+
await new Promise((resolve) => {
|
|
25520
|
+
const deadline = typeof maxWaitMs === "number" && maxWaitMs > 0 ? setTimeout(() => {
|
|
25521
|
+
if (entry.waiter === wake) entry.waiter = null;
|
|
25522
|
+
resolve();
|
|
25523
|
+
}, maxWaitMs) : null;
|
|
25524
|
+
const wake = () => {
|
|
25525
|
+
if (deadline) clearTimeout(deadline);
|
|
25526
|
+
resolve();
|
|
25527
|
+
};
|
|
25528
|
+
entry.waiter = wake;
|
|
25529
|
+
});
|
|
25530
|
+
}
|
|
25288
25531
|
const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
|
|
25289
25532
|
if (entry.done && entry.buffer.length === 0) {
|
|
25290
25533
|
this.proxyStreams.delete(id);
|
|
@@ -25304,6 +25547,9 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25304
25547
|
const entry = this.proxyStreams.get(id);
|
|
25305
25548
|
if (!entry) return;
|
|
25306
25549
|
this.proxyStreams.delete(id);
|
|
25550
|
+
entry.done = true;
|
|
25551
|
+
if (!entry.error) entry.error = "aborted";
|
|
25552
|
+
entry.notify();
|
|
25307
25553
|
try {
|
|
25308
25554
|
entry.close("aborted");
|
|
25309
25555
|
} catch {
|
|
@@ -25659,6 +25905,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25659
25905
|
this.broadcastEvent(type, data);
|
|
25660
25906
|
}
|
|
25661
25907
|
broadcastEvent(type, data) {
|
|
25908
|
+
if (!this.messageSockets || this.messageSockets.size === 0) return;
|
|
25662
25909
|
const payload = JSON.stringify({
|
|
25663
25910
|
type: "event",
|
|
25664
25911
|
eventType: type,
|
|
@@ -26085,6 +26332,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26085
26332
|
} catch {
|
|
26086
26333
|
}
|
|
26087
26334
|
this.isExecuting = false;
|
|
26335
|
+
this.notifyExecutionIdleWaiters();
|
|
26088
26336
|
this.currentAbortController = null;
|
|
26089
26337
|
await this.recordRegistryActivity(threadId, false);
|
|
26090
26338
|
tookOver = true;
|
|
@@ -26277,9 +26525,22 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
26277
26525
|
await this.assertNotTerminated();
|
|
26278
26526
|
if (this.isExecuting) {
|
|
26279
26527
|
const busyWaitBudgetMs = 3e4;
|
|
26280
|
-
const
|
|
26281
|
-
|
|
26282
|
-
|
|
26528
|
+
const deadline = Date.now() + busyWaitBudgetMs;
|
|
26529
|
+
this.executionIdleWaiters ??= /* @__PURE__ */ new Set();
|
|
26530
|
+
while (this.isExecuting) {
|
|
26531
|
+
const remainingMs = deadline - Date.now();
|
|
26532
|
+
if (remainingMs <= 0) break;
|
|
26533
|
+
await new Promise((resolve) => {
|
|
26534
|
+
const timer = setTimeout(() => {
|
|
26535
|
+
this.executionIdleWaiters.delete(wake);
|
|
26536
|
+
resolve();
|
|
26537
|
+
}, remainingMs);
|
|
26538
|
+
const wake = () => {
|
|
26539
|
+
clearTimeout(timer);
|
|
26540
|
+
resolve();
|
|
26541
|
+
};
|
|
26542
|
+
this.executionIdleWaiters.add(wake);
|
|
26543
|
+
});
|
|
26283
26544
|
}
|
|
26284
26545
|
if (this.isExecuting) {
|
|
26285
26546
|
throw new Error(
|
|
@@ -26418,6 +26679,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
26418
26679
|
return flowResult;
|
|
26419
26680
|
} finally {
|
|
26420
26681
|
this.isExecuting = false;
|
|
26682
|
+
this.notifyExecutionIdleWaiters();
|
|
26421
26683
|
this.currentAbortController = null;
|
|
26422
26684
|
await this.disarmExecutionWatchdog(threadId);
|
|
26423
26685
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -26631,6 +26893,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
26631
26893
|
}, true);
|
|
26632
26894
|
} finally {
|
|
26633
26895
|
this.isExecuting = false;
|
|
26896
|
+
this.notifyExecutionIdleWaiters();
|
|
26634
26897
|
this.currentAbortController = null;
|
|
26635
26898
|
await this.disarmExecutionWatchdog(threadId);
|
|
26636
26899
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -26757,6 +27020,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
26757
27020
|
);
|
|
26758
27021
|
} finally {
|
|
26759
27022
|
this.isExecuting = false;
|
|
27023
|
+
this.notifyExecutionIdleWaiters();
|
|
26760
27024
|
this.currentAbortController = null;
|
|
26761
27025
|
await this.disarmExecutionWatchdog(threadId);
|
|
26762
27026
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -27183,6 +27447,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
27183
27447
|
const fs = this.getFileStorage();
|
|
27184
27448
|
const from = normalizePath2(sourcePath);
|
|
27185
27449
|
const record = await fs.move(sourcePath, destinationPath);
|
|
27450
|
+
this.broadcastEvent("file_moved", { from, path: record.path, file: record });
|
|
27186
27451
|
this.broadcastEvent("file_deleted", { path: from });
|
|
27187
27452
|
this.broadcastEvent("file_created", { path: record.path, file: record });
|
|
27188
27453
|
return { success: true, file: record };
|
|
@@ -27201,6 +27466,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
27201
27466
|
const fs = this.getFileStorage();
|
|
27202
27467
|
const from = normalizePath2(path);
|
|
27203
27468
|
const record = await fs.rename(path, newName);
|
|
27469
|
+
this.broadcastEvent("file_moved", { from, path: record.path, file: record });
|
|
27204
27470
|
this.broadcastEvent("file_deleted", { path: from });
|
|
27205
27471
|
this.broadcastEvent("file_created", { path: record.path, file: record });
|
|
27206
27472
|
return { success: true, file: record };
|