@astrosheep/keiyaku 4.6.7 → 4.6.8
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/README.md +6 -2
- package/build/plugins/pi/keiyaku.ts +0 -5
- package/build/src/akuma/akuma-call.d.ts +2 -0
- package/build/src/akuma/akuma-call.js +8 -2
- package/build/src/akuma/akuma-instance.d.ts +12 -1
- package/build/src/akuma/akuma-instance.js +27 -7
- package/build/src/akuma/akuma-observe.js +2 -0
- package/build/src/akuma/akuma-owner.d.ts +3 -0
- package/build/src/akuma/akuma-owner.js +4 -0
- package/build/src/akuma/akuma.d.ts +35 -5
- package/build/src/akuma/akuma.js +8 -10
- package/build/src/akuma/archetype.d.ts +7 -15
- package/build/src/akuma/archetype.js +36 -35
- package/build/src/akuma/body.d.ts +5 -3
- package/build/src/akuma/body.js +29 -7
- package/build/src/akuma/call-request.d.ts +18 -10
- package/build/src/akuma/call-request.js +10 -3
- package/build/src/akuma/caller.d.ts +39 -0
- package/build/src/akuma/caller.js +36 -0
- package/build/src/akuma/dispatch-association.d.ts +0 -1
- package/build/src/akuma/dispatch-association.js +0 -3
- package/build/src/akuma/heart/facts.d.ts +16 -12
- package/build/src/akuma/heart/facts.js +8 -0
- package/build/src/akuma/heart/index.d.ts +2 -7
- package/build/src/akuma/heart/index.js +3 -17
- package/build/src/akuma/heart/rows.d.ts +3 -3
- package/build/src/akuma/heart/soul.d.ts +1 -1
- package/build/src/akuma/heart/soul.js +5 -24
- package/build/src/akuma/heart/storage.d.ts +5 -0
- package/build/src/akuma/heart/storage.js +7 -3
- package/build/src/akuma/identity.d.ts +0 -1
- package/build/src/akuma/identity.js +2 -5
- package/build/src/akuma/index.d.ts +5 -2
- package/build/src/akuma/index.js +1 -0
- package/build/src/akuma/nuke.js +6 -28
- package/build/src/akuma/projection.d.ts +0 -11
- package/build/src/akuma/provider-recipe.d.ts +7 -6
- package/build/src/akuma/provider-recipe.js +1 -1
- package/build/src/akuma/provider.d.ts +6 -10
- package/build/src/akuma/provider.js +6 -7
- package/build/src/akuma/providers/acp/events.js +5 -18
- package/build/src/akuma/providers/acp/index.js +2 -3
- package/build/src/akuma/providers/claude/events.js +35 -60
- package/build/src/akuma/providers/claude/index.d.ts +2 -2
- package/build/src/akuma/providers/claude/index.js +2 -3
- package/build/src/akuma/providers/codex-app-server/index.js +2 -2
- package/build/src/akuma/providers/decode.d.ts +25 -0
- package/build/src/akuma/providers/decode.js +44 -0
- package/build/src/akuma/providers/execution-environment.d.ts +2 -1
- package/build/src/akuma/providers/execution-environment.js +3 -0
- package/build/src/akuma/providers/grok-build/index.js +6 -22
- package/build/src/akuma/providers/index.js +2 -0
- package/build/src/akuma/providers/lifecycle.d.ts +9 -0
- package/build/src/akuma/providers/lifecycle.js +10 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +1 -14
- package/build/src/akuma/providers/opencode-sdk/index.js +10 -17
- package/build/src/akuma/providers/opencode-sdk/session.js +1 -3
- package/build/src/akuma/providers/paseo/events.d.ts +25 -0
- package/build/src/akuma/providers/paseo/events.js +157 -0
- package/build/src/akuma/providers/paseo/index.d.ts +9 -0
- package/build/src/akuma/providers/paseo/index.js +353 -0
- package/build/src/akuma/providers/pi/events.js +5 -15
- package/build/src/akuma/publication.d.ts +2 -0
- package/build/src/akuma/publication.js +5 -0
- package/build/src/akuma/request-lifecycle.d.ts +3 -0
- package/build/src/akuma/request-lifecycle.js +25 -4
- package/build/src/akuma/requests.d.ts +3 -1
- package/build/src/akuma/requests.js +32 -0
- package/build/src/akuma/selection-observation.d.ts +72 -2
- package/build/src/akuma/selection-observation.js +0 -6
- package/build/src/akuma/turn-drive.js +2 -5
- package/build/src/cli/akuma-caller.d.ts +3 -0
- package/build/src/cli/akuma-caller.js +21 -0
- package/build/src/cli/commands/akuma-grammar.js +7 -12
- package/build/src/cli/commands/akuma.js +12 -4
- package/build/src/cli/commands/contract-grammar.d.ts +2 -1
- package/build/src/cli/commands/contract-grammar.js +13 -15
- package/build/src/cli/commands/contract-help.d.ts +2 -1
- package/build/src/cli/commands/contract-help.js +31 -1
- package/build/src/cli/commands/contract.js +24 -53
- package/build/src/cli/commands/task.js +18 -35
- package/build/src/cli/parse.d.ts +2 -2
- package/build/src/cli/render/catalog.d.ts +2 -5
- package/build/src/cli/render/contract-history.js +3 -6
- package/build/src/cli/render/contract.js +15 -50
- package/build/src/cli/render/receipt.js +3 -10
- package/build/src/cli/render/task.d.ts +4 -6
- package/build/src/cli/render/task.js +9 -26
- package/build/src/cli/runtime.d.ts +1 -1
- package/build/src/cli/runtime.js +11 -17
- package/build/src/cli/selectors.d.ts +3 -6
- package/build/src/cli/selectors.js +22 -53
- package/build/src/dispatch/index.d.ts +3 -3
- package/build/src/dispatch/index.js +1 -2
- package/build/src/git/observe.d.ts +6 -2
- package/build/src/git/observe.js +6 -2
- package/build/src/index.d.ts +6 -6
- package/build/src/index.js +2 -2
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/read.js +21 -6
- package/build/src/library/address.js +2 -4
- package/build/src/library/akuma-creation.d.ts +5 -1
- package/build/src/library/akuma-creation.js +27 -47
- package/build/src/library/akumas.d.ts +5 -4
- package/build/src/library/akumas.js +13 -9
- package/build/src/library/bind.d.ts +14 -14
- package/build/src/library/bind.js +11 -7
- package/build/src/library/contract-address.d.ts +9 -0
- package/build/src/library/contract-address.js +71 -0
- package/build/src/library/contract-composition.d.ts +4 -7
- package/build/src/library/contract-composition.js +6 -18
- package/build/src/library/contract-creation.d.ts +3 -5
- package/build/src/library/contract-creation.js +19 -17
- package/build/src/library/contract-execution.js +18 -23
- package/build/src/library/contract-outcomes.d.ts +2 -14
- package/build/src/library/contract-outcomes.js +2 -13
- package/build/src/library/contract-types.d.ts +36 -2
- package/build/src/library/delivery.d.ts +0 -2
- package/build/src/library/delivery.js +2 -5
- package/build/src/library/keiyaku.d.ts +0 -5
- package/build/src/library/keiyaku.js +6 -12
- package/build/src/library/outcome.d.ts +500 -499
- package/build/src/library/outcome.js +10 -0
- package/build/src/library/reconcile.js +8 -16
- package/build/src/library/refusal.d.ts +2 -2
- package/build/src/library/repo.d.ts +3 -2
- package/build/src/library/repo.js +10 -5
- package/build/src/library/selection.d.ts +4 -4
- package/build/src/library/selection.js +12 -12
- package/build/src/plugin/akuma-signals.d.ts +2 -0
- package/build/src/plugin/akuma-signals.js +4 -0
- package/build/src/plugin/public.d.ts +27 -2
- package/build/src/plugin/runtime.js +2 -2
- package/build/src/protocol/amend.js +3 -2
- package/build/src/protocol/attempt.js +3 -8
- package/build/src/protocol/bind.d.ts +2 -2
- package/build/src/protocol/bind.js +1 -1
- package/build/src/protocol/deliver.js +3 -3
- package/build/src/protocol/operations.d.ts +1 -3
- package/build/src/protocol/operations.js +0 -8
- package/build/src/protocol/outcome.d.ts +1 -2
- package/build/src/protocol/outcome.js +1 -13
- package/build/src/protocol/placement.js +3 -10
- package/build/src/protocol/read/status.js +31 -70
- package/build/src/protocol/reintegrate.js +3 -10
- package/build/src/protocol/review.js +3 -3
- package/build/src/protocol/run.d.ts +5 -1
- package/build/src/protocol/run.js +3 -3
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/package.json +8 -4
- package/plugins/pi/keiyaku.ts +57 -0
- package/skills/keiyaku/SKILL.md +44 -0
- package/skills/keiyaku-akuma/SKILL.md +130 -0
- package/skills/keiyaku-akuma/references/automation.md +269 -0
- package/skills/keiyaku-babysit/SKILL.md +65 -0
- package/skills/keiyaku-bind/SKILL.md +216 -0
- package/skills/keiyaku-bind/agents/openai.yaml +4 -0
- package/skills/keiyaku-task/SKILL.md +104 -0
- package/skills/keiyaku-workflow/SKILL.md +129 -0
- package/build/src/cli/commands/amend.d.ts +0 -12
- package/build/src/cli/commands/amend.js +0 -10
- package/build/src/cli/commands/bind.d.ts +0 -11
- package/build/src/cli/commands/bind.js +0 -23
- package/build/src/dispatch/association.d.ts +0 -1
- package/build/src/dispatch/association.js +0 -1
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
// Pinned vendor CLI entry: the public SDK does not expose cancellation or selectors.
|
|
5
|
+
import { DaemonClient } from "@getpaseo/client/internal/daemon-client";
|
|
6
|
+
import { bindWsHandler, createWebSocketTransportFactory, defaultWebSocketFactory, } from "@getpaseo/client/internal/daemon-client-websocket-transport";
|
|
7
|
+
import { abortable } from "../../abort.js";
|
|
8
|
+
import { AKUMA_REQUESTS_ENTRY_ENV, akumaExecutionEnvironment } from "../execution-environment.js";
|
|
9
|
+
import { PaseoTurn } from "./events.js";
|
|
10
|
+
import { providerLifecycle } from "../lifecycle.js";
|
|
11
|
+
const configSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
url: z.string().default("ws://127.0.0.1:6767/ws"),
|
|
14
|
+
provider: z.string().trim().min(1).default("claude"),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
function failures(errors, message) {
|
|
18
|
+
if (errors.length === 1)
|
|
19
|
+
throw errors[0];
|
|
20
|
+
if (errors.length > 1)
|
|
21
|
+
throw new AggregateError(errors, message);
|
|
22
|
+
}
|
|
23
|
+
/** Native disposal forgets sockets before their close event and swallows close errors. */
|
|
24
|
+
class PaseoSockets {
|
|
25
|
+
retiring = false;
|
|
26
|
+
errors = [];
|
|
27
|
+
sockets = [];
|
|
28
|
+
factory = (options) => {
|
|
29
|
+
if (this.retiring)
|
|
30
|
+
throw new Error("Paseo socket custody retired");
|
|
31
|
+
const native = createWebSocketTransportFactory((url, settings) => {
|
|
32
|
+
if (this.retiring)
|
|
33
|
+
throw new Error("Paseo socket custody retired");
|
|
34
|
+
const socket = defaultWebSocketFactory(url, settings);
|
|
35
|
+
let proveClosed;
|
|
36
|
+
const closed = new Promise((resolve) => {
|
|
37
|
+
proveClosed = resolve;
|
|
38
|
+
});
|
|
39
|
+
const owned = { socket, closed, observed: false };
|
|
40
|
+
const remove = bindWsHandler(socket, "close", () => {
|
|
41
|
+
owned.observed = true;
|
|
42
|
+
remove();
|
|
43
|
+
proveClosed();
|
|
44
|
+
});
|
|
45
|
+
this.sockets.push(owned);
|
|
46
|
+
return socket;
|
|
47
|
+
})(options);
|
|
48
|
+
return {
|
|
49
|
+
...native,
|
|
50
|
+
close: (code, reason) => {
|
|
51
|
+
try {
|
|
52
|
+
native.close(code, reason);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
this.errors.push(error);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
async retire(client) {
|
|
62
|
+
this.retiring = true;
|
|
63
|
+
// Begin logical retirement, but independently close saved sockets even if
|
|
64
|
+
// native disposal already forgot one after a rejected close code (1001).
|
|
65
|
+
const logical = (async () => {
|
|
66
|
+
try {
|
|
67
|
+
await client.close();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
this.errors.push(error);
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
const physical = [];
|
|
74
|
+
for (const owned of this.sockets) {
|
|
75
|
+
try {
|
|
76
|
+
if (!owned.observed)
|
|
77
|
+
owned.socket.close(1000, "Paseo adapter retired");
|
|
78
|
+
physical.push(owned.closed);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
this.errors.push(error);
|
|
82
|
+
// No success proof for this socket; the Body's existing custody policy
|
|
83
|
+
// receives unsuccessful closure rather than a fabricated close event.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
await Promise.all([logical, ...physical]);
|
|
87
|
+
failures(this.errors, "Paseo client/socket retirement failed");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Check before invoking native work, not only after creating its promise. */
|
|
91
|
+
async function setup(signal, operation) {
|
|
92
|
+
signal.throwIfAborted();
|
|
93
|
+
const result = await abortable(operation(), signal);
|
|
94
|
+
signal.throwIfAborted();
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
function configuration(execution) {
|
|
98
|
+
if (execution.executable !== undefined)
|
|
99
|
+
throw new TypeError("Paseo connects to a local daemon, not an executable");
|
|
100
|
+
const parsed = configSchema.safeParse(execution.config ?? {});
|
|
101
|
+
if (!parsed.success)
|
|
102
|
+
throw new TypeError(`Paseo configuration: ${parsed.error.issues[0]?.message}`, { cause: parsed.error });
|
|
103
|
+
const config = parsed.data;
|
|
104
|
+
const url = new URL(config.url);
|
|
105
|
+
if (!["ws:", "wss:"].includes(url.protocol) ||
|
|
106
|
+
!["127.0.0.1", "localhost", "[::1]"].includes(url.hostname) ||
|
|
107
|
+
url.pathname !== "/ws" ||
|
|
108
|
+
url.username !== "" ||
|
|
109
|
+
url.password !== "" ||
|
|
110
|
+
url.search !== "" ||
|
|
111
|
+
url.hash !== "") {
|
|
112
|
+
throw new TypeError("Paseo url must be a loopback WebSocket endpoint ending in /ws");
|
|
113
|
+
}
|
|
114
|
+
if (config.provider.includes("/"))
|
|
115
|
+
throw new TypeError("Paseo provider must be a bare native provider id; use the model option separately");
|
|
116
|
+
return config;
|
|
117
|
+
}
|
|
118
|
+
function optionAdmission(options) {
|
|
119
|
+
if (options.network !== undefined)
|
|
120
|
+
return { kind: "refused", diagnostic: "Paseo does not support the network option" };
|
|
121
|
+
if (options.systemPromptMode === "replace")
|
|
122
|
+
return { kind: "refused", diagnostic: "Paseo does not support systemPrompt replacement" };
|
|
123
|
+
return { kind: "admitted", options };
|
|
124
|
+
}
|
|
125
|
+
function usable(agent, input, config) {
|
|
126
|
+
if (agent.archivedAt != null || agent.providerUnavailable || agent.status === "closed")
|
|
127
|
+
throw new Error(`Paseo agent ${agent.id} is unavailable`);
|
|
128
|
+
if (agent.cwd !== input.cwd || agent.provider !== config.provider)
|
|
129
|
+
throw new Error(`Paseo agent ${agent.id} no longer matches its execution recipe`);
|
|
130
|
+
if (agent.activeTurn != null || agent.status === "running" || agent.status === "initializing")
|
|
131
|
+
throw new Error(`Paseo agent ${agent.id} is busy; refusing to submit into another native execution`);
|
|
132
|
+
}
|
|
133
|
+
async function offeredModel(client, agent, input, config) {
|
|
134
|
+
const { model, effort } = input.options;
|
|
135
|
+
if (model === undefined && effort === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
const catalog = await setup(input.signal, () => client.listProviderModels(config.provider, { cwd: input.cwd }));
|
|
138
|
+
if (catalog.error != null)
|
|
139
|
+
throw new Error(`Paseo selector catalog unavailable: ${catalog.error}`);
|
|
140
|
+
const requested = model ?? agent.runtimeInfo?.model ?? agent.model;
|
|
141
|
+
const selected = catalog.models?.find((choice) => choice.isSelectable !== false &&
|
|
142
|
+
(requested == null ? choice.isDefault : choice.id === requested || choice.aliases?.includes(requested)));
|
|
143
|
+
if (selected === undefined)
|
|
144
|
+
throw new Error(`Paseo model is not available: ${requested ?? "native default"}`);
|
|
145
|
+
if (effort !== undefined && !selected.thinkingOptions?.some((choice) => choice.id === effort)) {
|
|
146
|
+
throw new Error(`Paseo effort is not available for ${selected.id}: ${effort}`);
|
|
147
|
+
}
|
|
148
|
+
return model === undefined ? undefined : selected.id;
|
|
149
|
+
}
|
|
150
|
+
async function applyOptions(client, agent, input, config) {
|
|
151
|
+
usable(agent, input, config);
|
|
152
|
+
const model = await offeredModel(client, agent, input, config);
|
|
153
|
+
const { effort } = input.options;
|
|
154
|
+
if (model !== undefined)
|
|
155
|
+
await setup(input.signal, () => client.setAgentModel(agent.id, model));
|
|
156
|
+
if (effort !== undefined) {
|
|
157
|
+
const notice = await setup(input.signal, () => client.setAgentThinkingOption(agent.id, effort));
|
|
158
|
+
if (notice?.type === "error")
|
|
159
|
+
throw new Error(`Paseo effort refused: ${notice.message}`);
|
|
160
|
+
}
|
|
161
|
+
const observed = await setup(input.signal, () => client.fetchAgent(agent.id));
|
|
162
|
+
if (observed === null)
|
|
163
|
+
throw new Error(`Paseo agent ${agent.id} is unavailable`);
|
|
164
|
+
usable(observed.agent, input, config);
|
|
165
|
+
if (model !== undefined && (observed.agent.runtimeInfo?.model ?? observed.agent.model) !== model) {
|
|
166
|
+
throw new Error(`Paseo model selection was not confirmed: ${model}`);
|
|
167
|
+
}
|
|
168
|
+
if (effort !== undefined &&
|
|
169
|
+
(observed.agent.effectiveThinkingOptionId ?? observed.agent.runtimeInfo?.thinkingOptionId) !== effort) {
|
|
170
|
+
throw new Error(`Paseo effort selection was not confirmed: ${effort}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
class PaseoDrive {
|
|
174
|
+
client;
|
|
175
|
+
environment;
|
|
176
|
+
sockets;
|
|
177
|
+
closed;
|
|
178
|
+
turn = new PaseoTurn(randomUUID());
|
|
179
|
+
proveClosed;
|
|
180
|
+
failClosed;
|
|
181
|
+
closure;
|
|
182
|
+
removeConnectionListener = () => undefined;
|
|
183
|
+
agentId;
|
|
184
|
+
submitted = false;
|
|
185
|
+
sequence;
|
|
186
|
+
epoch;
|
|
187
|
+
constructor(client, custody, environment, sockets) {
|
|
188
|
+
this.client = client;
|
|
189
|
+
this.environment = environment;
|
|
190
|
+
this.sockets = sockets;
|
|
191
|
+
this.closed = new Promise((resolve, reject) => {
|
|
192
|
+
this.proveClosed = resolve;
|
|
193
|
+
this.failClosed = reject;
|
|
194
|
+
});
|
|
195
|
+
custody.own({ closed: this.closed, abort: () => this.retire(true), forceDispose: () => this.retire(true) });
|
|
196
|
+
this.removeConnectionListener = client.subscribeConnectionStatus((state) => {
|
|
197
|
+
if (this.closure !== undefined)
|
|
198
|
+
return;
|
|
199
|
+
if (state.status === "disconnected" || state.status === "disposed")
|
|
200
|
+
this.turn.fail(new Error("Paseo connection lost before own turn completion"));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async establish(input, config, coordinate) {
|
|
204
|
+
const entry = input.requests.entry;
|
|
205
|
+
if (entry === undefined || !isAbsolute(entry) || resolve(entry) !== entry)
|
|
206
|
+
throw new Error("Paseo requires the Body's stable request entry");
|
|
207
|
+
input.signal.throwIfAborted();
|
|
208
|
+
await setup(input.signal, () => this.client.connect());
|
|
209
|
+
const agent = coordinate === undefined
|
|
210
|
+
? await setup(input.signal, () => this.client.createAgent({
|
|
211
|
+
provider: config.provider,
|
|
212
|
+
cwd: input.cwd,
|
|
213
|
+
...(input.options.systemPrompt === undefined ? {} : { systemPrompt: input.options.systemPrompt }),
|
|
214
|
+
env: { ...akumaExecutionEnvironment(process.env, this.environment), [AKUMA_REQUESTS_ENTRY_ENV]: entry },
|
|
215
|
+
autoArchive: false,
|
|
216
|
+
}))
|
|
217
|
+
: await this.existingAgent(coordinate, input.signal);
|
|
218
|
+
this.agentId = agent.id;
|
|
219
|
+
await applyOptions(this.client, agent, input, config);
|
|
220
|
+
input.signal.throwIfAborted();
|
|
221
|
+
const subscription = this.client.subscribeAgentTimeline(agent.id, (message) => this.receive(message));
|
|
222
|
+
await abortable(subscription.ready, input.signal);
|
|
223
|
+
this.turn.events.emit({ type: "session", coordinate: { sessionId: agent.id } });
|
|
224
|
+
// Native steer may still replace work if a human wins the check/send race.
|
|
225
|
+
input.signal.throwIfAborted();
|
|
226
|
+
const parts = [input.body, ...input.launchTells.map((tell) => tell.text)];
|
|
227
|
+
if (input.schemaJson !== undefined) {
|
|
228
|
+
parts.push("Respond with JSON matching this JSON Schema and no other text:", input.schemaJson);
|
|
229
|
+
}
|
|
230
|
+
const prompt = parts.filter((text) => text.length > 0).join("\n\n");
|
|
231
|
+
await setup(input.signal, () => {
|
|
232
|
+
this.submitted = true;
|
|
233
|
+
return this.client.sendMessage(agent.id, prompt, { messageId: this.turn.messageId, activeTurnBehavior: "steer" });
|
|
234
|
+
});
|
|
235
|
+
const admissionSignal = AbortSignal.any([input.signal, AbortSignal.timeout(15_000)]);
|
|
236
|
+
const fence = await abortable(this.turn.admission, admissionSignal).catch((error) => {
|
|
237
|
+
if (input.signal.aborted)
|
|
238
|
+
throw error;
|
|
239
|
+
throw new Error("Paseo canonical native admission was not observed", { cause: error });
|
|
240
|
+
});
|
|
241
|
+
const completion = this.finish();
|
|
242
|
+
void completion.catch(() => undefined);
|
|
243
|
+
return {
|
|
244
|
+
admission: { fence },
|
|
245
|
+
events: this.turn.events,
|
|
246
|
+
completion,
|
|
247
|
+
abort: () => this.retire(true),
|
|
248
|
+
forceDispose: () => this.retire(true),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async existingAgent(coordinate, signal) {
|
|
252
|
+
if (!("sessionId" in coordinate) || coordinate.sessionId === undefined || "sessionFile" in coordinate)
|
|
253
|
+
throw new Error("Paseo resume requires an agent id");
|
|
254
|
+
const found = await setup(signal, () => this.client.fetchAgent(coordinate.sessionId));
|
|
255
|
+
if (found === null)
|
|
256
|
+
throw new Error(`Paseo agent ${coordinate.sessionId} is unavailable`);
|
|
257
|
+
return found.agent;
|
|
258
|
+
}
|
|
259
|
+
receive(message) {
|
|
260
|
+
if (this.closure !== undefined || message.payload.agentId !== this.agentId)
|
|
261
|
+
return;
|
|
262
|
+
if (message.type === "agent_stream") {
|
|
263
|
+
const { seq, epoch, event } = message.payload;
|
|
264
|
+
if (this.epoch !== undefined && epoch !== undefined && epoch !== this.epoch)
|
|
265
|
+
return this.turn.fail(new Error("Paseo timeline epoch changed before own turn completion"));
|
|
266
|
+
if (epoch !== undefined)
|
|
267
|
+
this.epoch = epoch;
|
|
268
|
+
if (seq !== undefined) {
|
|
269
|
+
if (this.sequence !== undefined && seq > this.sequence + 1)
|
|
270
|
+
return this.turn.fail(new Error("Paseo timeline delivery gap before own turn completion"));
|
|
271
|
+
if (this.sequence !== undefined && seq <= this.sequence)
|
|
272
|
+
return;
|
|
273
|
+
this.sequence = seq;
|
|
274
|
+
}
|
|
275
|
+
this.turn.receive(event);
|
|
276
|
+
}
|
|
277
|
+
else
|
|
278
|
+
this.turn.fail(new Error(message.type === "agent.timeline.error"
|
|
279
|
+
? `Paseo timeline failed: ${message.payload.error}`
|
|
280
|
+
: "Paseo timeline was invalidated before own turn completion"));
|
|
281
|
+
}
|
|
282
|
+
async finish() {
|
|
283
|
+
try {
|
|
284
|
+
const result = await this.turn.completion;
|
|
285
|
+
await this.retire(false);
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
try {
|
|
290
|
+
await this.retire(true);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// Attempt closure retains the control or client-retirement failure independently.
|
|
294
|
+
}
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
retire(cancel) {
|
|
299
|
+
return (this.closure ??= this.close(cancel));
|
|
300
|
+
}
|
|
301
|
+
async close(cancel) {
|
|
302
|
+
this.removeConnectionListener();
|
|
303
|
+
try {
|
|
304
|
+
const errors = [];
|
|
305
|
+
try {
|
|
306
|
+
if (cancel && this.submitted && !this.turn.terminal && this.agentId !== undefined)
|
|
307
|
+
await this.client.cancelAgent(this.agentId);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
errors.push(error);
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
await this.sockets.retire(this.client);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
errors.push(error);
|
|
317
|
+
}
|
|
318
|
+
failures(errors, "Paseo native control and retirement failed");
|
|
319
|
+
this.proveClosed();
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
this.failClosed(error);
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
this.turn.fail(new Error("Paseo client retired"));
|
|
327
|
+
this.turn.events.end();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
export function createPaseoProvider(execution, dependencies = {}) {
|
|
332
|
+
const config = configuration(execution);
|
|
333
|
+
return {
|
|
334
|
+
admitOptions: optionAdmission,
|
|
335
|
+
...providerLifecycle(async (input, custody) => {
|
|
336
|
+
custody.signal.throwIfAborted();
|
|
337
|
+
const environment = akumaExecutionEnvironment(process.env, execution.env);
|
|
338
|
+
const sockets = new PaseoSockets();
|
|
339
|
+
const client = (dependencies.createClient ?? ((value) => new DaemonClient(value)))({
|
|
340
|
+
url: config.url,
|
|
341
|
+
clientId: randomUUID(),
|
|
342
|
+
clientType: "cli",
|
|
343
|
+
reconnect: { enabled: false },
|
|
344
|
+
transportFactory: sockets.factory,
|
|
345
|
+
connectTimeoutMs: 15_000,
|
|
346
|
+
...(environment.PASEO_PASSWORD === undefined ? {} : { password: environment.PASEO_PASSWORD }),
|
|
347
|
+
});
|
|
348
|
+
const drive = new PaseoDrive(client, custody, execution.env ?? {}, sockets);
|
|
349
|
+
const signal = AbortSignal.any([custody.signal, AbortSignal.timeout(15_000)]);
|
|
350
|
+
return await drive.establish({ ...input, signal }, config, input.session.kind === "resume" ? input.session.coordinate : undefined);
|
|
351
|
+
}),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { noteEvent, otherToolCall, unknownEvent } from "../../provider.js";
|
|
2
|
+
import { nonblank, positiveLine, searchToolCall } from "../decode.js";
|
|
2
3
|
import { diffstatFromUnifiedPatch } from "../unified-patch.js";
|
|
3
4
|
export const PI_EVENT_DISPOSITIONS = {
|
|
4
5
|
agent_end: "drop",
|
|
@@ -51,12 +52,6 @@ function hasTextContent(content) {
|
|
|
51
52
|
return value?.type === "text" && typeof value.text === "string";
|
|
52
53
|
}));
|
|
53
54
|
}
|
|
54
|
-
function positiveLine(value) {
|
|
55
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 ? value : undefined;
|
|
56
|
-
}
|
|
57
|
-
function optionalText(value) {
|
|
58
|
-
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
59
|
-
}
|
|
60
55
|
function runCall(name, value) {
|
|
61
56
|
return name === "bash" && typeof value.command === "string" ? { kind: "run", command: value.command } : undefined;
|
|
62
57
|
}
|
|
@@ -78,15 +73,10 @@ function searchCall(name, value) {
|
|
|
78
73
|
const query = value.pattern ?? value.query;
|
|
79
74
|
if (typeof query !== "string")
|
|
80
75
|
return undefined;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
query,
|
|
86
|
-
scope: name === "find" ? "files" : "content",
|
|
87
|
-
...(path === undefined ? {} : { path }),
|
|
88
|
-
...(name === "grep" && glob !== undefined ? { glob } : {}),
|
|
89
|
-
};
|
|
76
|
+
return searchToolCall(query, name === "find" ? "files" : "content", {
|
|
77
|
+
path: nonblank(value.path),
|
|
78
|
+
glob: name === "grep" ? nonblank(value.glob) : undefined,
|
|
79
|
+
});
|
|
90
80
|
}
|
|
91
81
|
function fileChangeCall(name, value) {
|
|
92
82
|
// Native write cannot prove creation versus overwrite and reports no patch, so
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AkumaCaller } from "./caller.js";
|
|
1
2
|
import { type Soul } from "./heart/index.js";
|
|
2
3
|
import { type AllocatedAkuma, type AkumaPaths, type AkuId } from "./identity.js";
|
|
3
4
|
import type { OwnedProcess } from "../runtime/proc/run.js";
|
|
@@ -81,6 +82,7 @@ type PreparedCallResult = Readonly<{
|
|
|
81
82
|
type PreparedCallInput = Readonly<{
|
|
82
83
|
archetype: string;
|
|
83
84
|
cwd: string;
|
|
85
|
+
caller?: AkumaCaller;
|
|
84
86
|
initialTell?: CallInitialTell;
|
|
85
87
|
signal?: AbortSignal;
|
|
86
88
|
custody: PreparedCallCustody;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeAkumaCaller } from "./caller.js";
|
|
1
2
|
import { HeldAkumaLeash, initializeHeart, readHeart, readSeal, readSoul } from "./heart/index.js";
|
|
2
3
|
import { acquireLeash } from "./control.js";
|
|
3
4
|
import { allocateAkumaDirectory } from "./identity.js";
|
|
@@ -337,6 +338,9 @@ async function durablyBorn(allocated) {
|
|
|
337
338
|
*/
|
|
338
339
|
export async function executePreparedCall(input) {
|
|
339
340
|
const { custody } = input;
|
|
341
|
+
const caller = custody.kind === "request"
|
|
342
|
+
? { kind: "akuma", world: custody.world, akuId: custody.parent.id }
|
|
343
|
+
: decodeAkumaCaller(input.caller);
|
|
340
344
|
let world;
|
|
341
345
|
if (custody.kind === "local") {
|
|
342
346
|
world = custody.world;
|
|
@@ -383,6 +387,7 @@ export async function executePreparedCall(input) {
|
|
|
383
387
|
allowed: recipe.allowed,
|
|
384
388
|
cwd: input.cwd,
|
|
385
389
|
origin,
|
|
390
|
+
...(caller === undefined ? {} : { caller }),
|
|
386
391
|
},
|
|
387
392
|
});
|
|
388
393
|
},
|
|
@@ -19,6 +19,7 @@ export declare class BodyRequestPump {
|
|
|
19
19
|
private readonly input;
|
|
20
20
|
private readonly serveClaim;
|
|
21
21
|
readonly directory: string;
|
|
22
|
+
readonly entry: string;
|
|
22
23
|
readonly failure: Promise<never>;
|
|
23
24
|
private readonly closeSignal;
|
|
24
25
|
private readonly executionSignal;
|
|
@@ -26,10 +27,12 @@ export declare class BodyRequestPump {
|
|
|
26
27
|
private readonly running;
|
|
27
28
|
private readonly cancelBody;
|
|
28
29
|
private admissionClosed;
|
|
30
|
+
private closure;
|
|
29
31
|
protected constructor(input: PumpInput, serveClaim: ServeClaim);
|
|
30
32
|
static openWithService(input: PumpInput, serveClaim: ServeClaim): Promise<BodyRequestPump>;
|
|
31
33
|
private run;
|
|
32
34
|
close(): Promise<void>;
|
|
35
|
+
private closeTransport;
|
|
33
36
|
stopAdmission(): void;
|
|
34
37
|
}
|
|
35
38
|
export declare function clearBodyRequestTransport(paths: AkumaPaths): Promise<void>;
|
|
@@ -4,12 +4,13 @@ import { abortableDelay } from "./abort.js";
|
|
|
4
4
|
import { HeldAkumaLeash, readNonterminalRequests, readSoul, serveRequest, unproveRequest, voidRequest, } from "./heart/index.js";
|
|
5
5
|
import { pathsForAkuId, worldRootForAkumaPaths } from "./identity.js";
|
|
6
6
|
import { BIRTH_TIMEOUT_MS } from "./publication.js";
|
|
7
|
-
import { decodeEnvelope } from "./request-wire.js";
|
|
7
|
+
import { atomicJson, decodeEnvelope } from "./request-wire.js";
|
|
8
8
|
const POLL_MS = 100;
|
|
9
9
|
export class BodyRequestPump {
|
|
10
10
|
input;
|
|
11
11
|
serveClaim;
|
|
12
12
|
directory;
|
|
13
|
+
entry;
|
|
13
14
|
failure;
|
|
14
15
|
closeSignal = new AbortController();
|
|
15
16
|
executionSignal = new AbortController();
|
|
@@ -17,10 +18,12 @@ export class BodyRequestPump {
|
|
|
17
18
|
running;
|
|
18
19
|
cancelBody;
|
|
19
20
|
admissionClosed = false;
|
|
21
|
+
closure;
|
|
20
22
|
constructor(input, serveClaim) {
|
|
21
23
|
this.input = input;
|
|
22
24
|
this.serveClaim = serveClaim;
|
|
23
25
|
this.directory = join(input.paths.directory, "requests", String(input.bodySequence));
|
|
26
|
+
this.entry = join(input.paths.directory, "request-entry.json");
|
|
24
27
|
this.cancelBody = () => {
|
|
25
28
|
this.stopAdmission();
|
|
26
29
|
if (!this.executionSignal.signal.aborted)
|
|
@@ -33,7 +36,16 @@ export class BodyRequestPump {
|
|
|
33
36
|
this.failure = this.running.then(() => new Promise(() => { }), (error) => Promise.reject(error));
|
|
34
37
|
}
|
|
35
38
|
static async openWithService(input, serveClaim) {
|
|
36
|
-
|
|
39
|
+
input.signal.throwIfAborted();
|
|
40
|
+
const directory = join(input.paths.directory, "requests", String(input.bodySequence));
|
|
41
|
+
await mkdir(directory, { recursive: true });
|
|
42
|
+
try {
|
|
43
|
+
await atomicJson(join(input.paths.directory, "request-entry.json"), { directory }, input.signal);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
await rm(directory, { recursive: true, force: true });
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
37
49
|
return new BodyRequestPump(input, serveClaim);
|
|
38
50
|
}
|
|
39
51
|
async run() {
|
|
@@ -84,12 +96,20 @@ export class BodyRequestPump {
|
|
|
84
96
|
}
|
|
85
97
|
}
|
|
86
98
|
}
|
|
87
|
-
|
|
99
|
+
close() {
|
|
100
|
+
return (this.closure ??= this.closeTransport());
|
|
101
|
+
}
|
|
102
|
+
async closeTransport() {
|
|
88
103
|
this.stopAdmission();
|
|
89
104
|
if (!this.executionSignal.signal.aborted)
|
|
90
105
|
this.executionSignal.abort(new Error("Body request pump closed"));
|
|
91
106
|
try {
|
|
92
|
-
|
|
107
|
+
try {
|
|
108
|
+
await rm(this.entry, { force: true });
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
await this.running;
|
|
112
|
+
}
|
|
93
113
|
}
|
|
94
114
|
finally {
|
|
95
115
|
this.input.signal.removeEventListener("abort", this.cancelBody);
|
|
@@ -103,6 +123,7 @@ export class BodyRequestPump {
|
|
|
103
123
|
}
|
|
104
124
|
}
|
|
105
125
|
export async function clearBodyRequestTransport(paths) {
|
|
126
|
+
await rm(join(paths.directory, "request-entry.json"), { force: true });
|
|
106
127
|
await rm(join(paths.directory, "requests"), { recursive: true, force: true });
|
|
107
128
|
}
|
|
108
129
|
async function requestFiles(directory) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AkumaBodyRequestError, requestBodyCommand } from "./request-rendezvous.js";
|
|
2
|
-
|
|
2
|
+
type ExecutionChannel = Readonly<{
|
|
3
3
|
kind: "local";
|
|
4
4
|
}> | Readonly<{
|
|
5
5
|
kind: "body-request";
|
|
@@ -15,4 +15,6 @@ export declare function bodyRequestExecutionContext(directory: unknown): Executi
|
|
|
15
15
|
export declare function bodyRequestExecution(input: Readonly<{
|
|
16
16
|
directory: string;
|
|
17
17
|
}>): LibraryExecution;
|
|
18
|
+
/** Resolve persistent transport once at private process composition, never during an operation. */
|
|
19
|
+
export declare function executionForEnvironment(environment: Readonly<Record<string, string | undefined>>, signal?: AbortSignal): Promise<LibraryExecution>;
|
|
18
20
|
export declare function executionChannel(context?: ExecutionContext): ExecutionChannel;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { AKUMA_REQUESTS_ENTRY_ENV, AKUMA_REQUESTS_ENV } from "./providers/execution-environment.js";
|
|
2
4
|
export { AkumaBodyRequestError, requestBodyCommand } from "./request-rendezvous.js";
|
|
3
5
|
const LOCAL_CHANNEL = Object.freeze({ kind: "local" });
|
|
4
6
|
export function localExecutionContext() {
|
|
@@ -35,6 +37,36 @@ export function bodyRequestExecution(input) {
|
|
|
35
37
|
throw new TypeError(error instanceof Error ? error.message : String(error));
|
|
36
38
|
}
|
|
37
39
|
}
|
|
40
|
+
/** Resolve persistent transport once at private process composition, never during an operation. */
|
|
41
|
+
export async function executionForEnvironment(environment, signal) {
|
|
42
|
+
signal?.throwIfAborted();
|
|
43
|
+
const directory = environment[AKUMA_REQUESTS_ENV];
|
|
44
|
+
const entry = environment[AKUMA_REQUESTS_ENTRY_ENV];
|
|
45
|
+
if (entry === undefined)
|
|
46
|
+
return directory === undefined ? localExecutionContext() : bodyRequestExecution({ directory });
|
|
47
|
+
if (directory !== undefined)
|
|
48
|
+
throw new TypeError("AKUMA_REQUESTS and AKUMA_REQUESTS_ENTRY are mutually exclusive");
|
|
49
|
+
if (!isAbsolute(entry) || resolve(entry) !== entry) {
|
|
50
|
+
throw new TypeError("AKUMA_REQUESTS_ENTRY must be an absolute normalized path");
|
|
51
|
+
}
|
|
52
|
+
let serialized;
|
|
53
|
+
try {
|
|
54
|
+
serialized = await readFile(entry, { encoding: "utf8", signal });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
signal?.throwIfAborted();
|
|
58
|
+
throw new Error(`Akuma stable request entry unavailable: no current request channel at ${entry}`, { cause: error });
|
|
59
|
+
}
|
|
60
|
+
signal?.throwIfAborted();
|
|
61
|
+
try {
|
|
62
|
+
const value = valueRecord(JSON.parse(serialized), "body request entry");
|
|
63
|
+
closed(value, ["directory"], "body request entry");
|
|
64
|
+
return bodyRequestExecution({ directory: value.directory });
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw new TypeError(`Akuma stable request entry malformed at ${entry}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
38
70
|
export function executionChannel(context) {
|
|
39
71
|
return context?.channel ?? LOCAL_CHANNEL;
|
|
40
72
|
}
|