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,78 +1,78 @@
|
|
|
1
|
-
const MODEL_TIER_MAP = Object.freeze({
|
|
2
|
-
sonnet: "anthropic/claude-sonnet-4-6",
|
|
3
|
-
haiku: "anthropic/claude-haiku-4-5",
|
|
4
|
-
opus: "anthropic/claude-opus-4-6",
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
const CLAUDE_MODEL_BY_TIER = Object.freeze({
|
|
8
|
-
haiku: "claude-haiku-4-5",
|
|
9
|
-
sonnet: "claude-sonnet-4-6",
|
|
10
|
-
opus: "claude-opus-4-6",
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
function getLaneValue(lane, camelKey, snakeKey) {
|
|
14
|
-
if (!lane || typeof lane !== "object") return null;
|
|
15
|
-
return lane[camelKey] ?? lane[snakeKey] ?? null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function getInteractivePrimaryLane(routerConfig = null) {
|
|
19
|
-
return routerConfig?.interactive_primary || routerConfig?.interactivePrimary || null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function inferAnthropicPrefix(routerConfig = null) {
|
|
23
|
-
const lane = getInteractivePrimaryLane(routerConfig);
|
|
24
|
-
const sourceModel = getLaneValue(lane, "manualBump", "manual_bump")
|
|
25
|
-
|| getLaneValue(lane, "primary", "primary");
|
|
26
|
-
|
|
27
|
-
if (typeof sourceModel !== "string") return null;
|
|
28
|
-
if (sourceModel.startsWith("openrouter/anthropic/")) return "openrouter/anthropic";
|
|
29
|
-
if (sourceModel.startsWith("anthropic/")) return "anthropic";
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function inferAnthropicModelForTier(tier, { routerConfig = null } = {}) {
|
|
34
|
-
const prefix = inferAnthropicPrefix(routerConfig);
|
|
35
|
-
const modelName = CLAUDE_MODEL_BY_TIER[tier] || null;
|
|
36
|
-
if (!prefix || !modelName) return null;
|
|
37
|
-
return `${prefix}/${modelName}`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function resolveModelOverride(modelOverride, { routerConfig = null } = {}) {
|
|
41
|
-
if (!modelOverride || modelOverride === "default") {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
if (modelOverride.includes("/")) {
|
|
45
|
-
return modelOverride;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const interactivePrimaryLane = getInteractivePrimaryLane(routerConfig);
|
|
49
|
-
if (modelOverride === "haiku") {
|
|
50
|
-
return getLaneValue(interactivePrimaryLane, "primary", "primary")
|
|
51
|
-
|| inferAnthropicModelForTier("haiku", { routerConfig })
|
|
52
|
-
|| MODEL_TIER_MAP.haiku;
|
|
53
|
-
}
|
|
54
|
-
if (modelOverride === "sonnet") {
|
|
55
|
-
return getLaneValue(interactivePrimaryLane, "manualBump", "manual_bump")
|
|
56
|
-
|| inferAnthropicModelForTier("sonnet", { routerConfig })
|
|
57
|
-
|| MODEL_TIER_MAP.sonnet;
|
|
58
|
-
}
|
|
59
|
-
if (modelOverride === "opus") {
|
|
60
|
-
return inferAnthropicModelForTier("opus", { routerConfig }) || MODEL_TIER_MAP.opus;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return MODEL_TIER_MAP[modelOverride] || modelOverride;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function resolveSessionStatusModel(session, { routerConfig = null, agentConfigs = null } = {}) {
|
|
67
|
-
const modelOverride = resolveModelOverride(session?.model_override, { routerConfig });
|
|
68
|
-
if (modelOverride) {
|
|
69
|
-
return modelOverride;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const agentConfig = agentConfigs?.[session?.agent_id] || null;
|
|
73
|
-
const primaryLane = agentConfig?.primaryLane || agentConfig?.primary_lane || "interactive_primary";
|
|
74
|
-
const resolvedModel = getLaneValue(routerConfig?.[primaryLane], "primary", "primary");
|
|
75
|
-
const interactivePrimaryModel = getLaneValue(getInteractivePrimaryLane(routerConfig), "primary", "primary");
|
|
76
|
-
|
|
77
|
-
return resolvedModel || interactivePrimaryModel || "default";
|
|
78
|
-
}
|
|
1
|
+
const MODEL_TIER_MAP = Object.freeze({
|
|
2
|
+
sonnet: "anthropic/claude-sonnet-4-6",
|
|
3
|
+
haiku: "anthropic/claude-haiku-4-5",
|
|
4
|
+
opus: "anthropic/claude-opus-4-6",
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const CLAUDE_MODEL_BY_TIER = Object.freeze({
|
|
8
|
+
haiku: "claude-haiku-4-5",
|
|
9
|
+
sonnet: "claude-sonnet-4-6",
|
|
10
|
+
opus: "claude-opus-4-6",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function getLaneValue(lane, camelKey, snakeKey) {
|
|
14
|
+
if (!lane || typeof lane !== "object") return null;
|
|
15
|
+
return lane[camelKey] ?? lane[snakeKey] ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getInteractivePrimaryLane(routerConfig = null) {
|
|
19
|
+
return routerConfig?.interactive_primary || routerConfig?.interactivePrimary || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function inferAnthropicPrefix(routerConfig = null) {
|
|
23
|
+
const lane = getInteractivePrimaryLane(routerConfig);
|
|
24
|
+
const sourceModel = getLaneValue(lane, "manualBump", "manual_bump")
|
|
25
|
+
|| getLaneValue(lane, "primary", "primary");
|
|
26
|
+
|
|
27
|
+
if (typeof sourceModel !== "string") return null;
|
|
28
|
+
if (sourceModel.startsWith("openrouter/anthropic/")) return "openrouter/anthropic";
|
|
29
|
+
if (sourceModel.startsWith("anthropic/")) return "anthropic";
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function inferAnthropicModelForTier(tier, { routerConfig = null } = {}) {
|
|
34
|
+
const prefix = inferAnthropicPrefix(routerConfig);
|
|
35
|
+
const modelName = CLAUDE_MODEL_BY_TIER[tier] || null;
|
|
36
|
+
if (!prefix || !modelName) return null;
|
|
37
|
+
return `${prefix}/${modelName}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function resolveModelOverride(modelOverride, { routerConfig = null } = {}) {
|
|
41
|
+
if (!modelOverride || modelOverride === "default") {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (modelOverride.includes("/")) {
|
|
45
|
+
return modelOverride;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const interactivePrimaryLane = getInteractivePrimaryLane(routerConfig);
|
|
49
|
+
if (modelOverride === "haiku") {
|
|
50
|
+
return getLaneValue(interactivePrimaryLane, "primary", "primary")
|
|
51
|
+
|| inferAnthropicModelForTier("haiku", { routerConfig })
|
|
52
|
+
|| MODEL_TIER_MAP.haiku;
|
|
53
|
+
}
|
|
54
|
+
if (modelOverride === "sonnet") {
|
|
55
|
+
return getLaneValue(interactivePrimaryLane, "manualBump", "manual_bump")
|
|
56
|
+
|| inferAnthropicModelForTier("sonnet", { routerConfig })
|
|
57
|
+
|| MODEL_TIER_MAP.sonnet;
|
|
58
|
+
}
|
|
59
|
+
if (modelOverride === "opus") {
|
|
60
|
+
return inferAnthropicModelForTier("opus", { routerConfig }) || MODEL_TIER_MAP.opus;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return MODEL_TIER_MAP[modelOverride] || modelOverride;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function resolveSessionStatusModel(session, { routerConfig = null, agentConfigs = null } = {}) {
|
|
67
|
+
const modelOverride = resolveModelOverride(session?.model_override, { routerConfig });
|
|
68
|
+
if (modelOverride) {
|
|
69
|
+
return modelOverride;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const agentConfig = agentConfigs?.[session?.agent_id] || null;
|
|
73
|
+
const primaryLane = agentConfig?.primaryLane || agentConfig?.primary_lane || "interactive_primary";
|
|
74
|
+
const resolvedModel = getLaneValue(routerConfig?.[primaryLane], "primary", "primary");
|
|
75
|
+
const interactivePrimaryModel = getLaneValue(getInteractivePrimaryLane(routerConfig), "primary", "primary");
|
|
76
|
+
|
|
77
|
+
return resolvedModel || interactivePrimaryModel || "default";
|
|
78
|
+
}
|
package/src/runtime/network.js
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
export function classifyNetworkFailure(errorOrMessage, { surface = "runtime" } = {}) {
|
|
2
|
-
const error = typeof errorOrMessage === "string" ? null : errorOrMessage;
|
|
3
|
-
const message = String(error?.message || errorOrMessage || "");
|
|
4
|
-
const normalized = message.toLowerCase();
|
|
5
|
-
const code = String(error?.code || "").toUpperCase();
|
|
6
|
-
const errno = String(error?.errno || "").toUpperCase();
|
|
7
|
-
const signalTimedOut =
|
|
8
|
-
error?.name === "TimeoutError" ||
|
|
9
|
-
error?.name === "AbortError" ||
|
|
10
|
-
normalized.includes("timed out") ||
|
|
11
|
-
normalized.includes("timeout");
|
|
12
|
-
|
|
13
|
-
let networkClass = null;
|
|
14
|
-
if (signalTimedOut || code === "ETIMEDOUT" || errno === "ETIMEDOUT") {
|
|
15
|
-
networkClass = normalized.includes("read") ? "read_timeout" : "connect_timeout";
|
|
16
|
-
} else if (code === "EHOSTUNREACH" || errno === "EHOSTUNREACH" || normalized.includes("hostunreach")) {
|
|
17
|
-
networkClass = "host_unreachable";
|
|
18
|
-
} else if (code === "ENETUNREACH" || errno === "ENETUNREACH" || normalized.includes("network is unreachable")) {
|
|
19
|
-
networkClass = "network_unreachable";
|
|
20
|
-
} else if (
|
|
21
|
-
code === "ENOTFOUND" ||
|
|
22
|
-
code === "EAI_AGAIN" ||
|
|
23
|
-
errno === "ENOTFOUND" ||
|
|
24
|
-
errno === "EAI_AGAIN" ||
|
|
25
|
-
normalized.includes("getaddrinfo") ||
|
|
26
|
-
normalized.includes("dns")
|
|
27
|
-
) {
|
|
28
|
-
networkClass = "dns_resolution_failure";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let failureClass = null;
|
|
32
|
-
if (surface === "provider") {
|
|
33
|
-
if (signalTimedOut || networkClass === "connect_timeout" || networkClass === "read_timeout") {
|
|
34
|
-
failureClass = "provider_timeout";
|
|
35
|
-
} else if (networkClass) {
|
|
36
|
-
failureClass = networkClass;
|
|
37
|
-
}
|
|
38
|
-
} else if (surface === "delivery") {
|
|
39
|
-
if (signalTimedOut || networkClass === "connect_timeout" || networkClass === "read_timeout") {
|
|
40
|
-
failureClass = "delivery_timeout";
|
|
41
|
-
} else if (networkClass) {
|
|
42
|
-
failureClass = networkClass;
|
|
43
|
-
}
|
|
44
|
-
} else if (networkClass) {
|
|
45
|
-
failureClass = networkClass;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
failureClass,
|
|
50
|
-
networkClass
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function annotateError(errorOrMessage, options = {}) {
|
|
55
|
-
const error = typeof errorOrMessage === "string" ? new Error(errorOrMessage) : errorOrMessage;
|
|
56
|
-
const classification = classifyNetworkFailure(error, options);
|
|
57
|
-
if (classification.failureClass && !error.failureClass) {
|
|
58
|
-
error.failureClass = classification.failureClass;
|
|
59
|
-
}
|
|
60
|
-
if (classification.networkClass && !error.networkClass) {
|
|
61
|
-
error.networkClass = classification.networkClass;
|
|
62
|
-
}
|
|
63
|
-
return error;
|
|
64
|
-
}
|
|
1
|
+
export function classifyNetworkFailure(errorOrMessage, { surface = "runtime" } = {}) {
|
|
2
|
+
const error = typeof errorOrMessage === "string" ? null : errorOrMessage;
|
|
3
|
+
const message = String(error?.message || errorOrMessage || "");
|
|
4
|
+
const normalized = message.toLowerCase();
|
|
5
|
+
const code = String(error?.code || "").toUpperCase();
|
|
6
|
+
const errno = String(error?.errno || "").toUpperCase();
|
|
7
|
+
const signalTimedOut =
|
|
8
|
+
error?.name === "TimeoutError" ||
|
|
9
|
+
error?.name === "AbortError" ||
|
|
10
|
+
normalized.includes("timed out") ||
|
|
11
|
+
normalized.includes("timeout");
|
|
12
|
+
|
|
13
|
+
let networkClass = null;
|
|
14
|
+
if (signalTimedOut || code === "ETIMEDOUT" || errno === "ETIMEDOUT") {
|
|
15
|
+
networkClass = normalized.includes("read") ? "read_timeout" : "connect_timeout";
|
|
16
|
+
} else if (code === "EHOSTUNREACH" || errno === "EHOSTUNREACH" || normalized.includes("hostunreach")) {
|
|
17
|
+
networkClass = "host_unreachable";
|
|
18
|
+
} else if (code === "ENETUNREACH" || errno === "ENETUNREACH" || normalized.includes("network is unreachable")) {
|
|
19
|
+
networkClass = "network_unreachable";
|
|
20
|
+
} else if (
|
|
21
|
+
code === "ENOTFOUND" ||
|
|
22
|
+
code === "EAI_AGAIN" ||
|
|
23
|
+
errno === "ENOTFOUND" ||
|
|
24
|
+
errno === "EAI_AGAIN" ||
|
|
25
|
+
normalized.includes("getaddrinfo") ||
|
|
26
|
+
normalized.includes("dns")
|
|
27
|
+
) {
|
|
28
|
+
networkClass = "dns_resolution_failure";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let failureClass = null;
|
|
32
|
+
if (surface === "provider") {
|
|
33
|
+
if (signalTimedOut || networkClass === "connect_timeout" || networkClass === "read_timeout") {
|
|
34
|
+
failureClass = "provider_timeout";
|
|
35
|
+
} else if (networkClass) {
|
|
36
|
+
failureClass = networkClass;
|
|
37
|
+
}
|
|
38
|
+
} else if (surface === "delivery") {
|
|
39
|
+
if (signalTimedOut || networkClass === "connect_timeout" || networkClass === "read_timeout") {
|
|
40
|
+
failureClass = "delivery_timeout";
|
|
41
|
+
} else if (networkClass) {
|
|
42
|
+
failureClass = networkClass;
|
|
43
|
+
}
|
|
44
|
+
} else if (networkClass) {
|
|
45
|
+
failureClass = networkClass;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
failureClass,
|
|
50
|
+
networkClass
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function annotateError(errorOrMessage, options = {}) {
|
|
55
|
+
const error = typeof errorOrMessage === "string" ? new Error(errorOrMessage) : errorOrMessage;
|
|
56
|
+
const classification = classifyNetworkFailure(error, options);
|
|
57
|
+
if (classification.failureClass && !error.failureClass) {
|
|
58
|
+
error.failureClass = classification.failureClass;
|
|
59
|
+
}
|
|
60
|
+
if (classification.networkClass && !error.networkClass) {
|
|
61
|
+
error.networkClass = classification.networkClass;
|
|
62
|
+
}
|
|
63
|
+
return error;
|
|
64
|
+
}
|
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { ensureDir, listFilesRecursive, readJson, writeJson } from "../utils/fs.js";
|
|
3
|
-
import { buildRetentionPolicy, pruneJsonBuckets } from "./retention.js";
|
|
4
|
-
import { createRuntimeId } from "../utils/ids.js";
|
|
5
|
-
|
|
6
|
-
function stamp() {
|
|
7
|
-
return new Date().toISOString().replace(/[:.]/g, "-");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function normalizeFilePath(filePath) {
|
|
11
|
-
return path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class NotificationStore {
|
|
15
|
-
constructor({ rootDir, retention = {} }) {
|
|
16
|
-
this.rootDir = rootDir;
|
|
17
|
-
this.setRetentionPolicy(retention);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
setRetentionPolicy(retention = {}) {
|
|
21
|
-
this.retentionPolicy = buildRetentionPolicy(retention, {
|
|
22
|
-
ttlDays: 14,
|
|
23
|
-
maxFilesPerBucket: 1000
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async saveNotification(jobId, notification) {
|
|
28
|
-
const notificationDir = path.join(this.rootDir, jobId);
|
|
29
|
-
await ensureDir(notificationDir);
|
|
30
|
-
const filePath = path.join(notificationDir, `${stamp()}.json`);
|
|
31
|
-
await writeJson(filePath, {
|
|
32
|
-
id: notification.id || createRuntimeId("notification"),
|
|
33
|
-
...notification
|
|
34
|
-
});
|
|
35
|
-
await pruneJsonBuckets(this.rootDir, this.retentionPolicy);
|
|
36
|
-
return filePath;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async updateNotification(filePath, patch = {}) {
|
|
40
|
-
const resolvedPath = normalizeFilePath(filePath);
|
|
41
|
-
const current = await readJson(resolvedPath, null);
|
|
42
|
-
if (!current) {
|
|
43
|
-
throw new Error(`Notification file not found: ${resolvedPath}`);
|
|
44
|
-
}
|
|
45
|
-
const updated = {
|
|
46
|
-
...current,
|
|
47
|
-
...patch
|
|
48
|
-
};
|
|
49
|
-
await writeJson(resolvedPath, updated);
|
|
50
|
-
return {
|
|
51
|
-
filePath: resolvedPath,
|
|
52
|
-
...updated
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async getNotification(filePath) {
|
|
57
|
-
const resolvedPath = normalizeFilePath(filePath);
|
|
58
|
-
const current = await readJson(resolvedPath, null);
|
|
59
|
-
if (!current) return null;
|
|
60
|
-
return {
|
|
61
|
-
filePath: resolvedPath,
|
|
62
|
-
...current
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async getNotifications(filePaths = []) {
|
|
67
|
-
const notifications = await Promise.all((filePaths || []).map((filePath) => this.getNotification(filePath)));
|
|
68
|
-
return notifications.filter(Boolean);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async listRecent(limit = 10) {
|
|
72
|
-
const notifications = await this.listAll();
|
|
73
|
-
return notifications
|
|
74
|
-
.sort((a, b) => String(b.timestamp || "").localeCompare(String(a.timestamp || "")))
|
|
75
|
-
.slice(0, limit);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async listAll() {
|
|
79
|
-
const files = (await listFilesRecursive(this.rootDir)).filter((filePath) => filePath.endsWith(".json"));
|
|
80
|
-
const notifications = [];
|
|
81
|
-
|
|
82
|
-
for (const filePath of files) {
|
|
83
|
-
const data = await readJson(filePath, null);
|
|
84
|
-
if (!data) continue;
|
|
85
|
-
notifications.push({
|
|
86
|
-
filePath,
|
|
87
|
-
...data
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return notifications.sort((a, b) => String(b.timestamp || "").localeCompare(String(a.timestamp || "")));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async prune(options = {}) {
|
|
95
|
-
return pruneJsonBuckets(this.rootDir, this.retentionPolicy, options);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ensureDir, listFilesRecursive, readJson, writeJson } from "../utils/fs.js";
|
|
3
|
+
import { buildRetentionPolicy, pruneJsonBuckets } from "./retention.js";
|
|
4
|
+
import { createRuntimeId } from "../utils/ids.js";
|
|
5
|
+
|
|
6
|
+
function stamp() {
|
|
7
|
+
return new Date().toISOString().replace(/[:.]/g, "-");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalizeFilePath(filePath) {
|
|
11
|
+
return path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class NotificationStore {
|
|
15
|
+
constructor({ rootDir, retention = {} }) {
|
|
16
|
+
this.rootDir = rootDir;
|
|
17
|
+
this.setRetentionPolicy(retention);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setRetentionPolicy(retention = {}) {
|
|
21
|
+
this.retentionPolicy = buildRetentionPolicy(retention, {
|
|
22
|
+
ttlDays: 14,
|
|
23
|
+
maxFilesPerBucket: 1000
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async saveNotification(jobId, notification) {
|
|
28
|
+
const notificationDir = path.join(this.rootDir, jobId);
|
|
29
|
+
await ensureDir(notificationDir);
|
|
30
|
+
const filePath = path.join(notificationDir, `${stamp()}.json`);
|
|
31
|
+
await writeJson(filePath, {
|
|
32
|
+
id: notification.id || createRuntimeId("notification"),
|
|
33
|
+
...notification
|
|
34
|
+
});
|
|
35
|
+
await pruneJsonBuckets(this.rootDir, this.retentionPolicy);
|
|
36
|
+
return filePath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async updateNotification(filePath, patch = {}) {
|
|
40
|
+
const resolvedPath = normalizeFilePath(filePath);
|
|
41
|
+
const current = await readJson(resolvedPath, null);
|
|
42
|
+
if (!current) {
|
|
43
|
+
throw new Error(`Notification file not found: ${resolvedPath}`);
|
|
44
|
+
}
|
|
45
|
+
const updated = {
|
|
46
|
+
...current,
|
|
47
|
+
...patch
|
|
48
|
+
};
|
|
49
|
+
await writeJson(resolvedPath, updated);
|
|
50
|
+
return {
|
|
51
|
+
filePath: resolvedPath,
|
|
52
|
+
...updated
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getNotification(filePath) {
|
|
57
|
+
const resolvedPath = normalizeFilePath(filePath);
|
|
58
|
+
const current = await readJson(resolvedPath, null);
|
|
59
|
+
if (!current) return null;
|
|
60
|
+
return {
|
|
61
|
+
filePath: resolvedPath,
|
|
62
|
+
...current
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getNotifications(filePaths = []) {
|
|
67
|
+
const notifications = await Promise.all((filePaths || []).map((filePath) => this.getNotification(filePath)));
|
|
68
|
+
return notifications.filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async listRecent(limit = 10) {
|
|
72
|
+
const notifications = await this.listAll();
|
|
73
|
+
return notifications
|
|
74
|
+
.sort((a, b) => String(b.timestamp || "").localeCompare(String(a.timestamp || "")))
|
|
75
|
+
.slice(0, limit);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async listAll() {
|
|
79
|
+
const files = (await listFilesRecursive(this.rootDir)).filter((filePath) => filePath.endsWith(".json"));
|
|
80
|
+
const notifications = [];
|
|
81
|
+
|
|
82
|
+
for (const filePath of files) {
|
|
83
|
+
const data = await readJson(filePath, null);
|
|
84
|
+
if (!data) continue;
|
|
85
|
+
notifications.push({
|
|
86
|
+
filePath,
|
|
87
|
+
...data
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return notifications.sort((a, b) => String(b.timestamp || "").localeCompare(String(a.timestamp || "")));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async prune(options = {}) {
|
|
95
|
+
return pruneJsonBuckets(this.rootDir, this.retentionPolicy, options);
|
|
96
|
+
}
|
|
97
|
+
}
|