nemoris 0.1.0 → 0.1.2
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/.env.example +49 -49
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/SECURITY.md +59 -119
- package/bin/nemoris +46 -46
- package/config/agents/agent.toml.example +28 -28
- package/config/agents/content.toml +23 -0
- package/config/agents/default.toml +22 -22
- package/config/agents/heartbeat.toml +35 -0
- package/config/agents/iris.toml +23 -0
- package/config/agents/lab.toml +23 -0
- package/config/agents/main.toml +45 -0
- package/config/agents/nemo.toml +21 -0
- package/config/agents/ops.toml +38 -0
- package/config/agents/orchestrator.toml +18 -18
- package/config/agents/revenue.toml +23 -0
- package/config/agents/testyboo.toml +19 -0
- package/config/delivery.toml +73 -73
- package/config/embeddings.toml +5 -5
- package/config/identity/content-purpose.md +11 -0
- package/config/identity/content-soul.md +45 -0
- package/config/identity/default-purpose.md +1 -1
- package/config/identity/default-soul.md +3 -3
- package/config/identity/heartbeat-purpose.md +9 -0
- package/config/identity/heartbeat-soul.md +16 -0
- package/config/identity/iris-purpose.md +17 -0
- package/config/identity/iris-soul.md +68 -0
- package/config/identity/lab-purpose.md +10 -0
- package/config/identity/lab-soul.md +38 -0
- package/config/identity/main-purpose.md +17 -0
- package/config/identity/main-soul.md +66 -0
- package/config/identity/main-user.md +22 -0
- package/config/identity/ops-purpose.md +9 -0
- package/config/identity/ops-soul.md +16 -0
- package/config/identity/orchestrator-purpose.md +1 -1
- package/config/identity/orchestrator-soul.md +1 -1
- package/config/identity/revenue-purpose.md +9 -0
- package/config/identity/revenue-soul.md +41 -0
- package/config/identity/testyboo-purpose.md +13 -0
- package/config/identity/testyboo-soul.md +20 -0
- package/config/improvement-targets.toml +15 -15
- package/config/jobs/heartbeat-check.toml +30 -30
- package/config/jobs/memory-rollup.toml +46 -46
- package/config/jobs/workspace-health.toml +63 -63
- package/config/mcp.toml +16 -16
- package/config/output-contracts.toml +17 -17
- package/config/peers.toml +32 -32
- package/config/peers.toml.example +32 -32
- package/config/policies/memory-default.toml +10 -10
- package/config/policies/memory-heartbeat.toml +5 -5
- package/config/policies/memory-ops.toml +10 -10
- package/config/policies/tools-heartbeat-minimal.toml +8 -8
- package/config/policies/tools-interactive-safe.toml +8 -8
- package/config/policies/tools-ops-bounded.toml +8 -8
- package/config/policies/tools-orchestrator.toml +7 -7
- package/config/providers/anthropic.toml +15 -15
- package/config/providers/ollama.toml +5 -5
- package/config/providers/openai-codex.toml +9 -9
- package/config/providers/openrouter.toml +5 -5
- package/config/router.toml +22 -22
- package/config/runtime.toml +114 -114
- package/config/skills/self-improvement.toml +15 -15
- package/config/skills/telegram-onboarding-spec.md +240 -240
- package/config/skills/workspace-monitor.toml +15 -15
- package/config/task-router.toml +42 -42
- package/install.sh +50 -50
- package/package.json +91 -90
- package/src/auth/auth-profiles.js +169 -169
- package/src/auth/openai-codex-oauth.js +285 -285
- package/src/battle.js +449 -449
- package/src/cli/help.js +265 -265
- package/src/cli/output-filter.js +49 -49
- package/src/cli/runtime-control.js +704 -704
- package/src/cli-main.js +2763 -2763
- package/src/cli.js +78 -78
- package/src/config/loader.js +332 -332
- package/src/config/schema-validator.js +214 -214
- package/src/config/toml-lite.js +8 -8
- package/src/daemon/action-handlers.js +71 -71
- package/src/daemon/healing-tick.js +87 -87
- package/src/daemon/health-probes.js +90 -90
- package/src/daemon/notifier.js +57 -57
- package/src/daemon/nurse.js +218 -218
- package/src/daemon/repair-log.js +106 -106
- package/src/daemon/rule-staging.js +90 -90
- package/src/daemon/rules.js +29 -29
- package/src/daemon/telegram-commands.js +54 -54
- package/src/daemon/updater.js +85 -85
- package/src/jobs/job-runner.js +78 -78
- package/src/mcp/consumer.js +129 -129
- package/src/memory/active-recall.js +171 -171
- package/src/memory/backend-manager.js +97 -97
- package/src/memory/backends/file-backend.js +38 -38
- package/src/memory/backends/qmd-backend.js +219 -219
- package/src/memory/embedding-guards.js +24 -24
- package/src/memory/embedding-index.js +118 -118
- package/src/memory/embedding-service.js +179 -179
- package/src/memory/file-index.js +177 -177
- package/src/memory/memory-signature.js +5 -5
- package/src/memory/memory-store.js +648 -648
- package/src/memory/retrieval-planner.js +66 -66
- package/src/memory/scoring.js +145 -145
- package/src/memory/simhash.js +78 -78
- package/src/memory/sqlite-active-store.js +824 -824
- package/src/memory/write-policy.js +36 -36
- package/src/onboarding/aliases.js +33 -33
- package/src/onboarding/auth/api-key.js +224 -224
- package/src/onboarding/auth/ollama-detect.js +42 -42
- package/src/onboarding/clack-prompter.js +77 -77
- package/src/onboarding/doctor.js +530 -530
- package/src/onboarding/lock.js +42 -42
- package/src/onboarding/model-catalog.js +344 -344
- package/src/onboarding/phases/auth.js +576 -589
- package/src/onboarding/phases/build.js +130 -130
- package/src/onboarding/phases/choose.js +82 -82
- package/src/onboarding/phases/detect.js +98 -98
- package/src/onboarding/phases/hatch.js +216 -216
- package/src/onboarding/phases/identity.js +79 -79
- package/src/onboarding/phases/ollama.js +345 -345
- package/src/onboarding/phases/scaffold.js +99 -99
- package/src/onboarding/phases/telegram.js +377 -377
- package/src/onboarding/phases/validate.js +204 -204
- package/src/onboarding/phases/verify.js +206 -206
- package/src/onboarding/platform.js +482 -482
- package/src/onboarding/status-bar.js +95 -95
- package/src/onboarding/templates.js +794 -794
- package/src/onboarding/toml-writer.js +38 -38
- package/src/onboarding/tui.js +250 -250
- package/src/onboarding/uninstall.js +153 -153
- package/src/onboarding/wizard.js +516 -499
- package/src/providers/anthropic.js +168 -168
- package/src/providers/base.js +247 -247
- package/src/providers/circuit-breaker.js +136 -136
- package/src/providers/ollama.js +163 -163
- package/src/providers/openai-codex.js +149 -149
- package/src/providers/openrouter.js +136 -136
- package/src/providers/registry.js +36 -36
- package/src/providers/router.js +16 -16
- package/src/runtime/bootstrap-cache.js +47 -47
- package/src/runtime/capabilities-prompt.js +25 -25
- package/src/runtime/completion-ping.js +99 -99
- package/src/runtime/config-validator.js +121 -121
- package/src/runtime/context-ledger.js +360 -360
- package/src/runtime/cutover-readiness.js +42 -42
- package/src/runtime/daemon.js +729 -729
- package/src/runtime/delivery-ack.js +195 -195
- package/src/runtime/delivery-adapters/local-file.js +41 -41
- package/src/runtime/delivery-adapters/openclaw-cli.js +94 -94
- package/src/runtime/delivery-adapters/openclaw-peer.js +98 -98
- package/src/runtime/delivery-adapters/shadow.js +13 -13
- package/src/runtime/delivery-adapters/standalone-http.js +98 -98
- package/src/runtime/delivery-adapters/telegram.js +104 -104
- package/src/runtime/delivery-adapters/tui.js +128 -128
- package/src/runtime/delivery-manager.js +807 -807
- package/src/runtime/delivery-store.js +168 -168
- package/src/runtime/dependency-health.js +118 -118
- package/src/runtime/envelope.js +114 -114
- package/src/runtime/evaluation.js +1089 -1089
- package/src/runtime/exec-approvals.js +216 -216
- package/src/runtime/executor.js +500 -500
- package/src/runtime/failure-ping.js +67 -67
- package/src/runtime/flows.js +83 -83
- package/src/runtime/guards.js +45 -45
- package/src/runtime/handoff.js +51 -51
- package/src/runtime/identity-cache.js +28 -28
- package/src/runtime/improvement-engine.js +109 -109
- package/src/runtime/improvement-harness.js +581 -581
- package/src/runtime/input-sanitiser.js +72 -72
- package/src/runtime/interaction-contract.js +347 -347
- package/src/runtime/lane-readiness.js +226 -226
- package/src/runtime/migration.js +323 -323
- package/src/runtime/model-resolution.js +78 -78
- package/src/runtime/network.js +64 -64
- package/src/runtime/notification-store.js +97 -97
- package/src/runtime/notifier.js +256 -256
- package/src/runtime/orchestrator.js +53 -53
- package/src/runtime/orphan-reaper.js +41 -41
- package/src/runtime/output-contract-schema.js +139 -139
- package/src/runtime/output-contract-validator.js +439 -439
- package/src/runtime/peer-readiness.js +69 -69
- package/src/runtime/peer-registry.js +133 -133
- package/src/runtime/pilot-status.js +108 -108
- package/src/runtime/prompt-builder.js +261 -261
- package/src/runtime/provider-attempt.js +582 -582
- package/src/runtime/report-fallback.js +71 -71
- package/src/runtime/result-normalizer.js +183 -183
- package/src/runtime/retention.js +74 -74
- package/src/runtime/review.js +244 -244
- package/src/runtime/route-job.js +15 -15
- package/src/runtime/run-store.js +38 -38
- package/src/runtime/schedule.js +88 -88
- package/src/runtime/scheduler-state.js +434 -434
- package/src/runtime/scheduler.js +656 -656
- package/src/runtime/session-compactor.js +182 -182
- package/src/runtime/session-search.js +155 -155
- package/src/runtime/slack-inbound.js +249 -249
- package/src/runtime/ssrf.js +102 -102
- package/src/runtime/status-aggregator.js +330 -330
- package/src/runtime/task-contract.js +140 -140
- package/src/runtime/task-packet.js +107 -107
- package/src/runtime/task-router.js +140 -140
- package/src/runtime/telegram-inbound.js +1565 -1565
- package/src/runtime/token-counter.js +134 -134
- package/src/runtime/token-estimator.js +59 -59
- package/src/runtime/tool-loop.js +200 -200
- package/src/runtime/transport-server.js +311 -311
- package/src/runtime/tui-server.js +411 -411
- package/src/runtime/ulid.js +44 -44
- package/src/security/ssrf-check.js +197 -197
- package/src/setup.js +369 -369
- package/src/shadow/bridge.js +303 -303
- package/src/skills/loader.js +84 -84
- package/src/tools/catalog.json +49 -49
- package/src/tools/cli-delegate.js +44 -44
- package/src/tools/mcp-client.js +106 -106
- package/src/tools/micro/cancel-task.js +6 -6
- package/src/tools/micro/complete-task.js +6 -6
- package/src/tools/micro/fail-task.js +6 -6
- package/src/tools/micro/http-fetch.js +74 -74
- package/src/tools/micro/index.js +36 -36
- package/src/tools/micro/lcm-recall.js +60 -60
- package/src/tools/micro/list-dir.js +17 -17
- package/src/tools/micro/list-skills.js +46 -46
- package/src/tools/micro/load-skill.js +38 -38
- package/src/tools/micro/memory-search.js +45 -45
- package/src/tools/micro/read-file.js +11 -11
- package/src/tools/micro/session-search.js +54 -54
- package/src/tools/micro/shell-exec.js +43 -43
- package/src/tools/micro/trigger-job.js +79 -79
- package/src/tools/micro/web-search.js +58 -58
- package/src/tools/micro/workspace-paths.js +39 -39
- package/src/tools/micro/write-file.js +14 -14
- package/src/tools/micro/write-memory.js +41 -41
- package/src/tools/registry.js +348 -348
- package/src/tools/tool-result-contract.js +36 -36
- package/src/tui/chat.js +835 -835
- package/src/tui/renderer.js +175 -175
- package/src/tui/socket-client.js +217 -217
- package/src/utils/canonical-json.js +29 -29
- package/src/utils/compaction.js +30 -30
- package/src/utils/env-loader.js +5 -5
- package/src/utils/errors.js +80 -80
- package/src/utils/fs.js +101 -101
- package/src/utils/ids.js +5 -5
- package/src/utils/model-context-limits.js +30 -30
- package/src/utils/token-budget.js +74 -74
- package/src/utils/usage-cost.js +25 -25
- package/src/utils/usage-metrics.js +14 -14
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
import { getProviderModePolicy } from "./guards.js";
|
|
2
|
-
|
|
3
|
-
function readFlag(name) {
|
|
4
|
-
const raw = process.env[name];
|
|
5
|
-
return raw === "1" || raw === "true";
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function classifyRuntimeFailure(message) {
|
|
9
|
-
const normalized = String(message || "").toLowerCase();
|
|
10
|
-
if (!normalized) return "unknown_failure";
|
|
11
|
-
if (normalized.includes("timed out")) return "timeout";
|
|
12
|
-
if (normalized.includes("loading model") || normalized.includes("server loading")) return "provider_loading";
|
|
13
|
-
if (normalized.includes("missing auth token") || normalized.includes("missing api key")) return "auth_missing";
|
|
14
|
-
if (normalized.includes("model") && normalized.includes("not found")) return "missing_model";
|
|
15
|
-
if (normalized.includes("structured") && normalized.includes("output")) return "structured_output_failure";
|
|
16
|
-
if (normalized.includes("provider error")) return "provider_error";
|
|
17
|
-
return "unknown_failure";
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function getReportFallbackPolicy(runtimeConfig = {}, options = {}) {
|
|
21
|
-
const configured = runtimeConfig?.reportFallback || {};
|
|
22
|
-
const envEnabled = readFlag("NEMORIS_ALLOW_REPORT_FALLBACK");
|
|
23
|
-
const allowReportFallback = Boolean(options.allowReportFallback || envEnabled || configured.enabled === true);
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
enabled: allowReportFallback,
|
|
27
|
-
configEnabled: configured.enabled === true,
|
|
28
|
-
envEnabled,
|
|
29
|
-
overrideEnabled: Boolean(options.allowReportFallback),
|
|
30
|
-
lane: configured.lane || "report_fallback_lowcost",
|
|
31
|
-
allowedJobIds: configured.allowedJobIds || ["workspace-health"],
|
|
32
|
-
allowedFailureClasses: configured.allowedFailureClasses || ["timeout", "provider_loading"]
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function resolveReportFallback({ runtimeConfig, job, routingDecision, providerId, failureClass, options = {} }) {
|
|
37
|
-
const policy = getReportFallbackPolicy(runtimeConfig, options);
|
|
38
|
-
const jobPolicy = job?.reportFallback || null;
|
|
39
|
-
|
|
40
|
-
if (!jobPolicy?.enabled) {
|
|
41
|
-
return { allowed: false, policy, reason: "Job is not configured for report fallback." };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!policy.enabled) {
|
|
45
|
-
return { allowed: false, policy, reason: "Report fallback is disabled." };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!policy.allowedJobIds.includes(job.id)) {
|
|
49
|
-
return { allowed: false, policy, reason: `Job ${job.id} is not allowed to use report fallback.` };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!policy.allowedFailureClasses.includes(failureClass)) {
|
|
53
|
-
return { allowed: false, policy, reason: `Failure class ${failureClass} does not qualify for report fallback.` };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (providerId !== "ollama" || routingDecision?.selectedLane !== "local_report") {
|
|
57
|
-
return { allowed: false, policy, reason: "Report fallback only applies after local_report Ollama failures." };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const providerModePolicy = getProviderModePolicy();
|
|
61
|
-
if (!providerModePolicy.allowRemoteProviders) {
|
|
62
|
-
return { allowed: false, policy, reason: "Remote provider execution is not enabled." };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
allowed: true,
|
|
67
|
-
policy,
|
|
68
|
-
targetLane: jobPolicy.lane || policy.lane,
|
|
69
|
-
trigger: failureClass
|
|
70
|
-
};
|
|
71
|
-
}
|
|
1
|
+
import { getProviderModePolicy } from "./guards.js";
|
|
2
|
+
|
|
3
|
+
function readFlag(name) {
|
|
4
|
+
const raw = process.env[name];
|
|
5
|
+
return raw === "1" || raw === "true";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function classifyRuntimeFailure(message) {
|
|
9
|
+
const normalized = String(message || "").toLowerCase();
|
|
10
|
+
if (!normalized) return "unknown_failure";
|
|
11
|
+
if (normalized.includes("timed out")) return "timeout";
|
|
12
|
+
if (normalized.includes("loading model") || normalized.includes("server loading")) return "provider_loading";
|
|
13
|
+
if (normalized.includes("missing auth token") || normalized.includes("missing api key")) return "auth_missing";
|
|
14
|
+
if (normalized.includes("model") && normalized.includes("not found")) return "missing_model";
|
|
15
|
+
if (normalized.includes("structured") && normalized.includes("output")) return "structured_output_failure";
|
|
16
|
+
if (normalized.includes("provider error")) return "provider_error";
|
|
17
|
+
return "unknown_failure";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getReportFallbackPolicy(runtimeConfig = {}, options = {}) {
|
|
21
|
+
const configured = runtimeConfig?.reportFallback || {};
|
|
22
|
+
const envEnabled = readFlag("NEMORIS_ALLOW_REPORT_FALLBACK");
|
|
23
|
+
const allowReportFallback = Boolean(options.allowReportFallback || envEnabled || configured.enabled === true);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
enabled: allowReportFallback,
|
|
27
|
+
configEnabled: configured.enabled === true,
|
|
28
|
+
envEnabled,
|
|
29
|
+
overrideEnabled: Boolean(options.allowReportFallback),
|
|
30
|
+
lane: configured.lane || "report_fallback_lowcost",
|
|
31
|
+
allowedJobIds: configured.allowedJobIds || ["workspace-health"],
|
|
32
|
+
allowedFailureClasses: configured.allowedFailureClasses || ["timeout", "provider_loading"]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveReportFallback({ runtimeConfig, job, routingDecision, providerId, failureClass, options = {} }) {
|
|
37
|
+
const policy = getReportFallbackPolicy(runtimeConfig, options);
|
|
38
|
+
const jobPolicy = job?.reportFallback || null;
|
|
39
|
+
|
|
40
|
+
if (!jobPolicy?.enabled) {
|
|
41
|
+
return { allowed: false, policy, reason: "Job is not configured for report fallback." };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!policy.enabled) {
|
|
45
|
+
return { allowed: false, policy, reason: "Report fallback is disabled." };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!policy.allowedJobIds.includes(job.id)) {
|
|
49
|
+
return { allowed: false, policy, reason: `Job ${job.id} is not allowed to use report fallback.` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!policy.allowedFailureClasses.includes(failureClass)) {
|
|
53
|
+
return { allowed: false, policy, reason: `Failure class ${failureClass} does not qualify for report fallback.` };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (providerId !== "ollama" || routingDecision?.selectedLane !== "local_report") {
|
|
57
|
+
return { allowed: false, policy, reason: "Report fallback only applies after local_report Ollama failures." };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const providerModePolicy = getProviderModePolicy();
|
|
61
|
+
if (!providerModePolicy.allowRemoteProviders) {
|
|
62
|
+
return { allowed: false, policy, reason: "Remote provider execution is not enabled." };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
allowed: true,
|
|
67
|
+
policy,
|
|
68
|
+
targetLane: jobPolicy.lane || policy.lane,
|
|
69
|
+
trigger: failureClass
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
import { renderContractOutput } from "./output-contract-validator.js";
|
|
2
|
-
import { estimateCost } from "../utils/usage-cost.js";
|
|
3
|
-
import { extractUsageMetrics } from "../utils/usage-metrics.js";
|
|
4
|
-
|
|
5
|
-
export function synthesizeDryRun(plan, modelId) {
|
|
6
|
-
const memoryCount = plan.packet.layers.memory.length;
|
|
7
|
-
const summary = `Dry-run shadow execution for ${plan.job.id} prepared on ${modelId} with ${memoryCount} relevant memory items.`;
|
|
8
|
-
const output = [
|
|
9
|
-
`Job ${plan.job.id} would run on lane ${plan.job.modelLane} using model ${modelId}.`,
|
|
10
|
-
`Workspace: ${plan.agent.workspaceRoot || "unknown"}.`,
|
|
11
|
-
`Tools: ${plan.packet.layers.tools.join(", ") || "none"}.`,
|
|
12
|
-
`Memory items included: ${memoryCount}.`
|
|
13
|
-
].join(" ");
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
summary,
|
|
17
|
-
output,
|
|
18
|
-
nextActions: ["switch to provider mode for a real shadow run", "compare with live cron output"]
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function formatOutputContract(contract) {
|
|
23
|
-
const lines = [];
|
|
24
|
-
if (contract.format) lines.push(`- format: ${contract.format}`);
|
|
25
|
-
if (contract.requiredSections?.length) {
|
|
26
|
-
lines.push(`- required sections: ${contract.requiredSections.join(", ")}`);
|
|
27
|
-
}
|
|
28
|
-
if (contract.styleHints?.length) {
|
|
29
|
-
lines.push(`- style hints: ${contract.styleHints.join("; ")}`);
|
|
30
|
-
}
|
|
31
|
-
if (contract.profile?.sectionStyle) {
|
|
32
|
-
lines.push(`- section style: ${contract.profile.sectionStyle}`);
|
|
33
|
-
}
|
|
34
|
-
if (contract.profile?.requireStatus) {
|
|
35
|
-
lines.push("- requires status: yes");
|
|
36
|
-
}
|
|
37
|
-
return lines.join("\n");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function formatReportGuidance(reportGuidance) {
|
|
41
|
-
const lines = [];
|
|
42
|
-
if (reportGuidance.focus?.length) {
|
|
43
|
-
lines.push(`- focus: ${reportGuidance.focus.join("; ")}`);
|
|
44
|
-
}
|
|
45
|
-
if (reportGuidance.qualityChecks?.length) {
|
|
46
|
-
lines.push(`- quality checks: ${reportGuidance.qualityChecks.join("; ")}`);
|
|
47
|
-
}
|
|
48
|
-
if (reportGuidance.avoid?.length) {
|
|
49
|
-
lines.push(`- avoid: ${reportGuidance.avoid.join("; ")}`);
|
|
50
|
-
}
|
|
51
|
-
return lines.join("\n");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function normalizeNextActions(nextActions = []) {
|
|
55
|
-
return (nextActions || [])
|
|
56
|
-
.map((item) => {
|
|
57
|
-
if (typeof item === "string") return item.trim();
|
|
58
|
-
if (item && typeof item === "object") {
|
|
59
|
-
const action = String(item.action || "").trim();
|
|
60
|
-
const details = String(item.details || item.summary || "").trim();
|
|
61
|
-
return [action, details].filter(Boolean).join(": ");
|
|
62
|
-
}
|
|
63
|
-
return String(item || "").trim();
|
|
64
|
-
})
|
|
65
|
-
.filter(Boolean);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function hasOnlyShadowImportEvidence(plan) {
|
|
69
|
-
const memory = plan?.packet?.layers?.memory || [];
|
|
70
|
-
if (!memory.length) return false;
|
|
71
|
-
return memory.every((item) => ["shadow_import", "artifact_summary"].includes(item.category));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function normalizeResultForContract(plan, result, options = {}) {
|
|
75
|
-
const contract = plan?.packet?.layers?.outputContract || null;
|
|
76
|
-
if (!contract || options.mode !== "provider") {
|
|
77
|
-
return {
|
|
78
|
-
...result,
|
|
79
|
-
nextActions: normalizeNextActions(result.nextActions || [])
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const originalOutput = result.output;
|
|
84
|
-
const canonicalOutput = renderContractOutput(contract, originalOutput, {
|
|
85
|
-
shadowOnlyEvidence: contract.format === "structured_rollup" && hasOnlyShadowImportEvidence(plan),
|
|
86
|
-
status: result.summary
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
...result,
|
|
91
|
-
output: canonicalOutput,
|
|
92
|
-
outputStructured: originalOutput,
|
|
93
|
-
nextActions: normalizeNextActions(result.nextActions || [])
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function buildOutputTemplate(contract) {
|
|
98
|
-
if (!contract) return null;
|
|
99
|
-
if (contract.profile?.templateLines?.length) {
|
|
100
|
-
return contract.profile.templateLines.join("\n");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (contract.format === "bulleted_briefing") {
|
|
104
|
-
return [
|
|
105
|
-
"Status: <one-line status>",
|
|
106
|
-
"- Calendar: <brief update or None>",
|
|
107
|
-
"- Issues: <brief update or None>",
|
|
108
|
-
"- Weather: <brief update or None>"
|
|
109
|
-
].join("\n");
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (contract.format === "structured_rollup") {
|
|
113
|
-
return [
|
|
114
|
-
"## Inbox",
|
|
115
|
-
"- <brief update or None>",
|
|
116
|
-
"",
|
|
117
|
-
"## Projects",
|
|
118
|
-
"- <brief update or None>",
|
|
119
|
-
"",
|
|
120
|
-
"## Backlog",
|
|
121
|
-
"- <brief update or None>",
|
|
122
|
-
"",
|
|
123
|
-
"## Update",
|
|
124
|
-
"- <brief update or None>"
|
|
125
|
-
].join("\n");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function normalizeProviderResponse(rawResponse, adapter) {
|
|
132
|
-
if (typeof adapter?.normalizeResponse === "function") {
|
|
133
|
-
return adapter.normalizeResponse(rawResponse);
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
summary: "Provider returned unnormalized data.",
|
|
137
|
-
output: rawResponse,
|
|
138
|
-
nextActions: [],
|
|
139
|
-
reasoning: null,
|
|
140
|
-
raw: rawResponse
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export async function normalizeResult({ plan, rawResponse, toolResults = [], usage, options = {} }) {
|
|
145
|
-
const mode = options.mode || "dry-run";
|
|
146
|
-
let result = null;
|
|
147
|
-
|
|
148
|
-
if (options.pendingApprovalResult) {
|
|
149
|
-
result = options.pendingApprovalResult;
|
|
150
|
-
} else if (mode === "dry-run") {
|
|
151
|
-
result = synthesizeDryRun(plan, options.modelId);
|
|
152
|
-
} else {
|
|
153
|
-
result = normalizeProviderResponse(rawResponse, options.adapter);
|
|
154
|
-
result = normalizeResultForContract(plan, result, { mode });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const usageMetrics = usage || extractUsageMetrics(rawResponse || {});
|
|
158
|
-
const costUsd = options.modelId ? estimateCost(options.modelId, usageMetrics.tokensIn, usageMetrics.tokensOut) : null;
|
|
159
|
-
|
|
160
|
-
if (!options.pendingApprovalResult && mode === "provider" && rawResponse && typeof options.logUsage === "function") {
|
|
161
|
-
try {
|
|
162
|
-
await options.logUsage({
|
|
163
|
-
plan,
|
|
164
|
-
providerId: options.providerId,
|
|
165
|
-
modelId: options.modelId,
|
|
166
|
-
usage: usageMetrics,
|
|
167
|
-
costUsd,
|
|
168
|
-
rawResponse,
|
|
169
|
-
toolResults,
|
|
170
|
-
sessionId: options.sessionId || plan.job.chat_id || "unknown"
|
|
171
|
-
});
|
|
172
|
-
} catch (err) {
|
|
173
|
-
console.error(JSON.stringify({ service: "usage_logging", error: err.message, jobId: plan.job.id }));
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return {
|
|
178
|
-
result,
|
|
179
|
-
usage: usageMetrics,
|
|
180
|
-
costUsd,
|
|
181
|
-
cached: Boolean(usageMetrics.cacheIn || usageMetrics.cacheCreation)
|
|
182
|
-
};
|
|
183
|
-
}
|
|
1
|
+
import { renderContractOutput } from "./output-contract-validator.js";
|
|
2
|
+
import { estimateCost } from "../utils/usage-cost.js";
|
|
3
|
+
import { extractUsageMetrics } from "../utils/usage-metrics.js";
|
|
4
|
+
|
|
5
|
+
export function synthesizeDryRun(plan, modelId) {
|
|
6
|
+
const memoryCount = plan.packet.layers.memory.length;
|
|
7
|
+
const summary = `Dry-run shadow execution for ${plan.job.id} prepared on ${modelId} with ${memoryCount} relevant memory items.`;
|
|
8
|
+
const output = [
|
|
9
|
+
`Job ${plan.job.id} would run on lane ${plan.job.modelLane} using model ${modelId}.`,
|
|
10
|
+
`Workspace: ${plan.agent.workspaceRoot || "unknown"}.`,
|
|
11
|
+
`Tools: ${plan.packet.layers.tools.join(", ") || "none"}.`,
|
|
12
|
+
`Memory items included: ${memoryCount}.`
|
|
13
|
+
].join(" ");
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
summary,
|
|
17
|
+
output,
|
|
18
|
+
nextActions: ["switch to provider mode for a real shadow run", "compare with live cron output"]
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatOutputContract(contract) {
|
|
23
|
+
const lines = [];
|
|
24
|
+
if (contract.format) lines.push(`- format: ${contract.format}`);
|
|
25
|
+
if (contract.requiredSections?.length) {
|
|
26
|
+
lines.push(`- required sections: ${contract.requiredSections.join(", ")}`);
|
|
27
|
+
}
|
|
28
|
+
if (contract.styleHints?.length) {
|
|
29
|
+
lines.push(`- style hints: ${contract.styleHints.join("; ")}`);
|
|
30
|
+
}
|
|
31
|
+
if (contract.profile?.sectionStyle) {
|
|
32
|
+
lines.push(`- section style: ${contract.profile.sectionStyle}`);
|
|
33
|
+
}
|
|
34
|
+
if (contract.profile?.requireStatus) {
|
|
35
|
+
lines.push("- requires status: yes");
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function formatReportGuidance(reportGuidance) {
|
|
41
|
+
const lines = [];
|
|
42
|
+
if (reportGuidance.focus?.length) {
|
|
43
|
+
lines.push(`- focus: ${reportGuidance.focus.join("; ")}`);
|
|
44
|
+
}
|
|
45
|
+
if (reportGuidance.qualityChecks?.length) {
|
|
46
|
+
lines.push(`- quality checks: ${reportGuidance.qualityChecks.join("; ")}`);
|
|
47
|
+
}
|
|
48
|
+
if (reportGuidance.avoid?.length) {
|
|
49
|
+
lines.push(`- avoid: ${reportGuidance.avoid.join("; ")}`);
|
|
50
|
+
}
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function normalizeNextActions(nextActions = []) {
|
|
55
|
+
return (nextActions || [])
|
|
56
|
+
.map((item) => {
|
|
57
|
+
if (typeof item === "string") return item.trim();
|
|
58
|
+
if (item && typeof item === "object") {
|
|
59
|
+
const action = String(item.action || "").trim();
|
|
60
|
+
const details = String(item.details || item.summary || "").trim();
|
|
61
|
+
return [action, details].filter(Boolean).join(": ");
|
|
62
|
+
}
|
|
63
|
+
return String(item || "").trim();
|
|
64
|
+
})
|
|
65
|
+
.filter(Boolean);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function hasOnlyShadowImportEvidence(plan) {
|
|
69
|
+
const memory = plan?.packet?.layers?.memory || [];
|
|
70
|
+
if (!memory.length) return false;
|
|
71
|
+
return memory.every((item) => ["shadow_import", "artifact_summary"].includes(item.category));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function normalizeResultForContract(plan, result, options = {}) {
|
|
75
|
+
const contract = plan?.packet?.layers?.outputContract || null;
|
|
76
|
+
if (!contract || options.mode !== "provider") {
|
|
77
|
+
return {
|
|
78
|
+
...result,
|
|
79
|
+
nextActions: normalizeNextActions(result.nextActions || [])
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const originalOutput = result.output;
|
|
84
|
+
const canonicalOutput = renderContractOutput(contract, originalOutput, {
|
|
85
|
+
shadowOnlyEvidence: contract.format === "structured_rollup" && hasOnlyShadowImportEvidence(plan),
|
|
86
|
+
status: result.summary
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
...result,
|
|
91
|
+
output: canonicalOutput,
|
|
92
|
+
outputStructured: originalOutput,
|
|
93
|
+
nextActions: normalizeNextActions(result.nextActions || [])
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function buildOutputTemplate(contract) {
|
|
98
|
+
if (!contract) return null;
|
|
99
|
+
if (contract.profile?.templateLines?.length) {
|
|
100
|
+
return contract.profile.templateLines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (contract.format === "bulleted_briefing") {
|
|
104
|
+
return [
|
|
105
|
+
"Status: <one-line status>",
|
|
106
|
+
"- Calendar: <brief update or None>",
|
|
107
|
+
"- Issues: <brief update or None>",
|
|
108
|
+
"- Weather: <brief update or None>"
|
|
109
|
+
].join("\n");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (contract.format === "structured_rollup") {
|
|
113
|
+
return [
|
|
114
|
+
"## Inbox",
|
|
115
|
+
"- <brief update or None>",
|
|
116
|
+
"",
|
|
117
|
+
"## Projects",
|
|
118
|
+
"- <brief update or None>",
|
|
119
|
+
"",
|
|
120
|
+
"## Backlog",
|
|
121
|
+
"- <brief update or None>",
|
|
122
|
+
"",
|
|
123
|
+
"## Update",
|
|
124
|
+
"- <brief update or None>"
|
|
125
|
+
].join("\n");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function normalizeProviderResponse(rawResponse, adapter) {
|
|
132
|
+
if (typeof adapter?.normalizeResponse === "function") {
|
|
133
|
+
return adapter.normalizeResponse(rawResponse);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
summary: "Provider returned unnormalized data.",
|
|
137
|
+
output: rawResponse,
|
|
138
|
+
nextActions: [],
|
|
139
|
+
reasoning: null,
|
|
140
|
+
raw: rawResponse
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function normalizeResult({ plan, rawResponse, toolResults = [], usage, options = {} }) {
|
|
145
|
+
const mode = options.mode || "dry-run";
|
|
146
|
+
let result = null;
|
|
147
|
+
|
|
148
|
+
if (options.pendingApprovalResult) {
|
|
149
|
+
result = options.pendingApprovalResult;
|
|
150
|
+
} else if (mode === "dry-run") {
|
|
151
|
+
result = synthesizeDryRun(plan, options.modelId);
|
|
152
|
+
} else {
|
|
153
|
+
result = normalizeProviderResponse(rawResponse, options.adapter);
|
|
154
|
+
result = normalizeResultForContract(plan, result, { mode });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const usageMetrics = usage || extractUsageMetrics(rawResponse || {});
|
|
158
|
+
const costUsd = options.modelId ? estimateCost(options.modelId, usageMetrics.tokensIn, usageMetrics.tokensOut) : null;
|
|
159
|
+
|
|
160
|
+
if (!options.pendingApprovalResult && mode === "provider" && rawResponse && typeof options.logUsage === "function") {
|
|
161
|
+
try {
|
|
162
|
+
await options.logUsage({
|
|
163
|
+
plan,
|
|
164
|
+
providerId: options.providerId,
|
|
165
|
+
modelId: options.modelId,
|
|
166
|
+
usage: usageMetrics,
|
|
167
|
+
costUsd,
|
|
168
|
+
rawResponse,
|
|
169
|
+
toolResults,
|
|
170
|
+
sessionId: options.sessionId || plan.job.chat_id || "unknown"
|
|
171
|
+
});
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error(JSON.stringify({ service: "usage_logging", error: err.message, jobId: plan.job.id }));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
result,
|
|
179
|
+
usage: usageMetrics,
|
|
180
|
+
costUsd,
|
|
181
|
+
cached: Boolean(usageMetrics.cacheIn || usageMetrics.cacheCreation)
|
|
182
|
+
};
|
|
183
|
+
}
|
package/src/runtime/retention.js
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { listFilesRecursive, readJson, removePath } from "../utils/fs.js";
|
|
3
|
-
|
|
4
|
-
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
5
|
-
|
|
6
|
-
function inferTimestampFromPath(filePath) {
|
|
7
|
-
const base = path.basename(filePath, ".json");
|
|
8
|
-
const candidate = base.replace(/-/g, ":").replace(/(\d{4}):(\d{2}):(\d{2})T/, "$1-$2-$3T").replace(/:(\d{3})$/, ".$1");
|
|
9
|
-
const parsed = new Date(candidate);
|
|
10
|
-
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function normalizeTimestamp(item) {
|
|
14
|
-
return item.timestamp || item.receivedAt || inferTimestampFromPath(item.filePath) || "1970-01-01T00:00:00.000Z";
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function isJsonFile(filePath) {
|
|
18
|
-
return filePath.endsWith(".json");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function buildRetentionPolicy(config = {}, defaults = {}) {
|
|
22
|
-
return {
|
|
23
|
-
ttlDays: Number(config.ttlDays ?? defaults.ttlDays ?? 30),
|
|
24
|
-
maxFilesPerBucket: Number(config.maxFilesPerBucket ?? defaults.maxFilesPerBucket ?? 1000)
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function pruneJsonBuckets(rootDir, policy, options = {}) {
|
|
29
|
-
const now = options.now ? new Date(options.now) : new Date();
|
|
30
|
-
const ttlCutoff = now.getTime() - policy.ttlDays * DAY_MS;
|
|
31
|
-
const excluded = new Set((options.excludePaths || []).map((filePath) => path.resolve(filePath)));
|
|
32
|
-
const files = (await listFilesRecursive(rootDir)).filter((filePath) => isJsonFile(filePath) && !excluded.has(path.resolve(filePath)));
|
|
33
|
-
const buckets = new Map();
|
|
34
|
-
|
|
35
|
-
for (const filePath of files) {
|
|
36
|
-
const bucket = path.dirname(filePath);
|
|
37
|
-
if (!buckets.has(bucket)) buckets.set(bucket, []);
|
|
38
|
-
const data = await readJson(filePath, null);
|
|
39
|
-
buckets.get(bucket).push({
|
|
40
|
-
filePath,
|
|
41
|
-
timestamp: normalizeTimestamp({
|
|
42
|
-
...(data || {}),
|
|
43
|
-
filePath
|
|
44
|
-
})
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const removed = [];
|
|
49
|
-
for (const entries of buckets.values()) {
|
|
50
|
-
entries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
51
|
-
const keep = new Set();
|
|
52
|
-
|
|
53
|
-
for (let index = 0; index < entries.length; index += 1) {
|
|
54
|
-
const item = entries[index];
|
|
55
|
-
const expired = new Date(item.timestamp).getTime() < ttlCutoff;
|
|
56
|
-
const overflow = index >= policy.maxFilesPerBucket;
|
|
57
|
-
if (!expired && !overflow) {
|
|
58
|
-
keep.add(item.filePath);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for (const item of entries) {
|
|
63
|
-
if (keep.has(item.filePath)) continue;
|
|
64
|
-
await removePath(item.filePath);
|
|
65
|
-
removed.push(item.filePath);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
rootDir,
|
|
71
|
-
removedCount: removed.length,
|
|
72
|
-
removed
|
|
73
|
-
};
|
|
74
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { listFilesRecursive, readJson, removePath } from "../utils/fs.js";
|
|
3
|
+
|
|
4
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
5
|
+
|
|
6
|
+
function inferTimestampFromPath(filePath) {
|
|
7
|
+
const base = path.basename(filePath, ".json");
|
|
8
|
+
const candidate = base.replace(/-/g, ":").replace(/(\d{4}):(\d{2}):(\d{2})T/, "$1-$2-$3T").replace(/:(\d{3})$/, ".$1");
|
|
9
|
+
const parsed = new Date(candidate);
|
|
10
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeTimestamp(item) {
|
|
14
|
+
return item.timestamp || item.receivedAt || inferTimestampFromPath(item.filePath) || "1970-01-01T00:00:00.000Z";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isJsonFile(filePath) {
|
|
18
|
+
return filePath.endsWith(".json");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildRetentionPolicy(config = {}, defaults = {}) {
|
|
22
|
+
return {
|
|
23
|
+
ttlDays: Number(config.ttlDays ?? defaults.ttlDays ?? 30),
|
|
24
|
+
maxFilesPerBucket: Number(config.maxFilesPerBucket ?? defaults.maxFilesPerBucket ?? 1000)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function pruneJsonBuckets(rootDir, policy, options = {}) {
|
|
29
|
+
const now = options.now ? new Date(options.now) : new Date();
|
|
30
|
+
const ttlCutoff = now.getTime() - policy.ttlDays * DAY_MS;
|
|
31
|
+
const excluded = new Set((options.excludePaths || []).map((filePath) => path.resolve(filePath)));
|
|
32
|
+
const files = (await listFilesRecursive(rootDir)).filter((filePath) => isJsonFile(filePath) && !excluded.has(path.resolve(filePath)));
|
|
33
|
+
const buckets = new Map();
|
|
34
|
+
|
|
35
|
+
for (const filePath of files) {
|
|
36
|
+
const bucket = path.dirname(filePath);
|
|
37
|
+
if (!buckets.has(bucket)) buckets.set(bucket, []);
|
|
38
|
+
const data = await readJson(filePath, null);
|
|
39
|
+
buckets.get(bucket).push({
|
|
40
|
+
filePath,
|
|
41
|
+
timestamp: normalizeTimestamp({
|
|
42
|
+
...(data || {}),
|
|
43
|
+
filePath
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const removed = [];
|
|
49
|
+
for (const entries of buckets.values()) {
|
|
50
|
+
entries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
51
|
+
const keep = new Set();
|
|
52
|
+
|
|
53
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
54
|
+
const item = entries[index];
|
|
55
|
+
const expired = new Date(item.timestamp).getTime() < ttlCutoff;
|
|
56
|
+
const overflow = index >= policy.maxFilesPerBucket;
|
|
57
|
+
if (!expired && !overflow) {
|
|
58
|
+
keep.add(item.filePath);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const item of entries) {
|
|
63
|
+
if (keep.has(item.filePath)) continue;
|
|
64
|
+
await removePath(item.filePath);
|
|
65
|
+
removed.push(item.filePath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
rootDir,
|
|
71
|
+
removedCount: removed.length,
|
|
72
|
+
removed
|
|
73
|
+
};
|
|
74
|
+
}
|