@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/runtime.js
CHANGED
|
@@ -13729,8 +13729,25 @@ var init_ThreadStateImpl = __esm({
|
|
|
13729
13729
|
const mustParkForApproval = await this.forwardedCallNeedsApproval(request);
|
|
13730
13730
|
if (mustParkForApproval && canPark && toolCallId && typeof instance.hasBridge === "function" && await instance.hasBridge()) {
|
|
13731
13731
|
await instance.dispatchClientTool(request, toolCallId, side);
|
|
13732
|
-
const
|
|
13733
|
-
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;
|
|
13734
13751
|
const graceDeadline = Date.now() + 4e3;
|
|
13735
13752
|
while (Date.now() < graceDeadline) {
|
|
13736
13753
|
await new Promise((r) => setTimeout(r, 150));
|
|
@@ -13820,7 +13837,14 @@ var init_ThreadStateImpl = __esm({
|
|
|
13820
13837
|
if (typeof stub.proxyStreamOpen === "function") {
|
|
13821
13838
|
const opened = await stub.proxyStreamOpen(withWake(parentId));
|
|
13822
13839
|
if (opened.ok && opened.id) {
|
|
13823
|
-
return {
|
|
13840
|
+
return {
|
|
13841
|
+
ok: true,
|
|
13842
|
+
duplex: this.buildProxyDuplex(
|
|
13843
|
+
stub,
|
|
13844
|
+
opened.id,
|
|
13845
|
+
opened.parkedPull === true
|
|
13846
|
+
)
|
|
13847
|
+
};
|
|
13824
13848
|
}
|
|
13825
13849
|
if (opened.error && !opened.noClient) {
|
|
13826
13850
|
return {
|
|
@@ -13838,11 +13862,15 @@ var init_ThreadStateImpl = __esm({
|
|
|
13838
13862
|
}
|
|
13839
13863
|
/**
|
|
13840
13864
|
* Build a client duplex that bridges to a stream opened on an ancestor DO
|
|
13841
|
-
* (the bridge holder) via chunk-pull RPCs. `readable`
|
|
13842
|
-
*
|
|
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
|
|
13843
13870
|
* `proxyStreamAbort`.
|
|
13844
13871
|
*/
|
|
13845
|
-
|
|
13872
|
+
static PROXY_PULL_MAX_WAIT_MS = 2e4;
|
|
13873
|
+
buildProxyDuplex(stub, id, supportsParkedPull = false) {
|
|
13846
13874
|
let settleDone;
|
|
13847
13875
|
const done = new Promise((r) => settleDone = r);
|
|
13848
13876
|
let closed = false;
|
|
@@ -13855,9 +13883,13 @@ var init_ThreadStateImpl = __esm({
|
|
|
13855
13883
|
const readable = new ReadableStream({
|
|
13856
13884
|
async pull(controller) {
|
|
13857
13885
|
while (!closed) {
|
|
13886
|
+
const pullStartedAt = Date.now();
|
|
13858
13887
|
let res;
|
|
13859
13888
|
try {
|
|
13860
|
-
res = await stub.proxyStreamPull(
|
|
13889
|
+
res = await stub.proxyStreamPull(
|
|
13890
|
+
id,
|
|
13891
|
+
supportsParkedPull ? void 0 : _ThreadStateImpl.PROXY_PULL_MAX_WAIT_MS
|
|
13892
|
+
);
|
|
13861
13893
|
} catch (error) {
|
|
13862
13894
|
controller.error(error instanceof Error ? error : new Error(String(error)));
|
|
13863
13895
|
settle({ ok: false, error: "proxy stream pull failed" });
|
|
@@ -13872,7 +13904,14 @@ var init_ThreadStateImpl = __esm({
|
|
|
13872
13904
|
return;
|
|
13873
13905
|
}
|
|
13874
13906
|
if (res.chunks.length > 0) return;
|
|
13875
|
-
|
|
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
|
+
}
|
|
13876
13915
|
}
|
|
13877
13916
|
},
|
|
13878
13917
|
cancel() {
|
|
@@ -14047,7 +14086,7 @@ var init_ThreadStateImpl = __esm({
|
|
|
14047
14086
|
return bytes.buffer;
|
|
14048
14087
|
}
|
|
14049
14088
|
async readFileStream(path, options) {
|
|
14050
|
-
const fileInfo = await this._threadInstance.statFile(path);
|
|
14089
|
+
const fileInfo = this._unwrapStatRecord(await this._threadInstance.statFile(path));
|
|
14051
14090
|
if (!fileInfo) {
|
|
14052
14091
|
return null;
|
|
14053
14092
|
}
|
|
@@ -14102,12 +14141,32 @@ var init_ThreadStateImpl = __esm({
|
|
|
14102
14141
|
}
|
|
14103
14142
|
};
|
|
14104
14143
|
}
|
|
14144
|
+
/**
|
|
14145
|
+
* Unwrap the thread DO's `{ success, file }` stat envelope (older shapes
|
|
14146
|
+
* returned the record directly). A failed or absent lookup is NULL — the
|
|
14147
|
+
* old code mapped the envelope itself, producing a truthy record with
|
|
14148
|
+
* every field defaulted (size 0, application/octet-stream) even for
|
|
14149
|
+
* NONEXISTENT paths, which let a phantom asset pass an existence check and
|
|
14150
|
+
* a real 1.2MB image read as "0 bytes" (2026-08-11).
|
|
14151
|
+
*/
|
|
14152
|
+
_unwrapStatRecord(result) {
|
|
14153
|
+
const record = result?.file ?? result;
|
|
14154
|
+
if (!record || typeof record !== "object") return null;
|
|
14155
|
+
if (!("path" in record) && !("name" in record)) return null;
|
|
14156
|
+
return record;
|
|
14157
|
+
}
|
|
14105
14158
|
async statFile(path) {
|
|
14106
|
-
const
|
|
14107
|
-
return
|
|
14159
|
+
const record = this._unwrapStatRecord(await this._threadInstance.statFile(path));
|
|
14160
|
+
return record ? this._mapFileRecord(record) : null;
|
|
14108
14161
|
}
|
|
14109
14162
|
async readdirFile(path) {
|
|
14110
|
-
const
|
|
14163
|
+
const result = await this._threadInstance.readdirFile(path);
|
|
14164
|
+
const entries = Array.isArray(result) ? result : result?.files;
|
|
14165
|
+
if (!Array.isArray(entries)) {
|
|
14166
|
+
throw new Error(
|
|
14167
|
+
result?.error || `Could not list directory: ${path}`
|
|
14168
|
+
);
|
|
14169
|
+
}
|
|
14111
14170
|
return {
|
|
14112
14171
|
entries: entries.map((e) => this._mapFileRecord(e))
|
|
14113
14172
|
};
|
|
@@ -15991,6 +16050,9 @@ var AlarmQueue = class {
|
|
|
15991
16050
|
options.sourceLogId ?? null
|
|
15992
16051
|
);
|
|
15993
16052
|
await this.rescheduleAlarm();
|
|
16053
|
+
if (method === "runEffect") {
|
|
16054
|
+
await this.emitEffectRunChange(id);
|
|
16055
|
+
}
|
|
15994
16056
|
return id;
|
|
15995
16057
|
}
|
|
15996
16058
|
/**
|
|
@@ -16029,6 +16091,9 @@ var AlarmQueue = class {
|
|
|
16029
16091
|
`UPDATE alarm_queue SET status = 'processing', attempts = attempts + 1 WHERE id = ?`,
|
|
16030
16092
|
item.id
|
|
16031
16093
|
);
|
|
16094
|
+
if (item.method === "runEffect") {
|
|
16095
|
+
await this.emitEffectRunChange(item.id);
|
|
16096
|
+
}
|
|
16032
16097
|
try {
|
|
16033
16098
|
const args = JSON.parse(item.args);
|
|
16034
16099
|
await this.executeMethod(item.method, args, item.id);
|
|
@@ -16082,6 +16147,7 @@ var AlarmQueue = class {
|
|
|
16082
16147
|
now,
|
|
16083
16148
|
id
|
|
16084
16149
|
);
|
|
16150
|
+
await this.emitEffectRunChange(id);
|
|
16085
16151
|
}
|
|
16086
16152
|
/**
|
|
16087
16153
|
* Mark an item as failed but keep it for 24 hours for debugging
|
|
@@ -16101,6 +16167,7 @@ var AlarmQueue = class {
|
|
|
16101
16167
|
now,
|
|
16102
16168
|
id
|
|
16103
16169
|
);
|
|
16170
|
+
await this.emitEffectRunChange(id);
|
|
16104
16171
|
}
|
|
16105
16172
|
/**
|
|
16106
16173
|
* Clean up failed items older than 24 hours
|
|
@@ -16190,6 +16257,10 @@ var AlarmQueue = class {
|
|
|
16190
16257
|
*/
|
|
16191
16258
|
async recoverOrphanedProcessing(maxAttempts = 3) {
|
|
16192
16259
|
const now = Date.now() * 1e3;
|
|
16260
|
+
const orphanedEffectsCursor = await this.sql.exec(
|
|
16261
|
+
`SELECT id FROM alarm_queue WHERE status = 'processing' AND method = 'runEffect'`
|
|
16262
|
+
);
|
|
16263
|
+
const orphanedEffectIds = orphanedEffectsCursor.toArray().map((row) => row.id);
|
|
16193
16264
|
await this.sql.exec(
|
|
16194
16265
|
`UPDATE alarm_queue
|
|
16195
16266
|
SET status = 'failed',
|
|
@@ -16202,6 +16273,9 @@ var AlarmQueue = class {
|
|
|
16202
16273
|
await this.sql.exec(
|
|
16203
16274
|
`UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
|
|
16204
16275
|
);
|
|
16276
|
+
for (const id of orphanedEffectIds) {
|
|
16277
|
+
await this.emitEffectRunChange(id);
|
|
16278
|
+
}
|
|
16205
16279
|
}
|
|
16206
16280
|
// ============================================================
|
|
16207
16281
|
// Effect-specific Methods
|
|
@@ -16380,6 +16454,7 @@ var AlarmQueue = class {
|
|
|
16380
16454
|
const canceled = changesRows.length > 0 && changesRows[0].changes > 0;
|
|
16381
16455
|
if (canceled) {
|
|
16382
16456
|
await this.rescheduleAlarm();
|
|
16457
|
+
await this.emitEffectRunChange(id);
|
|
16383
16458
|
}
|
|
16384
16459
|
return canceled;
|
|
16385
16460
|
}
|
|
@@ -16454,6 +16529,7 @@ var AlarmQueue = class {
|
|
|
16454
16529
|
if (!changed) {
|
|
16455
16530
|
throw new Error(`Effect run ${item.id} is no longer pending`);
|
|
16456
16531
|
}
|
|
16532
|
+
await this.emitEffectRunChange(item.id);
|
|
16457
16533
|
try {
|
|
16458
16534
|
await this.executeMethod(item.method, JSON.parse(item.args), item.id);
|
|
16459
16535
|
await this.complete(item.id, item.method);
|
|
@@ -16465,6 +16541,45 @@ var AlarmQueue = class {
|
|
|
16465
16541
|
await this.rescheduleAlarm();
|
|
16466
16542
|
}
|
|
16467
16543
|
}
|
|
16544
|
+
/**
|
|
16545
|
+
* Read one effect row and hand its bounded projection (no args payload) to
|
|
16546
|
+
* the change listener. Self-filtering: operational (non-effect) rows and
|
|
16547
|
+
* already-deleted rows emit nothing, and a listener failure must never
|
|
16548
|
+
* disturb queue processing.
|
|
16549
|
+
*/
|
|
16550
|
+
async emitEffectRunChange(id) {
|
|
16551
|
+
const listener = this.options.onEffectRunChanged;
|
|
16552
|
+
if (!listener) return;
|
|
16553
|
+
try {
|
|
16554
|
+
const cursor = await this.sql.exec(
|
|
16555
|
+
`
|
|
16556
|
+
SELECT id, args, scheduled_at, created_at, status, attempts, error, completed_at, manual_run, manual_run_at, replayed_from
|
|
16557
|
+
FROM alarm_queue
|
|
16558
|
+
WHERE id = ? AND method = 'runEffect'
|
|
16559
|
+
LIMIT 1
|
|
16560
|
+
`,
|
|
16561
|
+
id
|
|
16562
|
+
);
|
|
16563
|
+
const row = cursor.toArray()[0];
|
|
16564
|
+
if (!row) return;
|
|
16565
|
+
const { effectName } = this.parseEffectPayload(row.args);
|
|
16566
|
+
listener({
|
|
16567
|
+
id: row.id,
|
|
16568
|
+
name: effectName,
|
|
16569
|
+
scheduledAt: row.scheduled_at,
|
|
16570
|
+
createdAt: row.created_at,
|
|
16571
|
+
status: row.status,
|
|
16572
|
+
attempts: row.attempts,
|
|
16573
|
+
error: row.error,
|
|
16574
|
+
completedAt: row.completed_at,
|
|
16575
|
+
manualRun: row.manual_run === 1,
|
|
16576
|
+
manualRunAt: row.manual_run_at,
|
|
16577
|
+
replayedFrom: row.replayed_from
|
|
16578
|
+
});
|
|
16579
|
+
} catch (error) {
|
|
16580
|
+
console.error(`[AlarmQueue] Failed to emit effect run change for ${id}:`, error);
|
|
16581
|
+
}
|
|
16582
|
+
}
|
|
16468
16583
|
normalizeOffset(value) {
|
|
16469
16584
|
const parsed = typeof value === "number" ? Math.trunc(value) : Number.NaN;
|
|
16470
16585
|
if (!Number.isFinite(parsed)) return 0;
|
|
@@ -20935,6 +21050,25 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
20935
21050
|
alarmQueueRearmed = false;
|
|
20936
21051
|
currentAbortController = null;
|
|
20937
21052
|
isExecuting = false;
|
|
21053
|
+
/**
|
|
21054
|
+
* In-memory waiters parked until the running flow finishes (AB-09): the
|
|
21055
|
+
* executeFlow busy-guard awaits this signal instead of re-checking
|
|
21056
|
+
* isExecuting every 500ms. Every isExecuting=false transition (execution
|
|
21057
|
+
* finallys and the watchdog takeover) notifies. In-memory only — a DO
|
|
21058
|
+
* restart drops waiters together with the busy caller.
|
|
21059
|
+
*/
|
|
21060
|
+
executionIdleWaiters = /* @__PURE__ */ new Set();
|
|
21061
|
+
notifyExecutionIdleWaiters() {
|
|
21062
|
+
if (!this.executionIdleWaiters || this.executionIdleWaiters.size === 0) return;
|
|
21063
|
+
const waiters = Array.from(this.executionIdleWaiters);
|
|
21064
|
+
this.executionIdleWaiters.clear();
|
|
21065
|
+
for (const wake of waiters) {
|
|
21066
|
+
try {
|
|
21067
|
+
wake();
|
|
21068
|
+
} catch {
|
|
21069
|
+
}
|
|
21070
|
+
}
|
|
21071
|
+
}
|
|
20938
21072
|
// ── Execution watchdog state (see watchdogCheck) ─────────────────────────
|
|
20939
21073
|
// In-memory heartbeat: bumped by noteExecutionProgress() on every streamed
|
|
20940
21074
|
// chunk and request attempt. Deliberately NOT persisted — after a DO restart
|
|
@@ -20959,7 +21093,11 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
20959
21093
|
this.alarmQueue = new AlarmQueue(ctx, {
|
|
20960
21094
|
retentionEnabled: env.AGENT_BUILDER_ALARM_RETENTION_ENABLED !== "false",
|
|
20961
21095
|
isForensicHold: async () => (await this.runtimeSafety.getStatus()).forensicHold,
|
|
20962
|
-
invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 }
|
|
21096
|
+
invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 },
|
|
21097
|
+
// Effect lifecycle transitions broadcast on the thread event channel so
|
|
21098
|
+
// inspectors stay live from the already-open message socket instead of
|
|
21099
|
+
// polling the effects list.
|
|
21100
|
+
onEffectRunChanged: (change) => this.broadcastEvent("effect_changed", change)
|
|
20963
21101
|
});
|
|
20964
21102
|
this.alarmQueue.setExecutor(
|
|
20965
21103
|
async (method, args, itemId) => {
|
|
@@ -21518,6 +21656,7 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
21518
21656
|
if (value === null || value === void 0) {
|
|
21519
21657
|
await this.ctx.storage.sql.exec(`DELETE FROM thread_values WHERE key = ?`, key);
|
|
21520
21658
|
this.broadcastLog({ type: "kv_data", key, deleted: true, updated_at: updatedAt });
|
|
21659
|
+
this.broadcastEvent("kv_changed", { key, deleted: true, updated_at: updatedAt });
|
|
21521
21660
|
return;
|
|
21522
21661
|
}
|
|
21523
21662
|
const serialized = this.serializeThreadValue(value);
|
|
@@ -21534,6 +21673,7 @@ var DurableThread = class _DurableThread extends DurableObject {
|
|
|
21534
21673
|
updatedAt
|
|
21535
21674
|
);
|
|
21536
21675
|
this.broadcastLog({ type: "kv_data", key, deleted: false, updated_at: updatedAt });
|
|
21676
|
+
this.broadcastEvent("kv_changed", { key, deleted: false, updated_at: updatedAt });
|
|
21537
21677
|
}
|
|
21538
21678
|
async listValues(options = {}) {
|
|
21539
21679
|
await this.ensureMigrated();
|
|
@@ -24554,6 +24694,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
24554
24694
|
if (isOwner) {
|
|
24555
24695
|
void this.resendPendingClientCalls(server);
|
|
24556
24696
|
}
|
|
24697
|
+
this.notifyOwnerBridgeWaiters();
|
|
24557
24698
|
return new Response(null, {
|
|
24558
24699
|
status: 101,
|
|
24559
24700
|
webSocket: client
|
|
@@ -24589,6 +24730,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
24589
24730
|
/** Persist + cache the execution-owner record (null releases ownership). */
|
|
24590
24731
|
async setExecutionOwner(owner) {
|
|
24591
24732
|
await this.setValue(EXECUTION_OWNER_KEY, owner);
|
|
24733
|
+
this.notifyOwnerBridgeWaiters();
|
|
24592
24734
|
}
|
|
24593
24735
|
/** Whether the given client id has an open bridge socket (excluding `except`).
|
|
24594
24736
|
* Idle bridge heartbeats are handled by Cloudflare without waking this object,
|
|
@@ -24946,6 +25088,55 @@ ${resultContent}${attachmentSummary}`;
|
|
|
24946
25088
|
* permission level covers the call) otherwise race the placeholder into
|
|
24947
25089
|
* stored history, leaving the model blind to its own tool outcomes.
|
|
24948
25090
|
*/
|
|
25091
|
+
/**
|
|
25092
|
+
* In-memory waiters parked on a forwarded call's result delivery (AB-09).
|
|
25093
|
+
* `deliverClientToolResult` resolves them so the approval grace-wait reads
|
|
25094
|
+
* delivered state exactly once instead of re-polling it every 150ms.
|
|
25095
|
+
* In-memory only: a DO restart drops waiters along with the caller that
|
|
25096
|
+
* registered them, and the durable park path still resumes the turn.
|
|
25097
|
+
*/
|
|
25098
|
+
deliveredResultWaiters = /* @__PURE__ */ new Map();
|
|
25099
|
+
notifyDeliveredResultWaiters(toolCallId) {
|
|
25100
|
+
const waiters = this.deliveredResultWaiters?.get(toolCallId);
|
|
25101
|
+
if (!waiters) return;
|
|
25102
|
+
this.deliveredResultWaiters.delete(toolCallId);
|
|
25103
|
+
for (const wake of waiters) {
|
|
25104
|
+
try {
|
|
25105
|
+
wake();
|
|
25106
|
+
} catch {
|
|
25107
|
+
}
|
|
25108
|
+
}
|
|
25109
|
+
}
|
|
25110
|
+
/**
|
|
25111
|
+
* Await a parked forwarded call's delivered result: one immediate read,
|
|
25112
|
+
* then a single parked wait resolved by result delivery, bounded by one
|
|
25113
|
+
* hard deadline, then one final read. Replaces the caller-side 150ms
|
|
25114
|
+
* re-poll of {@link getDeliveredClientToolResult}.
|
|
25115
|
+
*/
|
|
25116
|
+
async awaitDeliveredClientToolResult(toolCallId, maxWaitMs) {
|
|
25117
|
+
const immediate = await this.getDeliveredClientToolResult(toolCallId);
|
|
25118
|
+
if (immediate) return immediate;
|
|
25119
|
+
if (!(maxWaitMs > 0)) return null;
|
|
25120
|
+
await new Promise((resolve) => {
|
|
25121
|
+
let waiters = this.deliveredResultWaiters.get(toolCallId);
|
|
25122
|
+
if (!waiters) {
|
|
25123
|
+
waiters = /* @__PURE__ */ new Set();
|
|
25124
|
+
this.deliveredResultWaiters.set(toolCallId, waiters);
|
|
25125
|
+
}
|
|
25126
|
+
const registered = waiters;
|
|
25127
|
+
const deadline = setTimeout(() => {
|
|
25128
|
+
registered.delete(wake);
|
|
25129
|
+
if (registered.size === 0) this.deliveredResultWaiters.delete(toolCallId);
|
|
25130
|
+
resolve();
|
|
25131
|
+
}, maxWaitMs);
|
|
25132
|
+
const wake = () => {
|
|
25133
|
+
clearTimeout(deadline);
|
|
25134
|
+
resolve();
|
|
25135
|
+
};
|
|
25136
|
+
registered.add(wake);
|
|
25137
|
+
});
|
|
25138
|
+
return this.getDeliveredClientToolResult(toolCallId);
|
|
25139
|
+
}
|
|
24949
25140
|
async getDeliveredClientToolResult(toolCallId) {
|
|
24950
25141
|
await this.ensureMigrated();
|
|
24951
25142
|
await this.ensurePendingClientTable();
|
|
@@ -25081,6 +25272,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25081
25272
|
`UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
|
|
25082
25273
|
toolCallId
|
|
25083
25274
|
);
|
|
25275
|
+
this.notifyDeliveredResultWaiters(toolCallId);
|
|
25084
25276
|
try {
|
|
25085
25277
|
const msgCursor = await this.ctx.storage.sql.exec(
|
|
25086
25278
|
`SELECT id, role, content, name, tool_call_id, tool_status, created_at, depth
|
|
@@ -25099,8 +25291,24 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25099
25291
|
// they are bound to one bridge socket and never park.
|
|
25100
25292
|
/** Default time to wait for the client to accept a stream open. */
|
|
25101
25293
|
static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
|
|
25102
|
-
/**
|
|
25103
|
-
|
|
25294
|
+
/**
|
|
25295
|
+
* In-memory waiters parked on the owner's bridge socket returning (AB-09).
|
|
25296
|
+
* Resolved by bridge attach and by execution-owner changes; each waiter
|
|
25297
|
+
* re-evaluates against the CURRENT owner on wake. In-memory only: a DO
|
|
25298
|
+
* restart drops waiters with the stream-open call that registered them.
|
|
25299
|
+
*/
|
|
25300
|
+
ownerBridgeWaiters = /* @__PURE__ */ new Set();
|
|
25301
|
+
notifyOwnerBridgeWaiters() {
|
|
25302
|
+
if (!this.ownerBridgeWaiters || this.ownerBridgeWaiters.size === 0) return;
|
|
25303
|
+
const waiters = Array.from(this.ownerBridgeWaiters);
|
|
25304
|
+
this.ownerBridgeWaiters.clear();
|
|
25305
|
+
for (const wake of waiters) {
|
|
25306
|
+
try {
|
|
25307
|
+
wake();
|
|
25308
|
+
} catch {
|
|
25309
|
+
}
|
|
25310
|
+
}
|
|
25311
|
+
}
|
|
25104
25312
|
/**
|
|
25105
25313
|
* The thread HAS an elected execution owner, but its bridge socket is gone
|
|
25106
25314
|
* (daemon restart, network blip, DO handoff). Summon the owning machine
|
|
@@ -25109,6 +25317,11 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25109
25317
|
* Sama One turns with "workspace unavailable" whenever a slow tool/approval
|
|
25110
25318
|
* outlived the socket. Threads with NO recorded owner (subagent children)
|
|
25111
25319
|
* still fail fast so ancestor walk-up routing stays immediate.
|
|
25320
|
+
*
|
|
25321
|
+
* The wait parks on bridge-attach/owner-change signals under ONE hard
|
|
25322
|
+
* deadline — no re-poll cadence. Each wake re-reads the owner because the
|
|
25323
|
+
* returning daemon may reclaim with a new generation, and the socket must
|
|
25324
|
+
* match the CURRENT owner.
|
|
25112
25325
|
*/
|
|
25113
25326
|
async awaitOwnerBridgeSocket(request) {
|
|
25114
25327
|
if (request.wake?.userId) {
|
|
@@ -25116,15 +25329,25 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25116
25329
|
}
|
|
25117
25330
|
const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
|
|
25118
25331
|
const deadline = Date.now() + timeoutMs;
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
(resolve) => setTimeout(resolve, _DurableThread.OWNER_REATTACH_POLL_MS)
|
|
25122
|
-
);
|
|
25332
|
+
this.ownerBridgeWaiters ??= /* @__PURE__ */ new Set();
|
|
25333
|
+
while (true) {
|
|
25123
25334
|
const owner = await this.getExecutionOwner();
|
|
25124
25335
|
const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
|
|
25125
25336
|
if (socket) return socket;
|
|
25337
|
+
const remainingMs = deadline - Date.now();
|
|
25338
|
+
if (remainingMs <= 0) return null;
|
|
25339
|
+
await new Promise((resolve) => {
|
|
25340
|
+
const timer = setTimeout(() => {
|
|
25341
|
+
this.ownerBridgeWaiters.delete(wake);
|
|
25342
|
+
resolve();
|
|
25343
|
+
}, remainingMs);
|
|
25344
|
+
const wake = () => {
|
|
25345
|
+
clearTimeout(timer);
|
|
25346
|
+
resolve();
|
|
25347
|
+
};
|
|
25348
|
+
this.ownerBridgeWaiters.add(wake);
|
|
25349
|
+
});
|
|
25126
25350
|
}
|
|
25127
|
-
return null;
|
|
25128
25351
|
}
|
|
25129
25352
|
/** Open a bidirectional byte stream to the connected owner client. */
|
|
25130
25353
|
async openClientStream(request) {
|
|
@@ -25268,27 +25491,38 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25268
25491
|
done: false,
|
|
25269
25492
|
error: void 0,
|
|
25270
25493
|
pump: Promise.resolve(),
|
|
25271
|
-
close: (error) => duplex.close(error)
|
|
25494
|
+
close: (error) => duplex.close(error),
|
|
25495
|
+
waiter: null,
|
|
25496
|
+
notify: () => {
|
|
25497
|
+
const waiter = entry.waiter;
|
|
25498
|
+
entry.waiter = null;
|
|
25499
|
+
waiter?.();
|
|
25500
|
+
}
|
|
25272
25501
|
};
|
|
25273
25502
|
entry.pump = (async () => {
|
|
25274
25503
|
try {
|
|
25275
25504
|
while (true) {
|
|
25276
25505
|
const { value, done } = await reader.read();
|
|
25277
25506
|
if (done) break;
|
|
25278
|
-
if (value)
|
|
25507
|
+
if (value) {
|
|
25508
|
+
entry.buffer.push(value);
|
|
25509
|
+
entry.notify();
|
|
25510
|
+
}
|
|
25279
25511
|
}
|
|
25280
25512
|
} catch (error) {
|
|
25281
25513
|
entry.error = error instanceof Error ? error.message : String(error);
|
|
25282
25514
|
} finally {
|
|
25283
25515
|
entry.done = true;
|
|
25516
|
+
entry.notify();
|
|
25284
25517
|
}
|
|
25285
25518
|
})();
|
|
25286
25519
|
void duplex.done.then((d) => {
|
|
25287
25520
|
entry.done = true;
|
|
25288
25521
|
if (!d.ok && !entry.error) entry.error = d.error;
|
|
25522
|
+
entry.notify();
|
|
25289
25523
|
});
|
|
25290
25524
|
this.proxyStreams.set(id, entry);
|
|
25291
|
-
return { ok: true, id };
|
|
25525
|
+
return { ok: true, id, parkedPull: true };
|
|
25292
25526
|
}
|
|
25293
25527
|
async proxyStreamWrite(id, dataB64) {
|
|
25294
25528
|
const entry = this.proxyStreams.get(id);
|
|
@@ -25298,9 +25532,22 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25298
25532
|
} catch {
|
|
25299
25533
|
}
|
|
25300
25534
|
}
|
|
25301
|
-
async proxyStreamPull(id) {
|
|
25535
|
+
async proxyStreamPull(id, maxWaitMs) {
|
|
25302
25536
|
const entry = this.proxyStreams.get(id);
|
|
25303
25537
|
if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
|
|
25538
|
+
if (entry.buffer.length === 0 && !entry.done) {
|
|
25539
|
+
await new Promise((resolve) => {
|
|
25540
|
+
const deadline = typeof maxWaitMs === "number" && maxWaitMs > 0 ? setTimeout(() => {
|
|
25541
|
+
if (entry.waiter === wake) entry.waiter = null;
|
|
25542
|
+
resolve();
|
|
25543
|
+
}, maxWaitMs) : null;
|
|
25544
|
+
const wake = () => {
|
|
25545
|
+
if (deadline) clearTimeout(deadline);
|
|
25546
|
+
resolve();
|
|
25547
|
+
};
|
|
25548
|
+
entry.waiter = wake;
|
|
25549
|
+
});
|
|
25550
|
+
}
|
|
25304
25551
|
const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
|
|
25305
25552
|
if (entry.done && entry.buffer.length === 0) {
|
|
25306
25553
|
this.proxyStreams.delete(id);
|
|
@@ -25320,6 +25567,9 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25320
25567
|
const entry = this.proxyStreams.get(id);
|
|
25321
25568
|
if (!entry) return;
|
|
25322
25569
|
this.proxyStreams.delete(id);
|
|
25570
|
+
entry.done = true;
|
|
25571
|
+
if (!entry.error) entry.error = "aborted";
|
|
25572
|
+
entry.notify();
|
|
25323
25573
|
try {
|
|
25324
25574
|
entry.close("aborted");
|
|
25325
25575
|
} catch {
|
|
@@ -25675,6 +25925,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
25675
25925
|
this.broadcastEvent(type, data);
|
|
25676
25926
|
}
|
|
25677
25927
|
broadcastEvent(type, data) {
|
|
25928
|
+
if (!this.messageSockets || this.messageSockets.size === 0) return;
|
|
25678
25929
|
const payload = JSON.stringify({
|
|
25679
25930
|
type: "event",
|
|
25680
25931
|
eventType: type,
|
|
@@ -26101,6 +26352,7 @@ ${resultContent}${attachmentSummary}`;
|
|
|
26101
26352
|
} catch {
|
|
26102
26353
|
}
|
|
26103
26354
|
this.isExecuting = false;
|
|
26355
|
+
this.notifyExecutionIdleWaiters();
|
|
26104
26356
|
this.currentAbortController = null;
|
|
26105
26357
|
await this.recordRegistryActivity(threadId, false);
|
|
26106
26358
|
tookOver = true;
|
|
@@ -26293,9 +26545,22 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
26293
26545
|
await this.assertNotTerminated();
|
|
26294
26546
|
if (this.isExecuting) {
|
|
26295
26547
|
const busyWaitBudgetMs = 3e4;
|
|
26296
|
-
const
|
|
26297
|
-
|
|
26298
|
-
|
|
26548
|
+
const deadline = Date.now() + busyWaitBudgetMs;
|
|
26549
|
+
this.executionIdleWaiters ??= /* @__PURE__ */ new Set();
|
|
26550
|
+
while (this.isExecuting) {
|
|
26551
|
+
const remainingMs = deadline - Date.now();
|
|
26552
|
+
if (remainingMs <= 0) break;
|
|
26553
|
+
await new Promise((resolve) => {
|
|
26554
|
+
const timer = setTimeout(() => {
|
|
26555
|
+
this.executionIdleWaiters.delete(wake);
|
|
26556
|
+
resolve();
|
|
26557
|
+
}, remainingMs);
|
|
26558
|
+
const wake = () => {
|
|
26559
|
+
clearTimeout(timer);
|
|
26560
|
+
resolve();
|
|
26561
|
+
};
|
|
26562
|
+
this.executionIdleWaiters.add(wake);
|
|
26563
|
+
});
|
|
26299
26564
|
}
|
|
26300
26565
|
if (this.isExecuting) {
|
|
26301
26566
|
throw new Error(
|
|
@@ -26434,6 +26699,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
26434
26699
|
return flowResult;
|
|
26435
26700
|
} finally {
|
|
26436
26701
|
this.isExecuting = false;
|
|
26702
|
+
this.notifyExecutionIdleWaiters();
|
|
26437
26703
|
this.currentAbortController = null;
|
|
26438
26704
|
await this.disarmExecutionWatchdog(threadId);
|
|
26439
26705
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -26647,6 +26913,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
26647
26913
|
}, true);
|
|
26648
26914
|
} finally {
|
|
26649
26915
|
this.isExecuting = false;
|
|
26916
|
+
this.notifyExecutionIdleWaiters();
|
|
26650
26917
|
this.currentAbortController = null;
|
|
26651
26918
|
await this.disarmExecutionWatchdog(threadId);
|
|
26652
26919
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -26773,6 +27040,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
26773
27040
|
);
|
|
26774
27041
|
} finally {
|
|
26775
27042
|
this.isExecuting = false;
|
|
27043
|
+
this.notifyExecutionIdleWaiters();
|
|
26776
27044
|
this.currentAbortController = null;
|
|
26777
27045
|
await this.disarmExecutionWatchdog(threadId);
|
|
26778
27046
|
await this.recordRegistryActivity(threadId, false);
|
|
@@ -27199,6 +27467,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
27199
27467
|
const fs = this.getFileStorage();
|
|
27200
27468
|
const from = normalizePath2(sourcePath);
|
|
27201
27469
|
const record = await fs.move(sourcePath, destinationPath);
|
|
27470
|
+
this.broadcastEvent("file_moved", { from, path: record.path, file: record });
|
|
27202
27471
|
this.broadcastEvent("file_deleted", { path: from });
|
|
27203
27472
|
this.broadcastEvent("file_created", { path: record.path, file: record });
|
|
27204
27473
|
return { success: true, file: record };
|
|
@@ -27217,6 +27486,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
|
|
|
27217
27486
|
const fs = this.getFileStorage();
|
|
27218
27487
|
const from = normalizePath2(path);
|
|
27219
27488
|
const record = await fs.rename(path, newName);
|
|
27489
|
+
this.broadcastEvent("file_moved", { from, path: record.path, file: record });
|
|
27220
27490
|
this.broadcastEvent("file_deleted", { path: from });
|
|
27221
27491
|
this.broadcastEvent("file_created", { path: record.path, file: record });
|
|
27222
27492
|
return { success: true, file: record };
|