@themoltnet/agent-daemon 0.24.0 → 0.25.1

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.
Files changed (3) hide show
  1. package/README.md +7 -8
  2. package/dist/main.js +207 -13
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -179,14 +179,13 @@ This matters for evals in particular. `run_eval` tasks declare their intended
179
179
  workspace shape in `input.execution.workspace`: `none` becomes a
180
180
  `scratch_mount`, `shared_mount` uses the daemon mount, and
181
181
  `dedicated_worktree` uses an isolated checkout. Downstream
182
- `judge_eval_attempt` tasks only resolve against a still-live producer
183
- session/workspace slot. If the producer slot or its local session/workspace
184
- files cannot be resolved, the judge fails with `producer_context_missing`.
185
- When the judge does claim in time, it immediately forks the producer session and copies the
186
- producer workspace into judge-owned scratch state so the running judge no
187
- longer depends on the producer slot after claim time. Repo-specific
188
- `resumeCommands` that should not run in scratch mode must still be guarded
189
- with `when.workspaceMode`.
182
+ `judge_eval_attempt` tasks can hydrate the producer Pi session from durable
183
+ runtime-session storage when producer slot/workspace metadata is available but
184
+ the local session file is unavailable. Workspace copying still depends on
185
+ producer slot/workspace metadata; if the daemon cannot resolve the required
186
+ producer context, the judge fails with `producer_context_missing`.
187
+ Repo-specific `resumeCommands` that should not run in scratch mode must still
188
+ be guarded with `when.workspaceMode`.
190
189
 
191
190
  ### 1. Start the local stack
192
191
 
package/dist/main.js CHANGED
@@ -12,7 +12,7 @@ import { parseArgs, promisify } from "node:util";
12
12
  import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource } from "@themoltnet/agent-runtime";
13
13
  import { createPiTaskExecutor, findMainWorktree } from "@themoltnet/pi-extension";
14
14
  import { execFile, execFileSync } from "node:child_process";
15
- import { accessSync, constants, existsSync, mkdirSync, readdirSync } from "node:fs";
15
+ import { accessSync, constants, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync } from "node:fs";
16
16
  import { MoltNetError, connect } from "@themoltnet/sdk";
17
17
  import { once } from "node:events";
18
18
  import { pino, transport } from "pino";
@@ -22,6 +22,8 @@ import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
22
22
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
23
23
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
24
24
  import { AsyncLocalStorage } from "node:async_hooks";
25
+ import { mkdir } from "node:fs/promises";
26
+ import { pipeline } from "node:stream/promises";
25
27
  //#region ../../libs/observability/src/instrumentation.ts
26
28
  /**
27
29
  * Register OTel auto-instrumentation for common Node.js modules.
@@ -4569,6 +4571,64 @@ _Object_({
4569
4571
  additionalProperties: false
4570
4572
  });
4571
4573
  //#endregion
4574
+ //#region ../../libs/tasks/src/runtime-sessions.ts
4575
+ var RuntimeSessionKind = Union([
4576
+ Literal("root"),
4577
+ Literal("extend"),
4578
+ Literal("fork")
4579
+ ]);
4580
+ var RuntimeSessionCheckpointKind = Union([Literal("attempt_final")]);
4581
+ _Object_({
4582
+ id: String$1({ format: "uuid" }),
4583
+ teamId: String$1({ format: "uuid" }),
4584
+ taskId: String$1({ format: "uuid" }),
4585
+ attemptN: Integer({ minimum: 1 }),
4586
+ sourceSlotId: Union([String$1({ format: "uuid" }), Null()]),
4587
+ sourceRuntimeProfileId: Union([String$1({ format: "uuid" }), Null()]),
4588
+ sessionKind: RuntimeSessionKind,
4589
+ parentSessionId: Union([String$1({ format: "uuid" }), Null()]),
4590
+ contentType: String$1({
4591
+ minLength: 1,
4592
+ maxLength: 200
4593
+ }),
4594
+ contentEncoding: Union([String$1({
4595
+ minLength: 1,
4596
+ maxLength: 100
4597
+ }), Null()]),
4598
+ sizeBytes: Integer({ minimum: 0 }),
4599
+ sha256: String$1({
4600
+ minLength: 64,
4601
+ maxLength: 64
4602
+ }),
4603
+ storageClass: String$1({
4604
+ minLength: 1,
4605
+ maxLength: 100
4606
+ }),
4607
+ checkpointKind: RuntimeSessionCheckpointKind,
4608
+ uploadedAt: String$1({ format: "date-time" })
4609
+ }, { $id: "RuntimeSession" });
4610
+ _Object_({
4611
+ sourceSlotId: Optional(String$1({ format: "uuid" })),
4612
+ sourceRuntimeProfileId: Optional(String$1({ format: "uuid" })),
4613
+ sessionKind: RuntimeSessionKind,
4614
+ parentSessionId: Optional(String$1({ format: "uuid" }))
4615
+ }, {
4616
+ $id: "UploadRuntimeSessionQuery",
4617
+ additionalProperties: false
4618
+ });
4619
+ String$1({
4620
+ $id: "RuntimeSessionContent",
4621
+ description: "Runtime session content stream.",
4622
+ format: "binary"
4623
+ });
4624
+ _Object_({
4625
+ taskId: String$1({ format: "uuid" }),
4626
+ attemptN: Integer({ minimum: 1 })
4627
+ }, {
4628
+ $id: "RuntimeSessionAttemptParams",
4629
+ additionalProperties: false
4630
+ });
4631
+ //#endregion
4572
4632
  //#region ../../libs/tasks/src/runtime-slots.ts
4573
4633
  var RuntimeWorkspaceKind = Union([
4574
4634
  Literal("origin"),
@@ -9572,12 +9632,13 @@ var ProducerContextResolutionError = class extends Error {
9572
9632
  };
9573
9633
  function createExecutionPlanCache(args) {
9574
9634
  const cache = /* @__PURE__ */ new Map();
9635
+ const runtimeSessionStore = args.runtimeSessionStore ?? createNullRuntimeSessionStore();
9575
9636
  return {
9576
9637
  async getOrCreate(claimedTask) {
9577
9638
  const key = buildClaimedTaskKey(claimedTask);
9578
9639
  const existing = cache.get(key);
9579
9640
  if (existing) return existing;
9580
- const plan = await maybeAttachWarmSlotContext(claimedTask, buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.warmSessionTtlSec, args.workspacePolicy), args.stateDirs, args.slotRegistry);
9641
+ const plan = await maybeAttachWarmSlotContext(claimedTask, buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.warmSessionTtlSec, args.workspacePolicy), args.stateDirs, args.slotRegistry, runtimeSessionStore);
9581
9642
  assertPlanAllowedByWorkspacePolicy(plan, args.workspacePolicy);
9582
9643
  cache.set(key, plan);
9583
9644
  return plan;
@@ -9587,6 +9648,17 @@ function createExecutionPlanCache(args) {
9587
9648
  }
9588
9649
  };
9589
9650
  }
9651
+ function createNullRuntimeSessionStore() {
9652
+ return {
9653
+ async findRuntimeSessionByTaskAttempt() {
9654
+ return null;
9655
+ },
9656
+ async hydrateSession() {
9657
+ throw new ProducerContextResolutionError("Cannot hydrate runtime session: no runtime session store configured");
9658
+ },
9659
+ async uploadAttemptFinal() {}
9660
+ };
9661
+ }
9590
9662
  function assertPlanAllowedByWorkspacePolicy(plan, policy) {
9591
9663
  const allowed = new Set(policy?.allowedWorkspaceModes && policy.allowedWorkspaceModes.length > 0 ? policy.allowedWorkspaceModes : [
9592
9664
  "none",
@@ -9604,10 +9676,17 @@ function planToRuntimeProfileWorkspaceMode(plan) {
9604
9676
  function buildClaimedTaskKey(task) {
9605
9677
  return `${task.task.id}:${task.attemptN}`;
9606
9678
  }
9607
- async function resolveWarmSlot(slotRegistry, teamId, sourceTaskId, sourceAttemptN, stateDirs) {
9679
+ async function resolveWarmSlot(slotRegistry, runtimeSessionStore, teamId, sourceTaskId, sourceAttemptN, stateDirs) {
9608
9680
  const producerContext = await slotRegistry.findLatestSlotByTaskAttempt(teamId, sourceTaskId, sourceAttemptN);
9609
9681
  if (!producerContext) return { kind: "missing" };
9610
- const sourceSessionPath = resolveProducerSessionPath(producerContext);
9682
+ const localSessionPath = resolveProducerSessionPath(producerContext);
9683
+ const remoteSession = localSessionPath ? null : await runtimeSessionStore.findRuntimeSessionByTaskAttempt(teamId, sourceTaskId, sourceAttemptN);
9684
+ const sourceSessionPath = localSessionPath ? localSessionPath : remoteSession ? await runtimeSessionStore.hydrateSession({
9685
+ attemptN: sourceAttemptN,
9686
+ destinationDir: `${stateDirs.piSessionsDir}/remote-${sourceTaskId}-attempt-${sourceAttemptN}`,
9687
+ taskId: sourceTaskId,
9688
+ teamId
9689
+ }) : null;
9611
9690
  if (!sourceSessionPath) return { kind: "no-session-path" };
9612
9691
  return {
9613
9692
  kind: "found",
@@ -9616,11 +9695,11 @@ async function resolveWarmSlot(slotRegistry, teamId, sourceTaskId, sourceAttempt
9616
9695
  workspacePath: resolveProducerWorkspaceCopySource(producerContext, stateDirs)
9617
9696
  };
9618
9697
  }
9619
- async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slotRegistry) {
9698
+ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slotRegistry, runtimeSessionStore) {
9620
9699
  if (claimedTask.task.taskType === "freeform") {
9621
9700
  const continueFrom = claimedTask.task.input.continueFrom;
9622
9701
  if (!continueFrom) return basePlan;
9623
- const resolution = await resolveWarmSlot(slotRegistry, claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN, stateDirs);
9702
+ const resolution = await resolveWarmSlot(slotRegistry, runtimeSessionStore, claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN, stateDirs);
9624
9703
  if (resolution.kind === "missing") throw new ProducerContextResolutionError(`Continuation source task ${continueFrom.taskId} attempt ${continueFrom.attemptN} has no live runtime slot on this daemon — claim affinity filter should have prevented this claim`);
9625
9704
  if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Continuation source attempt ${continueFrom.taskId}/${continueFrom.attemptN} has no persisted Pi session path`);
9626
9705
  const sessionDir = `${stateDirs.piSessionsDir}/continue-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`;
@@ -9657,7 +9736,7 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
9657
9736
  const targetTaskId = typeof claimedTask.task.input.targetTaskId === "string" ? claimedTask.task.input.targetTaskId : null;
9658
9737
  const targetAttemptN = typeof claimedTask.task.input.targetAttemptN === "number" ? claimedTask.task.input.targetAttemptN : null;
9659
9738
  if (!targetTaskId || !targetAttemptN) throw new ProducerContextResolutionError("judge_eval_attempt is missing targetTaskId/targetAttemptN");
9660
- const resolution = await resolveWarmSlot(slotRegistry, claimedTask.task.teamId, targetTaskId, targetAttemptN, stateDirs);
9739
+ const resolution = await resolveWarmSlot(slotRegistry, runtimeSessionStore, claimedTask.task.teamId, targetTaskId, targetAttemptN, stateDirs);
9661
9740
  if (resolution.kind === "missing") throw new ProducerContextResolutionError(`No live producer runtime slot found for task ${targetTaskId} attempt ${targetAttemptN}`);
9662
9741
  if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has no persisted Pi session path`);
9663
9742
  return {
@@ -10067,6 +10146,70 @@ function isExecutable(path) {
10067
10146
  }
10068
10147
  }
10069
10148
  //#endregion
10149
+ //#region src/lib/runtime-sessions.ts
10150
+ function resolveRuntimeSessionKind(claimedTask) {
10151
+ const continueFrom = resolveContinueFrom(claimedTask);
10152
+ if (!continueFrom) return "root";
10153
+ return continueFrom.mode === "fork" ? "fork" : "extend";
10154
+ }
10155
+ async function resolveParentRuntimeSession(runtimeSessionStore, claimedTask) {
10156
+ const continueFrom = resolveContinueFrom(claimedTask);
10157
+ if (!continueFrom) return null;
10158
+ return runtimeSessionStore.findRuntimeSessionByTaskAttempt(claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN);
10159
+ }
10160
+ function applyRuntimeSessionUploadFailure(output, err) {
10161
+ if (output.status !== "completed") return output;
10162
+ return {
10163
+ ...output,
10164
+ contentSignature: void 0,
10165
+ error: {
10166
+ code: "runtime_session_upload_failed",
10167
+ message: "Task completed, but durable runtime session checkpoint upload failed: " + (err instanceof Error ? err.message : String(err)),
10168
+ retryable: true
10169
+ },
10170
+ output: null,
10171
+ outputCid: null,
10172
+ status: "failed"
10173
+ };
10174
+ }
10175
+ function createApiRuntimeSessionStore(args) {
10176
+ const { agent } = args;
10177
+ return {
10178
+ async findRuntimeSessionByTaskAttempt(teamId, taskId, attemptN) {
10179
+ return agent.runtimeSessions.getForAttempt({
10180
+ attemptN,
10181
+ taskId
10182
+ }, { teamId });
10183
+ },
10184
+ async hydrateSession(input) {
10185
+ const downloaded = await agent.runtimeSessions.download({
10186
+ attemptN: input.attemptN,
10187
+ taskId: input.taskId
10188
+ }, { teamId: input.teamId });
10189
+ await mkdir(input.destinationDir, { recursive: true });
10190
+ const sessionPath = join(input.destinationDir, `remote-${input.taskId}-attempt-${input.attemptN}.jsonl`);
10191
+ await pipeline(downloaded, createWriteStream(sessionPath));
10192
+ return sessionPath;
10193
+ },
10194
+ async uploadAttemptFinal(input) {
10195
+ const sessionPath = resolveLatestPiSessionPath(input.sessionDir);
10196
+ if (!sessionPath) throw new Error(`Cannot upload runtime session for ${input.taskId}/${input.attemptN}: no local session file in ${input.sessionDir}`);
10197
+ await agent.runtimeSessions.upload({
10198
+ attemptN: input.attemptN,
10199
+ taskId: input.taskId
10200
+ }, createReadStream(sessionPath), {
10201
+ parentSessionId: input.parentSessionId ?? void 0,
10202
+ sessionKind: input.sessionKind,
10203
+ sourceRuntimeProfileId: input.sourceRuntimeProfileId ?? void 0,
10204
+ sourceSlotId: input.sourceSlotId ?? void 0
10205
+ }, { teamId: input.teamId });
10206
+ }
10207
+ };
10208
+ }
10209
+ function resolveContinueFrom(claimedTask) {
10210
+ return claimedTask.task.input.continueFrom;
10211
+ }
10212
+ //#endregion
10070
10213
  //#region src/lib/runtime-slots.ts
10071
10214
  function createApiRuntimeSlotStore(args) {
10072
10215
  const { agent } = args;
@@ -10108,7 +10251,11 @@ function createApiRuntimeSlotStore(args) {
10108
10251
  }, { teamId });
10109
10252
  if (!resolved) return null;
10110
10253
  return {
10111
- slot: { expiresAtMs: resolved.slot.expiresAtMs },
10254
+ slot: {
10255
+ expiresAtMs: resolved.slot.expiresAtMs,
10256
+ id: resolved.slot.id,
10257
+ runtimeProfileId: resolved.slot.runtimeProfileId
10258
+ },
10112
10259
  session: resolved.slot.sessionDir ? {
10113
10260
  sessionDir: resolved.slot.sessionDir,
10114
10261
  sessionPath: resolved.slot.sessionPath
@@ -10263,6 +10410,7 @@ async function runPolling(opts) {
10263
10410
  });
10264
10411
  for (const profile of profiles) validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
10265
10412
  const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
10413
+ const runtimeSessionStore = createApiRuntimeSessionStore({ agent: ctx.agent });
10266
10414
  const runtimes = /* @__PURE__ */ new Map();
10267
10415
  for (const profile of profiles) {
10268
10416
  const common = parseCommonOptions(values, { runtimeDefaults: {
@@ -10292,7 +10440,8 @@ async function runPolling(opts) {
10292
10440
  defaultWorkspaceMode: profile.defaultWorkspaceMode,
10293
10441
  allowedWorkspaceModes: profile.allowedWorkspaceModes
10294
10442
  },
10295
- slotRegistry
10443
+ slotRegistry,
10444
+ runtimeSessionStore
10296
10445
  });
10297
10446
  runtimes.set(profile.id, {
10298
10447
  common,
@@ -10401,7 +10550,8 @@ async function runPolling(opts) {
10401
10550
  stopWhenEmpty: opts.stopWhenEmpty,
10402
10551
  debug: baseCommon.debug,
10403
10552
  logger: rootLogger,
10404
- slotRegistry
10553
+ slotRegistry,
10554
+ sessionRegistry: runtimeSessionStore
10405
10555
  }),
10406
10556
  makeReporter: (claimedTask) => {
10407
10557
  const selected = runtimeForClaimedTask(runtimes, claimedTask);
@@ -10416,7 +10566,28 @@ async function runPolling(opts) {
10416
10566
  onTaskFinished: async (output, claimedTask) => {
10417
10567
  const selected = runtimeForClaimedTask(runtimes, claimedTask);
10418
10568
  const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
10419
- return finalizeTask(ctx.agent, output, {
10569
+ let terminalOutput = output;
10570
+ if (resolved?.session?.sessionDir) try {
10571
+ const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
10572
+ await runtimeSessionStore.uploadAttemptFinal({
10573
+ attemptN: claimedTask.attemptN,
10574
+ parentSessionId: parentSession?.id ?? null,
10575
+ sessionDir: resolved.session.sessionDir,
10576
+ sessionKind: resolveRuntimeSessionKind(claimedTask),
10577
+ sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
10578
+ sourceSlotId: resolved.slot.id,
10579
+ taskId: claimedTask.task.id,
10580
+ teamId: claimedTask.task.teamId
10581
+ });
10582
+ } catch (err) {
10583
+ rootLogger.error({
10584
+ err,
10585
+ taskId: claimedTask.task.id,
10586
+ attemptN: claimedTask.attemptN
10587
+ }, "agent-daemon.runtime_session_upload_failed");
10588
+ terminalOutput = applyRuntimeSessionUploadFailure(output, err);
10589
+ }
10590
+ return finalizeTask(ctx.agent, terminalOutput, {
10420
10591
  task: claimedTask.task,
10421
10592
  slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
10422
10593
  writeCorrelationAnchors: makePrBodyAnchorWriter({
@@ -10681,6 +10852,7 @@ async function runOnce(argv) {
10681
10852
  activatePiCodingAgentDir(piAgentDir.path);
10682
10853
  const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
10683
10854
  const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
10855
+ const runtimeSessionStore = createApiRuntimeSessionStore({ agent: ctx.agent });
10684
10856
  const slotIdentity = {
10685
10857
  agentName: opts.agent,
10686
10858
  runtimeProfileId: profile.id
@@ -10693,7 +10865,8 @@ async function runOnce(argv) {
10693
10865
  defaultWorkspaceMode: profile.defaultWorkspaceMode,
10694
10866
  allowedWorkspaceModes: profile.allowedWorkspaceModes
10695
10867
  },
10696
- slotRegistry
10868
+ slotRegistry,
10869
+ runtimeSessionStore
10697
10870
  });
10698
10871
  const otelShutdown = await initWorkerOtel({
10699
10872
  serviceName: "moltnet.agent-daemon.once",
@@ -10853,7 +11026,28 @@ async function runOnce(argv) {
10853
11026
  }),
10854
11027
  onTaskFinished: async (output, claimedTask) => {
10855
11028
  const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
10856
- return finalizeTask(ctx.agent, output, {
11029
+ let terminalOutput = output;
11030
+ if (resolved?.session?.sessionDir) try {
11031
+ const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
11032
+ await runtimeSessionStore.uploadAttemptFinal({
11033
+ attemptN: claimedTask.attemptN,
11034
+ parentSessionId: parentSession?.id ?? null,
11035
+ sessionDir: resolved.session.sessionDir,
11036
+ sessionKind: resolveRuntimeSessionKind(claimedTask),
11037
+ sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
11038
+ sourceSlotId: resolved.slot.id,
11039
+ taskId: claimedTask.task.id,
11040
+ teamId: claimedTask.task.teamId
11041
+ });
11042
+ } catch (err) {
11043
+ rootLogger.error({
11044
+ err,
11045
+ taskId: claimedTask.task.id,
11046
+ attemptN: claimedTask.attemptN
11047
+ }, "agent-daemon.runtime_session_upload_failed");
11048
+ terminalOutput = applyRuntimeSessionUploadFailure(output, err);
11049
+ }
11050
+ return finalizeTask(ctx.agent, terminalOutput, {
10857
11051
  task: claimedTask.task,
10858
11052
  slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
10859
11053
  writeCorrelationAnchors,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.24.0",
3
+ "version": "0.25.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "description": "MoltNet agent daemon — claims and executes tasks (fulfill_brief, assess_brief) from the MoltNet task-service via Pi-headless. CLI: moltnet-agent.",
@@ -45,18 +45,18 @@
45
45
  "@opentelemetry/semantic-conventions": "^1.39.0",
46
46
  "pino": "^10.3.1",
47
47
  "pino-pretty": "^13.1.3",
48
- "@themoltnet/agent-runtime": "0.29.0",
49
- "@themoltnet/pi-extension": "0.27.0",
50
- "@themoltnet/sdk": "0.112.0"
48
+ "@themoltnet/pi-extension": "0.27.2",
49
+ "@themoltnet/sdk": "0.113.1",
50
+ "@themoltnet/agent-runtime": "0.30.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "tsx": "^4.7.0",
54
54
  "typescript": "~5.9.2",
55
55
  "vite": "^8.0.0",
56
56
  "vitest": "^3.0.0",
57
- "@moltnet/observability": "0.1.0",
58
57
  "@moltnet/bootstrap": "0.1.0",
59
58
  "@moltnet/crypto-service": "0.1.0",
59
+ "@moltnet/observability": "0.1.0",
60
60
  "@moltnet/tasks": "0.1.0"
61
61
  },
62
62
  "nx": {