@noopolis/daimon 0.1.0 → 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 +141 -5
- 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 +20 -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.d.ts +2 -0
- package/dist/examples/jungian-play-org.d.ts.map +1 -0
- package/dist/examples/jungian-play-org.js +281 -0
- package/dist/examples/jungian-triad-org.d.ts +2 -0
- package/dist/examples/jungian-triad-org.d.ts.map +1 -0
- package/dist/examples/jungian-triad-org.js +281 -0
- package/dist/examples/jungianConversationPlan.d.ts +10 -0
- package/dist/examples/jungianConversationPlan.d.ts.map +1 -0
- package/dist/examples/jungianConversationPlan.js +64 -0
- package/dist/examples/jungianPiRepresentative.d.ts +36 -0
- package/dist/examples/jungianPiRepresentative.d.ts.map +1 -0
- package/dist/examples/jungianPiRepresentative.js +81 -0
- package/dist/examples/jungianPlayAgent.d.ts +36 -0
- package/dist/examples/jungianPlayAgent.d.ts.map +1 -0
- package/dist/examples/jungianPlayAgent.js +111 -0
- package/dist/examples/jungianProfiles.d.ts +34 -0
- package/dist/examples/jungianProfiles.d.ts.map +1 -0
- package/dist/examples/jungianProfiles.js +190 -0
- package/dist/examples/jungianTrace.d.ts +47 -0
- package/dist/examples/jungianTrace.d.ts.map +1 -0
- package/dist/examples/jungianTrace.js +101 -0
- package/dist/examples/jungianTriadProfiles.d.ts +35 -0
- package/dist/examples/jungianTriadProfiles.d.ts.map +1 -0
- package/dist/examples/jungianTriadProfiles.js +81 -0
- package/dist/examples/pi-agent.js +3 -2
- package/dist/examples/pi-memory-org.d.ts +2 -0
- package/dist/examples/pi-memory-org.d.ts.map +1 -0
- package/dist/examples/pi-memory-org.js +231 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- 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 +4 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +3 -0
- package/dist/observability/orgObserver.d.ts +98 -0
- package/dist/observability/orgObserver.d.ts.map +1 -0
- package/dist/observability/orgObserver.js +181 -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 +9 -0
- package/dist/pi/index.d.ts.map +1 -0
- package/dist/pi/index.js +8 -0
- package/dist/pi/memoryTools.d.ts +69 -0
- package/dist/pi/memoryTools.d.ts.map +1 -0
- package/dist/pi/memoryTools.js +190 -0
- 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 +36 -2
- package/dist/pi/piHarness.d.ts.map +1 -1
- package/dist/pi/piHarness.js +100 -127
- 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 +23 -4
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { types } from "node:util";
|
|
2
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
3
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { createWorldClaimRequestBody, createWorldRequestBody, parseWorldClaimResponse } from "./worldToolProtocol.js";
|
|
5
|
+
export const PI_WORLD_TOOL_NAMES = Object.freeze([
|
|
6
|
+
"world_claim",
|
|
7
|
+
"world_status",
|
|
8
|
+
"world_capabilities",
|
|
9
|
+
"world_observe",
|
|
10
|
+
"world_affordances",
|
|
11
|
+
"world_act",
|
|
12
|
+
"world_ledger"
|
|
13
|
+
]);
|
|
14
|
+
export const PI_WORLD_TOOL_LIMITS = Object.freeze({
|
|
15
|
+
requestBytes: 64 * 1024,
|
|
16
|
+
responseBytes: 1024 * 1024,
|
|
17
|
+
timeoutMs: 5_000
|
|
18
|
+
});
|
|
19
|
+
export { WORLD_ACTION_RESULT_PAGE_REQUEST_VERSION } from "./worldToolProtocol.js";
|
|
20
|
+
const ERROR_MESSAGES = Object.freeze({
|
|
21
|
+
world_auth_unavailable: "World tool authentication is unavailable.",
|
|
22
|
+
world_request_cancelled: "World tool request was cancelled.",
|
|
23
|
+
world_request_denied: "World tool request was denied.",
|
|
24
|
+
world_request_invalid: "World tool request is invalid.",
|
|
25
|
+
world_request_rejected: "World tool request was rejected.",
|
|
26
|
+
world_request_timeout: "World tool request timed out.",
|
|
27
|
+
world_response_invalid: "World tool returned an invalid response.",
|
|
28
|
+
world_transport_unavailable: "World tool transport is unavailable."
|
|
29
|
+
});
|
|
30
|
+
export class PiWorldToolError extends Error {
|
|
31
|
+
code;
|
|
32
|
+
constructor(code) {
|
|
33
|
+
super(ERROR_MESSAGES[code]);
|
|
34
|
+
this.name = "PiWorldToolError";
|
|
35
|
+
this.code = code;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
class BodyReadCancelled extends Error {
|
|
39
|
+
}
|
|
40
|
+
class RequestInterrupted extends Error {
|
|
41
|
+
}
|
|
42
|
+
const UTF8 = new TextEncoder();
|
|
43
|
+
const fail = (code) => { throw new PiWorldToolError(code); };
|
|
44
|
+
const text = (value, maximum = 256) => typeof value === "string"
|
|
45
|
+
&& value.length > 0 && value.length <= maximum && value === value.trim();
|
|
46
|
+
const token = (value) => text(value, 1_024)
|
|
47
|
+
&& /^[A-Za-z0-9._~+\/-]+={0,2}$/u.test(value);
|
|
48
|
+
const binding = (value) => {
|
|
49
|
+
try {
|
|
50
|
+
if (value === null || typeof value !== "object" || types.isProxy(value)
|
|
51
|
+
|| Object.getPrototypeOf(value) !== Object.prototype)
|
|
52
|
+
return undefined;
|
|
53
|
+
const keys = Reflect.ownKeys(value);
|
|
54
|
+
if (keys.length !== 2 || !keys.includes("url") || !keys.includes("tokenEnv"))
|
|
55
|
+
return undefined;
|
|
56
|
+
const url = Object.getOwnPropertyDescriptor(value, "url");
|
|
57
|
+
const tokenEnv = Object.getOwnPropertyDescriptor(value, "tokenEnv");
|
|
58
|
+
if (!url?.enumerable || !("value" in url) || !tokenEnv?.enumerable || !("value" in tokenEnv)
|
|
59
|
+
|| typeof url.value !== "string" || url.value !== url.value.trim() || url.value.length > 2_048
|
|
60
|
+
|| url.value.includes("?") || url.value.includes("#")
|
|
61
|
+
|| typeof tokenEnv.value !== "string" || !/^[A-Z_][A-Z0-9_]{0,127}$/u.test(tokenEnv.value))
|
|
62
|
+
return undefined;
|
|
63
|
+
const parsed = new URL(url.value);
|
|
64
|
+
if (parsed.href !== url.value || (parsed.protocol !== "http:" && parsed.protocol !== "https:") || parsed.username !== ""
|
|
65
|
+
|| parsed.password !== "" || parsed.search !== "" || parsed.hash !== ""
|
|
66
|
+
|| !parsed.pathname.endsWith("/v1/world") || parsed.pathname.endsWith("/"))
|
|
67
|
+
return undefined;
|
|
68
|
+
return Object.freeze({ url: url.value, tokenEnv: tokenEnv.value });
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const result = (details, secrets) => {
|
|
75
|
+
const pending = [details];
|
|
76
|
+
while (pending.length > 0) {
|
|
77
|
+
const value = pending.pop();
|
|
78
|
+
if (typeof value === "string") {
|
|
79
|
+
if (secrets.some((secret) => value.includes(secret)))
|
|
80
|
+
return fail("world_response_invalid");
|
|
81
|
+
}
|
|
82
|
+
else if (Array.isArray(value)) {
|
|
83
|
+
pending.push(...value);
|
|
84
|
+
}
|
|
85
|
+
else if (value !== null && typeof value === "object") {
|
|
86
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
87
|
+
if (secrets.some((secret) => key.includes(secret)))
|
|
88
|
+
return fail("world_response_invalid");
|
|
89
|
+
pending.push(nested);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
let serialized;
|
|
94
|
+
try {
|
|
95
|
+
serialized = JSON.stringify(details);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return fail("world_response_invalid");
|
|
99
|
+
}
|
|
100
|
+
if (secrets.some((secret) => serialized.includes(secret)))
|
|
101
|
+
return fail("world_response_invalid");
|
|
102
|
+
return { content: [{ type: "text", text: serialized }], details };
|
|
103
|
+
};
|
|
104
|
+
const serialize = (value) => {
|
|
105
|
+
try {
|
|
106
|
+
const output = JSON.stringify(value);
|
|
107
|
+
if (output === undefined || UTF8.encode(output).byteLength > PI_WORLD_TOOL_LIMITS.requestBytes)
|
|
108
|
+
return fail("world_request_invalid");
|
|
109
|
+
return output;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return fail("world_request_invalid");
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const fetchResponse = async (fetchWorld, url, init, signal) => {
|
|
116
|
+
if (signal.aborted)
|
|
117
|
+
throw new RequestInterrupted();
|
|
118
|
+
let interrupted;
|
|
119
|
+
const interruption = new Promise((_resolve, reject) => {
|
|
120
|
+
interrupted = () => reject(new RequestInterrupted());
|
|
121
|
+
signal.addEventListener("abort", interrupted, { once: true });
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
return await Promise.race([fetchWorld(url, init), interruption]);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
if (interrupted !== undefined)
|
|
128
|
+
signal.removeEventListener("abort", interrupted);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const readChunk = async (reader, signal) => {
|
|
132
|
+
if (signal.aborted)
|
|
133
|
+
throw new BodyReadCancelled();
|
|
134
|
+
let cancelled;
|
|
135
|
+
const interruption = new Promise((_resolve, reject) => {
|
|
136
|
+
cancelled = () => reject(new BodyReadCancelled());
|
|
137
|
+
signal.addEventListener("abort", cancelled, { once: true });
|
|
138
|
+
});
|
|
139
|
+
try {
|
|
140
|
+
return await Promise.race([reader.read(), interruption]);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
if (cancelled !== undefined)
|
|
144
|
+
signal.removeEventListener("abort", cancelled);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const readResponse = async (response, signal, maximum) => {
|
|
148
|
+
const contentType = response.headers.get("content-type")?.toLowerCase();
|
|
149
|
+
const contentLength = response.headers.get("content-length");
|
|
150
|
+
if (contentType === undefined || contentType.split(";", 1)[0]?.trim() !== "application/json"
|
|
151
|
+
|| contentLength !== null && (!/^(?:0|[1-9][0-9]*)$/u.test(contentLength) || Number(contentLength) > maximum)
|
|
152
|
+
|| response.body === null)
|
|
153
|
+
return fail("world_response_invalid");
|
|
154
|
+
const reader = response.body.getReader();
|
|
155
|
+
const chunks = [];
|
|
156
|
+
let length = 0;
|
|
157
|
+
try {
|
|
158
|
+
while (true) {
|
|
159
|
+
const next = await readChunk(reader, signal);
|
|
160
|
+
if (next.done)
|
|
161
|
+
break;
|
|
162
|
+
length += next.value.byteLength;
|
|
163
|
+
if (length > maximum)
|
|
164
|
+
return fail("world_response_invalid");
|
|
165
|
+
chunks.push(next.value.slice());
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error instanceof BodyReadCancelled)
|
|
170
|
+
throw error;
|
|
171
|
+
return fail("world_response_invalid");
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
const release = () => {
|
|
175
|
+
try {
|
|
176
|
+
reader.releaseLock();
|
|
177
|
+
}
|
|
178
|
+
catch { /* A failed read must not mask the fixed error. */ }
|
|
179
|
+
};
|
|
180
|
+
if (length > maximum || signal.aborted) {
|
|
181
|
+
try {
|
|
182
|
+
void reader.cancel().then(release, release);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
release();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else
|
|
189
|
+
release();
|
|
190
|
+
}
|
|
191
|
+
const bytes = new Uint8Array(length);
|
|
192
|
+
let offset = 0;
|
|
193
|
+
for (const chunk of chunks) {
|
|
194
|
+
bytes.set(chunk, offset);
|
|
195
|
+
offset += chunk.byteLength;
|
|
196
|
+
}
|
|
197
|
+
let parsed;
|
|
198
|
+
try {
|
|
199
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return fail("world_response_invalid");
|
|
203
|
+
}
|
|
204
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
205
|
+
return fail("world_response_invalid");
|
|
206
|
+
return parsed;
|
|
207
|
+
};
|
|
208
|
+
const cancelBody = (response) => {
|
|
209
|
+
try {
|
|
210
|
+
const body = response.body;
|
|
211
|
+
if (body !== null)
|
|
212
|
+
void body.cancel().catch(() => { });
|
|
213
|
+
}
|
|
214
|
+
catch { /* Never surface response diagnostics. */ }
|
|
215
|
+
};
|
|
216
|
+
const boundSchemas = Object.freeze({
|
|
217
|
+
claim: Type.Object({}, { additionalProperties: false }),
|
|
218
|
+
status: Type.Object({}, { additionalProperties: false }),
|
|
219
|
+
capabilities: Type.Object({}, { additionalProperties: false }),
|
|
220
|
+
observe: Type.Object({
|
|
221
|
+
sense: Type.String({ description: "Granted world sense address." })
|
|
222
|
+
}, { additionalProperties: false }),
|
|
223
|
+
affordances: Type.Object({}, { additionalProperties: false }),
|
|
224
|
+
act: Type.Object({
|
|
225
|
+
affordance: Type.String({ description: "Granted world affordance address." }),
|
|
226
|
+
target: Type.String({ description: "World target entity address." }),
|
|
227
|
+
input: Type.Unknown({ description: "Typed input declared by the selected affordance." })
|
|
228
|
+
}, { additionalProperties: false }),
|
|
229
|
+
ledger: Type.Object({
|
|
230
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
|
|
231
|
+
result_after: Type.Optional(Type.Unknown({ description: "Opaque result cursor returned by a previous ledger call." }))
|
|
232
|
+
}, { additionalProperties: false })
|
|
233
|
+
});
|
|
234
|
+
const descriptors = Object.freeze([
|
|
235
|
+
{ name: "world_claim", operation: "claim", label: "Claim world authority", description: "Privately bind world authority to this organization-owned wake." },
|
|
236
|
+
{ name: "world_status", operation: "status", label: "World status", description: "Read authenticated world orientation and decision status." },
|
|
237
|
+
{ name: "world_capabilities", operation: "capabilities", label: "World capabilities", description: "Read the authenticated caller's world capability manifest." },
|
|
238
|
+
{ name: "world_observe", operation: "observe", label: "Observe world", description: "Invoke one granted world sense against current state." },
|
|
239
|
+
{ name: "world_affordances", operation: "affordances", label: "World affordances", description: "List currently available granted world actions." },
|
|
240
|
+
{ name: "world_act", operation: "act", label: "Act in world", description: "Attempt one world affordance with a stable request id." },
|
|
241
|
+
{ name: "world_ledger", operation: "ledger", label: "World ledger", description: "Read authenticated terminal action results." }
|
|
242
|
+
]);
|
|
243
|
+
export const createPiWorldTools = (input) => {
|
|
244
|
+
const world = binding(input.world);
|
|
245
|
+
const timeoutMs = input.timeoutMs ?? PI_WORLD_TOOL_LIMITS.timeoutMs;
|
|
246
|
+
const maximum = input.maxResponseBytes ?? PI_WORLD_TOOL_LIMITS.responseBytes;
|
|
247
|
+
const fetchWorld = input.fetch ?? globalThis.fetch;
|
|
248
|
+
const readEnvironment = input.readEnvironment ?? ((name) => process.env[name]);
|
|
249
|
+
if (world === undefined || typeof fetchWorld !== "function" || typeof readEnvironment !== "function"
|
|
250
|
+
|| !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 30_000
|
|
251
|
+
|| !Number.isSafeInteger(maximum) || maximum < 128 || maximum > PI_WORLD_TOOL_LIMITS.responseBytes) {
|
|
252
|
+
throw new TypeError("invalid Pi world tool configuration");
|
|
253
|
+
}
|
|
254
|
+
const available = input.contextRef === undefined
|
|
255
|
+
? descriptors.filter((descriptor) => descriptor.operation !== "claim")
|
|
256
|
+
: descriptors;
|
|
257
|
+
return available.map((descriptor) => defineTool({
|
|
258
|
+
name: descriptor.name,
|
|
259
|
+
label: descriptor.label,
|
|
260
|
+
description: descriptor.description,
|
|
261
|
+
promptSnippet: descriptor.description,
|
|
262
|
+
promptGuidelines: ["Treat world tool values as scoped current state; never invent caller identity or world authority fields."],
|
|
263
|
+
parameters: boundSchemas[descriptor.operation],
|
|
264
|
+
async execute(_toolCallId, params, callerSignal) {
|
|
265
|
+
if (callerSignal?.aborted)
|
|
266
|
+
return fail("world_request_cancelled");
|
|
267
|
+
if (input.contextRef === undefined)
|
|
268
|
+
return fail("world_request_denied");
|
|
269
|
+
let bearer;
|
|
270
|
+
try {
|
|
271
|
+
bearer = readEnvironment(world.tokenEnv);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return fail("world_auth_unavailable");
|
|
275
|
+
}
|
|
276
|
+
if (!token(bearer))
|
|
277
|
+
return fail("world_auth_unavailable");
|
|
278
|
+
const serialized = serialize(descriptor.operation === "claim"
|
|
279
|
+
? createWorldClaimRequestBody(input.contextRef.current) ?? fail("world_request_invalid")
|
|
280
|
+
: createWorldRequestBody(descriptor.operation, params, input.contextRef.current)
|
|
281
|
+
?? fail("world_request_invalid"));
|
|
282
|
+
const controller = new AbortController();
|
|
283
|
+
let timedOut = false;
|
|
284
|
+
const cancel = () => controller.abort();
|
|
285
|
+
if (callerSignal !== undefined)
|
|
286
|
+
callerSignal.addEventListener("abort", cancel, { once: true });
|
|
287
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs);
|
|
288
|
+
try {
|
|
289
|
+
let response;
|
|
290
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
291
|
+
if (callerSignal?.aborted)
|
|
292
|
+
return fail("world_request_cancelled");
|
|
293
|
+
if (controller.signal.aborted)
|
|
294
|
+
return fail(timedOut ? "world_request_timeout" : "world_request_cancelled");
|
|
295
|
+
try {
|
|
296
|
+
response = await fetchResponse(fetchWorld, `${world.url}/${descriptor.operation}`, {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
299
|
+
body: serialized,
|
|
300
|
+
signal: controller.signal
|
|
301
|
+
}, controller.signal);
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (callerSignal?.aborted)
|
|
305
|
+
return fail("world_request_cancelled");
|
|
306
|
+
if (controller.signal.aborted)
|
|
307
|
+
return fail(timedOut ? "world_request_timeout" : "world_request_cancelled");
|
|
308
|
+
if (descriptor.operation !== "act" || !(error instanceof TypeError) || attempt === 1) {
|
|
309
|
+
return fail("world_transport_unavailable");
|
|
310
|
+
}
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
let status;
|
|
314
|
+
try {
|
|
315
|
+
status = response.status;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return fail("world_response_invalid");
|
|
319
|
+
}
|
|
320
|
+
if (descriptor.operation === "act" && status === 408 && attempt === 0) {
|
|
321
|
+
cancelBody(response);
|
|
322
|
+
response = undefined;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
if (response === undefined)
|
|
328
|
+
return fail("world_transport_unavailable");
|
|
329
|
+
try {
|
|
330
|
+
const status = response.status;
|
|
331
|
+
if (!response.ok) {
|
|
332
|
+
cancelBody(response);
|
|
333
|
+
if (status === 401 || status === 403)
|
|
334
|
+
return fail("world_request_denied");
|
|
335
|
+
return fail("world_request_rejected");
|
|
336
|
+
}
|
|
337
|
+
const details = await readResponse(response, controller.signal, maximum);
|
|
338
|
+
if (descriptor.operation === "claim") {
|
|
339
|
+
if (input.contextRef === undefined || input.contextRef.current === undefined) {
|
|
340
|
+
return fail("world_response_invalid");
|
|
341
|
+
}
|
|
342
|
+
const claimed = parseWorldClaimResponse(details) ?? fail("world_response_invalid");
|
|
343
|
+
input.contextRef.current = Object.freeze({
|
|
344
|
+
...input.contextRef.current,
|
|
345
|
+
decisionToken: claimed.decisionToken,
|
|
346
|
+
tick: claimed.issuedAtTick,
|
|
347
|
+
});
|
|
348
|
+
return result({
|
|
349
|
+
claimed: true,
|
|
350
|
+
issued_at_tick: claimed.issuedAtTick,
|
|
351
|
+
valid_through_tick: claimed.validThroughTick,
|
|
352
|
+
}, [bearer, claimed.decisionToken]);
|
|
353
|
+
}
|
|
354
|
+
return result(details, [bearer, input.contextRef.current?.decisionToken ?? ""]
|
|
355
|
+
.filter((value) => value.length > 0));
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
if (error instanceof BodyReadCancelled)
|
|
359
|
+
return fail(timedOut ? "world_request_timeout" : "world_request_cancelled");
|
|
360
|
+
if (error instanceof PiWorldToolError)
|
|
361
|
+
throw error;
|
|
362
|
+
return fail("world_response_invalid");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
finally {
|
|
366
|
+
clearTimeout(timer);
|
|
367
|
+
if (callerSignal !== undefined)
|
|
368
|
+
callerSignal.removeEventListener("abort", cancel);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}));
|
|
372
|
+
};
|
|
373
|
+
export const piWorldToolNames = (tools) => tools.map((tool) => tool.name);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PiTurnTraceModel } from "./turnTrace.js";
|
|
2
|
+
import type { PiWorldTurnContext } from "./worldNudge.js";
|
|
3
|
+
export declare const WORLD_TRAJECTORY_SCHEMA: "daimon.world_trajectory.v1";
|
|
4
|
+
/**
|
|
5
|
+
* Pi's SessionManager remains the private raw session recorder. This module
|
|
6
|
+
* derives a minimized public/evaluation projection from the same subscribed
|
|
7
|
+
* session events. Raw training capture is deliberately separate; see
|
|
8
|
+
* docs/WORLD_TRAJECTORIES.md.
|
|
9
|
+
*/
|
|
10
|
+
export interface PiWorldTrajectoryToolCall {
|
|
11
|
+
arguments?: unknown;
|
|
12
|
+
completed_at?: string;
|
|
13
|
+
duration_ms?: number;
|
|
14
|
+
name: string;
|
|
15
|
+
result?: unknown;
|
|
16
|
+
sequence: number;
|
|
17
|
+
started_at?: string;
|
|
18
|
+
status: "running" | "completed" | "failed";
|
|
19
|
+
tool_call_id: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PiWorldTrajectoryCapture {
|
|
22
|
+
readonly calls: PiWorldTrajectoryToolCall[];
|
|
23
|
+
readonly starts: Map<string, number>;
|
|
24
|
+
}
|
|
25
|
+
export interface PiWorldTrajectoryIdentity {
|
|
26
|
+
readonly instructions: string;
|
|
27
|
+
readonly thinkingLevel: string;
|
|
28
|
+
}
|
|
29
|
+
export interface PersistPiWorldTrajectoryInput {
|
|
30
|
+
agentId: string;
|
|
31
|
+
capture: PiWorldTrajectoryCapture;
|
|
32
|
+
completedAt: Date;
|
|
33
|
+
context: PiWorldTurnContext;
|
|
34
|
+
instructions: string;
|
|
35
|
+
model: PiTurnTraceModel;
|
|
36
|
+
promptText: string;
|
|
37
|
+
runtimeHomePath: string;
|
|
38
|
+
startedAt: Date;
|
|
39
|
+
status: "completed" | "failed";
|
|
40
|
+
thinkingLevel: string;
|
|
41
|
+
totalMs: number;
|
|
42
|
+
turnId: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Keeps the scoped world projection exact while removing authority, hidden
|
|
46
|
+
* reasoning, private memory, credentials, and host diagnostics.
|
|
47
|
+
*/
|
|
48
|
+
export declare const redactWorldTrajectoryValue: (value: unknown, depth?: number) => unknown;
|
|
49
|
+
export declare const createPiWorldTrajectoryCapture: () => PiWorldTrajectoryCapture;
|
|
50
|
+
export declare const capturePiWorldTrajectoryEvent: (capture: PiWorldTrajectoryCapture, event: unknown, now?: Date) => void;
|
|
51
|
+
export declare const persistPiWorldTrajectory: (input: PersistPiWorldTrajectoryInput) => Promise<void>;
|
|
52
|
+
//# sourceMappingURL=worldTrajectory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worldTrajectory.d.ts","sourceRoot":"","sources":["../../src/pi/worldTrajectory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,eAAO,MAAM,uBAAuB,EAAG,4BAAqC,CAAC;AAE7E;;;;;GAKG;AAEH,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC3C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,EAAE,yBAAyB,EAAE,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,wBAAwB,CAAC;IAClC,WAAW,EAAE,IAAI,CAAC;IAClB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,gBAAgB,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GACrC,OAAO,OAAO,EACd,cAAS,KACR,OAsBF,CAAC;AAKF,eAAO,MAAM,8BAA8B,QAAO,wBAGhD,CAAC;AAEH,eAAO,MAAM,6BAA6B,GACxC,SAAS,wBAAwB,EACjC,OAAO,OAAO,EACd,UAAgB,KACf,IAqCF,CAAC;AAKF,eAAO,MAAM,wBAAwB,GACnC,OAAO,6BAA6B,KACnC,OAAO,CAAC,IAAI,CAkDd,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { appendFile, mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { redactTraceText, sanitizeTraceFileId } from "./turnTrace.js";
|
|
5
|
+
export const WORLD_TRAJECTORY_SCHEMA = "daimon.world_trajectory.v1";
|
|
6
|
+
const asObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
7
|
+
? value
|
|
8
|
+
: undefined;
|
|
9
|
+
const forbiddenKey = /(?:authorization|bearer|credential|memory|password|prompt|reasoning|secret|thinking|token)/iu;
|
|
10
|
+
const maxArrayItems = 256;
|
|
11
|
+
const maxObjectKeys = 256;
|
|
12
|
+
const maxStringChars = 32_768;
|
|
13
|
+
const maxDepth = 12;
|
|
14
|
+
/**
|
|
15
|
+
* Keeps the scoped world projection exact while removing authority, hidden
|
|
16
|
+
* reasoning, private memory, credentials, and host diagnostics.
|
|
17
|
+
*/
|
|
18
|
+
export const redactWorldTrajectoryValue = (value, depth = 0) => {
|
|
19
|
+
if (depth > maxDepth)
|
|
20
|
+
return "[TRUNCATED]";
|
|
21
|
+
if (value === null || typeof value === "boolean")
|
|
22
|
+
return value;
|
|
23
|
+
if (typeof value === "number")
|
|
24
|
+
return Number.isFinite(value) ? value : String(value);
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
const redacted = redactTraceText(value);
|
|
27
|
+
return redacted.length > maxStringChars
|
|
28
|
+
? `${redacted.slice(0, maxStringChars)}...[TRUNCATED]`
|
|
29
|
+
: redacted;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
return value.slice(0, maxArrayItems)
|
|
33
|
+
.map((entry) => redactWorldTrajectoryValue(entry, depth + 1));
|
|
34
|
+
}
|
|
35
|
+
const record = asObject(value);
|
|
36
|
+
if (record === undefined)
|
|
37
|
+
return String(value);
|
|
38
|
+
return Object.fromEntries(Object.entries(record)
|
|
39
|
+
.filter(([key]) => !forbiddenKey.test(key))
|
|
40
|
+
.slice(0, maxObjectKeys)
|
|
41
|
+
.map(([key, nested]) => [key, redactWorldTrajectoryValue(nested, depth + 1)]));
|
|
42
|
+
};
|
|
43
|
+
const text = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
|
|
44
|
+
export const createPiWorldTrajectoryCapture = () => ({
|
|
45
|
+
calls: [],
|
|
46
|
+
starts: new Map()
|
|
47
|
+
});
|
|
48
|
+
export const capturePiWorldTrajectoryEvent = (capture, event, now = new Date()) => {
|
|
49
|
+
const record = asObject(event);
|
|
50
|
+
const type = text(record?.type);
|
|
51
|
+
if (type !== "tool_execution_start" && type !== "tool_execution_end")
|
|
52
|
+
return;
|
|
53
|
+
const name = text(record?.toolName);
|
|
54
|
+
const toolCallId = text(record?.toolCallId);
|
|
55
|
+
if (name === undefined || toolCallId === undefined || !name.startsWith("world_"))
|
|
56
|
+
return;
|
|
57
|
+
if (type === "tool_execution_start") {
|
|
58
|
+
capture.starts.set(toolCallId, now.getTime());
|
|
59
|
+
capture.calls.push({
|
|
60
|
+
arguments: redactWorldTrajectoryValue(record?.args),
|
|
61
|
+
name,
|
|
62
|
+
sequence: capture.calls.length,
|
|
63
|
+
started_at: now.toISOString(),
|
|
64
|
+
status: "running",
|
|
65
|
+
tool_call_id: toolCallId
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const call = capture.calls.findLast((candidate) => candidate.tool_call_id === toolCallId);
|
|
70
|
+
const resultRecord = asObject(record?.result);
|
|
71
|
+
const result = record?.isError === true
|
|
72
|
+
? record?.result
|
|
73
|
+
: resultRecord?.details ?? record?.result;
|
|
74
|
+
const startedAt = capture.starts.get(toolCallId);
|
|
75
|
+
const completed = call ?? {
|
|
76
|
+
name,
|
|
77
|
+
sequence: capture.calls.length,
|
|
78
|
+
status: "running",
|
|
79
|
+
tool_call_id: toolCallId
|
|
80
|
+
};
|
|
81
|
+
completed.completed_at = now.toISOString();
|
|
82
|
+
completed.duration_ms = startedAt === undefined ? undefined : Math.max(0, now.getTime() - startedAt);
|
|
83
|
+
completed.result = redactWorldTrajectoryValue(result);
|
|
84
|
+
completed.status = record?.isError === true ? "failed" : "completed";
|
|
85
|
+
if (call === undefined)
|
|
86
|
+
capture.calls.push(completed);
|
|
87
|
+
capture.starts.delete(toolCallId);
|
|
88
|
+
};
|
|
89
|
+
const sha256 = (value) => createHash("sha256").update(value, "utf8").digest("hex");
|
|
90
|
+
export const persistPiWorldTrajectory = async (input) => {
|
|
91
|
+
const chosenAction = input.capture.calls.findLast((call) => call.name === "world_act" && call.status === "completed");
|
|
92
|
+
const record = {
|
|
93
|
+
agent_id: input.agentId,
|
|
94
|
+
chosen_action: chosenAction === undefined ? undefined : {
|
|
95
|
+
arguments: chosenAction.arguments,
|
|
96
|
+
result: chosenAction.result,
|
|
97
|
+
tool_call_id: chosenAction.tool_call_id
|
|
98
|
+
},
|
|
99
|
+
completed_at: input.completedAt.toISOString(),
|
|
100
|
+
engine: {
|
|
101
|
+
auth_method: input.model.authMethod,
|
|
102
|
+
kind: "pi",
|
|
103
|
+
model: input.model.model,
|
|
104
|
+
provider: input.model.provider,
|
|
105
|
+
thinking_level: input.thinkingLevel
|
|
106
|
+
},
|
|
107
|
+
instruction: { sha256: sha256(input.instructions) },
|
|
108
|
+
outcome: {
|
|
109
|
+
status: chosenAction === undefined ? "no_action" : "pending_world_join",
|
|
110
|
+
join: chosenAction?.result
|
|
111
|
+
},
|
|
112
|
+
prompt: { sha256: sha256(input.promptText) },
|
|
113
|
+
schema: WORLD_TRAJECTORY_SCHEMA,
|
|
114
|
+
started_at: input.startedAt.toISOString(),
|
|
115
|
+
terminal_status: input.status,
|
|
116
|
+
timings_ms: { total: input.totalMs },
|
|
117
|
+
tool_calls: input.capture.calls,
|
|
118
|
+
turn_id: input.turnId,
|
|
119
|
+
world: {
|
|
120
|
+
run_id: input.context.runId,
|
|
121
|
+
tick: input.context.tick,
|
|
122
|
+
wake_id: input.context.wakeId
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const telemetryPath = path.join(input.runtimeHomePath, "telemetry");
|
|
126
|
+
const trajectoriesPath = path.join(telemetryPath, "world-trajectories");
|
|
127
|
+
await mkdir(trajectoriesPath, { recursive: true });
|
|
128
|
+
const bytes = `${JSON.stringify(record, null, 2)}\n`;
|
|
129
|
+
await writeFile(path.join(trajectoriesPath, `${sanitizeTraceFileId(input.turnId)}.json`), bytes, { encoding: "utf8", mode: 0o600 });
|
|
130
|
+
await appendFile(path.join(telemetryPath, "world-trajectories.ndjson"), `${JSON.stringify(record)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
131
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noopolis/daimon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Noopolis native per-agent runtime harness built on Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./pi": {
|
|
15
|
+
"types": "./dist/pi/index.d.ts",
|
|
16
|
+
"import": "./dist/pi/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./observability": {
|
|
19
|
+
"types": "./dist/observability/index.d.ts",
|
|
20
|
+
"import": "./dist/observability/index.js"
|
|
13
21
|
}
|
|
14
22
|
},
|
|
15
23
|
"files": [
|
|
@@ -30,14 +38,25 @@
|
|
|
30
38
|
"prepublishOnly": "npm run typecheck && npm test",
|
|
31
39
|
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
32
40
|
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
33
|
-
"
|
|
41
|
+
"emit-causal-fixture": "tsx src/observability/emitCausalFixture.ts",
|
|
42
|
+
"emit-causal-fixture:spoof": "tsx src/observability/emitCausalFixture.ts --spoof",
|
|
43
|
+
"live:codex-session": "node --import tsx scripts/liveCodexSession.mjs",
|
|
44
|
+
"e2e:pi-agent": "tsx src/examples/pi-agent.ts",
|
|
45
|
+
"e2e:pi-memory-org": "tsx src/examples/pi-memory-org.ts",
|
|
46
|
+
"e2e:jungian-play-org": "tsx src/examples/jungian-play-org.ts",
|
|
47
|
+
"e2e:jungian-triad-org": "tsx src/examples/jungian-triad-org.ts",
|
|
48
|
+
"image:runtime:local": "docker build -f Dockerfile.runtime -t noopolis/spawnfile-runtime-daimon:0.1.2-local --build-arg DAIMON_VERSION=0.1.2 --build-arg MNEME_VERSION=0.1.1 --build-arg PI_VERSION=0.79.10 .",
|
|
49
|
+
"image:runtime:local-source": "node scripts/buildLocalRuntimeImage.mjs"
|
|
34
50
|
},
|
|
35
51
|
"engines": {
|
|
36
52
|
"node": ">=22.19.0"
|
|
37
53
|
},
|
|
38
54
|
"dependencies": {
|
|
39
|
-
"@earendil-works/pi-ai": "^0.79.
|
|
40
|
-
"@earendil-works/pi-coding-agent": "^0.79.
|
|
55
|
+
"@earendil-works/pi-ai": "^0.79.10",
|
|
56
|
+
"@earendil-works/pi-coding-agent": "^0.79.10",
|
|
57
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
58
|
+
"@noopolis/mneme": "^0.1.1",
|
|
59
|
+
"ajv": "^8.17.1"
|
|
41
60
|
},
|
|
42
61
|
"devDependencies": {
|
|
43
62
|
"@types/node": "^24.12.4",
|