@onsignet/daemon 0.1.2 → 0.1.3
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/README.md +60 -0
- package/dist/audit-log 2.js +75 -0
- package/dist/audit-log.d 2.ts +23 -0
- package/dist/config 2.js +104 -0
- package/dist/config.d 3.ts +30 -0
- package/dist/config.js 2.map +1 -0
- package/dist/errors 2.js +24 -0
- package/dist/errors.d.ts 2.map +1 -0
- package/dist/errors.js 2.map +1 -0
- package/dist/index 2.js +524 -0
- package/dist/index.d.ts 3.map +1 -0
- package/dist/policies.d.ts 2.map +1 -0
- package/dist/policies.js 3.map +1 -0
- package/dist/relay-client.d.ts 2.map +1 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @onsignet/daemon
|
|
2
|
+
|
|
3
|
+
**Signet network daemon.** Sidecar process that handles cryptographic identity, relay connectivity, and the HTTP API for agent frameworks.
|
|
4
|
+
|
|
5
|
+
> You don't need to install this directly. Install [`signet-agent`](https://www.npmjs.com/package/signet-agent) and run `signet init` — the daemon starts automatically.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Generates and manages your agent's **Ed25519 keypair**
|
|
10
|
+
- Connects to the **Signet relay** (`wss://relay.onsignet.com`) via signed challenge auth
|
|
11
|
+
- Exposes a **local HTTP API** at `http://127.0.0.1:8766` for your agent framework
|
|
12
|
+
- Proxies directory and profile calls to `https://api.onsignet.com`
|
|
13
|
+
- Enforces **message policies** (auto-approve, require human approval, etc.)
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g signet-agent
|
|
19
|
+
signet init # auto-starts this daemon
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Manual usage
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
node dist/index.js
|
|
26
|
+
# or with env vars:
|
|
27
|
+
SIGNET_RELAY_URL=wss://relay.onsignet.com node dist/index.js
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
Config file: `~/.agentmesh/config.json`
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"relay_url": "wss://relay.onsignet.com",
|
|
37
|
+
"directory_api_url": "https://api.onsignet.com",
|
|
38
|
+
"http_port": 8766
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
All fields have sensible defaults — no config needed for standard use.
|
|
43
|
+
|
|
44
|
+
## HTTP API
|
|
45
|
+
|
|
46
|
+
The daemon exposes a local API at `http://127.0.0.1:8766`:
|
|
47
|
+
|
|
48
|
+
| Endpoint | Description |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `GET /status` | Connection status and node ID |
|
|
51
|
+
| `POST /send` | Send an encrypted message |
|
|
52
|
+
| `GET /messages` | Poll for incoming messages |
|
|
53
|
+
| `POST /directory/agents` | Register on the directory |
|
|
54
|
+
| `GET /directory/search` | Search the agent directory |
|
|
55
|
+
| `GET /contacts` | List saved contacts |
|
|
56
|
+
| `GET /pending` | Messages pending approval |
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuditLog = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Append-only JSONL audit log with SHA-256 hash chain.
|
|
6
|
+
*/
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const node_crypto_1 = require("node:crypto");
|
|
10
|
+
const LOG_FILE = "audit.log";
|
|
11
|
+
const MODE_APPEND = 0o600;
|
|
12
|
+
const ZERO_HASH = "0";
|
|
13
|
+
class AuditLog {
|
|
14
|
+
path;
|
|
15
|
+
lastHash = ZERO_HASH;
|
|
16
|
+
constructor(dataDir) {
|
|
17
|
+
this.path = (0, node_path_1.join)(dataDir, LOG_FILE);
|
|
18
|
+
if (!(0, node_fs_1.existsSync)(this.path)) {
|
|
19
|
+
const dir = (0, node_path_1.join)(dataDir);
|
|
20
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
21
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
22
|
+
this.lastHash = ZERO_HASH;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const lines = (0, node_fs_1.readFileSync)(this.path, "utf8").trim().split("\n").filter(Boolean);
|
|
26
|
+
if (lines.length > 0) {
|
|
27
|
+
const last = lines[lines.length - 1];
|
|
28
|
+
const parsed = JSON.parse(last);
|
|
29
|
+
this.lastHash = parsed.hash ?? ZERO_HASH;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
hashChain(prevHash, line) {
|
|
34
|
+
return (0, node_crypto_1.createHash)("sha256").update(prevHash + line).digest("hex");
|
|
35
|
+
}
|
|
36
|
+
append(entry) {
|
|
37
|
+
const withPrev = { ...entry, prevHash: this.lastHash };
|
|
38
|
+
const line = JSON.stringify(withPrev) + "\n";
|
|
39
|
+
const hash = this.hashChain(this.lastHash, line);
|
|
40
|
+
const record = { ...withPrev, hash };
|
|
41
|
+
(0, node_fs_1.appendFileSync)(this.path, JSON.stringify(record) + "\n", { mode: MODE_APPEND });
|
|
42
|
+
this.lastHash = hash;
|
|
43
|
+
}
|
|
44
|
+
/** Read the last N entries (newest last). Returns entries and whether the chain is valid. */
|
|
45
|
+
readRecent(limit) {
|
|
46
|
+
if (!(0, node_fs_1.existsSync)(this.path)) {
|
|
47
|
+
return { entries: [], lastHash: ZERO_HASH, chainValid: true };
|
|
48
|
+
}
|
|
49
|
+
const content = (0, node_fs_1.readFileSync)(this.path, "utf8").trim();
|
|
50
|
+
const lines = content ? content.split("\n").filter(Boolean) : [];
|
|
51
|
+
let prevHash = ZERO_HASH;
|
|
52
|
+
let chainValid = true;
|
|
53
|
+
const entries = [];
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
const record = JSON.parse(line);
|
|
56
|
+
const withPrev = (({ hash: _h, ...e }) => e)(record);
|
|
57
|
+
const lineThatWasHashed = JSON.stringify(withPrev) + "\n";
|
|
58
|
+
const expectedHash = this.hashChain(prevHash, lineThatWasHashed);
|
|
59
|
+
if (record.hash !== expectedHash)
|
|
60
|
+
chainValid = false;
|
|
61
|
+
prevHash = record.hash ?? prevHash;
|
|
62
|
+
entries.push(withPrev);
|
|
63
|
+
}
|
|
64
|
+
const lastHash = lines.length > 0
|
|
65
|
+
? JSON.parse(lines[lines.length - 1]).hash ?? ZERO_HASH
|
|
66
|
+
: ZERO_HASH;
|
|
67
|
+
return {
|
|
68
|
+
entries: entries.slice(-limit),
|
|
69
|
+
lastHash,
|
|
70
|
+
chainValid,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.AuditLog = AuditLog;
|
|
75
|
+
//# sourceMappingURL=audit-log.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type AuditEventType = "message_sent" | "message_received" | "connection_up" | "connection_down" | "policy_change" | "message_approved" | "message_denied" | "key_revoked" | "key_self_revoked" | "key_rotated" | "message_rejected_revoked" | "attestation_invalid" | "capability_invalid" | "message_filtered_tier";
|
|
2
|
+
export interface AuditEntry {
|
|
3
|
+
timestamp: string;
|
|
4
|
+
event: AuditEventType;
|
|
5
|
+
messageId?: string;
|
|
6
|
+
from?: string;
|
|
7
|
+
to?: string;
|
|
8
|
+
prevHash: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class AuditLog {
|
|
11
|
+
private path;
|
|
12
|
+
private lastHash;
|
|
13
|
+
constructor(dataDir: string);
|
|
14
|
+
private hashChain;
|
|
15
|
+
append(entry: Omit<AuditEntry, "prevHash">): void;
|
|
16
|
+
/** Read the last N entries (newest last). Returns entries and whether the chain is valid. */
|
|
17
|
+
readRecent(limit: number): {
|
|
18
|
+
entries: AuditEntry[];
|
|
19
|
+
lastHash: string;
|
|
20
|
+
chainValid: boolean;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=audit-log.d.ts.map
|
package/dist/config 2.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_DATA_DIR = void 0;
|
|
4
|
+
exports.loadConfig = loadConfig;
|
|
5
|
+
/**
|
|
6
|
+
* Daemon config: ~/.agentmesh/config.json (or ~/.config/agentmesh/config.json on Linux),
|
|
7
|
+
* then dataDir/config.json, then env. Env overrides file. No secrets in config file.
|
|
8
|
+
*/
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const node_os_1 = require("node:os");
|
|
12
|
+
exports.DEFAULT_DATA_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".Signet");
|
|
13
|
+
const DEFAULT_RELAY_URL = "wss://relay.onsignet.com";
|
|
14
|
+
const DEFAULT_HTTP_PORT = 8766;
|
|
15
|
+
function loadJson(path) {
|
|
16
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const raw = (0, node_fs_1.readFileSync)(path, "utf8");
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getStandardConfigPaths() {
|
|
27
|
+
const home = (0, node_os_1.homedir)();
|
|
28
|
+
const paths = [(0, node_path_1.join)(home, ".agentmesh", "config.json")];
|
|
29
|
+
if ((0, node_os_1.platform)() === "linux") {
|
|
30
|
+
paths.push((0, node_path_1.join)(home, ".config", "agentmesh", "config.json"));
|
|
31
|
+
}
|
|
32
|
+
return paths;
|
|
33
|
+
}
|
|
34
|
+
function isValidUrl(s) {
|
|
35
|
+
try {
|
|
36
|
+
const u = new URL(s);
|
|
37
|
+
return u.protocol === "ws:" || u.protocol === "wss:" || u.protocol === "http:" || u.protocol === "https:";
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function validateAndApply(fromFile, dataDir) {
|
|
44
|
+
const relayUrl = process.env.SIGNET_RELAY_URL ?? fromFile?.relayUrl ?? fromFile?.relay_url ?? DEFAULT_RELAY_URL;
|
|
45
|
+
const httpPortRaw = process.env.SIGNET_HTTP_PORT ?? fromFile?.httpPort ?? fromFile?.http_port ?? DEFAULT_HTTP_PORT;
|
|
46
|
+
const httpPort = Number(httpPortRaw);
|
|
47
|
+
const httpHost = process.env.SIGNET_HTTP_HOST ?? fromFile?.httpHost ?? fromFile?.http_host ?? "127.0.0.1";
|
|
48
|
+
const resolvedDataDir = process.env.SIGNET_DATA_DIR ?? fromFile?.data_dir ?? dataDir;
|
|
49
|
+
if (!Number.isInteger(httpPort) || httpPort < 1 || httpPort > 65535) {
|
|
50
|
+
throw new Error(`Invalid config: httpPort must be 1-65535, got: ${httpPortRaw}`);
|
|
51
|
+
}
|
|
52
|
+
if (!isValidUrl(relayUrl)) {
|
|
53
|
+
throw new Error(`Invalid config: relayUrl must be a valid ws/wss/http/https URL, got: ${relayUrl}`);
|
|
54
|
+
}
|
|
55
|
+
const marketplaceApiUrl = process.env.MARKETPLACE_API_URL ?? fromFile?.marketplace_api_url;
|
|
56
|
+
const directoryApiUrl = process.env.DIRECTORY_API_URL ?? fromFile?.directory_api_url ?? marketplaceApiUrl;
|
|
57
|
+
const openclawWebhookUrl = process.env.SIGNET_OPENCLAW_WEBHOOK_URL ?? fromFile?.openclaw_webhook_url;
|
|
58
|
+
const openclawWebhookSecret = process.env.SIGNET_OPENCLAW_WEBHOOK_SECRET ?? fromFile?.openclaw_webhook_secret;
|
|
59
|
+
return {
|
|
60
|
+
relayUrl,
|
|
61
|
+
httpPort,
|
|
62
|
+
httpHost,
|
|
63
|
+
dataDir: resolvedDataDir,
|
|
64
|
+
...(marketplaceApiUrl !== undefined && marketplaceApiUrl !== "" && { marketplaceApiUrl }),
|
|
65
|
+
...(directoryApiUrl !== undefined && directoryApiUrl !== "" && { directoryApiUrl }),
|
|
66
|
+
...(openclawWebhookUrl !== undefined && openclawWebhookUrl !== "" && { openclawWebhookUrl }),
|
|
67
|
+
...(openclawWebhookSecret !== undefined && openclawWebhookSecret !== "" && { openclawWebhookSecret }),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function loadConfig() {
|
|
71
|
+
const defaultDataDir = process.env.SIGNET_DATA_DIR ?? exports.DEFAULT_DATA_DIR;
|
|
72
|
+
const standardPaths = getStandardConfigPaths();
|
|
73
|
+
const dataDirConfigPath = (0, node_path_1.join)(defaultDataDir, "config.json");
|
|
74
|
+
let fromFile = null;
|
|
75
|
+
for (const p of standardPaths) {
|
|
76
|
+
fromFile = loadJson(p);
|
|
77
|
+
if (fromFile != null)
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
if (fromFile == null) {
|
|
81
|
+
fromFile = loadJson(dataDirConfigPath);
|
|
82
|
+
}
|
|
83
|
+
const applied = validateAndApply(fromFile, defaultDataDir);
|
|
84
|
+
const agent = fromFile?.agent?.command != null
|
|
85
|
+
? {
|
|
86
|
+
command: fromFile.agent.command,
|
|
87
|
+
...(fromFile.agent.healthCheck != null && fromFile.agent.healthCheck !== "" && { healthCheck: fromFile.agent.healthCheck }),
|
|
88
|
+
restartOnCrash: fromFile.agent.restartOnCrash !== false,
|
|
89
|
+
maxRestarts: typeof fromFile.agent.maxRestarts === "number" ? fromFile.agent.maxRestarts : 5,
|
|
90
|
+
}
|
|
91
|
+
: undefined;
|
|
92
|
+
return {
|
|
93
|
+
dataDir: applied.dataDir,
|
|
94
|
+
relayUrl: applied.relayUrl,
|
|
95
|
+
httpPort: applied.httpPort,
|
|
96
|
+
httpHost: applied.httpHost,
|
|
97
|
+
...(applied.openclawWebhookUrl !== undefined && { openclawWebhookUrl: applied.openclawWebhookUrl }),
|
|
98
|
+
...(applied.openclawWebhookSecret !== undefined && { openclawWebhookSecret: applied.openclawWebhookSecret }),
|
|
99
|
+
...(applied.marketplaceApiUrl !== undefined && { marketplaceApiUrl: applied.marketplaceApiUrl }),
|
|
100
|
+
...(applied.directoryApiUrl !== undefined && { directoryApiUrl: applied.directoryApiUrl }),
|
|
101
|
+
...(agent !== undefined && { agent }),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const DEFAULT_DATA_DIR: string;
|
|
2
|
+
/** Optional managed agent process (Addendum A1). */
|
|
3
|
+
export interface AgentProcessConfig {
|
|
4
|
+
/** Command to run (e.g. "node my-agent.js"). */
|
|
5
|
+
command: string;
|
|
6
|
+
/** Optional HTTP health check URL (e.g. "http://localhost:3000/health"). */
|
|
7
|
+
healthCheck?: string;
|
|
8
|
+
/** Restart agent on crash with exponential backoff. Default true. */
|
|
9
|
+
restartOnCrash?: boolean;
|
|
10
|
+
/** Max restarts before giving up; daemon keeps running. Default 5. */
|
|
11
|
+
maxRestarts?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DaemonConfig {
|
|
14
|
+
dataDir: string;
|
|
15
|
+
relayUrl: string;
|
|
16
|
+
httpPort: number;
|
|
17
|
+
httpHost: string;
|
|
18
|
+
/** Optional webhook URL for OpenClaw (daemon POSTs delivered messages here). */
|
|
19
|
+
openclawWebhookUrl?: string;
|
|
20
|
+
/** Optional secret for HMAC signing webhook payloads. */
|
|
21
|
+
openclawWebhookSecret?: string;
|
|
22
|
+
/** Optional marketplace API URL (legacy). Prefer directoryApiUrl for directory. */
|
|
23
|
+
marketplaceApiUrl?: string;
|
|
24
|
+
/** Optional directory API URL for profile, contacts, search (e.g. http://localhost:3001). */
|
|
25
|
+
directoryApiUrl?: string;
|
|
26
|
+
/** Optional managed agent process (signet start managed mode). */
|
|
27
|
+
agent?: AgentProcessConfig;
|
|
28
|
+
}
|
|
29
|
+
export declare function loadConfig(): DaemonConfig;
|
|
30
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/errors 2.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* API error codes for stable client handling (product plan Section 20.4).
|
|
4
|
+
* Use in JSON responses as { error: string, code?: string }.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ErrorCodes = void 0;
|
|
8
|
+
exports.apiError = apiError;
|
|
9
|
+
exports.ErrorCodes = {
|
|
10
|
+
E_RELAY_DOWN: "E_RELAY_DOWN",
|
|
11
|
+
E_RECIPIENT_OFFLINE: "E_RECIPIENT_OFFLINE",
|
|
12
|
+
E_UNKNOWN_RECIPIENT: "E_UNKNOWN_RECIPIENT",
|
|
13
|
+
E_INVALID_SIGNATURE: "E_INVALID_SIGNATURE",
|
|
14
|
+
E_EXPIRED_CAPABILITY: "E_EXPIRED_CAPABILITY",
|
|
15
|
+
E_INSUFFICIENT_CAPABILITY: "E_INSUFFICIENT_CAPABILITY",
|
|
16
|
+
E_RATE_LIMITED: "E_RATE_LIMITED",
|
|
17
|
+
E_DECRYPTION_FAILED: "E_DECRYPTION_FAILED",
|
|
18
|
+
E_POLICY_DENIED: "E_POLICY_DENIED",
|
|
19
|
+
E_HUMAN_DENIED: "E_HUMAN_DENIED",
|
|
20
|
+
};
|
|
21
|
+
function apiError(message, code) {
|
|
22
|
+
return code ? { error: message, code } : { error: message };
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;CAWb,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,gBAAgB,CAE5E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAsBH,4BAEC;AAtBY,QAAA,UAAU,GAAG;IACxB,YAAY,EAAE,cAAc;IAC5B,mBAAmB,EAAE,qBAAqB;IAC1C,mBAAmB,EAAE,qBAAqB;IAC1C,mBAAmB,EAAE,qBAAqB;IAC1C,oBAAoB,EAAE,sBAAsB;IAC5C,yBAAyB,EAAE,2BAA2B;IACtD,cAAc,EAAE,gBAAgB;IAChC,mBAAmB,EAAE,qBAAqB;IAC1C,eAAe,EAAE,iBAAiB;IAClC,cAAc,EAAE,gBAAgB;CACxB,CAAC;AASX,SAAgB,QAAQ,CAAC,OAAe,EAAE,IAAgB;IACxD,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC9D,CAAC"}
|
package/dist/index 2.js
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
/**
|
|
5
|
+
* Signet daemon: identity, relay client, HTTP API, audit log, pending approvals,
|
|
6
|
+
* policies, webhook, attestation/capability verification, key revocation.
|
|
7
|
+
*/
|
|
8
|
+
const node_http_1 = require("node:http");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const core_1 = require("@signet/core");
|
|
13
|
+
const config_js_1 = require("./config.js");
|
|
14
|
+
const identity_js_1 = require("./identity.js");
|
|
15
|
+
const relay_client_js_1 = require("./relay-client.js");
|
|
16
|
+
const server_js_1 = require("./server.js");
|
|
17
|
+
const audit_log_js_1 = require("./audit-log.js");
|
|
18
|
+
const agent_process_js_1 = require("./agent-process.js");
|
|
19
|
+
const policies_js_1 = require("./policies.js");
|
|
20
|
+
const REVOKED_KEYS_FILE = "revoked-keys.json";
|
|
21
|
+
function notifyWebhook(url, secret, payload) {
|
|
22
|
+
const body = JSON.stringify(payload);
|
|
23
|
+
const signature = secret
|
|
24
|
+
? (0, node_crypto_1.createHmac)("sha256", secret).update(body).digest("base64")
|
|
25
|
+
: "";
|
|
26
|
+
const headers = { "Content-Type": "application/json" };
|
|
27
|
+
if (signature)
|
|
28
|
+
headers["X-Signet-Signature"] = signature;
|
|
29
|
+
fetch(url, { method: "POST", body, headers }).catch(() => { });
|
|
30
|
+
}
|
|
31
|
+
function loadRevokedKeys(dataDir) {
|
|
32
|
+
const path = (0, node_path_1.join)(dataDir, REVOKED_KEYS_FILE);
|
|
33
|
+
try {
|
|
34
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
35
|
+
const data = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
36
|
+
return new Set(data);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch { /* ignore */ }
|
|
40
|
+
return new Set();
|
|
41
|
+
}
|
|
42
|
+
function saveRevokedKeys(dataDir, keys) {
|
|
43
|
+
const path = (0, node_path_1.join)(dataDir, REVOKED_KEYS_FILE);
|
|
44
|
+
try {
|
|
45
|
+
(0, node_fs_1.writeFileSync)(path, JSON.stringify(Array.from(keys)));
|
|
46
|
+
}
|
|
47
|
+
catch { /* ignore */ }
|
|
48
|
+
}
|
|
49
|
+
async function main() {
|
|
50
|
+
const config = (0, config_js_1.loadConfig)();
|
|
51
|
+
(0, identity_js_1.ensureDataDir)(config.dataDir);
|
|
52
|
+
const identity = (0, identity_js_1.loadOrCreateIdentity)(config.dataDir);
|
|
53
|
+
const owner = (0, identity_js_1.loadOrCreateOwner)(config.dataDir);
|
|
54
|
+
const attestation = (0, identity_js_1.createOwnerAttestation)(identity.nodeId, owner.ed25519.secretKey);
|
|
55
|
+
const capability = (0, core_1.createCapabilityToken)("general", {}, owner.ed25519.secretKey);
|
|
56
|
+
const ownerPublicKeyBase64 = (0, core_1.encodeBase64)(owner.ed25519.publicKey);
|
|
57
|
+
const auditLog = new audit_log_js_1.AuditLog(config.dataDir);
|
|
58
|
+
const messages = [];
|
|
59
|
+
const pending = new Map();
|
|
60
|
+
const MAX_MESSAGES = 500;
|
|
61
|
+
const revokedKeys = loadRevokedKeys(config.dataDir);
|
|
62
|
+
// ── accepts_from inbound tier filter ──────────────────────────────────
|
|
63
|
+
// Cached preferences so we don't hit the directory API on every message.
|
|
64
|
+
const PREFS_CACHE_TTL_MS = 60_000;
|
|
65
|
+
const TIER_CACHE_TTL_MS = 300_000; // sender tiers change rarely
|
|
66
|
+
let cachedAcceptsFrom = null;
|
|
67
|
+
let prefsLastFetched = 0;
|
|
68
|
+
const senderTierCache = new Map();
|
|
69
|
+
async function getOwnAcceptsFrom() {
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
if (cachedAcceptsFrom !== null && now - prefsLastFetched < PREFS_CACHE_TTL_MS) {
|
|
72
|
+
return cachedAcceptsFrom;
|
|
73
|
+
}
|
|
74
|
+
const dirBase = config.directoryApiUrl ?? config.marketplaceApiUrl;
|
|
75
|
+
if (!dirBase)
|
|
76
|
+
return "all";
|
|
77
|
+
try {
|
|
78
|
+
const res = await fetch(`${dirBase.replace(/\/$/, "")}/agents/${encodeURIComponent(identity.nodeId)}`, { signal: AbortSignal.timeout(5000) });
|
|
79
|
+
if (!res.ok)
|
|
80
|
+
return cachedAcceptsFrom ?? "all";
|
|
81
|
+
const data = (await res.json());
|
|
82
|
+
cachedAcceptsFrom = data.communication_preferences?.accepts_from ?? "all";
|
|
83
|
+
prefsLastFetched = now;
|
|
84
|
+
return cachedAcceptsFrom;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return cachedAcceptsFrom ?? "all";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function getSenderTier(senderNodeId) {
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const cached = senderTierCache.get(senderNodeId);
|
|
93
|
+
if (cached && now - cached.fetchedAt < TIER_CACHE_TTL_MS)
|
|
94
|
+
return cached.tier;
|
|
95
|
+
const dirBase = config.directoryApiUrl ?? config.marketplaceApiUrl;
|
|
96
|
+
if (!dirBase)
|
|
97
|
+
return "free";
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(`${dirBase.replace(/\/$/, "")}/agents/${encodeURIComponent(senderNodeId)}`, { signal: AbortSignal.timeout(5000) });
|
|
100
|
+
if (!res.ok)
|
|
101
|
+
return cached?.tier ?? "free";
|
|
102
|
+
const data = (await res.json());
|
|
103
|
+
const tier = data.owner_verification_tier ?? "free";
|
|
104
|
+
senderTierCache.set(senderNodeId, { tier, fetchedAt: now });
|
|
105
|
+
return tier;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return cached?.tier ?? "free";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Returns true if the sender's tier satisfies the recipient's accepts_from policy.
|
|
113
|
+
* Tier hierarchy: business > pro > verified > free
|
|
114
|
+
*/
|
|
115
|
+
function tierSatisfiesPolicy(senderTier, acceptsFrom) {
|
|
116
|
+
if (acceptsFrom === "all")
|
|
117
|
+
return true;
|
|
118
|
+
if (acceptsFrom === "verified") {
|
|
119
|
+
return senderTier === "pro" || senderTier === "business";
|
|
120
|
+
}
|
|
121
|
+
if (acceptsFrom === "signet_pro") {
|
|
122
|
+
return senderTier === "pro" || senderTier === "business";
|
|
123
|
+
}
|
|
124
|
+
if (acceptsFrom === "signet_business") {
|
|
125
|
+
return senderTier === "business";
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
function getPolicies() {
|
|
130
|
+
return (0, policies_js_1.loadPolicies)(config.dataDir);
|
|
131
|
+
}
|
|
132
|
+
function setPolicies(rules) {
|
|
133
|
+
(0, policies_js_1.savePolicies)(config.dataDir, rules);
|
|
134
|
+
auditLog.append({ timestamp: new Date().toISOString(), event: "policy_change" });
|
|
135
|
+
}
|
|
136
|
+
function deliverMessage(decrypted) {
|
|
137
|
+
if (messages.length >= MAX_MESSAGES)
|
|
138
|
+
messages.shift();
|
|
139
|
+
messages.push(decrypted);
|
|
140
|
+
auditLog.append({
|
|
141
|
+
timestamp: new Date().toISOString(),
|
|
142
|
+
event: "message_received",
|
|
143
|
+
messageId: decrypted.id,
|
|
144
|
+
from: decrypted.from.nodeId,
|
|
145
|
+
to: identity.nodeId,
|
|
146
|
+
});
|
|
147
|
+
if (config.openclawWebhookUrl) {
|
|
148
|
+
const summary = typeof decrypted.payload.content?.summary === "string"
|
|
149
|
+
? decrypted.payload.content.summary
|
|
150
|
+
: undefined;
|
|
151
|
+
const payload = {
|
|
152
|
+
messageId: decrypted.id,
|
|
153
|
+
from: decrypted.from.nodeId,
|
|
154
|
+
to: decrypted.to,
|
|
155
|
+
payloadType: decrypted.payload.type,
|
|
156
|
+
timestamp: decrypted.timestamp,
|
|
157
|
+
};
|
|
158
|
+
if (summary !== undefined)
|
|
159
|
+
payload.summary = summary;
|
|
160
|
+
notifyWebhook(config.openclawWebhookUrl, config.openclawWebhookSecret, payload);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function buildAndSend(to, payload, recipientX25519PublicKeyBase64) {
|
|
164
|
+
if (!relayClient.isConnected())
|
|
165
|
+
return null;
|
|
166
|
+
let recipientX25519;
|
|
167
|
+
try {
|
|
168
|
+
recipientX25519 = (0, core_1.decodeBase64)(recipientX25519PublicKeyBase64);
|
|
169
|
+
if (recipientX25519.length !== 32)
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const wire = (0, core_1.createEnvelope)(identity.ed25519, identity.x25519, to, { type: payload.type, version: "1.0", content: payload.content }, capability, {
|
|
176
|
+
recipientX25519PublicKey: recipientX25519,
|
|
177
|
+
ownerAttestation: attestation,
|
|
178
|
+
ownerPublicKey: ownerPublicKeyBase64,
|
|
179
|
+
});
|
|
180
|
+
const ok = relayClient.send(wire);
|
|
181
|
+
if (!ok)
|
|
182
|
+
return null;
|
|
183
|
+
auditLog.append({
|
|
184
|
+
timestamp: new Date().toISOString(),
|
|
185
|
+
event: "message_sent",
|
|
186
|
+
messageId: wire.id,
|
|
187
|
+
from: identity.nodeId,
|
|
188
|
+
to,
|
|
189
|
+
});
|
|
190
|
+
return wire;
|
|
191
|
+
}
|
|
192
|
+
const webChatIncoming = [];
|
|
193
|
+
const MAX_WEB_CHAT_INCOMING = 100;
|
|
194
|
+
const relayClient = new relay_client_js_1.RelayClient({
|
|
195
|
+
relayUrl: config.relayUrl,
|
|
196
|
+
nodeId: identity.nodeId,
|
|
197
|
+
ed25519KeyPair: identity.ed25519,
|
|
198
|
+
onWebChatMessage: (payload) => {
|
|
199
|
+
if (webChatIncoming.length >= MAX_WEB_CHAT_INCOMING)
|
|
200
|
+
webChatIncoming.shift();
|
|
201
|
+
webChatIncoming.push({
|
|
202
|
+
sessionId: payload.sessionId,
|
|
203
|
+
visitorAuthLevel: payload.visitorAuthLevel,
|
|
204
|
+
content: payload.content,
|
|
205
|
+
receivedAt: new Date().toISOString(),
|
|
206
|
+
});
|
|
207
|
+
auditLog.append({
|
|
208
|
+
timestamp: new Date().toISOString(),
|
|
209
|
+
event: "message_received",
|
|
210
|
+
from: "signet_web_chat",
|
|
211
|
+
to: identity.nodeId,
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
onKeyRevocation: (revocation) => {
|
|
215
|
+
if ((0, core_1.verifyKeyRevocation)(revocation)) {
|
|
216
|
+
revokedKeys.add(revocation.revokedNodeId);
|
|
217
|
+
saveRevokedKeys(config.dataDir, revokedKeys);
|
|
218
|
+
auditLog.append({
|
|
219
|
+
timestamp: new Date().toISOString(),
|
|
220
|
+
event: "key_revoked",
|
|
221
|
+
from: revocation.revokedNodeId,
|
|
222
|
+
});
|
|
223
|
+
console.log(`[daemon] Received key revocation for ${revocation.revokedNodeId.slice(0, 20)}… reason=${revocation.reason}`);
|
|
224
|
+
if (revocation.revokedNodeId === identity.nodeId) {
|
|
225
|
+
console.error("[daemon] THIS NODE'S KEY HAS BEEN REVOKED. Shutting down.");
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
onMessage: async (wire) => {
|
|
231
|
+
// Reject messages from revoked keys
|
|
232
|
+
if (revokedKeys.has(wire.from.nodeId)) {
|
|
233
|
+
auditLog.append({
|
|
234
|
+
timestamp: new Date().toISOString(),
|
|
235
|
+
event: "message_rejected_revoked",
|
|
236
|
+
messageId: wire.id,
|
|
237
|
+
from: wire.from.nodeId,
|
|
238
|
+
to: identity.nodeId,
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Verify envelope signature
|
|
243
|
+
const senderPublicKey = (0, core_1.parseNodeId)(wire.from.nodeId);
|
|
244
|
+
if (!senderPublicKey)
|
|
245
|
+
return;
|
|
246
|
+
if (!(0, core_1.verifyEnvelope)(wire, senderPublicKey))
|
|
247
|
+
return;
|
|
248
|
+
// Verify owner attestation if present
|
|
249
|
+
if (wire.from.ownerAttestation && wire.from.ownerPublicKey) {
|
|
250
|
+
try {
|
|
251
|
+
const ownerPubKey = (0, core_1.decodeBase64)(wire.from.ownerPublicKey);
|
|
252
|
+
if (!(0, core_1.verifyOwnerAttestation)(wire.from.nodeId, wire.from.ownerAttestation, ownerPubKey)) {
|
|
253
|
+
auditLog.append({
|
|
254
|
+
timestamp: new Date().toISOString(),
|
|
255
|
+
event: "attestation_invalid",
|
|
256
|
+
messageId: wire.id,
|
|
257
|
+
from: wire.from.nodeId,
|
|
258
|
+
});
|
|
259
|
+
// Don't reject — just log. Attestation is informational for now.
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// Invalid key format — log but don't reject
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Verify capability signature if ownerPublicKey is available
|
|
267
|
+
if (wire.capability?.ownerSignature && wire.from.ownerPublicKey) {
|
|
268
|
+
try {
|
|
269
|
+
const ownerPubKey = (0, core_1.decodeBase64)(wire.from.ownerPublicKey);
|
|
270
|
+
if (!(0, core_1.verifyCapabilitySignature)(wire.capability, ownerPubKey)) {
|
|
271
|
+
auditLog.append({
|
|
272
|
+
timestamp: new Date().toISOString(),
|
|
273
|
+
event: "capability_invalid",
|
|
274
|
+
messageId: wire.id,
|
|
275
|
+
from: wire.from.nodeId,
|
|
276
|
+
});
|
|
277
|
+
// Log but don't reject — capability verification is defense-in-depth
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Invalid key format
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
let decrypted;
|
|
285
|
+
try {
|
|
286
|
+
decrypted = (0, core_1.decryptEnvelope)(wire, identity.x25519.secretKey);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const rules = getPolicies();
|
|
292
|
+
const payloadType = decrypted.payload?.type ?? "general/1";
|
|
293
|
+
if ((0, policies_js_1.isDenied)(rules, decrypted.from.nodeId, payloadType)) {
|
|
294
|
+
auditLog.append({
|
|
295
|
+
timestamp: new Date().toISOString(),
|
|
296
|
+
event: "message_denied",
|
|
297
|
+
messageId: wire.id,
|
|
298
|
+
from: wire.from.nodeId,
|
|
299
|
+
to: identity.nodeId,
|
|
300
|
+
});
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// Enforce accepts_from tier preference — check sender's tier against our policy
|
|
304
|
+
try {
|
|
305
|
+
const acceptsFrom = await getOwnAcceptsFrom();
|
|
306
|
+
if (acceptsFrom !== "all") {
|
|
307
|
+
const senderTier = await getSenderTier(decrypted.from.nodeId);
|
|
308
|
+
if (!tierSatisfiesPolicy(senderTier, acceptsFrom)) {
|
|
309
|
+
auditLog.append({
|
|
310
|
+
timestamp: new Date().toISOString(),
|
|
311
|
+
event: "message_filtered_tier",
|
|
312
|
+
messageId: wire.id,
|
|
313
|
+
from: wire.from.nodeId,
|
|
314
|
+
to: identity.nodeId,
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// Directory API unreachable — fail open (deliver the message)
|
|
322
|
+
}
|
|
323
|
+
if ((0, policies_js_1.shouldRequireApproval)(rules, decrypted.from.nodeId, payloadType)) {
|
|
324
|
+
pending.set(wire.id, decrypted);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
deliverMessage(decrypted);
|
|
328
|
+
},
|
|
329
|
+
onConnectionChange: (connected) => {
|
|
330
|
+
auditLog.append({
|
|
331
|
+
timestamp: new Date().toISOString(),
|
|
332
|
+
event: connected ? "connection_up" : "connection_down",
|
|
333
|
+
});
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
const relayHttpUrl = config.relayUrl.replace(/^ws/, "http").replace(/\/$/, "");
|
|
337
|
+
async function discoverAgents() {
|
|
338
|
+
const res = await fetch(`${relayHttpUrl}/discover`);
|
|
339
|
+
if (!res.ok)
|
|
340
|
+
throw new Error(`relay discover: ${res.status}`);
|
|
341
|
+
const data = (await res.json());
|
|
342
|
+
const agents = data.agents ?? [];
|
|
343
|
+
return agents.map((a) => ({ nodeId: a.nodeId }));
|
|
344
|
+
}
|
|
345
|
+
function getPending() {
|
|
346
|
+
return Array.from(pending.values()).map((e) => ({
|
|
347
|
+
messageId: e.id,
|
|
348
|
+
from: e.from.nodeId,
|
|
349
|
+
to: e.to,
|
|
350
|
+
timestamp: e.timestamp,
|
|
351
|
+
payload: e.payload,
|
|
352
|
+
capabilityScope: e.capability?.scope,
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
355
|
+
function approve(messageId) {
|
|
356
|
+
const decrypted = pending.get(messageId);
|
|
357
|
+
if (!decrypted)
|
|
358
|
+
return false;
|
|
359
|
+
pending.delete(messageId);
|
|
360
|
+
deliverMessage(decrypted);
|
|
361
|
+
auditLog.append({
|
|
362
|
+
timestamp: new Date().toISOString(),
|
|
363
|
+
event: "message_approved",
|
|
364
|
+
messageId,
|
|
365
|
+
from: decrypted.from.nodeId,
|
|
366
|
+
to: identity.nodeId,
|
|
367
|
+
});
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
function deny(messageId, _reason) {
|
|
371
|
+
const decrypted = pending.get(messageId);
|
|
372
|
+
if (!decrypted)
|
|
373
|
+
return false;
|
|
374
|
+
pending.delete(messageId);
|
|
375
|
+
auditLog.append({
|
|
376
|
+
timestamp: new Date().toISOString(),
|
|
377
|
+
event: "message_denied",
|
|
378
|
+
messageId,
|
|
379
|
+
from: decrypted.from.nodeId,
|
|
380
|
+
to: identity.nodeId,
|
|
381
|
+
});
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
/** Revoke this daemon's key. Broadcasts revocation to all peers via relay. */
|
|
385
|
+
function revokeKey(reason, replacementNodeId) {
|
|
386
|
+
const revocation = (0, core_1.createKeyRevocation)(identity.nodeId, reason, owner.ed25519.secretKey, owner.ed25519.publicKey, replacementNodeId);
|
|
387
|
+
const sent = relayClient.sendRaw({ type: "key_revocation", revocation });
|
|
388
|
+
if (sent) {
|
|
389
|
+
revokedKeys.add(identity.nodeId);
|
|
390
|
+
saveRevokedKeys(config.dataDir, revokedKeys);
|
|
391
|
+
auditLog.append({
|
|
392
|
+
timestamp: new Date().toISOString(),
|
|
393
|
+
event: "key_self_revoked",
|
|
394
|
+
from: identity.nodeId,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return sent;
|
|
398
|
+
}
|
|
399
|
+
const uiPath = [(0, node_path_1.join)(process.cwd(), "public"), (0, node_path_1.join)(process.cwd(), "packages", "daemon", "public")].find((p) => (0, node_fs_1.existsSync)(p));
|
|
400
|
+
function getConversationHistory(withNodeId, limit = 100) {
|
|
401
|
+
const { entries } = auditLog.readRecent(5000);
|
|
402
|
+
const selfId = identity.nodeId;
|
|
403
|
+
const filtered = entries.filter((e) => (e.event === "message_sent" || e.event === "message_received" || e.event === "message_approved" || e.event === "message_denied") &&
|
|
404
|
+
((e.from === withNodeId && e.to === selfId) || (e.from === selfId && e.to === withNodeId)));
|
|
405
|
+
return filtered.slice(-limit).map((e) => {
|
|
406
|
+
const out = {
|
|
407
|
+
timestamp: e.timestamp,
|
|
408
|
+
event: e.event,
|
|
409
|
+
};
|
|
410
|
+
if (e.messageId !== undefined)
|
|
411
|
+
out.messageId = e.messageId;
|
|
412
|
+
if (e.from !== undefined)
|
|
413
|
+
out.from = e.from;
|
|
414
|
+
if (e.to !== undefined)
|
|
415
|
+
out.to = e.to;
|
|
416
|
+
return out;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
let agentRunner = null;
|
|
420
|
+
if (config.agent?.command) {
|
|
421
|
+
agentRunner = (0, agent_process_js_1.createAgentProcessRunner)(config.agent);
|
|
422
|
+
}
|
|
423
|
+
function rotateKey() {
|
|
424
|
+
try {
|
|
425
|
+
const { oldIdentity, newIdentity } = (0, identity_js_1.rotateIdentity)(config.dataDir);
|
|
426
|
+
// Revoke old key with rotation reason, pointing to replacement
|
|
427
|
+
revokeKey("rotation", newIdentity.nodeId);
|
|
428
|
+
auditLog.append({
|
|
429
|
+
timestamp: new Date().toISOString(),
|
|
430
|
+
event: "key_rotated",
|
|
431
|
+
from: oldIdentity.nodeId,
|
|
432
|
+
to: newIdentity.nodeId,
|
|
433
|
+
});
|
|
434
|
+
return { oldNodeId: oldIdentity.nodeId, newNodeId: newIdentity.nodeId };
|
|
435
|
+
}
|
|
436
|
+
catch (e) {
|
|
437
|
+
console.error("[daemon] Key rotation failed:", e instanceof Error ? e.message : e);
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const app = (0, server_js_1.createHttpServer)({
|
|
442
|
+
nodeId: identity.nodeId,
|
|
443
|
+
relayUrl: config.relayUrl,
|
|
444
|
+
x25519PublicKeyBase64: (0, core_1.encodeBase64)(identity.x25519.publicKey),
|
|
445
|
+
ownerAttestation: true,
|
|
446
|
+
isConnected: () => relayClient.isConnected(),
|
|
447
|
+
buildAndSend,
|
|
448
|
+
getMessages: (limit) => (limit != null ? messages.slice(-limit) : [...messages]),
|
|
449
|
+
discoverAgents,
|
|
450
|
+
getPending,
|
|
451
|
+
approve,
|
|
452
|
+
deny,
|
|
453
|
+
getPolicies,
|
|
454
|
+
setPolicies,
|
|
455
|
+
getActivity: (limit) => auditLog.readRecent(limit),
|
|
456
|
+
getConversationHistory,
|
|
457
|
+
sendWebChatReply: (sessionId, content) => relayClient.sendRaw({ type: "web_chat_out", sessionId, content }),
|
|
458
|
+
getWebChatIncoming: () => [...webChatIncoming],
|
|
459
|
+
revokeKey,
|
|
460
|
+
getRevokedKeys: () => Array.from(revokedKeys),
|
|
461
|
+
rotateKey,
|
|
462
|
+
halt: () => shutdown(),
|
|
463
|
+
...(agentRunner != null && {
|
|
464
|
+
getAgentStatus: () => {
|
|
465
|
+
const s = agentRunner.getStatus();
|
|
466
|
+
return {
|
|
467
|
+
status: s.status,
|
|
468
|
+
pid: s.pid,
|
|
469
|
+
restartCount: s.restartCount,
|
|
470
|
+
lastExitCode: s.lastExitCode,
|
|
471
|
+
lastExitSignal: s.lastExitSignal,
|
|
472
|
+
};
|
|
473
|
+
},
|
|
474
|
+
}),
|
|
475
|
+
...(uiPath !== undefined && { uiPath }),
|
|
476
|
+
...(config.marketplaceApiUrl !== undefined && { marketplaceApiUrl: config.marketplaceApiUrl }),
|
|
477
|
+
...(config.directoryApiUrl !== undefined && { directoryApiUrl: config.directoryApiUrl }),
|
|
478
|
+
});
|
|
479
|
+
const pidFile = (0, node_path_1.join)(config.dataDir, "signet.pid");
|
|
480
|
+
try {
|
|
481
|
+
(0, node_fs_1.writeFileSync)(pidFile, String(process.pid));
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// ignore if dataDir not writable
|
|
485
|
+
}
|
|
486
|
+
const httpServer = (0, node_http_1.createServer)(app);
|
|
487
|
+
httpServer.listen(config.httpPort, config.httpHost, () => {
|
|
488
|
+
console.log(`Signet daemon listening on http://${config.httpHost}:${config.httpPort} (nodeId=${identity.nodeId})`);
|
|
489
|
+
});
|
|
490
|
+
relayClient.connect();
|
|
491
|
+
if (agentRunner != null) {
|
|
492
|
+
agentRunner.start((err) => {
|
|
493
|
+
if (err)
|
|
494
|
+
console.error("[signet] agent process start error:", err.message);
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
function shutdown() {
|
|
498
|
+
try {
|
|
499
|
+
if ((0, node_fs_1.existsSync)(pidFile))
|
|
500
|
+
(0, node_fs_1.unlinkSync)(pidFile);
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
// ignore
|
|
504
|
+
}
|
|
505
|
+
if (agentRunner != null) {
|
|
506
|
+
agentRunner.clearRestartTimeout();
|
|
507
|
+
agentRunner.stop(() => {
|
|
508
|
+
relayClient.disconnect();
|
|
509
|
+
httpServer.close(() => process.exit(0));
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
relayClient.disconnect();
|
|
514
|
+
httpServer.close(() => process.exit(0));
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
process.on("SIGINT", shutdown);
|
|
518
|
+
process.on("SIGTERM", shutdown);
|
|
519
|
+
}
|
|
520
|
+
main().catch((err) => {
|
|
521
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
522
|
+
process.exit(1);
|
|
523
|
+
});
|
|
524
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../src/policies.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,kBAAkB,CAAC;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAMD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAiB1D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI,CAIvE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAM/F;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAM5G"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policies.js","sourceRoot":"","sources":["../src/policies.ts"],"names":[],"mappings":";;AAmBA,oCAiBC;AAED,oCAIC;AAED,4BAMC;AAED,sDAMC;AA1DD;;GAEG;AACH,qCAA6E;AAC7E,yCAAiC;AAEjC,MAAM,aAAa,GAAG,eAAe,CAAC;AAStC,SAAS,OAAO,CAAC,OAAe;IAC9B,OAAO,IAAA,gBAAI,EAAC,OAAO,EAAE,aAAa,CAAC,CAAC;AACtC,CAAC;AAED,SAAgB,YAAY,CAAC,OAAe;IAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,IAAI,CAAC,IAAA,oBAAU,EAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,sBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC;QACtD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CACtB,CAAC,CAAC,EAAmB,EAAE,CACrB,CAAC,IAAI,IAAI;YACT,OAAO,CAAC,KAAK,QAAQ;YACrB,OAAQ,CAAgB,CAAC,EAAE,KAAK,QAAQ;YACxC,CAAC,OAAO,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC,QAAQ,CAAE,CAAgB,CAAC,MAAM,CAAC,CAC3D,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAgB,YAAY,CAAC,OAAe,EAAE,KAAmB;IAC/D,IAAI,CAAC,IAAA,oBAAU,EAAC,OAAO,CAAC;QAAE,IAAA,mBAAS,EAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,IAAA,uBAAa,EAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,SAAgB,QAAQ,CAAC,KAAmB,EAAE,UAAkB,EAAE,YAAoB;IACpF,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;YAAE,SAAS;QAClC,IAAI,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC,EAAE,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAgB,qBAAqB,CAAC,KAAmB,EAAE,UAAkB,EAAE,YAAoB;IACjG,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,MAAM,KAAK,kBAAkB;YAAE,SAAS;QAC9C,IAAI,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC,EAAE,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-client.d.ts","sourceRoot":"","sources":["../src/relay-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AAMtB,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,cAAc,CAAC;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;IACnD,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACxD,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,aAAa,KAAK,IAAI,CAAC;CACxD;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,iBAAiB,CAAS;gBAEtB,OAAO,EAAE,kBAAkB;IAIvC,OAAO,IAAI,IAAI;IA4Gf,OAAO,CAAC,iBAAiB;IASzB,IAAI,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO;IAUvC,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAUlC,UAAU,IAAI,IAAI;IAclB,WAAW,IAAI,OAAO;CAGvB"}
|