@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,231 @@
|
|
|
1
|
+
import { access, mkdir, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { JsonlMemoryStore } from "@noopolis/mneme";
|
|
5
|
+
import { seedPiOpenAICodexAuthFromCodex } from "../pi/auth.js";
|
|
6
|
+
import { PiHarnessAdapter } from "../pi/piHarness.js";
|
|
7
|
+
import { exampleCausalId } from "./exampleCausalId.js";
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const daimonRoot = path.resolve(__dirname, "../..");
|
|
10
|
+
const runtimeRoot = path.join(daimonRoot, ".runtime", "pi-memory-org");
|
|
11
|
+
const piAuthPath = path.join(runtimeRoot, "auth", "auth.json");
|
|
12
|
+
const codexAuthPath = path.join(process.env.HOME ?? "", ".codex", "auth.json");
|
|
13
|
+
const demoAgents = [
|
|
14
|
+
{ id: "atlas", name: "Atlas", marker: "ATLAS_BLUE_17" },
|
|
15
|
+
{ id: "oracle", name: "Oracle", marker: "ORACLE_GOLD_23" },
|
|
16
|
+
{ id: "keeper", name: "Keeper", marker: "KEEPER_GREEN_31" }
|
|
17
|
+
];
|
|
18
|
+
const roomContext = {
|
|
19
|
+
networkId: "memory-lab",
|
|
20
|
+
roomId: "council",
|
|
21
|
+
teamId: "memory-e2e",
|
|
22
|
+
participants: demoAgents.map((agent) => agent.id)
|
|
23
|
+
};
|
|
24
|
+
const assertIncludes = (label, content, expected) => {
|
|
25
|
+
if (!content.includes(expected)) {
|
|
26
|
+
throw new Error(`${label} did not include ${expected}.\nActual:\n${content}`);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const assertCurrentPromptDoesNotLeakMarkers = (event) => {
|
|
30
|
+
for (const agent of demoAgents) {
|
|
31
|
+
if (event.text.includes(agent.marker)) {
|
|
32
|
+
throw new Error(`wake ${event.id} leaked marker ${agent.marker} in current prompt`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const agentRuntimeHome = (agentId) => path.join(runtimeRoot, "agents", agentId, "runtime");
|
|
37
|
+
const prepareWorkspace = async (agentId) => {
|
|
38
|
+
const workspacePath = path.join(runtimeRoot, "agents", agentId, "workspace");
|
|
39
|
+
const runtimeHomePath = agentRuntimeHome(agentId);
|
|
40
|
+
await mkdir(workspacePath, { recursive: true });
|
|
41
|
+
await mkdir(runtimeHomePath, { recursive: true });
|
|
42
|
+
await writeFile(path.join(workspacePath, "AGENTS.md"), [
|
|
43
|
+
`# ${agentId}`,
|
|
44
|
+
"",
|
|
45
|
+
"This workspace is part of the Daimon Pi memory-org E2E.",
|
|
46
|
+
"The test intentionally disables file tools and verifies persisted Daimon memory."
|
|
47
|
+
].join("\n"));
|
|
48
|
+
return { workspacePath, runtimeHomePath };
|
|
49
|
+
};
|
|
50
|
+
const setupRuntime = async () => {
|
|
51
|
+
await access(codexAuthPath);
|
|
52
|
+
await rm(runtimeRoot, { recursive: true, force: true });
|
|
53
|
+
await mkdir(path.dirname(piAuthPath), { recursive: true });
|
|
54
|
+
await seedPiOpenAICodexAuthFromCodex({ codexAuthPath, piAuthPath });
|
|
55
|
+
};
|
|
56
|
+
const resetPiSessionHistory = async (agentId) => {
|
|
57
|
+
await rm(path.join(agentRuntimeHome(agentId), "sessions"), { recursive: true, force: true });
|
|
58
|
+
};
|
|
59
|
+
const startAgent = async (adapter, agent) => {
|
|
60
|
+
const paths = await prepareWorkspace(agent.id);
|
|
61
|
+
return adapter.startAgent({
|
|
62
|
+
id: agent.id,
|
|
63
|
+
name: agent.name,
|
|
64
|
+
instructions: [
|
|
65
|
+
"You are participating in a live Daimon memory E2E.",
|
|
66
|
+
"Use the Memory context injected above the wake event as authoritative recalled memory.",
|
|
67
|
+
"When asked to recall marker tokens, copy exact marker tokens from memory.",
|
|
68
|
+
"Keep every response to one concise line unless the wake explicitly asks otherwise."
|
|
69
|
+
].join(" "),
|
|
70
|
+
tools: [],
|
|
71
|
+
...paths
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const startAllAgents = async (adapter) => {
|
|
75
|
+
const handles = new Map();
|
|
76
|
+
for (const agent of demoAgents) {
|
|
77
|
+
handles.set(agent.id, await startAgent(adapter, agent));
|
|
78
|
+
}
|
|
79
|
+
return handles;
|
|
80
|
+
};
|
|
81
|
+
const stopAll = async (handles) => {
|
|
82
|
+
await Promise.all([...handles.values()].map((handle) => handle.stop()));
|
|
83
|
+
};
|
|
84
|
+
const wake = async (handle, event, options = {}) => {
|
|
85
|
+
if (options.assertNoMarkerLeak) {
|
|
86
|
+
assertCurrentPromptDoesNotLeakMarkers(event);
|
|
87
|
+
}
|
|
88
|
+
const result = await handle.wake(event);
|
|
89
|
+
console.log(`[${event.id}] ${result.agentId}: ${result.text}`);
|
|
90
|
+
return result;
|
|
91
|
+
};
|
|
92
|
+
const seedPrivateMemories = async (handles) => {
|
|
93
|
+
console.log("\n== Private seed phase ==");
|
|
94
|
+
for (const agent of demoAgents) {
|
|
95
|
+
const result = await handles.get(agent.id)?.wake({
|
|
96
|
+
id: exampleCausalId(`seed-${agent.id}`),
|
|
97
|
+
kind: "manual",
|
|
98
|
+
text: [
|
|
99
|
+
"Private memory seed.",
|
|
100
|
+
`Remember this exact marker token for later recall: ${agent.marker}`,
|
|
101
|
+
"Do not write files. Reply with exactly: stored"
|
|
102
|
+
].join("\n")
|
|
103
|
+
});
|
|
104
|
+
console.log(`[seed-${agent.id}] ${agent.id}: ${result?.text ?? ""}`);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const runRoomConversation = async (handles) => {
|
|
108
|
+
console.log("\n== Room recall phase ==");
|
|
109
|
+
const transcript = [];
|
|
110
|
+
const atlas = await wake(handles.get("atlas"), {
|
|
111
|
+
id: exampleCausalId("room-atlas-1"),
|
|
112
|
+
kind: "manual",
|
|
113
|
+
context: roomContext,
|
|
114
|
+
text: [
|
|
115
|
+
"Moltnet room simulation. Visible room transcript so far:",
|
|
116
|
+
transcript.join("\n") || "(empty)",
|
|
117
|
+
"Recall your private marker token from Daimon memory.",
|
|
118
|
+
"Reply in one line: @oracle atlas recalls <exact token>",
|
|
119
|
+
"Do not invent markers for other agents."
|
|
120
|
+
].join("\n")
|
|
121
|
+
}, { assertNoMarkerLeak: true });
|
|
122
|
+
assertIncludes("atlas room reply", atlas.text, "ATLAS_BLUE_17");
|
|
123
|
+
transcript.push(`atlas: ${atlas.text}`);
|
|
124
|
+
const oracle = await wake(handles.get("oracle"), {
|
|
125
|
+
id: exampleCausalId("room-oracle-1"),
|
|
126
|
+
kind: "manual",
|
|
127
|
+
context: roomContext,
|
|
128
|
+
text: [
|
|
129
|
+
"Moltnet room simulation. Visible room transcript so far:",
|
|
130
|
+
transcript.join("\n"),
|
|
131
|
+
"Recall your private marker token from Daimon memory and acknowledge Atlas from the transcript.",
|
|
132
|
+
"Reply in one line: @keeper oracle recalls <exact token>; observed atlas=<atlas token>"
|
|
133
|
+
].join("\n")
|
|
134
|
+
});
|
|
135
|
+
assertIncludes("oracle room reply", oracle.text, "ORACLE_GOLD_23");
|
|
136
|
+
assertIncludes("oracle room reply", oracle.text, "ATLAS_BLUE_17");
|
|
137
|
+
transcript.push(`oracle: ${oracle.text}`);
|
|
138
|
+
const keeper = await wake(handles.get("keeper"), {
|
|
139
|
+
id: exampleCausalId("room-keeper-1"),
|
|
140
|
+
kind: "manual",
|
|
141
|
+
context: roomContext,
|
|
142
|
+
text: [
|
|
143
|
+
"Moltnet room simulation. Visible room transcript so far:",
|
|
144
|
+
transcript.join("\n"),
|
|
145
|
+
"Recall your private marker token from Daimon memory and acknowledge Atlas and Oracle from the transcript.",
|
|
146
|
+
"Reply in one line: keeper recalls <exact token>; observed atlas=<atlas token> oracle=<oracle token>"
|
|
147
|
+
].join("\n")
|
|
148
|
+
});
|
|
149
|
+
assertIncludes("keeper room reply", keeper.text, "KEEPER_GREEN_31");
|
|
150
|
+
assertIncludes("keeper room reply", keeper.text, "ATLAS_BLUE_17");
|
|
151
|
+
assertIncludes("keeper room reply", keeper.text, "ORACLE_GOLD_23");
|
|
152
|
+
transcript.push(`keeper: ${keeper.text}`);
|
|
153
|
+
console.log("\nRoom transcript:");
|
|
154
|
+
for (const line of transcript) {
|
|
155
|
+
console.log(line);
|
|
156
|
+
}
|
|
157
|
+
return transcript;
|
|
158
|
+
};
|
|
159
|
+
const verifyFreshSessionRecall = async (adapter, previousKeeper) => {
|
|
160
|
+
console.log("\n== Fresh session recall phase ==");
|
|
161
|
+
await previousKeeper.stop();
|
|
162
|
+
await resetPiSessionHistory("keeper");
|
|
163
|
+
const keeper = await startAgent(adapter, demoAgents.find((agent) => agent.id === "keeper"));
|
|
164
|
+
try {
|
|
165
|
+
const event = {
|
|
166
|
+
id: exampleCausalId("room-keeper-2"),
|
|
167
|
+
kind: "manual",
|
|
168
|
+
context: roomContext,
|
|
169
|
+
text: [
|
|
170
|
+
"Fresh session check. You do not get the room transcript in this wake.",
|
|
171
|
+
"Use only Daimon memory recalled into this turn.",
|
|
172
|
+
"Report the marker tokens for atlas, oracle, and keeper from your previous memory-lab room turn.",
|
|
173
|
+
"Reply in one line: final atlas=<token> oracle=<token> keeper=<token>"
|
|
174
|
+
].join("\n")
|
|
175
|
+
};
|
|
176
|
+
const result = await wake(keeper, event, { assertNoMarkerLeak: true });
|
|
177
|
+
assertIncludes("fresh keeper recall", result.text, "ATLAS_BLUE_17");
|
|
178
|
+
assertIncludes("fresh keeper recall", result.text, "ORACLE_GOLD_23");
|
|
179
|
+
assertIncludes("fresh keeper recall", result.text, "KEEPER_GREEN_31");
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
await keeper.stop();
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const printMemoryCounts = async () => {
|
|
186
|
+
console.log("\nMemory event counts:");
|
|
187
|
+
for (const agent of demoAgents) {
|
|
188
|
+
const events = await new JsonlMemoryStore(agentRuntimeHome(agent.id)).read();
|
|
189
|
+
const counts = events.reduce((memo, event) => {
|
|
190
|
+
memo[event.type] = (memo[event.type] ?? 0) + 1;
|
|
191
|
+
return memo;
|
|
192
|
+
}, {});
|
|
193
|
+
console.log(`${agent.id}: ${JSON.stringify(counts)}`);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const run = async () => {
|
|
197
|
+
await setupRuntime();
|
|
198
|
+
const adapter = new PiHarnessAdapter({
|
|
199
|
+
authPath: piAuthPath,
|
|
200
|
+
model: {
|
|
201
|
+
provider: "openai-codex",
|
|
202
|
+
name: process.env.HARNESS_PI_MODEL ?? "gpt-5.4-mini"
|
|
203
|
+
},
|
|
204
|
+
memory: {
|
|
205
|
+
tokenBudget: 1800
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
const seedHandles = await startAllAgents(adapter);
|
|
209
|
+
await seedPrivateMemories(seedHandles);
|
|
210
|
+
await stopAll(seedHandles);
|
|
211
|
+
await Promise.all(demoAgents.map((agent) => resetPiSessionHistory(agent.id)));
|
|
212
|
+
const roomHandles = await startAllAgents(adapter);
|
|
213
|
+
try {
|
|
214
|
+
await runRoomConversation(roomHandles);
|
|
215
|
+
await verifyFreshSessionRecall(adapter, roomHandles.get("keeper"));
|
|
216
|
+
await roomHandles.get("atlas")?.stop();
|
|
217
|
+
await roomHandles.get("oracle")?.stop();
|
|
218
|
+
await printMemoryCounts();
|
|
219
|
+
console.log("\ne2e:pi-memory-org ok");
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
await Promise.all([
|
|
223
|
+
roomHandles.get("atlas")?.stop(),
|
|
224
|
+
roomHandles.get("oracle")?.stop()
|
|
225
|
+
]);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
run().catch((error) => {
|
|
229
|
+
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
230
|
+
process.exitCode = 1;
|
|
231
|
+
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
export declare class McpToolTurnLimitError extends Error {
|
|
4
|
+
constructor(maxToolTurns: number);
|
|
5
|
+
}
|
|
6
|
+
export declare class McpWakeDeadlineError extends Error {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
export interface PiToolMcpServerOptions {
|
|
10
|
+
readonly maxToolTurns: number;
|
|
11
|
+
readonly wakeDeadline: number;
|
|
12
|
+
}
|
|
13
|
+
export declare const createPiToolMcpServer: (tools: ToolDefinition[], options: PiToolMcpServerOptions) => Server;
|
|
14
|
+
//# sourceMappingURL=toolServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolServer.d.ts","sourceRoot":"","sources":["../../src/mcp/toolServer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAYnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,qBAAa,qBAAsB,SAAQ,KAAK;gBAC3B,YAAY,EAAE,MAAM;CAIxC;AAED,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAqCD,eAAO,MAAM,qBAAqB,GAChC,OAAO,cAAc,EAAE,EACvB,SAAS,sBAAsB,KAC9B,MAmEF,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
export class McpToolTurnLimitError extends Error {
|
|
5
|
+
constructor(maxToolTurns) {
|
|
6
|
+
super(`McpToolTurnLimitError: maximum ${maxToolTurns} calls per wake`);
|
|
7
|
+
this.name = "McpToolTurnLimitError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class McpWakeDeadlineError extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super("McpWakeDeadlineError: wake deadline exceeded");
|
|
13
|
+
this.name = "McpWakeDeadlineError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const jsonSchema = (parameters) => {
|
|
17
|
+
if (parameters === null || typeof parameters !== "object" || Array.isArray(parameters)) {
|
|
18
|
+
throw new TypeError("Pi tool parameters must be a JSON schema object");
|
|
19
|
+
}
|
|
20
|
+
return Object.fromEntries(Object.entries(parameters));
|
|
21
|
+
};
|
|
22
|
+
const toolResult = (result) => ({
|
|
23
|
+
content: result.content,
|
|
24
|
+
...(result.details !== undefined && typeof result.details === "object" && result.details !== null
|
|
25
|
+
? { structuredContent: Object.fromEntries(Object.entries(result.details)) }
|
|
26
|
+
: {})
|
|
27
|
+
});
|
|
28
|
+
const toolError = (error) => ({
|
|
29
|
+
content: [{ type: "text", text: error instanceof Error ? `${error.name}: ${error.message}` : String(error) }],
|
|
30
|
+
isError: true
|
|
31
|
+
});
|
|
32
|
+
// Pi's ExtensionContext has no meaning outside a Pi session. Mounted tools
|
|
33
|
+
// must not read it; this named value documents the explicit absence.
|
|
34
|
+
// The MCP mount deliberately has no Pi session context; `never` preserves typed positional checks.
|
|
35
|
+
const NO_PI_EXTENSION_CONTEXT = undefined;
|
|
36
|
+
const validateOptions = (options) => {
|
|
37
|
+
if (!Number.isSafeInteger(options.maxToolTurns) || options.maxToolTurns < 1) {
|
|
38
|
+
throw new TypeError("maxToolTurns must be a positive safe integer");
|
|
39
|
+
}
|
|
40
|
+
if (!Number.isFinite(options.wakeDeadline)) {
|
|
41
|
+
throw new TypeError("wakeDeadline must be a finite epoch-millisecond deadline");
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export const createPiToolMcpServer = (tools, options) => {
|
|
45
|
+
validateOptions(options);
|
|
46
|
+
const server = new Server({ name: "daimon-pi-tools", version: "0.1.2" });
|
|
47
|
+
const validators = new Map(tools.map((tool) => {
|
|
48
|
+
const schema = jsonSchema(tool.parameters);
|
|
49
|
+
return [tool.name, new Ajv2020({ strict: false }).compile(schema)];
|
|
50
|
+
}));
|
|
51
|
+
let toolTurns = 0;
|
|
52
|
+
server.registerCapabilities({ tools: { listChanged: true } });
|
|
53
|
+
server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
54
|
+
tools: tools.map((tool) => ({
|
|
55
|
+
name: tool.name,
|
|
56
|
+
title: tool.label,
|
|
57
|
+
description: tool.description,
|
|
58
|
+
inputSchema: jsonSchema(tool.parameters)
|
|
59
|
+
}))
|
|
60
|
+
}));
|
|
61
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
62
|
+
try {
|
|
63
|
+
if (Date.now() >= options.wakeDeadline)
|
|
64
|
+
throw new McpWakeDeadlineError();
|
|
65
|
+
if (toolTurns >= options.maxToolTurns)
|
|
66
|
+
throw new McpToolTurnLimitError(options.maxToolTurns);
|
|
67
|
+
const tool = tools.find((candidate) => candidate.name === request.params.name);
|
|
68
|
+
const validator = validators.get(request.params.name);
|
|
69
|
+
if (tool === undefined || validator === undefined) {
|
|
70
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);
|
|
71
|
+
}
|
|
72
|
+
const args = request.params.arguments ?? {};
|
|
73
|
+
if (!validator(args)) {
|
|
74
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for tool ${tool.name}`);
|
|
75
|
+
}
|
|
76
|
+
toolTurns += 1;
|
|
77
|
+
// Ajv validated this value against this tool's own schema immediately above.
|
|
78
|
+
const validatedArgs = args;
|
|
79
|
+
const deadlineController = new AbortController();
|
|
80
|
+
const remainingMs = Math.max(0, options.wakeDeadline - Date.now());
|
|
81
|
+
const deadlineTimer = setTimeout(() => deadlineController.abort(), remainingMs);
|
|
82
|
+
const signal = extra.signal === undefined
|
|
83
|
+
? deadlineController.signal
|
|
84
|
+
: AbortSignal.any([extra.signal, deadlineController.signal]);
|
|
85
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
86
|
+
deadlineController.signal.addEventListener("abort", () => reject(new McpWakeDeadlineError()), { once: true });
|
|
87
|
+
});
|
|
88
|
+
let result;
|
|
89
|
+
try {
|
|
90
|
+
result = await Promise.race([
|
|
91
|
+
tool.execute(`mcp-tool-turn-${toolTurns}`, validatedArgs, signal, undefined, NO_PI_EXTENSION_CONTEXT),
|
|
92
|
+
deadline
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
clearTimeout(deadlineTimer);
|
|
97
|
+
}
|
|
98
|
+
return toolResult(result);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return toolError(error);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return server;
|
|
105
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daimon's own copy of the `noopolis.causal-event.v1` wire envelope. Field-
|
|
3
|
+
* for-field the canonical shape in `specs/causal-event.v1.schema.json` and
|
|
4
|
+
* `specs/CAUSAL.md` (root repo) — daimon is an independent repo and does not
|
|
5
|
+
* import that schema, it only conforms to it. The wire JSON emitted by
|
|
6
|
+
* `appendCausalEvent` is the actual contract; this type is local sugar over
|
|
7
|
+
* it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CAUSAL_EVENT_VERSION: "noopolis.causal-event.v1";
|
|
10
|
+
export type CausalEventSystem = "simfile" | "moltnet" | "mneme" | "daimon";
|
|
11
|
+
export interface CausalEventEmitter {
|
|
12
|
+
system: CausalEventSystem;
|
|
13
|
+
stream_id: string;
|
|
14
|
+
seq: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CausalEvent<TPayload = Record<string, unknown>> {
|
|
17
|
+
cause_event_ids: string[];
|
|
18
|
+
emitter: CausalEventEmitter;
|
|
19
|
+
event_id: string;
|
|
20
|
+
payload: TPayload;
|
|
21
|
+
principal_id: string;
|
|
22
|
+
recorded_at: string;
|
|
23
|
+
run_id: string;
|
|
24
|
+
type: string;
|
|
25
|
+
version: typeof CAUSAL_EVENT_VERSION;
|
|
26
|
+
}
|
|
27
|
+
/** Payload for `turn.input.submitted`, stamped just before a Pi session prompt call. */
|
|
28
|
+
export interface TurnInputSubmittedPayload extends Record<string, unknown> {
|
|
29
|
+
input_content_sha256: string;
|
|
30
|
+
input_message_ids: string[];
|
|
31
|
+
prompt_sha256: string;
|
|
32
|
+
turn_id: string;
|
|
33
|
+
}
|
|
34
|
+
/** Payload for `turn.output.completed`, stamped once a Pi turn finishes successfully. */
|
|
35
|
+
export interface TurnOutputCompletedPayload extends Record<string, unknown> {
|
|
36
|
+
output_sha256: string;
|
|
37
|
+
turn_id: string;
|
|
38
|
+
}
|
|
39
|
+
export declare const TURN_INPUT_SUBMITTED_TYPE: "turn.input.submitted";
|
|
40
|
+
export declare const TURN_OUTPUT_COMPLETED_TYPE: "turn.output.completed";
|
|
41
|
+
/** Name of the environment variable every Noopolis authority reads `run_id` from. Never model output. */
|
|
42
|
+
export declare const NOOPOLIS_RUN_ID_ENV = "NOOPOLIS_RUN_ID";
|
|
43
|
+
/**
|
|
44
|
+
* Resolves `run_id` from the `NOOPOLIS_RUN_ID` environment variable, per
|
|
45
|
+
* `specs/CAUSAL.md`. Never derived from a WakeEvent, model output, or any
|
|
46
|
+
* other in-turn data. A causal event cannot be emitted without a real run id.
|
|
47
|
+
*/
|
|
48
|
+
export declare const resolveRunId: (env?: NodeJS.ProcessEnv) => string;
|
|
49
|
+
export declare const sha256Hex: (value: string) => string;
|
|
50
|
+
/** Deterministic, non-model-derived event id for a turn's `turn.input.submitted` record. */
|
|
51
|
+
export declare const turnInputSubmittedEventId: (turnId: string) => string;
|
|
52
|
+
/** Deterministic, non-model-derived event id for a turn's `turn.output.completed` record. */
|
|
53
|
+
export declare const turnOutputCompletedEventId: (turnId: string) => string;
|
|
54
|
+
/**
|
|
55
|
+
* The `cause_event_ids` an outbound Moltnet reply for `turnId` should carry.
|
|
56
|
+
* Pure and deterministic from `turn_id` alone — the harness owns this value,
|
|
57
|
+
* never the model. Daimon does not construct Moltnet `SendMessageRequest`
|
|
58
|
+
* values itself (see repo AGENTS.md: Moltnet wiring belongs to the caller,
|
|
59
|
+
* not this package), so this helper is what a caller sending the reply on
|
|
60
|
+
* Daimon's behalf attaches to that request's `cause_event_ids`.
|
|
61
|
+
*/
|
|
62
|
+
export declare const replyCauseEventIds: (turnId: string) => string[];
|
|
63
|
+
/**
|
|
64
|
+
* Allocates the next contiguous seq number for `(run_id, stream_id)`,
|
|
65
|
+
* persisted under `runtimeHome/telemetry/causal.seq.json`. Daimon runs at
|
|
66
|
+
* most one wake at a time per agent (`PiAgentHandle.wakeQueue` serializes
|
|
67
|
+
* them), so read-modify-write here does not need extra locking.
|
|
68
|
+
*/
|
|
69
|
+
export declare const nextCausalSeq: (input: {
|
|
70
|
+
runId: string;
|
|
71
|
+
runtimeHomePath: string;
|
|
72
|
+
streamId: string;
|
|
73
|
+
}) => Promise<number>;
|
|
74
|
+
/** Appends one CausalEvent record as a line of `runtimeHome/telemetry/causal.jsonl`. */
|
|
75
|
+
export declare const appendCausalEvent: (runtimeHomePath: string, event: CausalEvent) => Promise<void>;
|
|
76
|
+
export interface EmitCausalEventInput<TPayload extends Record<string, unknown>> {
|
|
77
|
+
agentId: string;
|
|
78
|
+
causeEventIds: string[];
|
|
79
|
+
eventId: string;
|
|
80
|
+
payload: TPayload;
|
|
81
|
+
principalId: string;
|
|
82
|
+
runId: string;
|
|
83
|
+
runtimeHomePath: string;
|
|
84
|
+
type: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Stamps and appends one causal event for this agent's stream
|
|
88
|
+
* (`agent:<agentId>`). `run_id` and `principal_id` are always caller-
|
|
89
|
+
* supplied values (never read from `payload` or model output) — see
|
|
90
|
+
* `piHarness.ts`, which is the only caller and always passes the resolved
|
|
91
|
+
* `NOOPOLIS_RUN_ID` and the authenticated agent identity.
|
|
92
|
+
*/
|
|
93
|
+
export declare const emitCausalEvent: <TPayload extends Record<string, unknown>>(input: EmitCausalEventInput<TPayload>) => Promise<CausalEvent<TPayload>>;
|
|
94
|
+
export interface EmitTurnInputSubmittedInput {
|
|
95
|
+
agentId: string;
|
|
96
|
+
causeEventIds: string[];
|
|
97
|
+
inputContentSha256: string;
|
|
98
|
+
inputMessageIds: string[];
|
|
99
|
+
principalId: string;
|
|
100
|
+
promptSha256: string;
|
|
101
|
+
runId: string;
|
|
102
|
+
runtimeHomePath: string;
|
|
103
|
+
turnId: string;
|
|
104
|
+
}
|
|
105
|
+
/** Stamps `turn.input.submitted` for one Pi turn, right before the engine prompt call. */
|
|
106
|
+
export declare const emitTurnInputSubmitted: (input: EmitTurnInputSubmittedInput) => Promise<CausalEvent<TurnInputSubmittedPayload>>;
|
|
107
|
+
export interface EmitTurnOutputCompletedInput {
|
|
108
|
+
agentId: string;
|
|
109
|
+
causeEventIds: string[];
|
|
110
|
+
outputSha256: string;
|
|
111
|
+
principalId: string;
|
|
112
|
+
runId: string;
|
|
113
|
+
runtimeHomePath: string;
|
|
114
|
+
turnId: string;
|
|
115
|
+
}
|
|
116
|
+
/** Stamps `turn.output.completed` once a Pi turn finishes successfully. */
|
|
117
|
+
export declare const emitTurnOutputCompleted: (input: EmitTurnOutputCompletedInput) => Promise<CausalEvent<TurnOutputCompletedPayload>>;
|
|
118
|
+
//# sourceMappingURL=causalEvents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"causalEvents.d.ts","sourceRoot":"","sources":["../../src/observability/causalEvents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Daimon's own copy of the `noopolis.causal-event.v1` wire envelope. Field-
|
|
6
|
+
* for-field the canonical shape in `specs/causal-event.v1.schema.json` and
|
|
7
|
+
* `specs/CAUSAL.md` (root repo) — daimon is an independent repo and does not
|
|
8
|
+
* import that schema, it only conforms to it. The wire JSON emitted by
|
|
9
|
+
* `appendCausalEvent` is the actual contract; this type is local sugar over
|
|
10
|
+
* it.
|
|
11
|
+
*/
|
|
12
|
+
export const CAUSAL_EVENT_VERSION = "noopolis.causal-event.v1";
|
|
13
|
+
export const TURN_INPUT_SUBMITTED_TYPE = "turn.input.submitted";
|
|
14
|
+
export const TURN_OUTPUT_COMPLETED_TYPE = "turn.output.completed";
|
|
15
|
+
/** Name of the environment variable every Noopolis authority reads `run_id` from. Never model output. */
|
|
16
|
+
export const NOOPOLIS_RUN_ID_ENV = "NOOPOLIS_RUN_ID";
|
|
17
|
+
/**
|
|
18
|
+
* Resolves `run_id` from the `NOOPOLIS_RUN_ID` environment variable, per
|
|
19
|
+
* `specs/CAUSAL.md`. Never derived from a WakeEvent, model output, or any
|
|
20
|
+
* other in-turn data. A causal event cannot be emitted without a real run id.
|
|
21
|
+
*/
|
|
22
|
+
export const resolveRunId = (env = process.env) => {
|
|
23
|
+
const value = env[NOOPOLIS_RUN_ID_ENV];
|
|
24
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
25
|
+
throw new Error(`${NOOPOLIS_RUN_ID_ENV} must be set to a non-blank value`);
|
|
26
|
+
}
|
|
27
|
+
return value.trim();
|
|
28
|
+
};
|
|
29
|
+
export const sha256Hex = (value) => createHash("sha256").update(value, "utf8").digest("hex");
|
|
30
|
+
/** Deterministic, non-model-derived event id for a turn's `turn.input.submitted` record. */
|
|
31
|
+
export const turnInputSubmittedEventId = (turnId) => `daimon:${turnId}:turn.input.submitted`;
|
|
32
|
+
/** Deterministic, non-model-derived event id for a turn's `turn.output.completed` record. */
|
|
33
|
+
export const turnOutputCompletedEventId = (turnId) => `daimon:${turnId}:turn.output.completed`;
|
|
34
|
+
/**
|
|
35
|
+
* The `cause_event_ids` an outbound Moltnet reply for `turnId` should carry.
|
|
36
|
+
* Pure and deterministic from `turn_id` alone — the harness owns this value,
|
|
37
|
+
* never the model. Daimon does not construct Moltnet `SendMessageRequest`
|
|
38
|
+
* values itself (see repo AGENTS.md: Moltnet wiring belongs to the caller,
|
|
39
|
+
* not this package), so this helper is what a caller sending the reply on
|
|
40
|
+
* Daimon's behalf attaches to that request's `cause_event_ids`.
|
|
41
|
+
*/
|
|
42
|
+
export const replyCauseEventIds = (turnId) => [turnOutputCompletedEventId(turnId)];
|
|
43
|
+
const telemetryDir = (runtimeHomePath) => path.join(runtimeHomePath, "telemetry");
|
|
44
|
+
const seqFilePath = (runtimeHomePath) => path.join(telemetryDir(runtimeHomePath), "causal.seq.json");
|
|
45
|
+
const jsonlFilePath = (runtimeHomePath) => path.join(telemetryDir(runtimeHomePath), "causal.jsonl");
|
|
46
|
+
const readSeqStore = async (runtimeHomePath) => {
|
|
47
|
+
try {
|
|
48
|
+
const raw = await readFile(seqFilePath(runtimeHomePath), "utf8");
|
|
49
|
+
return JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const writeSeqStore = async (runtimeHomePath, store) => {
|
|
59
|
+
await mkdir(telemetryDir(runtimeHomePath), { recursive: true });
|
|
60
|
+
await writeFile(seqFilePath(runtimeHomePath), `${JSON.stringify(store, null, 2)}\n`, "utf8");
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Allocates the next contiguous seq number for `(run_id, stream_id)`,
|
|
64
|
+
* persisted under `runtimeHome/telemetry/causal.seq.json`. Daimon runs at
|
|
65
|
+
* most one wake at a time per agent (`PiAgentHandle.wakeQueue` serializes
|
|
66
|
+
* them), so read-modify-write here does not need extra locking.
|
|
67
|
+
*/
|
|
68
|
+
export const nextCausalSeq = async (input) => {
|
|
69
|
+
const store = await readSeqStore(input.runtimeHomePath);
|
|
70
|
+
const forRun = store[input.runId] ?? {};
|
|
71
|
+
const next = (forRun[input.streamId] ?? 0) + 1;
|
|
72
|
+
forRun[input.streamId] = next;
|
|
73
|
+
store[input.runId] = forRun;
|
|
74
|
+
await writeSeqStore(input.runtimeHomePath, store);
|
|
75
|
+
return next;
|
|
76
|
+
};
|
|
77
|
+
/** Appends one CausalEvent record as a line of `runtimeHome/telemetry/causal.jsonl`. */
|
|
78
|
+
export const appendCausalEvent = async (runtimeHomePath, event) => {
|
|
79
|
+
await mkdir(telemetryDir(runtimeHomePath), { recursive: true });
|
|
80
|
+
await appendFile(jsonlFilePath(runtimeHomePath), `${JSON.stringify(event)}\n`, "utf8");
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Stamps and appends one causal event for this agent's stream
|
|
84
|
+
* (`agent:<agentId>`). `run_id` and `principal_id` are always caller-
|
|
85
|
+
* supplied values (never read from `payload` or model output) — see
|
|
86
|
+
* `piHarness.ts`, which is the only caller and always passes the resolved
|
|
87
|
+
* `NOOPOLIS_RUN_ID` and the authenticated agent identity.
|
|
88
|
+
*/
|
|
89
|
+
export const emitCausalEvent = async (input) => {
|
|
90
|
+
const streamId = `agent:${input.agentId}`;
|
|
91
|
+
const seq = await nextCausalSeq({ runId: input.runId, runtimeHomePath: input.runtimeHomePath, streamId });
|
|
92
|
+
const event = {
|
|
93
|
+
cause_event_ids: [...input.causeEventIds],
|
|
94
|
+
emitter: { system: "daimon", stream_id: streamId, seq },
|
|
95
|
+
event_id: input.eventId,
|
|
96
|
+
payload: input.payload,
|
|
97
|
+
principal_id: input.principalId,
|
|
98
|
+
recorded_at: new Date().toISOString(),
|
|
99
|
+
run_id: input.runId,
|
|
100
|
+
type: input.type,
|
|
101
|
+
version: CAUSAL_EVENT_VERSION
|
|
102
|
+
};
|
|
103
|
+
await appendCausalEvent(input.runtimeHomePath, event);
|
|
104
|
+
return event;
|
|
105
|
+
};
|
|
106
|
+
/** Stamps `turn.input.submitted` for one Pi turn, right before the engine prompt call. */
|
|
107
|
+
export const emitTurnInputSubmitted = (input) => emitCausalEvent({
|
|
108
|
+
agentId: input.agentId,
|
|
109
|
+
causeEventIds: input.causeEventIds,
|
|
110
|
+
eventId: turnInputSubmittedEventId(input.turnId),
|
|
111
|
+
payload: {
|
|
112
|
+
input_content_sha256: input.inputContentSha256,
|
|
113
|
+
input_message_ids: [...input.inputMessageIds],
|
|
114
|
+
prompt_sha256: input.promptSha256,
|
|
115
|
+
turn_id: input.turnId
|
|
116
|
+
},
|
|
117
|
+
principalId: input.principalId,
|
|
118
|
+
runId: input.runId,
|
|
119
|
+
runtimeHomePath: input.runtimeHomePath,
|
|
120
|
+
type: TURN_INPUT_SUBMITTED_TYPE
|
|
121
|
+
});
|
|
122
|
+
/** Stamps `turn.output.completed` once a Pi turn finishes successfully. */
|
|
123
|
+
export const emitTurnOutputCompleted = (input) => emitCausalEvent({
|
|
124
|
+
agentId: input.agentId,
|
|
125
|
+
causeEventIds: input.causeEventIds,
|
|
126
|
+
eventId: turnOutputCompletedEventId(input.turnId),
|
|
127
|
+
payload: {
|
|
128
|
+
output_sha256: input.outputSha256,
|
|
129
|
+
turn_id: input.turnId
|
|
130
|
+
},
|
|
131
|
+
principalId: input.principalId,
|
|
132
|
+
runId: input.runId,
|
|
133
|
+
runtimeHomePath: input.runtimeHomePath,
|
|
134
|
+
type: TURN_OUTPUT_COMPLETED_TYPE
|
|
135
|
+
});
|