@noopolis/daimon 0.1.1 → 0.1.2
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 +54 -10
- package/dist/core/siblingBuildFreshness.d.ts +21 -0
- package/dist/core/siblingBuildFreshness.d.ts.map +1 -0
- package/dist/core/siblingBuildFreshness.js +25 -0
- package/dist/core/types.d.ts +10 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/examples/exampleCausalId.d.ts +3 -0
- package/dist/examples/exampleCausalId.d.ts.map +1 -0
- package/dist/examples/exampleCausalId.js +8 -0
- package/dist/examples/jungian-play-org.js +3 -2
- package/dist/examples/jungian-triad-org.js +3 -2
- package/dist/examples/jungianPlayAgent.d.ts +1 -1
- package/dist/examples/jungianPlayAgent.d.ts.map +1 -1
- package/dist/examples/jungianPlayAgent.js +1 -1
- package/dist/examples/jungianProfiles.d.ts +1 -1
- package/dist/examples/jungianProfiles.d.ts.map +1 -1
- package/dist/examples/jungianTriadProfiles.d.ts +1 -1
- package/dist/examples/jungianTriadProfiles.d.ts.map +1 -1
- package/dist/examples/pi-agent.js +3 -2
- package/dist/examples/pi-memory-org.js +6 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/mcp/toolServer.d.ts +14 -0
- package/dist/mcp/toolServer.d.ts.map +1 -0
- package/dist/mcp/toolServer.js +105 -0
- package/dist/observability/causalEvents.d.ts +118 -0
- package/dist/observability/causalEvents.d.ts.map +1 -0
- package/dist/observability/causalEvents.js +135 -0
- package/dist/observability/controlCausal.d.ts +117 -0
- package/dist/observability/controlCausal.d.ts.map +1 -0
- package/dist/observability/controlCausal.js +107 -0
- package/dist/observability/emitCausalFixture.d.ts +25 -0
- package/dist/observability/emitCausalFixture.d.ts.map +1 -0
- package/dist/observability/emitCausalFixture.js +98 -0
- package/dist/observability/index.d.ts +2 -0
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +2 -0
- package/dist/pi/cliSession.d.ts +46 -0
- package/dist/pi/cliSession.d.ts.map +1 -0
- package/dist/pi/cliSession.js +224 -0
- package/dist/pi/index.d.ts +5 -0
- package/dist/pi/index.d.ts.map +1 -1
- package/dist/pi/index.js +5 -0
- package/dist/pi/memoryTools.d.ts +55 -1
- package/dist/pi/memoryTools.d.ts.map +1 -1
- package/dist/pi/memoryTools.js +144 -24
- package/dist/pi/modelRegistry.d.ts +13 -0
- package/dist/pi/modelRegistry.d.ts.map +1 -0
- package/dist/pi/modelRegistry.js +19 -0
- package/dist/pi/piAgentHandle.d.ts +45 -0
- package/dist/pi/piAgentHandle.d.ts.map +1 -0
- package/dist/pi/piAgentHandle.js +324 -0
- package/dist/pi/piAgentWakeSupport.d.ts +68 -0
- package/dist/pi/piAgentWakeSupport.d.ts.map +1 -0
- package/dist/pi/piAgentWakeSupport.js +147 -0
- package/dist/pi/piHarness.d.ts +30 -4
- package/dist/pi/piHarness.d.ts.map +1 -1
- package/dist/pi/piHarness.js +97 -255
- package/dist/pi/prompts.d.ts +9 -0
- package/dist/pi/prompts.d.ts.map +1 -0
- package/dist/pi/prompts.js +39 -0
- package/dist/pi/rawTrainingCapture.d.ts +59 -0
- package/dist/pi/rawTrainingCapture.d.ts.map +1 -0
- package/dist/pi/rawTrainingCapture.js +175 -0
- package/dist/pi/turnCausal.d.ts +73 -0
- package/dist/pi/turnCausal.d.ts.map +1 -0
- package/dist/pi/turnCausal.js +75 -0
- package/dist/pi/turnTrace.d.ts +128 -0
- package/dist/pi/turnTrace.d.ts.map +1 -0
- package/dist/pi/turnTrace.js +141 -0
- package/dist/pi/wakeAcceptance.d.ts +49 -0
- package/dist/pi/wakeAcceptance.d.ts.map +1 -0
- package/dist/pi/wakeAcceptance.js +281 -0
- package/dist/pi/wakeAcceptanceFs.d.ts +44 -0
- package/dist/pi/wakeAcceptanceFs.d.ts.map +1 -0
- package/dist/pi/wakeAcceptanceFs.js +318 -0
- package/dist/pi/wakeAcceptanceSchema.d.ts +75 -0
- package/dist/pi/wakeAcceptanceSchema.d.ts.map +1 -0
- package/dist/pi/wakeAcceptanceSchema.js +265 -0
- package/dist/pi/wakeModes.d.ts +9 -0
- package/dist/pi/wakeModes.d.ts.map +1 -0
- package/dist/pi/wakeModes.js +19 -0
- package/dist/pi/worldNudge.d.ts +22 -0
- package/dist/pi/worldNudge.d.ts.map +1 -0
- package/dist/pi/worldNudge.js +62 -0
- package/dist/pi/worldToolProtocol.d.ts +13 -0
- package/dist/pi/worldToolProtocol.d.ts.map +1 -0
- package/dist/pi/worldToolProtocol.js +49 -0
- package/dist/pi/worldTools.d.ts +32 -0
- package/dist/pi/worldTools.d.ts.map +1 -0
- package/dist/pi/worldTools.js +373 -0
- package/dist/pi/worldTrajectory.d.ts +52 -0
- package/dist/pi/worldTrajectory.d.ts.map +1 -0
- package/dist/pi/worldTrajectory.js +131 -0
- package/package.json +10 -13
- package/dist/examples/mixed-engine-org.d.ts +0 -2
- package/dist/examples/mixed-engine-org.d.ts.map +0 -1
- package/dist/examples/mixed-engine-org.js +0 -351
- package/dist/examples/mixedEngineCli.d.ts +0 -15
- package/dist/examples/mixedEngineCli.d.ts.map +0 -1
- package/dist/examples/mixedEngineCli.js +0 -174
package/dist/pi/piHarness.js
CHANGED
|
@@ -1,228 +1,14 @@
|
|
|
1
1
|
import { mkdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { AuthStorage, createAgentSession,
|
|
3
|
+
import { AuthStorage, createAgentSession, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { createMemoryRuntime } from "@noopolis/mneme";
|
|
4
5
|
import { resolvePiHarnessModel } from "./modelConfig.js";
|
|
6
|
+
import { createPiModelRegistry } from "./modelRegistry.js";
|
|
5
7
|
import { createPiMemoryTools, piMemoryToolNames } from "./memoryTools.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
: ModelRegistry.inMemory(authStorage);
|
|
11
|
-
if (!options.modelsPath && options.model?.endpoint) {
|
|
12
|
-
const { modelsConfig } = resolvePiHarnessModel(options.model);
|
|
13
|
-
for (const [provider, config] of Object.entries(modelsConfig.providers)) {
|
|
14
|
-
registry.registerProvider(provider, {
|
|
15
|
-
api: config.api,
|
|
16
|
-
apiKey: config.apiKey,
|
|
17
|
-
baseUrl: config.baseUrl,
|
|
18
|
-
models: config.models
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return registry;
|
|
23
|
-
};
|
|
24
|
-
const fallbackPacket = (input) => ({
|
|
25
|
-
principal: {
|
|
26
|
-
agentId: "unknown",
|
|
27
|
-
scope: "global"
|
|
28
|
-
},
|
|
29
|
-
sections: [{ heading: "Wake event", text: formatWakePrompt(input) }],
|
|
30
|
-
rawHint: "memory bypass active"
|
|
31
|
-
});
|
|
32
|
-
const extractOutputText = (chunks) => chunks.join("\n").trim();
|
|
33
|
-
class PiAgentHandle {
|
|
34
|
-
id;
|
|
35
|
-
session;
|
|
36
|
-
memory;
|
|
37
|
-
memoryToolContext;
|
|
38
|
-
state = "idle";
|
|
39
|
-
lastWakeAt;
|
|
40
|
-
lastError;
|
|
41
|
-
wakeQueue = Promise.resolve();
|
|
42
|
-
constructor(id, session, memory, memoryToolContext) {
|
|
43
|
-
this.id = id;
|
|
44
|
-
this.session = session;
|
|
45
|
-
this.memory = memory;
|
|
46
|
-
this.memoryToolContext = memoryToolContext;
|
|
47
|
-
}
|
|
48
|
-
async wake(event) {
|
|
49
|
-
const queued = this.wakeQueue.then(() => this.runWake(event), () => this.runWake(event));
|
|
50
|
-
this.wakeQueue = queued.then(() => undefined, () => undefined);
|
|
51
|
-
return queued;
|
|
52
|
-
}
|
|
53
|
-
async runWake(event) {
|
|
54
|
-
const startedAt = Date.now();
|
|
55
|
-
const chunks = [];
|
|
56
|
-
const toolEvents = [];
|
|
57
|
-
this.state = "running";
|
|
58
|
-
this.lastWakeAt = new Date().toISOString();
|
|
59
|
-
this.lastError = undefined;
|
|
60
|
-
const unsubscribe = this.session.subscribe((piEvent) => {
|
|
61
|
-
if (piEvent && typeof piEvent === "object" && "type" in piEvent && piEvent.type !== "turn_end") {
|
|
62
|
-
toolEvents.push(piEvent);
|
|
63
|
-
}
|
|
64
|
-
if (piEvent.type !== "turn_end") {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const message = piEvent.message;
|
|
68
|
-
const content = message.content;
|
|
69
|
-
if (typeof content === "string") {
|
|
70
|
-
chunks.push(content);
|
|
71
|
-
}
|
|
72
|
-
else if (Array.isArray(content)) {
|
|
73
|
-
chunks.push(content
|
|
74
|
-
.filter((item) => {
|
|
75
|
-
const candidate = item;
|
|
76
|
-
return candidate.type === "text" && typeof candidate.text === "string";
|
|
77
|
-
})
|
|
78
|
-
.map((item) => item.text)
|
|
79
|
-
.join(""));
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
const memoryContext = readMemoryContext({
|
|
83
|
-
kind: event.kind,
|
|
84
|
-
id: event.id,
|
|
85
|
-
from: event.from,
|
|
86
|
-
text: event.text,
|
|
87
|
-
context: event.context
|
|
88
|
-
});
|
|
89
|
-
const request = {
|
|
90
|
-
eventId: event.id,
|
|
91
|
-
kind: event.kind,
|
|
92
|
-
text: event.text,
|
|
93
|
-
from: event.from,
|
|
94
|
-
context: memoryContext
|
|
95
|
-
};
|
|
96
|
-
let prepared;
|
|
97
|
-
let promptText = formatWakePrompt(event);
|
|
98
|
-
try {
|
|
99
|
-
if (this.memory) {
|
|
100
|
-
prepared = await this.memory.prepareTurn(request);
|
|
101
|
-
promptText = prepared.promptText;
|
|
102
|
-
if (this.memoryToolContext) {
|
|
103
|
-
this.memoryToolContext.current = {
|
|
104
|
-
wakeId: event.id,
|
|
105
|
-
threadId: `${memoryContext.networkId ?? "local"}:${memoryContext.roomId ?? event.from ?? "manual"}`,
|
|
106
|
-
principal: prepared.principal,
|
|
107
|
-
conversationScope: memoryScopeId(prepared.principal),
|
|
108
|
-
audienceKey: memoryContext.roomId ?? event.from ?? this.id,
|
|
109
|
-
transport: "in_process"
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
await this.session.prompt(promptText, { expandPromptTemplates: false });
|
|
114
|
-
this.state = "idle";
|
|
115
|
-
const outputText = extractOutputText(chunks);
|
|
116
|
-
if (this.memory) {
|
|
117
|
-
const promptPacket = prepared?.packet ?? fallbackPacket(event);
|
|
118
|
-
await this.memory.recordTurn({
|
|
119
|
-
principal: {
|
|
120
|
-
agentId: this.id,
|
|
121
|
-
scope: prepared?.principal.scope ?? "global",
|
|
122
|
-
qualifier: prepared?.principal.qualifier
|
|
123
|
-
},
|
|
124
|
-
prompt: {
|
|
125
|
-
...promptPacket,
|
|
126
|
-
principal: {
|
|
127
|
-
agentId: this.id,
|
|
128
|
-
scope: prepared?.principal.scope ?? "global",
|
|
129
|
-
qualifier: prepared?.principal.qualifier
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
request,
|
|
133
|
-
recall: prepared?.recall,
|
|
134
|
-
result: "completed",
|
|
135
|
-
outputText,
|
|
136
|
-
toolEvents
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
agentId: this.id,
|
|
141
|
-
text: outputText,
|
|
142
|
-
durationMs: Date.now() - startedAt
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
this.state = "failed";
|
|
147
|
-
this.lastError = error instanceof Error ? error.message : String(error);
|
|
148
|
-
if (this.memory) {
|
|
149
|
-
try {
|
|
150
|
-
const promptPacket = prepared?.packet ?? fallbackPacket(event);
|
|
151
|
-
await this.memory.recordTurn({
|
|
152
|
-
principal: {
|
|
153
|
-
agentId: this.id,
|
|
154
|
-
scope: prepared?.principal.scope ?? "global",
|
|
155
|
-
qualifier: prepared?.principal.qualifier
|
|
156
|
-
},
|
|
157
|
-
prompt: {
|
|
158
|
-
...promptPacket,
|
|
159
|
-
principal: {
|
|
160
|
-
agentId: this.id,
|
|
161
|
-
scope: prepared?.principal.scope ?? "global",
|
|
162
|
-
qualifier: prepared?.principal.qualifier
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
request,
|
|
166
|
-
recall: prepared?.recall,
|
|
167
|
-
result: "failed",
|
|
168
|
-
outputText: extractOutputText(chunks),
|
|
169
|
-
toolEvents,
|
|
170
|
-
error: this.lastError
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
// Memory write-back is best-effort when waking fails.
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
throw error;
|
|
178
|
-
}
|
|
179
|
-
finally {
|
|
180
|
-
if (this.memoryToolContext) {
|
|
181
|
-
this.memoryToolContext.current = undefined;
|
|
182
|
-
}
|
|
183
|
-
unsubscribe();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
status() {
|
|
187
|
-
return {
|
|
188
|
-
agentId: this.id,
|
|
189
|
-
state: this.state,
|
|
190
|
-
lastWakeAt: this.lastWakeAt,
|
|
191
|
-
lastError: this.lastError
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
async stop() {
|
|
195
|
-
this.session.dispose();
|
|
196
|
-
this.state = "stopped";
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
const formatWakePrompt = (event) => `Wake event:
|
|
200
|
-
- id: ${event.id}
|
|
201
|
-
- kind: ${event.kind}
|
|
202
|
-
- from: ${event.from ?? "operator"}
|
|
203
|
-
|
|
204
|
-
${event.text}`;
|
|
205
|
-
const createResourceLoader = (input) => {
|
|
206
|
-
const systemPrompt = [
|
|
207
|
-
`You are ${input.name} (${input.id}).`,
|
|
208
|
-
input.instructions,
|
|
209
|
-
"You are running inside a harnessed workspace prepared by the caller.",
|
|
210
|
-
"Use the available coding tools when asked to read, write, edit, or inspect files.",
|
|
211
|
-
"Use memory_search, memory_locate, memory_register, memory_summarize, and memory_forget when scoped memory matters.",
|
|
212
|
-
"Keep responses brief and report the exact files you created or modified."
|
|
213
|
-
].join("\n\n");
|
|
214
|
-
return {
|
|
215
|
-
getExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),
|
|
216
|
-
getSkills: () => ({ skills: [], diagnostics: [] }),
|
|
217
|
-
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
|
218
|
-
getThemes: () => ({ themes: [], diagnostics: [] }),
|
|
219
|
-
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
220
|
-
getSystemPrompt: () => systemPrompt,
|
|
221
|
-
getAppendSystemPrompt: () => [],
|
|
222
|
-
extendResources: () => { },
|
|
223
|
-
reload: async () => { }
|
|
224
|
-
};
|
|
225
|
-
};
|
|
8
|
+
import { createResourceLoader } from "./prompts.js";
|
|
9
|
+
import { PiAgentHandle } from "./piAgentHandle.js";
|
|
10
|
+
import { createPiWorldTools, piWorldToolNames } from "./worldTools.js";
|
|
11
|
+
import { bindPiRawTrainingCapture, validatePiRawTrainingCaptureOptions } from "./rawTrainingCapture.js";
|
|
226
12
|
export class PiHarnessAdapter {
|
|
227
13
|
options;
|
|
228
14
|
authStorage;
|
|
@@ -231,12 +17,15 @@ export class PiHarnessAdapter {
|
|
|
231
17
|
constructor(options) {
|
|
232
18
|
this.options = options;
|
|
233
19
|
this.authStorage = AuthStorage.create(options.authPath);
|
|
234
|
-
this.modelRegistry =
|
|
20
|
+
this.modelRegistry = createPiModelRegistry(this.authStorage, options);
|
|
235
21
|
this.sessionFactory = options.sessionFactory ?? createAgentSession;
|
|
236
22
|
}
|
|
237
23
|
async startAgent(input) {
|
|
24
|
+
validatePiRawTrainingCaptureOptions(this.options.rawTrainingCapture);
|
|
238
25
|
await mkdir(input.runtimeHomePath, { recursive: true });
|
|
239
26
|
await mkdir(input.workspacePath, { recursive: true });
|
|
27
|
+
const memoryRuntimeHomePath = this.options.memory?.runtimeHomePath ?? input.runtimeHomePath;
|
|
28
|
+
await mkdir(memoryRuntimeHomePath, { recursive: true });
|
|
240
29
|
const modelSpec = this.options.model ?? {
|
|
241
30
|
auth: { method: "codex" },
|
|
242
31
|
provider: "openai",
|
|
@@ -247,38 +36,91 @@ export class PiHarnessAdapter {
|
|
|
247
36
|
if (!model) {
|
|
248
37
|
throw new Error(`Pi model not found: ${resolvedModel.provider}/${resolvedModel.name}`);
|
|
249
38
|
}
|
|
250
|
-
const memory =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
39
|
+
const memory = this.options.memory === undefined
|
|
40
|
+
? undefined
|
|
41
|
+
: createMemoryRuntime({
|
|
42
|
+
agentId: input.id,
|
|
43
|
+
authority: this.options.memory.authority,
|
|
44
|
+
embeddingProvider: this.options.memory.embeddingProvider,
|
|
45
|
+
runtimeHomePath: memoryRuntimeHomePath,
|
|
46
|
+
source: this.options.memory.source,
|
|
47
|
+
tokenBudget: this.options.memory.tokenBudget
|
|
48
|
+
});
|
|
49
|
+
const memoryToolContext = memory === undefined ? undefined : {};
|
|
50
|
+
const worldToolContext = this.options.world === undefined ? undefined : {};
|
|
51
|
+
const rawTrainingCaptureRef = this.options.rawTrainingCapture === undefined ? undefined : {};
|
|
52
|
+
const sessionInput = (mode, sessionDirectory) => {
|
|
53
|
+
const memoryTools = memory === undefined || memoryToolContext === undefined
|
|
54
|
+
? []
|
|
55
|
+
: createPiMemoryTools({
|
|
56
|
+
agentId: input.id,
|
|
57
|
+
memory,
|
|
58
|
+
contextRef: memoryToolContext,
|
|
59
|
+
mode
|
|
60
|
+
});
|
|
61
|
+
const worldTools = this.options.world === undefined
|
|
62
|
+
? undefined
|
|
63
|
+
: createPiWorldTools({
|
|
64
|
+
world: this.options.world,
|
|
65
|
+
contextRef: worldToolContext
|
|
66
|
+
});
|
|
67
|
+
const toolNames = [
|
|
68
|
+
...(input.tools ?? ["read", "write", "edit", "bash", "grep", "find", "ls"]),
|
|
69
|
+
...piMemoryToolNames(memoryTools),
|
|
70
|
+
...(worldTools === undefined ? [] : piWorldToolNames(worldTools))
|
|
71
|
+
];
|
|
72
|
+
return {
|
|
73
|
+
cwd: input.workspacePath,
|
|
74
|
+
agentDir: input.runtimeHomePath,
|
|
75
|
+
daimonSecretEnvironmentNames: this.options.world === undefined ? [] : [this.options.world.tokenEnv],
|
|
76
|
+
authStorage: this.authStorage,
|
|
77
|
+
modelRegistry: this.modelRegistry,
|
|
78
|
+
model,
|
|
79
|
+
thinkingLevel: this.options.thinkingLevel ?? "off",
|
|
80
|
+
resourceLoader: createResourceLoader(input, mode, {
|
|
81
|
+
memory: memory !== undefined,
|
|
82
|
+
world: worldTools !== undefined
|
|
83
|
+
}),
|
|
84
|
+
tools: [...new Set(toolNames)],
|
|
85
|
+
customTools: worldTools === undefined ? memoryTools : [...memoryTools, ...worldTools],
|
|
86
|
+
sessionManager: SessionManager.create(input.workspacePath, sessionDirectory),
|
|
87
|
+
settingsManager: SettingsManager.inMemory({
|
|
88
|
+
compaction: { enabled: false },
|
|
89
|
+
retry: { enabled: true, maxRetries: 1 }
|
|
90
|
+
})
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
if (this.options.rawTrainingCapture !== undefined) {
|
|
94
|
+
const createSession = async (mode, sessionDirectory) => {
|
|
95
|
+
const { session } = await createAgentSession(sessionInput(mode, sessionDirectory));
|
|
96
|
+
if (rawTrainingCaptureRef !== undefined) {
|
|
97
|
+
bindPiRawTrainingCapture(session, rawTrainingCaptureRef);
|
|
98
|
+
}
|
|
99
|
+
return session;
|
|
100
|
+
};
|
|
101
|
+
const session = await createSession("awake", path.join(input.runtimeHomePath, "sessions"));
|
|
102
|
+
return new PiAgentHandle(input.id, session, createSession, input.runtimeHomePath, {
|
|
103
|
+
authMethod: modelSpec.auth?.method ?? "none",
|
|
104
|
+
model: resolvedModel.name,
|
|
105
|
+
provider: resolvedModel.provider
|
|
106
|
+
}, memory, memoryToolContext, {}, worldToolContext, rawTrainingCaptureRef, this.options.rawTrainingCapture, worldToolContext === undefined
|
|
107
|
+
? undefined
|
|
108
|
+
: { instructions: input.instructions, thinkingLevel: this.options.thinkingLevel ?? "off" }, session);
|
|
109
|
+
}
|
|
110
|
+
const createSession = async (mode, sessionDirectory) => {
|
|
111
|
+
const { session } = await (this.options.sessionFactory ?? createAgentSession)(sessionInput(mode, sessionDirectory));
|
|
112
|
+
return session;
|
|
113
|
+
};
|
|
114
|
+
const session = await createSession("awake", path.join(input.runtimeHomePath, "sessions"));
|
|
115
|
+
return new PiAgentHandle(input.id, session, createSession, input.runtimeHomePath, {
|
|
116
|
+
authMethod: modelSpec.auth?.method ?? "none",
|
|
117
|
+
model: resolvedModel.name,
|
|
118
|
+
provider: resolvedModel.provider
|
|
119
|
+
}, memory, memoryToolContext, {}, worldToolContext, undefined, undefined, worldToolContext === undefined
|
|
120
|
+
? undefined
|
|
121
|
+
: {
|
|
122
|
+
instructions: input.instructions,
|
|
123
|
+
thinkingLevel: this.options.thinkingLevel ?? "off"
|
|
124
|
+
}, undefined);
|
|
283
125
|
}
|
|
284
126
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ResourceLoader } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type MemoryWakeMode } from "@noopolis/mneme";
|
|
3
|
+
import type { AgentStartInput, WakeEvent } from "../core/types.js";
|
|
4
|
+
export declare const formatWakePrompt: (event: WakeEvent) => string;
|
|
5
|
+
export declare const createResourceLoader: (input: AgentStartInput, mode: MemoryWakeMode, capabilities: Readonly<{
|
|
6
|
+
memory: boolean;
|
|
7
|
+
world: boolean;
|
|
8
|
+
}>) => ResourceLoader;
|
|
9
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/pi/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEnE,eAAO,MAAM,gBAAgB,GAAI,OAAO,SAAS,KAAG,MAKtC,CAAC;AAEf,eAAO,MAAM,oBAAoB,GAC/B,OAAO,eAAe,EACtB,MAAM,cAAc,EACpB,cAAc,QAAQ,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,KAC1D,cA+BF,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createExtensionRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getMemorySkillTextForMode } from "@noopolis/mneme";
|
|
3
|
+
export const formatWakePrompt = (event) => `Wake event:
|
|
4
|
+
- id: ${event.id}
|
|
5
|
+
- kind: ${event.kind}
|
|
6
|
+
- from: ${event.from === undefined ? "[no attribution supplied] (absence)" : JSON.stringify(event.from)}
|
|
7
|
+
|
|
8
|
+
${event.text}`;
|
|
9
|
+
export const createResourceLoader = (input, mode, capabilities) => {
|
|
10
|
+
const systemPrompt = [
|
|
11
|
+
`You are ${input.name} (${input.id}).`,
|
|
12
|
+
input.instructions,
|
|
13
|
+
"You are running inside a harnessed workspace prepared by the caller.",
|
|
14
|
+
...(input.tools === undefined || input.tools.length > 0
|
|
15
|
+
? [
|
|
16
|
+
"Use the available coding tools when asked to read, write, edit, or inspect files.",
|
|
17
|
+
"Keep responses brief and report the exact files you created or modified."
|
|
18
|
+
]
|
|
19
|
+
: []),
|
|
20
|
+
...(capabilities.memory ? [getMemorySkillTextForMode(mode)] : []),
|
|
21
|
+
...(capabilities.world
|
|
22
|
+
? [
|
|
23
|
+
"Use only the authenticated world tools and standing instructions to perceive and act. "
|
|
24
|
+
+ "The harness binds wake authority and request identity; choose only the sense, affordance, target, and typed action input exposed by tool schemas."
|
|
25
|
+
]
|
|
26
|
+
: [])
|
|
27
|
+
].filter((section) => section.length > 0).join("\n\n");
|
|
28
|
+
return {
|
|
29
|
+
getExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),
|
|
30
|
+
getSkills: () => ({ skills: [], diagnostics: [] }),
|
|
31
|
+
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
|
32
|
+
getThemes: () => ({ themes: [], diagnostics: [] }),
|
|
33
|
+
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
34
|
+
getSystemPrompt: () => systemPrompt,
|
|
35
|
+
getAppendSystemPrompt: () => [],
|
|
36
|
+
extendResources: () => { },
|
|
37
|
+
reload: async () => { }
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { createAgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { PiWorldTurnContext } from "./worldNudge.js";
|
|
3
|
+
export declare const PI_RAW_TRAINING_CAPTURE_SCHEMA: "daimon.pi.raw_training_capture.v2";
|
|
4
|
+
export interface PiRawTrainingCaptureOptions {
|
|
5
|
+
enabled: true;
|
|
6
|
+
retention: {
|
|
7
|
+
maxTurns: number;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface PiRawTrainingCapture {
|
|
11
|
+
readonly events: string[];
|
|
12
|
+
readonly requests: Array<{
|
|
13
|
+
model: unknown;
|
|
14
|
+
payload: unknown;
|
|
15
|
+
requested_at: string;
|
|
16
|
+
response?: unknown;
|
|
17
|
+
response_at?: string;
|
|
18
|
+
sequence: number;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export interface PiRawTrainingCaptureRef {
|
|
22
|
+
current?: PiRawTrainingCapture;
|
|
23
|
+
}
|
|
24
|
+
type PiNativeSession = Awaited<ReturnType<typeof createAgentSession>>["session"];
|
|
25
|
+
type PiRawTrainingSession = Pick<PiNativeSession, "agent" | "model" | "sessionFile" | "sessionId" | "thinkingLevel">;
|
|
26
|
+
export interface PersistPiRawTrainingCaptureInput {
|
|
27
|
+
agentId: string;
|
|
28
|
+
capture: PiRawTrainingCapture;
|
|
29
|
+
completedAt: Date;
|
|
30
|
+
options: PiRawTrainingCaptureOptions;
|
|
31
|
+
runtimeHomePath: string;
|
|
32
|
+
session: PiRawTrainingSession;
|
|
33
|
+
startedAt: Date;
|
|
34
|
+
status: "completed" | "failed";
|
|
35
|
+
totalMs: number;
|
|
36
|
+
turnId: string;
|
|
37
|
+
world?: PiWorldTurnContext;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Installs a transparent recorder at Pi's native provider-payload seam.
|
|
41
|
+
*
|
|
42
|
+
* The recorder returns the exact result of any pre-existing hook, so enabling
|
|
43
|
+
* capture cannot alter the request sent to the model.
|
|
44
|
+
*/
|
|
45
|
+
export declare const bindPiRawTrainingCapture: (session: PiRawTrainingSession, ref: PiRawTrainingCaptureRef) => void;
|
|
46
|
+
export declare const createPiRawTrainingCapture: () => PiRawTrainingCapture;
|
|
47
|
+
export declare const capturePiRawTrainingEvent: (capture: PiRawTrainingCapture, event: unknown, now?: Date) => void;
|
|
48
|
+
export declare const validatePiRawTrainingCaptureOptions: (options: PiRawTrainingCaptureOptions | undefined) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Persists a deliberately private, unredacted training artifact.
|
|
51
|
+
*
|
|
52
|
+
* `pi-session.jsonl` is copied byte-for-byte from Pi's SessionManager. The
|
|
53
|
+
* request payloads come from Pi AI's `onPayload` seam after any prior payload
|
|
54
|
+
* transform, so they contain the exact structured provider input, including
|
|
55
|
+
* system/character prompts, messages, tool schemas, and sampling fields.
|
|
56
|
+
*/
|
|
57
|
+
export declare const persistPiRawTrainingCapture: (input: PersistPiRawTrainingCaptureInput) => Promise<string>;
|
|
58
|
+
export {};
|
|
59
|
+
//# sourceMappingURL=rawTrainingCapture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rawTrainingCapture.d.ts","sourceRoot":"","sources":["../../src/pi/rawTrainingCapture.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,eAAO,MAAM,8BAA8B,EACzC,mCAA4C,CAAC;AAE/C,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;QACvB,KAAK,EAAE,OAAO,CAAC;QACf,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,KAAK,eAAe,GAClB,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAC5D,KAAK,oBAAoB,GAAG,IAAI,CAC9B,eAAe,EACf,OAAO,GAAG,OAAO,GAAG,aAAa,GAAG,WAAW,GAAG,eAAe,CAClE,CAAC;AAEF,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,WAAW,EAAE,IAAI,CAAC;IAClB,OAAO,EAAE,2BAA2B,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAMD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS,oBAAoB,EAC7B,KAAK,uBAAuB,KAC3B,IA4BF,CAAC;AAEF,eAAO,MAAM,0BAA0B,QAAO,oBAG5C,CAAC;AAEH,eAAO,MAAM,yBAAyB,GACpC,SAAS,oBAAoB,EAC7B,OAAO,OAAO,EACd,UAAgB,KACf,IAKF,CAAC;AAEF,eAAO,MAAM,mCAAmC,GAC9C,SAAS,2BAA2B,GAAG,SAAS,KAC/C,IAUF,CAAC;AAaF;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACtC,OAAO,gCAAgC,KACtC,OAAO,CAAC,MAAM,CAoIhB,CAAC"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { chmod, readFile, readdir, rename, rm, mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { sanitizeTraceFileId } from "./turnTrace.js";
|
|
5
|
+
export const PI_RAW_TRAINING_CAPTURE_SCHEMA = "daimon.pi.raw_training_capture.v2";
|
|
6
|
+
const json = (value) => JSON.stringify(value);
|
|
7
|
+
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
8
|
+
/**
|
|
9
|
+
* Installs a transparent recorder at Pi's native provider-payload seam.
|
|
10
|
+
*
|
|
11
|
+
* The recorder returns the exact result of any pre-existing hook, so enabling
|
|
12
|
+
* capture cannot alter the request sent to the model.
|
|
13
|
+
*/
|
|
14
|
+
export const bindPiRawTrainingCapture = (session, ref) => {
|
|
15
|
+
const previousPayload = session.agent.onPayload;
|
|
16
|
+
const previousResponse = session.agent.onResponse;
|
|
17
|
+
session.agent.onPayload = async (payload, model) => {
|
|
18
|
+
const transformed = await previousPayload?.(payload, model);
|
|
19
|
+
const effectivePayload = transformed === undefined ? payload : transformed;
|
|
20
|
+
const capture = ref.current;
|
|
21
|
+
if (capture !== undefined) {
|
|
22
|
+
capture.requests.push({
|
|
23
|
+
model: structuredClone(model),
|
|
24
|
+
payload: structuredClone(effectivePayload),
|
|
25
|
+
requested_at: new Date().toISOString(),
|
|
26
|
+
sequence: capture.requests.length
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return transformed;
|
|
30
|
+
};
|
|
31
|
+
session.agent.onResponse = async (response, model) => {
|
|
32
|
+
await previousResponse?.(response, model);
|
|
33
|
+
const capture = ref.current;
|
|
34
|
+
const request = capture?.requests.at(-1);
|
|
35
|
+
if (request !== undefined) {
|
|
36
|
+
request.response = structuredClone(response);
|
|
37
|
+
request.response_at = new Date().toISOString();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export const createPiRawTrainingCapture = () => ({
|
|
42
|
+
events: [],
|
|
43
|
+
requests: []
|
|
44
|
+
});
|
|
45
|
+
export const capturePiRawTrainingEvent = (capture, event, now = new Date()) => {
|
|
46
|
+
// Serialize at event time so later mutation cannot change the captured event.
|
|
47
|
+
capture.events.push(`{"recorded_at":${json(now.toISOString())},"event":${json(event)}}`);
|
|
48
|
+
};
|
|
49
|
+
export const validatePiRawTrainingCaptureOptions = (options) => {
|
|
50
|
+
if (options === undefined)
|
|
51
|
+
return;
|
|
52
|
+
if (options.enabled !== true
|
|
53
|
+
|| !Number.isSafeInteger(options.retention.maxTurns)
|
|
54
|
+
|| options.retention.maxTurns < 1
|
|
55
|
+
|| options.retention.maxTurns > 100_000) {
|
|
56
|
+
throw new Error("Pi raw training capture requires enabled: true and retention.maxTurns between 1 and 100000");
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const pruneTurns = async (turnsPath, maxTurns) => {
|
|
60
|
+
const names = (await readdir(turnsPath, { withFileTypes: true }))
|
|
61
|
+
.filter((entry) => entry.isDirectory())
|
|
62
|
+
.map((entry) => entry.name)
|
|
63
|
+
.sort();
|
|
64
|
+
await Promise.all(names.slice(0, Math.max(0, names.length - maxTurns))
|
|
65
|
+
.map((name) => rm(path.join(turnsPath, name), { force: true, recursive: true })));
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Persists a deliberately private, unredacted training artifact.
|
|
69
|
+
*
|
|
70
|
+
* `pi-session.jsonl` is copied byte-for-byte from Pi's SessionManager. The
|
|
71
|
+
* request payloads come from Pi AI's `onPayload` seam after any prior payload
|
|
72
|
+
* transform, so they contain the exact structured provider input, including
|
|
73
|
+
* system/character prompts, messages, tool schemas, and sampling fields.
|
|
74
|
+
*/
|
|
75
|
+
export const persistPiRawTrainingCapture = async (input) => {
|
|
76
|
+
validatePiRawTrainingCaptureOptions(input.options);
|
|
77
|
+
if (input.session.sessionFile === undefined) {
|
|
78
|
+
throw new Error("Pi raw training capture requires a persisted native session");
|
|
79
|
+
}
|
|
80
|
+
const nativeSessionBytes = await readFile(input.session.sessionFile);
|
|
81
|
+
const privateTrainingPath = path.join(input.runtimeHomePath, "private-training");
|
|
82
|
+
const piPath = path.join(privateTrainingPath, "pi");
|
|
83
|
+
const root = path.join(piPath, "raw");
|
|
84
|
+
const turnsPath = path.join(root, "turns");
|
|
85
|
+
const turnPath = path.join(turnsPath, `${String(input.startedAt.getTime()).padStart(13, "0")}-${sanitizeTraceFileId(input.turnId)}`);
|
|
86
|
+
const partialTurnPath = path.join(turnsPath, `.partial-${path.basename(turnPath)}-${randomUUID()}`);
|
|
87
|
+
await mkdir(turnsPath, { mode: 0o700, recursive: true });
|
|
88
|
+
await Promise.all([privateTrainingPath, piPath, root, turnsPath]
|
|
89
|
+
.map((directory) => chmod(directory, 0o700)));
|
|
90
|
+
await mkdir(partialTurnPath, { mode: 0o700 });
|
|
91
|
+
const providerExchange = {
|
|
92
|
+
model: structuredClone(input.session.model),
|
|
93
|
+
requests: input.capture.requests,
|
|
94
|
+
session_id: input.session.sessionId,
|
|
95
|
+
thinking_level: input.session.thinkingLevel
|
|
96
|
+
};
|
|
97
|
+
const eventsBytes = Buffer.from(input.capture.events.length === 0 ? "" : `${input.capture.events.join("\n")}\n`, "utf8");
|
|
98
|
+
const providerExchangeBytes = Buffer.from(`${JSON.stringify(providerExchange, null, 2)}\n`, "utf8");
|
|
99
|
+
const manifest = {
|
|
100
|
+
access: {
|
|
101
|
+
classification: "private_raw_training",
|
|
102
|
+
export_by_default: false,
|
|
103
|
+
contains_unredacted_model_context: true
|
|
104
|
+
},
|
|
105
|
+
agent_id: input.agentId,
|
|
106
|
+
completed_at: input.completedAt.toISOString(),
|
|
107
|
+
files: {
|
|
108
|
+
events: "events.ndjson",
|
|
109
|
+
native_pi_session: "pi-session.jsonl",
|
|
110
|
+
provider_exchange: "provider-exchange.json"
|
|
111
|
+
},
|
|
112
|
+
integrity: {
|
|
113
|
+
capture_boundary: "post_turn",
|
|
114
|
+
files: {
|
|
115
|
+
events: {
|
|
116
|
+
bytes: eventsBytes.byteLength,
|
|
117
|
+
records: input.capture.events.length,
|
|
118
|
+
sha256: sha256(eventsBytes)
|
|
119
|
+
},
|
|
120
|
+
native_pi_session: {
|
|
121
|
+
bytes: nativeSessionBytes.byteLength,
|
|
122
|
+
sha256: sha256(nativeSessionBytes)
|
|
123
|
+
},
|
|
124
|
+
provider_exchange: {
|
|
125
|
+
bytes: providerExchangeBytes.byteLength,
|
|
126
|
+
requests: input.capture.requests.length,
|
|
127
|
+
sha256: sha256(providerExchangeBytes)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
join: input.world === undefined ? undefined : {
|
|
132
|
+
run_id: input.world.runId,
|
|
133
|
+
tick: input.world.tick,
|
|
134
|
+
wake_id: input.world.wakeId
|
|
135
|
+
},
|
|
136
|
+
native_session: {
|
|
137
|
+
id: input.session.sessionId
|
|
138
|
+
},
|
|
139
|
+
retention: {
|
|
140
|
+
max_turns: input.options.retention.maxTurns
|
|
141
|
+
},
|
|
142
|
+
schema: PI_RAW_TRAINING_CAPTURE_SCHEMA,
|
|
143
|
+
started_at: input.startedAt.toISOString(),
|
|
144
|
+
terminal_status: input.status,
|
|
145
|
+
timings_ms: {
|
|
146
|
+
total: input.totalMs
|
|
147
|
+
},
|
|
148
|
+
turn_id: input.turnId
|
|
149
|
+
};
|
|
150
|
+
const files = [
|
|
151
|
+
"manifest.json",
|
|
152
|
+
"provider-exchange.json",
|
|
153
|
+
"events.ndjson",
|
|
154
|
+
"pi-session.jsonl"
|
|
155
|
+
];
|
|
156
|
+
try {
|
|
157
|
+
await Promise.all([
|
|
158
|
+
writeFile(path.join(partialTurnPath, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, { encoding: "utf8", mode: 0o600 }),
|
|
159
|
+
writeFile(path.join(partialTurnPath, "provider-exchange.json"), providerExchangeBytes, { mode: 0o600 }),
|
|
160
|
+
writeFile(path.join(partialTurnPath, "events.ndjson"), eventsBytes, { mode: 0o600 }),
|
|
161
|
+
writeFile(path.join(partialTurnPath, "pi-session.jsonl"), nativeSessionBytes, { mode: 0o600 })
|
|
162
|
+
]);
|
|
163
|
+
await Promise.all([
|
|
164
|
+
chmod(partialTurnPath, 0o700),
|
|
165
|
+
...files.map((name) => chmod(path.join(partialTurnPath, name), 0o600))
|
|
166
|
+
]);
|
|
167
|
+
await rename(partialTurnPath, turnPath);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
await rm(partialTurnPath, { force: true, recursive: true });
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
await pruneTurns(turnsPath, input.options.retention.maxTurns);
|
|
174
|
+
return turnPath;
|
|
175
|
+
};
|