@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/index.js
CHANGED
|
@@ -10112,10 +10112,18 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
|
|
|
10112
10112
|
const mimeType = match[1];
|
|
10113
10113
|
const base64Data = match[2];
|
|
10114
10114
|
const ext = mimeType.split("/")[1] || "png";
|
|
10115
|
+
if (base64Data.length < 64) {
|
|
10116
|
+
console.error("[FlowEngine] Generated image payload is empty/truncated, skipping");
|
|
10117
|
+
continue;
|
|
10118
|
+
}
|
|
10115
10119
|
const attachmentId = crypto.randomUUID();
|
|
10116
10120
|
const path21 = `/attachments/${Date.now()}-${attachmentId.slice(0, 8)}.${ext}`;
|
|
10117
10121
|
try {
|
|
10118
|
-
await state.thread.instance.writeFile(path21, base64Data, mimeType);
|
|
10122
|
+
const written = await state.thread.instance.writeFile(path21, base64Data, mimeType);
|
|
10123
|
+
if (written && written.success === false) {
|
|
10124
|
+
console.error("[FlowEngine] Generated image write failed:", written.error);
|
|
10125
|
+
continue;
|
|
10126
|
+
}
|
|
10119
10127
|
if (image.id) {
|
|
10120
10128
|
imagePathMap.set(image.id, path21);
|
|
10121
10129
|
}
|
|
@@ -10182,10 +10190,18 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
|
|
|
10182
10190
|
const mimeType = imageUrl.slice(dataPrefix.length, base64Index);
|
|
10183
10191
|
const base64Data = imageUrl.slice(base64Index + base64Marker.length);
|
|
10184
10192
|
const ext = mimeType.split("/")[1] || "png";
|
|
10193
|
+
if (base64Data.length < 64) {
|
|
10194
|
+
console.error(`[FlowEngine] ${toolName} image payload is empty/truncated, skipping`);
|
|
10195
|
+
continue;
|
|
10196
|
+
}
|
|
10185
10197
|
const attachmentId = crypto.randomUUID();
|
|
10186
10198
|
const path21 = `/attachments/${Date.now()}-${attachmentId.slice(0, 8)}.${ext}`;
|
|
10187
10199
|
try {
|
|
10188
|
-
await state.thread.instance.writeFile(path21, base64Data, mimeType);
|
|
10200
|
+
const written = await state.thread.instance.writeFile(path21, base64Data, mimeType);
|
|
10201
|
+
if (written && written.success === false) {
|
|
10202
|
+
console.error(`[FlowEngine] ${toolName} image write failed:`, written.error);
|
|
10203
|
+
continue;
|
|
10204
|
+
}
|
|
10189
10205
|
const attachmentRef = {
|
|
10190
10206
|
id: attachmentId,
|
|
10191
10207
|
type: "file",
|
|
@@ -13722,8 +13738,25 @@ var init_ThreadStateImpl = __esm({
|
|
|
13722
13738
|
const mustParkForApproval = await this.forwardedCallNeedsApproval(request);
|
|
13723
13739
|
if (mustParkForApproval && canPark && toolCallId && typeof instance.hasBridge === "function" && await instance.hasBridge()) {
|
|
13724
13740
|
await instance.dispatchClientTool(request, toolCallId, side);
|
|
13725
|
-
const
|
|
13726
|
-
if (typeof
|
|
13741
|
+
const withDelivery = instance;
|
|
13742
|
+
if (typeof withDelivery.awaitDeliveredClientToolResult === "function") {
|
|
13743
|
+
try {
|
|
13744
|
+
const delivered = await withDelivery.awaitDeliveredClientToolResult.call(
|
|
13745
|
+
instance,
|
|
13746
|
+
toolCallId,
|
|
13747
|
+
4e3
|
|
13748
|
+
);
|
|
13749
|
+
if (delivered) {
|
|
13750
|
+
return {
|
|
13751
|
+
ok: delivered.toolStatus !== "error",
|
|
13752
|
+
result: delivered.content,
|
|
13753
|
+
data: { toolCallId }
|
|
13754
|
+
};
|
|
13755
|
+
}
|
|
13756
|
+
} catch {
|
|
13757
|
+
}
|
|
13758
|
+
} else if (typeof withDelivery.getDeliveredClientToolResult === "function") {
|
|
13759
|
+
const getDelivered = withDelivery.getDeliveredClientToolResult;
|
|
13727
13760
|
const graceDeadline = Date.now() + 4e3;
|
|
13728
13761
|
while (Date.now() < graceDeadline) {
|
|
13729
13762
|
await new Promise((r) => setTimeout(r, 150));
|
|
@@ -13813,7 +13846,14 @@ var init_ThreadStateImpl = __esm({
|
|
|
13813
13846
|
if (typeof stub.proxyStreamOpen === "function") {
|
|
13814
13847
|
const opened = await stub.proxyStreamOpen(withWake(parentId));
|
|
13815
13848
|
if (opened.ok && opened.id) {
|
|
13816
|
-
return {
|
|
13849
|
+
return {
|
|
13850
|
+
ok: true,
|
|
13851
|
+
duplex: this.buildProxyDuplex(
|
|
13852
|
+
stub,
|
|
13853
|
+
opened.id,
|
|
13854
|
+
opened.parkedPull === true
|
|
13855
|
+
)
|
|
13856
|
+
};
|
|
13817
13857
|
}
|
|
13818
13858
|
if (opened.error && !opened.noClient) {
|
|
13819
13859
|
return {
|
|
@@ -13831,11 +13871,15 @@ var init_ThreadStateImpl = __esm({
|
|
|
13831
13871
|
}
|
|
13832
13872
|
/**
|
|
13833
13873
|
* Build a client duplex that bridges to a stream opened on an ancestor DO
|
|
13834
|
-
* (the bridge holder) via chunk-pull RPCs. `readable`
|
|
13835
|
-
*
|
|
13874
|
+
* (the bridge holder) via chunk-pull RPCs. `readable` issues PARKED pulls —
|
|
13875
|
+
* a current ancestor holds one `proxyStreamPull` until bytes, completion,
|
|
13876
|
+
* error, or abort, so an idle open stream performs zero repeated pulls.
|
|
13877
|
+
* Older ancestors use the bounded long-pull compatibility path. `writable`
|
|
13878
|
+
* forwards each chunk with `proxyStreamWrite`; teardown calls
|
|
13836
13879
|
* `proxyStreamAbort`.
|
|
13837
13880
|
*/
|
|
13838
|
-
|
|
13881
|
+
static PROXY_PULL_MAX_WAIT_MS = 2e4;
|
|
13882
|
+
buildProxyDuplex(stub, id, supportsParkedPull = false) {
|
|
13839
13883
|
let settleDone;
|
|
13840
13884
|
const done = new Promise((r) => settleDone = r);
|
|
13841
13885
|
let closed = false;
|
|
@@ -13848,9 +13892,13 @@ var init_ThreadStateImpl = __esm({
|
|
|
13848
13892
|
const readable = new ReadableStream({
|
|
13849
13893
|
async pull(controller) {
|
|
13850
13894
|
while (!closed) {
|
|
13895
|
+
const pullStartedAt = Date.now();
|
|
13851
13896
|
let res;
|
|
13852
13897
|
try {
|
|
13853
|
-
res = await stub.proxyStreamPull(
|
|
13898
|
+
res = await stub.proxyStreamPull(
|
|
13899
|
+
id,
|
|
13900
|
+
supportsParkedPull ? void 0 : _ThreadStateImpl.PROXY_PULL_MAX_WAIT_MS
|
|
13901
|
+
);
|
|
13854
13902
|
} catch (error) {
|
|
13855
13903
|
controller.error(error instanceof Error ? error : new Error(String(error)));
|
|
13856
13904
|
settle({ ok: false, error: "proxy stream pull failed" });
|
|
@@ -13865,7 +13913,14 @@ var init_ThreadStateImpl = __esm({
|
|
|
13865
13913
|
return;
|
|
13866
13914
|
}
|
|
13867
13915
|
if (res.chunks.length > 0) return;
|
|
13868
|
-
|
|
13916
|
+
if (supportsParkedPull) {
|
|
13917
|
+
controller.error(new Error("parked proxy pull returned without data"));
|
|
13918
|
+
settle({ ok: false, error: "parked proxy pull returned without data" });
|
|
13919
|
+
return;
|
|
13920
|
+
}
|
|
13921
|
+
if (Date.now() - pullStartedAt < 250) {
|
|
13922
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
13923
|
+
}
|
|
13869
13924
|
}
|
|
13870
13925
|
},
|
|
13871
13926
|
cancel() {
|
|
@@ -22229,6 +22284,10 @@ ${packedThreadRouteCode}
|
|
|
22229
22284
|
const isApiRoute = routePath.startsWith('/api/');
|
|
22230
22285
|
|
|
22231
22286
|
let authContext = null;
|
|
22287
|
+
// The router's thread-authorization result, bound to the exact matched
|
|
22288
|
+
// thread id, for same-request reuse by controllers (AB-05). Never cached
|
|
22289
|
+
// across requests.
|
|
22290
|
+
let threadAccess = null;
|
|
22232
22291
|
|
|
22233
22292
|
// Require authentication for all API routes except public ones
|
|
22234
22293
|
if (isApiRoute && !publicRoute) {
|
|
@@ -22261,6 +22320,7 @@ ${packedThreadRouteCode}
|
|
|
22261
22320
|
if (!canAccessThread(authContext, thread)) {
|
|
22262
22321
|
return addCorsHeaders(Response.json({ error: "Forbidden: You don't have access to this thread" }, { status: 403 }));
|
|
22263
22322
|
}
|
|
22323
|
+
threadAccess = { threadId, thread };
|
|
22264
22324
|
}
|
|
22265
22325
|
}
|
|
22266
22326
|
}
|
|
@@ -22281,6 +22341,7 @@ ${packedThreadRouteCode}
|
|
|
22281
22341
|
config,
|
|
22282
22342
|
tools,
|
|
22283
22343
|
auth: authContext, // Add auth context to controller context
|
|
22344
|
+
threadAccess, // Router's thread authorization, bound to the exact id
|
|
22284
22345
|
};
|
|
22285
22346
|
const result = await controller(context);
|
|
22286
22347
|
|
|
@@ -24384,6 +24445,9 @@ var AlarmQueue = class {
|
|
|
24384
24445
|
options.sourceLogId ?? null
|
|
24385
24446
|
);
|
|
24386
24447
|
await this.rescheduleAlarm();
|
|
24448
|
+
if (method === "runEffect") {
|
|
24449
|
+
await this.emitEffectRunChange(id);
|
|
24450
|
+
}
|
|
24387
24451
|
return id;
|
|
24388
24452
|
}
|
|
24389
24453
|
/**
|
|
@@ -24422,6 +24486,9 @@ var AlarmQueue = class {
|
|
|
24422
24486
|
`UPDATE alarm_queue SET status = 'processing', attempts = attempts + 1 WHERE id = ?`,
|
|
24423
24487
|
item.id
|
|
24424
24488
|
);
|
|
24489
|
+
if (item.method === "runEffect") {
|
|
24490
|
+
await this.emitEffectRunChange(item.id);
|
|
24491
|
+
}
|
|
24425
24492
|
try {
|
|
24426
24493
|
const args = JSON.parse(item.args);
|
|
24427
24494
|
await this.executeMethod(item.method, args, item.id);
|
|
@@ -24475,6 +24542,7 @@ var AlarmQueue = class {
|
|
|
24475
24542
|
now,
|
|
24476
24543
|
id
|
|
24477
24544
|
);
|
|
24545
|
+
await this.emitEffectRunChange(id);
|
|
24478
24546
|
}
|
|
24479
24547
|
/**
|
|
24480
24548
|
* Mark an item as failed but keep it for 24 hours for debugging
|
|
@@ -24494,6 +24562,7 @@ var AlarmQueue = class {
|
|
|
24494
24562
|
now,
|
|
24495
24563
|
id
|
|
24496
24564
|
);
|
|
24565
|
+
await this.emitEffectRunChange(id);
|
|
24497
24566
|
}
|
|
24498
24567
|
/**
|
|
24499
24568
|
* Clean up failed items older than 24 hours
|
|
@@ -24583,6 +24652,10 @@ var AlarmQueue = class {
|
|
|
24583
24652
|
*/
|
|
24584
24653
|
async recoverOrphanedProcessing(maxAttempts = 3) {
|
|
24585
24654
|
const now = Date.now() * 1e3;
|
|
24655
|
+
const orphanedEffectsCursor = await this.sql.exec(
|
|
24656
|
+
`SELECT id FROM alarm_queue WHERE status = 'processing' AND method = 'runEffect'`
|
|
24657
|
+
);
|
|
24658
|
+
const orphanedEffectIds = orphanedEffectsCursor.toArray().map((row) => row.id);
|
|
24586
24659
|
await this.sql.exec(
|
|
24587
24660
|
`UPDATE alarm_queue
|
|
24588
24661
|
SET status = 'failed',
|
|
@@ -24595,6 +24668,9 @@ var AlarmQueue = class {
|
|
|
24595
24668
|
await this.sql.exec(
|
|
24596
24669
|
`UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
|
|
24597
24670
|
);
|
|
24671
|
+
for (const id of orphanedEffectIds) {
|
|
24672
|
+
await this.emitEffectRunChange(id);
|
|
24673
|
+
}
|
|
24598
24674
|
}
|
|
24599
24675
|
// ============================================================
|
|
24600
24676
|
// Effect-specific Methods
|
|
@@ -24773,6 +24849,7 @@ var AlarmQueue = class {
|
|
|
24773
24849
|
const canceled = changesRows.length > 0 && changesRows[0].changes > 0;
|
|
24774
24850
|
if (canceled) {
|
|
24775
24851
|
await this.rescheduleAlarm();
|
|
24852
|
+
await this.emitEffectRunChange(id);
|
|
24776
24853
|
}
|
|
24777
24854
|
return canceled;
|
|
24778
24855
|
}
|
|
@@ -24847,6 +24924,7 @@ var AlarmQueue = class {
|
|
|
24847
24924
|
if (!changed) {
|
|
24848
24925
|
throw new Error(`Effect run ${item.id} is no longer pending`);
|
|
24849
24926
|
}
|
|
24927
|
+
await this.emitEffectRunChange(item.id);
|
|
24850
24928
|
try {
|
|
24851
24929
|
await this.executeMethod(item.method, JSON.parse(item.args), item.id);
|
|
24852
24930
|
await this.complete(item.id, item.method);
|
|
@@ -24858,6 +24936,45 @@ var AlarmQueue = class {
|
|
|
24858
24936
|
await this.rescheduleAlarm();
|
|
24859
24937
|
}
|
|
24860
24938
|
}
|
|
24939
|
+
/**
|
|
24940
|
+
* Read one effect row and hand its bounded projection (no args payload) to
|
|
24941
|
+
* the change listener. Self-filtering: operational (non-effect) rows and
|
|
24942
|
+
* already-deleted rows emit nothing, and a listener failure must never
|
|
24943
|
+
* disturb queue processing.
|
|
24944
|
+
*/
|
|
24945
|
+
async emitEffectRunChange(id) {
|
|
24946
|
+
const listener = this.options.onEffectRunChanged;
|
|
24947
|
+
if (!listener) return;
|
|
24948
|
+
try {
|
|
24949
|
+
const cursor = await this.sql.exec(
|
|
24950
|
+
`
|
|
24951
|
+
SELECT id, args, scheduled_at, created_at, status, attempts, error, completed_at, manual_run, manual_run_at, replayed_from
|
|
24952
|
+
FROM alarm_queue
|
|
24953
|
+
WHERE id = ? AND method = 'runEffect'
|
|
24954
|
+
LIMIT 1
|
|
24955
|
+
`,
|
|
24956
|
+
id
|
|
24957
|
+
);
|
|
24958
|
+
const row = cursor.toArray()[0];
|
|
24959
|
+
if (!row) return;
|
|
24960
|
+
const { effectName } = this.parseEffectPayload(row.args);
|
|
24961
|
+
listener({
|
|
24962
|
+
id: row.id,
|
|
24963
|
+
name: effectName,
|
|
24964
|
+
scheduledAt: row.scheduled_at,
|
|
24965
|
+
createdAt: row.created_at,
|
|
24966
|
+
status: row.status,
|
|
24967
|
+
attempts: row.attempts,
|
|
24968
|
+
error: row.error,
|
|
24969
|
+
completedAt: row.completed_at,
|
|
24970
|
+
manualRun: row.manual_run === 1,
|
|
24971
|
+
manualRunAt: row.manual_run_at,
|
|
24972
|
+
replayedFrom: row.replayed_from
|
|
24973
|
+
});
|
|
24974
|
+
} catch (error) {
|
|
24975
|
+
console.error(`[AlarmQueue] Failed to emit effect run change for ${id}:`, error);
|
|
24976
|
+
}
|
|
24977
|
+
}
|
|
24861
24978
|
normalizeOffset(value) {
|
|
24862
24979
|
const parsed = typeof value === "number" ? Math.trunc(value) : Number.NaN;
|
|
24863
24980
|
if (!Number.isFinite(parsed)) return 0;
|
|
@@ -29328,6 +29445,25 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
29328
29445
|
alarmQueueRearmed = false;
|
|
29329
29446
|
currentAbortController = null;
|
|
29330
29447
|
isExecuting = false;
|
|
29448
|
+
/**
|
|
29449
|
+
* In-memory waiters parked until the running flow finishes (AB-09): the
|
|
29450
|
+
* executeFlow busy-guard awaits this signal instead of re-checking
|
|
29451
|
+
* isExecuting every 500ms. Every isExecuting=false transition (execution
|
|
29452
|
+
* finallys and the watchdog takeover) notifies. In-memory only — a DO
|
|
29453
|
+
* restart drops waiters together with the busy caller.
|
|
29454
|
+
*/
|
|
29455
|
+
executionIdleWaiters = /* @__PURE__ */ new Set();
|
|
29456
|
+
notifyExecutionIdleWaiters() {
|
|
29457
|
+
if (!this.executionIdleWaiters || this.executionIdleWaiters.size === 0) return;
|
|
29458
|
+
const waiters = Array.from(this.executionIdleWaiters);
|
|
29459
|
+
this.executionIdleWaiters.clear();
|
|
29460
|
+
for (const wake of waiters) {
|
|
29461
|
+
try {
|
|
29462
|
+
wake();
|
|
29463
|
+
} catch {
|
|
29464
|
+
}
|
|
29465
|
+
}
|
|
29466
|
+
}
|
|
29331
29467
|
// ── Execution watchdog state (see watchdogCheck) ─────────────────────────
|
|
29332
29468
|
// In-memory heartbeat: bumped by noteExecutionProgress() on every streamed
|
|
29333
29469
|
// chunk and request attempt. Deliberately NOT persisted — after a DO restart
|
|
@@ -29352,7 +29488,11 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
29352
29488
|
this.alarmQueue = new AlarmQueue(ctx, {
|
|
29353
29489
|
retentionEnabled: env.AGENT_BUILDER_ALARM_RETENTION_ENABLED !== "false",
|
|
29354
29490
|
isForensicHold: async () => (await this.runtimeSafety.getStatus()).forensicHold,
|
|
29355
|
-
invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 }
|
|
29491
|
+
invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 },
|
|
29492
|
+
// Effect lifecycle transitions broadcast on the thread event channel so
|
|
29493
|
+
// inspectors stay live from the already-open message socket instead of
|
|
29494
|
+
// polling the effects list.
|
|
29495
|
+
onEffectRunChanged: (change) => this.broadcastEvent("effect_changed", change)
|
|
29356
29496
|
});
|
|
29357
29497
|
this.alarmQueue.setExecutor(
|
|
29358
29498
|
async (method, args, itemId) => {
|
|
@@ -29911,6 +30051,7 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
29911
30051
|
if (value === null || value === void 0) {
|
|
29912
30052
|
await this.ctx.storage.sql.exec(`DELETE FROM thread_values WHERE key = ?`, key);
|
|
29913
30053
|
this.broadcastLog({ type: "kv_data", key, deleted: true, updated_at: updatedAt });
|
|
30054
|
+
this.broadcastEvent("kv_changed", { key, deleted: true, updated_at: updatedAt });
|
|
29914
30055
|
return;
|
|
29915
30056
|
}
|
|
29916
30057
|
const serialized = this.serializeThreadValue(value);
|
|
@@ -29927,6 +30068,7 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
29927
30068
|
updatedAt
|
|
29928
30069
|
);
|
|
29929
30070
|
this.broadcastLog({ type: "kv_data", key, deleted: false, updated_at: updatedAt });
|
|
30071
|
+
this.broadcastEvent("kv_changed", { key, deleted: false, updated_at: updatedAt });
|
|
29930
30072
|
}
|
|
29931
30073
|
async listValues(options = {}) {
|
|
29932
30074
|
await this.ensureMigrated();
|
|
@@ -32947,6 +33089,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
32947
33089
|
if (isOwner) {
|
|
32948
33090
|
void this.resendPendingClientCalls(server);
|
|
32949
33091
|
}
|
|
33092
|
+
this.notifyOwnerBridgeWaiters();
|
|
32950
33093
|
return new Response(null, {
|
|
32951
33094
|
status: 101,
|
|
32952
33095
|
webSocket: client
|
|
@@ -32982,6 +33125,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
32982
33125
|
/** Persist + cache the execution-owner record (null releases ownership). */
|
|
32983
33126
|
async setExecutionOwner(owner) {
|
|
32984
33127
|
await this.setValue(EXECUTION_OWNER_KEY, owner);
|
|
33128
|
+
this.notifyOwnerBridgeWaiters();
|
|
32985
33129
|
}
|
|
32986
33130
|
/** Whether the given client id has an open bridge socket (excluding `except`).
|
|
32987
33131
|
* Idle bridge heartbeats are handled by Cloudflare without waking this object,
|
|
@@ -33339,6 +33483,55 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33339
33483
|
* permission level covers the call) otherwise race the placeholder into
|
|
33340
33484
|
* stored history, leaving the model blind to its own tool outcomes.
|
|
33341
33485
|
*/
|
|
33486
|
+
/**
|
|
33487
|
+
* In-memory waiters parked on a forwarded call's result delivery (AB-09).
|
|
33488
|
+
* `deliverClientToolResult` resolves them so the approval grace-wait reads
|
|
33489
|
+
* delivered state exactly once instead of re-polling it every 150ms.
|
|
33490
|
+
* In-memory only: a DO restart drops waiters along with the caller that
|
|
33491
|
+
* registered them, and the durable park path still resumes the turn.
|
|
33492
|
+
*/
|
|
33493
|
+
deliveredResultWaiters = /* @__PURE__ */ new Map();
|
|
33494
|
+
notifyDeliveredResultWaiters(toolCallId) {
|
|
33495
|
+
const waiters = this.deliveredResultWaiters?.get(toolCallId);
|
|
33496
|
+
if (!waiters) return;
|
|
33497
|
+
this.deliveredResultWaiters.delete(toolCallId);
|
|
33498
|
+
for (const wake of waiters) {
|
|
33499
|
+
try {
|
|
33500
|
+
wake();
|
|
33501
|
+
} catch {
|
|
33502
|
+
}
|
|
33503
|
+
}
|
|
33504
|
+
}
|
|
33505
|
+
/**
|
|
33506
|
+
* Await a parked forwarded call's delivered result: one immediate read,
|
|
33507
|
+
* then a single parked wait resolved by result delivery, bounded by one
|
|
33508
|
+
* hard deadline, then one final read. Replaces the caller-side 150ms
|
|
33509
|
+
* re-poll of {@link getDeliveredClientToolResult}.
|
|
33510
|
+
*/
|
|
33511
|
+
async awaitDeliveredClientToolResult(toolCallId, maxWaitMs) {
|
|
33512
|
+
const immediate = await this.getDeliveredClientToolResult(toolCallId);
|
|
33513
|
+
if (immediate) return immediate;
|
|
33514
|
+
if (!(maxWaitMs > 0)) return null;
|
|
33515
|
+
await new Promise((resolve3) => {
|
|
33516
|
+
let waiters = this.deliveredResultWaiters.get(toolCallId);
|
|
33517
|
+
if (!waiters) {
|
|
33518
|
+
waiters = /* @__PURE__ */ new Set();
|
|
33519
|
+
this.deliveredResultWaiters.set(toolCallId, waiters);
|
|
33520
|
+
}
|
|
33521
|
+
const registered = waiters;
|
|
33522
|
+
const deadline = setTimeout(() => {
|
|
33523
|
+
registered.delete(wake);
|
|
33524
|
+
if (registered.size === 0) this.deliveredResultWaiters.delete(toolCallId);
|
|
33525
|
+
resolve3();
|
|
33526
|
+
}, maxWaitMs);
|
|
33527
|
+
const wake = () => {
|
|
33528
|
+
clearTimeout(deadline);
|
|
33529
|
+
resolve3();
|
|
33530
|
+
};
|
|
33531
|
+
registered.add(wake);
|
|
33532
|
+
});
|
|
33533
|
+
return this.getDeliveredClientToolResult(toolCallId);
|
|
33534
|
+
}
|
|
33342
33535
|
async getDeliveredClientToolResult(toolCallId) {
|
|
33343
33536
|
await this.ensureMigrated();
|
|
33344
33537
|
await this.ensurePendingClientTable();
|
|
@@ -33474,6 +33667,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33474
33667
|
`UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
|
|
33475
33668
|
toolCallId
|
|
33476
33669
|
);
|
|
33670
|
+
this.notifyDeliveredResultWaiters(toolCallId);
|
|
33477
33671
|
try {
|
|
33478
33672
|
const msgCursor = await this.ctx.storage.sql.exec(
|
|
33479
33673
|
`SELECT id, role, content, name, tool_call_id, tool_status, created_at, depth
|
|
@@ -33492,8 +33686,24 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33492
33686
|
// they are bound to one bridge socket and never park.
|
|
33493
33687
|
/** Default time to wait for the client to accept a stream open. */
|
|
33494
33688
|
static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
|
|
33495
|
-
/**
|
|
33496
|
-
|
|
33689
|
+
/**
|
|
33690
|
+
* In-memory waiters parked on the owner's bridge socket returning (AB-09).
|
|
33691
|
+
* Resolved by bridge attach and by execution-owner changes; each waiter
|
|
33692
|
+
* re-evaluates against the CURRENT owner on wake. In-memory only: a DO
|
|
33693
|
+
* restart drops waiters with the stream-open call that registered them.
|
|
33694
|
+
*/
|
|
33695
|
+
ownerBridgeWaiters = /* @__PURE__ */ new Set();
|
|
33696
|
+
notifyOwnerBridgeWaiters() {
|
|
33697
|
+
if (!this.ownerBridgeWaiters || this.ownerBridgeWaiters.size === 0) return;
|
|
33698
|
+
const waiters = Array.from(this.ownerBridgeWaiters);
|
|
33699
|
+
this.ownerBridgeWaiters.clear();
|
|
33700
|
+
for (const wake of waiters) {
|
|
33701
|
+
try {
|
|
33702
|
+
wake();
|
|
33703
|
+
} catch {
|
|
33704
|
+
}
|
|
33705
|
+
}
|
|
33706
|
+
}
|
|
33497
33707
|
/**
|
|
33498
33708
|
* The thread HAS an elected execution owner, but its bridge socket is gone
|
|
33499
33709
|
* (daemon restart, network blip, DO handoff). Summon the owning machine
|
|
@@ -33502,6 +33712,11 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33502
33712
|
* Sama One turns with "workspace unavailable" whenever a slow tool/approval
|
|
33503
33713
|
* outlived the socket. Threads with NO recorded owner (subagent children)
|
|
33504
33714
|
* still fail fast so ancestor walk-up routing stays immediate.
|
|
33715
|
+
*
|
|
33716
|
+
* The wait parks on bridge-attach/owner-change signals under ONE hard
|
|
33717
|
+
* deadline — no re-poll cadence. Each wake re-reads the owner because the
|
|
33718
|
+
* returning daemon may reclaim with a new generation, and the socket must
|
|
33719
|
+
* match the CURRENT owner.
|
|
33505
33720
|
*/
|
|
33506
33721
|
async awaitOwnerBridgeSocket(request) {
|
|
33507
33722
|
if (request.wake?.userId) {
|
|
@@ -33509,15 +33724,25 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33509
33724
|
}
|
|
33510
33725
|
const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
|
|
33511
33726
|
const deadline = Date.now() + timeoutMs;
|
|
33512
|
-
|
|
33513
|
-
|
|
33514
|
-
(resolve3) => setTimeout(resolve3, _DurableThread.OWNER_REATTACH_POLL_MS)
|
|
33515
|
-
);
|
|
33727
|
+
this.ownerBridgeWaiters ??= /* @__PURE__ */ new Set();
|
|
33728
|
+
while (true) {
|
|
33516
33729
|
const owner = await this.getExecutionOwner();
|
|
33517
33730
|
const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
|
|
33518
33731
|
if (socket) return socket;
|
|
33732
|
+
const remainingMs = deadline - Date.now();
|
|
33733
|
+
if (remainingMs <= 0) return null;
|
|
33734
|
+
await new Promise((resolve3) => {
|
|
33735
|
+
const timer = setTimeout(() => {
|
|
33736
|
+
this.ownerBridgeWaiters.delete(wake);
|
|
33737
|
+
resolve3();
|
|
33738
|
+
}, remainingMs);
|
|
33739
|
+
const wake = () => {
|
|
33740
|
+
clearTimeout(timer);
|
|
33741
|
+
resolve3();
|
|
33742
|
+
};
|
|
33743
|
+
this.ownerBridgeWaiters.add(wake);
|
|
33744
|
+
});
|
|
33519
33745
|
}
|
|
33520
|
-
return null;
|
|
33521
33746
|
}
|
|
33522
33747
|
/** Open a bidirectional byte stream to the connected owner client. */
|
|
33523
33748
|
async openClientStream(request) {
|
|
@@ -33661,27 +33886,38 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33661
33886
|
done: false,
|
|
33662
33887
|
error: void 0,
|
|
33663
33888
|
pump: Promise.resolve(),
|
|
33664
|
-
close: (error) => duplex.close(error)
|
|
33889
|
+
close: (error) => duplex.close(error),
|
|
33890
|
+
waiter: null,
|
|
33891
|
+
notify: () => {
|
|
33892
|
+
const waiter = entry.waiter;
|
|
33893
|
+
entry.waiter = null;
|
|
33894
|
+
waiter?.();
|
|
33895
|
+
}
|
|
33665
33896
|
};
|
|
33666
33897
|
entry.pump = (async () => {
|
|
33667
33898
|
try {
|
|
33668
33899
|
while (true) {
|
|
33669
33900
|
const { value, done } = await reader.read();
|
|
33670
33901
|
if (done) break;
|
|
33671
|
-
if (value)
|
|
33902
|
+
if (value) {
|
|
33903
|
+
entry.buffer.push(value);
|
|
33904
|
+
entry.notify();
|
|
33905
|
+
}
|
|
33672
33906
|
}
|
|
33673
33907
|
} catch (error) {
|
|
33674
33908
|
entry.error = error instanceof Error ? error.message : String(error);
|
|
33675
33909
|
} finally {
|
|
33676
33910
|
entry.done = true;
|
|
33911
|
+
entry.notify();
|
|
33677
33912
|
}
|
|
33678
33913
|
})();
|
|
33679
33914
|
void duplex.done.then((d) => {
|
|
33680
33915
|
entry.done = true;
|
|
33681
33916
|
if (!d.ok && !entry.error) entry.error = d.error;
|
|
33917
|
+
entry.notify();
|
|
33682
33918
|
});
|
|
33683
33919
|
this.proxyStreams.set(id, entry);
|
|
33684
|
-
return { ok: true, id };
|
|
33920
|
+
return { ok: true, id, parkedPull: true };
|
|
33685
33921
|
}
|
|
33686
33922
|
async proxyStreamWrite(id, dataB64) {
|
|
33687
33923
|
const entry = this.proxyStreams.get(id);
|
|
@@ -33691,9 +33927,22 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33691
33927
|
} catch {
|
|
33692
33928
|
}
|
|
33693
33929
|
}
|
|
33694
|
-
async proxyStreamPull(id) {
|
|
33930
|
+
async proxyStreamPull(id, maxWaitMs) {
|
|
33695
33931
|
const entry = this.proxyStreams.get(id);
|
|
33696
33932
|
if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
|
|
33933
|
+
if (entry.buffer.length === 0 && !entry.done) {
|
|
33934
|
+
await new Promise((resolve3) => {
|
|
33935
|
+
const deadline = typeof maxWaitMs === "number" && maxWaitMs > 0 ? setTimeout(() => {
|
|
33936
|
+
if (entry.waiter === wake) entry.waiter = null;
|
|
33937
|
+
resolve3();
|
|
33938
|
+
}, maxWaitMs) : null;
|
|
33939
|
+
const wake = () => {
|
|
33940
|
+
if (deadline) clearTimeout(deadline);
|
|
33941
|
+
resolve3();
|
|
33942
|
+
};
|
|
33943
|
+
entry.waiter = wake;
|
|
33944
|
+
});
|
|
33945
|
+
}
|
|
33697
33946
|
const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
|
|
33698
33947
|
if (entry.done && entry.buffer.length === 0) {
|
|
33699
33948
|
this.proxyStreams.delete(id);
|
|
@@ -33713,6 +33962,9 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33713
33962
|
const entry = this.proxyStreams.get(id);
|
|
33714
33963
|
if (!entry) return;
|
|
33715
33964
|
this.proxyStreams.delete(id);
|
|
33965
|
+
entry.done = true;
|
|
33966
|
+
if (!entry.error) entry.error = "aborted";
|
|
33967
|
+
entry.notify();
|
|
33716
33968
|
try {
|
|
33717
33969
|
entry.close("aborted");
|
|
33718
33970
|
} catch {
|
|
@@ -34068,6 +34320,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
34068
34320
|
this.broadcastEvent(type, data);
|
|
34069
34321
|
}
|
|
34070
34322
|
broadcastEvent(type, data) {
|
|
34323
|
+
if (!this.messageSockets || this.messageSockets.size === 0) return;
|
|
34071
34324
|
const payload = JSON.stringify({
|
|
34072
34325
|
type: "event",
|
|
34073
34326
|
eventType: type,
|
|
@@ -34494,6 +34747,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
34494
34747
|
} catch {
|
|
34495
34748
|
}
|
|
34496
34749
|
this.isExecuting = false;
|
|
34750
|
+
this.notifyExecutionIdleWaiters();
|
|
34497
34751
|
this.currentAbortController = null;
|
|
34498
34752
|
await this.recordRegistryActivity(threadId, false);
|
|
34499
34753
|
tookOver = true;
|
|
@@ -34686,9 +34940,22 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
34686
34940
|
await this.assertNotTerminated();
|
|
34687
34941
|
if (this.isExecuting) {
|
|
34688
34942
|
const busyWaitBudgetMs = 3e4;
|
|
34689
|
-
const
|
|
34690
|
-
|
|
34691
|
-
|
|
34943
|
+
const deadline = Date.now() + busyWaitBudgetMs;
|
|
34944
|
+
this.executionIdleWaiters ??= /* @__PURE__ */ new Set();
|
|
34945
|
+
while (this.isExecuting) {
|
|
34946
|
+
const remainingMs = deadline - Date.now();
|
|
34947
|
+
if (remainingMs <= 0) break;
|
|
34948
|
+
await new Promise((resolve3) => {
|
|
34949
|
+
const timer = setTimeout(() => {
|
|
34950
|
+
this.executionIdleWaiters.delete(wake);
|
|
34951
|
+
resolve3();
|
|
34952
|
+
}, remainingMs);
|
|
34953
|
+
const wake = () => {
|
|
34954
|
+
clearTimeout(timer);
|
|
34955
|
+
resolve3();
|
|
34956
|
+
};
|
|
34957
|
+
this.executionIdleWaiters.add(wake);
|
|
34958
|
+
});
|
|
34692
34959
|
}
|
|
34693
34960
|
if (this.isExecuting) {
|
|
34694
34961
|
throw new Error(
|
|
@@ -34827,6 +35094,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
34827
35094
|
return flowResult;
|
|
34828
35095
|
} finally {
|
|
34829
35096
|
this.isExecuting = false;
|
|
35097
|
+
this.notifyExecutionIdleWaiters();
|
|
34830
35098
|
this.currentAbortController = null;
|
|
34831
35099
|
await this.disarmExecutionWatchdog(threadId);
|
|
34832
35100
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -35040,6 +35308,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
35040
35308
|
}, true);
|
|
35041
35309
|
} finally {
|
|
35042
35310
|
this.isExecuting = false;
|
|
35311
|
+
this.notifyExecutionIdleWaiters();
|
|
35043
35312
|
this.currentAbortController = null;
|
|
35044
35313
|
await this.disarmExecutionWatchdog(threadId);
|
|
35045
35314
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -35166,6 +35435,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
35166
35435
|
);
|
|
35167
35436
|
} finally {
|
|
35168
35437
|
this.isExecuting = false;
|
|
35438
|
+
this.notifyExecutionIdleWaiters();
|
|
35169
35439
|
this.currentAbortController = null;
|
|
35170
35440
|
await this.disarmExecutionWatchdog(threadId);
|
|
35171
35441
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -35592,6 +35862,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
35592
35862
|
const fs19 = this.getFileStorage();
|
|
35593
35863
|
const from = normalizePath2(sourcePath);
|
|
35594
35864
|
const record = await fs19.move(sourcePath, destinationPath);
|
|
35865
|
+
this.broadcastEvent("file_moved", { from, path: record.path, file: record });
|
|
35595
35866
|
this.broadcastEvent("file_deleted", { path: from });
|
|
35596
35867
|
this.broadcastEvent("file_created", { path: record.path, file: record });
|
|
35597
35868
|
return { success: true, file: record };
|
|
@@ -35610,6 +35881,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
35610
35881
|
const fs19 = this.getFileStorage();
|
|
35611
35882
|
const from = normalizePath2(path21);
|
|
35612
35883
|
const record = await fs19.rename(path21, newName);
|
|
35884
|
+
this.broadcastEvent("file_moved", { from, path: record.path, file: record });
|
|
35613
35885
|
this.broadcastEvent("file_deleted", { path: from });
|
|
35614
35886
|
this.broadcastEvent("file_created", { path: record.path, file: record });
|
|
35615
35887
|
return { success: true, file: record };
|