electric-ax 0.2.21 → 0.2.23

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.
@@ -1,9 +1,8 @@
1
- import { createEntityStreamDB, entityApiUrl } from "./entity-stream-db-UBN2gowE.js";
2
- import { buildSections, createEntityIncludesQuery, normalizeEntityTimelineData } from "@electric-ax/agents-runtime";
3
- import { createOptimisticAction } from "@durable-streams/state/db";
1
+ import { createEntityStreamDB, entityApiUrl } from "./entity-stream-db-thqr5ONI.js";
2
+ import { createEntityTimelineQuery, createPendingTimelineOrder } from "@electric-ax/agents-runtime";
4
3
  import React, { useEffect, useMemo, useRef, useState } from "react";
5
4
  import { Box, Newline, Text, render, useInput } from "ink";
6
- import { useLiveQuery } from "@tanstack/react-db";
5
+ import { createOptimisticAction, useLiveQuery } from "@tanstack/react-db";
7
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
7
 
9
8
  //#region src/observe-ui.tsx
@@ -23,6 +22,159 @@ function formatTime(iso) {
23
22
  function truncate(s, max) {
24
23
  return s.length > max ? s.slice(0, max - 3) + `...` : s;
25
24
  }
25
+ function createMessageSendBody(text, opts) {
26
+ return {
27
+ ...opts?.key ? { key: opts.key } : {},
28
+ payload: { text }
29
+ };
30
+ }
31
+ const OPTIMISTIC_INBOX_ORDER_START = Number.MAX_SAFE_INTEGER - 1e6;
32
+ const SEND_TXID_TIMEOUT_MS = 1e4;
33
+ let optimisticInboxOrderIndex = OPTIMISTIC_INBOX_ORDER_START;
34
+ function nextOptimisticInboxOrderIndex() {
35
+ optimisticInboxOrderIndex += 1;
36
+ if (optimisticInboxOrderIndex >= Number.MAX_SAFE_INTEGER) optimisticInboxOrderIndex = OPTIMISTIC_INBOX_ORDER_START;
37
+ return optimisticInboxOrderIndex;
38
+ }
39
+ function createOptimisticInboxKey(pendingOrderIndex) {
40
+ return `optimistic-${Date.now()}-${pendingOrderIndex}`;
41
+ }
42
+ async function readSendTxid(res) {
43
+ const data = await res.json().catch(() => null);
44
+ const txid = data?.txid;
45
+ if (typeof txid === `string` || typeof txid === `number`) return String(txid);
46
+ throw new Error(`Send response did not include txid`);
47
+ }
48
+ function GutterLine({ prefix = `│ `, color, dimColor, children }) {
49
+ return /* @__PURE__ */ jsxs(Box, {
50
+ width: "100%",
51
+ children: [/* @__PURE__ */ jsx(Text, {
52
+ color,
53
+ dimColor,
54
+ children: prefix
55
+ }), /* @__PURE__ */ jsx(Box, {
56
+ flexGrow: 1,
57
+ flexShrink: 1,
58
+ children: /* @__PURE__ */ jsx(Text, {
59
+ color,
60
+ dimColor,
61
+ wrap: "wrap",
62
+ children
63
+ })
64
+ })]
65
+ });
66
+ }
67
+ function payloadText(payload) {
68
+ if (typeof payload === `string`) return payload;
69
+ if (typeof payload === `object` && payload !== null) {
70
+ const record = payload;
71
+ return typeof record.text === `string` ? record.text : JSON.stringify(payload);
72
+ }
73
+ return String(payload ?? ``);
74
+ }
75
+ function resultText(result) {
76
+ if (result === void 0) return void 0;
77
+ return typeof result === `string` ? result : JSON.stringify(result);
78
+ }
79
+ function isPlainObject(value) {
80
+ return typeof value === `object` && value !== null && !Array.isArray(value);
81
+ }
82
+ function timelineRowsToDisplayRows(rows) {
83
+ let userMessageCount = 0;
84
+ return rows.flatMap((row) => {
85
+ if (row.inbox) {
86
+ const section = {
87
+ kind: `user_message`,
88
+ from: row.inbox.from,
89
+ text: payloadText(row.inbox.payload),
90
+ timestamp: Date.parse(row.inbox.timestamp || ``) || Date.now(),
91
+ isInitial: userMessageCount === 0
92
+ };
93
+ userMessageCount++;
94
+ return [{
95
+ kind: `section`,
96
+ key: row.$key,
97
+ section
98
+ }];
99
+ }
100
+ if (row.wake) {
101
+ const timestamp = Date.parse(row.wake.payload.timestamp);
102
+ return [{
103
+ kind: `section`,
104
+ key: row.$key,
105
+ section: {
106
+ kind: `wake`,
107
+ payload: row.wake.payload,
108
+ timestamp: Number.isFinite(timestamp) ? timestamp : Date.now()
109
+ }
110
+ }];
111
+ }
112
+ if (row.run) return [{
113
+ kind: `run`,
114
+ key: row.$key,
115
+ run: row.run
116
+ }];
117
+ if (row.error) return [{
118
+ kind: `section`,
119
+ key: row.$key,
120
+ section: {
121
+ kind: `agent_response`,
122
+ items: [],
123
+ error: row.error.error_code ? `${row.error.error_code}: ${row.error.message}` : row.error.message
124
+ }
125
+ }];
126
+ return [];
127
+ });
128
+ }
129
+ function textContent(item) {
130
+ return typeof item?.content === `string` ? item.content : ``;
131
+ }
132
+ function compareTimelineOrderValues(left, right) {
133
+ if (typeof left === `number` && typeof right === `number`) return left - right;
134
+ return String(left).localeCompare(String(right));
135
+ }
136
+ function runItemKind(item) {
137
+ return item.text ? `text` : `toolCall`;
138
+ }
139
+ function runItemKey(item) {
140
+ return item.text?.key ?? item.toolCall?.key ?? ``;
141
+ }
142
+ function compareRunItems(left, right) {
143
+ const orderCompare = compareTimelineOrderValues(left.text?.order ?? left.toolCall?.order ?? `~`, right.text?.order ?? right.toolCall?.order ?? `~`);
144
+ if (orderCompare !== 0) return orderCompare;
145
+ const kindCompare = runItemKind(left).localeCompare(runItemKind(right));
146
+ if (kindCompare !== 0) return kindCompare;
147
+ return runItemKey(left).localeCompare(runItemKey(right));
148
+ }
149
+ function runItemsToContentItems(items) {
150
+ const contentItems = [];
151
+ for (const item of items) {
152
+ if (item.text) {
153
+ const content = textContent(item.text);
154
+ if (content.trim().length > 0) contentItems.push({
155
+ kind: `text`,
156
+ text: content
157
+ });
158
+ continue;
159
+ }
160
+ if (!item.toolCall) continue;
161
+ contentItems.push({
162
+ kind: `tool_call`,
163
+ toolCallId: item.toolCall.tool_call_id ?? item.toolCall.key,
164
+ toolName: item.toolCall.tool_name,
165
+ args: isPlainObject(item.toolCall.args) ? item.toolCall.args : {},
166
+ status: item.toolCall.status,
167
+ result: resultText(item.toolCall.result),
168
+ error: item.toolCall.error,
169
+ isError: item.toolCall.status === `failed`
170
+ });
171
+ }
172
+ return contentItems;
173
+ }
174
+ function runErrorsText(errors) {
175
+ const messages = errors.map((error) => error.error_code ? `${error.error_code}: ${error.message}` : error.message).filter(Boolean);
176
+ return messages.length > 0 ? messages.join(`; `) : void 0;
177
+ }
26
178
  function UserMessageView({ msg }) {
27
179
  const time = formatTime(msg.timestamp);
28
180
  const payload = msg.payload;
@@ -42,9 +194,9 @@ function UserMessageView({ msg }) {
42
194
  }), time ? /* @__PURE__ */ jsx(Text, {
43
195
  dimColor: true,
44
196
  children: ` ${time}`
45
- }) : null] }), text.split(`\n`).map((line, i) => /* @__PURE__ */ jsx(Text, {
197
+ }) : null] }), text.split(`\n`).map((line, i) => /* @__PURE__ */ jsx(GutterLine, {
46
198
  color: "white",
47
- children: `│ ${line}`
199
+ children: line
48
200
  }, i))]
49
201
  });
50
202
  }
@@ -58,9 +210,9 @@ function AgentTextView({ text, accumulatedText, label }) {
58
210
  bold: true,
59
211
  color: "green",
60
212
  children: `┌ ${label ?? `assistant`}`
61
- }) }), lines.map((line, i) => /* @__PURE__ */ jsx(Text, {
213
+ }) }), lines.map((line, i) => /* @__PURE__ */ jsx(GutterLine, {
62
214
  color: "white",
63
- children: `│ ${line}${i === lines.length - 1 ? cursor : ``}`
215
+ children: `${line}${i === lines.length - 1 ? cursor : ``}`
64
216
  }, i))]
65
217
  });
66
218
  }
@@ -107,9 +259,10 @@ function ToolResultView({ result }) {
107
259
  const remaining = lines.length - maxLines;
108
260
  return /* @__PURE__ */ jsxs(Box, {
109
261
  flexDirection: "column",
110
- children: [shown.map((line, i) => /* @__PURE__ */ jsx(Text, {
262
+ children: [shown.map((line, i) => /* @__PURE__ */ jsx(GutterLine, {
263
+ prefix: " │ ",
111
264
  dimColor: true,
112
- children: ` │ ${truncate(line, 100)}`
265
+ children: truncate(line, 100)
113
266
  }, i)), remaining > 0 ? /* @__PURE__ */ jsx(Text, {
114
267
  dimColor: true,
115
268
  children: ` │ ... ${remaining} more lines`
@@ -120,25 +273,28 @@ function MessageInput({ db, baseUrl, entityUrl, identity, headers, disabled }) {
120
273
  const [value, setValue] = useState(``);
121
274
  const [error, setError] = useState(null);
122
275
  const sendAction = useMemo(() => createOptimisticAction({
123
- onMutate: ({ text }) => {
276
+ onMutate: ({ text, key, pendingOrderIndex }) => {
277
+ const now = new Date().toISOString();
124
278
  db.collections.inbox.insert({
125
- key: `optimistic-${Date.now()}`,
279
+ key,
280
+ _timeline_order: createPendingTimelineOrder(pendingOrderIndex),
126
281
  from: identity,
282
+ from_principal: identity,
127
283
  payload: { text },
128
- timestamp: new Date().toISOString()
284
+ timestamp: now,
285
+ mode: `immediate`,
286
+ status: `processed`,
287
+ processed_at: now
129
288
  });
130
289
  },
131
- mutationFn: async ({ text }) => {
290
+ mutationFn: async ({ text, key }) => {
132
291
  const res = await fetch(entityApiUrl(baseUrl, entityUrl, `/send`), {
133
292
  method: `POST`,
134
293
  headers: {
135
294
  "content-type": `application/json`,
136
295
  ...headers
137
296
  },
138
- body: JSON.stringify({
139
- from: identity,
140
- payload: { text }
141
- })
297
+ body: JSON.stringify(createMessageSendBody(text, { key }))
142
298
  });
143
299
  if (!res.ok) {
144
300
  const body = await res.text().catch(() => ``);
@@ -153,6 +309,7 @@ function MessageInput({ db, baseUrl, entityUrl, identity, headers, disabled }) {
153
309
  }
154
310
  throw new Error(message);
155
311
  }
312
+ await db.utils.awaitTxId(await readSendTxid(res), SEND_TXID_TIMEOUT_MS);
156
313
  }
157
314
  }), [
158
315
  db,
@@ -166,7 +323,12 @@ function MessageInput({ db, baseUrl, entityUrl, identity, headers, disabled }) {
166
323
  if (key.return) {
167
324
  if (value.trim()) {
168
325
  setError(null);
169
- const tx = sendAction({ text: value.trim() });
326
+ const pendingOrderIndex = nextOptimisticInboxOrderIndex();
327
+ const tx = sendAction({
328
+ text: value.trim(),
329
+ key: createOptimisticInboxKey(pendingOrderIndex),
330
+ pendingOrderIndex
331
+ });
170
332
  setValue(``);
171
333
  tx.isPersisted.promise.catch((err) => {
172
334
  setError(err.message);
@@ -204,6 +366,7 @@ function MessageInput({ db, baseUrl, entityUrl, identity, headers, disabled }) {
204
366
  function AgentResponseView({ section, label, isStreaming }) {
205
367
  return /* @__PURE__ */ jsxs(Box, {
206
368
  flexDirection: "column",
369
+ width: "100%",
207
370
  children: [
208
371
  section.items.map((item, i) => {
209
372
  if (item.kind === `text`) return /* @__PURE__ */ jsx(AgentTextView, {
@@ -233,6 +396,31 @@ function AgentResponseView({ section, label, isStreaming }) {
233
396
  ]
234
397
  });
235
398
  }
399
+ function AgentRunView({ run, label, isStreaming }) {
400
+ const { data: items = [] } = useLiveQuery((q) => run.items ? q.from({ item: run.items }) : void 0, [run.items]);
401
+ const { data: errors = [] } = useLiveQuery((q) => run.errors ? q.from({ error: run.errors }) : void 0, [run.errors]);
402
+ const sortedItems = useMemo(() => [...items].sort(compareRunItems), [items]);
403
+ const runErrors = useMemo(() => runErrorsText(errors), [errors]);
404
+ const finishReason = run.status === `failed` && run.finish_reason ? `finish_reason=${run.finish_reason}` : void 0;
405
+ const section = useMemo(() => ({
406
+ kind: `agent_response`,
407
+ items: runItemsToContentItems(sortedItems),
408
+ ...run.status === `completed` && { done: true },
409
+ ...runErrors || finishReason ? { error: runErrors || finishReason } : {},
410
+ ...run.tokens && { tokens: run.tokens }
411
+ }), [
412
+ finishReason,
413
+ run.status,
414
+ run.tokens,
415
+ runErrors,
416
+ sortedItems
417
+ ]);
418
+ return /* @__PURE__ */ jsx(AgentResponseView, {
419
+ section,
420
+ label,
421
+ isStreaming
422
+ });
423
+ }
236
424
  function wakeReason(section) {
237
425
  const { payload } = section;
238
426
  if (payload.timeout) return `timeout`;
@@ -265,64 +453,59 @@ function ObserveExitHotkey({ onExit }) {
265
453
  return null;
266
454
  }
267
455
  function ObserveView({ db, entityUrl, baseUrl, identity, headers }) {
268
- const timelineQuery = useMemo(() => createEntityIncludesQuery(db), [db]);
456
+ const timelineQuery = useMemo(() => createEntityTimelineQuery(db), [db]);
269
457
  const { data: timelineRows = [] } = useLiveQuery(timelineQuery, [timelineQuery]);
270
- const timelineData = normalizeEntityTimelineData(timelineRows[0] ?? {
271
- runs: [],
272
- inbox: [],
273
- wakes: [],
274
- signals: [],
275
- contextInserted: [],
276
- contextRemoved: [],
277
- entities: []
278
- });
279
- const typedRuns = timelineData.runs;
280
- const typedInbox = timelineData.inbox;
281
- const typedWakes = timelineData.wakes;
282
- const timeline = useMemo(() => buildSections(typedRuns, typedInbox, typedWakes), [
283
- typedRuns,
284
- typedInbox,
285
- typedWakes
286
- ]);
287
- const { data: stopped = [] } = useLiveQuery((q) => q.from({ entityStopped: db.collections.entityStopped }), [db]);
288
- const closed = stopped.length > 0;
458
+ const displayRows = useMemo(() => timelineRowsToDisplayRows(timelineRows), [timelineRows]);
459
+ const { data: entityStoppedRows = [] } = useLiveQuery((q) => q.from({ stopped: db.collections.entityStopped }), [db.collections.entityStopped]);
460
+ const closed = entityStoppedRows.length > 0;
289
461
  const lastAgentIndex = useMemo(() => {
290
- for (let i = timeline.length - 1; i >= 0; i--) if (timeline[i].kind === `agent_response`) return i;
462
+ for (let i = displayRows.length - 1; i >= 0; i--) {
463
+ const row = displayRows[i];
464
+ if (row.kind === `run` || row.kind === `section` && row.section.kind === `agent_response`) return i;
465
+ }
291
466
  return -1;
292
- }, [timeline]);
467
+ }, [displayRows]);
293
468
  return /* @__PURE__ */ jsxs(Box, {
294
469
  flexDirection: "column",
295
470
  children: [
296
471
  /* @__PURE__ */ jsx(Box, {
297
472
  marginBottom: 1,
473
+ width: "100%",
298
474
  children: /* @__PURE__ */ jsx(Text, {
299
475
  dimColor: true,
300
476
  children: `Observing ${entityUrl}${closed ? `` : ` (Ctrl+C to stop)`}`
301
477
  })
302
478
  }),
303
479
  /* @__PURE__ */ jsxs(Box, {
480
+ width: "100%",
304
481
  borderStyle: "single",
305
482
  borderColor: "gray",
306
483
  flexDirection: "column",
307
484
  paddingX: 1,
308
485
  children: [
309
- timeline.length === 0 ? /* @__PURE__ */ jsx(Text, {
486
+ displayRows.length === 0 ? /* @__PURE__ */ jsx(Text, {
310
487
  dimColor: true,
311
488
  children: "Waiting for events..."
312
489
  }) : null,
313
- timeline.map((section, i) => {
490
+ displayRows.map((row, i) => {
491
+ if (row.kind === `run`) return /* @__PURE__ */ jsx(AgentRunView, {
492
+ run: row.run,
493
+ label: entityUrl,
494
+ isStreaming: !closed && i === lastAgentIndex && row.run.status !== `completed`
495
+ }, row.key);
496
+ const { section } = row;
314
497
  if (section.kind === `user_message`) return /* @__PURE__ */ jsx(UserMessageView, { msg: {
315
498
  key: `timeline-${i}`,
316
499
  from: section.from ?? `user`,
317
500
  payload: { text: section.text },
318
501
  timestamp: new Date(section.timestamp).toISOString()
319
- } }, `msg-${i}`);
320
- if (section.kind === `wake`) return /* @__PURE__ */ jsx(WakeView, { section }, `wake-${i}`);
502
+ } }, row.key);
503
+ if (section.kind === `wake`) return /* @__PURE__ */ jsx(WakeView, { section }, row.key);
321
504
  return /* @__PURE__ */ jsx(AgentResponseView, {
322
505
  section,
323
506
  label: entityUrl,
324
507
  isStreaming: !closed && i === lastAgentIndex && !section.done
325
- }, `agent-${i}`);
508
+ }, row.key);
326
509
  }),
327
510
  closed ? /* @__PURE__ */ jsx(Box, {
328
511
  marginTop: 1,
@@ -420,4 +603,4 @@ function renderObserve(opts) {
420
603
  }
421
604
 
422
605
  //#endregion
423
- export { AgentTextView, MessageInput, ObserveExitHotkey, ToolCallView, ToolResultView, UserMessageView, formatTime, renderObserve, truncate };
606
+ export { AgentTextView, MessageInput, ObserveExitHotkey, ObserveView, ToolCallView, ToolResultView, UserMessageView, compareRunItems, createMessageSendBody, formatTime, renderObserve, runItemKey, runItemKind, runItemsToContentItems, truncate };
package/dist/start.cjs CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  const require_chunk = require('./chunk-BCwAaXi7.cjs');
3
- const require_version = require('./version-BU1gpSFU.cjs');
3
+ const require_version = require('./version-Dp_ZxaMK.cjs');
4
4
  const __electric_ax_agents_runtime = require_chunk.__toESM(require("@electric-ax/agents-runtime"));
5
+ const node_os = require_chunk.__toESM(require("node:os"));
5
6
  const node_url = require_chunk.__toESM(require("node:url"));
6
7
  const __electric_ax_agents_server_headers = require_chunk.__toESM(require("@electric-ax/agents/server-headers"));
7
8
  const node_child_process = require_chunk.__toESM(require("node:child_process"));
@@ -11,8 +12,13 @@ const __electric_ax_agents = require_chunk.__toESM(require("@electric-ax/agents"
11
12
  const DEFAULT_ELECTRIC_AGENTS_PORT = 4437;
12
13
  const DEFAULT_COMPOSE_PROJECT_NAME = `electric-agents`;
13
14
  const DEFAULT_PULL_WAKE_RUNNER_ID = `builtin-agents`;
14
- const DEFAULT_PULL_WAKE_OWNER_PRINCIPAL = `/principal/system%3Abuiltin-agents`;
15
15
  const PRINCIPAL_URL_PREFIX = `/principal/`;
16
+ const PRINCIPAL_KEY_PREFIXES = new Set([
17
+ `user`,
18
+ `agent`,
19
+ `service`,
20
+ `system`
21
+ ]);
16
22
  const DOCKER_COMPOSE_FILE = (0, node_url.fileURLToPath)(new URL(`../docker-compose.full.yml`, require("url").pathToFileURL(__filename).href));
17
23
  function resolveElectricAgentsPort(env = process.env, fileEnv = require_version.readDotEnvFile()) {
18
24
  const raw = env.ELECTRIC_AGENTS_PORT?.trim() || fileEnv.ELECTRIC_AGENTS_PORT?.trim();
@@ -58,6 +64,14 @@ function runnerIdFromIdentity(identity) {
58
64
  function principalUrlFromConfig(value) {
59
65
  return value.startsWith(PRINCIPAL_URL_PREFIX) ? value : `${PRINCIPAL_URL_PREFIX}${encodeURIComponent(value)}`;
60
66
  }
67
+ function principalKeyFromIdentity(identity) {
68
+ const colon = identity.indexOf(`:`);
69
+ if (colon > 0 && PRINCIPAL_KEY_PREFIXES.has(identity.slice(0, colon))) return identity;
70
+ return `user:${identity}`;
71
+ }
72
+ function defaultPullWakeOwnerPrincipal() {
73
+ return principalUrlFromConfig(principalKeyFromIdentity(`${(0, node_os.userInfo)().username}@${(0, node_os.hostname)()}`));
74
+ }
61
75
  function resolvePullWakeRunnerId(env = process.env, fileEnv = require_version.readDotEnvFile()) {
62
76
  return readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_PULL_WAKE_RUNNER_ID`, `PULL_WAKE_RUNNER_ID`]) ?? runnerIdFromIdentity(readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_IDENTITY`]));
63
77
  }
@@ -65,8 +79,8 @@ function resolvePullWakeOwnerPrincipal(env = process.env, fileEnv = require_vers
65
79
  const principal = readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_PRINCIPAL`]);
66
80
  if (principal) return principalUrlFromConfig(principal);
67
81
  const identity = readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_IDENTITY`]);
68
- if (identity) return principalUrlFromConfig(identity);
69
- return DEFAULT_PULL_WAKE_OWNER_PRINCIPAL;
82
+ if (identity) return principalUrlFromConfig(principalKeyFromIdentity(identity));
83
+ return defaultPullWakeOwnerPrincipal();
70
84
  }
71
85
  function parseAdditionalServerHeaders(env, fileEnv) {
72
86
  const raw = readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_SERVER_HEADERS`]);
@@ -89,6 +103,9 @@ function parseAdditionalServerHeaders(env, fileEnv) {
89
103
  function resolveServerHeaders(env, fileEnv) {
90
104
  return (0, __electric_ax_agents_server_headers.mergeElectricPrincipalHeader)(parseAdditionalServerHeaders(env, fileEnv), readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_PRINCIPAL`]));
91
105
  }
106
+ function resolveBuiltinServerHeaders(env, fileEnv, ownerPrincipal) {
107
+ return mergeHeaders(resolveServerHeaders(env, fileEnv), (0, __electric_ax_agents_server_headers.mergeElectricPrincipalHeader)(void 0, ownerPrincipal));
108
+ }
92
109
  function mergeHeaders(...sources) {
93
110
  const headers = new Headers();
94
111
  for (const source of sources) {
@@ -227,7 +244,7 @@ async function startBuiltinAgentsServer(options, params = {}) {
227
244
  const anthropicApiKey = require_version.resolveAnthropicApiKey(options, env, fileEnv);
228
245
  const runnerId = resolvePullWakeRunnerId(env, fileEnv);
229
246
  const ownerPrincipal = resolvePullWakeOwnerPrincipal(env, fileEnv);
230
- const serverHeaders = mergeHeaders(resolveServerHeaders(env, fileEnv));
247
+ const serverHeaders = resolveBuiltinServerHeaders(env, fileEnv, ownerPrincipal);
231
248
  const agentServerUrl = params.agentServerUrl ?? env.ELECTRIC_AGENTS_URL?.trim() ?? `http://localhost:${resolveElectricAgentsPort(env, fileEnv)}`;
232
249
  process.env.ANTHROPIC_API_KEY = anthropicApiKey;
233
250
  await waitForElectricAgentsServer(agentServerUrl, { headers: serverHeaders });
@@ -235,6 +252,7 @@ async function startBuiltinAgentsServer(options, params = {}) {
235
252
  agentServerUrl,
236
253
  workingDirectory: cwd,
237
254
  loadProjectMcpConfig: true,
255
+ durableStreamsFetchCache: false,
238
256
  pullWake: {
239
257
  runnerId,
240
258
  ownerPrincipal,
@@ -261,6 +279,7 @@ exports.getStartedEnvironmentMessage = getStartedEnvironmentMessage
261
279
  exports.getStoppedEnvironmentMessage = getStoppedEnvironmentMessage
262
280
  exports.readDotEnvFile = require_version.readDotEnvFile
263
281
  exports.resolveAnthropicApiKey = require_version.resolveAnthropicApiKey
282
+ exports.resolveBuiltinServerHeaders = resolveBuiltinServerHeaders
264
283
  exports.resolveComposeProjectName = resolveComposeProjectName
265
284
  exports.resolveElectricAgentsPort = resolveElectricAgentsPort
266
285
  exports.resolvePullWakeOwnerPrincipal = resolvePullWakeOwnerPrincipal
package/dist/start.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer } from "./index-_AHIJdEA.cjs";
2
- export { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
1
+ import { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinServerHeaders, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer } from "./index-BGw1zeGA.cjs";
2
+ export { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinServerHeaders, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
package/dist/start.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer } from "./index-CQN8S3b9.js";
2
- export { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
1
+ import { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinServerHeaders, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer } from "./index-gzb1Ju8x.js";
2
+ export { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinServerHeaders, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
package/dist/start.js CHANGED
@@ -1,5 +1,6 @@
1
- import { ELECTRIC_AGENTS_SERVER_IMAGE_TAG, ELECTRIC_IMAGE_TAG, readDotEnvFile$1 as readDotEnvFile, resolveAnthropicApiKey$1 as resolveAnthropicApiKey } from "./version-BXm4o7Lz.js";
1
+ import { ELECTRIC_AGENTS_SERVER_IMAGE_TAG, ELECTRIC_IMAGE_TAG, readDotEnvFile$1 as readDotEnvFile, resolveAnthropicApiKey$1 as resolveAnthropicApiKey } from "./version-D62IOgCl.js";
2
2
  import { appendPathToUrl } from "@electric-ax/agents-runtime";
3
+ import { hostname, userInfo } from "node:os";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { mergeElectricPrincipalHeader } from "@electric-ax/agents/server-headers";
5
6
  import { spawn } from "node:child_process";
@@ -9,8 +10,13 @@ import { BuiltinAgentsServer } from "@electric-ax/agents";
9
10
  const DEFAULT_ELECTRIC_AGENTS_PORT = 4437;
10
11
  const DEFAULT_COMPOSE_PROJECT_NAME = `electric-agents`;
11
12
  const DEFAULT_PULL_WAKE_RUNNER_ID = `builtin-agents`;
12
- const DEFAULT_PULL_WAKE_OWNER_PRINCIPAL = `/principal/system%3Abuiltin-agents`;
13
13
  const PRINCIPAL_URL_PREFIX = `/principal/`;
14
+ const PRINCIPAL_KEY_PREFIXES = new Set([
15
+ `user`,
16
+ `agent`,
17
+ `service`,
18
+ `system`
19
+ ]);
14
20
  const DOCKER_COMPOSE_FILE = fileURLToPath(new URL(`../docker-compose.full.yml`, import.meta.url));
15
21
  function resolveElectricAgentsPort(env = process.env, fileEnv = readDotEnvFile()) {
16
22
  const raw = env.ELECTRIC_AGENTS_PORT?.trim() || fileEnv.ELECTRIC_AGENTS_PORT?.trim();
@@ -56,6 +62,14 @@ function runnerIdFromIdentity(identity) {
56
62
  function principalUrlFromConfig(value) {
57
63
  return value.startsWith(PRINCIPAL_URL_PREFIX) ? value : `${PRINCIPAL_URL_PREFIX}${encodeURIComponent(value)}`;
58
64
  }
65
+ function principalKeyFromIdentity(identity) {
66
+ const colon = identity.indexOf(`:`);
67
+ if (colon > 0 && PRINCIPAL_KEY_PREFIXES.has(identity.slice(0, colon))) return identity;
68
+ return `user:${identity}`;
69
+ }
70
+ function defaultPullWakeOwnerPrincipal() {
71
+ return principalUrlFromConfig(principalKeyFromIdentity(`${userInfo().username}@${hostname()}`));
72
+ }
59
73
  function resolvePullWakeRunnerId(env = process.env, fileEnv = readDotEnvFile()) {
60
74
  return readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_PULL_WAKE_RUNNER_ID`, `PULL_WAKE_RUNNER_ID`]) ?? runnerIdFromIdentity(readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_IDENTITY`]));
61
75
  }
@@ -63,8 +77,8 @@ function resolvePullWakeOwnerPrincipal(env = process.env, fileEnv = readDotEnvFi
63
77
  const principal = readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_PRINCIPAL`]);
64
78
  if (principal) return principalUrlFromConfig(principal);
65
79
  const identity = readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_IDENTITY`]);
66
- if (identity) return principalUrlFromConfig(identity);
67
- return DEFAULT_PULL_WAKE_OWNER_PRINCIPAL;
80
+ if (identity) return principalUrlFromConfig(principalKeyFromIdentity(identity));
81
+ return defaultPullWakeOwnerPrincipal();
68
82
  }
69
83
  function parseAdditionalServerHeaders(env, fileEnv) {
70
84
  const raw = readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_SERVER_HEADERS`]);
@@ -87,6 +101,9 @@ function parseAdditionalServerHeaders(env, fileEnv) {
87
101
  function resolveServerHeaders(env, fileEnv) {
88
102
  return mergeElectricPrincipalHeader(parseAdditionalServerHeaders(env, fileEnv), readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_PRINCIPAL`]));
89
103
  }
104
+ function resolveBuiltinServerHeaders(env, fileEnv, ownerPrincipal) {
105
+ return mergeHeaders(resolveServerHeaders(env, fileEnv), mergeElectricPrincipalHeader(void 0, ownerPrincipal));
106
+ }
90
107
  function mergeHeaders(...sources) {
91
108
  const headers = new Headers();
92
109
  for (const source of sources) {
@@ -225,7 +242,7 @@ async function startBuiltinAgentsServer(options, params = {}) {
225
242
  const anthropicApiKey = resolveAnthropicApiKey(options, env, fileEnv);
226
243
  const runnerId = resolvePullWakeRunnerId(env, fileEnv);
227
244
  const ownerPrincipal = resolvePullWakeOwnerPrincipal(env, fileEnv);
228
- const serverHeaders = mergeHeaders(resolveServerHeaders(env, fileEnv));
245
+ const serverHeaders = resolveBuiltinServerHeaders(env, fileEnv, ownerPrincipal);
229
246
  const agentServerUrl = params.agentServerUrl ?? env.ELECTRIC_AGENTS_URL?.trim() ?? `http://localhost:${resolveElectricAgentsPort(env, fileEnv)}`;
230
247
  process.env.ANTHROPIC_API_KEY = anthropicApiKey;
231
248
  await waitForElectricAgentsServer(agentServerUrl, { headers: serverHeaders });
@@ -233,6 +250,7 @@ async function startBuiltinAgentsServer(options, params = {}) {
233
250
  agentServerUrl,
234
251
  workingDirectory: cwd,
235
252
  loadProjectMcpConfig: true,
253
+ durableStreamsFetchCache: false,
236
254
  pullWake: {
237
255
  runnerId,
238
256
  ownerPrincipal,
@@ -254,4 +272,4 @@ async function startBuiltinAgentsServer(options, params = {}) {
254
272
  }
255
273
 
256
274
  //#endregion
257
- export { getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
275
+ export { getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinServerHeaders, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerPrincipal, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
@@ -40,13 +40,15 @@ function resolveAnthropicApiKey(options, env = process.env, fileEnv = readDotEnv
40
40
  * Default Docker image tags used by the CLI when launching
41
41
  * the dev environment via docker compose.
42
42
  *
43
- * For release builds these remain "latest".
43
+ * For release builds the agents-server image is pinned to the matching
44
+ * @electric-ax/agents-server version by the tsdown build.
44
45
  * The canary CI overwrites them to "canary" before building.
45
46
  */
46
- const injectedCliVersion = `0.2.21`;
47
+ const injectedCliVersion = `0.2.23`;
48
+ const injectedAgentsServerImageTag = `0.6.3`;
47
49
  const ELECTRIC_AX_CLI_VERSION = injectedCliVersion.startsWith(`__ELECTRIC_AX_`) ? `0.0.0` : injectedCliVersion;
48
50
  const ELECTRIC_IMAGE_TAG = `latest`;
49
- const ELECTRIC_AGENTS_SERVER_IMAGE_TAG = `latest`;
51
+ const ELECTRIC_AGENTS_SERVER_IMAGE_TAG = injectedAgentsServerImageTag.startsWith(`__ELECTRIC_`) ? `latest` : injectedAgentsServerImageTag;
50
52
 
51
53
  //#endregion
52
54
  export { ELECTRIC_AGENTS_SERVER_IMAGE_TAG, ELECTRIC_AX_CLI_VERSION, ELECTRIC_IMAGE_TAG, readDotEnvFile as readDotEnvFile$1, resolveAnthropicApiKey as resolveAnthropicApiKey$1 };
@@ -42,13 +42,15 @@ function resolveAnthropicApiKey(options, env = process.env, fileEnv = readDotEnv
42
42
  * Default Docker image tags used by the CLI when launching
43
43
  * the dev environment via docker compose.
44
44
  *
45
- * For release builds these remain "latest".
45
+ * For release builds the agents-server image is pinned to the matching
46
+ * @electric-ax/agents-server version by the tsdown build.
46
47
  * The canary CI overwrites them to "canary" before building.
47
48
  */
48
- const injectedCliVersion = `0.2.21`;
49
+ const injectedCliVersion = `0.2.23`;
50
+ const injectedAgentsServerImageTag = `0.6.3`;
49
51
  const ELECTRIC_AX_CLI_VERSION = injectedCliVersion.startsWith(`__ELECTRIC_AX_`) ? `0.0.0` : injectedCliVersion;
50
52
  const ELECTRIC_IMAGE_TAG = `latest`;
51
- const ELECTRIC_AGENTS_SERVER_IMAGE_TAG = `latest`;
53
+ const ELECTRIC_AGENTS_SERVER_IMAGE_TAG = injectedAgentsServerImageTag.startsWith(`__ELECTRIC_`) ? `latest` : injectedAgentsServerImageTag;
52
54
 
53
55
  //#endregion
54
56
  Object.defineProperty(exports, 'ELECTRIC_AGENTS_SERVER_IMAGE_TAG', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electric-ax",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "CLI for Electric Agents",
5
5
  "author": "ElectricSQL team and contributors",
6
6
  "license": "Apache-2.0",
@@ -39,15 +39,15 @@
39
39
  "dependencies": {
40
40
  "@durable-streams/client": "^0.2.6",
41
41
  "@durable-streams/state": "^0.3.1",
42
- "@electric-sql/client": "^1.5.21",
43
- "@tanstack/db": "^0.6.4",
42
+ "@electric-sql/client": "^1.5.22",
43
+ "@tanstack/db": "^0.6.6",
44
44
  "@tanstack/react-db": "^0.1.85",
45
45
  "commander": "^13.1.0",
46
46
  "ink": "^6.8.0",
47
47
  "omelette": "^0.4.17",
48
48
  "react": "^19.2.0",
49
- "@electric-ax/agents": "0.6.1",
50
- "@electric-ax/agents-runtime": "0.6.1"
49
+ "@electric-ax/agents": "0.6.3",
50
+ "@electric-ax/agents-runtime": "0.6.3"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@vitest/coverage-v8": "^4.1.0",
@@ -57,7 +57,7 @@
57
57
  "ink-testing-library": "^4.0.0",
58
58
  "tsdown": "^0.9.0",
59
59
  "tsx": "^4.19.2",
60
- "typescript": "^5.7.2",
60
+ "typescript": "^5.9.3",
61
61
  "vitest": "^4.1.0"
62
62
  },
63
63
  "files": [