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,98 +1,98 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
|
|
4
|
-
const execFileAsync = promisify(execFile);
|
|
5
|
-
|
|
6
|
-
function readFlag(name) {
|
|
7
|
-
const raw = process.env[name];
|
|
8
|
-
return raw === "1" || raw === "true";
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function parseJson(text) {
|
|
12
|
-
try {
|
|
13
|
-
return JSON.parse(String(text || "").trim() || "null");
|
|
14
|
-
} catch {
|
|
15
|
-
return {
|
|
16
|
-
raw: String(text || "")
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class OpenClawPeerDeliveryAdapter {
|
|
22
|
-
static adapterId = "openclaw_peer";
|
|
23
|
-
|
|
24
|
-
constructor({ execFileImpl } = {}) {
|
|
25
|
-
this.execFileImpl = execFileImpl || execFileAsync;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async deliver(notification, context = {}) {
|
|
29
|
-
const profile = context.profileConfig || {};
|
|
30
|
-
const target = context.target || {};
|
|
31
|
-
const dryRun = profile.dryRun === true;
|
|
32
|
-
|
|
33
|
-
if (!dryRun && !readFlag("NEMORIS_ALLOW_PEER_DELIVERY")) {
|
|
34
|
-
throw new Error("Peer delivery disabled. Set NEMORIS_ALLOW_PEER_DELIVERY=1 to enable live peer delivery.");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const sessionKey = target.sessionKey || profile.sessionKey;
|
|
38
|
-
if (!sessionKey) {
|
|
39
|
-
throw new Error("Peer delivery requires a sessionKey.");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (dryRun) {
|
|
43
|
-
return {
|
|
44
|
-
status: "peer_preview",
|
|
45
|
-
adapter: OpenClawPeerDeliveryAdapter.adapterId,
|
|
46
|
-
profile: context.profileName || null,
|
|
47
|
-
peerId: target.peerId || null,
|
|
48
|
-
sessionKey,
|
|
49
|
-
command: {
|
|
50
|
-
bin: "openclaw",
|
|
51
|
-
args: [
|
|
52
|
-
"agent",
|
|
53
|
-
"--message",
|
|
54
|
-
String(notification.message || ""),
|
|
55
|
-
"--session-id",
|
|
56
|
-
String(sessionKey),
|
|
57
|
-
"--json"
|
|
58
|
-
]
|
|
59
|
-
},
|
|
60
|
-
response: {
|
|
61
|
-
ok: true,
|
|
62
|
-
preview: true
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const args = [
|
|
68
|
-
"agent",
|
|
69
|
-
"--message",
|
|
70
|
-
String(notification.message || ""),
|
|
71
|
-
"--session-id",
|
|
72
|
-
String(sessionKey),
|
|
73
|
-
"--json"
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
if (profile.agentId) {
|
|
77
|
-
args.push("--agent", String(profile.agentId));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const { stdout } = await this.execFileImpl("openclaw", args, {
|
|
81
|
-
timeout: Number(profile.timeoutMs || 15000),
|
|
82
|
-
maxBuffer: 1024 * 1024
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
status: dryRun ? "peer_preview" : "peer_sent",
|
|
87
|
-
adapter: OpenClawPeerDeliveryAdapter.adapterId,
|
|
88
|
-
profile: context.profileName || null,
|
|
89
|
-
peerId: target.peerId || null,
|
|
90
|
-
sessionKey,
|
|
91
|
-
command: {
|
|
92
|
-
bin: "openclaw",
|
|
93
|
-
args
|
|
94
|
-
},
|
|
95
|
-
response: parseJson(stdout)
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
|
|
6
|
+
function readFlag(name) {
|
|
7
|
+
const raw = process.env[name];
|
|
8
|
+
return raw === "1" || raw === "true";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseJson(text) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(String(text || "").trim() || "null");
|
|
14
|
+
} catch {
|
|
15
|
+
return {
|
|
16
|
+
raw: String(text || "")
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class OpenClawPeerDeliveryAdapter {
|
|
22
|
+
static adapterId = "openclaw_peer";
|
|
23
|
+
|
|
24
|
+
constructor({ execFileImpl } = {}) {
|
|
25
|
+
this.execFileImpl = execFileImpl || execFileAsync;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async deliver(notification, context = {}) {
|
|
29
|
+
const profile = context.profileConfig || {};
|
|
30
|
+
const target = context.target || {};
|
|
31
|
+
const dryRun = profile.dryRun === true;
|
|
32
|
+
|
|
33
|
+
if (!dryRun && !readFlag("NEMORIS_ALLOW_PEER_DELIVERY")) {
|
|
34
|
+
throw new Error("Peer delivery disabled. Set NEMORIS_ALLOW_PEER_DELIVERY=1 to enable live peer delivery.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sessionKey = target.sessionKey || profile.sessionKey;
|
|
38
|
+
if (!sessionKey) {
|
|
39
|
+
throw new Error("Peer delivery requires a sessionKey.");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (dryRun) {
|
|
43
|
+
return {
|
|
44
|
+
status: "peer_preview",
|
|
45
|
+
adapter: OpenClawPeerDeliveryAdapter.adapterId,
|
|
46
|
+
profile: context.profileName || null,
|
|
47
|
+
peerId: target.peerId || null,
|
|
48
|
+
sessionKey,
|
|
49
|
+
command: {
|
|
50
|
+
bin: "openclaw",
|
|
51
|
+
args: [
|
|
52
|
+
"agent",
|
|
53
|
+
"--message",
|
|
54
|
+
String(notification.message || ""),
|
|
55
|
+
"--session-id",
|
|
56
|
+
String(sessionKey),
|
|
57
|
+
"--json"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
response: {
|
|
61
|
+
ok: true,
|
|
62
|
+
preview: true
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const args = [
|
|
68
|
+
"agent",
|
|
69
|
+
"--message",
|
|
70
|
+
String(notification.message || ""),
|
|
71
|
+
"--session-id",
|
|
72
|
+
String(sessionKey),
|
|
73
|
+
"--json"
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
if (profile.agentId) {
|
|
77
|
+
args.push("--agent", String(profile.agentId));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { stdout } = await this.execFileImpl("openclaw", args, {
|
|
81
|
+
timeout: Number(profile.timeoutMs || 15000),
|
|
82
|
+
maxBuffer: 1024 * 1024
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
status: dryRun ? "peer_preview" : "peer_sent",
|
|
87
|
+
adapter: OpenClawPeerDeliveryAdapter.adapterId,
|
|
88
|
+
profile: context.profileName || null,
|
|
89
|
+
peerId: target.peerId || null,
|
|
90
|
+
sessionKey,
|
|
91
|
+
command: {
|
|
92
|
+
bin: "openclaw",
|
|
93
|
+
args
|
|
94
|
+
},
|
|
95
|
+
response: parseJson(stdout)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export class ShadowDeliveryAdapter {
|
|
2
|
-
static adapterId = "shadow";
|
|
3
|
-
|
|
4
|
-
async deliver(notification, context = {}) {
|
|
5
|
-
return {
|
|
6
|
-
status: "shadowed",
|
|
7
|
-
adapter: ShadowDeliveryAdapter.adapterId,
|
|
8
|
-
target: notification.target,
|
|
9
|
-
message: notification.message,
|
|
10
|
-
profile: context.profileName || null
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
}
|
|
1
|
+
export class ShadowDeliveryAdapter {
|
|
2
|
+
static adapterId = "shadow";
|
|
3
|
+
|
|
4
|
+
async deliver(notification, context = {}) {
|
|
5
|
+
return {
|
|
6
|
+
status: "shadowed",
|
|
7
|
+
adapter: ShadowDeliveryAdapter.adapterId,
|
|
8
|
+
target: notification.target,
|
|
9
|
+
message: notification.message,
|
|
10
|
+
profile: context.profileName || null
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import { OUTBOUND_ADDRESS_POLICY } from "../../security/ssrf-check.js";
|
|
2
|
-
import { fetchWithOutboundPolicy } from "../ssrf.js";
|
|
3
|
-
|
|
4
|
-
function readFlag(name) {
|
|
5
|
-
const raw = process.env[name];
|
|
6
|
-
return raw === "1" || raw === "true";
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class StandaloneHttpDeliveryAdapter {
|
|
10
|
-
static adapterId = "standalone_http";
|
|
11
|
-
|
|
12
|
-
constructor({ fetchImpl } = {}) {
|
|
13
|
-
this.fetchImpl = fetchImpl || globalThis.fetch;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async deliver(notification, context = {}) {
|
|
17
|
-
if (!this.fetchImpl) {
|
|
18
|
-
throw new Error("No fetch implementation available for standalone HTTP delivery.");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const profile = context.profileConfig || {};
|
|
22
|
-
const dryRun = profile.dryRun === true;
|
|
23
|
-
const baseUrl = String(profile.baseUrl || "").replace(/\/$/, "");
|
|
24
|
-
if (!baseUrl) {
|
|
25
|
-
throw new Error("Standalone HTTP delivery profile is missing baseUrl.");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!dryRun && !readFlag("NEMORIS_ALLOW_STANDALONE_HTTP_DELIVERY")) {
|
|
29
|
-
throw new Error("Standalone HTTP delivery disabled. Set NEMORIS_ALLOW_STANDALONE_HTTP_DELIVERY=1 to enable live sends.");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const headers = {
|
|
33
|
-
"content-type": "application/json"
|
|
34
|
-
};
|
|
35
|
-
const tokenEnv = profile.authTokenEnv || null;
|
|
36
|
-
const token = tokenEnv ? process.env[tokenEnv] || null : null;
|
|
37
|
-
if (token) {
|
|
38
|
-
headers.authorization = `Bearer ${token}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const payload = {
|
|
42
|
-
timestamp: new Date().toISOString(),
|
|
43
|
-
jobId: notification.jobId,
|
|
44
|
-
stage: notification.stage,
|
|
45
|
-
status: notification.status || "ready",
|
|
46
|
-
profile: context.profileName || null,
|
|
47
|
-
target: context.target || notification.target || null,
|
|
48
|
-
message: notification.message || ""
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
if (dryRun) {
|
|
52
|
-
return {
|
|
53
|
-
status: "http_preview",
|
|
54
|
-
adapter: StandaloneHttpDeliveryAdapter.adapterId,
|
|
55
|
-
endpoint: `${baseUrl}/messages`,
|
|
56
|
-
payload
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Standalone delivery is operator-configured. Allow loopback for the local
|
|
61
|
-
// transport server, but block other private/reserved destinations.
|
|
62
|
-
const response = await fetchWithOutboundPolicy(
|
|
63
|
-
`${baseUrl}/messages`,
|
|
64
|
-
{
|
|
65
|
-
method: "POST",
|
|
66
|
-
headers,
|
|
67
|
-
body: JSON.stringify(payload),
|
|
68
|
-
signal: typeof AbortSignal?.timeout === "function" ? AbortSignal.timeout(Number(profile.timeoutMs || 5000)) : undefined
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
fetchImpl: this.fetchImpl,
|
|
72
|
-
surface: "standalone_http_delivery",
|
|
73
|
-
addressPolicy: OUTBOUND_ADDRESS_POLICY.BLOCK_PRIVATE,
|
|
74
|
-
allowLoopbackAddresses: true,
|
|
75
|
-
privateAddressMessage: `Standalone HTTP delivery blocked — target resolves to a private/reserved IP address for ${baseUrl}.`,
|
|
76
|
-
}
|
|
77
|
-
);
|
|
78
|
-
const text = await response.text();
|
|
79
|
-
let data;
|
|
80
|
-
try {
|
|
81
|
-
data = text ? JSON.parse(text) : null;
|
|
82
|
-
} catch {
|
|
83
|
-
data = { raw: text };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!response.ok) {
|
|
87
|
-
throw new Error(`Standalone HTTP delivery failed with ${response.status}: ${typeof data === "string" ? data : JSON.stringify(data)}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
status: "http_sent",
|
|
92
|
-
adapter: StandaloneHttpDeliveryAdapter.adapterId,
|
|
93
|
-
endpoint: `${baseUrl}/messages`,
|
|
94
|
-
payload,
|
|
95
|
-
response: data
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
1
|
+
import { OUTBOUND_ADDRESS_POLICY } from "../../security/ssrf-check.js";
|
|
2
|
+
import { fetchWithOutboundPolicy } from "../ssrf.js";
|
|
3
|
+
|
|
4
|
+
function readFlag(name) {
|
|
5
|
+
const raw = process.env[name];
|
|
6
|
+
return raw === "1" || raw === "true";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class StandaloneHttpDeliveryAdapter {
|
|
10
|
+
static adapterId = "standalone_http";
|
|
11
|
+
|
|
12
|
+
constructor({ fetchImpl } = {}) {
|
|
13
|
+
this.fetchImpl = fetchImpl || globalThis.fetch;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async deliver(notification, context = {}) {
|
|
17
|
+
if (!this.fetchImpl) {
|
|
18
|
+
throw new Error("No fetch implementation available for standalone HTTP delivery.");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const profile = context.profileConfig || {};
|
|
22
|
+
const dryRun = profile.dryRun === true;
|
|
23
|
+
const baseUrl = String(profile.baseUrl || "").replace(/\/$/, "");
|
|
24
|
+
if (!baseUrl) {
|
|
25
|
+
throw new Error("Standalone HTTP delivery profile is missing baseUrl.");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!dryRun && !readFlag("NEMORIS_ALLOW_STANDALONE_HTTP_DELIVERY")) {
|
|
29
|
+
throw new Error("Standalone HTTP delivery disabled. Set NEMORIS_ALLOW_STANDALONE_HTTP_DELIVERY=1 to enable live sends.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const headers = {
|
|
33
|
+
"content-type": "application/json"
|
|
34
|
+
};
|
|
35
|
+
const tokenEnv = profile.authTokenEnv || null;
|
|
36
|
+
const token = tokenEnv ? process.env[tokenEnv] || null : null;
|
|
37
|
+
if (token) {
|
|
38
|
+
headers.authorization = `Bearer ${token}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const payload = {
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
jobId: notification.jobId,
|
|
44
|
+
stage: notification.stage,
|
|
45
|
+
status: notification.status || "ready",
|
|
46
|
+
profile: context.profileName || null,
|
|
47
|
+
target: context.target || notification.target || null,
|
|
48
|
+
message: notification.message || ""
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (dryRun) {
|
|
52
|
+
return {
|
|
53
|
+
status: "http_preview",
|
|
54
|
+
adapter: StandaloneHttpDeliveryAdapter.adapterId,
|
|
55
|
+
endpoint: `${baseUrl}/messages`,
|
|
56
|
+
payload
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Standalone delivery is operator-configured. Allow loopback for the local
|
|
61
|
+
// transport server, but block other private/reserved destinations.
|
|
62
|
+
const response = await fetchWithOutboundPolicy(
|
|
63
|
+
`${baseUrl}/messages`,
|
|
64
|
+
{
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers,
|
|
67
|
+
body: JSON.stringify(payload),
|
|
68
|
+
signal: typeof AbortSignal?.timeout === "function" ? AbortSignal.timeout(Number(profile.timeoutMs || 5000)) : undefined
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
fetchImpl: this.fetchImpl,
|
|
72
|
+
surface: "standalone_http_delivery",
|
|
73
|
+
addressPolicy: OUTBOUND_ADDRESS_POLICY.BLOCK_PRIVATE,
|
|
74
|
+
allowLoopbackAddresses: true,
|
|
75
|
+
privateAddressMessage: `Standalone HTTP delivery blocked — target resolves to a private/reserved IP address for ${baseUrl}.`,
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
const text = await response.text();
|
|
79
|
+
let data;
|
|
80
|
+
try {
|
|
81
|
+
data = text ? JSON.parse(text) : null;
|
|
82
|
+
} catch {
|
|
83
|
+
data = { raw: text };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`Standalone HTTP delivery failed with ${response.status}: ${typeof data === "string" ? data : JSON.stringify(data)}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
status: "http_sent",
|
|
92
|
+
adapter: StandaloneHttpDeliveryAdapter.adapterId,
|
|
93
|
+
endpoint: `${baseUrl}/messages`,
|
|
94
|
+
payload,
|
|
95
|
+
response: data
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
import { getDaemonEnv } from "../../onboarding/platform.js";
|
|
2
|
-
|
|
3
|
-
function readFlag(name) {
|
|
4
|
-
const raw = process.env[name];
|
|
5
|
-
return raw === "1" || raw === "true";
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export async function resolveSecretValue(name, { execFileImpl, platform } = {}) {
|
|
9
|
-
return getDaemonEnv(name, { execFileImpl, platform });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class TelegramDeliveryAdapter {
|
|
13
|
-
static adapterId = "telegram";
|
|
14
|
-
|
|
15
|
-
constructor({ fetchImpl, secretResolver, execFileImpl } = {}) {
|
|
16
|
-
this.fetchImpl = fetchImpl || globalThis.fetch;
|
|
17
|
-
this.secretResolver =
|
|
18
|
-
secretResolver ||
|
|
19
|
-
((name) =>
|
|
20
|
-
resolveSecretValue(name, {
|
|
21
|
-
execFileImpl
|
|
22
|
-
}));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
requireFetch() {
|
|
26
|
-
if (!this.fetchImpl) {
|
|
27
|
-
throw new Error("Telegram delivery requires fetch support.");
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async deliver(notification, context = {}) {
|
|
32
|
-
this.requireFetch();
|
|
33
|
-
|
|
34
|
-
if (!readFlag("NEMORIS_ALLOW_TELEGRAM_DELIVERY")) {
|
|
35
|
-
throw new Error("Telegram delivery disabled. Set NEMORIS_ALLOW_TELEGRAM_DELIVERY=1 to enable live delivery.");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const profile = context.profileConfig || {};
|
|
39
|
-
const tokenEnv = profile.botTokenEnv;
|
|
40
|
-
const secret = await this.secretResolver(tokenEnv);
|
|
41
|
-
const token = secret.value;
|
|
42
|
-
if (!token) {
|
|
43
|
-
throw new Error(
|
|
44
|
-
`Missing Telegram bot token env ${tokenEnv || "(unset profile token env)"} in process env or daemon config.`
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Use notification's chatId for interactive (Telegram-sourced) jobs,
|
|
49
|
-
// fall back to static profile chatId for scheduled job delivery.
|
|
50
|
-
const chatId = (notification.source === "telegram" && notification.chatId)
|
|
51
|
-
? notification.chatId
|
|
52
|
-
: profile.chatId;
|
|
53
|
-
if (!chatId) {
|
|
54
|
-
throw new Error("Telegram delivery profile is missing chatId.");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Split long messages and send with delay between chunks
|
|
58
|
-
const { splitTelegramMessage } = await import("../telegram-inbound.js");
|
|
59
|
-
const chunks = splitTelegramMessage(notification.message);
|
|
60
|
-
let lastResponse;
|
|
61
|
-
|
|
62
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
63
|
-
if (i > 0) {
|
|
64
|
-
await new Promise((resolve) => setTimeout(resolve, 250)); // 250ms delay between chunks
|
|
65
|
-
}
|
|
66
|
-
const url = `https://api.telegram.org/bot${token}/sendMessage`;
|
|
67
|
-
lastResponse = await this.fetchImpl(url, {
|
|
68
|
-
method: "POST",
|
|
69
|
-
headers: {
|
|
70
|
-
"content-type": "application/json"
|
|
71
|
-
},
|
|
72
|
-
body: JSON.stringify({
|
|
73
|
-
chat_id: chatId,
|
|
74
|
-
text: chunks[i],
|
|
75
|
-
})
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const response = lastResponse;
|
|
80
|
-
|
|
81
|
-
const raw = await response.text();
|
|
82
|
-
let parsed;
|
|
83
|
-
try {
|
|
84
|
-
parsed = JSON.parse(raw);
|
|
85
|
-
} catch {
|
|
86
|
-
parsed = raw;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!response.ok) {
|
|
90
|
-
throw new Error(`Telegram delivery failed with status ${response.status}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
status: "sent",
|
|
95
|
-
adapter: TelegramDeliveryAdapter.adapterId,
|
|
96
|
-
authSource: secret.source,
|
|
97
|
-
target: {
|
|
98
|
-
mode: "telegram",
|
|
99
|
-
chatId
|
|
100
|
-
},
|
|
101
|
-
response: parsed
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
1
|
+
import { getDaemonEnv } from "../../onboarding/platform.js";
|
|
2
|
+
|
|
3
|
+
function readFlag(name) {
|
|
4
|
+
const raw = process.env[name];
|
|
5
|
+
return raw === "1" || raw === "true";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function resolveSecretValue(name, { execFileImpl, platform } = {}) {
|
|
9
|
+
return getDaemonEnv(name, { execFileImpl, platform });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class TelegramDeliveryAdapter {
|
|
13
|
+
static adapterId = "telegram";
|
|
14
|
+
|
|
15
|
+
constructor({ fetchImpl, secretResolver, execFileImpl } = {}) {
|
|
16
|
+
this.fetchImpl = fetchImpl || globalThis.fetch;
|
|
17
|
+
this.secretResolver =
|
|
18
|
+
secretResolver ||
|
|
19
|
+
((name) =>
|
|
20
|
+
resolveSecretValue(name, {
|
|
21
|
+
execFileImpl
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
requireFetch() {
|
|
26
|
+
if (!this.fetchImpl) {
|
|
27
|
+
throw new Error("Telegram delivery requires fetch support.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async deliver(notification, context = {}) {
|
|
32
|
+
this.requireFetch();
|
|
33
|
+
|
|
34
|
+
if (!readFlag("NEMORIS_ALLOW_TELEGRAM_DELIVERY")) {
|
|
35
|
+
throw new Error("Telegram delivery disabled. Set NEMORIS_ALLOW_TELEGRAM_DELIVERY=1 to enable live delivery.");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const profile = context.profileConfig || {};
|
|
39
|
+
const tokenEnv = profile.botTokenEnv;
|
|
40
|
+
const secret = await this.secretResolver(tokenEnv);
|
|
41
|
+
const token = secret.value;
|
|
42
|
+
if (!token) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Missing Telegram bot token env ${tokenEnv || "(unset profile token env)"} in process env or daemon config.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Use notification's chatId for interactive (Telegram-sourced) jobs,
|
|
49
|
+
// fall back to static profile chatId for scheduled job delivery.
|
|
50
|
+
const chatId = (notification.source === "telegram" && notification.chatId)
|
|
51
|
+
? notification.chatId
|
|
52
|
+
: profile.chatId;
|
|
53
|
+
if (!chatId) {
|
|
54
|
+
throw new Error("Telegram delivery profile is missing chatId.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Split long messages and send with delay between chunks
|
|
58
|
+
const { splitTelegramMessage } = await import("../telegram-inbound.js");
|
|
59
|
+
const chunks = splitTelegramMessage(notification.message);
|
|
60
|
+
let lastResponse;
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
63
|
+
if (i > 0) {
|
|
64
|
+
await new Promise((resolve) => setTimeout(resolve, 250)); // 250ms delay between chunks
|
|
65
|
+
}
|
|
66
|
+
const url = `https://api.telegram.org/bot${token}/sendMessage`;
|
|
67
|
+
lastResponse = await this.fetchImpl(url, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"content-type": "application/json"
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
chat_id: chatId,
|
|
74
|
+
text: chunks[i],
|
|
75
|
+
})
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const response = lastResponse;
|
|
80
|
+
|
|
81
|
+
const raw = await response.text();
|
|
82
|
+
let parsed;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(raw);
|
|
85
|
+
} catch {
|
|
86
|
+
parsed = raw;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
throw new Error(`Telegram delivery failed with status ${response.status}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
status: "sent",
|
|
95
|
+
adapter: TelegramDeliveryAdapter.adapterId,
|
|
96
|
+
authSource: secret.source,
|
|
97
|
+
target: {
|
|
98
|
+
mode: "telegram",
|
|
99
|
+
chatId
|
|
100
|
+
},
|
|
101
|
+
response: parsed
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|