@namzu/sdk 0.1.7 → 0.2.0
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/CHANGELOG.md +23 -5
- package/README.md +14 -9
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +5 -3
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/RouterAgent.d.ts.map +1 -1
- package/dist/agents/RouterAgent.js +3 -0
- package/dist/agents/RouterAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +18 -5
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +6 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/a2a/task.d.ts +2 -2
- package/dist/bridge/a2a/task.d.ts.map +1 -1
- package/dist/bridge/a2a/task.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +6 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/constants/a2a/index.d.ts +2 -2
- package/dist/constants/a2a/index.d.ts.map +1 -1
- package/dist/constants/a2a/index.js.map +1 -1
- package/dist/contracts/api.d.ts +22 -3
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +3 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/gateway/local.d.ts.map +1 -1
- package/dist/gateway/local.js +6 -0
- package/dist/gateway/local.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/manager/agent/__tests__/lifecycle.test.d.ts +2 -0
- package/dist/manager/agent/__tests__/lifecycle.test.d.ts.map +1 -0
- package/dist/manager/agent/__tests__/lifecycle.test.js +302 -0
- package/dist/manager/agent/__tests__/lifecycle.test.js.map +1 -0
- package/dist/manager/agent/lifecycle.d.ts +58 -3
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +311 -12
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +8 -1
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +15 -0
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +25 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/runtime/query/__tests__/context.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/context.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/context.test.js +84 -0
- package/dist/runtime/query/__tests__/context.test.js.map +1 -0
- package/dist/runtime/query/context.d.ts +55 -2
- package/dist/runtime/query/context.d.ts.map +1 -1
- package/dist/runtime/query/context.js +48 -8
- package/dist/runtime/query/context.js.map +1 -1
- package/dist/runtime/query/events.d.ts.map +1 -1
- package/dist/runtime/query/events.js +8 -0
- package/dist/runtime/query/events.js.map +1 -1
- package/dist/runtime/query/index.d.ts +25 -2
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +11 -1
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/session/__tests__/integration/_fixtures.d.ts +115 -0
- package/dist/session/__tests__/integration/_fixtures.d.ts.map +1 -0
- package/dist/session/__tests__/integration/_fixtures.js +198 -0
- package/dist/session/__tests__/integration/_fixtures.js.map +1 -0
- package/dist/session/__tests__/integration/capacity-caps.test.d.ts +13 -0
- package/dist/session/__tests__/integration/capacity-caps.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/capacity-caps.test.js +116 -0
- package/dist/session/__tests__/integration/capacity-caps.test.js.map +1 -0
- package/dist/session/__tests__/integration/e2e-spawn.test.d.ts +18 -0
- package/dist/session/__tests__/integration/e2e-spawn.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/e2e-spawn.test.js +226 -0
- package/dist/session/__tests__/integration/e2e-spawn.test.js.map +1 -0
- package/dist/session/__tests__/integration/event-stream-ordering.test.d.ts +15 -0
- package/dist/session/__tests__/integration/event-stream-ordering.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/event-stream-ordering.test.js +323 -0
- package/dist/session/__tests__/integration/event-stream-ordering.test.js.map +1 -0
- package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.d.ts +12 -0
- package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js +170 -0
- package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js.map +1 -0
- package/dist/session/__tests__/integration/handoff-illegal-transition.test.d.ts +18 -0
- package/dist/session/__tests__/integration/handoff-illegal-transition.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/handoff-illegal-transition.test.js +146 -0
- package/dist/session/__tests__/integration/handoff-illegal-transition.test.js.map +1 -0
- package/dist/session/__tests__/integration/handoff-single-e2e.test.d.ts +15 -0
- package/dist/session/__tests__/integration/handoff-single-e2e.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/handoff-single-e2e.test.js +163 -0
- package/dist/session/__tests__/integration/handoff-single-e2e.test.js.map +1 -0
- package/dist/session/__tests__/integration/hierarchy-lifecycle.test.d.ts +12 -0
- package/dist/session/__tests__/integration/hierarchy-lifecycle.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js +157 -0
- package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js.map +1 -0
- package/dist/session/__tests__/integration/migration-filesystem.test.d.ts +11 -0
- package/dist/session/__tests__/integration/migration-filesystem.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/migration-filesystem.test.js +140 -0
- package/dist/session/__tests__/integration/migration-filesystem.test.js.map +1 -0
- package/dist/session/__tests__/integration/migration-id-prefix.test.d.ts +13 -0
- package/dist/session/__tests__/integration/migration-id-prefix.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/migration-id-prefix.test.js +84 -0
- package/dist/session/__tests__/integration/migration-id-prefix.test.js.map +1 -0
- package/dist/session/__tests__/integration/prev-artifact-dag.test.d.ts +14 -0
- package/dist/session/__tests__/integration/prev-artifact-dag.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/prev-artifact-dag.test.js +241 -0
- package/dist/session/__tests__/integration/prev-artifact-dag.test.js.map +1 -0
- package/dist/session/__tests__/integration/retention-archive.test.d.ts +12 -0
- package/dist/session/__tests__/integration/retention-archive.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/retention-archive.test.js +186 -0
- package/dist/session/__tests__/integration/retention-archive.test.js.map +1 -0
- package/dist/session/__tests__/integration/summary-materialization-e2e.test.d.ts +18 -0
- package/dist/session/__tests__/integration/summary-materialization-e2e.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/summary-materialization-e2e.test.js +200 -0
- package/dist/session/__tests__/integration/summary-materialization-e2e.test.js.map +1 -0
- package/dist/session/__tests__/integration/tenant-isolation.test.d.ts +14 -0
- package/dist/session/__tests__/integration/tenant-isolation.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/tenant-isolation.test.js +180 -0
- package/dist/session/__tests__/integration/tenant-isolation.test.js.map +1 -0
- package/dist/session/errors.d.ts +60 -0
- package/dist/session/errors.d.ts.map +1 -0
- package/dist/session/errors.js +50 -0
- package/dist/session/errors.js.map +1 -0
- package/dist/session/events/index.d.ts +4 -0
- package/dist/session/events/index.d.ts.map +1 -0
- package/dist/session/events/index.js +8 -0
- package/dist/session/events/index.js.map +1 -0
- package/dist/session/events/schema-version.d.ts +13 -0
- package/dist/session/events/schema-version.d.ts.map +1 -0
- package/dist/session/events/schema-version.js +12 -0
- package/dist/session/events/schema-version.js.map +1 -0
- package/dist/session/events/types.d.ts +64 -0
- package/dist/session/events/types.d.ts.map +1 -0
- package/dist/session/events/types.js +2 -0
- package/dist/session/events/types.js.map +1 -0
- package/dist/session/handoff/__tests__/broadcast.test.d.ts +2 -0
- package/dist/session/handoff/__tests__/broadcast.test.d.ts.map +1 -0
- package/dist/session/handoff/__tests__/broadcast.test.js +243 -0
- package/dist/session/handoff/__tests__/broadcast.test.js.map +1 -0
- package/dist/session/handoff/__tests__/capacity.test.d.ts +2 -0
- package/dist/session/handoff/__tests__/capacity.test.d.ts.map +1 -0
- package/dist/session/handoff/__tests__/capacity.test.js +100 -0
- package/dist/session/handoff/__tests__/capacity.test.js.map +1 -0
- package/dist/session/handoff/__tests__/single.test.d.ts +2 -0
- package/dist/session/handoff/__tests__/single.test.d.ts.map +1 -0
- package/dist/session/handoff/__tests__/single.test.js +230 -0
- package/dist/session/handoff/__tests__/single.test.js.map +1 -0
- package/dist/session/handoff/assignment.d.ts +59 -0
- package/dist/session/handoff/assignment.d.ts.map +1 -0
- package/dist/session/handoff/assignment.js +11 -0
- package/dist/session/handoff/assignment.js.map +1 -0
- package/dist/session/handoff/broadcast.d.ts +47 -0
- package/dist/session/handoff/broadcast.d.ts.map +1 -0
- package/dist/session/handoff/broadcast.js +296 -0
- package/dist/session/handoff/broadcast.js.map +1 -0
- package/dist/session/handoff/capacity.d.ts +66 -0
- package/dist/session/handoff/capacity.d.ts.map +1 -0
- package/dist/session/handoff/capacity.js +60 -0
- package/dist/session/handoff/capacity.js.map +1 -0
- package/dist/session/handoff/events.d.ts +66 -0
- package/dist/session/handoff/events.d.ts.map +1 -0
- package/dist/session/handoff/events.js +13 -0
- package/dist/session/handoff/events.js.map +1 -0
- package/dist/session/handoff/index.d.ts +12 -0
- package/dist/session/handoff/index.d.ts.map +1 -0
- package/dist/session/handoff/index.js +9 -0
- package/dist/session/handoff/index.js.map +1 -0
- package/dist/session/handoff/single.d.ts +62 -0
- package/dist/session/handoff/single.d.ts.map +1 -0
- package/dist/session/handoff/single.js +217 -0
- package/dist/session/handoff/single.js.map +1 -0
- package/dist/session/handoff/version.d.ts +52 -0
- package/dist/session/handoff/version.d.ts.map +1 -0
- package/dist/session/handoff/version.js +36 -0
- package/dist/session/handoff/version.js.map +1 -0
- package/dist/session/hierarchy/__tests__/session.test.d.ts +2 -0
- package/dist/session/hierarchy/__tests__/session.test.d.ts.map +1 -0
- package/dist/session/hierarchy/__tests__/session.test.js +67 -0
- package/dist/session/hierarchy/__tests__/session.test.js.map +1 -0
- package/dist/session/hierarchy/actor.d.ts +26 -0
- package/dist/session/hierarchy/actor.d.ts.map +1 -0
- package/dist/session/hierarchy/actor.js +2 -0
- package/dist/session/hierarchy/actor.js.map +1 -0
- package/dist/session/hierarchy/index.d.ts +8 -0
- package/dist/session/hierarchy/index.d.ts.map +1 -0
- package/dist/session/hierarchy/index.js +4 -0
- package/dist/session/hierarchy/index.js.map +1 -0
- package/dist/session/hierarchy/lineage.d.ts +15 -0
- package/dist/session/hierarchy/lineage.d.ts.map +1 -0
- package/dist/session/hierarchy/lineage.js +2 -0
- package/dist/session/hierarchy/lineage.js.map +1 -0
- package/dist/session/hierarchy/project.d.ts +40 -0
- package/dist/session/hierarchy/project.d.ts.map +1 -0
- package/dist/session/hierarchy/project.js +2 -0
- package/dist/session/hierarchy/project.js.map +1 -0
- package/dist/session/hierarchy/session.d.ts +59 -0
- package/dist/session/hierarchy/session.d.ts.map +1 -0
- package/dist/session/hierarchy/session.js +51 -0
- package/dist/session/hierarchy/session.js.map +1 -0
- package/dist/session/hierarchy/sub-session.d.ts +76 -0
- package/dist/session/hierarchy/sub-session.d.ts.map +1 -0
- package/dist/session/hierarchy/sub-session.js +2 -0
- package/dist/session/hierarchy/sub-session.js.map +1 -0
- package/dist/session/hierarchy/tenant.d.ts +13 -0
- package/dist/session/hierarchy/tenant.d.ts.map +1 -0
- package/dist/session/hierarchy/tenant.js +2 -0
- package/dist/session/hierarchy/tenant.js.map +1 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +15 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/intervention/__tests__/prev-artifact.test.d.ts +2 -0
- package/dist/session/intervention/__tests__/prev-artifact.test.d.ts.map +1 -0
- package/dist/session/intervention/__tests__/prev-artifact.test.js +179 -0
- package/dist/session/intervention/__tests__/prev-artifact.test.js.map +1 -0
- package/dist/session/intervention/index.d.ts +3 -0
- package/dist/session/intervention/index.d.ts.map +1 -0
- package/dist/session/intervention/index.js +8 -0
- package/dist/session/intervention/index.js.map +1 -0
- package/dist/session/intervention/prev-artifact.d.ts +103 -0
- package/dist/session/intervention/prev-artifact.d.ts.map +1 -0
- package/dist/session/intervention/prev-artifact.js +112 -0
- package/dist/session/intervention/prev-artifact.js.map +1 -0
- package/dist/session/migration/__tests__/filesystem.test.d.ts +2 -0
- package/dist/session/migration/__tests__/filesystem.test.d.ts.map +1 -0
- package/dist/session/migration/__tests__/filesystem.test.js +188 -0
- package/dist/session/migration/__tests__/filesystem.test.js.map +1 -0
- package/dist/session/migration/__tests__/id-prefix.test.d.ts +2 -0
- package/dist/session/migration/__tests__/id-prefix.test.d.ts.map +1 -0
- package/dist/session/migration/__tests__/id-prefix.test.js +83 -0
- package/dist/session/migration/__tests__/id-prefix.test.js.map +1 -0
- package/dist/session/migration/__tests__/marker.test.d.ts +2 -0
- package/dist/session/migration/__tests__/marker.test.d.ts.map +1 -0
- package/dist/session/migration/__tests__/marker.test.js +75 -0
- package/dist/session/migration/__tests__/marker.test.js.map +1 -0
- package/dist/session/migration/errors.d.ts +26 -0
- package/dist/session/migration/errors.d.ts.map +1 -0
- package/dist/session/migration/errors.js +22 -0
- package/dist/session/migration/errors.js.map +1 -0
- package/dist/session/migration/filesystem.d.ts +94 -0
- package/dist/session/migration/filesystem.d.ts.map +1 -0
- package/dist/session/migration/filesystem.js +319 -0
- package/dist/session/migration/filesystem.js.map +1 -0
- package/dist/session/migration/id-prefix.d.ts +98 -0
- package/dist/session/migration/id-prefix.d.ts.map +1 -0
- package/dist/session/migration/id-prefix.js +116 -0
- package/dist/session/migration/id-prefix.js.map +1 -0
- package/dist/session/migration/index.d.ts +8 -0
- package/dist/session/migration/index.d.ts.map +1 -0
- package/dist/session/migration/index.js +8 -0
- package/dist/session/migration/index.js.map +1 -0
- package/dist/session/migration/marker.d.ts +57 -0
- package/dist/session/migration/marker.d.ts.map +1 -0
- package/dist/session/migration/marker.js +111 -0
- package/dist/session/migration/marker.js.map +1 -0
- package/dist/session/retention/__tests__/archive.test.d.ts +2 -0
- package/dist/session/retention/__tests__/archive.test.d.ts.map +1 -0
- package/dist/session/retention/__tests__/archive.test.js +252 -0
- package/dist/session/retention/__tests__/archive.test.js.map +1 -0
- package/dist/session/retention/__tests__/disk-backend.test.d.ts +2 -0
- package/dist/session/retention/__tests__/disk-backend.test.d.ts.map +1 -0
- package/dist/session/retention/__tests__/disk-backend.test.js +154 -0
- package/dist/session/retention/__tests__/disk-backend.test.js.map +1 -0
- package/dist/session/retention/archive-backend-ref.d.ts +18 -0
- package/dist/session/retention/archive-backend-ref.d.ts.map +1 -0
- package/dist/session/retention/archive-backend-ref.js +2 -0
- package/dist/session/retention/archive-backend-ref.js.map +1 -0
- package/dist/session/retention/archive.d.ts +130 -0
- package/dist/session/retention/archive.d.ts.map +1 -0
- package/dist/session/retention/archive.js +203 -0
- package/dist/session/retention/archive.js.map +1 -0
- package/dist/session/retention/backend.d.ts +101 -0
- package/dist/session/retention/backend.d.ts.map +1 -0
- package/dist/session/retention/backend.js +15 -0
- package/dist/session/retention/backend.js.map +1 -0
- package/dist/session/retention/disk-backend.d.ts +59 -0
- package/dist/session/retention/disk-backend.d.ts.map +1 -0
- package/dist/session/retention/disk-backend.js +236 -0
- package/dist/session/retention/disk-backend.js.map +1 -0
- package/dist/session/retention/index.d.ts +9 -0
- package/dist/session/retention/index.d.ts.map +1 -0
- package/dist/session/retention/index.js +6 -0
- package/dist/session/retention/index.js.map +1 -0
- package/dist/session/retention/policy.d.ts +49 -0
- package/dist/session/retention/policy.d.ts.map +1 -0
- package/dist/session/retention/policy.js +21 -0
- package/dist/session/retention/policy.js.map +1 -0
- package/dist/session/summary/__tests__/materialize.test.d.ts +2 -0
- package/dist/session/summary/__tests__/materialize.test.d.ts.map +1 -0
- package/dist/session/summary/__tests__/materialize.test.js +269 -0
- package/dist/session/summary/__tests__/materialize.test.js.map +1 -0
- package/dist/session/summary/deliverable.d.ts +74 -0
- package/dist/session/summary/deliverable.d.ts.map +1 -0
- package/dist/session/summary/deliverable.js +20 -0
- package/dist/session/summary/deliverable.js.map +1 -0
- package/dist/session/summary/index.d.ts +6 -0
- package/dist/session/summary/index.d.ts.map +1 -0
- package/dist/session/summary/index.js +9 -0
- package/dist/session/summary/index.js.map +1 -0
- package/dist/session/summary/materialize.d.ts +82 -0
- package/dist/session/summary/materialize.d.ts.map +1 -0
- package/dist/session/summary/materialize.js +117 -0
- package/dist/session/summary/materialize.js.map +1 -0
- package/dist/session/summary/ref.d.ts +91 -0
- package/dist/session/summary/ref.d.ts.map +1 -0
- package/dist/session/summary/ref.js +51 -0
- package/dist/session/summary/ref.js.map +1 -0
- package/dist/session/workspace/__tests__/git-worktree.test.d.ts +2 -0
- package/dist/session/workspace/__tests__/git-worktree.test.d.ts.map +1 -0
- package/dist/session/workspace/__tests__/git-worktree.test.js +244 -0
- package/dist/session/workspace/__tests__/git-worktree.test.js.map +1 -0
- package/dist/session/workspace/__tests__/path-builder.test.d.ts +2 -0
- package/dist/session/workspace/__tests__/path-builder.test.d.ts.map +1 -0
- package/dist/session/workspace/__tests__/path-builder.test.js +37 -0
- package/dist/session/workspace/__tests__/path-builder.test.js.map +1 -0
- package/dist/session/workspace/driver.d.ts +55 -0
- package/dist/session/workspace/driver.d.ts.map +1 -0
- package/dist/session/workspace/driver.js +12 -0
- package/dist/session/workspace/driver.js.map +1 -0
- package/dist/session/workspace/git-worktree.d.ts +65 -0
- package/dist/session/workspace/git-worktree.d.ts.map +1 -0
- package/dist/session/workspace/git-worktree.js +156 -0
- package/dist/session/workspace/git-worktree.js.map +1 -0
- package/dist/session/workspace/index.d.ts +8 -0
- package/dist/session/workspace/index.d.ts.map +1 -0
- package/dist/session/workspace/index.js +7 -0
- package/dist/session/workspace/index.js.map +1 -0
- package/dist/session/workspace/path-builder.d.ts +50 -0
- package/dist/session/workspace/path-builder.d.ts.map +1 -0
- package/dist/session/workspace/path-builder.js +50 -0
- package/dist/session/workspace/path-builder.js.map +1 -0
- package/dist/session/workspace/ref.d.ts +46 -0
- package/dist/session/workspace/ref.d.ts.map +1 -0
- package/dist/session/workspace/ref.js +11 -0
- package/dist/session/workspace/ref.js.map +1 -0
- package/dist/session/workspace/registry.d.ts +26 -0
- package/dist/session/workspace/registry.d.ts.map +1 -0
- package/dist/session/workspace/registry.js +35 -0
- package/dist/session/workspace/registry.js.map +1 -0
- package/dist/store/conversation/memory.d.ts +22 -0
- package/dist/store/conversation/memory.d.ts.map +1 -1
- package/dist/store/conversation/memory.js +22 -0
- package/dist/store/conversation/memory.js.map +1 -1
- package/dist/store/session/__tests__/disk.test.d.ts +2 -0
- package/dist/store/session/__tests__/disk.test.d.ts.map +1 -0
- package/dist/store/session/__tests__/disk.test.js +240 -0
- package/dist/store/session/__tests__/disk.test.js.map +1 -0
- package/dist/store/session/__tests__/memory.test.d.ts +2 -0
- package/dist/store/session/__tests__/memory.test.d.ts.map +1 -0
- package/dist/store/session/__tests__/memory.test.js +217 -0
- package/dist/store/session/__tests__/memory.test.js.map +1 -0
- package/dist/store/session/disk.d.ts +85 -0
- package/dist/store/session/disk.d.ts.map +1 -0
- package/dist/store/session/disk.js +757 -0
- package/dist/store/session/disk.js.map +1 -0
- package/dist/store/session/index.d.ts +7 -0
- package/dist/store/session/index.d.ts.map +1 -0
- package/dist/store/session/index.js +11 -0
- package/dist/store/session/index.js.map +1 -0
- package/dist/store/session/linkage.d.ts +38 -0
- package/dist/store/session/linkage.d.ts.map +1 -0
- package/dist/store/session/linkage.js +64 -0
- package/dist/store/session/linkage.js.map +1 -0
- package/dist/store/session/memory.d.ts +48 -0
- package/dist/store/session/memory.d.ts.map +1 -0
- package/dist/store/session/memory.js +322 -0
- package/dist/store/session/memory.js.map +1 -0
- package/dist/store/session/messages.d.ts +20 -0
- package/dist/store/session/messages.d.ts.map +1 -0
- package/dist/store/session/messages.js +12 -0
- package/dist/store/session/messages.js.map +1 -0
- package/dist/tools/builtins/__tests__/structuredOutput.example.d.ts +1 -1
- package/dist/types/agent/base.d.ts +28 -1
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +50 -2
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/agent/task.js.map +1 -1
- package/dist/types/conversation/index.d.ts +7 -0
- package/dist/types/conversation/index.d.ts.map +1 -1
- package/dist/types/ids/index.d.ts +26 -3
- package/dist/types/ids/index.d.ts.map +1 -1
- package/dist/types/ids/index.js +8 -1
- package/dist/types/ids/index.js.map +1 -1
- package/dist/types/invocation/__tests__/state.test.js +36 -29
- package/dist/types/invocation/__tests__/state.test.js.map +1 -1
- package/dist/types/invocation/index.d.ts +20 -4
- package/dist/types/invocation/index.d.ts.map +1 -1
- package/dist/types/invocation/index.js +10 -7
- package/dist/types/invocation/index.js.map +1 -1
- package/dist/types/run/config.d.ts +11 -1
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +26 -1
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/index.d.ts.map +1 -1
- package/dist/types/run/index.js +8 -0
- package/dist/types/run/index.js.map +1 -1
- package/dist/types/run/metadata.d.ts +24 -1
- package/dist/types/run/metadata.d.ts.map +1 -1
- package/dist/types/run/status.d.ts +26 -0
- package/dist/types/run/status.d.ts.map +1 -0
- package/dist/types/run/status.js +2 -0
- package/dist/types/run/status.js.map +1 -0
- package/dist/types/session/ids.d.ts +18 -0
- package/dist/types/session/ids.d.ts.map +1 -0
- package/dist/types/session/ids.js +12 -0
- package/dist/types/session/ids.js.map +1 -0
- package/dist/types/session/index.d.ts +3 -0
- package/dist/types/session/index.d.ts.map +1 -0
- package/dist/types/session/index.js +5 -0
- package/dist/types/session/index.js.map +1 -0
- package/dist/types/session/store.d.ts +188 -0
- package/dist/types/session/store.d.ts.map +1 -0
- package/dist/types/session/store.js +14 -0
- package/dist/types/session/store.js.map +1 -0
- package/dist/utils/id.d.ts +18 -1
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +42 -4
- package/dist/utils/id.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +7 -3
- package/src/agents/RouterAgent.ts +5 -0
- package/src/agents/SupervisorAgent.ts +26 -6
- package/src/bridge/a2a/mapper.ts +7 -0
- package/src/bridge/a2a/task.ts +2 -2
- package/src/bridge/sse/mapper.ts +8 -1
- package/src/constants/a2a/index.ts +2 -2
- package/src/contracts/api.ts +23 -3
- package/src/contracts/index.ts +2 -0
- package/src/gateway/local.ts +6 -0
- package/src/index.ts +14 -0
- package/src/manager/agent/__tests__/lifecycle.test.ts +452 -0
- package/src/manager/agent/lifecycle.ts +434 -19
- package/src/manager/run/persistence.ts +20 -1
- package/src/run/reporter.ts +28 -0
- package/src/runtime/query/__tests__/context.test.ts +101 -0
- package/src/runtime/query/context.ts +106 -10
- package/src/runtime/query/events.ts +8 -0
- package/src/runtime/query/index.ts +41 -3
- package/src/session/__tests__/integration/_fixtures.ts +282 -0
- package/src/session/__tests__/integration/capacity-caps.test.ts +164 -0
- package/src/session/__tests__/integration/e2e-spawn.test.ts +278 -0
- package/src/session/__tests__/integration/event-stream-ordering.test.ts +403 -0
- package/src/session/__tests__/integration/handoff-broadcast-e2e.test.ts +245 -0
- package/src/session/__tests__/integration/handoff-illegal-transition.test.ts +179 -0
- package/src/session/__tests__/integration/handoff-single-e2e.test.ts +220 -0
- package/src/session/__tests__/integration/hierarchy-lifecycle.test.ts +237 -0
- package/src/session/__tests__/integration/migration-filesystem.test.ts +209 -0
- package/src/session/__tests__/integration/migration-id-prefix.test.ts +101 -0
- package/src/session/__tests__/integration/prev-artifact-dag.test.ts +318 -0
- package/src/session/__tests__/integration/retention-archive.test.ts +231 -0
- package/src/session/__tests__/integration/summary-materialization-e2e.test.ts +237 -0
- package/src/session/__tests__/integration/tenant-isolation.test.ts +282 -0
- package/src/session/errors.ts +70 -0
- package/src/session/events/index.ts +16 -0
- package/src/session/events/schema-version.ts +13 -0
- package/src/session/events/types.ts +71 -0
- package/src/session/handoff/__tests__/broadcast.test.ts +350 -0
- package/src/session/handoff/__tests__/capacity.test.ts +123 -0
- package/src/session/handoff/__tests__/single.test.ts +316 -0
- package/src/session/handoff/assignment.ts +62 -0
- package/src/session/handoff/broadcast.ts +381 -0
- package/src/session/handoff/capacity.ts +121 -0
- package/src/session/handoff/events.ts +72 -0
- package/src/session/handoff/index.ts +29 -0
- package/src/session/handoff/single.ts +288 -0
- package/src/session/handoff/version.ts +59 -0
- package/src/session/hierarchy/__tests__/session.test.ts +92 -0
- package/src/session/hierarchy/actor.ts +17 -0
- package/src/session/hierarchy/index.ts +17 -0
- package/src/session/hierarchy/lineage.ts +15 -0
- package/src/session/hierarchy/project.ts +41 -0
- package/src/session/hierarchy/session.ts +97 -0
- package/src/session/hierarchy/sub-session.ts +92 -0
- package/src/session/hierarchy/tenant.ts +13 -0
- package/src/session/index.ts +15 -0
- package/src/session/intervention/__tests__/prev-artifact.test.ts +234 -0
- package/src/session/intervention/index.ts +16 -0
- package/src/session/intervention/prev-artifact.ts +180 -0
- package/src/session/migration/__tests__/filesystem.test.ts +263 -0
- package/src/session/migration/__tests__/id-prefix.test.ts +101 -0
- package/src/session/migration/__tests__/marker.test.ts +84 -0
- package/src/session/migration/errors.ts +23 -0
- package/src/session/migration/filesystem.ts +401 -0
- package/src/session/migration/id-prefix.ts +146 -0
- package/src/session/migration/index.ts +38 -0
- package/src/session/migration/marker.ts +131 -0
- package/src/session/retention/__tests__/archive.test.ts +316 -0
- package/src/session/retention/__tests__/disk-backend.test.ts +180 -0
- package/src/session/retention/archive-backend-ref.ts +17 -0
- package/src/session/retention/archive.ts +281 -0
- package/src/session/retention/backend.ts +107 -0
- package/src/session/retention/disk-backend.ts +304 -0
- package/src/session/retention/index.ts +16 -0
- package/src/session/retention/policy.ts +53 -0
- package/src/session/summary/__tests__/materialize.test.ts +341 -0
- package/src/session/summary/deliverable.ts +84 -0
- package/src/session/summary/index.ts +31 -0
- package/src/session/summary/materialize.ts +169 -0
- package/src/session/summary/ref.ts +104 -0
- package/src/session/workspace/__tests__/git-worktree.test.ts +258 -0
- package/src/session/workspace/__tests__/path-builder.test.ts +51 -0
- package/src/session/workspace/driver.ts +60 -0
- package/src/session/workspace/git-worktree.ts +209 -0
- package/src/session/workspace/index.ts +25 -0
- package/src/session/workspace/path-builder.ts +71 -0
- package/src/session/workspace/ref.ts +50 -0
- package/src/session/workspace/registry.ts +42 -0
- package/src/store/conversation/memory.ts +23 -0
- package/src/store/session/__tests__/disk.test.ts +346 -0
- package/src/store/session/__tests__/memory.test.ts +327 -0
- package/src/store/session/disk.ts +920 -0
- package/src/store/session/index.ts +14 -0
- package/src/store/session/linkage.ts +80 -0
- package/src/store/session/memory.ts +400 -0
- package/src/store/session/messages.ts +21 -0
- package/src/types/agent/base.ts +31 -1
- package/src/types/agent/task.ts +58 -2
- package/src/types/conversation/index.ts +7 -0
- package/src/types/ids/index.ts +41 -3
- package/src/types/invocation/__tests__/state.test.ts +37 -29
- package/src/types/invocation/index.ts +26 -10
- package/src/types/run/config.ts +12 -1
- package/src/types/run/events.ts +36 -1
- package/src/types/run/index.ts +8 -0
- package/src/types/run/metadata.ts +24 -1
- package/src/types/run/status.ts +33 -0
- package/src/types/session/ids.ts +34 -0
- package/src/types/session/index.ts +28 -0
- package/src/types/session/store.ts +229 -0
- package/src/utils/id.ts +55 -4
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchivalManager — on-demand archival primitive for sub-sessions. Pattern
|
|
3
|
+
* doc §12.3 (Retention and Archival).
|
|
4
|
+
*
|
|
5
|
+
* Convention #9 (Registry + Manager + Store): Manager-shape with explicit
|
|
6
|
+
* deps for the {@link SessionStore}, {@link WorkspaceBackendRegistry}, and a
|
|
7
|
+
* pluggable {@link ArchiveBackend}. Convention #5 deny-by-default: absent
|
|
8
|
+
* backend → {@link ArchiveNotConfiguredError} on every `archive()` call.
|
|
9
|
+
*
|
|
10
|
+
* Atomic invariant (pattern doc §12.3):
|
|
11
|
+
*
|
|
12
|
+
* 1. Read sub-session + owning session + messages + optional summary
|
|
13
|
+
* 2. backend.store(bundle) — archive durability confirmed
|
|
14
|
+
* 3. Flip sub-session to `status: 'archived'` + attach archiveRef/archivedAt
|
|
15
|
+
* 4. Workspace driver dispose (idempotent)
|
|
16
|
+
*
|
|
17
|
+
* Step 2 is the durability boundary: if the process crashes between step 2
|
|
18
|
+
* and step 3, the archive exists but the live record is still non-archived
|
|
19
|
+
* — recovery is to re-invoke `archive()`, which sees a non-terminal status
|
|
20
|
+
* and replays. Step 3 is the single point of no return.
|
|
21
|
+
*
|
|
22
|
+
* A completed archive leaves the sub-session in-place with `status:
|
|
23
|
+
* 'archived'` + an attached `archiveRef`. Pattern doc §12.3 calls this the
|
|
24
|
+
* tombstone: `SessionStore.drill` still finds it through the normal linkage
|
|
25
|
+
* path, so the archive is navigable without a parallel index.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { SessionId, SubSessionId, TenantId } from '../../types/ids/index.js'
|
|
29
|
+
import type { WorkspaceId } from '../../types/session/ids.js'
|
|
30
|
+
import type { SessionStore } from '../../types/session/store.js'
|
|
31
|
+
import type { SubSession, SubSessionStatus } from '../hierarchy/sub-session.js'
|
|
32
|
+
import type { WorkspaceRef } from '../workspace/ref.js'
|
|
33
|
+
import type { WorkspaceBackendRegistry } from '../workspace/registry.js'
|
|
34
|
+
import type { ArchiveBackend, SubSessionTombstone } from './backend.js'
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Raised when {@link ArchivalManager.archive} or {@link ArchivalManager.restore}
|
|
38
|
+
* is invoked against a project whose retention policy does not supply an
|
|
39
|
+
* {@link ArchiveBackend}. Convention #5 — explicit error rather than silent
|
|
40
|
+
* no-op.
|
|
41
|
+
*/
|
|
42
|
+
export class ArchiveNotConfiguredError extends Error {
|
|
43
|
+
constructor() {
|
|
44
|
+
super('Retention archival not configured for this project (Convention #5: deny-by-default)')
|
|
45
|
+
this.name = 'ArchiveNotConfiguredError'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Raised when {@link ArchivalManager.archive} targets a sub-session that is
|
|
51
|
+
* not eligible for archival. The three reasons map to pattern doc §12.3
|
|
52
|
+
* (archival only applies to idle / merged / rejected / failed sub-sessions).
|
|
53
|
+
*/
|
|
54
|
+
export class SubSessionNotArchivableError extends Error {
|
|
55
|
+
readonly details: {
|
|
56
|
+
readonly subSessionId: SubSessionId
|
|
57
|
+
readonly reason: 'not_idle' | 'already_archived' | 'missing'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
constructor(details: {
|
|
61
|
+
subSessionId: SubSessionId
|
|
62
|
+
reason: 'not_idle' | 'already_archived' | 'missing'
|
|
63
|
+
}) {
|
|
64
|
+
super(`Sub-session ${details.subSessionId} not archivable: ${details.reason}`)
|
|
65
|
+
this.name = 'SubSessionNotArchivableError'
|
|
66
|
+
this.details = details
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Raised when {@link ArchivalManager.restore} is called against a
|
|
72
|
+
* sub-session that is not currently archived. Distinct from
|
|
73
|
+
* {@link SubSessionNotArchivableError} because the semantics are inverted:
|
|
74
|
+
* restore requires an archived record, archive rejects one.
|
|
75
|
+
*/
|
|
76
|
+
export class SubSessionNotArchivedError extends Error {
|
|
77
|
+
readonly details: {
|
|
78
|
+
readonly subSessionId: SubSessionId
|
|
79
|
+
readonly reason: 'not_archived' | 'missing' | 'missing_archive_ref'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
constructor(details: {
|
|
83
|
+
subSessionId: SubSessionId
|
|
84
|
+
reason: 'not_archived' | 'missing' | 'missing_archive_ref'
|
|
85
|
+
}) {
|
|
86
|
+
super(`Sub-session ${details.subSessionId} cannot be restored: ${details.reason}`)
|
|
87
|
+
this.name = 'SubSessionNotArchivedError'
|
|
88
|
+
this.details = details
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Lookup callback resolving a live {@link WorkspaceRef} from the id stored
|
|
94
|
+
* on a {@link SubSession}. Injected by the caller because Phase 8 does not
|
|
95
|
+
* ship a dedicated workspace store — the ref typically lives in a handoff
|
|
96
|
+
* assignment or is held by the agent lifecycle manager. Return `null` when
|
|
97
|
+
* the ref is unknown or already disposed; the manager archives without
|
|
98
|
+
* workspace data in that case (the disk backend allows it).
|
|
99
|
+
*/
|
|
100
|
+
export type WorkspaceResolver = (
|
|
101
|
+
workspaceId: WorkspaceId,
|
|
102
|
+
tenantId: TenantId,
|
|
103
|
+
) => Promise<WorkspaceRef | null>
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Sub-session statuses eligible for archival. Pattern doc §12.3 speaks of
|
|
107
|
+
* "idle" broadly; we honor the enumerated terminal-like states. Anything
|
|
108
|
+
* else (active / pending / in-flight merge) rejects with
|
|
109
|
+
* `'not_idle'`.
|
|
110
|
+
*/
|
|
111
|
+
const ARCHIVABLE_STATUSES: ReadonlySet<SubSessionStatus> = new Set([
|
|
112
|
+
'idle',
|
|
113
|
+
'merged',
|
|
114
|
+
'merge_rejected',
|
|
115
|
+
'failed',
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
export interface ArchivalManagerDeps {
|
|
119
|
+
readonly sessionStore: SessionStore
|
|
120
|
+
readonly workspaceRegistry: WorkspaceBackendRegistry
|
|
121
|
+
/**
|
|
122
|
+
* Archive backend. Absent = archival disabled for this manager
|
|
123
|
+
* (`archive()`/`restore()` throw {@link ArchiveNotConfiguredError}).
|
|
124
|
+
*/
|
|
125
|
+
readonly archiveBackend?: ArchiveBackend
|
|
126
|
+
/**
|
|
127
|
+
* Optional workspace resolver. When absent, `archive()` skips workspace
|
|
128
|
+
* snapshotting (only the ref would be captured) and workspace disposal
|
|
129
|
+
* (nothing to dispose). This is the conservative default and matches
|
|
130
|
+
* pattern doc §7.1 (lazy workspace provisioning).
|
|
131
|
+
*/
|
|
132
|
+
readonly workspaceResolver?: WorkspaceResolver
|
|
133
|
+
/**
|
|
134
|
+
* Optional logger hook for `sub_session.archived` emission. Pattern doc
|
|
135
|
+
* §12.3 requires the event; full event-bus wiring is a platform concern
|
|
136
|
+
* (a later phase of the roadmap). Phase 8 ships the log seam so tests
|
|
137
|
+
* can observe without the bus.
|
|
138
|
+
*/
|
|
139
|
+
readonly onArchived?: (tombstone: SubSessionTombstone) => void
|
|
140
|
+
readonly onRestored?: (subSessionId: SubSessionId, tenantId: TenantId) => void
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export class ArchivalManager {
|
|
144
|
+
private readonly deps: ArchivalManagerDeps
|
|
145
|
+
|
|
146
|
+
constructor(deps: ArchivalManagerDeps) {
|
|
147
|
+
this.deps = deps
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Archive an eligible sub-session. See module header for the atomic
|
|
152
|
+
* invariant and recovery semantics.
|
|
153
|
+
*
|
|
154
|
+
* Returns the {@link SubSessionTombstone} shape — the same identity +
|
|
155
|
+
* archive fields the store now carries on the live record.
|
|
156
|
+
*/
|
|
157
|
+
async archive(subSessionId: SubSessionId, tenantId: TenantId): Promise<SubSessionTombstone> {
|
|
158
|
+
const backend = this.requireBackend()
|
|
159
|
+
|
|
160
|
+
// 1. Resolve + validate sub-session.
|
|
161
|
+
const sub = await this.deps.sessionStore.getSubSession(subSessionId, tenantId)
|
|
162
|
+
if (!sub) {
|
|
163
|
+
throw new SubSessionNotArchivableError({ subSessionId, reason: 'missing' })
|
|
164
|
+
}
|
|
165
|
+
if (sub.status === 'archived') {
|
|
166
|
+
throw new SubSessionNotArchivableError({ subSessionId, reason: 'already_archived' })
|
|
167
|
+
}
|
|
168
|
+
if (!ARCHIVABLE_STATUSES.has(sub.status)) {
|
|
169
|
+
throw new SubSessionNotArchivableError({ subSessionId, reason: 'not_idle' })
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 2. Load owning child session bundle (messages + optional summary).
|
|
173
|
+
// Phase 9 Known Delta #7: uses `loadSessionMessages` for full-fidelity
|
|
174
|
+
// round-trip (original MessageId + timestamp preserved). Previously
|
|
175
|
+
// the Phase 8 archivalmanager synthesized `msg_restored_N` IDs from the
|
|
176
|
+
// payload-only `loadMessages` return — that lossy reshape is gone.
|
|
177
|
+
const childSessionId: SessionId = sub.childSessionId
|
|
178
|
+
const messages = await this.deps.sessionStore.loadSessionMessages(childSessionId, tenantId)
|
|
179
|
+
|
|
180
|
+
const summaryRefOrNull = await this.deps.sessionStore.getSummary(childSessionId, tenantId)
|
|
181
|
+
const summaryRef = summaryRefOrNull ?? undefined
|
|
182
|
+
|
|
183
|
+
// 3. Resolve optional live workspace ref.
|
|
184
|
+
let workspace: WorkspaceRef | undefined
|
|
185
|
+
if (sub.workspaceId && this.deps.workspaceResolver) {
|
|
186
|
+
const resolved = await this.deps.workspaceResolver(sub.workspaceId, tenantId)
|
|
187
|
+
if (resolved) workspace = resolved
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 4. Durability boundary: persist the archive bundle.
|
|
191
|
+
const bundleOut = await backend.store({
|
|
192
|
+
subSessionId: sub.id,
|
|
193
|
+
sessionId: childSessionId,
|
|
194
|
+
tenantId,
|
|
195
|
+
...(workspace !== undefined && { workspace }),
|
|
196
|
+
...(summaryRef !== undefined && { summaryRef }),
|
|
197
|
+
messages,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// 5. Point-of-no-return: flip sub-session to archived + attach ref.
|
|
201
|
+
const archived: SubSession = {
|
|
202
|
+
...sub,
|
|
203
|
+
status: 'archived',
|
|
204
|
+
archiveRef: bundleOut.archiveRef,
|
|
205
|
+
archivedAt: bundleOut.archivedAt,
|
|
206
|
+
}
|
|
207
|
+
await this.deps.sessionStore.updateSubSession(archived, tenantId)
|
|
208
|
+
|
|
209
|
+
// 6. Dispose the workspace (idempotent — driver contract tolerates
|
|
210
|
+
// already-disposed refs; a missing ref is a no-op).
|
|
211
|
+
if (workspace) {
|
|
212
|
+
try {
|
|
213
|
+
const driver = this.deps.workspaceRegistry.get(workspace.meta.backend)
|
|
214
|
+
await driver.dispose(workspace)
|
|
215
|
+
} catch {
|
|
216
|
+
// Idempotent — a secondary failure here must not unwind the
|
|
217
|
+
// already-committed archive. Pattern doc §12.3: workspace
|
|
218
|
+
// disposal is a cleanup operation, not part of the atomic
|
|
219
|
+
// archive envelope.
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const tombstone: SubSessionTombstone = {
|
|
224
|
+
subSessionId: sub.id,
|
|
225
|
+
sessionId: childSessionId,
|
|
226
|
+
tenantId,
|
|
227
|
+
...(sub.summaryRef !== undefined && { summaryRef: sub.summaryRef }),
|
|
228
|
+
archiveRef: bundleOut.archiveRef,
|
|
229
|
+
archivedAt: bundleOut.archivedAt,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.deps.onArchived?.(tombstone)
|
|
233
|
+
return tombstone
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Reverse of {@link archive}. Reads the tombstone, invokes
|
|
238
|
+
* `backend.restore`, then flips the sub-session back to `idle`. Does NOT
|
|
239
|
+
* re-materialize the workspace — the caller decides whether to
|
|
240
|
+
* re-provision via a {@link WorkspaceBackendDriver}.
|
|
241
|
+
*
|
|
242
|
+
* The restored `ArchiveInput` bundle is NOT returned here because the
|
|
243
|
+
* concrete pattern in Phase 8 is "flip status and make navigable again";
|
|
244
|
+
* consumers that need the bundle itself can call the backend directly.
|
|
245
|
+
*/
|
|
246
|
+
async restore(subSessionId: SubSessionId, tenantId: TenantId): Promise<void> {
|
|
247
|
+
const backend = this.requireBackend()
|
|
248
|
+
|
|
249
|
+
const sub = await this.deps.sessionStore.getSubSession(subSessionId, tenantId)
|
|
250
|
+
if (!sub) {
|
|
251
|
+
throw new SubSessionNotArchivedError({ subSessionId, reason: 'missing' })
|
|
252
|
+
}
|
|
253
|
+
if (sub.status !== 'archived') {
|
|
254
|
+
throw new SubSessionNotArchivedError({ subSessionId, reason: 'not_archived' })
|
|
255
|
+
}
|
|
256
|
+
if (!sub.archiveRef) {
|
|
257
|
+
throw new SubSessionNotArchivedError({ subSessionId, reason: 'missing_archive_ref' })
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Validate the archive ref by invoking the backend — this surfaces
|
|
261
|
+
// ArchiveNotFoundError up the stack if the bundle is missing or
|
|
262
|
+
// corrupt, rather than silently un-archiving an orphaned record.
|
|
263
|
+
await backend.restore(sub.archiveRef)
|
|
264
|
+
|
|
265
|
+
const restored: SubSession = {
|
|
266
|
+
...sub,
|
|
267
|
+
status: 'idle',
|
|
268
|
+
archiveRef: undefined,
|
|
269
|
+
archivedAt: undefined,
|
|
270
|
+
}
|
|
271
|
+
await this.deps.sessionStore.updateSubSession(restored, tenantId)
|
|
272
|
+
|
|
273
|
+
this.deps.onRestored?.(subSessionId, tenantId)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private requireBackend(): ArchiveBackend {
|
|
277
|
+
const backend = this.deps.archiveBackend
|
|
278
|
+
if (!backend) throw new ArchiveNotConfiguredError()
|
|
279
|
+
return backend
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchiveBackend — pluggable archival contract. See session-hierarchy.md
|
|
3
|
+
* §12.3 Retention and Archival.
|
|
4
|
+
*
|
|
5
|
+
* Convention #10 (Provider abstraction): interface lives here; concrete
|
|
6
|
+
* implementations (disk, s3, glacier, …) live in sibling files. Phase 8 ships
|
|
7
|
+
* the reference disk impl (`disk-backend.ts`); production deployments plug
|
|
8
|
+
* their own over the same surface.
|
|
9
|
+
*
|
|
10
|
+
* Convention #9 (Registry + Manager + Store): {@link ArchivalManager} is the
|
|
11
|
+
* Manager; this `ArchiveBackend` is the Provider slot it drives; the live
|
|
12
|
+
* `SessionStore` is the Store it mutates.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { SessionMessage } from '../../store/session/messages.js'
|
|
16
|
+
import type { SessionId, SubSessionId, TenantId } from '../../types/ids/index.js'
|
|
17
|
+
import type { SummaryId } from '../../types/session/ids.js'
|
|
18
|
+
import type { SessionSummaryRef } from '../summary/ref.js'
|
|
19
|
+
import type { WorkspaceRef } from '../workspace/ref.js'
|
|
20
|
+
import type { ArchiveBackendRef } from './archive-backend-ref.js'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Output of a successful {@link ArchiveBackend.store} call. `archiveRef` is
|
|
24
|
+
* the opaque lookup key used by {@link ArchiveBackend.restore} to rehydrate
|
|
25
|
+
* the bundle.
|
|
26
|
+
*/
|
|
27
|
+
export interface ArchiveOutput {
|
|
28
|
+
readonly archiveRef: ArchiveBackendRef
|
|
29
|
+
readonly archivedAt: Date
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Bundle handed to {@link ArchiveBackend.store} (and returned by
|
|
34
|
+
* {@link ArchiveBackend.restore}). Captures the minimum surface needed to
|
|
35
|
+
* re-hydrate a sub-session in the future — sub-session metadata plus the
|
|
36
|
+
* owning session's messages and optional summary.
|
|
37
|
+
*/
|
|
38
|
+
export interface ArchiveInput {
|
|
39
|
+
readonly subSessionId: SubSessionId
|
|
40
|
+
/**
|
|
41
|
+
* The session the sub-session owns. Each sub-session wraps exactly one
|
|
42
|
+
* child {@link Session} (pattern doc §4.4); the archived bundle captures
|
|
43
|
+
* that session's messages and summary.
|
|
44
|
+
*/
|
|
45
|
+
readonly sessionId: SessionId
|
|
46
|
+
readonly tenantId: TenantId
|
|
47
|
+
/**
|
|
48
|
+
* Present when the sub-session had a provisioned workspace at archive
|
|
49
|
+
* time. Absent for sub-sessions that never materialized one (pattern doc
|
|
50
|
+
* §7.1 allows lazy workspace provisioning).
|
|
51
|
+
*/
|
|
52
|
+
readonly workspace?: WorkspaceRef
|
|
53
|
+
readonly summaryRef?: SessionSummaryRef
|
|
54
|
+
/**
|
|
55
|
+
* Full message log for the owning session. Captured at archive time;
|
|
56
|
+
* append-only discipline (pattern doc §13.4) means this is a complete
|
|
57
|
+
* snapshot, not a partial view.
|
|
58
|
+
*/
|
|
59
|
+
readonly messages: readonly SessionMessage[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Pluggable archival backend. Implementations MUST write atomically
|
|
64
|
+
* (Convention #8 — write-tmp-rename per file) and MUST produce a unique
|
|
65
|
+
* {@link ArchiveBackendRef} per call. Concurrent `store` calls with
|
|
66
|
+
* overlapping input are a caller error — the Manager serializes per
|
|
67
|
+
* sub-session.
|
|
68
|
+
*/
|
|
69
|
+
export interface ArchiveBackend {
|
|
70
|
+
/** Discriminator. Free-form; reference impl uses `'disk'`. */
|
|
71
|
+
readonly kind: string
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Persist the bundle. Returns the lookup ref consumers embed in the
|
|
75
|
+
* sub-session tombstone (§12.3). Must be atomic at the bundle level —
|
|
76
|
+
* post-success, a restore sees the full bundle or throws.
|
|
77
|
+
*/
|
|
78
|
+
store(input: ArchiveInput): Promise<ArchiveOutput>
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Reverse of {@link store}. Throws when the ref does not resolve or when
|
|
82
|
+
* the bundle is corrupt. Does NOT re-materialize the workspace on disk —
|
|
83
|
+
* callers decide whether to re-provision via a
|
|
84
|
+
* {@link WorkspaceBackendDriver}.
|
|
85
|
+
*/
|
|
86
|
+
restore(archiveRef: ArchiveBackendRef): Promise<ArchiveInput>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The in-store marker a sub-session becomes after archival. Replaces the
|
|
91
|
+
* live record in-slot (see pattern doc §12.3): the sub-session's status
|
|
92
|
+
* flips to `'archived'` and `archiveRef` + `archivedAt` are attached. This
|
|
93
|
+
* way `SessionStore.drill` still finds it via the normal linkage path — the
|
|
94
|
+
* tombstone is navigable without a parallel index.
|
|
95
|
+
*
|
|
96
|
+
* Fields are a subset of {@link SubSession} — the identity columns plus the
|
|
97
|
+
* archive pointer — extracted here so consumers can destructure a tombstone
|
|
98
|
+
* view without carrying the full live-record shape.
|
|
99
|
+
*/
|
|
100
|
+
export interface SubSessionTombstone {
|
|
101
|
+
readonly subSessionId: SubSessionId
|
|
102
|
+
readonly sessionId: SessionId
|
|
103
|
+
readonly tenantId: TenantId
|
|
104
|
+
readonly summaryRef?: SummaryId
|
|
105
|
+
readonly archiveRef: ArchiveBackendRef
|
|
106
|
+
readonly archivedAt: Date
|
|
107
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DiskArchiveBackend — reference filesystem-backed {@link ArchiveBackend}.
|
|
3
|
+
*
|
|
4
|
+
* Convention #8 (atomic writes): every file lands via write-tmp-rename; the
|
|
5
|
+
* final `archive.json` marker is written LAST so a crash mid-bundle leaves
|
|
6
|
+
* the directory visible but UN-marked, and `restore` treats such a bundle
|
|
7
|
+
* as missing. This mirrors the Phase 5 materializer's "marker-last"
|
|
8
|
+
* invariant.
|
|
9
|
+
*
|
|
10
|
+
* Layout:
|
|
11
|
+
*
|
|
12
|
+
* {rootDir}/archive/{arc_<opaque>}/
|
|
13
|
+
* subsession.json # Identity + metadata
|
|
14
|
+
* summary.json # SessionSummaryRef (optional — present iff input.summaryRef)
|
|
15
|
+
* messages.jsonl # One SessionMessage per line (append-style, serialized once)
|
|
16
|
+
* workspace.json # WorkspaceRef (optional — present iff input.workspace)
|
|
17
|
+
* archive.json # Marker — final write; presence = bundle committed
|
|
18
|
+
*
|
|
19
|
+
* Workspace directory contents are NOT archived — we persist the
|
|
20
|
+
* {@link WorkspaceRef} only. The pattern doc §12.3 explicitly allows this:
|
|
21
|
+
* the ref is the re-hydration handle; the worktree itself is disposed at
|
|
22
|
+
* archive time via the caller's {@link WorkspaceBackendDriver}, not copied
|
|
23
|
+
* into the archive bundle. This is a Phase 8 interpretation — see the
|
|
24
|
+
* roadmap deliverable report for rationale (tar bundling deferred).
|
|
25
|
+
*
|
|
26
|
+
* See session-hierarchy.md §12.3.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { randomBytes } from 'node:crypto'
|
|
30
|
+
import { mkdir, readFile, readdir, rename, unlink, writeFile } from 'node:fs/promises'
|
|
31
|
+
import { join } from 'node:path'
|
|
32
|
+
import type { SessionMessage } from '../../store/session/messages.js'
|
|
33
|
+
import type { SessionId, SubSessionId, TenantId } from '../../types/ids/index.js'
|
|
34
|
+
import type { SessionSummaryRef } from '../summary/ref.js'
|
|
35
|
+
import type { WorkspaceRef } from '../workspace/ref.js'
|
|
36
|
+
import type { ArchiveBackendRef } from './archive-backend-ref.js'
|
|
37
|
+
import type { ArchiveBackend, ArchiveInput, ArchiveOutput } from './backend.js'
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Raised when {@link DiskArchiveBackend.restore} cannot resolve the supplied
|
|
41
|
+
* ref — either the directory is missing, or the `archive.json` marker is
|
|
42
|
+
* absent (crash-mid-write signal).
|
|
43
|
+
*/
|
|
44
|
+
export class ArchiveNotFoundError extends Error {
|
|
45
|
+
readonly details: { archiveRef: ArchiveBackendRef; reason: 'missing' | 'incomplete' }
|
|
46
|
+
|
|
47
|
+
constructor(details: { archiveRef: ArchiveBackendRef; reason: 'missing' | 'incomplete' }) {
|
|
48
|
+
super(`Archive ${details.archiveRef} could not be resolved: ${details.reason}`)
|
|
49
|
+
this.name = 'ArchiveNotFoundError'
|
|
50
|
+
this.details = details
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Config for {@link DiskArchiveBackend}. `rootDir` is absolute — bundles land
|
|
56
|
+
* under `{rootDir}/archive/`.
|
|
57
|
+
*/
|
|
58
|
+
export interface DiskArchiveBackendConfig {
|
|
59
|
+
readonly rootDir: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface PersistedSubSessionEntry {
|
|
63
|
+
readonly subSessionId: SubSessionId
|
|
64
|
+
readonly sessionId: SessionId
|
|
65
|
+
readonly tenantId: TenantId
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface PersistedWorkspaceEntry {
|
|
69
|
+
readonly workspace: WorkspaceRef
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface PersistedSummaryEntry {
|
|
73
|
+
readonly summary: SessionSummaryRef
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface PersistedMessageLine {
|
|
77
|
+
readonly id: SessionMessage['id']
|
|
78
|
+
readonly sessionId: SessionId
|
|
79
|
+
readonly tenantId: TenantId
|
|
80
|
+
readonly message: SessionMessage['message']
|
|
81
|
+
readonly at: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface PersistedArchiveMarker {
|
|
85
|
+
readonly archiveRef: ArchiveBackendRef
|
|
86
|
+
readonly archivedAt: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class DiskArchiveBackend implements ArchiveBackend {
|
|
90
|
+
readonly kind = 'disk'
|
|
91
|
+
private readonly rootDir: string
|
|
92
|
+
|
|
93
|
+
constructor(config: DiskArchiveBackendConfig) {
|
|
94
|
+
this.rootDir = config.rootDir
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async store(input: ArchiveInput): Promise<ArchiveOutput> {
|
|
98
|
+
const archiveRef = generateArchiveBackendRef()
|
|
99
|
+
const archivedAt = new Date()
|
|
100
|
+
const dir = join(this.rootDir, 'archive', archiveRef)
|
|
101
|
+
await mkdir(dir, { recursive: true })
|
|
102
|
+
|
|
103
|
+
// 1. Sub-session identity.
|
|
104
|
+
const subEntry: PersistedSubSessionEntry = {
|
|
105
|
+
subSessionId: input.subSessionId,
|
|
106
|
+
sessionId: input.sessionId,
|
|
107
|
+
tenantId: input.tenantId,
|
|
108
|
+
}
|
|
109
|
+
await atomicWriteJson(join(dir, 'subsession.json'), subEntry)
|
|
110
|
+
|
|
111
|
+
// 2. Summary (optional).
|
|
112
|
+
if (input.summaryRef) {
|
|
113
|
+
const entry: PersistedSummaryEntry = { summary: serializeSummaryFields(input.summaryRef) }
|
|
114
|
+
await atomicWriteJson(join(dir, 'summary.json'), entry)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 3. Workspace ref (optional).
|
|
118
|
+
if (input.workspace) {
|
|
119
|
+
const entry: PersistedWorkspaceEntry = {
|
|
120
|
+
workspace: serializeWorkspaceFields(input.workspace),
|
|
121
|
+
}
|
|
122
|
+
await atomicWriteJson(join(dir, 'workspace.json'), entry)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 4. Messages — one JSON per line, serialized once with an atomic
|
|
126
|
+
// tmp-rename. No append I/O: we have the full bundle upfront, and
|
|
127
|
+
// single-write is atomic at the file level.
|
|
128
|
+
const lines = input.messages.map((m) => {
|
|
129
|
+
const line: PersistedMessageLine = {
|
|
130
|
+
id: m.id,
|
|
131
|
+
sessionId: m.sessionId,
|
|
132
|
+
tenantId: m.tenantId,
|
|
133
|
+
message: m.message,
|
|
134
|
+
at: m.at.toISOString(),
|
|
135
|
+
}
|
|
136
|
+
return JSON.stringify(line)
|
|
137
|
+
})
|
|
138
|
+
const body = lines.length > 0 ? `${lines.join('\n')}\n` : ''
|
|
139
|
+
await atomicWriteText(join(dir, 'messages.jsonl'), body)
|
|
140
|
+
|
|
141
|
+
// 5. Marker — ALWAYS last. Presence = bundle committed.
|
|
142
|
+
const marker: PersistedArchiveMarker = {
|
|
143
|
+
archiveRef,
|
|
144
|
+
archivedAt: archivedAt.toISOString(),
|
|
145
|
+
}
|
|
146
|
+
await atomicWriteJson(join(dir, 'archive.json'), marker)
|
|
147
|
+
|
|
148
|
+
return { archiveRef, archivedAt }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async restore(archiveRef: ArchiveBackendRef): Promise<ArchiveInput> {
|
|
152
|
+
const dir = join(this.rootDir, 'archive', archiveRef)
|
|
153
|
+
|
|
154
|
+
// Directory must exist.
|
|
155
|
+
try {
|
|
156
|
+
await readdir(dir)
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const code = (err as NodeJS.ErrnoException).code
|
|
159
|
+
if (code === 'ENOENT') {
|
|
160
|
+
throw new ArchiveNotFoundError({ archiveRef, reason: 'missing' })
|
|
161
|
+
}
|
|
162
|
+
throw err
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Marker must exist — absence = crash-mid-write.
|
|
166
|
+
const marker = await readJson<PersistedArchiveMarker>(join(dir, 'archive.json'))
|
|
167
|
+
if (!marker) {
|
|
168
|
+
throw new ArchiveNotFoundError({ archiveRef, reason: 'incomplete' })
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const subEntry = await readJson<PersistedSubSessionEntry>(join(dir, 'subsession.json'))
|
|
172
|
+
if (!subEntry) {
|
|
173
|
+
throw new ArchiveNotFoundError({ archiveRef, reason: 'incomplete' })
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const summaryEntry = await readJson<PersistedSummaryEntry>(join(dir, 'summary.json'))
|
|
177
|
+
const summaryRef = summaryEntry ? deserializeSummaryFields(summaryEntry.summary) : undefined
|
|
178
|
+
|
|
179
|
+
const workspaceEntry = await readJson<PersistedWorkspaceEntry>(join(dir, 'workspace.json'))
|
|
180
|
+
const workspace = workspaceEntry
|
|
181
|
+
? deserializeWorkspaceFields(workspaceEntry.workspace)
|
|
182
|
+
: undefined
|
|
183
|
+
|
|
184
|
+
const messagesRaw = await readText(join(dir, 'messages.jsonl'))
|
|
185
|
+
const messages: SessionMessage[] =
|
|
186
|
+
messagesRaw === null
|
|
187
|
+
? []
|
|
188
|
+
: messagesRaw
|
|
189
|
+
.split('\n')
|
|
190
|
+
.filter((l) => l.length > 0)
|
|
191
|
+
.map((l) => {
|
|
192
|
+
const raw = JSON.parse(l) as PersistedMessageLine
|
|
193
|
+
return {
|
|
194
|
+
id: raw.id,
|
|
195
|
+
sessionId: raw.sessionId,
|
|
196
|
+
tenantId: raw.tenantId,
|
|
197
|
+
message: raw.message,
|
|
198
|
+
at: new Date(raw.at),
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
subSessionId: subEntry.subSessionId,
|
|
204
|
+
sessionId: subEntry.sessionId,
|
|
205
|
+
tenantId: subEntry.tenantId,
|
|
206
|
+
...(workspace !== undefined && { workspace }),
|
|
207
|
+
...(summaryRef !== undefined && { summaryRef }),
|
|
208
|
+
messages,
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Serialization helpers ------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
function serializeSummaryFields(s: SessionSummaryRef): SessionSummaryRef {
|
|
216
|
+
// Dates survive JSON round-trip as strings; we let JSON.stringify handle
|
|
217
|
+
// the normal `toJSON` path but store them as ISO strings on the wire.
|
|
218
|
+
// On deserialize we coerce back. The in-memory record stays immutable.
|
|
219
|
+
return s
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function deserializeSummaryFields(s: SessionSummaryRef): SessionSummaryRef {
|
|
223
|
+
// JSON revives Dates as strings — resurrect them.
|
|
224
|
+
const at = s.at instanceof Date ? s.at : new Date(s.at as unknown as string)
|
|
225
|
+
const keyDecisions = s.keyDecisions.map((k) => ({
|
|
226
|
+
at: k.at instanceof Date ? k.at : new Date(k.at as unknown as string),
|
|
227
|
+
summary: k.summary,
|
|
228
|
+
}))
|
|
229
|
+
return { ...s, at, keyDecisions }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function serializeWorkspaceFields(w: WorkspaceRef): WorkspaceRef {
|
|
233
|
+
return w
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function deserializeWorkspaceFields(w: WorkspaceRef): WorkspaceRef {
|
|
237
|
+
const createdAt =
|
|
238
|
+
w.createdAt instanceof Date ? w.createdAt : new Date(w.createdAt as unknown as string)
|
|
239
|
+
return { ...w, createdAt }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ID generation --------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
|
|
245
|
+
const MAX_UNIFORM_BYTE = Math.floor(256 / ALPHABET.length) * ALPHABET.length
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Mints an {@link ArchiveBackendRef}. Local helper rather than a `utils/id`
|
|
249
|
+
* export because the ref's uniqueness + prefix is an archive-backend concern
|
|
250
|
+
* — other backends may mint refs very differently (e.g. S3 keys).
|
|
251
|
+
*/
|
|
252
|
+
function generateArchiveBackendRef(length = 12): ArchiveBackendRef {
|
|
253
|
+
let suffix = ''
|
|
254
|
+
let remaining = length
|
|
255
|
+
while (remaining > 0) {
|
|
256
|
+
const bytes = randomBytes(remaining + 8)
|
|
257
|
+
for (const byte of bytes) {
|
|
258
|
+
if (remaining <= 0) break
|
|
259
|
+
if (byte < MAX_UNIFORM_BYTE) {
|
|
260
|
+
suffix += ALPHABET[byte % ALPHABET.length]
|
|
261
|
+
remaining--
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return `arc_${suffix}` as ArchiveBackendRef
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// FS helpers -----------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
async function atomicWriteJson(filePath: string, value: unknown): Promise<void> {
|
|
271
|
+
await atomicWriteText(filePath, JSON.stringify(value, null, 2))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function atomicWriteText(filePath: string, body: string): Promise<void> {
|
|
275
|
+
const tempPath = `${filePath}.tmp`
|
|
276
|
+
try {
|
|
277
|
+
await writeFile(tempPath, body, 'utf-8')
|
|
278
|
+
await rename(tempPath, filePath)
|
|
279
|
+
} catch (err) {
|
|
280
|
+
await unlink(tempPath).catch(() => undefined)
|
|
281
|
+
throw err
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function readJson<T>(path: string): Promise<T | null> {
|
|
286
|
+
try {
|
|
287
|
+
const raw = await readFile(path, 'utf-8')
|
|
288
|
+
return JSON.parse(raw) as T
|
|
289
|
+
} catch (err) {
|
|
290
|
+
const code = (err as NodeJS.ErrnoException).code
|
|
291
|
+
if (code === 'ENOENT') return null
|
|
292
|
+
throw err
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function readText(path: string): Promise<string | null> {
|
|
297
|
+
try {
|
|
298
|
+
return await readFile(path, 'utf-8')
|
|
299
|
+
} catch (err) {
|
|
300
|
+
const code = (err as NodeJS.ErrnoException).code
|
|
301
|
+
if (code === 'ENOENT') return null
|
|
302
|
+
throw err
|
|
303
|
+
}
|
|
304
|
+
}
|