@delexec/ops 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +3 -0
  2. package/README.zh-CN.md +6 -0
  3. package/node_modules/@delexec/caller-controller/README.md +3 -0
  4. package/node_modules/@delexec/caller-controller/README.zh-CN.md +6 -0
  5. package/node_modules/@delexec/caller-controller/package.json +53 -0
  6. package/node_modules/@delexec/caller-controller/src/server.js +127 -0
  7. package/node_modules/@delexec/caller-controller-core/README.md +3 -0
  8. package/node_modules/@delexec/caller-controller-core/README.zh-CN.md +6 -0
  9. package/node_modules/@delexec/caller-controller-core/package.json +26 -0
  10. package/node_modules/@delexec/caller-controller-core/src/index.js +1612 -0
  11. package/node_modules/@delexec/caller-skill-adapter/package.json +12 -0
  12. package/node_modules/@delexec/caller-skill-adapter/src/server.js +1042 -0
  13. package/node_modules/@delexec/caller-skill-mcp-adapter/README.md +65 -0
  14. package/node_modules/@delexec/caller-skill-mcp-adapter/package.json +16 -0
  15. package/node_modules/@delexec/caller-skill-mcp-adapter/src/server.js +527 -0
  16. package/node_modules/@delexec/responder-controller/README.md +3 -0
  17. package/node_modules/@delexec/responder-controller/README.zh-CN.md +6 -0
  18. package/node_modules/@delexec/responder-controller/package.json +53 -0
  19. package/node_modules/@delexec/responder-controller/src/server.js +254 -0
  20. package/node_modules/@delexec/responder-runtime-core/README.md +3 -0
  21. package/node_modules/@delexec/responder-runtime-core/README.zh-CN.md +6 -0
  22. package/node_modules/@delexec/responder-runtime-core/package.json +26 -0
  23. package/node_modules/@delexec/responder-runtime-core/src/executors.js +326 -0
  24. package/node_modules/@delexec/responder-runtime-core/src/index.js +1202 -0
  25. package/node_modules/@delexec/runtime-utils/README.md +3 -0
  26. package/node_modules/@delexec/runtime-utils/README.zh-CN.md +6 -0
  27. package/node_modules/@delexec/runtime-utils/package.json +23 -0
  28. package/node_modules/@delexec/runtime-utils/src/index.js +338 -0
  29. package/node_modules/@delexec/sqlite-store/README.md +3 -0
  30. package/node_modules/@delexec/sqlite-store/README.zh-CN.md +6 -0
  31. package/node_modules/@delexec/sqlite-store/package.json +26 -0
  32. package/node_modules/@delexec/sqlite-store/src/index.js +68 -0
  33. package/node_modules/@delexec/transport-email/README.md +3 -0
  34. package/node_modules/@delexec/transport-email/README.zh-CN.md +6 -0
  35. package/node_modules/@delexec/transport-email/package.json +23 -0
  36. package/node_modules/@delexec/transport-email/src/index.js +185 -0
  37. package/node_modules/@delexec/transport-emailengine/README.md +3 -0
  38. package/node_modules/@delexec/transport-emailengine/README.zh-CN.md +6 -0
  39. package/node_modules/@delexec/transport-emailengine/package.json +26 -0
  40. package/node_modules/@delexec/transport-emailengine/src/index.js +210 -0
  41. package/node_modules/@delexec/transport-gmail/README.md +3 -0
  42. package/node_modules/@delexec/transport-gmail/README.zh-CN.md +6 -0
  43. package/node_modules/@delexec/transport-gmail/package.json +26 -0
  44. package/node_modules/@delexec/transport-gmail/src/index.js +295 -0
  45. package/node_modules/@delexec/transport-relay-http/README.md +3 -0
  46. package/node_modules/@delexec/transport-relay-http/README.zh-CN.md +6 -0
  47. package/node_modules/@delexec/transport-relay-http/package.json +23 -0
  48. package/node_modules/@delexec/transport-relay-http/src/index.js +124 -0
  49. package/package.json +64 -0
  50. package/src/cli.js +1571 -0
  51. package/src/config.js +1180 -0
  52. package/src/example-hotline-worker.js +65 -0
  53. package/src/example-hotline.js +196 -0
  54. package/src/logging.js +56 -0
  55. package/src/supervisor.js +3070 -0
@@ -0,0 +1,3 @@
1
+ # @delexec/runtime-utils
2
+
3
+ Shared runtime helpers for local state, env loading, and secret storage.
@@ -0,0 +1,6 @@
1
+ # @delexec/runtime-utils
2
+
3
+ > 英文版:README.md
4
+ > 说明:中文文档为准。
5
+
6
+ 用于本地状态、环境变量加载与密钥存储的共享运行时辅助工具。
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@delexec/runtime-utils",
3
+ "version": "0.1.0",
4
+ "description": "Shared runtime utilities for delegated execution services",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./package.json": "./package.json"
10
+ },
11
+ "files": [
12
+ "src",
13
+ "README.md"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "keywords": [
19
+ "delegated-execution",
20
+ "runtime",
21
+ "utilities"
22
+ ]
23
+ }
@@ -0,0 +1,338 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import crypto from "node:crypto";
5
+
6
+ const SECRET_STORE_VERSION = 1;
7
+ const KEY_LENGTH = 32;
8
+ const LEGACY_OPS_HOME_ENV = "CROC_OPS_HOME";
9
+ const OPS_HOME_ENV = "DELEXEC_HOME";
10
+ const LEGACY_OPS_HOME_BASENAME = ".remote-hotline";
11
+ const OPS_HOME_BASENAME = ".delexec";
12
+ const LEGACY_SQLITE_FILENAME = "croc.sqlite";
13
+ const OPS_SQLITE_FILENAME = "delexec.sqlite";
14
+
15
+ function stripWrappingQuotes(value) {
16
+ if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
17
+ return value.slice(1, -1);
18
+ }
19
+ return value;
20
+ }
21
+
22
+ function toBase64Url(buffer) {
23
+ return Buffer.from(buffer).toString("base64url");
24
+ }
25
+
26
+ function fromBase64Url(value) {
27
+ return Buffer.from(String(value || ""), "base64url");
28
+ }
29
+
30
+ function requirePassphrase(passphrase) {
31
+ const value = String(passphrase || "");
32
+ if (!value.trim()) {
33
+ throw new Error("secret_store_passphrase_required");
34
+ }
35
+ return value;
36
+ }
37
+
38
+ function deriveKey(passphrase, salt) {
39
+ return crypto.scryptSync(requirePassphrase(passphrase), salt, KEY_LENGTH);
40
+ }
41
+
42
+ function writeSecureTextFile(filePath, content) {
43
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
44
+ fs.writeFileSync(filePath, content, { encoding: "utf8", mode: 0o600 });
45
+ fs.chmodSync(filePath, 0o600);
46
+ }
47
+
48
+ function resolveDefaultOpsHomeDir() {
49
+ return path.join(os.homedir(), OPS_HOME_BASENAME);
50
+ }
51
+
52
+ function resolveLegacyOpsHomeDir() {
53
+ return path.join(os.homedir(), LEGACY_OPS_HOME_BASENAME);
54
+ }
55
+
56
+ function renameOrCopyDir(sourceDir, targetDir) {
57
+ try {
58
+ fs.renameSync(sourceDir, targetDir);
59
+ return;
60
+ } catch (error) {
61
+ if (!error || (error.code !== "EXDEV" && error.code !== "EACCES" && error.code !== "EPERM")) {
62
+ throw error;
63
+ }
64
+ }
65
+ fs.cpSync(sourceDir, targetDir, { recursive: true, force: false });
66
+ fs.rmSync(sourceDir, { recursive: true, force: true });
67
+ }
68
+
69
+ function migrateLegacySqliteFile(homeDir) {
70
+ const legacyFile = path.join(homeDir, LEGACY_SQLITE_FILENAME);
71
+ const nextFile = path.join(homeDir, OPS_SQLITE_FILENAME);
72
+ if (!fs.existsSync(legacyFile) || fs.existsSync(nextFile)) {
73
+ return;
74
+ }
75
+ fs.renameSync(legacyFile, nextFile);
76
+ }
77
+
78
+ export function migrateLegacyOpsHomeDir({
79
+ explicitHomeDir = process.env[OPS_HOME_ENV] || process.env[LEGACY_OPS_HOME_ENV] || null
80
+ } = {}) {
81
+ if (explicitHomeDir) {
82
+ const resolved = path.resolve(explicitHomeDir);
83
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
84
+ migrateLegacySqliteFile(resolved);
85
+ }
86
+ return resolved;
87
+ }
88
+
89
+ const nextHomeDir = path.resolve(resolveDefaultOpsHomeDir());
90
+ const legacyHomeDir = path.resolve(resolveLegacyOpsHomeDir());
91
+ if (!fs.existsSync(legacyHomeDir) || fs.existsSync(nextHomeDir)) {
92
+ if (fs.existsSync(nextHomeDir) && fs.statSync(nextHomeDir).isDirectory()) {
93
+ migrateLegacySqliteFile(nextHomeDir);
94
+ }
95
+ return nextHomeDir;
96
+ }
97
+
98
+ fs.mkdirSync(path.dirname(nextHomeDir), { recursive: true, mode: 0o700 });
99
+ renameOrCopyDir(legacyHomeDir, nextHomeDir);
100
+ migrateLegacySqliteFile(nextHomeDir);
101
+ return nextHomeDir;
102
+ }
103
+
104
+ function buildSecretPayload(secrets) {
105
+ return Buffer.from(
106
+ JSON.stringify({
107
+ version: SECRET_STORE_VERSION,
108
+ updated_at: new Date().toISOString(),
109
+ secrets: secrets || {}
110
+ }),
111
+ "utf8"
112
+ );
113
+ }
114
+
115
+ function encryptSecrets(passphrase, secrets, salt = crypto.randomBytes(16)) {
116
+ const iv = crypto.randomBytes(12);
117
+ const key = deriveKey(passphrase, salt);
118
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
119
+ const ciphertext = Buffer.concat([cipher.update(buildSecretPayload(secrets)), cipher.final()]);
120
+ const tag = cipher.getAuthTag();
121
+ return {
122
+ version: SECRET_STORE_VERSION,
123
+ kdf: "scrypt",
124
+ cipher: "aes-256-gcm",
125
+ salt: toBase64Url(salt),
126
+ iv: toBase64Url(iv),
127
+ tag: toBase64Url(tag),
128
+ ciphertext: toBase64Url(ciphertext)
129
+ };
130
+ }
131
+
132
+ function decryptEnvelope(passphrase, envelope) {
133
+ if (!envelope || envelope.version !== SECRET_STORE_VERSION) {
134
+ throw new Error("secret_store_version_unsupported");
135
+ }
136
+ const key = deriveKey(passphrase, fromBase64Url(envelope.salt));
137
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, fromBase64Url(envelope.iv));
138
+ decipher.setAuthTag(fromBase64Url(envelope.tag));
139
+ const plaintext = Buffer.concat([decipher.update(fromBase64Url(envelope.ciphertext)), decipher.final()]).toString("utf8");
140
+ const parsed = JSON.parse(plaintext);
141
+ if (parsed.version !== SECRET_STORE_VERSION || typeof parsed.secrets !== "object" || parsed.secrets === null) {
142
+ throw new Error("secret_store_payload_invalid");
143
+ }
144
+ return parsed;
145
+ }
146
+
147
+ export function parseEnvText(text) {
148
+ const result = {};
149
+ for (const rawLine of text.split(/\r?\n/)) {
150
+ const line = rawLine.trim();
151
+ if (!line || line.startsWith("#")) {
152
+ continue;
153
+ }
154
+ const separatorIndex = line.indexOf("=");
155
+ if (separatorIndex <= 0) {
156
+ continue;
157
+ }
158
+ const key = line.slice(0, separatorIndex).trim();
159
+ const value = stripWrappingQuotes(line.slice(separatorIndex + 1).trim());
160
+ result[key] = value;
161
+ }
162
+ return result;
163
+ }
164
+
165
+ export function readEnvFile(filePath) {
166
+ if (!fs.existsSync(filePath)) {
167
+ return {};
168
+ }
169
+ return parseEnvText(fs.readFileSync(filePath, "utf8"));
170
+ }
171
+
172
+ export function loadEnvFiles(filePaths, { override = false } = {}) {
173
+ for (const filePath of filePaths) {
174
+ if (!filePath || !fs.existsSync(filePath)) {
175
+ continue;
176
+ }
177
+ const entries = readEnvFile(filePath);
178
+ for (const [key, value] of Object.entries(entries)) {
179
+ if (override || process.env[key] === undefined) {
180
+ process.env[key] = value;
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ export function formatEnvFile(entries) {
187
+ return `${Object.entries(entries)
188
+ .sort(([left], [right]) => left.localeCompare(right))
189
+ .map(([key, value]) => `${key}=${value}`)
190
+ .join("\n")}\n`;
191
+ }
192
+
193
+ export function updateEnvFile(filePath, updates, options = {}) {
194
+ const removeNull = options.removeNull === true;
195
+ const current = readEnvFile(filePath);
196
+ const next = { ...current };
197
+ for (const [key, value] of Object.entries(updates)) {
198
+ if (value === undefined) {
199
+ continue;
200
+ }
201
+ if (value === null) {
202
+ if (removeNull) {
203
+ delete next[key];
204
+ }
205
+ continue;
206
+ }
207
+ next[key] = value;
208
+ }
209
+ writeSecureTextFile(filePath, formatEnvFile(next));
210
+ return next;
211
+ }
212
+
213
+ export function getOpsHomeDir() {
214
+ if (process.env[OPS_HOME_ENV]) {
215
+ return path.resolve(process.env[OPS_HOME_ENV]);
216
+ }
217
+ if (process.env[LEGACY_OPS_HOME_ENV]) {
218
+ return path.resolve(process.env[LEGACY_OPS_HOME_ENV]);
219
+ }
220
+ return migrateLegacyOpsHomeDir();
221
+ }
222
+
223
+ export function getOpsEnvFile() {
224
+ return path.join(getOpsHomeDir(), ".env.local");
225
+ }
226
+
227
+ export function getOpsSecretsFile() {
228
+ return path.join(getOpsHomeDir(), "secrets.enc.json");
229
+ }
230
+
231
+ export function getResponderConfigFile() {
232
+ return path.join(getOpsHomeDir(), "responder.config.json");
233
+ }
234
+
235
+ export function getOpsConfigFile() {
236
+ return path.join(getOpsHomeDir(), "ops.config.json");
237
+ }
238
+
239
+ export function ensureOpsDirectories() {
240
+ const homeDir = getOpsHomeDir();
241
+ fs.mkdirSync(homeDir, { recursive: true, mode: 0o700 });
242
+ fs.mkdirSync(path.join(homeDir, "logs"), { recursive: true, mode: 0o700 });
243
+ fs.mkdirSync(path.join(homeDir, "run"), { recursive: true, mode: 0o700 });
244
+ migrateLegacySqliteFile(homeDir);
245
+ return homeDir;
246
+ }
247
+
248
+ export function buildOpsEnvSearchPaths(rootDir, profileName = null) {
249
+ const homeDir = getOpsHomeDir();
250
+ const paths = [path.join(homeDir, ".env"), path.join(homeDir, ".env.local"), path.join(rootDir, ".env"), path.join(rootDir, ".env.local")];
251
+
252
+ if (profileName) {
253
+ paths.push(path.join(rootDir, `deploy/${profileName}/.env`));
254
+ paths.push(path.join(rootDir, `deploy/${profileName}/.env.local`));
255
+ }
256
+
257
+ paths.push(path.join(rootDir, "deploy/ops/.env"));
258
+ paths.push(path.join(rootDir, "deploy/ops/.env.local"));
259
+ return paths;
260
+ }
261
+
262
+ export function readJsonFile(filePath, fallback = null) {
263
+ if (!fs.existsSync(filePath)) {
264
+ return fallback;
265
+ }
266
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
267
+ }
268
+
269
+ export function writeJsonFile(filePath, value) {
270
+ writeSecureTextFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
271
+ }
272
+
273
+ export function secretStoreExists(filePath) {
274
+ return Boolean(filePath) && fs.existsSync(filePath);
275
+ }
276
+
277
+ export function initializeSecretStore(filePath, passphrase, secrets = {}) {
278
+ if (secretStoreExists(filePath)) {
279
+ throw new Error("secret_store_already_initialized");
280
+ }
281
+ const envelope = encryptSecrets(passphrase, secrets);
282
+ writeSecureTextFile(filePath, `${JSON.stringify(envelope, null, 2)}\n`);
283
+ return {
284
+ initialized: true,
285
+ secret_count: Object.keys(secrets || {}).length
286
+ };
287
+ }
288
+
289
+ export function unlockSecretStore(filePath, passphrase) {
290
+ if (!secretStoreExists(filePath)) {
291
+ throw new Error("secret_store_not_initialized");
292
+ }
293
+ const envelope = JSON.parse(fs.readFileSync(filePath, "utf8"));
294
+ const payload = decryptEnvelope(passphrase, envelope);
295
+ return {
296
+ secrets: payload.secrets,
297
+ updated_at: payload.updated_at || null
298
+ };
299
+ }
300
+
301
+ export function replaceSecretStore(filePath, passphrase, secrets = {}) {
302
+ if (!secretStoreExists(filePath)) {
303
+ throw new Error("secret_store_not_initialized");
304
+ }
305
+ const envelope = encryptSecrets(passphrase, secrets);
306
+ writeSecureTextFile(filePath, `${JSON.stringify(envelope, null, 2)}\n`);
307
+ return {
308
+ updated: true,
309
+ secret_count: Object.keys(secrets || {}).length
310
+ };
311
+ }
312
+
313
+ export function writeSecretValues(filePath, passphrase, updates = {}) {
314
+ const current = unlockSecretStore(filePath, passphrase).secrets;
315
+ const next = { ...current };
316
+ for (const [key, value] of Object.entries(updates)) {
317
+ if (value === undefined) {
318
+ continue;
319
+ }
320
+ if (value === null) {
321
+ delete next[key];
322
+ continue;
323
+ }
324
+ next[key] = value;
325
+ }
326
+ replaceSecretStore(filePath, passphrase, next);
327
+ return next;
328
+ }
329
+
330
+ export function rotateSecretStorePassphrase(filePath, currentPassphrase, nextPassphrase) {
331
+ const current = unlockSecretStore(filePath, currentPassphrase).secrets;
332
+ const envelope = encryptSecrets(nextPassphrase, current);
333
+ writeSecureTextFile(filePath, `${JSON.stringify(envelope, null, 2)}\n`);
334
+ return {
335
+ rotated: true,
336
+ secret_count: Object.keys(current).length
337
+ };
338
+ }
@@ -0,0 +1,3 @@
1
+ # @delexec/sqlite-store
2
+
3
+ SQLite snapshot persistence adapter for delegated execution services.
@@ -0,0 +1,6 @@
1
+ # @delexec/sqlite-store
2
+
3
+ > 英文版:README.md
4
+ > 说明:中文文档为准。
5
+
6
+ 用于委托执行服务的 SQLite 快照持久化适配器。
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@delexec/sqlite-store",
3
+ "version": "0.1.0",
4
+ "description": "SQLite snapshot store for delegated execution services",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./package.json": "./package.json"
10
+ },
11
+ "files": [
12
+ "src",
13
+ "README.md"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "keywords": [
19
+ "delegated-execution",
20
+ "sqlite",
21
+ "storage"
22
+ ],
23
+ "dependencies": {
24
+ "better-sqlite3": "^12.6.2"
25
+ }
26
+ }
@@ -0,0 +1,68 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import Database from "better-sqlite3";
5
+
6
+ export async function createSqliteSnapshotStore({
7
+ databasePath,
8
+ serviceName,
9
+ tableName = "service_state_snapshots"
10
+ } = {}) {
11
+ if (!databasePath) {
12
+ throw new Error("sqlite_store_database_path_required");
13
+ }
14
+ if (!serviceName) {
15
+ throw new Error("sqlite_store_service_name_required");
16
+ }
17
+
18
+ const resolvedPath = path.resolve(databasePath);
19
+ fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
20
+ const db = new Database(resolvedPath);
21
+
22
+ function migrate() {
23
+ db.exec(`
24
+ CREATE TABLE IF NOT EXISTS schema_migrations (
25
+ version TEXT,
26
+ applied_at TEXT
27
+ );
28
+
29
+ CREATE TABLE IF NOT EXISTS ${tableName} (
30
+ service_name TEXT,
31
+ state_json TEXT,
32
+ updated_at TEXT
33
+ );
34
+ `);
35
+ }
36
+
37
+ function loadSnapshot() {
38
+ const row = db
39
+ .prepare(`SELECT state_json FROM ${tableName} WHERE service_name = ? ORDER BY rowid DESC LIMIT 1`)
40
+ .get(serviceName);
41
+ return row?.state_json ? JSON.parse(row.state_json) : null;
42
+ }
43
+
44
+ function saveSnapshot(snapshot) {
45
+ const transaction = db.transaction((payload) => {
46
+ db.prepare(`DELETE FROM ${tableName} WHERE service_name = ?`).run(serviceName);
47
+ db.prepare(`INSERT INTO ${tableName} (service_name, state_json, updated_at) VALUES (?, ?, ?)`).run(
48
+ serviceName,
49
+ JSON.stringify(payload),
50
+ new Date().toISOString()
51
+ );
52
+ });
53
+ transaction(snapshot);
54
+ }
55
+
56
+ function close() {
57
+ db.close();
58
+ }
59
+
60
+ return {
61
+ migrate,
62
+ loadSnapshot,
63
+ saveSnapshot,
64
+ close,
65
+ db,
66
+ databasePath: resolvedPath
67
+ };
68
+ }
@@ -0,0 +1,3 @@
1
+ # @delexec/transport-email
2
+
3
+ Shared email transport primitives for delegated execution.
@@ -0,0 +1,6 @@
1
+ # @delexec/transport-email
2
+
3
+ > 英文版:README.md
4
+ > 说明:中文文档为准。
5
+
6
+ 用于委托执行的共享邮件传输基础组件。
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@delexec/transport-email",
3
+ "version": "0.1.0",
4
+ "description": "Shared email transport primitives for delegated execution",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./package.json": "./package.json"
10
+ },
11
+ "files": [
12
+ "src",
13
+ "README.md"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "keywords": [
19
+ "delegated-execution",
20
+ "transport",
21
+ "email"
22
+ ]
23
+ }