@electric-ax/agents-server 0.4.11 → 0.4.13

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/dist/index.js CHANGED
@@ -1960,7 +1960,8 @@ var StreamClient = class {
1960
1960
  url: this.streamUrl(path$1),
1961
1961
  headers: this.streamHeaders(),
1962
1962
  contentType: opts.contentType,
1963
- body: opts.body
1963
+ body: opts.body,
1964
+ closed: opts.closed
1964
1965
  });
1965
1966
  });
1966
1967
  }
@@ -2060,30 +2061,11 @@ var StreamClient = class {
2060
2061
  offset: fromOffset ?? `-1`,
2061
2062
  live: false
2062
2063
  });
2063
- const messages = [];
2064
- return await new Promise((resolve$1, reject) => {
2065
- let settled = false;
2066
- let unsub = () => {};
2067
- const finish = (r) => {
2068
- if (settled) return;
2069
- settled = true;
2070
- unsub();
2071
- resolve$1(r);
2072
- };
2073
- unsub = response.subscribeBytes((chunk) => {
2074
- messages.push({
2075
- data: chunk.data,
2076
- offset: chunk.offset
2077
- });
2078
- if (chunk.upToDate || chunk.streamClosed) finish({ messages });
2079
- });
2080
- response.closed.then(() => finish({ messages })).catch((err) => {
2081
- if (settled) return;
2082
- settled = true;
2083
- unsub();
2084
- reject(err);
2085
- });
2086
- });
2064
+ const body = await response.body();
2065
+ return { messages: body.length === 0 ? [] : [{
2066
+ data: body,
2067
+ offset: response.offset
2068
+ }] };
2087
2069
  });
2088
2070
  }
2089
2071
  async readJson(path$1, fromOffset) {
@@ -2690,9 +2672,45 @@ function createInitialQueuePosition(date) {
2690
2672
  const DEFAULT_FORK_WAIT_TIMEOUT_MS = 12e4;
2691
2673
  const DEFAULT_FORK_WAIT_POLL_MS = 250;
2692
2674
  const SERVER_SIGNAL_SENDER = `/_electric/server`;
2675
+ const DEFAULT_MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
2693
2676
  function sleep(ms) {
2694
2677
  return new Promise((resolve$1) => setTimeout(resolve$1, ms));
2695
2678
  }
2679
+ function maxAttachmentBytes() {
2680
+ const configured = Number(process.env.ELECTRIC_AGENTS_MAX_ATTACHMENT_BYTES);
2681
+ return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : DEFAULT_MAX_ATTACHMENT_BYTES;
2682
+ }
2683
+ function manifestAttachmentKey(id) {
2684
+ return `attachment:${id}`;
2685
+ }
2686
+ function getEntityAttachmentStreamPath(entityUrl, attachmentId) {
2687
+ return `${entityUrl.replace(/\/+$/, ``)}/attachments/${attachmentId}`;
2688
+ }
2689
+ function isStreamCreateConflict(error) {
2690
+ return !!error && typeof error === `object` && (`status` in error && error.status === 409 || `code` in error && error.code === `CONFLICT_SEQ`);
2691
+ }
2692
+ function assertCanonicalAttachmentStreamPath(entityUrl, attachment) {
2693
+ const expected = getEntityAttachmentStreamPath(entityUrl, attachment.id);
2694
+ if (attachment.streamPath === expected) return;
2695
+ throw new ElectricAgentsError(ErrCodeInvalidRequest, `Attachment stream path does not match its entity and id`, 409);
2696
+ }
2697
+ function validateAttachmentId(id) {
2698
+ if (!id || id.includes(`/`) || id.startsWith(`.`)) throw new ElectricAgentsError(ErrCodeInvalidRequest, `attachment id must not be empty, start with ".", or contain forward slashes`, 400);
2699
+ }
2700
+ function validateAttachmentSubject(subject) {
2701
+ if (!subject.key) throw new ElectricAgentsError(ErrCodeInvalidRequest, `attachment subject key is required`, 400);
2702
+ if (subject.type !== `inbox` && subject.type !== `run` && subject.type !== `text` && subject.type !== `tool_call` && subject.type !== `context`) throw new ElectricAgentsError(ErrCodeInvalidRequest, `invalid attachment subject type`, 400);
2703
+ }
2704
+ function concatByteMessages(messages) {
2705
+ const total = messages.reduce((sum, message) => sum + message.data.length, 0);
2706
+ const bytes = new Uint8Array(total);
2707
+ let offset = 0;
2708
+ for (const message of messages) {
2709
+ bytes.set(message.data, offset);
2710
+ offset += message.data.length;
2711
+ }
2712
+ return bytes;
2713
+ }
2696
2714
  function omitUndefined$1(value) {
2697
2715
  return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
2698
2716
  }
@@ -3058,6 +3076,15 @@ var EntityManager = class {
3058
3076
  await this.streamClient.fork(forkPath, sourcePath);
3059
3077
  createdStreams.push(forkPath);
3060
3078
  }
3079
+ for (const plan of entityPlans) {
3080
+ const manifests = snapshot.manifestsByEntity.get(plan.source.url) ?? new Map();
3081
+ for (const manifest of manifests.values()) {
3082
+ if (manifest.kind !== `attachment` || typeof manifest.streamPath !== `string` || typeof manifest.id !== `string`) continue;
3083
+ const forkPath = getEntityAttachmentStreamPath(plan.fork.url, manifest.id);
3084
+ await this.streamClient.fork(forkPath, manifest.streamPath);
3085
+ createdStreams.push(forkPath);
3086
+ }
3087
+ }
3061
3088
  for (const plan of entityPlans) {
3062
3089
  const reconciliation = this.buildForkReconciliation(plan, snapshot, entityUrlMap, sharedStateIdMap, stringMap);
3063
3090
  activeManifestsByEntity.set(plan.fork.url, reconciliation.manifests);
@@ -3467,6 +3494,16 @@ var EntityManager = class {
3467
3494
  changed: true
3468
3495
  };
3469
3496
  }
3497
+ if (next.kind === `attachment` && typeof next.streamPath === `string` && typeof next.id === `string`) for (const [sourceUrl, forkUrl] of entityUrlMap) {
3498
+ const prefix = `${sourceUrl}/attachments/`;
3499
+ if (!next.streamPath.startsWith(prefix)) continue;
3500
+ next.streamPath = getEntityAttachmentStreamPath(forkUrl, next.id);
3501
+ return {
3502
+ key,
3503
+ value: next,
3504
+ changed: true
3505
+ };
3506
+ }
3470
3507
  if (next.kind === `schedule` && next.scheduleType === `future_send`) {
3471
3508
  let changed = false;
3472
3509
  if (typeof next.targetUrl === `string`) {
@@ -3664,6 +3701,93 @@ var EntityManager = class {
3664
3701
  const envelope = entityStateSchema.inbox.delete({ key });
3665
3702
  await this.streamClient.append(entity.streams.main, this.encodeChangeEvent(envelope));
3666
3703
  }
3704
+ isAttachmentStreamPath(path$1) {
3705
+ return /^\/[^/]+\/[^/]+\/attachments\/[^/]+$/.test(path$1);
3706
+ }
3707
+ async createAttachment(entityUrl, req) {
3708
+ const entity = await this.registry.getEntity(entityUrl);
3709
+ if (!entity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
3710
+ if (rejectsNormalWrites(entity.status)) throw new ElectricAgentsError(ErrCodeNotRunning, `Entity is not accepting writes`, 409);
3711
+ if (this.isForkWorkLockedEntity(entityUrl)) this.assertEntityNotForkWorkLocked(entityUrl);
3712
+ const id = req.id ?? randomUUID();
3713
+ validateAttachmentId(id);
3714
+ validateAttachmentSubject(req.subject);
3715
+ const limit = maxAttachmentBytes();
3716
+ if (req.bytes.length > limit) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Attachment exceeds maximum size of ${limit} bytes`, 413);
3717
+ const mimeType = req.mimeType.trim() || `application/octet-stream`;
3718
+ const streamPath = getEntityAttachmentStreamPath(entityUrl, id);
3719
+ const manifestKey = manifestAttachmentKey(id);
3720
+ const txid = randomUUID();
3721
+ const now = new Date().toISOString();
3722
+ const sha256 = createHash(`sha256`).update(req.bytes).digest(`hex`);
3723
+ const attachment = {
3724
+ key: manifestKey,
3725
+ kind: `attachment`,
3726
+ id,
3727
+ streamPath,
3728
+ status: `complete`,
3729
+ subject: req.subject,
3730
+ role: req.role ?? `input`,
3731
+ mimeType,
3732
+ ...req.filename ? { filename: req.filename } : {},
3733
+ byteLength: req.bytes.length,
3734
+ sha256,
3735
+ createdAt: now,
3736
+ ...req.createdBy ? { createdBy: req.createdBy } : {},
3737
+ ...req.meta ? { meta: req.meta } : {}
3738
+ };
3739
+ let streamCreated = false;
3740
+ try {
3741
+ await this.streamClient.create(streamPath, {
3742
+ contentType: mimeType,
3743
+ body: req.bytes,
3744
+ closed: true
3745
+ });
3746
+ streamCreated = true;
3747
+ await this.writeManifestEntry(entityUrl, manifestKey, `upsert`, attachment, { txid });
3748
+ } catch (error) {
3749
+ if (streamCreated) await this.streamClient.delete(streamPath).catch(() => void 0);
3750
+ if (!streamCreated && isStreamCreateConflict(error)) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Attachment already exists at id "${id}"`, 409);
3751
+ throw error;
3752
+ }
3753
+ return {
3754
+ txid,
3755
+ attachment
3756
+ };
3757
+ }
3758
+ async getAttachment(entityUrl, id) {
3759
+ validateAttachmentId(id);
3760
+ const entity = await this.registry.getEntity(entityUrl);
3761
+ if (!entity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
3762
+ const events = await this.streamClient.readJson(entity.streams.main);
3763
+ const manifest = this.reduceStateRows(events, `manifest`).get(manifestAttachmentKey(id));
3764
+ if (!manifest || manifest.kind !== `attachment`) return null;
3765
+ return manifest;
3766
+ }
3767
+ async readAttachment(entityUrl, id) {
3768
+ const attachment = await this.getAttachment(entityUrl, id);
3769
+ if (!attachment) throw new ElectricAgentsError(ErrCodeNotFound, `Attachment not found`, 404);
3770
+ if (attachment.status !== `complete`) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Attachment is not complete`, 409);
3771
+ assertCanonicalAttachmentStreamPath(entityUrl, attachment);
3772
+ const result = await this.streamClient.read(attachment.streamPath);
3773
+ return {
3774
+ attachment,
3775
+ bytes: concatByteMessages(result.messages)
3776
+ };
3777
+ }
3778
+ async deleteAttachment(entityUrl, id) {
3779
+ const entity = await this.registry.getEntity(entityUrl);
3780
+ if (!entity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
3781
+ if (rejectsNormalWrites(entity.status)) throw new ElectricAgentsError(ErrCodeNotRunning, `Entity is not accepting writes`, 409);
3782
+ if (this.isForkWorkLockedEntity(entityUrl)) this.assertEntityNotForkWorkLocked(entityUrl);
3783
+ const attachment = await this.getAttachment(entityUrl, id);
3784
+ if (!attachment) throw new ElectricAgentsError(ErrCodeNotFound, `Attachment not found`, 404);
3785
+ assertCanonicalAttachmentStreamPath(entityUrl, attachment);
3786
+ const txid = randomUUID();
3787
+ await this.writeManifestEntry(entityUrl, manifestAttachmentKey(id), `delete`, void 0, { txid });
3788
+ await this.streamClient.delete(attachment.streamPath).catch(() => void 0);
3789
+ return { txid };
3790
+ }
3667
3791
  async setTag(entityUrl, key, req, token) {
3668
3792
  const entity = await this.registry.getEntity(entityUrl);
3669
3793
  if (!entity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
@@ -6065,6 +6189,7 @@ async function handleStreamAppend(request, runtime, forward) {
6065
6189
  const { manager } = runtime;
6066
6190
  const entity = await manager.registry.getEntityByStream(path$1);
6067
6191
  const isSharedState = path$1.startsWith(`/_electric/shared-state/`);
6192
+ if (!entity && manager.isAttachmentStreamPath(path$1)) return apiError(401, ErrCodeUnauthorized, `Invalid write token`);
6068
6193
  if (!entity && !isSharedState) return void 0;
6069
6194
  const body = await request.readBody();
6070
6195
  const event = decodeStreamAppendEvent(body);
@@ -6633,8 +6758,9 @@ async function streamAppend(request, ctx) {
6633
6758
  }));
6634
6759
  }
6635
6760
  async function proxyPassThrough(request, ctx) {
6636
- const upstream = await forwardToDurableStreams(ctx, request);
6637
6761
  const streamPath = new URL(request.url).pathname;
6762
+ if (ctx.entityManager?.isAttachmentStreamPath(streamPath)) return new Response(null, { status: 404 });
6763
+ const upstream = await forwardToDurableStreams(ctx, request);
6638
6764
  const method = request.method.toUpperCase();
6639
6765
  const endTrackedRead = method === `GET` ? await ctx.entityBridgeManager.beginClientRead(streamPath) : null;
6640
6766
  try {
@@ -6787,6 +6913,13 @@ const eventSourceSubscriptionBodySchema = Type.Object({
6787
6913
  lifetime: Type.Optional(subscriptionLifetimeSchema),
6788
6914
  reason: Type.Optional(Type.String())
6789
6915
  });
6916
+ const attachmentSubjectTypes = new Set([
6917
+ `inbox`,
6918
+ `run`,
6919
+ `text`,
6920
+ `tool_call`,
6921
+ `context`
6922
+ ]);
6790
6923
  const entitiesRouter = Router({ base: `/_electric/entities` });
6791
6924
  entitiesRouter.get(`/`, listEntities);
6792
6925
  entitiesRouter.put(`/:type/:instanceId`, withSpawnableEntityType, withSchema(spawnBodySchema), spawnEntity);
@@ -6795,6 +6928,9 @@ entitiesRouter.head(`/:type/:instanceId`, withExistingEntity, headEntity);
6795
6928
  entitiesRouter.delete(`/:type/:instanceId`, withExistingEntity, killEntity);
6796
6929
  entitiesRouter.post(`/:type/:instanceId/signal`, withExistingEntity, withSchema(signalBodySchema), signalEntity);
6797
6930
  entitiesRouter.post(`/:type/:instanceId/send`, withExistingEntity, withSchema(sendBodySchema), sendEntity);
6931
+ entitiesRouter.post(`/:type/:instanceId/attachments`, withExistingEntity, createAttachment);
6932
+ entitiesRouter.get(`/:type/:instanceId/attachments/:attachmentId`, withExistingEntity, readAttachment);
6933
+ entitiesRouter.delete(`/:type/:instanceId/attachments/:attachmentId`, withExistingEntity, deleteAttachment);
6798
6934
  entitiesRouter.patch(`/:type/:instanceId/inbox/:messageKey`, withExistingEntity, withSchema(inboxMessageBodySchema), updateInboxMessage);
6799
6935
  entitiesRouter.delete(`/:type/:instanceId/inbox/:messageKey`, withExistingEntity, deleteInboxMessage);
6800
6936
  entitiesRouter.post(`/:type/:instanceId/fork`, withExistingEntity, withSchema(forkBodySchema), forkEntity);
@@ -6821,6 +6957,82 @@ function requireExistingEntityRoute(request) {
6821
6957
  if (!request.entityRoute) throw new Error(`existing entity middleware did not run`);
6822
6958
  return request.entityRoute;
6823
6959
  }
6960
+ function invalidAttachmentRequest(message) {
6961
+ throw new ElectricAgentsError(ErrCodeInvalidRequest, message, 400);
6962
+ }
6963
+ function formString(form, key) {
6964
+ const value = form.get(key);
6965
+ if (typeof value !== `string`) return void 0;
6966
+ const trimmed = value.trim();
6967
+ return trimmed || void 0;
6968
+ }
6969
+ function parseJsonFormField(form, key) {
6970
+ const raw = formString(form, key);
6971
+ if (!raw) return void 0;
6972
+ try {
6973
+ return JSON.parse(raw);
6974
+ } catch {
6975
+ invalidAttachmentRequest(`Invalid JSON field: ${key}`);
6976
+ }
6977
+ }
6978
+ function parseAttachmentSubject(form) {
6979
+ const explicit = parseJsonFormField(form, `subject`);
6980
+ if (explicit !== void 0) {
6981
+ if (!explicit || typeof explicit !== `object` || Array.isArray(explicit)) invalidAttachmentRequest(`attachment subject must be an object`);
6982
+ const subject = explicit;
6983
+ const type$1 = subject.type;
6984
+ const key$1 = subject.key;
6985
+ if (typeof type$1 !== `string` || typeof key$1 !== `string`) invalidAttachmentRequest(`attachment subject requires type and key`);
6986
+ if (!attachmentSubjectTypes.has(type$1)) invalidAttachmentRequest(`invalid attachment subject type`);
6987
+ return {
6988
+ type: type$1,
6989
+ key: key$1
6990
+ };
6991
+ }
6992
+ const type = formString(form, `subjectType`);
6993
+ const key = formString(form, `subjectKey`);
6994
+ if (!type || !key) invalidAttachmentRequest(`attachment subject is required`);
6995
+ if (!attachmentSubjectTypes.has(type)) invalidAttachmentRequest(`invalid attachment subject type`);
6996
+ return {
6997
+ type,
6998
+ key
6999
+ };
7000
+ }
7001
+ function getUploadedFormFile(value) {
7002
+ if (value !== null && typeof value === `object` && `arrayBuffer` in value && typeof value.arrayBuffer === `function`) return value;
7003
+ return null;
7004
+ }
7005
+ async function parseAttachmentForm(request) {
7006
+ const contentType = request.headers.get(`content-type`)?.toLowerCase() ?? ``;
7007
+ if (!contentType.includes(`multipart/form-data`)) invalidAttachmentRequest(`Attachment uploads must use multipart/form-data`);
7008
+ let form;
7009
+ try {
7010
+ form = await request.formData();
7011
+ } catch {
7012
+ invalidAttachmentRequest(`Invalid multipart form data`);
7013
+ }
7014
+ const file = getUploadedFormFile(form.get(`file`));
7015
+ if (!file) invalidAttachmentRequest(`Missing file field`);
7016
+ const role = formString(form, `role`);
7017
+ if (role !== void 0 && role !== `input` && role !== `output`) invalidAttachmentRequest(`invalid attachment role`);
7018
+ const fileName = formString(form, `filename`) ?? (typeof file.name === `string` ? file.name : void 0);
7019
+ const mimeType = formString(form, `mimeType`) || (typeof file.type === `string` ? file.type : void 0) || `application/octet-stream`;
7020
+ const meta = parseJsonFormField(form, `meta`);
7021
+ if (meta !== void 0 && (typeof meta !== `object` || Array.isArray(meta))) invalidAttachmentRequest(`attachment meta must be an object`);
7022
+ return {
7023
+ id: formString(form, `id`),
7024
+ bytes: new Uint8Array(await file.arrayBuffer()),
7025
+ mimeType,
7026
+ filename: fileName,
7027
+ subject: parseAttachmentSubject(form),
7028
+ role,
7029
+ meta
7030
+ };
7031
+ }
7032
+ function contentDisposition(filename) {
7033
+ const fallback = filename.replace(/["\\\r\n]/g, `_`);
7034
+ return `attachment; filename="${fallback}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
7035
+ }
6824
7036
  function rejectPrincipalEntityMutation(request, action) {
6825
7037
  const { entity } = requireExistingEntityRoute(request);
6826
7038
  if (entity.type !== `principal`) return void 0;
@@ -7005,6 +7217,44 @@ async function sendEntity(request, ctx) {
7005
7217
  });
7006
7218
  return status(204);
7007
7219
  }
7220
+ async function createAttachment(request, ctx) {
7221
+ const principalMutationError = rejectPrincipalEntityMutation(request, `given attachments`);
7222
+ if (principalMutationError) return principalMutationError;
7223
+ const { entityUrl } = requireExistingEntityRoute(request);
7224
+ const form = await parseAttachmentForm(request);
7225
+ const result = await ctx.entityManager.createAttachment(entityUrl, {
7226
+ id: form.id,
7227
+ bytes: form.bytes,
7228
+ mimeType: form.mimeType,
7229
+ filename: form.filename,
7230
+ subject: form.subject,
7231
+ role: form.role,
7232
+ createdBy: ctx.principal.url,
7233
+ meta: form.meta
7234
+ });
7235
+ return json(result, { status: 201 });
7236
+ }
7237
+ async function readAttachment(request, ctx) {
7238
+ const { entityUrl } = requireExistingEntityRoute(request);
7239
+ const result = await ctx.entityManager.readAttachment(entityUrl, decodeURIComponent(request.params.attachmentId));
7240
+ const headers = new Headers({
7241
+ "content-type": result.attachment.mimeType,
7242
+ "content-length": String(result.bytes.length),
7243
+ "cache-control": `private, max-age=31536000, immutable`
7244
+ });
7245
+ if (result.attachment.filename) headers.set(`content-disposition`, contentDisposition(result.attachment.filename));
7246
+ return new Response(result.bytes, {
7247
+ status: 200,
7248
+ headers
7249
+ });
7250
+ }
7251
+ async function deleteAttachment(request, ctx) {
7252
+ const principalMutationError = rejectPrincipalEntityMutation(request, `stripped of attachments`);
7253
+ if (principalMutationError) return principalMutationError;
7254
+ const { entityUrl } = requireExistingEntityRoute(request);
7255
+ const result = await ctx.entityManager.deleteAttachment(entityUrl, decodeURIComponent(request.params.attachmentId));
7256
+ return json(result);
7257
+ }
7008
7258
  async function updateInboxMessage(request, ctx) {
7009
7259
  const parsed = routeBody(request);
7010
7260
  const { entityUrl } = requireExistingEntityRoute(request);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electric-ax/agents-server",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "Electric Agents entity runtime server",
5
5
  "author": "Durable Stream contributors",
6
6
  "bin": {
@@ -36,9 +36,9 @@
36
36
  "sideEffects": false,
37
37
  "dependencies": {
38
38
  "@anthropic-ai/sdk": "^0.78.0",
39
- "@durable-streams/client": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/client@5d5c217",
40
- "@durable-streams/server": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/server@eac712f",
41
- "@durable-streams/state": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/state@5d5c217",
39
+ "@durable-streams/client": "^0.2.6",
40
+ "@durable-streams/server": "^0.3.5",
41
+ "@durable-streams/state": "^0.2.9",
42
42
  "@electric-sql/client": "^1.5.19",
43
43
  "@mariozechner/pi-agent-core": "^0.70.2",
44
44
  "@opentelemetry/api": "^1.9.1",
@@ -54,7 +54,7 @@
54
54
  "pino-pretty": "^13.0.0",
55
55
  "postgres": "^3.4.0",
56
56
  "undici": "^7.24.7",
57
- "@electric-ax/agents-runtime": "0.3.5"
57
+ "@electric-ax/agents-runtime": "0.3.6"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^22.19.15",
@@ -65,9 +65,9 @@
65
65
  "tsx": "^4.19.0",
66
66
  "typescript": "^5.0.0",
67
67
  "vitest": "^4.1.0",
68
- "@electric-ax/agents": "0.4.9",
69
- "@electric-ax/agents-server-ui": "0.4.11",
70
- "@electric-ax/agents-server-conformance-tests": "0.1.8"
68
+ "@electric-ax/agents": "0.4.10",
69
+ "@electric-ax/agents-server-conformance-tests": "0.1.9",
70
+ "@electric-ax/agents-server-ui": "0.4.13"
71
71
  },
72
72
  "files": [
73
73
  "dist",