agents 0.29.0 → 0.30.0
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/browser/tools/pi.d.ts +6 -2
- package/dist/browser/tools/pi.js +6 -2
- package/dist/browser/tools/pi.js.map +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +46 -45
- package/dist/experimental/channels/web/index.js +1 -1
- package/dist/harness/container/index.js +1 -1
- package/dist/harness/opencode/index.js +1 -1
- package/dist/harness/pi/index.d.ts +57 -35
- package/dist/harness/pi/index.js +261 -119
- package/dist/harness/pi/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lifecycle/index.js +1 -1
- package/dist/{lifecycle-ClT4DmF2.js → lifecycle-v1rKHV2C.js} +57 -17
- package/dist/lifecycle-v1rKHV2C.js.map +1 -0
- package/dist/mcp/index.js +1 -1
- package/dist/models/pi-ai/index.js +2 -2
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/{src-6lr7MtM_.js → src-DfLzdwlK.js} +3 -3
- package/dist/{src-6lr7MtM_.js.map → src-DfLzdwlK.js.map} +1 -1
- package/dist/web/fetch/tools/pi.d.ts +10 -6
- package/dist/web/fetch/tools/pi.js +10 -6
- package/dist/web/fetch/tools/pi.js.map +1 -1
- package/dist/web/search/tools/pi.d.ts +10 -6
- package/dist/web/search/tools/pi.js +10 -6
- package/dist/web/search/tools/pi.js.map +1 -1
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-CqxnntmE.js → websockets-CyoeosdO.js} +2 -2
- package/dist/{websockets-CqxnntmE.js.map → websockets-CyoeosdO.js.map} +1 -1
- package/dist/workflows.js +1 -1
- package/docs/harnesses/pi.md +62 -28
- package/docs/lifecycle.md +7 -1
- package/docs/models-pi-ai.md +5 -1
- package/package.json +11 -6
- package/dist/lifecycle-ClT4DmF2.js.map +0 -1
package/dist/harness/pi/index.js
CHANGED
|
@@ -1,30 +1,12 @@
|
|
|
1
1
|
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../../classPrivateFieldGet2-DZBYAB34.js";
|
|
2
2
|
import { t as _classPrivateMethodInitSpec } from "../../classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
3
3
|
import { t as LifecycleCapability } from "../../capability-DBAKc6UZ.js";
|
|
4
|
-
import "../../lifecycle-
|
|
4
|
+
import "../../lifecycle-v1rKHV2C.js";
|
|
5
|
+
import { isDurableObjectMemoryLimitReset } from "../../retries.js";
|
|
5
6
|
import { Type } from "@earendil-works/pi-ai";
|
|
6
7
|
import { LiveDoc, ROOT_CONVERSATION_ID, watchEvents } from "@earendil-works/pi-durable";
|
|
8
|
+
import { BACKGROUND_CONTEXT, withAbortSignal } from "@earendil-works/chord/context";
|
|
7
9
|
import { openDurableObjectSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/cloudflare";
|
|
8
|
-
//#region src/harness/pi/context.ts
|
|
9
|
-
/** chord's `BACKGROUND_CONTEXT`: no values, never cancelled. */
|
|
10
|
-
const BACKGROUND_CONTEXT = Object.freeze({
|
|
11
|
-
abortSignal: void 0,
|
|
12
|
-
value: () => void 0,
|
|
13
|
-
toString: () => "[Context pi-harness background]"
|
|
14
|
-
});
|
|
15
|
-
/**
|
|
16
|
-
* chord's `withAbortSignal`: a context cancelled by either the parent's
|
|
17
|
-
* signal or `signal`, carrying the parent's values.
|
|
18
|
-
*/
|
|
19
|
-
function withAbortSignal(signal, parent) {
|
|
20
|
-
const abortSignal = parent.abortSignal === void 0 ? signal : AbortSignal.any([parent.abortSignal, signal]);
|
|
21
|
-
return Object.freeze({
|
|
22
|
-
abortSignal,
|
|
23
|
-
value: parent.value.bind(parent),
|
|
24
|
-
toString: () => `${parent}.WithAbortSignal`
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
10
|
//#region src/harness/pi/session-store.ts
|
|
29
11
|
const DEFAULT_PREFIX = "pi_";
|
|
30
12
|
/** Open pi-durable's storage over this object's SQLite database. */
|
|
@@ -39,15 +21,10 @@ const BG = BACKGROUND_CONTEXT;
|
|
|
39
21
|
/** The root session's id. */
|
|
40
22
|
const ROOT_SESSION = String(ROOT_CONVERSATION_ID);
|
|
41
23
|
/**
|
|
42
|
-
* The
|
|
43
|
-
*
|
|
24
|
+
* The wake timings. They suit a real deployment; only tests change them,
|
|
25
|
+
* with `setWakeTimingForTests`, so a suite does not sit on the real
|
|
44
26
|
* heartbeat.
|
|
45
27
|
*
|
|
46
|
-
* A pi long wait further away than this is handed to the alarm, so the
|
|
47
|
-
* alarm, not pi's in-memory timer, is what wakes the object.
|
|
48
|
-
*/
|
|
49
|
-
const SLEEP_THRESHOLD_MS = 6e4;
|
|
50
|
-
/**
|
|
51
28
|
* Longest one wake waits on pi. It waits inside an alarm invocation, which
|
|
52
29
|
* has a 15 minute wall-time limit, so a longer run is waited on across
|
|
53
30
|
* several alarms.
|
|
@@ -59,13 +36,28 @@ const WAIT_BUDGET_MS = 10 * 6e4;
|
|
|
59
36
|
* job is still due and its alarm restarts the object.
|
|
60
37
|
*/
|
|
61
38
|
const HEARTBEAT_MS = 3e4;
|
|
39
|
+
/**
|
|
40
|
+
* How often a wait re-reads `pi.wake()` when the factory did not pass
|
|
41
|
+
* `onWake`, so nothing tells the harness when it changes.
|
|
42
|
+
*/
|
|
43
|
+
const POLL_MS = 1e3;
|
|
44
|
+
/**
|
|
45
|
+
* The longest a wake job whose dispatch keeps failing waits before it tries
|
|
46
|
+
* again. Its delay starts at the heartbeat and doubles up to this.
|
|
47
|
+
*/
|
|
48
|
+
const MAX_WAKE_BACKOFF_MS = 10 * 6e4;
|
|
49
|
+
/**
|
|
50
|
+
* Wake dispatches that failed in a row. Stored, so the backoff holds across
|
|
51
|
+
* restarts, and outside the job: a push that saved it in the job's payload
|
|
52
|
+
* could overwrite a submit's due-now push.
|
|
53
|
+
*/
|
|
54
|
+
const WAKE_ERRORS_KEY = "pi-harness:wake-errors";
|
|
62
55
|
const WAKE_FN = "wake";
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
56
|
+
/**
|
|
57
|
+
* The one wake job. Before Pi 1.4 there was one per session, with ids
|
|
58
|
+
* `pi-wake:<session>`; a left-over one hands over to this one.
|
|
59
|
+
*/
|
|
60
|
+
const WAKE_JOB_ID = "pi-wake";
|
|
69
61
|
/** pi-durable's stored reference to a pi-ai model. */
|
|
70
62
|
function modelRef(model) {
|
|
71
63
|
return {
|
|
@@ -88,13 +80,18 @@ function signalContext(signal) {
|
|
|
88
80
|
return signal ? withAbortSignal(signal, BG) : BG;
|
|
89
81
|
}
|
|
90
82
|
var _options = /* @__PURE__ */ new WeakMap();
|
|
91
|
-
var
|
|
83
|
+
var _waiting = /* @__PURE__ */ new WeakMap();
|
|
84
|
+
var _changed = /* @__PURE__ */ new WeakMap();
|
|
92
85
|
var _admitting = /* @__PURE__ */ new WeakMap();
|
|
93
|
-
var
|
|
86
|
+
var _onWakeWired = /* @__PURE__ */ new WeakMap();
|
|
94
87
|
var _waitBudgetMs = /* @__PURE__ */ new WeakMap();
|
|
95
88
|
var _heartbeatMs = /* @__PURE__ */ new WeakMap();
|
|
89
|
+
var _pollMs = /* @__PURE__ */ new WeakMap();
|
|
96
90
|
var _opening = /* @__PURE__ */ new WeakMap();
|
|
91
|
+
var _lastClose = /* @__PURE__ */ new WeakMap();
|
|
92
|
+
var _closing = /* @__PURE__ */ new WeakMap();
|
|
97
93
|
var _PiHarness_brand = /* @__PURE__ */ new WeakSet();
|
|
94
|
+
var _onWake = /* @__PURE__ */ new WeakMap();
|
|
98
95
|
/**
|
|
99
96
|
* pi-durable hosted in a Durable Object, behind a small harness interface:
|
|
100
97
|
* `harness.prompt()`, `harness.sessions`, `harness.session(id)`. How a session reaches a
|
|
@@ -107,12 +104,14 @@ var _PiHarness_brand = /* @__PURE__ */ new WeakSet();
|
|
|
107
104
|
*
|
|
108
105
|
* What pi cannot do on a Durable Object is wake itself: its scheduler runs
|
|
109
106
|
* in memory, and an evicted object has no memory. The harness is that wake,
|
|
110
|
-
* with one Lifecycle job
|
|
111
|
-
* after the
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
107
|
+
* with one Lifecycle job driven by `pi.wake()`. Input goes to pi once, in
|
|
108
|
+
* `submit()`, after the job is scheduled. While pi has work running, the
|
|
109
|
+
* job waits on it, rescheduling itself as a heartbeat. While pi's tasks
|
|
110
|
+
* sleep, the job is due when the first one wakes; pi tells the harness
|
|
111
|
+
* through the `onWake` the factory passes to `Harness.open`. When pi has
|
|
112
|
+
* nothing to do, the job completes. An eviction mid-run leaves the job due,
|
|
113
|
+
* so its alarm restarts the object, pi reopens and resumes its own tasks,
|
|
114
|
+
* and the job waits again.
|
|
116
115
|
*
|
|
117
116
|
* @beta The API may change between releases.
|
|
118
117
|
*/
|
|
@@ -121,25 +120,54 @@ var PiHarness = class extends LifecycleCapability {
|
|
|
121
120
|
super("pi-harness");
|
|
122
121
|
_classPrivateMethodInitSpec(this, _PiHarness_brand);
|
|
123
122
|
_classPrivateFieldInitSpec(this, _options, void 0);
|
|
124
|
-
_classPrivateFieldInitSpec(this,
|
|
125
|
-
_classPrivateFieldInitSpec(this,
|
|
126
|
-
_classPrivateFieldInitSpec(this,
|
|
123
|
+
_classPrivateFieldInitSpec(this, _waiting, void 0);
|
|
124
|
+
_classPrivateFieldInitSpec(this, _changed, void 0);
|
|
125
|
+
_classPrivateFieldInitSpec(this, _admitting, 0);
|
|
126
|
+
_classPrivateFieldInitSpec(this, _onWakeWired, false);
|
|
127
127
|
_classPrivateFieldInitSpec(this, _waitBudgetMs, WAIT_BUDGET_MS);
|
|
128
128
|
_classPrivateFieldInitSpec(this, _heartbeatMs, HEARTBEAT_MS);
|
|
129
|
+
_classPrivateFieldInitSpec(this, _pollMs, POLL_MS);
|
|
129
130
|
_classPrivateFieldInitSpec(this, _opening, void 0);
|
|
131
|
+
_classPrivateFieldInitSpec(this, _lastClose, Promise.resolve());
|
|
132
|
+
_classPrivateFieldInitSpec(this, _closing, /* @__PURE__ */ new WeakSet());
|
|
133
|
+
_classPrivateFieldInitSpec(this, _onWake, (wake) => {
|
|
134
|
+
_classPrivateFieldSet2(_onWakeWired, this, true);
|
|
135
|
+
_classPrivateFieldGet2(_changed, this)?.call(this);
|
|
136
|
+
if (_classPrivateFieldGet2(_waiting, this)) return;
|
|
137
|
+
_assertClassBrand(_PiHarness_brand, this, _wakeFor).call(this, wake).catch((error) => _assertClassBrand(_PiHarness_brand, this, _wakeError).call(this, error));
|
|
138
|
+
});
|
|
130
139
|
_classPrivateFieldSet2(_options, this, options);
|
|
131
140
|
this.sessions = new PiSessions(this);
|
|
132
141
|
}
|
|
133
142
|
async onStart(_context) {
|
|
134
|
-
|
|
135
|
-
const inspection = await pi.inspect(BG);
|
|
136
|
-
const sessions = new Set(inspection.tasks.map((task) => String(task.record.conversationId)));
|
|
137
|
-
for (const session of sessions) await _assertClassBrand(_PiHarness_brand, this, _wake).call(this, session);
|
|
143
|
+
await _assertClassBrand(_PiHarness_brand, this, _open).call(this);
|
|
138
144
|
}
|
|
139
145
|
async onJob(context) {
|
|
140
146
|
if (context.job.fn !== WAKE_FN) return;
|
|
141
|
-
|
|
142
|
-
if (
|
|
147
|
+
let outcome;
|
|
148
|
+
if (context.job.id !== WAKE_JOB_ID) {
|
|
149
|
+
await _assertClassBrand(_PiHarness_brand, this, _wake).call(this);
|
|
150
|
+
outcome = void 0;
|
|
151
|
+
} else outcome = await _assertClassBrand(_PiHarness_brand, this, _wakeStep).call(this);
|
|
152
|
+
const kv = this.lifecycle.storage.kv;
|
|
153
|
+
if (kv.get(WAKE_ERRORS_KEY) !== void 0) kv.delete(WAKE_ERRORS_KEY);
|
|
154
|
+
return outcome;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A wake dispatch that failed through Lifecycle's retries, as when pi
|
|
158
|
+
* cannot be opened. Lifecycle would complete the job, and pi's work would
|
|
159
|
+
* never be woken again, so try again later: at the heartbeat, doubling
|
|
160
|
+
* while the failures continue, up to 10 minutes.
|
|
161
|
+
*/
|
|
162
|
+
onJobError(context, error) {
|
|
163
|
+
if (context.job.fn !== WAKE_FN) return;
|
|
164
|
+
console.error(`pi wake job "${context.job.id}" failed after ${context.attempt} attempts; retrying with backoff`, error);
|
|
165
|
+
_assertClassBrand(_PiHarness_brand, this, _wakeError).call(this, error);
|
|
166
|
+
const kv = this.lifecycle.storage.kv;
|
|
167
|
+
const errors = (kv.get(WAKE_ERRORS_KEY) ?? 0) + 1;
|
|
168
|
+
kv.put(WAKE_ERRORS_KEY, errors);
|
|
169
|
+
const delay = Math.min(_classPrivateFieldGet2(_heartbeatMs, this) * 2 ** (errors - 1), MAX_WAKE_BACKOFF_MS);
|
|
170
|
+
return { rescheduleAt: Date.now() + delay };
|
|
143
171
|
}
|
|
144
172
|
/** Close pi's in-memory resources. Durable state is untouched. */
|
|
145
173
|
async dispose() {
|
|
@@ -163,6 +191,13 @@ var PiHarness = class extends LifecycleCapability {
|
|
|
163
191
|
abort(options = {}) {
|
|
164
192
|
return this.session(options.session).abort(options.operationId);
|
|
165
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Run again the tasks pi's crash-loop guard parked in a session's work.
|
|
196
|
+
* False when nothing was parked.
|
|
197
|
+
*/
|
|
198
|
+
retry(options = {}) {
|
|
199
|
+
return this.session(options.session).retry();
|
|
200
|
+
}
|
|
166
201
|
wait(operationId, options = {}) {
|
|
167
202
|
return this.session(options.session).wait(operationId, options.signal);
|
|
168
203
|
}
|
|
@@ -174,10 +209,12 @@ var PiHarness = class extends LifecycleCapability {
|
|
|
174
209
|
async pending(options = {}) {
|
|
175
210
|
const { pi } = await _assertClassBrand(_PiHarness_brand, this, _open).call(this);
|
|
176
211
|
const id = options.session === void 0 ? void 0 : conversationId(options.session);
|
|
177
|
-
|
|
212
|
+
const inspection = await pi.inspect(BG);
|
|
213
|
+
const parked = await _assertClassBrand(_PiHarness_brand, this, _parked).call(this, inspection);
|
|
214
|
+
return inspection.submissions.filter((record) => record.requestId !== void 0 && (id === void 0 || record.conversationId === id)).map((record) => ({
|
|
178
215
|
operationId: record.requestId,
|
|
179
216
|
session: String(record.conversationId),
|
|
180
|
-
status: record.status === "queued" ? "queued" : "running"
|
|
217
|
+
status: record.status === "queued" ? "queued" : parked.some((task) => task.sessions.has(record.conversationId)) ? "parked" : "running"
|
|
181
218
|
}));
|
|
182
219
|
}
|
|
183
220
|
/** The opened pi Harness, for anything the interface does not cover. */
|
|
@@ -200,30 +237,33 @@ var PiHarness = class extends LifecycleCapability {
|
|
|
200
237
|
const operationId = options.operationId ?? crypto.randomUUID();
|
|
201
238
|
const { storage } = await _assertClassBrand(_PiHarness_brand, this, _open).call(this);
|
|
202
239
|
const conversation = await this.conversation(session);
|
|
203
|
-
_classPrivateFieldGet2(_admitting, this).set(session, (_classPrivateFieldGet2(_admitting, this).get(session) ?? 0) + 1);
|
|
204
|
-
let accepted;
|
|
205
|
-
try {
|
|
206
|
-
await _assertClassBrand(_PiHarness_brand, this, _wake).call(this, session);
|
|
207
|
-
accepted = await storage.submissionByRequest(conversation.id, operationId, BG) === void 0;
|
|
208
|
-
await conversation.submit({
|
|
209
|
-
type: "input",
|
|
210
|
-
content: input,
|
|
211
|
-
whenBusy: options.whenBusy ?? "followUp",
|
|
212
|
-
requestId: operationId,
|
|
213
|
-
...options.entry ? { entry: options.entry } : {}
|
|
214
|
-
}, BG);
|
|
215
|
-
} finally {
|
|
216
|
-
const left = (_classPrivateFieldGet2(_admitting, this).get(session) ?? 1) - 1;
|
|
217
|
-
if (left === 0) _classPrivateFieldGet2(_admitting, this).delete(session);
|
|
218
|
-
else _classPrivateFieldGet2(_admitting, this).set(session, left);
|
|
219
|
-
}
|
|
220
|
-
await _assertClassBrand(_PiHarness_brand, this, _wake).call(this, session);
|
|
221
240
|
return {
|
|
222
241
|
operationId,
|
|
223
242
|
session,
|
|
224
|
-
accepted
|
|
243
|
+
accepted: await _assertClassBrand(_PiHarness_brand, this, _admit).call(this, async () => {
|
|
244
|
+
const accepted = await storage.submissionByRequest(conversation.id, operationId, BG) === void 0;
|
|
245
|
+
await conversation.submit({
|
|
246
|
+
type: "input",
|
|
247
|
+
content: input,
|
|
248
|
+
whenBusy: options.whenBusy ?? "followUp",
|
|
249
|
+
requestId: operationId,
|
|
250
|
+
...options.entry ? { entry: options.entry } : {}
|
|
251
|
+
}, BG);
|
|
252
|
+
return accepted;
|
|
253
|
+
})
|
|
225
254
|
};
|
|
226
255
|
}
|
|
256
|
+
/** @internal Run again the tasks pi's restart limit parked in a session. */
|
|
257
|
+
async retryParked(session) {
|
|
258
|
+
const { pi } = await _assertClassBrand(_PiHarness_brand, this, _open).call(this);
|
|
259
|
+
const id = conversationId(session);
|
|
260
|
+
const parked = (await _assertClassBrand(_PiHarness_brand, this, _parked).call(this, await pi.inspect(BG))).filter((task) => task.sessions.has(id));
|
|
261
|
+
if (parked.length === 0) return false;
|
|
262
|
+
await _assertClassBrand(_PiHarness_brand, this, _admit).call(this, async () => {
|
|
263
|
+
for (const task of parked) await pi.retryTask(task.id, BG);
|
|
264
|
+
});
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
227
267
|
/** @internal Withdraw a queued input, or abort the run it joined. */
|
|
228
268
|
async withdraw(session, operationId) {
|
|
229
269
|
const { pi, storage } = await _assertClassBrand(_PiHarness_brand, this, _open).call(this);
|
|
@@ -256,94 +296,188 @@ var PiHarness = class extends LifecycleCapability {
|
|
|
256
296
|
};
|
|
257
297
|
}
|
|
258
298
|
};
|
|
259
|
-
/**
|
|
260
|
-
|
|
299
|
+
/**
|
|
300
|
+
* Hand pi work inside the wake. The wake is scheduled first, so if the
|
|
301
|
+
* object dies after pi has the work, that job restarts it; while `hand`
|
|
302
|
+
* is in flight the step will not complete. The wake steps again after, so
|
|
303
|
+
* it sees the work even if it just completed.
|
|
304
|
+
*/
|
|
305
|
+
async function _admit(hand) {
|
|
306
|
+
var _this$admitting;
|
|
307
|
+
_classPrivateFieldSet2(_admitting, this, (_this$admitting = _classPrivateFieldGet2(_admitting, this), _this$admitting++, _this$admitting));
|
|
308
|
+
let result;
|
|
309
|
+
try {
|
|
310
|
+
await _assertClassBrand(_PiHarness_brand, this, _wake).call(this);
|
|
311
|
+
result = await hand();
|
|
312
|
+
} finally {
|
|
313
|
+
var _this$admitting3;
|
|
314
|
+
_classPrivateFieldSet2(_admitting, this, (_this$admitting3 = _classPrivateFieldGet2(_admitting, this), _this$admitting3--, _this$admitting3));
|
|
315
|
+
}
|
|
316
|
+
await _assertClassBrand(_PiHarness_brand, this, _wake).call(this);
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
/** Schedule the wake job now, or at `time`. */
|
|
320
|
+
function _wake(time = Date.now()) {
|
|
261
321
|
return this.lifecycle.jobs.push({
|
|
262
|
-
id:
|
|
322
|
+
id: WAKE_JOB_ID,
|
|
263
323
|
fn: WAKE_FN,
|
|
264
324
|
time,
|
|
265
|
-
payload: { session },
|
|
266
325
|
singleflight: true,
|
|
267
326
|
recoveryLoop: true
|
|
268
327
|
});
|
|
269
328
|
}
|
|
329
|
+
/** Schedule the wake job for what `pi.wake()` says, if pi needs one. */
|
|
330
|
+
async function _wakeFor(wake) {
|
|
331
|
+
if (wake.active) await _assertClassBrand(_PiHarness_brand, this, _wake).call(this);
|
|
332
|
+
else if (wake.at !== void 0) await _assertClassBrand(_PiHarness_brand, this, _wake).call(this, wake.at);
|
|
333
|
+
}
|
|
270
334
|
/**
|
|
271
|
-
* One run of
|
|
272
|
-
*
|
|
273
|
-
*
|
|
335
|
+
* One run of the wake job. It never admits or replays anything: it reads
|
|
336
|
+
* `pi.wake()`, which is all of pi's sessions at once. Running work is
|
|
337
|
+
* waited on; a sleep reschedules the job for when it ends; no work
|
|
338
|
+
* completes the job. pi does all the work in between.
|
|
274
339
|
*/
|
|
275
|
-
async function _wakeStep(
|
|
340
|
+
async function _wakeStep() {
|
|
276
341
|
const heartbeat = { rescheduleAt: Date.now() + _classPrivateFieldGet2(_heartbeatMs, this) };
|
|
277
|
-
if (_classPrivateFieldGet2(
|
|
342
|
+
if (_classPrivateFieldGet2(_waiting, this)) return heartbeat;
|
|
278
343
|
const { pi } = await _assertClassBrand(_PiHarness_brand, this, _open).call(this);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
344
|
+
pi.resume();
|
|
345
|
+
const wake = pi.wake();
|
|
346
|
+
if (wake.active) {
|
|
347
|
+
const wait = _assertClassBrand(_PiHarness_brand, this, _waitWhileActive).call(this, pi).then((ended) => {
|
|
348
|
+
_classPrivateFieldSet2(_waiting, this, void 0);
|
|
349
|
+
if (ended) _assertClassBrand(_PiHarness_brand, this, _wake).call(this);
|
|
350
|
+
}, (error) => {
|
|
351
|
+
_classPrivateFieldSet2(_waiting, this, void 0);
|
|
352
|
+
throw error;
|
|
353
|
+
});
|
|
354
|
+
_classPrivateFieldSet2(_waiting, this, wait);
|
|
355
|
+
if (!this.lifecycle.trackAlarmWork(wait)) wait.catch(() => {});
|
|
356
|
+
return heartbeat;
|
|
357
|
+
}
|
|
358
|
+
if (_classPrivateFieldGet2(_admitting, this) > 0) return heartbeat;
|
|
359
|
+
if (wake.at !== void 0) return { rescheduleAt: wake.at };
|
|
293
360
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
361
|
+
/**
|
|
362
|
+
* Wait while pi has work running, up to the wait budget. With `onWake`, a
|
|
363
|
+
* change ends each sleep early, and so does pi closing; without it, the
|
|
364
|
+
* sleep is `pollMs`. Whether the wait ended, with pi idle or at the
|
|
365
|
+
* budget, rather than failing. Rejects with a memory-limit reset.
|
|
366
|
+
*/
|
|
367
|
+
async function _waitWhileActive(pi) {
|
|
368
|
+
const deadline = Date.now() + _classPrivateFieldGet2(_waitBudgetMs, this);
|
|
297
369
|
try {
|
|
298
|
-
|
|
370
|
+
while (pi.wake().active) {
|
|
371
|
+
const left = deadline - Date.now();
|
|
372
|
+
if (left <= 0) return true;
|
|
373
|
+
await new Promise((resolve) => {
|
|
374
|
+
const timer = setTimeout(resolve, _classPrivateFieldGet2(_onWakeWired, this) ? left : Math.min(left, _classPrivateFieldGet2(_pollMs, this)));
|
|
375
|
+
_classPrivateFieldSet2(_changed, this, () => {
|
|
376
|
+
clearTimeout(timer);
|
|
377
|
+
resolve();
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
_classPrivateFieldSet2(_changed, this, void 0);
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
299
383
|
} catch (error) {
|
|
300
|
-
if (!
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
});
|
|
304
|
-
} finally {
|
|
305
|
-
clearTimeout(timer);
|
|
384
|
+
if (!_classPrivateFieldGet2(_closing, this).has(pi)) _assertClassBrand(_PiHarness_brand, this, _wakeError).call(this, error);
|
|
385
|
+
if (isDurableObjectMemoryLimitReset(error)) throw error;
|
|
386
|
+
return false;
|
|
306
387
|
}
|
|
307
388
|
}
|
|
389
|
+
function _wakeError(error) {
|
|
390
|
+
this.lifecycle.events.emit("pi:wake_error", { error: error instanceof Error ? error.message : String(error) });
|
|
391
|
+
}
|
|
308
392
|
/**
|
|
309
393
|
* pi, opened. Waits for Lifecycle startup first, so pi is only ever
|
|
310
394
|
* opened inside it: startup holds the input gate (`blockConcurrencyWhile`)
|
|
311
395
|
* and awaits the open in `onStart`, so an open begun before startup would
|
|
312
396
|
* be awaited behind a closed gate, with its timers and I/O held back, and
|
|
313
397
|
* never finish.
|
|
398
|
+
*
|
|
399
|
+
* pi stays open until `dispose()`, or until it closes on its own: any
|
|
400
|
+
* storage error fails pi's Session for good. Then the next call, or the
|
|
401
|
+
* wake job's next step, opens pi again once the failed pi has closed, and
|
|
402
|
+
* pi recovers from what it had committed. Closing waits for running task
|
|
403
|
+
* code to return, so code that ignores its abort signal delays the reopen.
|
|
404
|
+
* An open that fails is not kept either, so the next call tries again.
|
|
314
405
|
*/
|
|
315
406
|
async function _open() {
|
|
316
407
|
await this.lifecycle.ready();
|
|
317
|
-
_classPrivateFieldGet2(_opening, this)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
408
|
+
if (_classPrivateFieldGet2(_opening, this) === void 0) {
|
|
409
|
+
const opening = _assertClassBrand(_PiHarness_brand, this, _doOpen).call(this, (pi, closed) => _assertClassBrand(_PiHarness_brand, this, _piClosing).call(this, opening, pi, closed)).catch((error) => {
|
|
410
|
+
if (_classPrivateFieldGet2(_opening, this) === opening) _classPrivateFieldSet2(_opening, this, void 0);
|
|
411
|
+
throw error;
|
|
412
|
+
});
|
|
413
|
+
_classPrivateFieldSet2(_opening, this, opening);
|
|
414
|
+
}
|
|
321
415
|
return _classPrivateFieldGet2(_opening, this);
|
|
322
416
|
}
|
|
323
|
-
async function _doOpen() {
|
|
417
|
+
async function _doOpen(onClose) {
|
|
418
|
+
await _classPrivateFieldGet2(_lastClose, this);
|
|
324
419
|
const storage = await openPiSessionStore(this.lifecycle.storage);
|
|
325
420
|
const pi = await _classPrivateFieldGet2(_options, this).harness({
|
|
326
421
|
storage,
|
|
327
|
-
context: BG
|
|
422
|
+
context: BG,
|
|
423
|
+
onWake: _classPrivateFieldGet2(_onWake, this)
|
|
328
424
|
});
|
|
329
|
-
|
|
330
|
-
|
|
425
|
+
try {
|
|
426
|
+
const closed = pi.closed;
|
|
427
|
+
pi.subscribeClose(() => onClose(pi, closed));
|
|
428
|
+
await pi.root(BG, { agent: this.agentDefaults() });
|
|
429
|
+
pi.resume();
|
|
430
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
431
|
+
if (!_classPrivateFieldGet2(_onWakeWired, this)) console.warn("PiHarness: the harness factory did not pass onWake to Harness.open, so pi's timers keep this Durable Object in memory while a task sleeps. Pass it: harness: ({ storage, context, onWake }) => Harness.open(storage, { ...options, onWake }, context)");
|
|
432
|
+
await _assertClassBrand(_PiHarness_brand, this, _wakeFor).call(this, pi.wake());
|
|
433
|
+
} catch (error) {
|
|
434
|
+
pi.close(BG).catch(() => {});
|
|
435
|
+
throw error;
|
|
436
|
+
}
|
|
331
437
|
return {
|
|
332
438
|
pi,
|
|
333
439
|
storage
|
|
334
440
|
};
|
|
335
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* pi began closing: on its own, as a storage error makes it, or by
|
|
444
|
+
* `dispose()`.
|
|
445
|
+
*/
|
|
446
|
+
function _piClosing(opening, pi, closed) {
|
|
447
|
+
_classPrivateFieldGet2(_closing, this).add(pi);
|
|
448
|
+
_classPrivateFieldGet2(_changed, this)?.call(this);
|
|
449
|
+
if (_classPrivateFieldGet2(_opening, this) !== opening) return;
|
|
450
|
+
_classPrivateFieldSet2(_opening, this, void 0);
|
|
451
|
+
_classPrivateFieldSet2(_lastClose, this, closed);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Tasks pi's restart limit parked, each with the sessions whose work it
|
|
455
|
+
* is: its own, and every session that owns that one, such as the session
|
|
456
|
+
* of a tool that started a subagent.
|
|
457
|
+
*/
|
|
458
|
+
async function _parked(inspection) {
|
|
459
|
+
const { storage } = await _assertClassBrand(_PiHarness_brand, this, _open).call(this);
|
|
460
|
+
const parked = [];
|
|
461
|
+
for (const { record, state } of inspection.tasks) {
|
|
462
|
+
if (state.kind !== "blocked" || state.reason !== "restart_limit") continue;
|
|
463
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
464
|
+
let id = record.conversationId;
|
|
465
|
+
while (id !== void 0 && !sessions.has(id)) {
|
|
466
|
+
sessions.add(id);
|
|
467
|
+
id = (await storage.conversation(id, BG))?.owner?.conversationId;
|
|
468
|
+
}
|
|
469
|
+
parked.push({
|
|
470
|
+
id: record.id,
|
|
471
|
+
sessions
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
return parked;
|
|
475
|
+
}
|
|
336
476
|
async function _findSubmission(id, operationId, context) {
|
|
337
477
|
const { pi, storage } = await _assertClassBrand(_PiHarness_brand, this, _open).call(this);
|
|
338
478
|
const record = await storage.submissionByRequest(id, operationId, context);
|
|
339
479
|
return record ? pi.submission(record.id, context) : void 0;
|
|
340
480
|
}
|
|
341
|
-
/** When pi's current wait is far enough away to hand to the alarm. */
|
|
342
|
-
async function _longWait(pi, id, context) {
|
|
343
|
-
const live = await pi.snapshot(LiveDoc, id, context);
|
|
344
|
-
const at = live?.generation?.deferred?.pollAt ?? live?.generation?.retry?.at;
|
|
345
|
-
return at !== void 0 && at - Date.now() > _classPrivateFieldGet2(_sleepThresholdMs, this) ? at : void 0;
|
|
346
|
-
}
|
|
347
481
|
async function _result(session, operationId, settled, context) {
|
|
348
482
|
if (settled.status === "unanswered") return {
|
|
349
483
|
operationId,
|
|
@@ -407,6 +541,14 @@ var PiSession = class {
|
|
|
407
541
|
await (await _classPrivateFieldGet2(_harness, this).conversation(this.id)).abort(BG);
|
|
408
542
|
return true;
|
|
409
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* Run again the tasks pi's crash-loop guard (`settings.restartLimit`)
|
|
546
|
+
* parked in this session's work, including subagent sessions it owns.
|
|
547
|
+
* False when nothing was parked.
|
|
548
|
+
*/
|
|
549
|
+
retry() {
|
|
550
|
+
return _classPrivateFieldGet2(_harness, this).retryParked(this.id);
|
|
551
|
+
}
|
|
410
552
|
/** Start a new context, optionally from a handoff note. */
|
|
411
553
|
async reset(handoff) {
|
|
412
554
|
await (await _classPrivateFieldGet2(_harness, this).conversation(this.id)).reset(handoff, BG);
|