@namzu/cli 26.3.0 → 28.0.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 +271 -0
- package/README.md +67 -46
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -1
- package/dist/checkpoints/store.d.ts +17 -4
- package/dist/checkpoints/store.d.ts.map +1 -1
- package/dist/checkpoints/store.js +20 -5
- package/dist/checkpoints/store.js.map +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +124 -14
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts +23 -1
- package/dist/commands/acp.d.ts.map +1 -1
- package/dist/commands/acp.js +41 -2
- package/dist/commands/acp.js.map +1 -1
- package/dist/commands/drain.d.ts +69 -2
- package/dist/commands/drain.d.ts.map +1 -1
- package/dist/commands/drain.js +216 -136
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/eval.js +1 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/{run-flags.d.ts → exec-flags.d.ts} +36 -28
- package/dist/commands/exec-flags.d.ts.map +1 -0
- package/dist/commands/{run-flags.js → exec-flags.js} +35 -27
- package/dist/commands/exec-flags.js.map +1 -0
- package/dist/commands/exec-json.d.ts +75 -0
- package/dist/commands/exec-json.d.ts.map +1 -0
- package/dist/commands/exec-json.js +473 -0
- package/dist/commands/exec-json.js.map +1 -0
- package/dist/commands/exec.d.ts +42 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/exec.js +683 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/host-queries.d.ts +12 -0
- package/dist/commands/host-queries.d.ts.map +1 -0
- package/dist/commands/host-queries.js +149 -0
- package/dist/commands/host-queries.js.map +1 -0
- package/dist/commands/prior-messages.js +1 -1
- package/dist/commands/prior-messages.js.map +1 -1
- package/dist/commands/provider-wait.d.ts +5 -5
- package/dist/commands/provider-wait.d.ts.map +1 -1
- package/dist/commands/provider-wait.js +2 -2
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +2 -0
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/resident-flags.d.ts +2 -2
- package/dist/commands/resident-flags.d.ts.map +1 -1
- package/dist/commands/resident-flags.js +4 -2
- package/dist/commands/resident-flags.js.map +1 -1
- package/dist/commands/resident-learning.js +1 -1
- package/dist/commands/resident-learning.js.map +1 -1
- package/dist/commands/resident.d.ts.map +1 -1
- package/dist/commands/resident.js +3 -2
- package/dist/commands/resident.js.map +1 -1
- package/dist/commands/serve.js +2 -2
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/skills.js +1 -1
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/state.js +8 -8
- package/dist/commands/state.js.map +1 -1
- package/dist/commands/stdin.d.ts +5 -5
- package/dist/commands/stdin.js +5 -5
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/config/load.d.ts +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +36 -5
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +59 -20
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/tool-result-screens.js +1 -1
- package/dist/config/turn-guards.d.ts +23 -0
- package/dist/config/turn-guards.d.ts.map +1 -0
- package/dist/config/turn-guards.js +46 -0
- package/dist/config/turn-guards.js.map +1 -0
- package/dist/context/directories.d.ts +37 -0
- package/dist/context/directories.d.ts.map +1 -0
- package/dist/context/directories.js +69 -0
- package/dist/context/directories.js.map +1 -0
- package/dist/context/environment.d.ts +55 -0
- package/dist/context/environment.d.ts.map +1 -1
- package/dist/context/environment.js +96 -0
- package/dist/context/environment.js.map +1 -1
- package/dist/context/project-tracker.d.ts +3 -3
- package/dist/context/project-tracker.d.ts.map +1 -1
- package/dist/context/project-tracker.js +3 -3
- package/dist/context/project-tracker.js.map +1 -1
- package/dist/context/project.d.ts +2 -2
- package/dist/context/project.js +2 -2
- package/dist/context/sandbox.d.ts +22 -3
- package/dist/context/sandbox.d.ts.map +1 -1
- package/dist/context/sandbox.js +42 -4
- package/dist/context/sandbox.js.map +1 -1
- package/dist/doctor/checks/chain.d.ts +1 -1
- package/dist/doctor/checks/chain.js +7 -7
- package/dist/doctor/checks/chain.js.map +1 -1
- package/dist/doctor/checks/invariants.d.ts +2 -2
- package/dist/doctor/checks/invariants.js +5 -5
- package/dist/doctor/checks/invariants.js.map +1 -1
- package/dist/doctor/checks/logging.js +1 -1
- package/dist/doctor/checks/logging.js.map +1 -1
- package/dist/doctor/checks/sandbox.js +1 -1
- package/dist/doctor/checks/sandbox.js.map +1 -1
- package/dist/doctor/checks/session-export.d.ts +1 -1
- package/dist/doctor/checks/session-export.js +2 -2
- package/dist/doctor/checks/session-export.js.map +1 -1
- package/dist/doctor/checks/telemetry.d.ts +1 -1
- package/dist/doctor/checks/telemetry.js +1 -1
- package/dist/doctor/registry.js +2 -2
- package/dist/doctor/registry.js.map +1 -1
- package/dist/exit-codes.d.ts +2 -2
- package/dist/exit-codes.js +2 -2
- package/dist/integrations/mcp/servers.d.ts +1 -1
- package/dist/integrations/providers/access.d.ts +4 -1
- package/dist/integrations/providers/access.d.ts.map +1 -1
- package/dist/integrations/providers/access.js +6 -3
- package/dist/integrations/providers/access.js.map +1 -1
- package/dist/integrations/providers/chain-capabilities.d.ts +6 -6
- package/dist/integrations/providers/chain-capabilities.js +8 -8
- package/dist/integrations/providers/chain-capabilities.js.map +1 -1
- package/dist/integrations/providers/register.d.ts +1 -1
- package/dist/integrations/providers/register.js +1 -1
- package/dist/integrations/providers/registry.d.ts +1 -1
- package/dist/integrations/providers/zen-catalogue.d.ts +100 -0
- package/dist/integrations/providers/zen-catalogue.d.ts.map +1 -0
- package/dist/integrations/providers/zen-catalogue.js +196 -0
- package/dist/integrations/providers/zen-catalogue.js.map +1 -0
- package/dist/integrations/resident/inspection.d.ts +4 -1
- package/dist/integrations/resident/inspection.d.ts.map +1 -1
- package/dist/integrations/resident/inspection.js +53 -34
- package/dist/integrations/resident/inspection.js.map +1 -1
- package/dist/integrations/resident/learning-storage.d.ts +7 -2
- package/dist/integrations/resident/learning-storage.d.ts.map +1 -1
- package/dist/integrations/resident/learning-storage.js +12 -8
- package/dist/integrations/resident/learning-storage.js.map +1 -1
- package/dist/integrations/resident/runner-launch.d.ts +3 -3
- package/dist/integrations/resident/runner-launch.d.ts.map +1 -1
- package/dist/integrations/resident/runner-store.d.ts +1 -1
- package/dist/integrations/resident/runner-store.d.ts.map +1 -1
- package/dist/integrations/resident/runner-store.js +5 -2
- package/dist/integrations/resident/runner-store.js.map +1 -1
- package/dist/integrations/resident/runner-worker.js +23 -0
- package/dist/integrations/resident/runner-worker.js.map +1 -1
- package/dist/integrations/resident/session-log-reads.d.ts +37 -0
- package/dist/integrations/resident/session-log-reads.d.ts.map +1 -0
- package/dist/integrations/resident/session-log-reads.js +125 -0
- package/dist/integrations/resident/session-log-reads.js.map +1 -0
- package/dist/integrations/resident/session-step.d.ts +4 -2
- package/dist/integrations/resident/session-step.d.ts.map +1 -1
- package/dist/integrations/resident/session-step.js +16 -14
- package/dist/integrations/resident/session-step.js.map +1 -1
- package/dist/integrations/resident/storage.d.ts +23 -1
- package/dist/integrations/resident/storage.d.ts.map +1 -1
- package/dist/integrations/resident/storage.js +60 -17
- package/dist/integrations/resident/storage.js.map +1 -1
- package/dist/integrations/resident/tool-evidence.d.ts +4 -2
- package/dist/integrations/resident/tool-evidence.d.ts.map +1 -1
- package/dist/integrations/resident/tool-evidence.js +36 -30
- package/dist/integrations/resident/tool-evidence.js.map +1 -1
- package/dist/integrations/resident/verification.d.ts +2 -2
- package/dist/integrations/resident/verification.d.ts.map +1 -1
- package/dist/integrations/resident/verification.js +4 -4
- package/dist/integrations/resident/verification.js.map +1 -1
- package/dist/integrations/sessions/checkpoint-view.d.ts +13 -0
- package/dist/integrations/sessions/checkpoint-view.d.ts.map +1 -0
- package/dist/integrations/sessions/checkpoint-view.js +48 -0
- package/dist/integrations/sessions/checkpoint-view.js.map +1 -0
- package/dist/integrations/sessions/context-inventory.js +1 -1
- package/dist/integrations/sessions/context-inventory.js.map +1 -1
- package/dist/integrations/sessions/conversation-search.d.ts +16 -20
- package/dist/integrations/sessions/conversation-search.d.ts.map +1 -1
- package/dist/integrations/sessions/conversation-search.js +305 -731
- package/dist/integrations/sessions/conversation-search.js.map +1 -1
- package/dist/integrations/sessions/evidence-page-validation.d.ts +4 -4
- package/dist/integrations/sessions/evidence-page-validation.d.ts.map +1 -1
- package/dist/integrations/sessions/evidence-page-validation.js +1 -1
- package/dist/integrations/sessions/evidence-page-validation.js.map +1 -1
- package/dist/integrations/sessions/evidence-recall.d.ts +40 -2
- package/dist/integrations/sessions/evidence-recall.d.ts.map +1 -1
- package/dist/integrations/sessions/evidence-recall.js +41 -97
- package/dist/integrations/sessions/evidence-recall.js.map +1 -1
- package/dist/integrations/sessions/store.d.ts +153 -85
- package/dist/integrations/sessions/store.d.ts.map +1 -1
- package/dist/integrations/sessions/store.js +414 -484
- package/dist/integrations/sessions/store.js.map +1 -1
- package/dist/integrations/sessions/task-context.d.ts +15 -2
- package/dist/integrations/sessions/task-context.d.ts.map +1 -1
- package/dist/integrations/sessions/task-context.js +34 -11
- package/dist/integrations/sessions/task-context.js.map +1 -1
- package/dist/integrations/sessions/transcript-export.d.ts +13 -6
- package/dist/integrations/sessions/transcript-export.d.ts.map +1 -1
- package/dist/integrations/sessions/transcript-export.js +148 -263
- package/dist/integrations/sessions/transcript-export.js.map +1 -1
- package/dist/integrations/state/home.d.ts +4 -16
- package/dist/integrations/state/home.d.ts.map +1 -1
- package/dist/integrations/state/home.js +6 -42
- package/dist/integrations/state/home.js.map +1 -1
- package/dist/integrations/state/project.d.ts +6 -7
- package/dist/integrations/state/project.d.ts.map +1 -1
- package/dist/integrations/state/project.js +7 -9
- package/dist/integrations/state/project.js.map +1 -1
- package/dist/integrations/state/report.d.ts +27 -41
- package/dist/integrations/state/report.d.ts.map +1 -1
- package/dist/integrations/state/report.js +172 -464
- package/dist/integrations/state/report.js.map +1 -1
- package/dist/integrations/state/retention.d.ts +18 -0
- package/dist/integrations/state/retention.d.ts.map +1 -0
- package/dist/integrations/state/retention.js +18 -0
- package/dist/integrations/state/retention.js.map +1 -0
- package/dist/integrations/subagents/activity.d.ts +36 -31
- package/dist/integrations/subagents/activity.d.ts.map +1 -1
- package/dist/integrations/subagents/activity.js +27 -23
- package/dist/integrations/subagents/activity.js.map +1 -1
- package/dist/integrations/subagents/batches.d.ts +91 -0
- package/dist/integrations/subagents/batches.d.ts.map +1 -0
- package/dist/integrations/subagents/batches.js +172 -0
- package/dist/integrations/subagents/batches.js.map +1 -0
- package/dist/integrations/subagents/parent.d.ts +26 -11
- package/dist/integrations/subagents/parent.d.ts.map +1 -1
- package/dist/integrations/subagents/parent.js +13 -28
- package/dist/integrations/subagents/parent.js.map +1 -1
- package/dist/integrations/subagents/policy.d.ts +2 -2
- package/dist/integrations/subagents/policy.d.ts.map +1 -1
- package/dist/integrations/subagents/policy.js +2 -2
- package/dist/integrations/subagents/policy.js.map +1 -1
- package/dist/integrations/subagents/replay.d.ts +91 -26
- package/dist/integrations/subagents/replay.d.ts.map +1 -1
- package/dist/integrations/subagents/replay.js +216 -114
- package/dist/integrations/subagents/replay.js.map +1 -1
- package/dist/integrations/subagents/runtime.d.ts +50 -26
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +132 -126
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/integrations/subagents/saved-agents.d.ts +57 -0
- package/dist/integrations/subagents/saved-agents.d.ts.map +1 -0
- package/dist/integrations/subagents/saved-agents.js +102 -0
- package/dist/integrations/subagents/saved-agents.js.map +1 -0
- package/dist/integrations/subagents/slash.d.ts +55 -0
- package/dist/integrations/subagents/slash.d.ts.map +1 -0
- package/dist/integrations/subagents/slash.js +45 -0
- package/dist/integrations/subagents/slash.js.map +1 -0
- package/dist/integrations/telemetry/session-export.d.ts +6 -6
- package/dist/integrations/telemetry/session-export.d.ts.map +1 -1
- package/dist/integrations/telemetry/session-export.js +2 -2
- package/dist/integrations/web/search.d.ts +1 -1
- package/dist/logging.d.ts +5 -5
- package/dist/logging.js +6 -6
- package/dist/memory/presentation.d.ts +11 -0
- package/dist/memory/presentation.d.ts.map +1 -1
- package/dist/memory/presentation.js +25 -0
- package/dist/memory/presentation.js.map +1 -1
- package/dist/memory/store.d.ts +13 -7
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +37 -11
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/typed.d.ts +116 -0
- package/dist/memory/typed.d.ts.map +1 -0
- package/dist/memory/typed.js +336 -0
- package/dist/memory/typed.js.map +1 -0
- package/dist/permissions/checks.d.ts +1 -1
- package/dist/permissions/checks.js +1 -1
- package/dist/permissions/headless-trust.d.ts +4 -4
- package/dist/permissions/headless-trust.d.ts.map +1 -1
- package/dist/permissions/headless-trust.js +6 -6
- package/dist/permissions/headless-trust.js.map +1 -1
- package/dist/permissions/mode.d.ts +4 -4
- package/dist/permissions/mode.js +1 -1
- package/dist/permissions/rules.d.ts +1 -1
- package/dist/permissions/rules.js +1 -1
- package/dist/termination.d.ts +75 -0
- package/dist/termination.d.ts.map +1 -0
- package/dist/termination.js +143 -0
- package/dist/termination.js.map +1 -0
- package/dist/tui/AgentExplorer.d.ts +2 -2
- package/dist/tui/AgentExplorer.js +4 -4
- package/dist/tui/AgentExplorer.js.map +1 -1
- package/dist/tui/App.d.ts +9 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +456 -307
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/LiveActivity.d.ts +1 -1
- package/dist/tui/LiveActivity.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts +8 -0
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +25 -2
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/Picker.js +1 -1
- package/dist/tui/agent.d.ts +211 -135
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +535 -316
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/conversation-evidence-view.js +3 -3
- package/dist/tui/conversation-evidence-view.js.map +1 -1
- package/dist/tui/conversation-history.d.ts +4 -21
- package/dist/tui/conversation-history.d.ts.map +1 -1
- package/dist/tui/conversation-history.js +4 -28
- package/dist/tui/conversation-history.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +27 -1
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/log-pane.js +1 -1
- package/dist/tui/markdownParser.d.ts +1 -1
- package/dist/tui/markdownParser.js +1 -1
- package/dist/tui/permission-review.d.ts +16 -0
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +25 -2
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +30 -41
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +75 -66
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/status-panel-layout.js +1 -1
- package/dist/tui/status-panel-layout.js.map +1 -1
- package/dist/tui/{run-interruption.d.ts → turn-interruption.d.ts} +4 -4
- package/dist/tui/turn-interruption.d.ts.map +1 -0
- package/dist/tui/{run-interruption.js → turn-interruption.js} +20 -20
- package/dist/tui/turn-interruption.js.map +1 -0
- package/dist/tui/turn-limits-settings.d.ts +39 -0
- package/dist/tui/turn-limits-settings.d.ts.map +1 -0
- package/dist/tui/{run-limits-settings.js → turn-limits-settings.js} +18 -18
- package/dist/tui/turn-limits-settings.js.map +1 -0
- package/dist/tui/types.d.ts +3 -3
- package/dist/tui/types.d.ts.map +1 -1
- package/dist/user-commands/store.d.ts +4 -4
- package/dist/user-commands/store.js +5 -5
- package/dist/user-commands/store.js.map +1 -1
- package/package.json +6 -6
- package/dist/commands/run-flags.d.ts.map +0 -1
- package/dist/commands/run-flags.js.map +0 -1
- package/dist/commands/run-stream.d.ts +0 -65
- package/dist/commands/run-stream.d.ts.map +0 -1
- package/dist/commands/run-stream.js +0 -603
- package/dist/commands/run-stream.js.map +0 -1
- package/dist/commands/run.d.ts +0 -39
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands/run.js +0 -533
- package/dist/commands/run.js.map +0 -1
- package/dist/config/run-limits.d.ts +0 -11
- package/dist/config/run-limits.d.ts.map +0 -1
- package/dist/config/run-limits.js +0 -64
- package/dist/config/run-limits.js.map +0 -1
- package/dist/integrations/sessions/compaction-evidence.d.ts +0 -5
- package/dist/integrations/sessions/compaction-evidence.d.ts.map +0 -1
- package/dist/integrations/sessions/compaction-evidence.js +0 -59
- package/dist/integrations/sessions/compaction-evidence.js.map +0 -1
- package/dist/integrations/sessions/database.d.ts +0 -5
- package/dist/integrations/sessions/database.d.ts.map +0 -1
- package/dist/integrations/sessions/database.js +0 -34
- package/dist/integrations/sessions/database.js.map +0 -1
- package/dist/integrations/sessions/paths.d.ts +0 -7
- package/dist/integrations/sessions/paths.d.ts.map +0 -1
- package/dist/integrations/sessions/paths.js +0 -14
- package/dist/integrations/sessions/paths.js.map +0 -1
- package/dist/integrations/sessions/run-discovery.d.ts +0 -24
- package/dist/integrations/sessions/run-discovery.d.ts.map +0 -1
- package/dist/integrations/sessions/run-discovery.js +0 -142
- package/dist/integrations/sessions/run-discovery.js.map +0 -1
- package/dist/integrations/sessions/turn-evidence.d.ts +0 -129
- package/dist/integrations/sessions/turn-evidence.d.ts.map +0 -1
- package/dist/integrations/sessions/turn-evidence.js +0 -517
- package/dist/integrations/sessions/turn-evidence.js.map +0 -1
- package/dist/integrations/subagents/history.d.ts +0 -26
- package/dist/integrations/subagents/history.d.ts.map +0 -1
- package/dist/integrations/subagents/history.js +0 -76
- package/dist/integrations/subagents/history.js.map +0 -1
- package/dist/integrations/subagents/runs.d.ts +0 -72
- package/dist/integrations/subagents/runs.d.ts.map +0 -1
- package/dist/integrations/subagents/runs.js +0 -155
- package/dist/integrations/subagents/runs.js.map +0 -1
- package/dist/tui/run-interruption.d.ts.map +0 -1
- package/dist/tui/run-interruption.js.map +0 -1
- package/dist/tui/run-limits-settings.d.ts +0 -39
- package/dist/tui/run-limits-settings.d.ts.map +0 -1
- package/dist/tui/run-limits-settings.js.map +0 -1
|
@@ -1,67 +1,52 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
1
|
import { constants } from 'node:fs';
|
|
3
2
|
import { lstat, open, opendir, realpath } from 'node:fs/promises';
|
|
4
3
|
import { homedir } from 'node:os';
|
|
5
|
-
import { basename,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { readIdentity } from './identity.js';
|
|
9
|
-
import { findCliProject } from './project.js';
|
|
4
|
+
import { basename, join, resolve, sep } from 'node:path';
|
|
5
|
+
import { hashedSlugForCwd, isEntityId, slugForCwd } from '@namzu/sdk';
|
|
6
|
+
import { cliProjectRoot } from './project.js';
|
|
10
7
|
const MAX_METADATA_BYTES = 4 * 1024 * 1024;
|
|
11
|
-
const MAX_ORIGIN_BYTES = 64 * 1024;
|
|
12
8
|
const MAX_REPORTED_ISSUES = 100;
|
|
13
9
|
const MAX_ENTRIES = 100_000;
|
|
14
|
-
const AUTHORED_TOP_LEVEL = new Set(['commands', 'plugins', 'skills']);
|
|
10
|
+
const AUTHORED_TOP_LEVEL = new Set(['agents', 'commands', 'plugins', 'skills', 'MEMORY.md']);
|
|
15
11
|
const CONFIG_TOP_LEVEL = new Set([
|
|
16
12
|
'config.yaml',
|
|
17
13
|
'credentials.json',
|
|
14
|
+
'identity.json',
|
|
18
15
|
'preferences.json',
|
|
19
16
|
'plugin-settings',
|
|
20
17
|
'trust.json',
|
|
21
18
|
]);
|
|
22
|
-
const RUNTIME_TOP_LEVEL = new Set([
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'memory',
|
|
30
|
-
'projects',
|
|
19
|
+
const RUNTIME_TOP_LEVEL = new Set(['attachments', 'cli', 'cli.json', 'projects', 'index.sqlite']);
|
|
20
|
+
/**
|
|
21
|
+
* The layout before sessions became logs (spec §3.4). Nothing reads these any
|
|
22
|
+
* more; the report names them so a person can decide what to keep. It never
|
|
23
|
+
* writes or removes them.
|
|
24
|
+
*/
|
|
25
|
+
const LEGACY_TOP_LEVEL = new Set([
|
|
31
26
|
'state',
|
|
32
27
|
'sessions',
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'checkpoints',
|
|
28
|
+
'titles.json',
|
|
29
|
+
'desktop-sessions.json',
|
|
36
30
|
'delegation-history',
|
|
31
|
+
'checkpoints',
|
|
37
32
|
'tenants',
|
|
38
|
-
'
|
|
33
|
+
'goals',
|
|
34
|
+
'feedback',
|
|
35
|
+
'learning',
|
|
36
|
+
'residents',
|
|
39
37
|
'worktrees',
|
|
38
|
+
'memory',
|
|
40
39
|
]);
|
|
41
40
|
const CONTROL_TOP_LEVEL = new Set(['.migration']);
|
|
42
|
-
const PRIVATE_BOUNDARIES = [
|
|
43
|
-
'learning',
|
|
44
|
-
'attachments',
|
|
45
|
-
'goals',
|
|
46
|
-
'memory',
|
|
47
|
-
'plugin-settings',
|
|
48
|
-
'projects',
|
|
49
|
-
'state',
|
|
50
|
-
'sessions',
|
|
51
|
-
'cli',
|
|
52
|
-
'residents',
|
|
53
|
-
'checkpoints',
|
|
54
|
-
'delegation-history',
|
|
55
|
-
'tenants',
|
|
56
|
-
];
|
|
41
|
+
const PRIVATE_BOUNDARIES = ['attachments', 'plugin-settings', 'projects'];
|
|
57
42
|
class EntryChangedError extends Error {
|
|
58
43
|
}
|
|
59
44
|
/**
|
|
60
45
|
* Inspect Namzu's filesystem estate without changing it.
|
|
61
46
|
*
|
|
62
|
-
* Collection uses direct bounded filesystem reads
|
|
63
|
-
*
|
|
64
|
-
*
|
|
47
|
+
* Collection uses direct bounded filesystem reads only: no store, no index,
|
|
48
|
+
* no lease. A report about an untouched tree leaves that tree untouched, the
|
|
49
|
+
* old layout included, which it names as `legacy` and never opens.
|
|
65
50
|
*/
|
|
66
51
|
export async function inspectNamzuState(options = {}) {
|
|
67
52
|
const cwd = await canonicalBase(options.cwd ?? process.cwd());
|
|
@@ -96,12 +81,10 @@ export async function inspectNamzuState(options = {}) {
|
|
|
96
81
|
}), { roots: 0, files: 0, logicalBytes: 0 });
|
|
97
82
|
const projectConfigPath = join(cwd, 'namzu.config.json');
|
|
98
83
|
const projectConfig = await inspectOneRegularFile(projectConfigPath);
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const centralBinding = await inspectCentralProjectBinding(userRoot, cwd);
|
|
102
|
-
const projectBinding = combineProjectBindings(localBinding, centralBinding, projectRoot, userRoot);
|
|
84
|
+
const userCollection = collections.get(userRoot);
|
|
85
|
+
const projectBinding = await inspectProjectBinding(userCollection, cwd);
|
|
103
86
|
return {
|
|
104
|
-
version:
|
|
87
|
+
version: 2,
|
|
105
88
|
readOnly: true,
|
|
106
89
|
snapshot: {
|
|
107
90
|
consistency: 'best-effort-unlocked',
|
|
@@ -121,62 +104,12 @@ export async function inspectNamzuState(options = {}) {
|
|
|
121
104
|
projectBinding,
|
|
122
105
|
};
|
|
123
106
|
}
|
|
124
|
-
function combineProjectBindings(local, central, localRoot, centralRoot) {
|
|
125
|
-
if (localRoot === centralRoot) {
|
|
126
|
-
return central.status === 'uninitialized' ? local : central;
|
|
127
|
-
}
|
|
128
|
-
if (local.status === 'uninitialized')
|
|
129
|
-
return central;
|
|
130
|
-
if (central.status === 'uninitialized')
|
|
131
|
-
return local;
|
|
132
|
-
if (central.status === 'unknown')
|
|
133
|
-
return central;
|
|
134
|
-
if (central.status === 'bound') {
|
|
135
|
-
return {
|
|
136
|
-
status: 'split',
|
|
137
|
-
detail: `Project-local state at ${localRoot} and central state at ${centralRoot} both claim this workspace. The CLI uses central state; this inventory does not merge or migrate the local history.`,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
return local;
|
|
141
|
-
}
|
|
142
|
-
async function inspectCentralProjectBinding(centralRoot, canonicalCwd) {
|
|
143
|
-
try {
|
|
144
|
-
// Read-only: an inventory reports what is there and mints nothing.
|
|
145
|
-
const identity = readIdentity(centralRoot);
|
|
146
|
-
if (!identity || !existsSync(sessionDatabasePath(centralRoot))) {
|
|
147
|
-
return {
|
|
148
|
-
status: 'uninitialized',
|
|
149
|
-
detail: 'No identity has been minted in this application home, so no Project can be bound.',
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
const project = await findCliProject(sessionStore(centralRoot, true), canonicalCwd, identity.tenantId);
|
|
153
|
-
if (!project) {
|
|
154
|
-
return {
|
|
155
|
-
status: 'uninitialized',
|
|
156
|
-
detail: 'No central Project is bound to this working directory or its checkout root.',
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
status: 'bound',
|
|
161
|
-
projectId: project.id,
|
|
162
|
-
detail: `Central Project ${project.id} is bound to ${project.rootPath}.`,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
return {
|
|
167
|
-
status: 'unknown',
|
|
168
|
-
detail: `The central Project binding could not be read safely: ${errorMessage(error)}`,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
107
|
async function canonicalBase(path) {
|
|
173
108
|
const absolute = resolve(path);
|
|
174
109
|
try {
|
|
175
110
|
return await realpath(absolute);
|
|
176
111
|
}
|
|
177
|
-
catch
|
|
178
|
-
if (error.code === 'ENOENT')
|
|
179
|
-
return absolute;
|
|
112
|
+
catch {
|
|
180
113
|
return absolute;
|
|
181
114
|
}
|
|
182
115
|
}
|
|
@@ -205,13 +138,7 @@ async function collectRoot(root, entryLimit) {
|
|
|
205
138
|
return { root, exists: false, entries: [], issues: [], omittedIssues: 0 };
|
|
206
139
|
}
|
|
207
140
|
addIssue(sink, root, error);
|
|
208
|
-
return {
|
|
209
|
-
root,
|
|
210
|
-
exists: true,
|
|
211
|
-
entries: [],
|
|
212
|
-
issues: sink.issues,
|
|
213
|
-
omittedIssues: sink.omitted,
|
|
214
|
-
};
|
|
141
|
+
return { root, exists: true, entries: [], issues: sink.issues, omittedIssues: sink.omitted };
|
|
215
142
|
}
|
|
216
143
|
if (rootStat.isSymbolicLink()) {
|
|
217
144
|
pushIssue(sink, {
|
|
@@ -254,17 +181,20 @@ async function collectRoot(root, entryLimit) {
|
|
|
254
181
|
omittedIssues: sink.omitted,
|
|
255
182
|
};
|
|
256
183
|
}
|
|
184
|
+
function entryKind(stat) {
|
|
185
|
+
return stat.isDirectory()
|
|
186
|
+
? 'directory'
|
|
187
|
+
: stat.isFile()
|
|
188
|
+
? 'file'
|
|
189
|
+
: stat.isSymbolicLink()
|
|
190
|
+
? 'symlink'
|
|
191
|
+
: 'other';
|
|
192
|
+
}
|
|
257
193
|
function rootEntry(root, stat) {
|
|
258
194
|
return {
|
|
259
195
|
absolute: root,
|
|
260
196
|
relative: '.',
|
|
261
|
-
kind: stat
|
|
262
|
-
? 'directory'
|
|
263
|
-
: stat.isFile()
|
|
264
|
-
? 'file'
|
|
265
|
-
: stat.isSymbolicLink()
|
|
266
|
-
? 'symlink'
|
|
267
|
-
: 'other',
|
|
197
|
+
kind: entryKind(stat),
|
|
268
198
|
size: stat.isFile() ? stat.size : 0,
|
|
269
199
|
device: stat.dev,
|
|
270
200
|
inode: stat.ino,
|
|
@@ -300,13 +230,7 @@ async function walk(root, relativeDir, entries, sink, entryLimit) {
|
|
|
300
230
|
const entry = {
|
|
301
231
|
absolute,
|
|
302
232
|
relative: normalizeRelative(relativePath),
|
|
303
|
-
kind: stat
|
|
304
|
-
? 'directory'
|
|
305
|
-
: stat.isFile()
|
|
306
|
-
? 'file'
|
|
307
|
-
: stat.isSymbolicLink()
|
|
308
|
-
? 'symlink'
|
|
309
|
-
: 'other',
|
|
233
|
+
kind: entryKind(stat),
|
|
310
234
|
size: stat.isFile() ? stat.size : 0,
|
|
311
235
|
device: stat.dev,
|
|
312
236
|
inode: stat.ino,
|
|
@@ -366,6 +290,7 @@ async function projectRootReport(collection, roles, privacyContext) {
|
|
|
366
290
|
authored: emptyMeasure(),
|
|
367
291
|
configuration: emptyMeasure(),
|
|
368
292
|
runtime: emptyMeasure(),
|
|
293
|
+
legacy: emptyMeasure(),
|
|
369
294
|
control: emptyMeasure(),
|
|
370
295
|
transient: emptyMeasure(),
|
|
371
296
|
unknown: emptyMeasure(),
|
|
@@ -388,7 +313,8 @@ async function projectRootReport(collection, roles, privacyContext) {
|
|
|
388
313
|
issues: [...collection.issues],
|
|
389
314
|
omitted: collection.omittedIssues,
|
|
390
315
|
};
|
|
391
|
-
const inventory =
|
|
316
|
+
const inventory = inventoryOf(collection);
|
|
317
|
+
const legacy = legacyOf(collection);
|
|
392
318
|
const privacy = privacyOf(collection, privacyContext, analysisSink);
|
|
393
319
|
return {
|
|
394
320
|
path: collection.root,
|
|
@@ -400,6 +326,7 @@ async function projectRootReport(collection, roles, privacyContext) {
|
|
|
400
326
|
logicalBytes,
|
|
401
327
|
categories,
|
|
402
328
|
inventory,
|
|
329
|
+
legacy,
|
|
403
330
|
privacy,
|
|
404
331
|
issues: analysisSink.issues,
|
|
405
332
|
omittedIssues: analysisSink.omitted,
|
|
@@ -408,79 +335,106 @@ async function projectRootReport(collection, roles, privacyContext) {
|
|
|
408
335
|
function emptyMeasure() {
|
|
409
336
|
return { files: 0, logicalBytes: 0 };
|
|
410
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* The top-level name, or `projects/<name>` when `<name>` is a UUID: a slug
|
|
340
|
+
* never looks like one (a POSIX path's slug starts with `-`, a Windows
|
|
341
|
+
* path's with `C-`), so only the old `projects/<projectId>/` tree matches.
|
|
342
|
+
*/
|
|
343
|
+
function legacyKey(path) {
|
|
344
|
+
const [top, second] = path.split('/');
|
|
345
|
+
if (top === undefined)
|
|
346
|
+
return undefined;
|
|
347
|
+
if (LEGACY_TOP_LEVEL.has(top))
|
|
348
|
+
return { key: top, kind: 'top-level' };
|
|
349
|
+
if (top === 'projects' && second !== undefined && isEntityId(second, 'project')) {
|
|
350
|
+
return { key: `projects/${second}`, kind: 'uuid-project' };
|
|
351
|
+
}
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
411
354
|
function categoryOf(path) {
|
|
412
355
|
const top = path.split('/')[0] ?? path;
|
|
413
356
|
const name = basename(path);
|
|
357
|
+
if (legacyKey(path))
|
|
358
|
+
return 'legacy';
|
|
414
359
|
if (name.endsWith('.lock') ||
|
|
415
360
|
name.includes('.tmp.') ||
|
|
416
|
-
name.
|
|
417
|
-
|
|
361
|
+
name.includes('.tmp-') ||
|
|
362
|
+
name.endsWith('.candidate')) {
|
|
418
363
|
return 'transient';
|
|
419
364
|
}
|
|
420
365
|
if (AUTHORED_TOP_LEVEL.has(top))
|
|
421
366
|
return 'authored';
|
|
422
367
|
if (CONFIG_TOP_LEVEL.has(top))
|
|
423
368
|
return 'configuration';
|
|
424
|
-
if (RUNTIME_TOP_LEVEL.has(top))
|
|
369
|
+
if (RUNTIME_TOP_LEVEL.has(top) || /^index\.sqlite(-wal|-shm|-journal)?$/.test(top))
|
|
425
370
|
return 'runtime';
|
|
426
371
|
if (CONTROL_TOP_LEVEL.has(top))
|
|
427
372
|
return 'control';
|
|
428
373
|
return 'unknown';
|
|
429
374
|
}
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const sessionDirs = directories.filter((entry) => isCanonicalSessionDir(entry.relative));
|
|
436
|
-
const runDirs = directories.filter((entry) => isCanonicalRunDir(entry.relative));
|
|
437
|
-
const validSessions = new Map();
|
|
438
|
-
let candidateAnalysisComplete = true;
|
|
439
|
-
let identity = null;
|
|
440
|
-
try {
|
|
441
|
-
identity = readIdentity(collection.root);
|
|
442
|
-
}
|
|
443
|
-
catch (error) {
|
|
444
|
-
metadataIssue(sink, 'identity.json', error);
|
|
445
|
-
candidateAnalysisComplete = false;
|
|
446
|
-
}
|
|
447
|
-
const database = identity && existsSync(sessionDatabasePath(collection.root))
|
|
448
|
-
? sessionStore(collection.root, true)
|
|
449
|
-
: undefined;
|
|
450
|
-
for (const directory of sessionDirs) {
|
|
451
|
-
if (directory.relative.startsWith('sessions/') && database && identity) {
|
|
452
|
-
try {
|
|
453
|
-
const id = asSessionId(basename(directory.relative));
|
|
454
|
-
if (await database.getSession(id, identity.tenantId))
|
|
455
|
-
validSessions.set(id, directory);
|
|
456
|
-
}
|
|
457
|
-
catch (error) {
|
|
458
|
-
metadataIssue(sink, directory.relative, error);
|
|
459
|
-
}
|
|
375
|
+
function legacyOf(collection) {
|
|
376
|
+
const found = new Map();
|
|
377
|
+
for (const entry of collection.entries) {
|
|
378
|
+
const legacy = legacyKey(entry.relative);
|
|
379
|
+
if (!legacy)
|
|
460
380
|
continue;
|
|
381
|
+
const current = found.get(legacy.key) ?? { kind: legacy.kind, files: 0, bytes: 0 };
|
|
382
|
+
if (entry.kind === 'file') {
|
|
383
|
+
current.files += 1;
|
|
384
|
+
current.bytes += entry.size;
|
|
461
385
|
}
|
|
462
|
-
|
|
463
|
-
if (!record)
|
|
464
|
-
continue;
|
|
465
|
-
const parsed = await readJsonRecord(record, sink);
|
|
466
|
-
const expected = basename(directory.relative);
|
|
467
|
-
if (recordId(parsed) === expected)
|
|
468
|
-
validSessions.set(expected, directory);
|
|
469
|
-
else
|
|
470
|
-
candidateAnalysisComplete = false;
|
|
386
|
+
found.set(legacy.key, current);
|
|
471
387
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
388
|
+
return [...found.entries()]
|
|
389
|
+
.map(([path, value]) => ({
|
|
390
|
+
path,
|
|
391
|
+
kind: value.kind,
|
|
392
|
+
files: value.files,
|
|
393
|
+
logicalBytes: value.bytes,
|
|
394
|
+
}))
|
|
395
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
396
|
+
}
|
|
397
|
+
/** `projects/<slug>/…` split into the slug and the rest, for a slug directory only. */
|
|
398
|
+
function inSlugProject(path) {
|
|
399
|
+
const parts = path.split('/');
|
|
400
|
+
if (parts[0] !== 'projects' || parts.length < 3)
|
|
401
|
+
return undefined;
|
|
402
|
+
const slug = parts[1];
|
|
403
|
+
if (isEntityId(slug, 'project') || !/^[A-Za-z0-9-]+$/.test(slug))
|
|
404
|
+
return undefined;
|
|
405
|
+
return { slug, rest: parts.slice(2) };
|
|
406
|
+
}
|
|
407
|
+
function inventoryOf(collection) {
|
|
408
|
+
const files = collection.entries.filter((entry) => entry.kind === 'file');
|
|
409
|
+
const projects = new Set();
|
|
410
|
+
const sessionLogs = [];
|
|
411
|
+
const subagentLogs = [];
|
|
412
|
+
const checkpointFiles = [];
|
|
413
|
+
const toolResultFiles = [];
|
|
414
|
+
for (const entry of files) {
|
|
415
|
+
const located = inSlugProject(entry.relative);
|
|
416
|
+
if (!located)
|
|
476
417
|
continue;
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
418
|
+
const { slug, rest } = located;
|
|
419
|
+
const file = rest.at(-1) ?? '';
|
|
420
|
+
if (rest.length === 1 && file === 'project.json')
|
|
421
|
+
projects.add(slug);
|
|
422
|
+
else if (rest.length === 1 &&
|
|
423
|
+
file.endsWith('.jsonl') &&
|
|
424
|
+
isEntityId(file.slice(0, -6), 'session'))
|
|
425
|
+
sessionLogs.push(entry);
|
|
426
|
+
else if (rest.at(-2) === 'subagents' &&
|
|
427
|
+
file.endsWith('.jsonl') &&
|
|
428
|
+
isEntityId(file.slice(0, -6), 'session'))
|
|
429
|
+
subagentLogs.push(entry);
|
|
430
|
+
else if (rest.at(-2) === 'checkpoints' &&
|
|
431
|
+
file.endsWith('.json') &&
|
|
432
|
+
isEntityId(file.slice(0, -5), 'checkpoint'))
|
|
433
|
+
checkpointFiles.push(entry);
|
|
434
|
+
else if (rest.at(-2) === 'tool-results')
|
|
435
|
+
toolResultFiles.push(entry);
|
|
436
|
+
}
|
|
437
|
+
const attachmentFiles = files.filter((entry) => entry.relative.startsWith('attachments/'));
|
|
484
438
|
const attachmentKeys = new Map();
|
|
485
439
|
for (const entry of attachmentFiles) {
|
|
486
440
|
const suffix = entry.relative.endsWith('.bin')
|
|
@@ -498,29 +452,12 @@ async function inventoryOf(collection, sink) {
|
|
|
498
452
|
pair.type = entry;
|
|
499
453
|
attachmentKeys.set(key, pair);
|
|
500
454
|
}
|
|
501
|
-
// SQLite messages and links cannot be classified by inspecting files alone.
|
|
502
|
-
const candidates = database
|
|
503
|
-
? { complete: false, directories: [] }
|
|
504
|
-
: await originOnlyCandidates(files, validSessions, runDirs, sink);
|
|
505
|
-
candidateAnalysisComplete &&= candidates.complete;
|
|
506
455
|
return {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
invalidOrMissingRecords: sessionDirs.length - validSessions.size,
|
|
511
|
-
},
|
|
512
|
-
originOnlySessionCandidates: {
|
|
513
|
-
...measureDirectories(candidates.directories, collection.entries),
|
|
514
|
-
complete: candidateAnalysisComplete,
|
|
515
|
-
limitation: 'Candidates have only a new-conversation origin, no messages, runs, goal, title, desktop mapping, fork reference, or sub-session link. They are not declared safe to delete because no writer lease was acquired.',
|
|
516
|
-
},
|
|
517
|
-
runs: {
|
|
518
|
-
...measureDirectories(validRuns, collection.entries),
|
|
519
|
-
directories: runDirs.length,
|
|
520
|
-
invalidOrMissingRecords: runDirs.length - validRuns.length,
|
|
521
|
-
},
|
|
456
|
+
projects: projects.size,
|
|
457
|
+
sessionLogs: measureFiles(sessionLogs),
|
|
458
|
+
subagentLogs: measureFiles(subagentLogs),
|
|
522
459
|
checkpointFiles: measureFiles(checkpointFiles),
|
|
523
|
-
|
|
460
|
+
toolResultFiles: measureFiles(toolResultFiles),
|
|
524
461
|
attachments: {
|
|
525
462
|
...measureFiles(attachmentFiles),
|
|
526
463
|
pairs: [...attachmentKeys.values()].filter((pair) => pair.data && pair.type).length,
|
|
@@ -531,157 +468,12 @@ async function inventoryOf(collection, sink) {
|
|
|
531
468
|
},
|
|
532
469
|
};
|
|
533
470
|
}
|
|
534
|
-
function isCanonicalSessionDir(path) {
|
|
535
|
-
const parts = path.split('/');
|
|
536
|
-
return ((parts.length === 2 && parts[0] === 'sessions' && isEntityId(parts[1], 'session')) ||
|
|
537
|
-
(parts.length === 4 &&
|
|
538
|
-
parts[0] === 'projects' &&
|
|
539
|
-
isEntityId(parts[1], 'project') &&
|
|
540
|
-
parts[2] === 'sessions' &&
|
|
541
|
-
isEntityId(parts[3], 'session')));
|
|
542
|
-
}
|
|
543
|
-
function isCanonicalRunDir(path) {
|
|
544
|
-
const parts = path.split('/');
|
|
545
|
-
const prefix = parts[0] === 'sessions' ? 2 : 4;
|
|
546
|
-
return ((parts.length === prefix + 2 ||
|
|
547
|
-
(parts.length === prefix + 4 &&
|
|
548
|
-
parts[prefix + 2] === 'children' &&
|
|
549
|
-
isEntityId(parts[prefix + 3], 'run'))) &&
|
|
550
|
-
isCanonicalSessionDir(parts.slice(0, prefix).join('/')) &&
|
|
551
|
-
parts[prefix] === 'runs' &&
|
|
552
|
-
isEntityId(parts[prefix + 1], 'run'));
|
|
553
|
-
}
|
|
554
|
-
function isCanonicalCheckpointFile(path) {
|
|
555
|
-
const parts = path.split('/');
|
|
556
|
-
const file = parts.at(-1);
|
|
557
|
-
return (file?.endsWith('.json') === true &&
|
|
558
|
-
isEntityId(file.slice(0, -5), 'checkpoint') &&
|
|
559
|
-
parts.at(-2) === 'checkpoints' &&
|
|
560
|
-
isCanonicalRunDir(parts.slice(0, -2).join('/')));
|
|
561
|
-
}
|
|
562
|
-
function isCanonicalEmergencyFile(path) {
|
|
563
|
-
const parts = path.split('/');
|
|
564
|
-
const prefix = parts[0] === 'sessions' ? 2 : 4;
|
|
565
|
-
const file = parts[prefix + 2];
|
|
566
|
-
return (parts.length === prefix + 3 &&
|
|
567
|
-
isCanonicalSessionDir(parts.slice(0, prefix).join('/')) &&
|
|
568
|
-
parts[prefix] === 'runs' &&
|
|
569
|
-
parts[prefix + 1] === 'emergency' &&
|
|
570
|
-
file?.endsWith('.json') === true &&
|
|
571
|
-
isEntityId(file.slice(0, -5), 'run'));
|
|
572
|
-
}
|
|
573
|
-
async function originOnlyCandidates(files, sessions, runDirs, sink) {
|
|
574
|
-
let complete = true;
|
|
575
|
-
const referenced = new Set();
|
|
576
|
-
const originKinds = new Map();
|
|
577
|
-
for (const [sessionId, directory] of sessions) {
|
|
578
|
-
const evidence = files.get(`${directory.relative}/turns.jsonl`);
|
|
579
|
-
if (!evidence)
|
|
580
|
-
continue;
|
|
581
|
-
if (evidence.size > MAX_ORIGIN_BYTES) {
|
|
582
|
-
complete = false;
|
|
583
|
-
pushIssue(sink, {
|
|
584
|
-
code: 'inspection_skipped',
|
|
585
|
-
path: evidence.relative,
|
|
586
|
-
detail: `Turn evidence exceeds the ${MAX_ORIGIN_BYTES}-byte origin-inspection cap; its raw bytes remain counted.`,
|
|
587
|
-
});
|
|
588
|
-
continue;
|
|
589
|
-
}
|
|
590
|
-
try {
|
|
591
|
-
const raw = await readBoundedRegularFile(evidence, MAX_ORIGIN_BYTES);
|
|
592
|
-
const lines = raw.split('\n').filter((line) => line.length > 0);
|
|
593
|
-
if (lines.length !== 1)
|
|
594
|
-
continue;
|
|
595
|
-
const parsed = JSON.parse(lines[0] ?? '');
|
|
596
|
-
if (parsed.type !== 'conversation_started' || parsed.sessionId !== sessionId)
|
|
597
|
-
continue;
|
|
598
|
-
const origin = parsed.origin;
|
|
599
|
-
if (typeof origin !== 'object' || origin === null)
|
|
600
|
-
continue;
|
|
601
|
-
const originRecord = origin;
|
|
602
|
-
if (typeof originRecord.kind === 'string')
|
|
603
|
-
originKinds.set(sessionId, originRecord.kind);
|
|
604
|
-
if (typeof originRecord.sourceSessionId === 'string') {
|
|
605
|
-
referenced.add(originRecord.sourceSessionId);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
catch (error) {
|
|
609
|
-
complete = false;
|
|
610
|
-
metadataIssue(sink, evidence.relative, error);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
const titles = await readStringKeys(files.get('titles.json'), sink);
|
|
614
|
-
const desktopTargets = await readStringValues(files.get('desktop-sessions.json'), sink);
|
|
615
|
-
if (titles === null || desktopTargets === null)
|
|
616
|
-
complete = false;
|
|
617
|
-
for (const id of titles ?? [])
|
|
618
|
-
referenced.add(id);
|
|
619
|
-
for (const id of desktopTargets ?? [])
|
|
620
|
-
referenced.add(id);
|
|
621
|
-
for (const entry of files.values()) {
|
|
622
|
-
if (!entry.relative.endsWith('/subsession.json'))
|
|
623
|
-
continue;
|
|
624
|
-
const parsed = await readJsonRecord(entry, sink);
|
|
625
|
-
if (!parsed) {
|
|
626
|
-
complete = false;
|
|
627
|
-
continue;
|
|
628
|
-
}
|
|
629
|
-
for (const key of ['parentSessionId', 'childSessionId']) {
|
|
630
|
-
const id = parsed[key];
|
|
631
|
-
if (typeof id === 'string')
|
|
632
|
-
referenced.add(id);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
if (!complete)
|
|
636
|
-
return { complete: false, directories: [] };
|
|
637
|
-
const candidates = [];
|
|
638
|
-
for (const [sessionId, directory] of sessions) {
|
|
639
|
-
if (originKinds.get(sessionId) !== 'new' || referenced.has(sessionId))
|
|
640
|
-
continue;
|
|
641
|
-
const messages = files.get(`${directory.relative}/messages.jsonl`);
|
|
642
|
-
if (messages && messages.size > 0)
|
|
643
|
-
continue;
|
|
644
|
-
if (runDirs.some((run) => run.relative.startsWith(`${directory.relative}/runs/`)))
|
|
645
|
-
continue;
|
|
646
|
-
if (files.has(`goals/${sessionId}.json`))
|
|
647
|
-
continue;
|
|
648
|
-
candidates.push(directory);
|
|
649
|
-
}
|
|
650
|
-
return { complete: true, directories: candidates };
|
|
651
|
-
}
|
|
652
|
-
async function readStringKeys(entry, sink) {
|
|
653
|
-
if (!entry)
|
|
654
|
-
return [];
|
|
655
|
-
const parsed = await readJsonRecord(entry, sink);
|
|
656
|
-
if (!parsed)
|
|
657
|
-
return null;
|
|
658
|
-
return Object.keys(parsed);
|
|
659
|
-
}
|
|
660
|
-
async function readStringValues(entry, sink) {
|
|
661
|
-
if (!entry)
|
|
662
|
-
return [];
|
|
663
|
-
const parsed = await readJsonRecord(entry, sink);
|
|
664
|
-
if (!parsed)
|
|
665
|
-
return null;
|
|
666
|
-
return Object.values(parsed).filter((value) => typeof value === 'string');
|
|
667
|
-
}
|
|
668
471
|
function measureFiles(entries) {
|
|
669
472
|
return {
|
|
670
473
|
files: entries.length,
|
|
671
474
|
logicalBytes: entries.reduce((sum, entry) => sum + entry.size, 0),
|
|
672
475
|
};
|
|
673
476
|
}
|
|
674
|
-
function measureDirectories(directories, entries) {
|
|
675
|
-
const selected = [...directories];
|
|
676
|
-
let logicalBytes = 0;
|
|
677
|
-
for (const directory of selected) {
|
|
678
|
-
const prefix = `${directory.relative}/`;
|
|
679
|
-
logicalBytes += entries
|
|
680
|
-
.filter((entry) => entry.kind === 'file' && entry.relative.startsWith(prefix))
|
|
681
|
-
.reduce((sum, entry) => sum + entry.size, 0);
|
|
682
|
-
}
|
|
683
|
-
return { files: selected.length, logicalBytes };
|
|
684
|
-
}
|
|
685
477
|
function privacyOf(collection, context, sink) {
|
|
686
478
|
const byRelative = new Map(collection.entries.map((entry) => [entry.relative, entry]));
|
|
687
479
|
const out = [];
|
|
@@ -690,11 +482,7 @@ function privacyOf(collection, context, sink) {
|
|
|
690
482
|
if (!entry)
|
|
691
483
|
continue;
|
|
692
484
|
if (entry.kind !== 'directory') {
|
|
693
|
-
out.push({
|
|
694
|
-
path: segment,
|
|
695
|
-
status: 'insecure',
|
|
696
|
-
detail: 'Boundary is not a directory.',
|
|
697
|
-
});
|
|
485
|
+
out.push({ path: segment, status: 'insecure', detail: 'Boundary is not a directory.' });
|
|
698
486
|
continue;
|
|
699
487
|
}
|
|
700
488
|
if (context.platform === 'win32' || context.uid === undefined) {
|
|
@@ -714,11 +502,7 @@ function privacyOf(collection, context, sink) {
|
|
|
714
502
|
path: segment,
|
|
715
503
|
detail: `Boundary is owned by uid ${entry.uid}, current uid is ${context.uid}.`,
|
|
716
504
|
});
|
|
717
|
-
out.push({
|
|
718
|
-
path: segment,
|
|
719
|
-
status: 'insecure',
|
|
720
|
-
detail: 'Boundary owner does not match.',
|
|
721
|
-
});
|
|
505
|
+
out.push({ path: segment, status: 'insecure', detail: 'Boundary owner does not match.' });
|
|
722
506
|
continue;
|
|
723
507
|
}
|
|
724
508
|
const exposed = entry.mode & 0o077;
|
|
@@ -741,144 +525,68 @@ function privacyOf(collection, context, sink) {
|
|
|
741
525
|
function modeText(mode) {
|
|
742
526
|
return (mode & 0o777).toString(8).padStart(3, '0');
|
|
743
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* Which `projects/<slug>/project.json` stands for this working directory's
|
|
530
|
+
* checkout, read without creating anything: the plain slug, or the hashed
|
|
531
|
+
* one when two directories slug alike.
|
|
532
|
+
*/
|
|
744
533
|
async function inspectProjectBinding(collection, canonicalCwd) {
|
|
745
534
|
if (!collection.exists) {
|
|
535
|
+
return { status: 'uninitialized', detail: 'No application home exists yet.' };
|
|
536
|
+
}
|
|
537
|
+
let projectRoot;
|
|
538
|
+
try {
|
|
539
|
+
projectRoot = await canonicalBase(cliProjectRoot(canonicalCwd));
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
746
542
|
return {
|
|
747
|
-
status: '
|
|
748
|
-
detail:
|
|
543
|
+
status: 'unknown',
|
|
544
|
+
detail: `The checkout root could not be resolved: ${errorMessage(error)}`,
|
|
749
545
|
};
|
|
750
546
|
}
|
|
751
547
|
const files = new Map(collection.entries
|
|
752
548
|
.filter((entry) => entry.kind === 'file')
|
|
753
549
|
.map((entry) => [entry.relative, entry]));
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
550
|
+
for (const slug of [slugForCwd(projectRoot), hashedSlugForCwd(projectRoot)]) {
|
|
551
|
+
const entry = files.get(`projects/${slug}/project.json`);
|
|
552
|
+
if (!entry)
|
|
553
|
+
continue;
|
|
554
|
+
let record;
|
|
555
|
+
try {
|
|
556
|
+
const value = JSON.parse(await readBoundedRegularFile(entry, MAX_METADATA_BYTES));
|
|
557
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
558
|
+
return { status: 'corrupt-project', slug, detail: 'project.json is not an object.' };
|
|
761
559
|
}
|
|
762
|
-
|
|
763
|
-
}
|
|
764
|
-
let parsed;
|
|
765
|
-
try {
|
|
766
|
-
const value = JSON.parse(await readBoundedRegularFile(pointer, MAX_METADATA_BYTES));
|
|
767
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
768
|
-
return {
|
|
769
|
-
status: 'invalid-pointer',
|
|
770
|
-
detail: 'cli.json is not an object.',
|
|
771
|
-
};
|
|
560
|
+
record = value;
|
|
772
561
|
}
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
catch (error) {
|
|
776
|
-
return {
|
|
777
|
-
status: 'invalid-pointer',
|
|
778
|
-
detail: `cli.json could not be read as bounded JSON: ${errorMessage(error)}`,
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
const projectId = parsed.projectId;
|
|
782
|
-
if (!isEntityId(projectId, 'project')) {
|
|
783
|
-
return {
|
|
784
|
-
status: 'invalid-pointer',
|
|
785
|
-
detail: 'cli.json has no valid projectId.',
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
const project = files.get(`projects/${projectId}/project.json`);
|
|
789
|
-
if (!project) {
|
|
790
|
-
return {
|
|
791
|
-
status: 'missing-project',
|
|
792
|
-
projectId,
|
|
793
|
-
detail: 'cli.json selects a project record that is not present.',
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
|
-
let record;
|
|
797
|
-
try {
|
|
798
|
-
const value = JSON.parse(await readBoundedRegularFile(project, MAX_METADATA_BYTES));
|
|
799
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
562
|
+
catch (error) {
|
|
800
563
|
return {
|
|
801
564
|
status: 'corrupt-project',
|
|
802
|
-
|
|
803
|
-
detail:
|
|
565
|
+
slug,
|
|
566
|
+
detail: `project.json could not be read as bounded JSON: ${errorMessage(error)}`,
|
|
804
567
|
};
|
|
805
568
|
}
|
|
806
|
-
record
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
status: 'corrupt-project',
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
if (record.id !== projectId) {
|
|
816
|
-
return {
|
|
817
|
-
status: 'corrupt-project',
|
|
818
|
-
projectId,
|
|
819
|
-
detail: 'project.json id does not match the selected directory.',
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
if (typeof record.rootPath !== 'string' || record.rootPath.length === 0) {
|
|
823
|
-
return {
|
|
824
|
-
status: 'legacy-unbound',
|
|
825
|
-
projectId,
|
|
826
|
-
detail: 'Project record predates canonical root binding; cli.json is its only locator.',
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
if (!isAbsolute(record.rootPath)) {
|
|
830
|
-
return {
|
|
831
|
-
status: 'corrupt-project',
|
|
832
|
-
projectId,
|
|
833
|
-
detail: 'project.json rootPath is not absolute.',
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
const recordedRoot = await canonicalBase(record.rootPath);
|
|
837
|
-
if (recordedRoot !== canonicalCwd) {
|
|
569
|
+
if (record.v !== 1 ||
|
|
570
|
+
record.kind !== 'project' ||
|
|
571
|
+
!isEntityId(record.projectId, 'project') ||
|
|
572
|
+
typeof record.cwd !== 'string') {
|
|
573
|
+
return { status: 'corrupt-project', slug, detail: 'project.json is not a project document.' };
|
|
574
|
+
}
|
|
575
|
+
// Two directories that slug alike: the plain slug belongs to the other one.
|
|
576
|
+
if (record.cwd !== projectRoot)
|
|
577
|
+
continue;
|
|
838
578
|
return {
|
|
839
|
-
status: '
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
detail:
|
|
579
|
+
status: 'bound',
|
|
580
|
+
slug,
|
|
581
|
+
projectId: record.projectId,
|
|
582
|
+
detail: `Project ${record.projectId} at projects/${slug} stands for ${projectRoot}.`,
|
|
843
583
|
};
|
|
844
584
|
}
|
|
845
585
|
return {
|
|
846
|
-
status: '
|
|
847
|
-
|
|
848
|
-
detail: 'Project id and canonical root agree.',
|
|
586
|
+
status: 'uninitialized',
|
|
587
|
+
detail: 'No project in this application home stands for this working directory yet.',
|
|
849
588
|
};
|
|
850
589
|
}
|
|
851
|
-
async function readJsonRecord(entry, sink) {
|
|
852
|
-
if (entry.size > MAX_METADATA_BYTES) {
|
|
853
|
-
pushIssue(sink, {
|
|
854
|
-
code: 'inspection_skipped',
|
|
855
|
-
path: entry.relative,
|
|
856
|
-
detail: `Metadata exceeds the ${MAX_METADATA_BYTES}-byte semantic-inspection cap; its raw bytes remain counted.`,
|
|
857
|
-
});
|
|
858
|
-
return null;
|
|
859
|
-
}
|
|
860
|
-
try {
|
|
861
|
-
const value = JSON.parse(await readBoundedRegularFile(entry, MAX_METADATA_BYTES));
|
|
862
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
863
|
-
throw new Error('expected a JSON object');
|
|
864
|
-
}
|
|
865
|
-
return value;
|
|
866
|
-
}
|
|
867
|
-
catch (error) {
|
|
868
|
-
metadataIssue(sink, entry.relative, error);
|
|
869
|
-
return null;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
function recordId(value) {
|
|
873
|
-
return typeof value?.id === 'string' ? value.id : undefined;
|
|
874
|
-
}
|
|
875
|
-
function metadataIssue(sink, path, error) {
|
|
876
|
-
pushIssue(sink, {
|
|
877
|
-
code: error instanceof EntryChangedError ? 'entry_changed' : 'corrupt_metadata',
|
|
878
|
-
path,
|
|
879
|
-
detail: errorMessage(error),
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
590
|
async function readBoundedRegularFile(entry, maxBytes) {
|
|
883
591
|
if (entry.kind !== 'file')
|
|
884
592
|
throw new Error('entry is not a regular file');
|