@aexhq/agentloop-pi 4.0.0 → 5.1.0

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 CHANGED
@@ -44,11 +44,17 @@ explicit `placements: { toolName: "environmentName" }` option. Set
44
44
  schema. The loop unwraps that choice before dispatch; Brain validates the selected pair and the
45
45
  canonical input. Both modes use the same session Tool placements.
46
46
 
47
- Transcript and kv changes are saved inline through Brain's state services. A later model or
47
+ Transcript changes and `ctx.kv.read/put/delete` use Brain's state services. KV mutations are
48
+ committed inline; missing keys remain distinct from stored JSON null. A later model or
48
49
  Tool failure preserves acknowledged writes; turn output contains only the result. Retained
49
50
  native model blocks pass through unchanged, and user images enter model context as media.
51
+ Version 5.1 also presents successful Tool outputs shaped as
52
+ `{ type: "aex_tool_output", version: 1, content, media: [{ type: "image", url }] }`
53
+ as Tool-result content and image media. Browser and MCP extensions use this presentation
54
+ convention; ordinary JSON results keep their existing behavior.
50
55
  Compaction explicitly resets the response format and installs a summary only after `end_turn`;
51
56
  a truncated, refused, or unknown summary leaves the original saved context intact.
52
57
 
53
- Rebuild Components against the matching Brain WIT when upgrading. Existing sessions keep their
54
- immutable loop implementation and must be recreated for this interface change.
58
+ Version 5.1 targets Brain 0.22 and preserves the existing WIT. Existing sessions keep their
59
+ immutable loop implementation; create new sessions to adopt the updated loop. Keep matching
60
+ server/artifacts for recovery. An upgrade does not migrate or delete session data.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexhq/agentloop-pi",
3
- "version": "4.0.0",
3
+ "version": "5.1.0",
4
4
  "description": "Pi-style parallel-Tool Brain extension",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,11 +30,12 @@
30
30
  ],
31
31
  "scripts": {
32
32
  "build": "node ../../tools/build-agentloop.mjs src/component.mjs src/index.mjs dist",
33
- "test": "npm run build && node --test",
33
+ "test": "npm run build && node --test *.test.mjs",
34
+ "test:journeys": "node --test test/journeys.mjs",
34
35
  "prepack": "npm run build"
35
36
  },
36
37
  "dependencies": {
37
- "@aexhq/brain": "0.19.0",
38
+ "@aexhq/brain": "0.22.0",
38
39
  "zod": "4.4.3"
39
40
  },
40
41
  "devDependencies": {}
package/src/component.mjs CHANGED
@@ -19,14 +19,20 @@ export async function turn(input) {
19
19
  const output = await runPi({
20
20
  input: JSON.parse(input.inputJson),
21
21
  transcript: JSON.parse(input.transcriptJson),
22
- kv: JSON.parse(input.kvJson),
23
22
  events: JSON.parse(input.eventsJson),
24
23
  configuration: JSON.parse(input.configurationJson),
25
24
  system: input.system,
26
25
  tools: JSON.parse(input.toolsJson),
27
26
  }, {
28
27
  setTranscript: (messages) => hosted(() => host.setTranscript(JSON.stringify(messages))),
29
- setKv: (key, value) => hosted(() => host.setKv(key, JSON.stringify(value))),
28
+ kv: {
29
+ read: (key) => {
30
+ const value = hosted(() => host.kvRead(key));
31
+ return value === undefined ? undefined : JSON.parse(value);
32
+ },
33
+ put: (key, value) => hosted(() => host.kvPut(key, JSON.stringify(value))),
34
+ delete: (key) => hosted(() => host.kvDelete(key)),
35
+ },
30
36
  events: (after) => JSON.parse(hosted(() => host.events(BigInt(after)))),
31
37
  model: (request) => JSON.parse(hosted(() => host.model(JSON.stringify(request)))),
32
38
  dispatch: (calls) => JSON.parse(hosted(() => host.dispatch(JSON.stringify(calls)))),
package/src/logic.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { toolPlacement } from "../../../shared/tool-placement.mjs";
2
2
  import { observeEvents } from "../../../shared/loop-events.mjs";
3
+ import { toolResult } from "../../../shared/tool-output.mjs";
3
4
 
4
5
  const CHECKPOINT_PREFIX = "Context checkpoint from earlier in this conversation:\n\n";
5
6
 
@@ -64,9 +65,9 @@ export async function runPi(input, context) {
64
65
  };
65
66
  const placement = toolPlacement(input.tools, options);
66
67
  const transcript = cloneJson(input.transcript);
67
- const observed_sequence = await observeEvents(context, transcript, input.kv.observed_sequence ?? 0);
68
- const saved = input.kv.checkpoint;
69
- await context.setKv("observed_sequence", observed_sequence);
68
+ const observed_sequence = await observeEvents(context, transcript, (await context.kv.read("observed_sequence")) ?? 0);
69
+ const saved = await context.kv.read("checkpoint");
70
+ await context.kv.put("observed_sequence", observed_sequence);
70
71
  const checkpoint = saved === undefined ? { summary: null } : cloneJson(saved);
71
72
  const body = () => checkpoint.summary === null ? transcript : transcript.slice(1);
72
73
  const shouldCompact = () =>
@@ -108,12 +109,12 @@ export async function runPi(input, context) {
108
109
  if (shouldCompact()) {
109
110
  await compact();
110
111
  await context.setTranscript(transcript);
111
- await context.setKv("checkpoint", checkpoint);
112
+ await context.kv.put("checkpoint", checkpoint);
112
113
  }
113
114
  const { message, stop_reason } = await context.model({ messages: transcript, tools: placement.definitions });
114
115
  transcript.push(message);
115
116
  await context.setTranscript(transcript);
116
- await context.setKv("checkpoint", checkpoint);
117
+ await context.kv.put("checkpoint", checkpoint);
117
118
  const calls = message.content
118
119
  .filter((block) => block.type === "tool_use")
119
120
  .map((block) => ({ call_id: block.id, name: block.name, input: block.input }));
@@ -132,15 +133,7 @@ export async function runPi(input, context) {
132
133
  const byCall = new Map(results.map((result) => [result.call_id, result]));
133
134
  transcript.push({
134
135
  role: "user",
135
- content: calls.map(({ call_id }) => {
136
- const result = byCall.get(call_id);
137
- return {
138
- type: "tool_result",
139
- tool_use_id: call_id,
140
- content: result === undefined ? "Tool produced no result." : result.output,
141
- is_error: result === undefined ? true : result.is_error,
142
- };
143
- }),
136
+ content: calls.map(({ call_id }) => toolResult(call_id, byCall.get(call_id))),
144
137
  });
145
138
  }
146
139
  }