@ours.network/fleet 1.1.0-nightly.8 → 1.1.0-nightly.9

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.
@@ -215,35 +215,81 @@ const taskActionMarkdown = (title, task, fields = []) => {
215
215
  ],
216
216
  });
217
217
  };
218
- function safeBrainSummary(definition) {
219
- const brain = definition?.brain;
220
- if (!brain || typeof brain !== 'object' || Array.isArray(brain))
218
+ function safeSelectionSummary(definition, kind) {
219
+ const selected = definition?.[kind];
220
+ if (!selected || typeof selected !== 'object' || Array.isArray(selected))
221
221
  return 'unresolved';
222
- const selection = brain;
222
+ const selection = selected;
223
+ if (selection.kind === 'ref' && typeof selection.id === 'string')
224
+ return `ref:${selection.id} (reference)`;
225
+ if (selection.kind === 'inline' && typeof selection.fingerprint === 'string')
226
+ return `inline:${selection.fingerprint} (inline)`;
223
227
  if (typeof selection.ref === 'string')
224
228
  return `ref:${selection.ref}`;
225
229
  if (!Object.hasOwn(selection, 'inline'))
226
230
  return 'unresolved';
227
- const fingerprint = createHash('sha256').update(JSON.stringify(selection.inline)).digest('hex').slice(0, 16);
231
+ const canonical = (value) => Array.isArray(value)
232
+ ? `[${value.map(canonical).join(',')}]`
233
+ : value && typeof value === 'object'
234
+ ? `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b))
235
+ .map(([key, child]) => `${JSON.stringify(key)}:${canonical(child)}`).join(',')}}`
236
+ : JSON.stringify(value);
237
+ const fingerprint = createHash('sha256').update(canonical(selection.inline)).digest('hex').slice(0, 16);
228
238
  return `inline:sha256:${fingerprint}`;
229
239
  }
230
- function auditTask(operation, task, previousState, newState = task.state) {
240
+ function safePermissionsSummary(definition) {
241
+ const permissions = definition?.permissions;
242
+ if (!permissions || typeof permissions !== 'object' || Array.isArray(permissions))
243
+ return undefined;
244
+ const policy = permissions;
245
+ const fields = ['approval', 'filesystem', 'unattended'].flatMap(key => typeof policy[key] === 'string' ? [`${key}=${policy[key]}`] : []);
246
+ return fields.length ? fields.join(',') : undefined;
247
+ }
248
+ function auditTask(operation, task, previousState, newState = task.state, revision) {
249
+ const materialSameState = operation === 'block' || operation === 'unblock' || operation === 'settling';
250
+ if (previousState === newState && !materialSameState)
251
+ return;
231
252
  recordFleetAuditResource('task', task.task_id);
232
253
  if (task.room_id)
233
254
  recordFleetAuditResource('room', task.room_id);
234
255
  const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
235
- const brains = new Map(room?.member_seats.map(seat => [seat.role_name, safeBrainSummary(seat.launch?.agent_definition)]) ?? []);
236
- recordFleetAuditPresentation({ kind: 'task', operation, id: task.task_id, title: task.title,
256
+ const definitions = new Map(room?.member_seats.map(seat => [seat.role_name, seat.launch?.agent_definition]) ?? []);
257
+ const semanticOperation = operation === 'recover'
258
+ ? newState === 'active' ? 'work' : newState === 'done' ? 'done'
259
+ : newState === 'cancelled' ? 'cancel' : undefined
260
+ : operation;
261
+ if (!semanticOperation)
262
+ return;
263
+ recordFleetAuditPresentation({ kind: 'task', operation: semanticOperation, id: task.task_id, title: task.title,
237
264
  previousState, newState,
265
+ revision: revision ?? task.blocked?.at ?? task.ended_at ?? task.started_at ?? task.created_at,
266
+ list: task.list_name ?? 'default',
238
267
  template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
239
- roomId: task.room_id, agents: task.member_roles.map(member => ({ name: member.name,
240
- brain: brains.get(member.name) ?? 'unresolved', role: member.cowork_role })) });
268
+ roomId: task.room_id, agents: [...task.member_roles].sort((a, b) => a.name.localeCompare(b.name)).map(member => ({ name: member.name,
269
+ brain: safeSelectionSummary(definitions.get(member.name), 'brain'),
270
+ role: safeSelectionSummary(definitions.get(member.name), 'role') === 'unresolved'
271
+ ? member.cowork_role : safeSelectionSummary(definitions.get(member.name), 'role'),
272
+ permissions: safePermissionsSummary(definitions.get(member.name)) })) });
241
273
  }
242
274
  function auditRoom(operation, room, previousState, newState = room.state) {
275
+ if (previousState === newState)
276
+ return;
243
277
  recordFleetAuditResource('room', room.room_id);
244
- recordFleetAuditPresentation({ kind: 'room', operation, id: room.room_id, previousState, newState,
278
+ const semanticOperation = operation === 'recover'
279
+ ? newState === 'active' ? 'activate' : newState === 'deleted' ? 'delete'
280
+ : newState === 'closing' ? 'close' : undefined
281
+ : operation;
282
+ if (!semanticOperation)
283
+ return;
284
+ recordFleetAuditPresentation({ kind: 'room', operation: semanticOperation, id: room.room_id, previousState, newState,
285
+ revision: room.closed_at ?? room.activated_at ?? room.close?.accepted_at ?? room.created_at,
286
+ name: room.room_name, taskId: room.task_id,
245
287
  template: room.template_snapshot ? `${room.template_snapshot.name}@${room.template_snapshot.version}` : undefined,
246
- participants: room.member_seats.map(member => ({ name: member.role_name, role: member.cowork_role })) });
288
+ participants: [...room.member_seats].sort((a, b) => a.role_name.localeCompare(b.role_name)).map(member => ({ name: member.role_name, id: member.identity_cid,
289
+ brain: safeSelectionSummary(member.launch?.agent_definition, 'brain'),
290
+ role: safeSelectionSummary(member.launch?.agent_definition, 'role') === 'unresolved'
291
+ ? member.cowork_role : safeSelectionSummary(member.launch?.agent_definition, 'role'),
292
+ permissions: safePermissionsSummary(member.launch?.agent_definition) })) });
247
293
  }
248
294
  const roomActionMarkdown = (title, room, fields = []) => {
249
295
  recordFleetAuditResource('room', room.room_id);
@@ -272,6 +318,10 @@ async function launchTaskSettleWorker(taskId, configPath) {
272
318
  actor: { kind: 'local_control', surface: 'cli' }, taskId, error: errorText(error),
273
319
  recoveryHint: `External settle worker failed to start. Retry task recover ${taskId}.`,
274
320
  });
321
+ const task = getTask(taskId);
322
+ recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
323
+ state: task.state, category: 'settlement_failed',
324
+ eventId: task.terminal_intent?.error_at ?? task.terminal_intent?.accepted_at ?? task.created_at });
275
325
  throw error;
276
326
  }
277
327
  const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
@@ -284,7 +334,11 @@ async function launchTaskSettleWorker(taskId, configPath) {
284
334
  }
285
335
  await sleep(PUBLIC_SETTLE_POLL_MS);
286
336
  }
287
- return { task: getTask(taskId), timedOut: true };
337
+ const task = getTask(taskId);
338
+ recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: taskId,
339
+ state: task.state, category: 'settlement_pending',
340
+ eventId: task.terminal_intent?.accepted_at ?? task.created_at });
341
+ return { task, timedOut: true };
288
342
  }
289
343
  async function launchRoomDeleteWorker(roomId, configPath) {
290
344
  const previousErrorAt = getRoomRecord(roomId)?.close?.error_at;
@@ -296,6 +350,11 @@ async function launchRoomDeleteWorker(roomId, configPath) {
296
350
  actor: { kind: 'local_control', surface: 'cli' }, roomId, error: errorText(error),
297
351
  recoveryHint: `External delete worker failed to start. Retry room delete ${roomId} ${roomId}.`,
298
352
  });
353
+ const room = getRoomRecord(roomId);
354
+ if (room)
355
+ recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
356
+ state: room.state, category: 'cleanup_failed',
357
+ eventId: room.close?.error_at ?? room.close?.accepted_at ?? room.created_at });
299
358
  throw error;
300
359
  }
301
360
  const deadline = Date.now() + PUBLIC_SETTLE_WAIT_MS;
@@ -308,7 +367,12 @@ async function launchRoomDeleteWorker(roomId, configPath) {
308
367
  }
309
368
  await sleep(PUBLIC_SETTLE_POLL_MS);
310
369
  }
311
- return { deleted: getRoomRecord(roomId) === undefined, timedOut: true };
370
+ const remaining = getRoomRecord(roomId);
371
+ if (remaining)
372
+ recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: roomId,
373
+ state: remaining.state, category: 'cleanup_pending',
374
+ eventId: remaining.close?.accepted_at ?? remaining.created_at });
375
+ return { deleted: remaining === undefined, timedOut: true };
312
376
  }
313
377
  function loadCfg(opts) {
314
378
  return loadConfig(opts.configuration);
@@ -410,21 +474,36 @@ async function provisionRoom(cfg, input) {
410
474
  setSagaError(record.room_id, error instanceof Error ? error.message : String(error), mismatch
411
475
  ? 'Verify rooms.owner.expected_cid and rotate the configured invite if necessary.'
412
476
  : 'Rotate rooms.owner.public_invite or public_invite_file, then run room recover.', mismatch ? 'owner_cid_mismatch' : 'waiting_owner_invite');
477
+ const failed = getRoomRecord(record.room_id);
478
+ if (failed)
479
+ recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room',
480
+ id: failed.room_id, state: failed.state, category: 'provision_failed',
481
+ eventId: `${failed.created_at}:${failed.saga.phase}:${failed.saga.step_index}` });
413
482
  throw error;
414
483
  }
415
484
  }
416
485
  record = advanceSaga(record.room_id, 'create_members', 3);
417
486
  if (input.template && input.template.members.length > 0) {
418
- record = await provisionMembers({
419
- cfg,
420
- cowork,
421
- roomId: record.room_id,
422
- taskId: input.taskId,
423
- template: input.template,
424
- binPath: getBinPath(),
425
- brief: input.brief,
426
- goal: input.goal,
427
- });
487
+ try {
488
+ record = await provisionMembers({
489
+ cfg,
490
+ cowork,
491
+ roomId: record.room_id,
492
+ taskId: input.taskId,
493
+ template: input.template,
494
+ binPath: getBinPath(),
495
+ brief: input.brief,
496
+ goal: input.goal,
497
+ });
498
+ }
499
+ catch (error) {
500
+ const failed = getRoomRecord(record.room_id);
501
+ if (failed)
502
+ recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room',
503
+ id: failed.room_id, state: failed.state, category: 'provision_failed',
504
+ eventId: `${failed.created_at}:${failed.saga.phase}:${failed.saga.step_index}` });
505
+ throw error;
506
+ }
428
507
  }
429
508
  else {
430
509
  record = activateRoom(record.room_id);
@@ -529,7 +608,6 @@ export function registerTaskCommands(parent, cOpt) {
529
608
  idempotencyKey: opts.idempotencyKey, origin: { type: 'cli' },
530
609
  list: opts.list,
531
610
  });
532
- auditTask('create', record, 'none');
533
611
  if (opts.json) {
534
612
  console.log(JSON.stringify({ schema_version: 1, task: record }, null, 2));
535
613
  return;
@@ -716,11 +794,9 @@ export function registerTaskCommands(parent, cOpt) {
716
794
  .option('--json', 'JSON output')
717
795
  .action(async (id, opts) => {
718
796
  try {
719
- const previous = getTask(id).state;
720
797
  const t = await taskRoomService(opts.configuration).startTask({
721
798
  actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
722
799
  });
723
- auditTask('start', t, previous);
724
800
  if (opts.json) {
725
801
  console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
726
802
  return;
@@ -766,11 +842,12 @@ export function registerTaskCommands(parent, cOpt) {
766
842
  .option('--json', 'JSON output')
767
843
  .action((id, opts) => {
768
844
  try {
769
- const previous = getTask(id).state;
845
+ const prior = getTask(id);
846
+ const previous = prior.state;
770
847
  const t = taskRoomService().unblockTask({
771
848
  actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
772
849
  });
773
- auditTask('unblock', t, previous);
850
+ auditTask('unblock', t, previous, t.state, prior.blocked?.at);
774
851
  if (opts.json) {
775
852
  console.log(JSON.stringify({ schema_version: 1, task: t }, null, 2));
776
853
  return;
@@ -820,6 +897,8 @@ export function registerTaskCommands(parent, cOpt) {
820
897
  const plan = await taskRoomService(opts.configuration).completeTask({
821
898
  actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
822
899
  });
900
+ if (plan.task.terminal_intent?.status === 'pending')
901
+ auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
823
902
  const settled = plan.settlementRequired
824
903
  ? await launchTaskSettleWorker(id, opts.configuration)
825
904
  : { task: plan.task, timedOut: false };
@@ -856,6 +935,8 @@ export function registerTaskCommands(parent, cOpt) {
856
935
  const plan = await taskRoomService(opts.configuration).cancelTask({
857
936
  actor: { kind: 'local_control', surface: 'cli' }, taskId: id,
858
937
  });
938
+ if (plan.task.terminal_intent?.status === 'pending')
939
+ auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
859
940
  const settled = plan.settlementRequired
860
941
  ? await launchTaskSettleWorker(id, opts.configuration)
861
942
  : { task: plan.task, timedOut: false };
@@ -1037,12 +1118,10 @@ export function registerTaskCommands(parent, cOpt) {
1037
1118
  .option('--json', 'JSON output')
1038
1119
  .action(async (id, opts) => {
1039
1120
  try {
1040
- const previous = getTask(id).state;
1041
1121
  const result = await taskRoomService(opts.configuration).ensureTaskWork({
1042
1122
  actor: { kind: 'local_control', surface: 'cli' }, taskId: id, template: opts.template,
1043
1123
  });
1044
1124
  const t = result.task;
1045
- auditTask('work', t, previous);
1046
1125
  if (result.status === 'already_active') {
1047
1126
  if (opts.json) {
1048
1127
  console.log(JSON.stringify({ schema_version: 1, task: t, status: 'already_active' }, null, 2));
@@ -1089,6 +1168,8 @@ export function registerTaskCommands(parent, cOpt) {
1089
1168
  const plan = await taskRoomService(opts.configuration).finishTask({
1090
1169
  actor: { kind: 'local_control', surface: 'cli' }, taskId: id, outcome,
1091
1170
  });
1171
+ if (plan.task.terminal_intent?.status === 'pending')
1172
+ auditTask('settling', plan.task, previous, previous, plan.task.terminal_intent.accepted_at);
1092
1173
  const settled = plan.settlementRequired
1093
1174
  ? await launchTaskSettleWorker(id, opts.configuration)
1094
1175
  : { task: plan.task, timedOut: false };
@@ -1133,7 +1214,6 @@ export function registerRoomCommands(parent, cOpt) {
1133
1214
  actor: { kind: 'local_control', surface: 'cli' }, name: opts.name,
1134
1215
  template: opts.template, goal: opts.goal, brief: opts.brief, briefFile: opts.briefFile,
1135
1216
  });
1136
- auditRoom('create', record, 'none');
1137
1217
  if (opts.json) {
1138
1218
  console.log(JSON.stringify({ schema_version: 1, room: record }, null, 2));
1139
1219
  return;
@@ -1219,8 +1299,6 @@ export function registerRoomCommands(parent, cOpt) {
1219
1299
  .action(async (id, opts) => {
1220
1300
  try {
1221
1301
  const { room, orchestration } = await taskRoomService(opts.configuration).getRoomDetail(id);
1222
- if (orchestration)
1223
- auditRoom('open', orchestration, orchestration.state);
1224
1302
  const url = `http://localhost:4460/room/${id}`;
1225
1303
  if (opts.json) {
1226
1304
  console.log(JSON.stringify({ schema_version: 1, room_id: id, url, room_name: room.room_name }, null, 2));
@@ -1247,8 +1325,6 @@ export function registerRoomCommands(parent, cOpt) {
1247
1325
  .action(async (id, opts) => {
1248
1326
  try {
1249
1327
  const { orchestration: r, members } = await taskRoomService(opts.configuration).getRoomMembers(id);
1250
- if (r)
1251
- auditRoom('members', r, r.state);
1252
1328
  if (opts.json) {
1253
1329
  console.log(JSON.stringify({
1254
1330
  schema_version: 1,
@@ -1286,9 +1362,12 @@ export function registerRoomCommands(parent, cOpt) {
1286
1362
  await taskRoomService(opts.configuration).requestRoomDeletion({
1287
1363
  actor: { kind: 'local_control', surface: 'cli' }, roomId: id,
1288
1364
  });
1365
+ const closing = getRoomRecord(id);
1366
+ if (previous && closing && previous.state !== closing.state)
1367
+ auditRoom('close', closing, previous.state, closing.state);
1289
1368
  const settled = await launchRoomDeleteWorker(id, opts.configuration);
1290
- if (previous)
1291
- auditRoom('delete', previous, previous.state, settled.deleted ? 'deleted' : 'closing');
1369
+ if (closing)
1370
+ auditRoom('delete', closing, closing.state, settled.deleted ? 'deleted' : 'closing');
1292
1371
  if (opts.json) {
1293
1372
  console.log(JSON.stringify({ schema_version: 1, room_id: id, deleted: settled.deleted }, null, 2));
1294
1373
  return;
@@ -13,6 +13,7 @@ import { buildRoomMemberTask, sha256Text } from './member-startup.js';
13
13
  import { agentDir } from '../paths.js';
14
14
  import { readProvenance } from '../creation.js';
15
15
  import { readTempSupervisor, secureStoppedTempArchive, tempArchiveForCreationAction, tempSupervisorLiveness, } from '../temp-lifecycle.js';
16
+ import { recordFleetAuditPresentation } from '../fleet-command-audit.js';
16
17
  export function getBinPath() {
17
18
  try {
18
19
  return realpathSync(process.argv[1]);
@@ -30,11 +31,30 @@ function canonicalJson(value) {
30
31
  .map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(',')}}`;
31
32
  return JSON.stringify(value);
32
33
  }
34
+ function selectionSummary(selection) {
35
+ if ('ref' in selection)
36
+ return `ref:${selection.ref} (reference)`;
37
+ return `inline:sha256:${createHash('sha256').update(canonicalJson(selection.inline)).digest('hex').slice(0, 16)} (inline)`;
38
+ }
39
+ function permissionSummary(definition) {
40
+ const permissions = definition.permissions;
41
+ if (!permissions)
42
+ return undefined;
43
+ return `approval=${permissions.approval},filesystem=${permissions.filesystem},unattended=${permissions.unattended}`;
44
+ }
33
45
  function launchDefinition(definition) {
34
- const selectionEvidence = (selection) => 'ref' in selection ? { kind: 'ref', id: selection.ref } : { kind: 'inline' };
46
+ const selectionEvidence = (selection) => 'ref' in selection ? { kind: 'ref', id: selection.ref } : {
47
+ kind: 'inline', fingerprint: `sha256:${createHash('sha256')
48
+ .update(canonicalJson(selection.inline)).digest('hex').slice(0, 16)}`,
49
+ };
35
50
  return {
36
51
  projection: {
37
52
  brain: selectionEvidence(definition.brain), role: selectionEvidence(definition.role),
53
+ ...(definition.permissions ? { permissions: {
54
+ approval: definition.permissions.approval,
55
+ filesystem: definition.permissions.filesystem,
56
+ unattended: definition.permissions.unattended,
57
+ } } : {}),
38
58
  operationalFields: Object.keys(definition)
39
59
  .filter(key => key !== 'brain' && key !== 'role').sort(),
40
60
  },
@@ -260,6 +280,11 @@ async function launchMember(input) {
260
280
  ...(launched.callerRole ? { caller_role: launched.callerRole } : {}),
261
281
  launch_id: supervisor.launchId, updated_at: new Date().toISOString(),
262
282
  } });
283
+ recordFleetAuditPresentation({ kind: 'agent_started', id: member.name, name: member.name,
284
+ lifetime: 'temporary', brain: selectionSummary(settings.definition.brain),
285
+ role: selectionSummary(settings.definition.role), harness: 'resolved', session: 'acp',
286
+ permissions: permissionSummary(settings.definition), parent: provision.roomId,
287
+ actionId: launched.creationActionId, inherited: [] });
263
288
  }
264
289
  catch (error) {
265
290
  updateMemberStartup(provision.roomId, member.name, { launch: {
@@ -269,6 +294,8 @@ async function launchMember(input) {
269
294
  updated_at: new Date().toISOString(),
270
295
  error: error instanceof Error ? error.message : String(error),
271
296
  } });
297
+ recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Agent', id: member.name,
298
+ state: 'failed', category: 'readiness_failed', eventId: effectiveActionId });
272
299
  throw error;
273
300
  }
274
301
  }
package/dist/runner.js CHANGED
@@ -621,7 +621,19 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
621
621
  throw error;
622
622
  }
623
623
  control.setFleetSpawner(async (requested) => {
624
- return executeManagedSpawn(role, configPath, requested, deps.log);
624
+ const event = await executeManagedSpawn(role, configPath, requested, deps.log);
625
+ // Room-member launches are collected with their Task/Room transaction so
626
+ // causal delivery is Task → Room → Agents → Room active → Task active.
627
+ if (!requested.roomMemberStartup && role.owner_channel && ownerChannel?.notifyFleetSpawn) {
628
+ try {
629
+ await ownerChannel.notifyFleetSpawn(event);
630
+ }
631
+ catch (error) {
632
+ deps.log(`[${name}] Agent lifecycle notice delivery failed: `
633
+ + `${error?.message ?? String(error)}`);
634
+ }
635
+ }
636
+ return event;
625
637
  });
626
638
  resolvedMonitorDeps.delivery = {
627
639
  // A wake is only delivered when its turn TERMINATES successfully. A
@@ -39,7 +39,7 @@ export interface ControlRequest {
39
39
  exitCode?: number;
40
40
  effect?: 'not_started' | 'completed' | 'unknown';
41
41
  resourceIds?: Record<string, string>;
42
- presentation?: FleetAuditPresentation;
42
+ presentations?: FleetAuditPresentation[];
43
43
  };
44
44
  }
45
45
  export interface ControlResponse {
@@ -341,7 +341,7 @@ export class RoleControlServer {
341
341
  correlationId: audit.correlationId, class: audit.class, effect: audit.effect,
342
342
  ...(audit.exitCode === undefined ? {} : { exitCode: audit.exitCode }),
343
343
  ...(audit.resourceIds ? { resourceIds: audit.resourceIds } : {}),
344
- ...(audit.presentation ? { presentation: audit.presentation } : {}),
344
+ ...(audit.presentations ? { presentations: audit.presentations } : {}),
345
345
  });
346
346
  this.write(socket, { version: 1, id: request.id, ok: true, result });
347
347
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.1.0-nightly.8",
3
+ "version": "1.1.0-nightly.9",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",