@animalabs/connectome-host 0.3.5 → 0.3.7

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/.env.example CHANGED
@@ -1,5 +1,11 @@
1
1
  ANTHROPIC_API_KEY=sk-ant-...
2
2
  # MODEL=claude-opus-4-6
3
+ # Agent-visible wall clock (IANA zone; recipe agent.timezone takes precedence)
4
+ # AGENT_TIMEZONE=America/Los_Angeles
5
+
6
+ # For recipes with agent.provider="openai-responses":
7
+ # OPENAI_API_KEY=sk-...
8
+ # OPENAI_BASE_URL=https://api.openai.com/v1
3
9
 
4
10
  # --- Recipe ${VAR} substitutions ---------------------------------------
5
11
  # Recipes in recipes/ reference these via ${VAR}. Unset = that branch of
package/README.md CHANGED
@@ -27,6 +27,7 @@ A recipe is a JSON file that configures everything domain-specific:
27
27
  "agent": {
28
28
  "name": "researcher",
29
29
  "model": "claude-opus-4-6",
30
+ "timezone": "America/Los_Angeles",
30
31
  "systemPrompt": "You are a ...",
31
32
  "maxTokens": 16384,
32
33
  "strategy": {
@@ -55,6 +56,10 @@ A recipe is a JSON file that configures everything domain-specific:
55
56
  }
56
57
  ```
57
58
 
59
+ `agent.timezone` is an IANA zone used only for times rendered to the agent.
60
+ Chronicle and MCPL protocol timestamps remain epoch/UTC. If the recipe omits
61
+ it, `AGENT_TIMEZONE` is used, then the process timezone.
62
+
58
63
  ### Recipe loading
59
64
 
60
65
  | Command | Behavior |
package/bun.lock CHANGED
@@ -5,10 +5,10 @@
5
5
  "": {
6
6
  "name": "connectome-host",
7
7
  "dependencies": {
8
- "@animalabs/agent-framework": "^0.6.0",
8
+ "@animalabs/agent-framework": "^0.6.4",
9
9
  "@animalabs/chronicle": "^0.2.0",
10
10
  "@animalabs/context-manager": "^0.5.5",
11
- "@animalabs/membrane": "^0.5.68",
11
+ "@animalabs/membrane": "^0.5.71",
12
12
  "@opentui/core": "^0.1.82",
13
13
  },
14
14
  "devDependencies": {
@@ -19,13 +19,13 @@
19
19
  },
20
20
  },
21
21
  "packages": {
22
- "@animalabs/agent-framework": ["@animalabs/agent-framework@0.6.0", "", { "dependencies": { "@animalabs/chronicle": "^0.2.0", "@animalabs/context-manager": "^0.5.5", "@animalabs/membrane": "^0.5.64", "chokidar": "^4.0.3", "discord.js": "^14.25.1", "ws": "^8.18.0" }, "bin": { "agent-framework-mcp": "dist/src/api/mcp-server.js" } }, "sha512-LsR2eYveMkC+aZTq0a58bEYoi8+Iq3XQSrSCZ1fbcg+mf9YNOk8ooBRUl0VM03fyPymdOf/fkVx2tdMZM7HjSQ=="],
22
+ "@animalabs/agent-framework": ["@animalabs/agent-framework@0.6.4", "", { "dependencies": { "@animalabs/chronicle": "^0.2.2", "@animalabs/context-manager": "^0.5.5", "@animalabs/membrane": "^0.5.64", "chokidar": "^4.0.3", "discord.js": "^14.25.1", "ws": "^8.18.0" }, "bin": { "agent-framework-mcp": "dist/src/api/mcp-server.js" } }, "sha512-jLDNNrE7rEI017loFwIx2zTxTBQDwXgo5taqGBrG84Ve0Kaiykj5Cm8pnGm8Mlidl2FtXp5v1NqpRntwZT2idA=="],
23
23
 
24
24
  "@animalabs/chronicle": ["@animalabs/chronicle@0.2.1", "", { "optionalDependencies": { "@animalabs/chronicle-darwin-arm64": "0.2.1", "@animalabs/chronicle-darwin-x64": "0.2.1", "@animalabs/chronicle-linux-arm64-gnu": "0.2.1", "@animalabs/chronicle-linux-x64-gnu": "0.2.1", "@animalabs/chronicle-win32-x64-msvc": "0.2.1" } }, "sha512-rDFc069yfi7BQUusgsXBwBdqljUWLByXSYaTF40AP+tonBh0No3eF9VzKTSy1I8DYg/jAUHIKtVdKDGsVMVubw=="],
25
25
 
26
26
  "@animalabs/context-manager": ["@animalabs/context-manager@0.5.5", "", { "dependencies": { "@animalabs/chronicle": "^0.2.0", "@animalabs/membrane": "^0.5.64" } }, "sha512-KWTluwU5VIZKIPF/IdfUbkvPNZ5zMxSfZjaxZb81IXe+DBPmjHuUVNIqgXIQ2Mv1pCn1vCNqieZ/iSrc6dFH8w=="],
27
27
 
28
- "@animalabs/membrane": ["@animalabs/membrane@0.5.68", "", { "dependencies": { "@anthropic-ai/sdk": "^0.52.0" } }, "sha512-MfSERy0ECxdBZ4NelHdZbqsP/ORWJsfeAI6vuyy6gP77aGcdJL8qf4XrUkOox5yZa5MqB50y0K58Ffv79jCnbg=="],
28
+ "@animalabs/membrane": ["@animalabs/membrane@0.5.71", "", { "dependencies": { "@anthropic-ai/sdk": "^0.52.0" } }, "sha512-Jos4Fh/kIbpOiJ43LKIFpmaCmWIzE6ly1x/4c5iHaMw8n6D8OJ2f4va7xo3vNdvfKWBbln4QghmNg4NWo3oFGA=="],
29
29
 
30
30
  "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.52.0", "", { "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-d4c+fg+xy9e46c8+YnrrgIQR45CZlAi7PwdzIfDXDM6ACxEZli1/fxhURsq30ZpMZy6LvSkr41jGq5aF5TD7rQ=="],
31
31
 
@@ -275,6 +275,10 @@
275
275
 
276
276
  "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
277
277
 
278
+ "@animalabs/agent-framework/@animalabs/chronicle": ["@animalabs/chronicle@0.2.6", "", { "optionalDependencies": { "@animalabs/chronicle-darwin-arm64": "0.2.6", "@animalabs/chronicle-darwin-x64": "0.2.6", "@animalabs/chronicle-linux-arm64-gnu": "0.2.6", "@animalabs/chronicle-linux-x64-gnu": "0.2.6", "@animalabs/chronicle-win32-x64-msvc": "0.2.6" } }, "sha512-+gbt2GR4SP8MnKxeqkJZf6oZn339fLiBtk6GHyBD/gHQ4e4eFqBpTaTd5eKycKBws0xWGcOqyiJFH4IdBkweUA=="],
279
+
280
+ "@animalabs/context-manager/@animalabs/membrane": ["@animalabs/membrane@0.5.68", "", { "dependencies": { "@anthropic-ai/sdk": "^0.52.0" } }, "sha512-MfSERy0ECxdBZ4NelHdZbqsP/ORWJsfeAI6vuyy6gP77aGcdJL8qf4XrUkOox5yZa5MqB50y0K58Ffv79jCnbg=="],
281
+
278
282
  "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
279
283
 
280
284
  "@discordjs/rest/@sapphire/snowflake": ["@sapphire/snowflake@3.5.5", "", {}, "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ=="],
@@ -239,11 +239,12 @@ ln -sfn ../../../connectome-local/context-manager/node_modules/@animalabs/chroni
239
239
  "agent": {
240
240
  "name": "<agent>", // = chronicle "self" participant
241
241
  "model": "<model-id>", // or gateway-prefixed, e.g. anthropic/claude-opus-4
242
+ "timezone": "America/Los_Angeles", // agent-visible wall clock only; storage stays UTC
242
243
  "systemPrompt": "<persona / or minimal>",
243
244
  "maxTokens": 16384, // response cap
244
245
  "maxStreamTokens": 180000, // recompile trigger; keep < model window
245
246
  "contextBudgetTokens": 160000, // SEE §11.1 — MUST fit under (window - maxTokens)
246
- "cacheTtl": "1h", // prompt-cache TTL; '1h' for residents replying slower than 5m (cache re-write premium dominates spend otherwise), omit/'5m' for rapid loops
247
+ "cacheTtl": "1h", // prompt-cache TTL; defaults to '1h', set '5m' explicitly for rapid sub-5m loops
247
248
  "strategy": {
248
249
  "type": "autobiographical",
249
250
  "headWindowTokens": 4000,
@@ -266,6 +267,10 @@ ln -sfn ../../../connectome-local/context-manager/node_modules/@animalabs/chroni
266
267
  }
267
268
  ```
268
269
 
270
+ Use `"cacheTtl": "1h"` for Connectome deployments. This is also the runtime
271
+ default when the field is omitted. Set `"5m"` only for intentionally rapid
272
+ workloads whose cache is normally reused within five minutes.
273
+
269
274
  **`.env`** (`chmod 600`). Common vars:
270
275
 
271
276
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animalabs/connectome-host",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "General-purpose agent TUI host with recipe-based configuration",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -11,10 +11,10 @@
11
11
  "postinstall": "test -d web && npm --prefix web install && npm --prefix web run build || true"
12
12
  },
13
13
  "dependencies": {
14
- "@animalabs/agent-framework": "^0.6.0",
14
+ "@animalabs/agent-framework": "^0.6.4",
15
15
  "@animalabs/chronicle": "^0.2.0",
16
16
  "@animalabs/context-manager": "^0.5.5",
17
- "@animalabs/membrane": "^0.5.68",
17
+ "@animalabs/membrane": "^0.5.71",
18
18
  "@opentui/core": "^0.1.82"
19
19
  },
20
20
  "devDependencies": {
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env bun
2
+ /** Import a Codex rollout JSONL as a KV-stable OpenAI Responses session. */
3
+
4
+ import {
5
+ basename,
6
+ dirname,
7
+ join,
8
+ resolve,
9
+ } from 'node:path';
10
+ import {
11
+ mkdirSync,
12
+ readFileSync,
13
+ writeFileSync,
14
+ } from 'node:fs';
15
+ import { JsStore } from '@animalabs/chronicle';
16
+ import { ContextManager } from '@animalabs/context-manager';
17
+ import type { ContentBlock } from '@animalabs/membrane';
18
+ import { SessionManager } from '../src/session-manager.js';
19
+
20
+ interface RolloutRecord {
21
+ timestamp?: string;
22
+ type?: string;
23
+ payload?: Record<string, unknown>;
24
+ }
25
+
26
+ interface NativeItem {
27
+ type?: string;
28
+ id?: string;
29
+ role?: string;
30
+ [key: string]: unknown;
31
+ }
32
+
33
+ interface EffectiveItem {
34
+ item: NativeItem;
35
+ timestamp?: string;
36
+ sourceLine: number;
37
+ restoredByCompaction: boolean;
38
+ }
39
+
40
+ function usage(): never {
41
+ console.error('Usage: bun scripts/import-codex-rollout.ts <rollout.jsonl> --out <instance-dir> [--agent <name>]');
42
+ process.exit(1);
43
+ }
44
+
45
+ function parseArgs(argv: string[]) {
46
+ const args = argv.slice(2);
47
+ if (!args[0] || args[0].startsWith('-')) usage();
48
+ const source = resolve(args[0]);
49
+ let outDir = '';
50
+ let agentName = 'Codex';
51
+ for (let i = 1; i < args.length; i++) {
52
+ if (args[i] === '--out') outDir = resolve(args[++i] ?? '');
53
+ else if (args[i] === '--agent') agentName = args[++i] ?? usage();
54
+ else usage();
55
+ }
56
+ if (!outDir) usage();
57
+ return { source, outDir, agentName };
58
+ }
59
+
60
+ /** Reconstruct the provider's current input window. A `compacted` record
61
+ * replaces all prior response-item history with its canonical
62
+ * `replacement_history`; later response items append normally. */
63
+ export function reconstructEffectiveHistory(records: RolloutRecord[]): EffectiveItem[] {
64
+ let history: EffectiveItem[] = [];
65
+ for (const [index, record] of records.entries()) {
66
+ if (record.type === 'compacted') {
67
+ const replacement = record.payload?.replacement_history;
68
+ if (Array.isArray(replacement)) {
69
+ history = replacement.map(item => ({
70
+ item: item as NativeItem,
71
+ timestamp: record.timestamp,
72
+ sourceLine: index + 1,
73
+ restoredByCompaction: true,
74
+ }));
75
+ }
76
+ continue;
77
+ }
78
+ if (record.type === 'response_item' && record.payload) {
79
+ history.push({
80
+ item: record.payload as NativeItem,
81
+ timestamp: record.timestamp,
82
+ sourceLine: index + 1,
83
+ restoredByCompaction: false,
84
+ });
85
+ }
86
+ }
87
+ return history;
88
+ }
89
+
90
+ function textFromContent(content: unknown): string {
91
+ if (typeof content === 'string') return content;
92
+ if (!Array.isArray(content)) return '';
93
+ return content
94
+ .map(part => {
95
+ if (!part || typeof part !== 'object') return '';
96
+ const value = part as Record<string, unknown>;
97
+ return typeof value.text === 'string' ? value.text
98
+ : typeof value.refusal === 'string' ? value.refusal
99
+ : '';
100
+ })
101
+ .filter(Boolean)
102
+ .join('\n');
103
+ }
104
+
105
+ /** A human-readable Chronicle projection. `rawItem` is the load-bearing field:
106
+ * the Responses formatter emits it verbatim and ignores the projection. */
107
+ export function projectItem(item: NativeItem): ContentBlock[] {
108
+ const carrier = <T extends ContentBlock>(block: T): ContentBlock =>
109
+ ({ ...block, rawItem: item } as ContentBlock);
110
+
111
+ switch (item.type) {
112
+ case 'message':
113
+ return [carrier({ type: 'text', text: textFromContent(item.content) })];
114
+ case 'reasoning':
115
+ if (typeof item.encrypted_content === 'string') {
116
+ return [carrier({ type: 'redacted_thinking', data: item.encrypted_content })];
117
+ }
118
+ return [carrier({ type: 'thinking', thinking: textFromContent(item.summary) })];
119
+ case 'compaction':
120
+ return [carrier({
121
+ type: 'redacted_thinking',
122
+ data: typeof item.encrypted_content === 'string' ? item.encrypted_content : '',
123
+ })];
124
+ case 'function_call':
125
+ return [carrier({
126
+ type: 'tool_use',
127
+ id: String(item.call_id ?? item.id ?? ''),
128
+ name: String(item.name ?? ''),
129
+ input: (() => {
130
+ try { return JSON.parse(String(item.arguments ?? '{}')) as Record<string, unknown>; }
131
+ catch { return { _rawArguments: item.arguments }; }
132
+ })(),
133
+ })];
134
+ case 'function_call_output':
135
+ return [carrier({
136
+ type: 'tool_result',
137
+ toolUseId: String(item.call_id ?? ''),
138
+ content: typeof item.output === 'string' ? item.output : JSON.stringify(item.output ?? null),
139
+ })];
140
+ default:
141
+ // Custom tool calls/outputs and future item types stay opaque. The
142
+ // zero-width projection avoids injecting synthetic text into the model.
143
+ return [carrier({ type: 'text', text: '' })];
144
+ }
145
+ }
146
+
147
+ function participantFor(item: NativeItem, agentName: string): string {
148
+ if (item.type === 'message') return item.role === 'assistant' ? agentName : 'user';
149
+ if (item.type === 'reasoning' || item.type === 'compaction' ||
150
+ item.type === 'function_call' || item.type === 'custom_tool_call') return agentName;
151
+ return 'user';
152
+ }
153
+
154
+ function writeRecipe(instanceDir: string, agentName: string): string {
155
+ const recipeDir = join(instanceDir, 'recipes');
156
+ mkdirSync(recipeDir, { recursive: true });
157
+ const path = join(recipeDir, 'codex-rollout.json');
158
+ const recipe = {
159
+ name: `${agentName} rollout revival`,
160
+ description: 'Stateless OpenAI Responses continuation imported from a Codex rollout.',
161
+ version: '1',
162
+ agent: {
163
+ name: agentName,
164
+ provider: 'openai-responses',
165
+ model: 'gpt-5.6-sol',
166
+ systemPrompt: 'The native imported Responses history contains the authoritative developer and system instructions.',
167
+ maxTokens: 128000,
168
+ maxStreamTokens: 900000,
169
+ contextBudgetTokens: 1000000,
170
+ strategy: { type: 'passthrough' },
171
+ responses: {
172
+ reasoningEffort: 'high',
173
+ reasoningContext: 'all_turns',
174
+ compactThreshold: 850000,
175
+ },
176
+ },
177
+ modules: {
178
+ subagents: false,
179
+ lessons: false,
180
+ retrieval: false,
181
+ wake: false,
182
+ },
183
+ };
184
+ writeFileSync(path, JSON.stringify(recipe, null, 2) + '\n');
185
+ return path;
186
+ }
187
+
188
+ async function main() {
189
+ const opts = parseArgs(process.argv);
190
+ const raw = readFileSync(opts.source, 'utf8');
191
+ const records = raw.trim().split('\n').filter(Boolean).map((line, index) => {
192
+ try { return JSON.parse(line) as RolloutRecord; }
193
+ catch (error) { throw new Error(`Invalid JSON at ${opts.source}:${index + 1}: ${String(error)}`); }
194
+ });
195
+ const history = reconstructEffectiveHistory(records);
196
+ if (history.length === 0) throw new Error('Rollout contains no effective response items.');
197
+
198
+ const dataDir = join(opts.outDir, 'data');
199
+ const sourceDir = join(opts.outDir, 'source');
200
+ mkdirSync(sourceDir, { recursive: true });
201
+ const snapshotPath = join(sourceDir, basename(opts.source));
202
+ writeFileSync(snapshotPath, raw);
203
+
204
+ const sessionManager = new SessionManager(dataDir);
205
+ const session = sessionManager.createSession(`Codex ${basename(opts.source).replace(/^rollout-|\.jsonl$/g, '')}`);
206
+ const storePath = sessionManager.getStorePath(session.id);
207
+ const store = JsStore.openOrCreate({ path: storePath });
208
+ try {
209
+ const cm = await ContextManager.open({ store });
210
+ try {
211
+ for (const entry of history) {
212
+ cm.addMessage(
213
+ participantFor(entry.item, opts.agentName),
214
+ projectItem(entry.item),
215
+ {
216
+ sourceId: entry.item.id ?? `rollout-line-${entry.sourceLine}`,
217
+ codexRollout: {
218
+ sourceLine: entry.sourceLine,
219
+ sourceTimestamp: entry.timestamp,
220
+ restoredByCompaction: entry.restoredByCompaction,
221
+ nativeType: entry.item.type,
222
+ },
223
+ },
224
+ );
225
+ }
226
+ } finally {
227
+ await cm.close?.();
228
+ }
229
+ } finally {
230
+ store.close?.();
231
+ }
232
+
233
+ const meta = records.find(record => record.type === 'session_meta')?.payload ?? {};
234
+ const turnContexts = records.filter(record => record.type === 'turn_context');
235
+ const latestTurn = turnContexts.at(-1)?.payload ?? {};
236
+ const compacted = records.filter(record => record.type === 'compacted');
237
+ const sidecarPath = join(dirname(storePath), `${session.id}.import-source.json`);
238
+ writeFileSync(sidecarPath, JSON.stringify({
239
+ source: opts.source,
240
+ snapshot: snapshotPath,
241
+ agentName: opts.agentName,
242
+ importedAt: new Date().toISOString(),
243
+ originalRecordCount: records.length,
244
+ importedMessageCount: history.length,
245
+ effectiveItemCount: history.length,
246
+ compactionCount: compacted.length,
247
+ latestWindowId: compacted.at(-1)?.payload?.window_id ?? null,
248
+ model: latestTurn.model ?? 'gpt-5.6-sol',
249
+ sessionMeta: meta,
250
+ }, null, 2) + '\n');
251
+
252
+ const index = sessionManager.load();
253
+ index.sessions[session.id]!.messageCount = history.length;
254
+ sessionManager.save(index);
255
+ const recipePath = writeRecipe(opts.outDir, opts.agentName);
256
+ writeFileSync(join(opts.outDir, '.env.example'), [
257
+ `DATA_DIR=${dataDir}`,
258
+ 'OPENAI_API_KEY=',
259
+ '',
260
+ ].join('\n'));
261
+ writeFileSync(join(opts.outDir, 'README.md'), [
262
+ '# Codex rollout Connectome instance',
263
+ '',
264
+ `Imported from \`${opts.source}\`.`,
265
+ '',
266
+ 'Run from the connectome-host checkout:',
267
+ '',
268
+ '```sh',
269
+ `DATA_DIR=${dataDir} OPENAI_API_KEY=... bun src/index.ts ${recipePath}`,
270
+ '```',
271
+ '',
272
+ 'The session uses provider-native Responses items and passthrough context management.',
273
+ 'Do not run the Claude warmup importer; it would normalize the native prefix.',
274
+ '',
275
+ ].join('\n'));
276
+
277
+ console.log(JSON.stringify({
278
+ instanceDir: opts.outDir,
279
+ dataDir,
280
+ recipePath,
281
+ sessionId: session.id,
282
+ importedItems: history.length,
283
+ compactions: compacted.length,
284
+ snapshotPath,
285
+ }, null, 2));
286
+ }
287
+
288
+ if (import.meta.main) await main();