@astrale-os/cli 1.0.0-beta.32 → 1.0.0-beta.43
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 +928 -0
- package/README.md +53 -43
- package/THIRD-PARTY-NOTICES.md +37 -1
- package/dist/astrale.js +349631 -83669
- package/dist/public/connect-core.js +16 -47
- package/dist/public/keys/index.js +1 -1
- package/dist/types/admin/contract.d.ts +11 -0
- package/dist/types/admin/graph/index.d.ts +1 -0
- package/dist/types/admin/graph/nodes.d.ts +12 -0
- package/dist/types/admin/instance/client.d.ts +2 -0
- package/dist/types/connection/lifetime.d.ts +1 -1
- package/dist/types/errors.d.ts +3 -0
- package/dist/types/lib/proc.d.ts +1 -0
- package/dist/types/lib/update.d.ts +15 -7
- package/package.json +8 -6
- package/studio/client/dist/assets/index-5C1uT6ML.css +2 -0
- package/studio/client/dist/assets/index-C4kNlvwF.js +80 -0
- package/studio/client/dist/assets/schema-studio-BR9d0faB.js +8 -0
- package/studio/client/dist/index.html +13 -2
- package/studio/package.json +2 -1
- package/studio/server/agent/ask.test.ts +7 -1
- package/studio/server/agent/bridge/grant.ts +15 -3
- package/studio/server/agent/prompts/system.test.ts +5 -0
- package/studio/server/agent/prompts/system.ts +19 -4
- package/studio/server/agent/routes.test.ts +39 -1
- package/studio/server/agent/routes.ts +3 -0
- package/studio/server/agent/run/coordinator.test.ts +7 -1
- package/studio/server/agent/run/preparation.ts +1 -0
- package/studio/server/agent/run/transcript.test.ts +47 -2
- package/studio/server/agent/run/transcript.ts +32 -3
- package/studio/server/api/documents.test.ts +31 -0
- package/studio/server/api/documents.ts +35 -1
- package/studio/server/api-agent-loadout.test.ts +7 -1
- package/studio/server/api.test.ts +7 -1
- package/studio/server/cache.ts +4 -4
- package/studio/server/detect.ts +9 -12
- package/studio/server/domain.test.ts +83 -3
- package/studio/server/domain.ts +205 -27
- package/studio/server/handoff/service.ts +1 -1
- package/studio/server/index.ts +5 -8
- package/studio/server/introspect/extractor.ts +111 -17
- package/studio/server/introspect/overlay-tsmorph.test.ts +9 -9
- package/studio/server/introspect/overlay.ts +1 -1
- package/studio/server/introspect/runtime.ts +9 -1
- package/studio/server/introspect/source-overlay/spans.ts +2 -5
- package/studio/server/lifecycle.ts +1 -1
- package/studio/server/state/baseline.test.ts +14 -0
- package/studio/server/state/baseline.ts +16 -2
- package/studio/server/watch.test.ts +2 -1
- package/studio/server/watch.ts +5 -2
- package/studio/server/workspace/create.ts +9 -8
- package/studio/server/workspace-state.ts +4 -6
- package/studio/server/workspace-watch.ts +33 -22
- package/studio/shared/contracts/agent.ts +3 -0
- package/viewer/dist/main.js +12 -12
- package/src/__tests__/fixtures/publication.ts +0 -20
- package/src/admin/.spec/api.d.ts +0 -3
- package/src/admin/.spec/architecture.md +0 -22
- package/src/admin/.spec/icon.svg +0 -3
- package/src/admin/.spec/layout.ts +0 -6
- package/src/admin/__tests__/fixture.ts +0 -25
- package/src/admin/catalog/.spec/api.d.ts +0 -63
- package/src/admin/catalog/.spec/architecture.md +0 -7
- package/src/admin/catalog/.spec/icon.svg +0 -3
- package/src/admin/catalog/.spec/layout.ts +0 -6
- package/src/admin/catalog/__tests__/client.test.ts +0 -245
- package/src/admin/catalog/client.ts +0 -195
- package/src/admin/catalog/index.ts +0 -13
- package/src/admin/catalog/model.ts +0 -41
- package/src/admin/catalog/tsconfig.json +0 -8
- package/src/admin/contract.ts +0 -47
- package/src/admin/graph/.spec/api.d.ts +0 -22
- package/src/admin/graph/.spec/architecture.md +0 -5
- package/src/admin/graph/.spec/icon.svg +0 -3
- package/src/admin/graph/.spec/layout.ts +0 -3
- package/src/admin/graph/index.ts +0 -6
- package/src/admin/graph/nodes.ts +0 -56
- package/src/admin/index.ts +0 -2
- package/src/admin/instance/.spec/api.d.ts +0 -62
- package/src/admin/instance/.spec/architecture.md +0 -10
- package/src/admin/instance/.spec/icon.svg +0 -3
- package/src/admin/instance/.spec/layout.ts +0 -6
- package/src/admin/instance/__tests__/client.test.ts +0 -273
- package/src/admin/instance/client.ts +0 -191
- package/src/admin/instance/index.ts +0 -15
- package/src/admin/instance/model.ts +0 -59
- package/src/admin/instance/tsconfig.json +0 -8
- package/src/commands/__tests__/admin-instance.test.ts +0 -91
- package/src/commands/__tests__/auth-login.test.ts +0 -231
- package/src/commands/__tests__/auth-token.test.ts +0 -223
- package/src/commands/__tests__/call-describe.test.ts +0 -80
- package/src/commands/__tests__/call.test.ts +0 -63
- package/src/commands/__tests__/domain-install-operation.test.ts +0 -145
- package/src/commands/__tests__/domain-install-owned.test.ts +0 -191
- package/src/commands/__tests__/domain-list.test.ts +0 -133
- package/src/commands/__tests__/domain-uninstall.test.ts +0 -53
- package/src/commands/__tests__/install-direct.test.ts +0 -128
- package/src/commands/__tests__/install-identity-override.test.ts +0 -76
- package/src/commands/__tests__/instance-bookmark.test.ts +0 -166
- package/src/commands/__tests__/instance-list-admin.test.ts +0 -26
- package/src/commands/__tests__/instance-list-rows.test.ts +0 -64
- package/src/commands/__tests__/instance-status.test.ts +0 -139
- package/src/commands/__tests__/instance-use.test.ts +0 -67
- package/src/commands/__tests__/introspect-parse.test.ts +0 -100
- package/src/commands/__tests__/logs.test.ts +0 -345
- package/src/commands/__tests__/read-commands.test.ts +0 -377
- package/src/commands/__tests__/setup-plan.test.ts +0 -61
- package/src/commands/__tests__/studio-descriptor.test.ts +0 -22
- package/src/commands/__tests__/token-ttl.test.ts +0 -66
- package/src/commands/__tests__/update.test.ts +0 -95
- package/src/commands/__tests__/view-build.test.ts +0 -58
- package/src/commands/__tests__/view.test.ts +0 -197
- package/src/commands/admin/status.ts +0 -61
- package/src/commands/admin/use.ts +0 -100
- package/src/commands/auth/login.ts +0 -82
- package/src/commands/auth/logout.ts +0 -53
- package/src/commands/auth/status.ts +0 -86
- package/src/commands/auth/token.ts +0 -174
- package/src/commands/browser.ts +0 -236
- package/src/commands/call-describe.ts +0 -123
- package/src/commands/call.ts +0 -230
- package/src/commands/domain/install.ts +0 -575
- package/src/commands/domain/list.ts +0 -155
- package/src/commands/domain/publish.ts +0 -136
- package/src/commands/domain/uninstall.ts +0 -128
- package/src/commands/get.ts +0 -69
- package/src/commands/identity/__tests__/register.test.ts +0 -93
- package/src/commands/identity/create.ts +0 -35
- package/src/commands/identity/delete.ts +0 -24
- package/src/commands/identity/export.ts +0 -46
- package/src/commands/identity/import.ts +0 -61
- package/src/commands/identity/list.ts +0 -56
- package/src/commands/identity/register.ts +0 -195
- package/src/commands/identity/sync.ts +0 -38
- package/src/commands/identity/unsync.ts +0 -34
- package/src/commands/identity/use.ts +0 -24
- package/src/commands/identity/whoami.ts +0 -34
- package/src/commands/idp/add.ts +0 -150
- package/src/commands/idp/list.ts +0 -57
- package/src/commands/idp/refresh.ts +0 -38
- package/src/commands/idp/remove.ts +0 -36
- package/src/commands/idp/show.ts +0 -29
- package/src/commands/instance/active.ts +0 -73
- package/src/commands/instance/bookmark.ts +0 -101
- package/src/commands/instance/create.ts +0 -61
- package/src/commands/instance/delete.ts +0 -69
- package/src/commands/instance/forget.ts +0 -26
- package/src/commands/instance/list.ts +0 -197
- package/src/commands/instance/status.ts +0 -92
- package/src/commands/instance/use.ts +0 -263
- package/src/commands/introspect.ts +0 -129
- package/src/commands/logs.ts +0 -371
- package/src/commands/mutate.ts +0 -122
- package/src/commands/query.ts +0 -142
- package/src/commands/session/analyze.ts +0 -72
- package/src/commands/session/list.ts +0 -36
- package/src/commands/setup.ts +0 -54
- package/src/commands/status.ts +0 -60
- package/src/commands/studio.ts +0 -434
- package/src/commands/token.ts +0 -121
- package/src/commands/ui/__tests__/commands.test.ts +0 -152
- package/src/commands/ui/add.ts +0 -51
- package/src/commands/ui/doctor.ts +0 -21
- package/src/commands/ui/init.ts +0 -38
- package/src/commands/ui/list.ts +0 -26
- package/src/commands/ui/preset-apply.ts +0 -19
- package/src/commands/ui/preset-list.ts +0 -12
- package/src/commands/ui/shared.ts +0 -25
- package/src/commands/update.ts +0 -356
- package/src/commands/use.ts +0 -86
- package/src/commands/view-serve.ts +0 -26
- package/src/commands/view.ts +0 -658
- package/src/connect-core.test.ts +0 -38
- package/src/connect-core.ts +0 -42
- package/src/connection/.spec/api.d.ts +0 -110
- package/src/connection/.spec/architecture.md +0 -58
- package/src/connection/.spec/capabilities/connection.ts +0 -13
- package/src/connection/.spec/examples/call.ts +0 -7
- package/src/connection/.spec/flows/command.ts +0 -20
- package/src/connection/.spec/flows/session.ts +0 -62
- package/src/connection/.spec/laws/connection.ts +0 -213
- package/src/connection/.spec/layout.ts +0 -22
- package/src/connection/__tests__/auth.test.ts +0 -129
- package/src/connection/__tests__/ca-fetch.test.ts +0 -60
- package/src/connection/__tests__/call.test.ts +0 -17
- package/src/connection/__tests__/credential.test.ts +0 -254
- package/src/connection/__tests__/errors.test.ts +0 -577
- package/src/connection/__tests__/exchange.test.ts +0 -404
- package/src/connection/__tests__/failure-fixtures.ts +0 -39
- package/src/connection/__tests__/failure-safety.test.ts +0 -75
- package/src/connection/__tests__/fixtures/localhost-cert.pem +0 -19
- package/src/connection/__tests__/fixtures/localhost-key.base64 +0 -1
- package/src/connection/__tests__/managed.test.ts +0 -39
- package/src/connection/__tests__/reasons.test.ts +0 -78
- package/src/connection/__tests__/self.test.ts +0 -71
- package/src/connection/__tests__/session.test.ts +0 -144
- package/src/connection/__tests__/target.test.ts +0 -95
- package/src/connection/auth.ts +0 -265
- package/src/connection/call.ts +0 -7
- package/src/connection/command.ts +0 -67
- package/src/connection/credential.ts +0 -138
- package/src/connection/errors.ts +0 -1
- package/src/connection/exchange.ts +0 -394
- package/src/connection/failure/classify.ts +0 -110
- package/src/connection/failure/index.ts +0 -18
- package/src/connection/failure/model.ts +0 -28
- package/src/connection/failure/render.ts +0 -116
- package/src/connection/index.ts +0 -8
- package/src/connection/lifetime.ts +0 -25
- package/src/connection/reasons.ts +0 -209
- package/src/connection/self.ts +0 -138
- package/src/connection/session.ts +0 -203
- package/src/connection/target.ts +0 -112
- package/src/connection/tsconfig.json +0 -6
- package/src/errors.ts +0 -66
- package/src/graph/.spec/api.d.ts +0 -41
- package/src/graph/.spec/laws/documents.ts +0 -24
- package/src/graph/.spec/laws/projection.ts +0 -13
- package/src/graph/.spec/layout.ts +0 -14
- package/src/graph/__tests__/mutation.test.ts +0 -51
- package/src/graph/__tests__/projection.test.ts +0 -22
- package/src/graph/__tests__/query.test.ts +0 -180
- package/src/graph/class.ts +0 -30
- package/src/graph/index.ts +0 -4
- package/src/graph/mutation.ts +0 -46
- package/src/graph/projection.ts +0 -21
- package/src/graph/query.ts +0 -95
- package/src/graph/tsconfig.json +0 -6
- package/src/identity/.history/ard/bootstrap-root-transfer.md +0 -57
- package/src/identity/.history/ard/domain-mediated-registration.md +0 -36
- package/src/identity/.spec/api.d.ts +0 -124
- package/src/identity/.spec/architecture.md +0 -33
- package/src/identity/.spec/capabilities/identity.ts +0 -7
- package/src/identity/.spec/flows/bootstrap-root.ts +0 -79
- package/src/identity/.spec/flows/domain-mediated-registration.ts +0 -51
- package/src/identity/.spec/flows/import.ts +0 -29
- package/src/identity/.spec/laws/identity.ts +0 -56
- package/src/identity/.spec/layout.ts +0 -13
- package/src/identity/__tests__/fixtures/registry-journey.ts +0 -65
- package/src/identity/__tests__/registration.test.ts +0 -64
- package/src/identity/__tests__/registry.test.ts +0 -74
- package/src/identity/__tests__/transfer.test.ts +0 -198
- package/src/identity/index.ts +0 -23
- package/src/identity/registration.ts +0 -72
- package/src/identity/registry.ts +0 -181
- package/src/identity/transfer.ts +0 -187
- package/src/identity/tsconfig.json +0 -6
- package/src/index.ts +0 -1
- package/src/keys/.spec/api.d.ts +0 -74
- package/src/keys/.spec/architecture.md +0 -13
- package/src/keys/.spec/laws/keys.ts +0 -43
- package/src/keys/.spec/layout.ts +0 -6
- package/src/keys/__tests__/algorithm.test.ts +0 -39
- package/src/keys/__tests__/keys.test.ts +0 -172
- package/src/keys/algorithm.ts +0 -33
- package/src/keys/credential.ts +0 -113
- package/src/keys/index.ts +0 -14
- package/src/keys/pair.ts +0 -219
- package/src/keys/tsconfig.json +0 -6
- package/src/lib/__tests__/admin-target.test.ts +0 -184
- package/src/lib/__tests__/binary.test.ts +0 -70
- package/src/lib/__tests__/browser-retention.test.ts +0 -212
- package/src/lib/__tests__/command-dx.test.ts +0 -102
- package/src/lib/__tests__/concurrency.test.ts +0 -62
- package/src/lib/__tests__/config.test.ts +0 -81
- package/src/lib/__tests__/design.test.ts +0 -99
- package/src/lib/__tests__/domain-publication.test.ts +0 -138
- package/src/lib/__tests__/format.test.ts +0 -22
- package/src/lib/__tests__/idp-session.driver.ts +0 -60
- package/src/lib/__tests__/idp-session.test.ts +0 -404
- package/src/lib/__tests__/idp.test.ts +0 -415
- package/src/lib/__tests__/instance-candidates.test.ts +0 -146
- package/src/lib/__tests__/instance-target.test.ts +0 -213
- package/src/lib/__tests__/instance.test.ts +0 -192
- package/src/lib/__tests__/local-status.test.ts +0 -202
- package/src/lib/__tests__/log-errors.test.ts +0 -64
- package/src/lib/__tests__/log.test.ts +0 -31
- package/src/lib/__tests__/meta.test.ts +0 -55
- package/src/lib/__tests__/output.test.ts +0 -162
- package/src/lib/__tests__/panel.test.ts +0 -40
- package/src/lib/__tests__/port.test.ts +0 -44
- package/src/lib/__tests__/prompt.test.ts +0 -25
- package/src/lib/__tests__/sdk-deps.test.ts +0 -68
- package/src/lib/__tests__/self.test.ts +0 -50
- package/src/lib/__tests__/skills.test.ts +0 -834
- package/src/lib/__tests__/studio-server-deps.test.ts +0 -111
- package/src/lib/__tests__/table.test.ts +0 -53
- package/src/lib/__tests__/update.test.ts +0 -511
- package/src/lib/__tests__/use-target.test.ts +0 -56
- package/src/lib/__tests__/validation.test.ts +0 -34
- package/src/lib/__tests__/view-assets.test.ts +0 -171
- package/src/lib/__tests__/view-external-open-intent.test.ts +0 -150
- package/src/lib/__tests__/view-external-open-origins.test.ts +0 -27
- package/src/lib/__tests__/view-open-intent.test.ts +0 -280
- package/src/lib/__tests__/view-port-allocation.test.ts +0 -82
- package/src/lib/__tests__/view-server.test.ts +0 -91
- package/src/lib/__tests__/view-session.test.ts +0 -100
- package/src/lib/__tests__/view-snapshot.test.ts +0 -77
- package/src/lib/admin-domain.ts +0 -43
- package/src/lib/admin-instance.ts +0 -56
- package/src/lib/admin-target.ts +0 -290
- package/src/lib/binary.ts +0 -127
- package/src/lib/browser-retention.ts +0 -210
- package/src/lib/browser.ts +0 -150
- package/src/lib/ca-fetch.ts +0 -133
- package/src/lib/command-dx.ts +0 -191
- package/src/lib/concurrency.ts +0 -31
- package/src/lib/config.ts +0 -57
- package/src/lib/credential-lifetime.ts +0 -24
- package/src/lib/domain-publication.ts +0 -104
- package/src/lib/failure-debug.ts +0 -26
- package/src/lib/format.ts +0 -4
- package/src/lib/idp-session.ts +0 -156
- package/src/lib/idp.ts +0 -926
- package/src/lib/instance-candidates.ts +0 -49
- package/src/lib/instance-target.ts +0 -197
- package/src/lib/instance.ts +0 -468
- package/src/lib/invocation.ts +0 -11
- package/src/lib/local-status.ts +0 -152
- package/src/lib/log.ts +0 -143
- package/src/lib/login-flow.ts +0 -201
- package/src/lib/meta.ts +0 -90
- package/src/lib/output.ts +0 -238
- package/src/lib/panel.ts +0 -61
- package/src/lib/port.ts +0 -41
- package/src/lib/proc.ts +0 -87
- package/src/lib/prompt.ts +0 -136
- package/src/lib/provision-instance.ts +0 -139
- package/src/lib/sdk-deps.ts +0 -104
- package/src/lib/self.ts +0 -22
- package/src/lib/skills/lock.ts +0 -117
- package/src/lib/skills/sync.ts +0 -878
- package/src/lib/skills.ts +0 -120
- package/src/lib/table.ts +0 -62
- package/src/lib/update.ts +0 -568
- package/src/lib/use-target.ts +0 -24
- package/src/lib/validation.ts +0 -59
- package/src/lib/view/assets.ts +0 -87
- package/src/lib/view/external-open-intent.ts +0 -41
- package/src/lib/view/external-open-origins.ts +0 -37
- package/src/lib/view/host-capabilities.ts +0 -17
- package/src/lib/view/open-intent.ts +0 -67
- package/src/lib/view/port-allocation.ts +0 -19
- package/src/lib/view/resolve.ts +0 -111
- package/src/lib/view/server.ts +0 -259
- package/src/lib/view/session.ts +0 -154
- package/src/lib/view/snapshot.ts +0 -101
- package/src/paths/.spec/api.d.ts +0 -13
- package/src/paths/.spec/architecture.md +0 -5
- package/src/paths/.spec/layout.ts +0 -3
- package/src/paths/index.ts +0 -13
- package/src/program/.spec/api.d.ts +0 -43
- package/src/program/.spec/architecture.md +0 -18
- package/src/program/.spec/laws/program.ts +0 -29
- package/src/program/.spec/layout.ts +0 -14
- package/src/program/__tests__/program.test.ts +0 -556
- package/src/program/argv.ts +0 -14
- package/src/program/build.ts +0 -205
- package/src/program/command.ts +0 -43
- package/src/program/index.ts +0 -3
- package/src/program/options.ts +0 -41
- package/src/program/registry.ts +0 -64
- package/src/program/tsconfig.json +0 -6
- package/src/setup/__tests__/instance-step.test.ts +0 -256
- package/src/setup/__tests__/util.test.ts +0 -29
- package/src/setup/engine.ts +0 -83
- package/src/setup/render.ts +0 -107
- package/src/setup/steps/admin.ts +0 -93
- package/src/setup/steps/agent-browser.ts +0 -81
- package/src/setup/steps/auth.ts +0 -54
- package/src/setup/steps/domain.ts +0 -68
- package/src/setup/steps/index.ts +0 -18
- package/src/setup/steps/instance.ts +0 -206
- package/src/setup/steps/skills-bridge.ts +0 -70
- package/src/setup/steps/skills.ts +0 -61
- package/src/setup/types.ts +0 -61
- package/src/setup/util.ts +0 -34
- package/src/state/.spec/api.d.ts +0 -152
- package/src/state/.spec/architecture.md +0 -45
- package/src/state/.spec/capabilities/state.ts +0 -7
- package/src/state/.spec/flows/identity-update.ts +0 -22
- package/src/state/.spec/laws/state.ts +0 -73
- package/src/state/.spec/layout.ts +0 -15
- package/src/state/__tests__/exchange-credentials.test.ts +0 -341
- package/src/state/__tests__/files.test.ts +0 -136
- package/src/state/__tests__/fixtures/identity-transition.ts +0 -37
- package/src/state/__tests__/fixtures/session-route-process.ts +0 -93
- package/src/state/__tests__/identities.test.ts +0 -142
- package/src/state/__tests__/paths.test.ts +0 -28
- package/src/state/__tests__/session-routes.test.ts +0 -138
- package/src/state/exchange-credentials.ts +0 -250
- package/src/state/files.ts +0 -170
- package/src/state/identities.ts +0 -229
- package/src/state/index.ts +0 -31
- package/src/state/paths.ts +0 -64
- package/src/state/session-routes.ts +0 -34
- package/src/state/tsconfig.json +0 -6
- package/src/telemetry/__tests__/analyze-log.test.ts +0 -38
- package/src/telemetry/__tests__/gate.test.ts +0 -63
- package/src/telemetry/__tests__/recorder.test.ts +0 -111
- package/src/telemetry/__tests__/redact.test.ts +0 -79
- package/src/telemetry/__tests__/retention.test.ts +0 -267
- package/src/telemetry/__tests__/session.test.ts +0 -166
- package/src/telemetry/__tests__/settings.test.ts +0 -102
- package/src/telemetry/__tests__/store-scan.test.ts +0 -128
- package/src/telemetry/__tests__/trigger.test.ts +0 -78
- package/src/telemetry/adapters/__tests__/claude-code.test.ts +0 -89
- package/src/telemetry/adapters/__tests__/codex.test.ts +0 -138
- package/src/telemetry/adapters/__tests__/index.test.ts +0 -88
- package/src/telemetry/adapters/claude-code.ts +0 -90
- package/src/telemetry/adapters/codex.ts +0 -160
- package/src/telemetry/adapters/index.ts +0 -45
- package/src/telemetry/adapters/types.ts +0 -21
- package/src/telemetry/analyze.ts +0 -249
- package/src/telemetry/gate.ts +0 -79
- package/src/telemetry/recorder.ts +0 -68
- package/src/telemetry/redact.ts +0 -53
- package/src/telemetry/retention.ts +0 -176
- package/src/telemetry/session.ts +0 -94
- package/src/telemetry/settings.ts +0 -79
- package/src/telemetry/store.ts +0 -174
- package/src/telemetry/trigger.ts +0 -107
- package/src/telemetry/types.ts +0 -64
- package/src/test-utils.ts +0 -18
- package/src/ui/.spec/api.d.ts +0 -14
- package/src/ui/.spec/architecture.md +0 -14
- package/src/ui/.spec/laws.ts +0 -50
- package/src/ui/.spec/layout.ts +0 -16
- package/src/ui/__tests__/ui.test.ts +0 -1742
- package/src/ui/index.ts +0 -13
- package/src/ui/lock.ts +0 -89
- package/src/ui/model.ts +0 -83
- package/src/ui/operations.ts +0 -1044
- package/src/ui/project.ts +0 -333
- package/src/ui/release.ts +0 -398
- package/src/ui/runner.ts +0 -18
- package/studio/client/dist/assets/index-BFVFs_8x.js +0 -81
- package/studio/client/dist/assets/index-DuB9iUdu.css +0 -2
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +0 -8
- package/studio/tsconfig.json +0 -24
- package/viewer/tsconfig.json +0 -13
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
|
|
6
|
-
import type { Finalizer } from '../recorder'
|
|
7
|
-
import type { SessionInfo } from '../store'
|
|
8
|
-
import type { TelemetryEvent } from '../types'
|
|
9
|
-
|
|
10
|
-
// ASTRALE_HOME must be set before the store/paths singleton is first imported.
|
|
11
|
-
process.env.ASTRALE_HOME = mkdtempSync(join(tmpdir(), 'astrale-tele-recorder-'))
|
|
12
|
-
|
|
13
|
-
let beginInvocation: (argv: string[]) => Finalizer
|
|
14
|
-
let eventsPath: (id: string) => string
|
|
15
|
-
let listSessions: () => SessionInfo[]
|
|
16
|
-
let sessionsRoot: () => string
|
|
17
|
-
let configPath: string
|
|
18
|
-
|
|
19
|
-
const SESSION = 'rec-test'
|
|
20
|
-
|
|
21
|
-
let work: string
|
|
22
|
-
let cwd0: string
|
|
23
|
-
|
|
24
|
-
beforeAll(async () => {
|
|
25
|
-
const recorder = await import('../recorder')
|
|
26
|
-
const store = await import('../store')
|
|
27
|
-
beginInvocation = recorder.beginInvocation
|
|
28
|
-
eventsPath = store.eventsPath
|
|
29
|
-
listSessions = store.listSessions
|
|
30
|
-
sessionsRoot = store.sessionsRoot
|
|
31
|
-
configPath = join(process.env.ASTRALE_HOME!, 'config.json')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
beforeEach(() => {
|
|
35
|
-
// Destructive cleanup must be provably confined to this file's mkdtemp home.
|
|
36
|
-
if (!sessionsRoot().startsWith(tmpdir())) throw new Error('refusing to clean a non-tmp home')
|
|
37
|
-
rmSync(sessionsRoot(), { recursive: true, force: true })
|
|
38
|
-
rmSync(configPath, { force: true })
|
|
39
|
-
delete process.env.ASTRALE_TELEMETRY
|
|
40
|
-
process.env.ASTRALE_SESSION = SESSION
|
|
41
|
-
work = mkdtempSync(join(tmpdir(), 'astrale-tele-recwork-'))
|
|
42
|
-
cwd0 = process.cwd()
|
|
43
|
-
process.chdir(work)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
afterEach(() => {
|
|
47
|
-
process.chdir(cwd0)
|
|
48
|
-
rmSync(work, { recursive: true, force: true })
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
function readEvents(id: string): TelemetryEvent[] {
|
|
52
|
-
return readFileSync(eventsPath(id), 'utf-8')
|
|
53
|
-
.split('\n')
|
|
54
|
-
.filter(Boolean)
|
|
55
|
-
.map((l) => JSON.parse(l) as TelemetryEvent)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
describe('beginInvocation', () => {
|
|
59
|
-
test('appends one valid, parseable event with redacted argv', () => {
|
|
60
|
-
const cwd = process.cwd()
|
|
61
|
-
const finalize = beginInvocation(['node', 'astrale', 'get', '--token', 'abc', 'x'])
|
|
62
|
-
finalize(0)
|
|
63
|
-
|
|
64
|
-
const events = readEvents(SESSION)
|
|
65
|
-
expect(events).toHaveLength(1)
|
|
66
|
-
const [e] = events
|
|
67
|
-
expect(e.v).toBe(1)
|
|
68
|
-
expect(e.surface).toBe('cli')
|
|
69
|
-
expect(e.exitCode).toBe(0)
|
|
70
|
-
expect(e.argv).toEqual(['get', '--token', '<redacted>', 'x'])
|
|
71
|
-
expect(e.cwd).toBe(cwd)
|
|
72
|
-
expect(e.root).toBe(cwd)
|
|
73
|
-
expect(typeof e.ts).toBe('string')
|
|
74
|
-
expect(typeof e.durationMs).toBe('number')
|
|
75
|
-
expect('errorName' in e).toBe(false)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test('records exitCode and errorName on failure', () => {
|
|
79
|
-
beginInvocation(['node', 'astrale', 'call', 'foo'])(1, 'AuthError')
|
|
80
|
-
const [e] = readEvents(SESSION)
|
|
81
|
-
expect(e.exitCode).toBe(1)
|
|
82
|
-
expect(e.errorName).toBe('AuthError')
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
test('appends a second line on a second invocation', () => {
|
|
86
|
-
beginInvocation(['node', 'astrale', 'query'])(0)
|
|
87
|
-
beginInvocation(['node', 'astrale', 'whoami'])(0)
|
|
88
|
-
expect(readEvents(SESSION)).toHaveLength(2)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('writes nothing for help/version-only invocations', () => {
|
|
92
|
-
beginInvocation(['node', 'astrale', '--help'])(0)
|
|
93
|
-
beginInvocation(['node', 'astrale', '--version'])(0)
|
|
94
|
-
beginInvocation(['node', 'astrale', '--cli-version'])(0)
|
|
95
|
-
beginInvocation(['node', 'astrale'])(0)
|
|
96
|
-
expect(listSessions()).toHaveLength(0)
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
test('writes nothing when disabled via ASTRALE_TELEMETRY', () => {
|
|
100
|
-
process.env.ASTRALE_TELEMETRY = '0'
|
|
101
|
-
beginInvocation(['node', 'astrale', 'get', 'x'])(0)
|
|
102
|
-
expect(listSessions()).toHaveLength(0)
|
|
103
|
-
expect(existsSync(eventsPath(SESSION))).toBe(false)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
test('writes nothing when disabled via config telemetry.enabled=false', () => {
|
|
107
|
-
writeFileSync(configPath, JSON.stringify({ telemetry: { enabled: false } }))
|
|
108
|
-
beginInvocation(['node', 'astrale', 'get', 'x'])(0)
|
|
109
|
-
expect(listSessions()).toHaveLength(0)
|
|
110
|
-
})
|
|
111
|
-
})
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
|
|
3
|
-
import { redactArgv } from '../redact'
|
|
4
|
-
|
|
5
|
-
describe('redactArgv', () => {
|
|
6
|
-
test('redacts the value following a secret flag', () => {
|
|
7
|
-
expect(redactArgv(['--token', 'abc123', '--json'])).toEqual(['--token', '<redacted>', '--json'])
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
test('matches secret substrings in flag names, case-insensitively', () => {
|
|
11
|
-
expect(redactArgv(['--api-Token', 's', '--Bearer', 'b', '--jwk', 'k'])).toEqual([
|
|
12
|
-
'--api-Token',
|
|
13
|
-
'<redacted>',
|
|
14
|
-
'--Bearer',
|
|
15
|
-
'<redacted>',
|
|
16
|
-
'--jwk',
|
|
17
|
-
'<redacted>',
|
|
18
|
-
])
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('redacts the value part of inline k=v secret args', () => {
|
|
22
|
-
expect(redactArgv(['--secret=hunter2', 'password=p', '--verbose'])).toEqual([
|
|
23
|
-
'--secret=<redacted>',
|
|
24
|
-
'password=<redacted>',
|
|
25
|
-
'--verbose',
|
|
26
|
-
])
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
test('leaves non-secret flags and their values intact', () => {
|
|
30
|
-
expect(redactArgv(['query', '--limit', '3', '--format=json'])).toEqual([
|
|
31
|
-
'query',
|
|
32
|
-
'--limit',
|
|
33
|
-
'3',
|
|
34
|
-
'--format=json',
|
|
35
|
-
])
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('truncates any arg longer than 200 chars to 200 + ellipsis', () => {
|
|
39
|
-
const long = 'x'.repeat(300)
|
|
40
|
-
const [only] = redactArgv([long])
|
|
41
|
-
expect(only).toBe('x'.repeat(200) + '…')
|
|
42
|
-
expect([...only].length).toBe(201)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test('a secret value is redacted before length matters', () => {
|
|
46
|
-
expect(redactArgv(['--password', 'y'.repeat(500)])).toEqual(['--password', '<redacted>'])
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('caps the array at 40 items with a …+N marker', () => {
|
|
50
|
-
const args = Array.from({ length: 45 }, (_, i) => `a${i}`)
|
|
51
|
-
const out = redactArgv(args)
|
|
52
|
-
expect(out.length).toBe(41)
|
|
53
|
-
expect(out.slice(0, 40)).toEqual(args.slice(0, 40))
|
|
54
|
-
expect(out[40]).toBe('…+5')
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test('does not cap at exactly 40 items', () => {
|
|
58
|
-
const args = Array.from({ length: 40 }, (_, i) => `a${i}`)
|
|
59
|
-
expect(redactArgv(args)).toEqual(args)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test('a trailing secret flag with no value does not throw', () => {
|
|
63
|
-
expect(redactArgv(['call', '--token'])).toEqual(['call', '--token'])
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
describe('secret-shaped values', () => {
|
|
68
|
-
test('positional JWT is redacted wherever it appears', () => {
|
|
69
|
-
const jwt =
|
|
70
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U'
|
|
71
|
-
expect(redactArgv(['call', '/x::y', jwt])).toEqual(['call', '/x::y', '<redacted>'])
|
|
72
|
-
expect(redactArgv(['call', `body=Bearer ${jwt}`])[1]).toBe('body=Bearer <redacted>')
|
|
73
|
-
})
|
|
74
|
-
test('sk- style api key is redacted, plain paths are not', () => {
|
|
75
|
-
expect(redactArgv(['x', 'sk-abcdefghijklmnop1234'])).toEqual(['x', '<redacted>'])
|
|
76
|
-
const p = '/Users/someone/Documents/astrale/workspace/cli/src/telemetry/adapters/index.ts'
|
|
77
|
-
expect(redactArgv([p])).toEqual([p])
|
|
78
|
-
})
|
|
79
|
-
})
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import { existsSync, mkdirSync, mkdtempSync, rmSync, utimesSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
|
|
6
|
-
import type { SweepOptions, SweepResult } from '../retention'
|
|
7
|
-
import type { RetentionBudget } from '../settings'
|
|
8
|
-
import type { SessionScan } from '../store'
|
|
9
|
-
|
|
10
|
-
// Set the home before the paths singleton is captured (dynamic imports below).
|
|
11
|
-
process.env.ASTRALE_HOME = mkdtempSync(join(tmpdir(), 'astrale-tele-retention-'))
|
|
12
|
-
|
|
13
|
-
let sweepByAge: (sessions: readonly SessionScan[], options?: SweepOptions) => SweepResult
|
|
14
|
-
let sweepToBudget: (sessions: readonly SessionScan[], options?: SweepOptions) => SweepResult
|
|
15
|
-
let sweepStore: (options?: SweepOptions) => SweepResult
|
|
16
|
-
let tidySession: (id: string, options?: { keepPrompt?: boolean }) => string[]
|
|
17
|
-
let scanSessions: () => SessionScan[]
|
|
18
|
-
let sessionDir: (id: string) => string
|
|
19
|
-
let sessionBytes: (id: string) => number
|
|
20
|
-
let sessionsRoot: () => string
|
|
21
|
-
|
|
22
|
-
const DAY = 24 * 60 * 60 * 1000
|
|
23
|
-
const BUDGET: RetentionBudget = { maxAgeMs: 30 * DAY, maxBytes: 10_000 }
|
|
24
|
-
|
|
25
|
-
beforeAll(async () => {
|
|
26
|
-
;({ sweepByAge, sweepToBudget, sweepStore, tidySession } = await import('../retention'))
|
|
27
|
-
;({ scanSessions, sessionDir, sessionBytes, sessionsRoot } = await import('../store'))
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
// Destructive cleanup must be provably confined to this file's mkdtemp home.
|
|
32
|
-
if (!sessionsRoot().startsWith(tmpdir())) throw new Error('refusing to clean a non-tmp home')
|
|
33
|
-
rmSync(sessionsRoot(), { recursive: true, force: true })
|
|
34
|
-
delete process.env.ASTRALE_TELEMETRY_MAX_AGE_DAYS
|
|
35
|
-
delete process.env.ASTRALE_TELEMETRY_MAX_BYTES
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
afterEach(() => {
|
|
39
|
-
rmSync(sessionsRoot(), { recursive: true, force: true })
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
type Seed = { ageMs?: number; analyzed?: boolean; bytes?: number; events?: boolean }
|
|
43
|
-
|
|
44
|
-
function seed(id: string, { ageMs = 0, analyzed = false, bytes = 0, events = true }: Seed): void {
|
|
45
|
-
const dir = sessionDir(id)
|
|
46
|
-
mkdirSync(dir, { recursive: true })
|
|
47
|
-
writeFileSync(join(dir, 'meta.json'), JSON.stringify({ id, root: '/w', explicit: false }))
|
|
48
|
-
if (bytes > 0) writeFileSync(join(dir, 'report.md'), 'x'.repeat(bytes))
|
|
49
|
-
if (analyzed) {
|
|
50
|
-
writeFileSync(
|
|
51
|
-
join(dir, '.analyzed'),
|
|
52
|
-
JSON.stringify({ analyzedAt: new Date().toISOString(), outcome: 'reported' }),
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
if (!events) return
|
|
56
|
-
writeFileSync(join(dir, 'events.jsonl'), '{}\n')
|
|
57
|
-
const when = new Date(Date.now() - ageMs)
|
|
58
|
-
utimesSync(join(dir, 'events.jsonl'), when, when)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const alive = (id: string): boolean => existsSync(sessionDir(id))
|
|
62
|
-
|
|
63
|
-
describe('sweepByAge', () => {
|
|
64
|
-
test('drops sessions past the age bound whether or not they were analyzed', () => {
|
|
65
|
-
seed('old-analyzed', { ageMs: 40 * DAY, analyzed: true })
|
|
66
|
-
seed('old-unanalyzed', { ageMs: 40 * DAY })
|
|
67
|
-
seed('recent-analyzed', { ageMs: 2 * DAY, analyzed: true })
|
|
68
|
-
seed('recent-unanalyzed', { ageMs: 2 * DAY })
|
|
69
|
-
|
|
70
|
-
const { removed } = sweepByAge(scanSessions(), { budget: BUDGET })
|
|
71
|
-
|
|
72
|
-
expect(removed.sort()).toEqual(['old-analyzed', 'old-unanalyzed'])
|
|
73
|
-
expect(alive('recent-analyzed')).toBe(true)
|
|
74
|
-
expect(alive('recent-unanalyzed')).toBe(true)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
test('never touches a session with no events — that is the invocation running now', () => {
|
|
78
|
-
seed('no-events-yet', { events: false })
|
|
79
|
-
expect(sweepByAge(scanSessions(), { budget: BUDGET }).removed).toEqual([])
|
|
80
|
-
expect(alive('no-events-yet')).toBe(true)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('honours the removal cap so a backlog drains over several runs', () => {
|
|
84
|
-
for (let i = 0; i < 5; i++) seed(`stale-${i}`, { ageMs: 40 * DAY, analyzed: true })
|
|
85
|
-
expect(sweepByAge(scanSessions(), { budget: BUDGET, limit: 2 }).removed).toHaveLength(2)
|
|
86
|
-
expect(scanSessions()).toHaveLength(3)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
test('respects the configured age bound', () => {
|
|
90
|
-
seed('week-old', { ageMs: 8 * DAY, analyzed: true })
|
|
91
|
-
const strict: RetentionBudget = { ...BUDGET, maxAgeMs: 7 * DAY }
|
|
92
|
-
expect(sweepByAge(scanSessions(), { budget: strict }).removed).toEqual(['week-old'])
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
test('protected ids survive the age bound', () => {
|
|
96
|
-
seed('old-but-mine', { ageMs: 40 * DAY, analyzed: true })
|
|
97
|
-
const options = { budget: BUDGET, protect: new Set(['old-but-mine']) }
|
|
98
|
-
expect(sweepByAge(scanSessions(), options).removed).toEqual([])
|
|
99
|
-
expect(alive('old-but-mine')).toBe(true)
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
describe('sweepToBudget', () => {
|
|
104
|
-
test('does nothing while the store fits', () => {
|
|
105
|
-
seed('small', { ageMs: DAY, analyzed: true, bytes: 100 })
|
|
106
|
-
expect(sweepToBudget(scanSessions(), { budget: BUDGET }).removed).toEqual([])
|
|
107
|
-
expect(alive('small')).toBe(true)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
test('evicts oldest-first until the store is back under budget', () => {
|
|
111
|
-
seed('oldest', { ageMs: 5 * DAY, analyzed: true, bytes: 4_000 })
|
|
112
|
-
seed('middle', { ageMs: 3 * DAY, analyzed: true, bytes: 4_000 })
|
|
113
|
-
seed('newest', { ageMs: DAY, analyzed: true, bytes: 4_000 })
|
|
114
|
-
|
|
115
|
-
// 3 × ~4 KB against a 5 KB bound: two must go, and the newest must stay.
|
|
116
|
-
const { removed } = sweepToBudget(scanSessions(), { budget: { ...BUDGET, maxBytes: 5_000 } })
|
|
117
|
-
|
|
118
|
-
expect(removed).toEqual(['oldest', 'middle'])
|
|
119
|
-
expect(alive('newest')).toBe(true)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
test('evicts analyzed sessions before unanalyzed ones of any age', () => {
|
|
123
|
-
seed('unanalyzed-oldest', { ageMs: 9 * DAY, bytes: 6_000 })
|
|
124
|
-
seed('analyzed-newest', { ageMs: DAY, analyzed: true, bytes: 6_000 })
|
|
125
|
-
|
|
126
|
-
expect(sweepToBudget(scanSessions(), { budget: BUDGET }).removed).toEqual(['analyzed-newest'])
|
|
127
|
-
expect(alive('unanalyzed-oldest')).toBe(true)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
test('falls through to unanalyzed sessions when analyzed ones are not enough', () => {
|
|
131
|
-
seed('unanalyzed-old', { ageMs: 9 * DAY, bytes: 6_000 })
|
|
132
|
-
seed('analyzed-old', { ageMs: 8 * DAY, analyzed: true, bytes: 6_000 })
|
|
133
|
-
seed('unanalyzed-new', { ageMs: DAY, bytes: 6_000 })
|
|
134
|
-
|
|
135
|
-
const { removed } = sweepToBudget(scanSessions(), { budget: BUDGET })
|
|
136
|
-
|
|
137
|
-
expect(removed).toEqual(['analyzed-old', 'unanalyzed-old'])
|
|
138
|
-
expect(alive('unanalyzed-new')).toBe(true)
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
test('never evicts an open session, even when that leaves the store over budget', () => {
|
|
142
|
-
// No age offset → inside IDLE_WINDOW_MS → open.
|
|
143
|
-
seed('open-and-huge', { analyzed: true, bytes: 20_000 })
|
|
144
|
-
expect(sweepToBudget(scanSessions(), { budget: BUDGET }).removed).toEqual([])
|
|
145
|
-
expect(alive('open-and-huge')).toBe(true)
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
test('never evicts a protected session', () => {
|
|
149
|
-
seed('just-analyzed', { ageMs: DAY, analyzed: true, bytes: 20_000 })
|
|
150
|
-
const options = { budget: BUDGET, protect: new Set(['just-analyzed']) }
|
|
151
|
-
expect(sweepToBudget(scanSessions(), options).removed).toEqual([])
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
test('counts nested analyzer output — a session is bounded by all it holds', () => {
|
|
155
|
-
seed('nested', { ageMs: DAY, analyzed: true, bytes: 100 })
|
|
156
|
-
mkdirSync(join(sessionDir('nested'), 'scratch'), { recursive: true })
|
|
157
|
-
writeFileSync(join(sessionDir('nested'), 'scratch', 'dump.txt'), 'x'.repeat(5_000))
|
|
158
|
-
|
|
159
|
-
expect(sessionBytes('nested')).toBeGreaterThan(5_000)
|
|
160
|
-
})
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
describe('sweepStore', () => {
|
|
164
|
-
test('applies age first, then trims what survives to the size bound', () => {
|
|
165
|
-
seed('expired', { ageMs: 40 * DAY, analyzed: true, bytes: 6_000 })
|
|
166
|
-
seed('fresh-big', { ageMs: 2 * DAY, analyzed: true, bytes: 6_000 })
|
|
167
|
-
seed('fresh-small', { ageMs: DAY, analyzed: true, bytes: 100 })
|
|
168
|
-
|
|
169
|
-
// Age alone frees 6 KB, leaving ~6.1 KB — already under the 10 KB bound,
|
|
170
|
-
// so the size pass must find nothing left to do.
|
|
171
|
-
expect(sweepStore({ budget: BUDGET }).removed).toEqual(['expired'])
|
|
172
|
-
expect(alive('fresh-big')).toBe(true)
|
|
173
|
-
expect(alive('fresh-small')).toBe(true)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
test('reads its budget from the environment when none is passed', () => {
|
|
177
|
-
process.env.ASTRALE_TELEMETRY_MAX_AGE_DAYS = '1'
|
|
178
|
-
seed('two-days-old', { ageMs: 2 * DAY, analyzed: true })
|
|
179
|
-
expect(sweepStore().removed).toEqual(['two-days-old'])
|
|
180
|
-
})
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
describe('sweepStore tidying', () => {
|
|
184
|
-
test('tidies analyzed survivors, so the bound applies to sessions already on disk', () => {
|
|
185
|
-
seed('store-tidy', { ageMs: DAY, analyzed: true, bytes: 100 })
|
|
186
|
-
writeFileSync(join(sessionDir('store-tidy'), 'calls.txt'), 'x'.repeat(50_000))
|
|
187
|
-
|
|
188
|
-
sweepStore({ budget: BUDGET })
|
|
189
|
-
|
|
190
|
-
expect(existsSync(join(sessionDir('store-tidy'), 'calls.txt'))).toBe(false)
|
|
191
|
-
expect(existsSync(join(sessionDir('store-tidy'), 'report.md'))).toBe(true)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
test('an unanalyzed session is left alone — its analyzer may still be running', () => {
|
|
195
|
-
seed('store-pending', { ageMs: DAY, bytes: 100 })
|
|
196
|
-
writeFileSync(join(sessionDir('store-pending'), 'analyzer-prompt.md'), '# in flight')
|
|
197
|
-
|
|
198
|
-
sweepStore({ budget: BUDGET })
|
|
199
|
-
|
|
200
|
-
expect(existsSync(join(sessionDir('store-pending'), 'analyzer-prompt.md'))).toBe(true)
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
test('scratch is tidied before the size bound is measured, not after', () => {
|
|
204
|
-
// 60 KB of scratch against a 10 KB budget: tidying first brings the store
|
|
205
|
-
// back under on its own, so nothing should be evicted.
|
|
206
|
-
seed('store-bloated', { ageMs: DAY, analyzed: true, bytes: 100 })
|
|
207
|
-
writeFileSync(join(sessionDir('store-bloated'), 'calls.txt'), 'x'.repeat(60_000))
|
|
208
|
-
|
|
209
|
-
expect(sweepStore({ budget: BUDGET }).removed).toEqual([])
|
|
210
|
-
expect(existsSync(sessionDir('store-bloated'))).toBe(true)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
test('a failed analysis keeps its prompt through the sweep', () => {
|
|
214
|
-
const dir = sessionDir('store-failed')
|
|
215
|
-
seed('store-failed', { ageMs: DAY, analyzed: true })
|
|
216
|
-
writeFileSync(
|
|
217
|
-
join(dir, '.analyzed'),
|
|
218
|
-
JSON.stringify({ analyzedAt: new Date().toISOString(), outcome: 'error', note: 'boom' }),
|
|
219
|
-
)
|
|
220
|
-
writeFileSync(join(dir, 'analyzer-prompt.md'), '# what we asked')
|
|
221
|
-
|
|
222
|
-
sweepStore({ budget: BUDGET })
|
|
223
|
-
|
|
224
|
-
expect(existsSync(join(dir, 'analyzer-prompt.md'))).toBe(true)
|
|
225
|
-
})
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
describe('tidySession', () => {
|
|
229
|
-
test('removes what the analyzer left behind, keeps the durable artifacts', () => {
|
|
230
|
-
const dir = sessionDir('tidy-scratch')
|
|
231
|
-
seed('tidy-scratch', { ageMs: DAY, analyzed: true, bytes: 100 })
|
|
232
|
-
writeFileSync(join(dir, 'analyzer.log'), 'exit 0')
|
|
233
|
-
writeFileSync(join(dir, 'analyzer-prompt.md'), '# prompt')
|
|
234
|
-
// Scratch: the analyzer runs with Write in here and answers to nobody.
|
|
235
|
-
writeFileSync(join(dir, 'calls.txt'), 'x'.repeat(50_000))
|
|
236
|
-
mkdirSync(join(dir, 'notes'), { recursive: true })
|
|
237
|
-
writeFileSync(join(dir, 'notes', 'draft.md'), 'scratch')
|
|
238
|
-
|
|
239
|
-
const removed = tidySession('tidy-scratch')
|
|
240
|
-
|
|
241
|
-
expect(removed.sort()).toEqual(['analyzer-prompt.md', 'calls.txt', 'notes'])
|
|
242
|
-
for (const keep of ['meta.json', 'events.jsonl', 'report.md', '.analyzed', 'analyzer.log']) {
|
|
243
|
-
expect(existsSync(join(dir, keep))).toBe(true)
|
|
244
|
-
}
|
|
245
|
-
expect(existsSync(join(dir, 'notes'))).toBe(false)
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
test('keepPrompt spares the prompt — the failing case is when it matters', () => {
|
|
249
|
-
const dir = sessionDir('tidy-failed')
|
|
250
|
-
seed('tidy-failed', { ageMs: DAY, analyzed: true })
|
|
251
|
-
writeFileSync(join(dir, 'analyzer-prompt.md'), '# prompt')
|
|
252
|
-
writeFileSync(join(dir, 'scratch.json'), '{}')
|
|
253
|
-
|
|
254
|
-
expect(tidySession('tidy-failed', { keepPrompt: true })).toEqual(['scratch.json'])
|
|
255
|
-
expect(existsSync(join(dir, 'analyzer-prompt.md'))).toBe(true)
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
test('a session with nothing to tidy is left exactly as it was', () => {
|
|
259
|
-
seed('tidy-clean', { ageMs: DAY, analyzed: true, bytes: 10 })
|
|
260
|
-
expect(tidySession('tidy-clean')).toEqual([])
|
|
261
|
-
expect(existsSync(sessionDir('tidy-clean'))).toBe(true)
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
test('a missing session directory is a no-op, not an error', () => {
|
|
265
|
-
expect(tidySession('tidy-absent')).toEqual([])
|
|
266
|
-
})
|
|
267
|
-
})
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import {
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
mkdtempSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
rmSync,
|
|
8
|
-
utimesSync,
|
|
9
|
-
writeFileSync,
|
|
10
|
-
} from 'node:fs'
|
|
11
|
-
import { tmpdir } from 'node:os'
|
|
12
|
-
import { join } from 'node:path'
|
|
13
|
-
|
|
14
|
-
import type { ResolvedSession } from '../session'
|
|
15
|
-
import type { SessionMeta } from '../types'
|
|
16
|
-
|
|
17
|
-
/** Seed an ambient session on disk with a chosen id + last-activity age. */
|
|
18
|
-
function seedAmbient(id: string, root: string, ageMs: number): void {
|
|
19
|
-
mkdirSync(sessionDir(id), { recursive: true })
|
|
20
|
-
const meta: SessionMeta = { id, root, explicit: false, createdAt: new Date().toISOString() }
|
|
21
|
-
writeFileSync(metaPath(id), JSON.stringify(meta))
|
|
22
|
-
writeFileSync(eventsPath(id), '{}\n')
|
|
23
|
-
const when = new Date(Date.now() - ageMs)
|
|
24
|
-
utimesSync(eventsPath(id), when, when)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Set ASTRALE_HOME before the store/paths singleton is first evaluated (below,
|
|
28
|
-
// via dynamic import). All on-disk access goes through the store helpers so it
|
|
29
|
-
// stays consistent whichever home the singleton captured.
|
|
30
|
-
process.env.ASTRALE_HOME = mkdtempSync(join(tmpdir(), 'astrale-tele-session-'))
|
|
31
|
-
|
|
32
|
-
let resolveSession: (cwd: string) => ResolvedSession
|
|
33
|
-
let ensureSession: (cwd: string) => ResolvedSession
|
|
34
|
-
let eventsPath: (id: string) => string
|
|
35
|
-
let metaPath: (id: string) => string
|
|
36
|
-
let sessionDir: (id: string) => string
|
|
37
|
-
let sessionsRoot: () => string
|
|
38
|
-
let IDLE_WINDOW_MS: number
|
|
39
|
-
|
|
40
|
-
let work: string
|
|
41
|
-
|
|
42
|
-
beforeAll(async () => {
|
|
43
|
-
const session = await import('../session')
|
|
44
|
-
const store = await import('../store')
|
|
45
|
-
resolveSession = session.resolveSession
|
|
46
|
-
ensureSession = session.ensureSession
|
|
47
|
-
eventsPath = store.eventsPath
|
|
48
|
-
metaPath = store.metaPath
|
|
49
|
-
sessionDir = store.sessionDir
|
|
50
|
-
sessionsRoot = store.sessionsRoot
|
|
51
|
-
IDLE_WINDOW_MS = store.IDLE_WINDOW_MS
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
beforeEach(() => {
|
|
55
|
-
if (!sessionsRoot().startsWith(tmpdir())) throw new Error('refusing to clean a non-tmp home')
|
|
56
|
-
rmSync(sessionsRoot(), { recursive: true, force: true })
|
|
57
|
-
delete process.env.ASTRALE_SESSION
|
|
58
|
-
work = mkdtempSync(join(tmpdir(), 'astrale-tele-work-'))
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
afterEach(() => {
|
|
62
|
-
rmSync(work, { recursive: true, force: true })
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
describe('resolveSession — ambient', () => {
|
|
66
|
-
test('reuses an open ambient session bucketed to the same root', () => {
|
|
67
|
-
// An old-stamp id proves reuse: a fresh mint would carry the current minute.
|
|
68
|
-
const existing = 'amb-deadbeef-200001010000'
|
|
69
|
-
seedAmbient(existing, work, 5 * 60_000)
|
|
70
|
-
|
|
71
|
-
const r = resolveSession(work)
|
|
72
|
-
|
|
73
|
-
expect(r).toEqual({ id: existing, root: work, explicit: false })
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
test('mints a fresh session once the previous one is idle', () => {
|
|
77
|
-
const existing = 'amb-deadbeef-200001010000'
|
|
78
|
-
seedAmbient(existing, work, IDLE_WINDOW_MS + 60_000)
|
|
79
|
-
|
|
80
|
-
const r = resolveSession(work)
|
|
81
|
-
|
|
82
|
-
expect(r.id).not.toBe(existing)
|
|
83
|
-
expect(r.explicit).toBe(false)
|
|
84
|
-
expect(r.id).toMatch(/^amb-[0-9a-f]{8}-\d{12}$/)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
test('does not reuse a session bucketed to a different root', () => {
|
|
88
|
-
const existing = 'amb-deadbeef-200001010000'
|
|
89
|
-
seedAmbient(existing, work, 5 * 60_000)
|
|
90
|
-
|
|
91
|
-
const other = mkdtempSync(join(tmpdir(), 'astrale-tele-other-'))
|
|
92
|
-
try {
|
|
93
|
-
expect(resolveSession(other).id).not.toBe(existing)
|
|
94
|
-
} finally {
|
|
95
|
-
rmSync(other, { recursive: true, force: true })
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
test('ensureSession creates then reuses one session across calls', () => {
|
|
100
|
-
const first = ensureSession(work)
|
|
101
|
-
const second = ensureSession(work)
|
|
102
|
-
expect(second.id).toBe(first.id)
|
|
103
|
-
expect(first.id).toMatch(/^amb-[0-9a-f]{8}-\d{12}$/)
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
describe('resolveSession — root bucketing', () => {
|
|
108
|
-
test('buckets by the nearest .git ancestor, not cwd', () => {
|
|
109
|
-
mkdirSync(join(work, '.git'), { recursive: true })
|
|
110
|
-
const deep = join(work, 'sub', 'deep')
|
|
111
|
-
mkdirSync(deep, { recursive: true })
|
|
112
|
-
|
|
113
|
-
expect(resolveSession(deep).root).toBe(work)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
test('falls back to cwd when there is no git root', () => {
|
|
117
|
-
expect(resolveSession(work).root).toBe(work)
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
describe('resolveSession — explicit ASTRALE_SESSION', () => {
|
|
122
|
-
test('honors an explicit id, sanitizing unsafe chars and capping at 64', () => {
|
|
123
|
-
process.env.ASTRALE_SESSION = 'my/weird:sess!!'
|
|
124
|
-
const r = resolveSession(work)
|
|
125
|
-
expect(r).toEqual({ id: 'myweirdsess', root: work, explicit: true })
|
|
126
|
-
|
|
127
|
-
process.env.ASTRALE_SESSION = 'a'.repeat(80)
|
|
128
|
-
expect(resolveSession(work).id).toBe('a'.repeat(64))
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
test('falls through to ambient when the id is empty after sanitizing', () => {
|
|
132
|
-
process.env.ASTRALE_SESSION = '///'
|
|
133
|
-
const r = resolveSession(work)
|
|
134
|
-
expect(r.explicit).toBe(false)
|
|
135
|
-
expect(r.id).toMatch(/^amb-/)
|
|
136
|
-
})
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
describe('ensureSession', () => {
|
|
140
|
-
test('creates the session dir and a readable meta.json once', () => {
|
|
141
|
-
const r = ensureSession(work)
|
|
142
|
-
|
|
143
|
-
expect(existsSync(metaPath(r.id))).toBe(true)
|
|
144
|
-
const meta = JSON.parse(readFileSync(metaPath(r.id), 'utf-8')) as SessionMeta
|
|
145
|
-
expect(meta.id).toBe(r.id)
|
|
146
|
-
expect(meta.root).toBe(work)
|
|
147
|
-
expect(meta.explicit).toBe(false)
|
|
148
|
-
expect(typeof meta.createdAt).toBe('string')
|
|
149
|
-
})
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
describe('analyzed sessions are never reused', () => {
|
|
153
|
-
test('an open ambient session with an .analyzed marker gets a fresh mint', async () => {
|
|
154
|
-
const { markerPath } = await import('../store')
|
|
155
|
-
const root = join(work, 'analyzed-root')
|
|
156
|
-
mkdirSync(root, { recursive: true })
|
|
157
|
-
seedAmbient('amb-analyzed-open', root, 0) // fresh mtime → open
|
|
158
|
-
writeFileSync(
|
|
159
|
-
markerPath('amb-analyzed-open'),
|
|
160
|
-
JSON.stringify({ analyzedAt: new Date().toISOString(), outcome: 'reported' }),
|
|
161
|
-
)
|
|
162
|
-
const resolved = resolveSession(root)
|
|
163
|
-
expect(resolved.id).not.toBe('amb-analyzed-open')
|
|
164
|
-
expect(resolved.id.startsWith('amb-')).toBe(true)
|
|
165
|
-
})
|
|
166
|
-
})
|