@standardagents/builder 0.37.4 → 0.37.6
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 +184 -83
- 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 +303 -27
- 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 +297 -27
- 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
|
@@ -13738,8 +13738,25 @@ var init_ThreadStateImpl = __esm({
|
|
|
13738
13738
|
const mustParkForApproval = await this.forwardedCallNeedsApproval(request);
|
|
13739
13739
|
if (mustParkForApproval && canPark && toolCallId && typeof instance.hasBridge === "function" && await instance.hasBridge()) {
|
|
13740
13740
|
await instance.dispatchClientTool(request, toolCallId, side);
|
|
13741
|
-
const
|
|
13742
|
-
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;
|
|
13743
13760
|
const graceDeadline = Date.now() + 4e3;
|
|
13744
13761
|
while (Date.now() < graceDeadline) {
|
|
13745
13762
|
await new Promise((r) => setTimeout(r, 150));
|
|
@@ -13829,7 +13846,14 @@ var init_ThreadStateImpl = __esm({
|
|
|
13829
13846
|
if (typeof stub.proxyStreamOpen === "function") {
|
|
13830
13847
|
const opened = await stub.proxyStreamOpen(withWake(parentId));
|
|
13831
13848
|
if (opened.ok && opened.id) {
|
|
13832
|
-
return {
|
|
13849
|
+
return {
|
|
13850
|
+
ok: true,
|
|
13851
|
+
duplex: this.buildProxyDuplex(
|
|
13852
|
+
stub,
|
|
13853
|
+
opened.id,
|
|
13854
|
+
opened.parkedPull === true
|
|
13855
|
+
)
|
|
13856
|
+
};
|
|
13833
13857
|
}
|
|
13834
13858
|
if (opened.error && !opened.noClient) {
|
|
13835
13859
|
return {
|
|
@@ -13847,11 +13871,15 @@ var init_ThreadStateImpl = __esm({
|
|
|
13847
13871
|
}
|
|
13848
13872
|
/**
|
|
13849
13873
|
* Build a client duplex that bridges to a stream opened on an ancestor DO
|
|
13850
|
-
* (the bridge holder) via chunk-pull RPCs. `readable`
|
|
13851
|
-
*
|
|
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
|
|
13852
13879
|
* `proxyStreamAbort`.
|
|
13853
13880
|
*/
|
|
13854
|
-
|
|
13881
|
+
static PROXY_PULL_MAX_WAIT_MS = 2e4;
|
|
13882
|
+
buildProxyDuplex(stub, id, supportsParkedPull = false) {
|
|
13855
13883
|
let settleDone;
|
|
13856
13884
|
const done = new Promise((r) => settleDone = r);
|
|
13857
13885
|
let closed = false;
|
|
@@ -13864,9 +13892,13 @@ var init_ThreadStateImpl = __esm({
|
|
|
13864
13892
|
const readable = new ReadableStream({
|
|
13865
13893
|
async pull(controller) {
|
|
13866
13894
|
while (!closed) {
|
|
13895
|
+
const pullStartedAt = Date.now();
|
|
13867
13896
|
let res;
|
|
13868
13897
|
try {
|
|
13869
|
-
res = await stub.proxyStreamPull(
|
|
13898
|
+
res = await stub.proxyStreamPull(
|
|
13899
|
+
id,
|
|
13900
|
+
supportsParkedPull ? void 0 : _ThreadStateImpl.PROXY_PULL_MAX_WAIT_MS
|
|
13901
|
+
);
|
|
13870
13902
|
} catch (error) {
|
|
13871
13903
|
controller.error(error instanceof Error ? error : new Error(String(error)));
|
|
13872
13904
|
settle({ ok: false, error: "proxy stream pull failed" });
|
|
@@ -13881,7 +13913,14 @@ var init_ThreadStateImpl = __esm({
|
|
|
13881
13913
|
return;
|
|
13882
13914
|
}
|
|
13883
13915
|
if (res.chunks.length > 0) return;
|
|
13884
|
-
|
|
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
|
+
}
|
|
13885
13924
|
}
|
|
13886
13925
|
},
|
|
13887
13926
|
cancel() {
|
|
@@ -14056,7 +14095,7 @@ var init_ThreadStateImpl = __esm({
|
|
|
14056
14095
|
return bytes.buffer;
|
|
14057
14096
|
}
|
|
14058
14097
|
async readFileStream(path21, options) {
|
|
14059
|
-
const fileInfo = await this._threadInstance.statFile(path21);
|
|
14098
|
+
const fileInfo = this._unwrapStatRecord(await this._threadInstance.statFile(path21));
|
|
14060
14099
|
if (!fileInfo) {
|
|
14061
14100
|
return null;
|
|
14062
14101
|
}
|
|
@@ -14111,12 +14150,32 @@ var init_ThreadStateImpl = __esm({
|
|
|
14111
14150
|
}
|
|
14112
14151
|
};
|
|
14113
14152
|
}
|
|
14153
|
+
/**
|
|
14154
|
+
* Unwrap the thread DO's `{ success, file }` stat envelope (older shapes
|
|
14155
|
+
* returned the record directly). A failed or absent lookup is NULL — the
|
|
14156
|
+
* old code mapped the envelope itself, producing a truthy record with
|
|
14157
|
+
* every field defaulted (size 0, application/octet-stream) even for
|
|
14158
|
+
* NONEXISTENT paths, which let a phantom asset pass an existence check and
|
|
14159
|
+
* a real 1.2MB image read as "0 bytes" (2026-08-11).
|
|
14160
|
+
*/
|
|
14161
|
+
_unwrapStatRecord(result) {
|
|
14162
|
+
const record = result?.file ?? result;
|
|
14163
|
+
if (!record || typeof record !== "object") return null;
|
|
14164
|
+
if (!("path" in record) && !("name" in record)) return null;
|
|
14165
|
+
return record;
|
|
14166
|
+
}
|
|
14114
14167
|
async statFile(path21) {
|
|
14115
|
-
const
|
|
14116
|
-
return
|
|
14168
|
+
const record = this._unwrapStatRecord(await this._threadInstance.statFile(path21));
|
|
14169
|
+
return record ? this._mapFileRecord(record) : null;
|
|
14117
14170
|
}
|
|
14118
14171
|
async readdirFile(path21) {
|
|
14119
|
-
const
|
|
14172
|
+
const result = await this._threadInstance.readdirFile(path21);
|
|
14173
|
+
const entries = Array.isArray(result) ? result : result?.files;
|
|
14174
|
+
if (!Array.isArray(entries)) {
|
|
14175
|
+
throw new Error(
|
|
14176
|
+
result?.error || `Could not list directory: ${path21}`
|
|
14177
|
+
);
|
|
14178
|
+
}
|
|
14120
14179
|
return {
|
|
14121
14180
|
entries: entries.map((e) => this._mapFileRecord(e))
|
|
14122
14181
|
};
|
|
@@ -22245,6 +22304,10 @@ ${packedThreadRouteCode}
|
|
|
22245
22304
|
const isApiRoute = routePath.startsWith('/api/');
|
|
22246
22305
|
|
|
22247
22306
|
let authContext = null;
|
|
22307
|
+
// The router's thread-authorization result, bound to the exact matched
|
|
22308
|
+
// thread id, for same-request reuse by controllers (AB-05). Never cached
|
|
22309
|
+
// across requests.
|
|
22310
|
+
let threadAccess = null;
|
|
22248
22311
|
|
|
22249
22312
|
// Require authentication for all API routes except public ones
|
|
22250
22313
|
if (isApiRoute && !publicRoute) {
|
|
@@ -22277,6 +22340,7 @@ ${packedThreadRouteCode}
|
|
|
22277
22340
|
if (!canAccessThread(authContext, thread)) {
|
|
22278
22341
|
return addCorsHeaders(Response.json({ error: "Forbidden: You don't have access to this thread" }, { status: 403 }));
|
|
22279
22342
|
}
|
|
22343
|
+
threadAccess = { threadId, thread };
|
|
22280
22344
|
}
|
|
22281
22345
|
}
|
|
22282
22346
|
}
|
|
@@ -22297,6 +22361,7 @@ ${packedThreadRouteCode}
|
|
|
22297
22361
|
config,
|
|
22298
22362
|
tools,
|
|
22299
22363
|
auth: authContext, // Add auth context to controller context
|
|
22364
|
+
threadAccess, // Router's thread authorization, bound to the exact id
|
|
22300
22365
|
};
|
|
22301
22366
|
const result = await controller(context);
|
|
22302
22367
|
|
|
@@ -24400,6 +24465,9 @@ var AlarmQueue = class {
|
|
|
24400
24465
|
options.sourceLogId ?? null
|
|
24401
24466
|
);
|
|
24402
24467
|
await this.rescheduleAlarm();
|
|
24468
|
+
if (method === "runEffect") {
|
|
24469
|
+
await this.emitEffectRunChange(id);
|
|
24470
|
+
}
|
|
24403
24471
|
return id;
|
|
24404
24472
|
}
|
|
24405
24473
|
/**
|
|
@@ -24438,6 +24506,9 @@ var AlarmQueue = class {
|
|
|
24438
24506
|
`UPDATE alarm_queue SET status = 'processing', attempts = attempts + 1 WHERE id = ?`,
|
|
24439
24507
|
item.id
|
|
24440
24508
|
);
|
|
24509
|
+
if (item.method === "runEffect") {
|
|
24510
|
+
await this.emitEffectRunChange(item.id);
|
|
24511
|
+
}
|
|
24441
24512
|
try {
|
|
24442
24513
|
const args = JSON.parse(item.args);
|
|
24443
24514
|
await this.executeMethod(item.method, args, item.id);
|
|
@@ -24491,6 +24562,7 @@ var AlarmQueue = class {
|
|
|
24491
24562
|
now,
|
|
24492
24563
|
id
|
|
24493
24564
|
);
|
|
24565
|
+
await this.emitEffectRunChange(id);
|
|
24494
24566
|
}
|
|
24495
24567
|
/**
|
|
24496
24568
|
* Mark an item as failed but keep it for 24 hours for debugging
|
|
@@ -24510,6 +24582,7 @@ var AlarmQueue = class {
|
|
|
24510
24582
|
now,
|
|
24511
24583
|
id
|
|
24512
24584
|
);
|
|
24585
|
+
await this.emitEffectRunChange(id);
|
|
24513
24586
|
}
|
|
24514
24587
|
/**
|
|
24515
24588
|
* Clean up failed items older than 24 hours
|
|
@@ -24599,6 +24672,10 @@ var AlarmQueue = class {
|
|
|
24599
24672
|
*/
|
|
24600
24673
|
async recoverOrphanedProcessing(maxAttempts = 3) {
|
|
24601
24674
|
const now = Date.now() * 1e3;
|
|
24675
|
+
const orphanedEffectsCursor = await this.sql.exec(
|
|
24676
|
+
`SELECT id FROM alarm_queue WHERE status = 'processing' AND method = 'runEffect'`
|
|
24677
|
+
);
|
|
24678
|
+
const orphanedEffectIds = orphanedEffectsCursor.toArray().map((row) => row.id);
|
|
24602
24679
|
await this.sql.exec(
|
|
24603
24680
|
`UPDATE alarm_queue
|
|
24604
24681
|
SET status = 'failed',
|
|
@@ -24611,6 +24688,9 @@ var AlarmQueue = class {
|
|
|
24611
24688
|
await this.sql.exec(
|
|
24612
24689
|
`UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
|
|
24613
24690
|
);
|
|
24691
|
+
for (const id of orphanedEffectIds) {
|
|
24692
|
+
await this.emitEffectRunChange(id);
|
|
24693
|
+
}
|
|
24614
24694
|
}
|
|
24615
24695
|
// ============================================================
|
|
24616
24696
|
// Effect-specific Methods
|
|
@@ -24789,6 +24869,7 @@ var AlarmQueue = class {
|
|
|
24789
24869
|
const canceled = changesRows.length > 0 && changesRows[0].changes > 0;
|
|
24790
24870
|
if (canceled) {
|
|
24791
24871
|
await this.rescheduleAlarm();
|
|
24872
|
+
await this.emitEffectRunChange(id);
|
|
24792
24873
|
}
|
|
24793
24874
|
return canceled;
|
|
24794
24875
|
}
|
|
@@ -24863,6 +24944,7 @@ var AlarmQueue = class {
|
|
|
24863
24944
|
if (!changed) {
|
|
24864
24945
|
throw new Error(`Effect run ${item.id} is no longer pending`);
|
|
24865
24946
|
}
|
|
24947
|
+
await this.emitEffectRunChange(item.id);
|
|
24866
24948
|
try {
|
|
24867
24949
|
await this.executeMethod(item.method, JSON.parse(item.args), item.id);
|
|
24868
24950
|
await this.complete(item.id, item.method);
|
|
@@ -24874,6 +24956,45 @@ var AlarmQueue = class {
|
|
|
24874
24956
|
await this.rescheduleAlarm();
|
|
24875
24957
|
}
|
|
24876
24958
|
}
|
|
24959
|
+
/**
|
|
24960
|
+
* Read one effect row and hand its bounded projection (no args payload) to
|
|
24961
|
+
* the change listener. Self-filtering: operational (non-effect) rows and
|
|
24962
|
+
* already-deleted rows emit nothing, and a listener failure must never
|
|
24963
|
+
* disturb queue processing.
|
|
24964
|
+
*/
|
|
24965
|
+
async emitEffectRunChange(id) {
|
|
24966
|
+
const listener = this.options.onEffectRunChanged;
|
|
24967
|
+
if (!listener) return;
|
|
24968
|
+
try {
|
|
24969
|
+
const cursor = await this.sql.exec(
|
|
24970
|
+
`
|
|
24971
|
+
SELECT id, args, scheduled_at, created_at, status, attempts, error, completed_at, manual_run, manual_run_at, replayed_from
|
|
24972
|
+
FROM alarm_queue
|
|
24973
|
+
WHERE id = ? AND method = 'runEffect'
|
|
24974
|
+
LIMIT 1
|
|
24975
|
+
`,
|
|
24976
|
+
id
|
|
24977
|
+
);
|
|
24978
|
+
const row = cursor.toArray()[0];
|
|
24979
|
+
if (!row) return;
|
|
24980
|
+
const { effectName } = this.parseEffectPayload(row.args);
|
|
24981
|
+
listener({
|
|
24982
|
+
id: row.id,
|
|
24983
|
+
name: effectName,
|
|
24984
|
+
scheduledAt: row.scheduled_at,
|
|
24985
|
+
createdAt: row.created_at,
|
|
24986
|
+
status: row.status,
|
|
24987
|
+
attempts: row.attempts,
|
|
24988
|
+
error: row.error,
|
|
24989
|
+
completedAt: row.completed_at,
|
|
24990
|
+
manualRun: row.manual_run === 1,
|
|
24991
|
+
manualRunAt: row.manual_run_at,
|
|
24992
|
+
replayedFrom: row.replayed_from
|
|
24993
|
+
});
|
|
24994
|
+
} catch (error) {
|
|
24995
|
+
console.error(`[AlarmQueue] Failed to emit effect run change for ${id}:`, error);
|
|
24996
|
+
}
|
|
24997
|
+
}
|
|
24877
24998
|
normalizeOffset(value) {
|
|
24878
24999
|
const parsed = typeof value === "number" ? Math.trunc(value) : Number.NaN;
|
|
24879
25000
|
if (!Number.isFinite(parsed)) return 0;
|
|
@@ -29344,6 +29465,25 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
29344
29465
|
alarmQueueRearmed = false;
|
|
29345
29466
|
currentAbortController = null;
|
|
29346
29467
|
isExecuting = false;
|
|
29468
|
+
/**
|
|
29469
|
+
* In-memory waiters parked until the running flow finishes (AB-09): the
|
|
29470
|
+
* executeFlow busy-guard awaits this signal instead of re-checking
|
|
29471
|
+
* isExecuting every 500ms. Every isExecuting=false transition (execution
|
|
29472
|
+
* finallys and the watchdog takeover) notifies. In-memory only — a DO
|
|
29473
|
+
* restart drops waiters together with the busy caller.
|
|
29474
|
+
*/
|
|
29475
|
+
executionIdleWaiters = /* @__PURE__ */ new Set();
|
|
29476
|
+
notifyExecutionIdleWaiters() {
|
|
29477
|
+
if (!this.executionIdleWaiters || this.executionIdleWaiters.size === 0) return;
|
|
29478
|
+
const waiters = Array.from(this.executionIdleWaiters);
|
|
29479
|
+
this.executionIdleWaiters.clear();
|
|
29480
|
+
for (const wake of waiters) {
|
|
29481
|
+
try {
|
|
29482
|
+
wake();
|
|
29483
|
+
} catch {
|
|
29484
|
+
}
|
|
29485
|
+
}
|
|
29486
|
+
}
|
|
29347
29487
|
// ── Execution watchdog state (see watchdogCheck) ─────────────────────────
|
|
29348
29488
|
// In-memory heartbeat: bumped by noteExecutionProgress() on every streamed
|
|
29349
29489
|
// chunk and request attempt. Deliberately NOT persisted — after a DO restart
|
|
@@ -29368,7 +29508,11 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
29368
29508
|
this.alarmQueue = new AlarmQueue(ctx, {
|
|
29369
29509
|
retentionEnabled: env.AGENT_BUILDER_ALARM_RETENTION_ENABLED !== "false",
|
|
29370
29510
|
isForensicHold: async () => (await this.runtimeSafety.getStatus()).forensicHold,
|
|
29371
|
-
invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 }
|
|
29511
|
+
invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 },
|
|
29512
|
+
// Effect lifecycle transitions broadcast on the thread event channel so
|
|
29513
|
+
// inspectors stay live from the already-open message socket instead of
|
|
29514
|
+
// polling the effects list.
|
|
29515
|
+
onEffectRunChanged: (change) => this.broadcastEvent("effect_changed", change)
|
|
29372
29516
|
});
|
|
29373
29517
|
this.alarmQueue.setExecutor(
|
|
29374
29518
|
async (method, args, itemId) => {
|
|
@@ -29927,6 +30071,7 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
29927
30071
|
if (value === null || value === void 0) {
|
|
29928
30072
|
await this.ctx.storage.sql.exec(`DELETE FROM thread_values WHERE key = ?`, key);
|
|
29929
30073
|
this.broadcastLog({ type: "kv_data", key, deleted: true, updated_at: updatedAt });
|
|
30074
|
+
this.broadcastEvent("kv_changed", { key, deleted: true, updated_at: updatedAt });
|
|
29930
30075
|
return;
|
|
29931
30076
|
}
|
|
29932
30077
|
const serialized = this.serializeThreadValue(value);
|
|
@@ -29943,6 +30088,7 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
29943
30088
|
updatedAt
|
|
29944
30089
|
);
|
|
29945
30090
|
this.broadcastLog({ type: "kv_data", key, deleted: false, updated_at: updatedAt });
|
|
30091
|
+
this.broadcastEvent("kv_changed", { key, deleted: false, updated_at: updatedAt });
|
|
29946
30092
|
}
|
|
29947
30093
|
async listValues(options = {}) {
|
|
29948
30094
|
await this.ensureMigrated();
|
|
@@ -32963,6 +33109,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
32963
33109
|
if (isOwner) {
|
|
32964
33110
|
void this.resendPendingClientCalls(server);
|
|
32965
33111
|
}
|
|
33112
|
+
this.notifyOwnerBridgeWaiters();
|
|
32966
33113
|
return new Response(null, {
|
|
32967
33114
|
status: 101,
|
|
32968
33115
|
webSocket: client
|
|
@@ -32998,6 +33145,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
32998
33145
|
/** Persist + cache the execution-owner record (null releases ownership). */
|
|
32999
33146
|
async setExecutionOwner(owner) {
|
|
33000
33147
|
await this.setValue(EXECUTION_OWNER_KEY, owner);
|
|
33148
|
+
this.notifyOwnerBridgeWaiters();
|
|
33001
33149
|
}
|
|
33002
33150
|
/** Whether the given client id has an open bridge socket (excluding `except`).
|
|
33003
33151
|
* Idle bridge heartbeats are handled by Cloudflare without waking this object,
|
|
@@ -33355,6 +33503,55 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33355
33503
|
* permission level covers the call) otherwise race the placeholder into
|
|
33356
33504
|
* stored history, leaving the model blind to its own tool outcomes.
|
|
33357
33505
|
*/
|
|
33506
|
+
/**
|
|
33507
|
+
* In-memory waiters parked on a forwarded call's result delivery (AB-09).
|
|
33508
|
+
* `deliverClientToolResult` resolves them so the approval grace-wait reads
|
|
33509
|
+
* delivered state exactly once instead of re-polling it every 150ms.
|
|
33510
|
+
* In-memory only: a DO restart drops waiters along with the caller that
|
|
33511
|
+
* registered them, and the durable park path still resumes the turn.
|
|
33512
|
+
*/
|
|
33513
|
+
deliveredResultWaiters = /* @__PURE__ */ new Map();
|
|
33514
|
+
notifyDeliveredResultWaiters(toolCallId) {
|
|
33515
|
+
const waiters = this.deliveredResultWaiters?.get(toolCallId);
|
|
33516
|
+
if (!waiters) return;
|
|
33517
|
+
this.deliveredResultWaiters.delete(toolCallId);
|
|
33518
|
+
for (const wake of waiters) {
|
|
33519
|
+
try {
|
|
33520
|
+
wake();
|
|
33521
|
+
} catch {
|
|
33522
|
+
}
|
|
33523
|
+
}
|
|
33524
|
+
}
|
|
33525
|
+
/**
|
|
33526
|
+
* Await a parked forwarded call's delivered result: one immediate read,
|
|
33527
|
+
* then a single parked wait resolved by result delivery, bounded by one
|
|
33528
|
+
* hard deadline, then one final read. Replaces the caller-side 150ms
|
|
33529
|
+
* re-poll of {@link getDeliveredClientToolResult}.
|
|
33530
|
+
*/
|
|
33531
|
+
async awaitDeliveredClientToolResult(toolCallId, maxWaitMs) {
|
|
33532
|
+
const immediate = await this.getDeliveredClientToolResult(toolCallId);
|
|
33533
|
+
if (immediate) return immediate;
|
|
33534
|
+
if (!(maxWaitMs > 0)) return null;
|
|
33535
|
+
await new Promise((resolve3) => {
|
|
33536
|
+
let waiters = this.deliveredResultWaiters.get(toolCallId);
|
|
33537
|
+
if (!waiters) {
|
|
33538
|
+
waiters = /* @__PURE__ */ new Set();
|
|
33539
|
+
this.deliveredResultWaiters.set(toolCallId, waiters);
|
|
33540
|
+
}
|
|
33541
|
+
const registered = waiters;
|
|
33542
|
+
const deadline = setTimeout(() => {
|
|
33543
|
+
registered.delete(wake);
|
|
33544
|
+
if (registered.size === 0) this.deliveredResultWaiters.delete(toolCallId);
|
|
33545
|
+
resolve3();
|
|
33546
|
+
}, maxWaitMs);
|
|
33547
|
+
const wake = () => {
|
|
33548
|
+
clearTimeout(deadline);
|
|
33549
|
+
resolve3();
|
|
33550
|
+
};
|
|
33551
|
+
registered.add(wake);
|
|
33552
|
+
});
|
|
33553
|
+
return this.getDeliveredClientToolResult(toolCallId);
|
|
33554
|
+
}
|
|
33358
33555
|
async getDeliveredClientToolResult(toolCallId) {
|
|
33359
33556
|
await this.ensureMigrated();
|
|
33360
33557
|
await this.ensurePendingClientTable();
|
|
@@ -33490,6 +33687,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33490
33687
|
`UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
|
|
33491
33688
|
toolCallId
|
|
33492
33689
|
);
|
|
33690
|
+
this.notifyDeliveredResultWaiters(toolCallId);
|
|
33493
33691
|
try {
|
|
33494
33692
|
const msgCursor = await this.ctx.storage.sql.exec(
|
|
33495
33693
|
`SELECT id, role, content, name, tool_call_id, tool_status, created_at, depth
|
|
@@ -33508,8 +33706,24 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33508
33706
|
// they are bound to one bridge socket and never park.
|
|
33509
33707
|
/** Default time to wait for the client to accept a stream open. */
|
|
33510
33708
|
static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
|
|
33511
|
-
/**
|
|
33512
|
-
|
|
33709
|
+
/**
|
|
33710
|
+
* In-memory waiters parked on the owner's bridge socket returning (AB-09).
|
|
33711
|
+
* Resolved by bridge attach and by execution-owner changes; each waiter
|
|
33712
|
+
* re-evaluates against the CURRENT owner on wake. In-memory only: a DO
|
|
33713
|
+
* restart drops waiters with the stream-open call that registered them.
|
|
33714
|
+
*/
|
|
33715
|
+
ownerBridgeWaiters = /* @__PURE__ */ new Set();
|
|
33716
|
+
notifyOwnerBridgeWaiters() {
|
|
33717
|
+
if (!this.ownerBridgeWaiters || this.ownerBridgeWaiters.size === 0) return;
|
|
33718
|
+
const waiters = Array.from(this.ownerBridgeWaiters);
|
|
33719
|
+
this.ownerBridgeWaiters.clear();
|
|
33720
|
+
for (const wake of waiters) {
|
|
33721
|
+
try {
|
|
33722
|
+
wake();
|
|
33723
|
+
} catch {
|
|
33724
|
+
}
|
|
33725
|
+
}
|
|
33726
|
+
}
|
|
33513
33727
|
/**
|
|
33514
33728
|
* The thread HAS an elected execution owner, but its bridge socket is gone
|
|
33515
33729
|
* (daemon restart, network blip, DO handoff). Summon the owning machine
|
|
@@ -33518,6 +33732,11 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33518
33732
|
* Sama One turns with "workspace unavailable" whenever a slow tool/approval
|
|
33519
33733
|
* outlived the socket. Threads with NO recorded owner (subagent children)
|
|
33520
33734
|
* still fail fast so ancestor walk-up routing stays immediate.
|
|
33735
|
+
*
|
|
33736
|
+
* The wait parks on bridge-attach/owner-change signals under ONE hard
|
|
33737
|
+
* deadline — no re-poll cadence. Each wake re-reads the owner because the
|
|
33738
|
+
* returning daemon may reclaim with a new generation, and the socket must
|
|
33739
|
+
* match the CURRENT owner.
|
|
33521
33740
|
*/
|
|
33522
33741
|
async awaitOwnerBridgeSocket(request) {
|
|
33523
33742
|
if (request.wake?.userId) {
|
|
@@ -33525,15 +33744,25 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33525
33744
|
}
|
|
33526
33745
|
const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
|
|
33527
33746
|
const deadline = Date.now() + timeoutMs;
|
|
33528
|
-
|
|
33529
|
-
|
|
33530
|
-
(resolve3) => setTimeout(resolve3, _DurableThread.OWNER_REATTACH_POLL_MS)
|
|
33531
|
-
);
|
|
33747
|
+
this.ownerBridgeWaiters ??= /* @__PURE__ */ new Set();
|
|
33748
|
+
while (true) {
|
|
33532
33749
|
const owner = await this.getExecutionOwner();
|
|
33533
33750
|
const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
|
|
33534
33751
|
if (socket) return socket;
|
|
33752
|
+
const remainingMs = deadline - Date.now();
|
|
33753
|
+
if (remainingMs <= 0) return null;
|
|
33754
|
+
await new Promise((resolve3) => {
|
|
33755
|
+
const timer = setTimeout(() => {
|
|
33756
|
+
this.ownerBridgeWaiters.delete(wake);
|
|
33757
|
+
resolve3();
|
|
33758
|
+
}, remainingMs);
|
|
33759
|
+
const wake = () => {
|
|
33760
|
+
clearTimeout(timer);
|
|
33761
|
+
resolve3();
|
|
33762
|
+
};
|
|
33763
|
+
this.ownerBridgeWaiters.add(wake);
|
|
33764
|
+
});
|
|
33535
33765
|
}
|
|
33536
|
-
return null;
|
|
33537
33766
|
}
|
|
33538
33767
|
/** Open a bidirectional byte stream to the connected owner client. */
|
|
33539
33768
|
async openClientStream(request) {
|
|
@@ -33677,27 +33906,38 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33677
33906
|
done: false,
|
|
33678
33907
|
error: void 0,
|
|
33679
33908
|
pump: Promise.resolve(),
|
|
33680
|
-
close: (error) => duplex.close(error)
|
|
33909
|
+
close: (error) => duplex.close(error),
|
|
33910
|
+
waiter: null,
|
|
33911
|
+
notify: () => {
|
|
33912
|
+
const waiter = entry.waiter;
|
|
33913
|
+
entry.waiter = null;
|
|
33914
|
+
waiter?.();
|
|
33915
|
+
}
|
|
33681
33916
|
};
|
|
33682
33917
|
entry.pump = (async () => {
|
|
33683
33918
|
try {
|
|
33684
33919
|
while (true) {
|
|
33685
33920
|
const { value, done } = await reader.read();
|
|
33686
33921
|
if (done) break;
|
|
33687
|
-
if (value)
|
|
33922
|
+
if (value) {
|
|
33923
|
+
entry.buffer.push(value);
|
|
33924
|
+
entry.notify();
|
|
33925
|
+
}
|
|
33688
33926
|
}
|
|
33689
33927
|
} catch (error) {
|
|
33690
33928
|
entry.error = error instanceof Error ? error.message : String(error);
|
|
33691
33929
|
} finally {
|
|
33692
33930
|
entry.done = true;
|
|
33931
|
+
entry.notify();
|
|
33693
33932
|
}
|
|
33694
33933
|
})();
|
|
33695
33934
|
void duplex.done.then((d) => {
|
|
33696
33935
|
entry.done = true;
|
|
33697
33936
|
if (!d.ok && !entry.error) entry.error = d.error;
|
|
33937
|
+
entry.notify();
|
|
33698
33938
|
});
|
|
33699
33939
|
this.proxyStreams.set(id, entry);
|
|
33700
|
-
return { ok: true, id };
|
|
33940
|
+
return { ok: true, id, parkedPull: true };
|
|
33701
33941
|
}
|
|
33702
33942
|
async proxyStreamWrite(id, dataB64) {
|
|
33703
33943
|
const entry = this.proxyStreams.get(id);
|
|
@@ -33707,9 +33947,22 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33707
33947
|
} catch {
|
|
33708
33948
|
}
|
|
33709
33949
|
}
|
|
33710
|
-
async proxyStreamPull(id) {
|
|
33950
|
+
async proxyStreamPull(id, maxWaitMs) {
|
|
33711
33951
|
const entry = this.proxyStreams.get(id);
|
|
33712
33952
|
if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
|
|
33953
|
+
if (entry.buffer.length === 0 && !entry.done) {
|
|
33954
|
+
await new Promise((resolve3) => {
|
|
33955
|
+
const deadline = typeof maxWaitMs === "number" && maxWaitMs > 0 ? setTimeout(() => {
|
|
33956
|
+
if (entry.waiter === wake) entry.waiter = null;
|
|
33957
|
+
resolve3();
|
|
33958
|
+
}, maxWaitMs) : null;
|
|
33959
|
+
const wake = () => {
|
|
33960
|
+
if (deadline) clearTimeout(deadline);
|
|
33961
|
+
resolve3();
|
|
33962
|
+
};
|
|
33963
|
+
entry.waiter = wake;
|
|
33964
|
+
});
|
|
33965
|
+
}
|
|
33713
33966
|
const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
|
|
33714
33967
|
if (entry.done && entry.buffer.length === 0) {
|
|
33715
33968
|
this.proxyStreams.delete(id);
|
|
@@ -33729,6 +33982,9 @@ ${resultContent}${attachmentSummary}`;
|
|
|
33729
33982
|
const entry = this.proxyStreams.get(id);
|
|
33730
33983
|
if (!entry) return;
|
|
33731
33984
|
this.proxyStreams.delete(id);
|
|
33985
|
+
entry.done = true;
|
|
33986
|
+
if (!entry.error) entry.error = "aborted";
|
|
33987
|
+
entry.notify();
|
|
33732
33988
|
try {
|
|
33733
33989
|
entry.close("aborted");
|
|
33734
33990
|
} catch {
|
|
@@ -34084,6 +34340,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
34084
34340
|
this.broadcastEvent(type, data);
|
|
34085
34341
|
}
|
|
34086
34342
|
broadcastEvent(type, data) {
|
|
34343
|
+
if (!this.messageSockets || this.messageSockets.size === 0) return;
|
|
34087
34344
|
const payload = JSON.stringify({
|
|
34088
34345
|
type: "event",
|
|
34089
34346
|
eventType: type,
|
|
@@ -34510,6 +34767,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
34510
34767
|
} catch {
|
|
34511
34768
|
}
|
|
34512
34769
|
this.isExecuting = false;
|
|
34770
|
+
this.notifyExecutionIdleWaiters();
|
|
34513
34771
|
this.currentAbortController = null;
|
|
34514
34772
|
await this.recordRegistryActivity(threadId, false);
|
|
34515
34773
|
tookOver = true;
|
|
@@ -34702,9 +34960,22 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
34702
34960
|
await this.assertNotTerminated();
|
|
34703
34961
|
if (this.isExecuting) {
|
|
34704
34962
|
const busyWaitBudgetMs = 3e4;
|
|
34705
|
-
const
|
|
34706
|
-
|
|
34707
|
-
|
|
34963
|
+
const deadline = Date.now() + busyWaitBudgetMs;
|
|
34964
|
+
this.executionIdleWaiters ??= /* @__PURE__ */ new Set();
|
|
34965
|
+
while (this.isExecuting) {
|
|
34966
|
+
const remainingMs = deadline - Date.now();
|
|
34967
|
+
if (remainingMs <= 0) break;
|
|
34968
|
+
await new Promise((resolve3) => {
|
|
34969
|
+
const timer = setTimeout(() => {
|
|
34970
|
+
this.executionIdleWaiters.delete(wake);
|
|
34971
|
+
resolve3();
|
|
34972
|
+
}, remainingMs);
|
|
34973
|
+
const wake = () => {
|
|
34974
|
+
clearTimeout(timer);
|
|
34975
|
+
resolve3();
|
|
34976
|
+
};
|
|
34977
|
+
this.executionIdleWaiters.add(wake);
|
|
34978
|
+
});
|
|
34708
34979
|
}
|
|
34709
34980
|
if (this.isExecuting) {
|
|
34710
34981
|
throw new Error(
|
|
@@ -34843,6 +35114,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
34843
35114
|
return flowResult;
|
|
34844
35115
|
} finally {
|
|
34845
35116
|
this.isExecuting = false;
|
|
35117
|
+
this.notifyExecutionIdleWaiters();
|
|
34846
35118
|
this.currentAbortController = null;
|
|
34847
35119
|
await this.disarmExecutionWatchdog(threadId);
|
|
34848
35120
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -35056,6 +35328,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
35056
35328
|
}, true);
|
|
35057
35329
|
} finally {
|
|
35058
35330
|
this.isExecuting = false;
|
|
35331
|
+
this.notifyExecutionIdleWaiters();
|
|
35059
35332
|
this.currentAbortController = null;
|
|
35060
35333
|
await this.disarmExecutionWatchdog(threadId);
|
|
35061
35334
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -35182,6 +35455,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
35182
35455
|
);
|
|
35183
35456
|
} finally {
|
|
35184
35457
|
this.isExecuting = false;
|
|
35458
|
+
this.notifyExecutionIdleWaiters();
|
|
35185
35459
|
this.currentAbortController = null;
|
|
35186
35460
|
await this.disarmExecutionWatchdog(threadId);
|
|
35187
35461
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -35608,6 +35882,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
35608
35882
|
const fs19 = this.getFileStorage();
|
|
35609
35883
|
const from = normalizePath2(sourcePath);
|
|
35610
35884
|
const record = await fs19.move(sourcePath, destinationPath);
|
|
35885
|
+
this.broadcastEvent("file_moved", { from, path: record.path, file: record });
|
|
35611
35886
|
this.broadcastEvent("file_deleted", { path: from });
|
|
35612
35887
|
this.broadcastEvent("file_created", { path: record.path, file: record });
|
|
35613
35888
|
return { success: true, file: record };
|
|
@@ -35626,6 +35901,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
35626
35901
|
const fs19 = this.getFileStorage();
|
|
35627
35902
|
const from = normalizePath2(path21);
|
|
35628
35903
|
const record = await fs19.rename(path21, newName);
|
|
35904
|
+
this.broadcastEvent("file_moved", { from, path: record.path, file: record });
|
|
35629
35905
|
this.broadcastEvent("file_deleted", { path: from });
|
|
35630
35906
|
this.broadcastEvent("file_created", { path: record.path, file: record });
|
|
35631
35907
|
return { success: true, file: record };
|