@astrosheep/keiyaku 4.1.1 → 4.1.3
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/build/src/akuma/akuma-errors.d.ts +6 -0
- package/build/src/akuma/akuma-errors.js +9 -0
- package/build/src/akuma/akuma-handle.d.ts +39 -0
- package/build/src/akuma/akuma-handle.js +263 -0
- package/build/src/akuma/akuma-observe.d.ts +19 -0
- package/build/src/akuma/akuma-observe.js +74 -0
- package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
- package/build/src/akuma/akuma-product-symbols.js +1 -0
- package/build/src/akuma/akuma-product.d.ts +18 -0
- package/build/src/akuma/akuma-product.js +154 -0
- package/build/src/akuma/akuma.d.ts +23 -70
- package/build/src/akuma/akuma.js +17 -433
- package/build/src/akuma/body-supervisor.d.ts +26 -0
- package/build/src/akuma/body-supervisor.js +89 -0
- package/build/src/akuma/body.d.ts +1 -1
- package/build/src/akuma/body.js +7 -3
- package/build/src/akuma/heart/index.d.ts +1 -1
- package/build/src/akuma/heart/index.js +1 -1
- package/build/src/akuma/heart/schema.d.ts +1 -0
- package/build/src/akuma/heart/schema.js +9 -0
- package/build/src/akuma/heart/storage.d.ts +1 -0
- package/build/src/akuma/heart/storage.js +18 -1
- package/build/src/akuma/nuke.js +6 -2
- package/build/src/akuma/projection-read.d.ts +17 -0
- package/build/src/akuma/projection-read.js +135 -0
- package/build/src/akuma/projection.d.ts +1 -18
- package/build/src/akuma/projection.js +21 -155
- package/build/src/akuma/providers/claude/index.js +101 -89
- package/build/src/akuma/providers/codex-app-server/index.js +9 -6
- package/build/src/akuma/providers/opencode-sdk/index.js +22 -15
- package/build/src/akuma/providers/pi/index.js +45 -43
- package/build/src/akuma/publication.d.ts +2 -1
- package/build/src/akuma/publication.js +72 -11
- package/build/src/akuma/request-execution.d.ts +8 -0
- package/build/src/akuma/request-execution.js +98 -0
- package/build/src/akuma/request-serve.d.ts +14 -2
- package/build/src/akuma/request-serve.js +3 -103
- package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +4 -26
- package/build/src/akuma/{body-turn.js → turn-drive.js} +14 -100
- package/build/src/cli/commands/akuma.d.ts +0 -6
- package/build/src/cli/commands/akuma.js +1 -11
- package/build/src/cli/commands/contract-invoke.d.ts +25 -0
- package/build/src/cli/commands/contract-invoke.js +193 -0
- package/build/src/cli/commands/contract.d.ts +12 -0
- package/build/src/cli/commands/contract.js +229 -0
- package/build/src/cli/invoke.js +24 -184
- package/build/src/cli/parse.d.ts +1 -1
- package/build/src/cli/parse.js +8 -222
- package/build/src/cli/render/akuma-activity.d.ts +38 -0
- package/build/src/cli/render/akuma-activity.js +362 -0
- package/build/src/cli/render/akuma.d.ts +1 -1
- package/build/src/cli/render/akuma.js +14 -371
- package/build/src/cli/render/contract-history.d.ts +2 -0
- package/build/src/cli/render/contract-history.js +83 -0
- package/build/src/cli/render/contract.d.ts +1 -2
- package/build/src/cli/render/contract.js +1 -83
- package/build/src/git/read-observation.js +15 -10
- package/build/src/git/reconcile.d.ts +8 -1
- package/build/src/git/reconcile.js +13 -301
- package/build/src/git/scratch.d.ts +8 -5
- package/build/src/git/scratch.js +29 -2
- package/build/src/git/terminal-reconcile.d.ts +21 -0
- package/build/src/git/terminal-reconcile.js +278 -0
- package/build/src/kanshi/read.js +32 -27
- package/build/src/library/contract-bind.d.ts +26 -0
- package/build/src/library/contract-bind.js +106 -0
- package/build/src/library/contract-operations.d.ts +77 -0
- package/build/src/library/contract-operations.js +130 -0
- package/build/src/library/contract-types.d.ts +95 -0
- package/build/src/library/contract-types.js +1 -0
- package/build/src/library/contract.d.ts +11 -136
- package/build/src/library/contract.js +9 -222
- package/build/src/library/fleet.js +2 -1
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/package.json +1 -1
|
@@ -118,8 +118,91 @@ function finishClaudeInput(input, terminal) {
|
|
|
118
118
|
else
|
|
119
119
|
input.close();
|
|
120
120
|
}
|
|
121
|
+
async function consumeClaudeQuery(context) {
|
|
122
|
+
const { query, input, events, observation, progress, admit, rejectAdmission, settle, controls } = context;
|
|
123
|
+
let admitted = false;
|
|
124
|
+
let terminal = null;
|
|
125
|
+
let historyId;
|
|
126
|
+
try {
|
|
127
|
+
for await (const message of query) {
|
|
128
|
+
if (!admitted && "session_id" in message && typeof message.session_id === "string") {
|
|
129
|
+
admitted = true;
|
|
130
|
+
events.emit({ type: "session", coordinate: { sessionId: message.session_id } });
|
|
131
|
+
admit();
|
|
132
|
+
}
|
|
133
|
+
emitClaudeMessage(message, events, observation);
|
|
134
|
+
if (message.type === "assistant" &&
|
|
135
|
+
message.parent_tool_use_id === null &&
|
|
136
|
+
typeof message.uuid === "string" &&
|
|
137
|
+
message.uuid.length > 0)
|
|
138
|
+
historyId = message.uuid;
|
|
139
|
+
if (message.type !== "result")
|
|
140
|
+
continue;
|
|
141
|
+
if (message.subtype === "success") {
|
|
142
|
+
terminal = successfulResult(message, historyId);
|
|
143
|
+
progress.checkpoint += 1;
|
|
144
|
+
controls.flushReceipts();
|
|
145
|
+
queueMicrotask(controls.closeWhenIdle);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
terminal = { kind: "failed", diagnostic: message.errors.join("; ") || message.subtype };
|
|
149
|
+
input.close();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
progress.ended = true;
|
|
153
|
+
const result = terminal ?? { kind: "failed", diagnostic: "Claude query ended without a result" };
|
|
154
|
+
if (!admitted)
|
|
155
|
+
rejectAdmission(new Error(result.kind === "failed" ? result.diagnostic : "Claude query ended before session admission"));
|
|
156
|
+
settle(result);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
progress.ended = true;
|
|
160
|
+
input.fail(error);
|
|
161
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
162
|
+
if (!admitted)
|
|
163
|
+
rejectAdmission(failure);
|
|
164
|
+
settle({ kind: "failed", diagnostic: failure.message });
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
finishClaudeInput(input, terminal);
|
|
168
|
+
events.end();
|
|
169
|
+
controls.settleReceipts();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function claudeTell(input, observed, accepted, state, run) {
|
|
173
|
+
return (tell) => {
|
|
174
|
+
if (observed.ended || input.closed)
|
|
175
|
+
return Promise.resolve({ kind: "turn-ended" });
|
|
176
|
+
state.openSubmissions += 1;
|
|
177
|
+
const ordinal = ++state.submission;
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
const pending = { id: tell.id, afterCheckpoint: 0, visible: false };
|
|
180
|
+
void input
|
|
181
|
+
.push(claudeUserMessage(tell.text), () => {
|
|
182
|
+
pending.afterCheckpoint = observed.checkpoint;
|
|
183
|
+
accepted.push(pending);
|
|
184
|
+
})
|
|
185
|
+
.then(() => {
|
|
186
|
+
state.openSubmissions -= 1;
|
|
187
|
+
resolve({ kind: "accepted", fence: `claude:${run}:${ordinal}` });
|
|
188
|
+
queueMicrotask(() => {
|
|
189
|
+
pending.visible = true;
|
|
190
|
+
observed.flushReceipts();
|
|
191
|
+
observed.settleReceipts();
|
|
192
|
+
});
|
|
193
|
+
}, (error) => {
|
|
194
|
+
state.openSubmissions -= 1;
|
|
195
|
+
if (isClaudeTurnEnded(error))
|
|
196
|
+
resolve({ kind: "turn-ended" });
|
|
197
|
+
else
|
|
198
|
+
reject(error);
|
|
199
|
+
observed.settleReceipts();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
}
|
|
121
204
|
function observeClaudeQuery(context) {
|
|
122
|
-
const {
|
|
205
|
+
const { input, receipts, accepted, state } = context;
|
|
123
206
|
const observation = { tools: new Map() };
|
|
124
207
|
let admit;
|
|
125
208
|
let rejectAdmission;
|
|
@@ -131,15 +214,15 @@ function observeClaudeQuery(context) {
|
|
|
131
214
|
const completion = new Promise((resolve) => {
|
|
132
215
|
settle = resolve;
|
|
133
216
|
});
|
|
134
|
-
|
|
217
|
+
const progress = { checkpoint: 0, ended: false };
|
|
135
218
|
const settleReceipts = () => {
|
|
136
|
-
if (ended && state.openSubmissions === 0 && accepted.every((tell) => tell.visible))
|
|
219
|
+
if (progress.ended && state.openSubmissions === 0 && accepted.every((tell) => tell.visible))
|
|
137
220
|
receipts.end();
|
|
138
221
|
};
|
|
139
222
|
const flushReceipts = () => {
|
|
140
223
|
for (let index = 0; index < accepted.length;) {
|
|
141
224
|
const tell = accepted[index];
|
|
142
|
-
if (!tell.visible || tell.afterCheckpoint >= checkpoint) {
|
|
225
|
+
if (!tell.visible || tell.afterCheckpoint >= progress.checkpoint) {
|
|
143
226
|
index += 1;
|
|
144
227
|
continue;
|
|
145
228
|
}
|
|
@@ -153,64 +236,23 @@ function observeClaudeQuery(context) {
|
|
|
153
236
|
if (state.openSubmissions === 0 && accepted.length === 0 && input.pending === 0)
|
|
154
237
|
input.close();
|
|
155
238
|
};
|
|
156
|
-
void (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
admit();
|
|
166
|
-
}
|
|
167
|
-
emitClaudeMessage(message, events, observation);
|
|
168
|
-
if (message.type === "assistant" &&
|
|
169
|
-
message.parent_tool_use_id === null &&
|
|
170
|
-
typeof message.uuid === "string" &&
|
|
171
|
-
message.uuid.length > 0)
|
|
172
|
-
historyId = message.uuid;
|
|
173
|
-
if (message.type !== "result")
|
|
174
|
-
continue;
|
|
175
|
-
if (message.subtype === "success") {
|
|
176
|
-
terminal = successfulResult(message, historyId);
|
|
177
|
-
checkpoint += 1;
|
|
178
|
-
flushReceipts();
|
|
179
|
-
queueMicrotask(closeWhenIdle);
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
terminal = { kind: "failed", diagnostic: message.errors.join("; ") || message.subtype };
|
|
183
|
-
input.close();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
ended = true;
|
|
187
|
-
const result = terminal ?? { kind: "failed", diagnostic: "Claude query ended without a result" };
|
|
188
|
-
if (!admitted)
|
|
189
|
-
rejectAdmission(new Error(result.kind === "failed" ? result.diagnostic : "Claude query ended before session admission"));
|
|
190
|
-
settle(result);
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
ended = true;
|
|
194
|
-
input.fail(error);
|
|
195
|
-
const failure = error instanceof Error ? error : new Error(String(error));
|
|
196
|
-
if (!admitted)
|
|
197
|
-
rejectAdmission(failure);
|
|
198
|
-
settle({ kind: "failed", diagnostic: failure.message });
|
|
199
|
-
}
|
|
200
|
-
finally {
|
|
201
|
-
finishClaudeInput(input, terminal);
|
|
202
|
-
events.end();
|
|
203
|
-
settleReceipts();
|
|
204
|
-
}
|
|
205
|
-
})();
|
|
239
|
+
void consumeClaudeQuery({
|
|
240
|
+
...context,
|
|
241
|
+
observation,
|
|
242
|
+
progress,
|
|
243
|
+
admit,
|
|
244
|
+
rejectAdmission,
|
|
245
|
+
settle,
|
|
246
|
+
controls: { flushReceipts, settleReceipts, closeWhenIdle },
|
|
247
|
+
});
|
|
206
248
|
return {
|
|
207
249
|
admission,
|
|
208
250
|
completion,
|
|
209
251
|
get ended() {
|
|
210
|
-
return ended;
|
|
252
|
+
return progress.ended;
|
|
211
253
|
},
|
|
212
254
|
get checkpoint() {
|
|
213
|
-
return checkpoint;
|
|
255
|
+
return progress.checkpoint;
|
|
214
256
|
},
|
|
215
257
|
flushReceipts,
|
|
216
258
|
settleReceipts,
|
|
@@ -229,8 +271,7 @@ async function driveClaude(load, execution, drive) {
|
|
|
229
271
|
const abortController = new AbortController();
|
|
230
272
|
const run = randomUUID();
|
|
231
273
|
const accepted = [];
|
|
232
|
-
|
|
233
|
-
let openSubmissions = 0;
|
|
274
|
+
const driveState = { submission: 0, openSubmissions: 0 };
|
|
234
275
|
const launchAcknowledged = input.push(claudeUserMessage(launchText(drive)));
|
|
235
276
|
const query = sdk.query({
|
|
236
277
|
prompt: input.iterable,
|
|
@@ -251,7 +292,7 @@ async function driveClaude(load, execution, drive) {
|
|
|
251
292
|
accepted,
|
|
252
293
|
state: {
|
|
253
294
|
get openSubmissions() {
|
|
254
|
-
return openSubmissions;
|
|
295
|
+
return driveState.openSubmissions;
|
|
255
296
|
},
|
|
256
297
|
},
|
|
257
298
|
});
|
|
@@ -266,36 +307,7 @@ async function driveClaude(load, execution, drive) {
|
|
|
266
307
|
events,
|
|
267
308
|
receipts,
|
|
268
309
|
completion: observed.completion,
|
|
269
|
-
tell(
|
|
270
|
-
if (observed.ended || input.closed)
|
|
271
|
-
return Promise.resolve({ kind: "turn-ended" });
|
|
272
|
-
openSubmissions += 1;
|
|
273
|
-
const ordinal = ++submission;
|
|
274
|
-
return new Promise((resolve, reject) => {
|
|
275
|
-
const pending = { id: tell.id, afterCheckpoint: 0, visible: false };
|
|
276
|
-
void input
|
|
277
|
-
.push(claudeUserMessage(tell.text), () => {
|
|
278
|
-
pending.afterCheckpoint = observed.checkpoint;
|
|
279
|
-
accepted.push(pending);
|
|
280
|
-
})
|
|
281
|
-
.then(() => {
|
|
282
|
-
openSubmissions -= 1;
|
|
283
|
-
resolve({ kind: "accepted", fence: `claude:${run}:${ordinal}` });
|
|
284
|
-
queueMicrotask(() => {
|
|
285
|
-
pending.visible = true;
|
|
286
|
-
observed.flushReceipts();
|
|
287
|
-
observed.settleReceipts();
|
|
288
|
-
});
|
|
289
|
-
}, (error) => {
|
|
290
|
-
openSubmissions -= 1;
|
|
291
|
-
if (isClaudeTurnEnded(error))
|
|
292
|
-
resolve({ kind: "turn-ended" });
|
|
293
|
-
else
|
|
294
|
-
reject(error);
|
|
295
|
-
observed.settleReceipts();
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
},
|
|
310
|
+
tell: claudeTell(input, observed, accepted, driveState, run),
|
|
299
311
|
async abort() {
|
|
300
312
|
shutDown(new Error("Claude query aborted"));
|
|
301
313
|
await observed.completion;
|
|
@@ -124,12 +124,8 @@ async function abortTurn(server, state, settle) {
|
|
|
124
124
|
settle({ kind: "failed", diagnostic: "codex app-server interrupted" });
|
|
125
125
|
await server.close(true);
|
|
126
126
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (input.session.kind === "resume" && !("sessionId" in input.session.coordinate))
|
|
130
|
-
throw new Error("Codex app-server resume requires sessionId");
|
|
131
|
-
const events = new AgentEventChannel();
|
|
132
|
-
const server = new LineRpcProcess({
|
|
127
|
+
function codexServer(execution, input) {
|
|
128
|
+
return new LineRpcProcess({
|
|
133
129
|
argv: [execution.executable ?? "codex", "app-server", "--listen", "stdio://"],
|
|
134
130
|
cwd: input.cwd,
|
|
135
131
|
...(execution.env === undefined && input.requests === undefined
|
|
@@ -142,6 +138,13 @@ async function startCodex(execution, input) {
|
|
|
142
138
|
},
|
|
143
139
|
}),
|
|
144
140
|
});
|
|
141
|
+
}
|
|
142
|
+
async function startCodex(execution, input) {
|
|
143
|
+
const signal = input.signal ?? new AbortController().signal;
|
|
144
|
+
if (input.session.kind === "resume" && !("sessionId" in input.session.coordinate))
|
|
145
|
+
throw new Error("Codex app-server resume requires sessionId");
|
|
146
|
+
const events = new AgentEventChannel();
|
|
147
|
+
const server = codexServer(execution, input);
|
|
145
148
|
const state = { settled: false, tools: new Map() };
|
|
146
149
|
let settle;
|
|
147
150
|
const completion = new Promise((resolve) => {
|
|
@@ -201,6 +201,26 @@ async function forceDisposeOpencode(abortController, close, finish) {
|
|
|
201
201
|
await close();
|
|
202
202
|
await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
|
|
203
203
|
}
|
|
204
|
+
async function loadDriveRuntime(execution, input, signal, loader) {
|
|
205
|
+
return await abortable(loadOpencode({
|
|
206
|
+
...execution,
|
|
207
|
+
env: {
|
|
208
|
+
...execution.env,
|
|
209
|
+
...(input.requests === undefined ? {} : { [AKUMA_REQUESTS_ENV]: input.requests.dir }),
|
|
210
|
+
},
|
|
211
|
+
}, input.cwd, signal, loader), signal, async (late) => await late.close());
|
|
212
|
+
}
|
|
213
|
+
async function openDriveSession(runtime, input, resumeSessionId, signal) {
|
|
214
|
+
const session = runtime.client.session;
|
|
215
|
+
const response = await abortable(input.session.kind === "fresh"
|
|
216
|
+
? session.create({ query: { directory: input.cwd }, throwOnError: true })
|
|
217
|
+
: session.get({ path: { id: resumeSessionId }, query: { directory: input.cwd }, throwOnError: true }), signal);
|
|
218
|
+
const info = object(object(response)?.data) ?? object(response);
|
|
219
|
+
const sessionId = text(info?.id) ?? resumeSessionId;
|
|
220
|
+
if (sessionId === undefined)
|
|
221
|
+
throw new Error("OpenCode did not return a session id");
|
|
222
|
+
return { session, sessionId };
|
|
223
|
+
}
|
|
204
224
|
async function drive(execution, input, loader) {
|
|
205
225
|
admit(input.options);
|
|
206
226
|
const signal = input.signal ?? new AbortController().signal;
|
|
@@ -209,13 +229,7 @@ async function drive(execution, input, loader) {
|
|
|
209
229
|
const abortSetup = () => abortController.abort(signal.reason);
|
|
210
230
|
signal.addEventListener("abort", abortSetup, { once: true });
|
|
211
231
|
signal.throwIfAborted();
|
|
212
|
-
const runtime = await
|
|
213
|
-
...execution,
|
|
214
|
-
env: {
|
|
215
|
-
...execution.env,
|
|
216
|
-
...(input.requests === undefined ? {} : { [AKUMA_REQUESTS_ENV]: input.requests.dir }),
|
|
217
|
-
},
|
|
218
|
-
}, input.cwd, abortController.signal, loader), abortController.signal, async (late) => await late.close());
|
|
232
|
+
const runtime = await loadDriveRuntime(execution, input, abortController.signal, loader);
|
|
219
233
|
let closing;
|
|
220
234
|
let iterator;
|
|
221
235
|
const closeOnce = () => {
|
|
@@ -223,14 +237,7 @@ async function drive(execution, input, loader) {
|
|
|
223
237
|
return closing;
|
|
224
238
|
};
|
|
225
239
|
try {
|
|
226
|
-
const session = runtime.
|
|
227
|
-
const sessionResponse = await abortable(input.session.kind === "fresh"
|
|
228
|
-
? session.create({ query: { directory: input.cwd }, throwOnError: true })
|
|
229
|
-
: session.get({ path: { id: resumeSessionId }, query: { directory: input.cwd }, throwOnError: true }), abortController.signal);
|
|
230
|
-
const info = object(object(sessionResponse)?.data) ?? object(sessionResponse);
|
|
231
|
-
const sessionId = text(info?.id) ?? resumeSessionId;
|
|
232
|
-
if (sessionId === undefined)
|
|
233
|
-
throw new Error("OpenCode did not return a session id");
|
|
240
|
+
const { session, sessionId } = await openDriveSession(runtime, input, resumeSessionId, abortController.signal);
|
|
234
241
|
const events = new AgentEventChannel();
|
|
235
242
|
const state = createEventState(sessionId);
|
|
236
243
|
const messageID = `msg_${randomUUID().replaceAll("-", "")}`;
|
|
@@ -83,6 +83,32 @@ function forceDisposePi(dispose, settle, setAborting) {
|
|
|
83
83
|
settle({ kind: "failed", diagnostic: "Pi session force-disposed" });
|
|
84
84
|
return Promise.resolve();
|
|
85
85
|
}
|
|
86
|
+
async function runPiPrompt(native, input, events, state, settle) {
|
|
87
|
+
try {
|
|
88
|
+
await native.prompt([input.body, ...input.launchTells.map((tell) => tell.text)].join("\n\n"));
|
|
89
|
+
let result;
|
|
90
|
+
if (state.aborting)
|
|
91
|
+
result = { kind: "failed", diagnostic: "Pi session aborted" };
|
|
92
|
+
else if (state.terminalFailure !== null)
|
|
93
|
+
result = { kind: "failed", diagnostic: state.terminalFailure };
|
|
94
|
+
else if (!events.assistantSeen)
|
|
95
|
+
result = { kind: "failed", diagnostic: "Pi completed without a native assistant answer" };
|
|
96
|
+
else {
|
|
97
|
+
const historyId = native.sessionManager.getLeafId();
|
|
98
|
+
result = {
|
|
99
|
+
kind: "answered",
|
|
100
|
+
answer: events.answer,
|
|
101
|
+
...(historyId === null ? {} : { historyId }),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
settle(result);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
settle(state.aborting
|
|
108
|
+
? { kind: "failed", diagnostic: "Pi session aborted" }
|
|
109
|
+
: { kind: "failed", diagnostic: diagnostic(error) });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
86
112
|
async function drivePi(sdk, execution, input, signal) {
|
|
87
113
|
const created = await createPiSession(sdk, execution, input, signal);
|
|
88
114
|
const native = created.session;
|
|
@@ -95,16 +121,17 @@ async function drivePi(sdk, execution, input, signal) {
|
|
|
95
121
|
throw new Error("Pi session admitted without sessionFile");
|
|
96
122
|
}
|
|
97
123
|
const events = new AgentEventChannel();
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
124
|
+
const eventState = { answer: "", assistantSeen: false, tools: new Map() };
|
|
125
|
+
const state = {
|
|
126
|
+
terminalFailure: null,
|
|
127
|
+
disposed: false,
|
|
128
|
+
aborting: false,
|
|
129
|
+
settled: false,
|
|
130
|
+
};
|
|
104
131
|
const dispose = () => {
|
|
105
|
-
if (disposed)
|
|
132
|
+
if (state.disposed)
|
|
106
133
|
return;
|
|
107
|
-
disposed = true;
|
|
134
|
+
state.disposed = true;
|
|
108
135
|
try {
|
|
109
136
|
unsubscribe();
|
|
110
137
|
}
|
|
@@ -119,8 +146,8 @@ async function drivePi(sdk, execution, input, signal) {
|
|
|
119
146
|
};
|
|
120
147
|
const unsubscribe = native.subscribe((event) => {
|
|
121
148
|
if (event.type === "agent_end" && !event.willRetry)
|
|
122
|
-
terminalFailure = piTerminalFailure(event.messages);
|
|
123
|
-
for (const translated of translatePiEvent(event,
|
|
149
|
+
state.terminalFailure = piTerminalFailure(event.messages);
|
|
150
|
+
for (const translated of translatePiEvent(event, eventState))
|
|
124
151
|
events.emit(translated);
|
|
125
152
|
});
|
|
126
153
|
events.emit({ type: "session", coordinate: { sessionFile: native.sessionFile, sessionId: native.sessionId } });
|
|
@@ -129,47 +156,22 @@ async function drivePi(sdk, execution, input, signal) {
|
|
|
129
156
|
settleCompletion = resolve;
|
|
130
157
|
});
|
|
131
158
|
const settle = (result) => {
|
|
132
|
-
if (settled)
|
|
159
|
+
if (state.settled)
|
|
133
160
|
return;
|
|
134
|
-
settled = true;
|
|
161
|
+
state.settled = true;
|
|
135
162
|
dispose();
|
|
136
163
|
settleCompletion(result);
|
|
137
164
|
};
|
|
138
|
-
void (
|
|
139
|
-
try {
|
|
140
|
-
await native.prompt([input.body, ...input.launchTells.map((tell) => tell.text)].join("\n\n"));
|
|
141
|
-
let result;
|
|
142
|
-
if (aborting)
|
|
143
|
-
result = { kind: "failed", diagnostic: "Pi session aborted" };
|
|
144
|
-
else if (terminalFailure !== null)
|
|
145
|
-
result = { kind: "failed", diagnostic: terminalFailure };
|
|
146
|
-
else if (!state.assistantSeen)
|
|
147
|
-
result = { kind: "failed", diagnostic: "Pi completed without a native assistant answer" };
|
|
148
|
-
else {
|
|
149
|
-
const historyId = native.sessionManager.getLeafId();
|
|
150
|
-
result = {
|
|
151
|
-
kind: "answered",
|
|
152
|
-
answer: state.answer,
|
|
153
|
-
...(historyId === null ? {} : { historyId }),
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
settle(result);
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
settle(aborting
|
|
160
|
-
? { kind: "failed", diagnostic: "Pi session aborted" }
|
|
161
|
-
: { kind: "failed", diagnostic: diagnostic(error) });
|
|
162
|
-
}
|
|
163
|
-
})();
|
|
165
|
+
void runPiPrompt(native, input, eventState, state, settle);
|
|
164
166
|
return {
|
|
165
167
|
admission: { fence: native.sessionId },
|
|
166
168
|
events,
|
|
167
169
|
completion,
|
|
168
170
|
abort: () => {
|
|
169
|
-
abortRequest ??= (async () => {
|
|
170
|
-
if (settled)
|
|
171
|
+
state.abortRequest ??= (async () => {
|
|
172
|
+
if (state.settled)
|
|
171
173
|
return;
|
|
172
|
-
aborting = true;
|
|
174
|
+
state.aborting = true;
|
|
173
175
|
try {
|
|
174
176
|
await native.abort();
|
|
175
177
|
}
|
|
@@ -178,10 +180,10 @@ async function drivePi(sdk, execution, input, signal) {
|
|
|
178
180
|
}
|
|
179
181
|
settle({ kind: "failed", diagnostic: "Pi session aborted" });
|
|
180
182
|
})();
|
|
181
|
-
return abortRequest;
|
|
183
|
+
return state.abortRequest;
|
|
182
184
|
},
|
|
183
185
|
forceDispose: () => forceDisposePi(dispose, settle, () => {
|
|
184
|
-
aborting = true;
|
|
186
|
+
state.aborting = true;
|
|
185
187
|
}),
|
|
186
188
|
};
|
|
187
189
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type AllocatedAkuma } from "./identity.js";
|
|
2
|
+
import type { OwnedProcess } from "../runtime/proc/run.js";
|
|
2
3
|
export declare const BIRTH_TIMEOUT_MS = 30000;
|
|
3
4
|
export declare function publishAkuma(input: Readonly<{
|
|
4
5
|
worldPath: string;
|
|
5
6
|
archetype: string;
|
|
6
7
|
awaitAsleep?: boolean;
|
|
7
8
|
reserve?(allocated: AllocatedAkuma): Promise<void>;
|
|
8
|
-
launch(allocated: AllocatedAkuma): Promise<void>;
|
|
9
|
+
launch(allocated: AllocatedAkuma): Promise<OwnedProcess | void>;
|
|
9
10
|
signal?: AbortSignal;
|
|
10
11
|
}>): Promise<AllocatedAkuma>;
|
|
@@ -40,7 +40,40 @@ async function observedBirthFailure(paths) {
|
|
|
40
40
|
leash.release();
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
function preAdmissionDiagnostic(exit) {
|
|
44
|
+
return exit.code === null ? `pre-admission signal ${exit.signal ?? "unknown"}` : `pre-admission exit ${exit.code}`;
|
|
45
|
+
}
|
|
46
|
+
async function sealObservedExit(paths, exit) {
|
|
47
|
+
const evidence = preAdmissionDiagnostic(exit);
|
|
48
|
+
try {
|
|
49
|
+
const leash = await HeldAkumaLeash.try(paths);
|
|
50
|
+
if (leash !== null) {
|
|
51
|
+
try {
|
|
52
|
+
await leash.sealIfUnborn(paths, { evidence, at: new Date().toISOString() });
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
leash.release();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* Parent evidence remains authoritative when best-effort sealing fails. */
|
|
61
|
+
}
|
|
62
|
+
throw new Error(evidence);
|
|
63
|
+
}
|
|
64
|
+
async function observeSettledExit(owned) {
|
|
65
|
+
if (owned === undefined)
|
|
66
|
+
return { kind: "pending" };
|
|
67
|
+
const pending = Symbol("pending");
|
|
68
|
+
try {
|
|
69
|
+
const exit = await Promise.race([owned.exited, Promise.resolve(pending)]);
|
|
70
|
+
return exit === pending ? { kind: "pending" } : { kind: "exited", exit };
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return { kind: "exit-error", error };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function awaitBirth(paths, owned, signal) {
|
|
44
77
|
const deadline = performance.now() + BIRTH_TIMEOUT_MS;
|
|
45
78
|
for (;;) {
|
|
46
79
|
signal?.throwIfAborted();
|
|
@@ -48,14 +81,37 @@ async function awaitBirth(paths, signal) {
|
|
|
48
81
|
if (soul !== null)
|
|
49
82
|
return soul;
|
|
50
83
|
const failure = await observedBirthFailure(paths);
|
|
51
|
-
if (failure !== null)
|
|
84
|
+
if (failure !== null) {
|
|
85
|
+
const settled = await observeSettledExit(owned);
|
|
86
|
+
if (settled.kind === "exited")
|
|
87
|
+
await sealObservedExit(paths, settled.exit);
|
|
88
|
+
if (settled.kind === "exit-error")
|
|
89
|
+
throw new Error(diagnostic(settled.error));
|
|
52
90
|
throw new Error(failure);
|
|
91
|
+
}
|
|
53
92
|
if (performance.now() >= deadline) {
|
|
54
93
|
const settled = await settleTimedOutBirth(paths);
|
|
55
94
|
if (settled !== null)
|
|
56
95
|
return settled;
|
|
57
96
|
}
|
|
58
|
-
|
|
97
|
+
if (owned === undefined) {
|
|
98
|
+
await abortableDelay(Math.min(POLL_MS, Math.max(0, deadline - performance.now())), signal);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const outcome = await Promise.race([
|
|
102
|
+
abortableDelay(Math.min(POLL_MS, Math.max(0, deadline - performance.now())), signal).then(() => "poll"),
|
|
103
|
+
owned.exited.then((exit) => ({ kind: "exited", exit }), (error) => ({ kind: "exit-error", error })),
|
|
104
|
+
]);
|
|
105
|
+
if (outcome === "poll")
|
|
106
|
+
continue;
|
|
107
|
+
if (outcome.kind === "exited") {
|
|
108
|
+
const settledSoul = await readSoul(paths);
|
|
109
|
+
if (settledSoul !== null)
|
|
110
|
+
return settledSoul;
|
|
111
|
+
await sealObservedExit(paths, outcome.exit);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
throw new Error(diagnostic(outcome.error));
|
|
59
115
|
}
|
|
60
116
|
}
|
|
61
117
|
async function takeLeashUntil(paths, deadline) {
|
|
@@ -108,17 +164,22 @@ export async function publishAkuma(input) {
|
|
|
108
164
|
input.signal?.throwIfAborted();
|
|
109
165
|
await input.reserve?.(allocated);
|
|
110
166
|
input.signal?.throwIfAborted();
|
|
111
|
-
await input.launch(allocated);
|
|
112
|
-
|
|
167
|
+
const owned = await input.launch(allocated);
|
|
168
|
+
try {
|
|
169
|
+
input.signal?.throwIfAborted();
|
|
170
|
+
const soul = await awaitBirth(allocated.paths, owned ?? undefined, input.signal);
|
|
171
|
+
if (soul.id !== allocated.id)
|
|
172
|
+
throw new Error("Akuma birth returned a different identity");
|
|
173
|
+
if (input.awaitAsleep === true)
|
|
174
|
+
await awaitAsleepBirth(allocated.paths);
|
|
175
|
+
return allocated;
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
owned?.release();
|
|
179
|
+
}
|
|
113
180
|
}
|
|
114
181
|
catch (error) {
|
|
115
182
|
await sealLocalFailure(allocated, error);
|
|
116
183
|
throw error;
|
|
117
184
|
}
|
|
118
|
-
const soul = await awaitBirth(allocated.paths, input.signal);
|
|
119
|
-
if (soul.id !== allocated.id)
|
|
120
|
-
throw new Error("Akuma birth returned a different identity");
|
|
121
|
-
if (input.awaitAsleep === true)
|
|
122
|
-
await awaitAsleepBirth(allocated.paths);
|
|
123
|
-
return allocated;
|
|
124
185
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { UpstreamRequestService } from "./heart/index.js";
|
|
2
|
+
import type { ServeInput, UpstreamFact } from "./request-serve.js";
|
|
3
|
+
type ExecutionResult = Readonly<{
|
|
4
|
+
result: unknown;
|
|
5
|
+
service?: UpstreamRequestService;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function executeRequest(input: ServeInput, request: UpstreamFact): Promise<ExecutionResult>;
|
|
8
|
+
export {};
|