@rynx-ai/daemon 0.1.0 → 0.1.9
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/dist/browser-cli-args.d.ts +20 -0
- package/dist/browser-cli-args.js +100 -0
- package/dist/browser-runner-session-context.d.ts +24 -0
- package/dist/browser-runner-session-context.js +113 -0
- package/dist/cdp-keyboard-input.d.ts +6 -0
- package/dist/cdp-keyboard-input.js +209 -0
- package/dist/channel-store.d.ts +0 -6
- package/dist/channel-store.js +2 -18
- package/dist/chrome-for-testing-release-resolver.d.ts +34 -0
- package/dist/chrome-for-testing-release-resolver.js +214 -0
- package/dist/chrome-for-testing-store.d.ts +85 -0
- package/dist/chrome-for-testing-store.js +836 -0
- package/dist/cli.js +467 -30
- package/dist/control-client.d.ts +113 -0
- package/dist/control-client.js +792 -0
- package/dist/control-deps.js +37 -6
- package/dist/control-endpoint.d.ts +14 -0
- package/dist/control-endpoint.js +124 -0
- package/dist/control-link-store.d.ts +16 -0
- package/dist/control-link-store.js +53 -0
- package/dist/daemon-owner.d.ts +14 -0
- package/dist/daemon-owner.js +104 -0
- package/dist/daemon-server.d.ts +12 -2
- package/dist/daemon-server.js +559 -28
- package/dist/db.js +140 -10
- package/dist/desktop-browser-host-client.d.ts +34 -0
- package/dist/desktop-browser-host-client.js +413 -0
- package/dist/direct-runtime-authenticator.d.ts +13 -0
- package/dist/direct-runtime-authenticator.js +127 -0
- package/dist/direct-runtime-config.d.ts +20 -0
- package/dist/direct-runtime-config.js +101 -0
- package/dist/embedded-browser-profile-cleanup.d.ts +58 -0
- package/dist/embedded-browser-profile-cleanup.js +338 -0
- package/dist/headless-browser-host.d.ts +14 -0
- package/dist/headless-browser-host.js +2156 -0
- package/dist/index-daemon.js +28 -5
- package/dist/installation-id.d.ts +33 -0
- package/dist/installation-id.js +692 -0
- package/dist/migrations/cleanup-legacy-sessions.d.ts +0 -3
- package/dist/migrations/cleanup-legacy-sessions.js +10 -28
- package/dist/plugin-cli.d.ts +30 -0
- package/dist/plugin-cli.js +270 -0
- package/dist/plugin-host-rpc.d.ts +85 -0
- package/dist/plugin-host-rpc.js +878 -0
- package/dist/plugin-install-restart.d.ts +13 -0
- package/dist/plugin-install-restart.js +24 -0
- package/dist/plugin-installer.d.ts +36 -15
- package/dist/plugin-installer.js +420 -155
- package/dist/plugin-package.d.ts +38 -0
- package/dist/plugin-package.js +553 -0
- package/dist/plugin-runtime-control.d.ts +2 -0
- package/dist/plugin-runtime-control.js +37 -0
- package/dist/plugin-store.d.ts +37 -8
- package/dist/plugin-store.js +80 -15
- package/dist/plugin-supervisor.d.ts +160 -0
- package/dist/plugin-supervisor.js +1143 -0
- package/dist/pm2.d.ts +3 -1
- package/dist/pm2.js +55 -13
- package/dist/provider-cli-service.d.ts +46 -0
- package/dist/provider-cli-service.js +510 -0
- package/dist/registry.d.ts +6 -19
- package/dist/registry.js +22 -87
- package/dist/remote-runtime-access-store.d.ts +61 -0
- package/dist/remote-runtime-access-store.js +386 -0
- package/dist/remote-runtime-admin.d.ts +32 -0
- package/dist/remote-runtime-admin.js +107 -0
- package/dist/remote-runtime-connection-manager.d.ts +92 -0
- package/dist/remote-runtime-connection-manager.js +1409 -0
- package/dist/remote-runtime-credential-store.d.ts +45 -0
- package/dist/remote-runtime-credential-store.js +639 -0
- package/dist/remote-runtime-target-control.d.ts +47 -0
- package/dist/remote-runtime-target-control.js +433 -0
- package/dist/remote-runtime-target-store.d.ts +53 -0
- package/dist/remote-runtime-target-store.js +307 -0
- package/dist/runtime-share-addresses.d.ts +21 -0
- package/dist/runtime-share-addresses.js +50 -0
- package/dist/session-browser-capability-store.d.ts +13 -0
- package/dist/session-browser-capability-store.js +58 -0
- package/dist/session-emulator-binding-store.d.ts +10 -0
- package/dist/session-emulator-binding-store.js +61 -0
- package/dist/session-launch-operations.d.ts +41 -0
- package/dist/session-launch-operations.js +124 -0
- package/dist/session-meta-store.d.ts +3 -2
- package/dist/session-meta-store.js +57 -7
- package/dist/setup.js +9 -4
- package/dist/skills-catalog.js +8 -4
- package/dist/update.d.ts +11 -2
- package/dist/update.js +0 -27
- package/package.json +18 -10
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { newSessionId } from "@rynx-ai/core";
|
|
2
|
+
import { db } from "./db.js";
|
|
3
|
+
import { createSessionMeta } from "./session-meta-store.js";
|
|
4
|
+
const MAX_ID_LENGTH = 512;
|
|
5
|
+
const SHA256_PATTERN = /^[a-fA-F0-9]{64}$/;
|
|
6
|
+
export class SessionLaunchOperationConflictError extends Error {
|
|
7
|
+
pluginId;
|
|
8
|
+
scopeId;
|
|
9
|
+
operationId;
|
|
10
|
+
code = "SESSION_LAUNCH_OPERATION_CONFLICT";
|
|
11
|
+
constructor(pluginId, scopeId, operationId) {
|
|
12
|
+
super(`session launch operation was reused with a different request: ${pluginId}/${scopeId}/${operationId}`);
|
|
13
|
+
this.pluginId = pluginId;
|
|
14
|
+
this.scopeId = scopeId;
|
|
15
|
+
this.operationId = operationId;
|
|
16
|
+
this.name = "SessionLaunchOperationConflictError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class SessionLaunchOperationTargetDeletedError extends Error {
|
|
20
|
+
pluginId;
|
|
21
|
+
scopeId;
|
|
22
|
+
operationId;
|
|
23
|
+
sessionId;
|
|
24
|
+
code = "SESSION_LAUNCH_OPERATION_TARGET_DELETED";
|
|
25
|
+
constructor(pluginId, scopeId, operationId, sessionId) {
|
|
26
|
+
super(`session launch operation target was deleted: ${pluginId}/${scopeId}/${operationId}`);
|
|
27
|
+
this.pluginId = pluginId;
|
|
28
|
+
this.scopeId = scopeId;
|
|
29
|
+
this.operationId = operationId;
|
|
30
|
+
this.sessionId = sessionId;
|
|
31
|
+
this.name = "SessionLaunchOperationTargetDeletedError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Atomically allocate a persisted machine session for one plugin operation.
|
|
36
|
+
*
|
|
37
|
+
* The durable operation row is the idempotency boundary across runner crashes,
|
|
38
|
+
* daemon restarts, and remote lease re-delivery. It deliberately excludes any
|
|
39
|
+
* Workspace concept: `scopeId` and `operationId` are opaque to the daemon.
|
|
40
|
+
*/
|
|
41
|
+
export function ensurePluginSessionLaunch(input) {
|
|
42
|
+
const pluginId = requiredId(input.pluginId, "pluginId");
|
|
43
|
+
const scopeId = requiredId(input.scopeId, "scopeId");
|
|
44
|
+
const operationId = requiredId(input.operationId, "operationId");
|
|
45
|
+
const requestHash = requiredRequestHash(input.requestHash);
|
|
46
|
+
const ensure = db().transaction(() => {
|
|
47
|
+
const existing = readOperation(pluginId, scopeId, operationId);
|
|
48
|
+
if (existing) {
|
|
49
|
+
if (existing.request_hash !== requestHash) {
|
|
50
|
+
throw new SessionLaunchOperationConflictError(pluginId, scopeId, operationId);
|
|
51
|
+
}
|
|
52
|
+
if (existing.state === "deleted") {
|
|
53
|
+
return { disposition: "deleted", sessionId: existing.session_id };
|
|
54
|
+
}
|
|
55
|
+
if (!sessionExists(existing.session_id)) {
|
|
56
|
+
// Preserve the operation key even when an older deletion path removed
|
|
57
|
+
// the session without first marking its launch operation. Throwing from
|
|
58
|
+
// inside this transaction would roll the repair back, so return a
|
|
59
|
+
// sentinel and raise the typed error after the commit.
|
|
60
|
+
tombstoneOperations(existing.session_id, new Date().toISOString());
|
|
61
|
+
return { disposition: "deleted", sessionId: existing.session_id };
|
|
62
|
+
}
|
|
63
|
+
return { disposition: "replayed", sessionId: existing.session_id };
|
|
64
|
+
}
|
|
65
|
+
const now = new Date().toISOString();
|
|
66
|
+
let sessionId = newSessionId();
|
|
67
|
+
while (sessionExists(sessionId))
|
|
68
|
+
sessionId = newSessionId();
|
|
69
|
+
createSessionMeta({
|
|
70
|
+
...input.session,
|
|
71
|
+
id: sessionId,
|
|
72
|
+
source: `plugin:${pluginId}`,
|
|
73
|
+
createdAt: now,
|
|
74
|
+
updatedAt: now,
|
|
75
|
+
});
|
|
76
|
+
if (!sessionExists(sessionId)) {
|
|
77
|
+
throw new Error(`plugin session launch failed to persist session metadata: ${sessionId}`);
|
|
78
|
+
}
|
|
79
|
+
db()
|
|
80
|
+
.prepare(`INSERT INTO plugin_session_launch_operations
|
|
81
|
+
(plugin_id, scope_id, operation_id, request_hash, session_id, state,
|
|
82
|
+
created_at, updated_at, deleted_at)
|
|
83
|
+
VALUES (?, ?, ?, ?, ?, 'active', ?, ?, NULL)`)
|
|
84
|
+
.run(pluginId, scopeId, operationId, requestHash, sessionId, now, now);
|
|
85
|
+
return { disposition: "created", sessionId };
|
|
86
|
+
});
|
|
87
|
+
const result = ensure.immediate();
|
|
88
|
+
if (result.disposition === "deleted") {
|
|
89
|
+
throw new SessionLaunchOperationTargetDeletedError(pluginId, scopeId, operationId, result.sessionId);
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
function readOperation(pluginId, scopeId, operationId) {
|
|
94
|
+
return db()
|
|
95
|
+
.prepare(`SELECT request_hash, session_id, state
|
|
96
|
+
FROM plugin_session_launch_operations
|
|
97
|
+
WHERE plugin_id = ? AND scope_id = ? AND operation_id = ?`)
|
|
98
|
+
.get(pluginId, scopeId, operationId);
|
|
99
|
+
}
|
|
100
|
+
function sessionExists(sessionId) {
|
|
101
|
+
return Boolean(db().prepare("SELECT 1 FROM sessions WHERE id = ?").pluck().get(sessionId));
|
|
102
|
+
}
|
|
103
|
+
function tombstoneOperations(sessionId, now) {
|
|
104
|
+
return db()
|
|
105
|
+
.prepare(`UPDATE plugin_session_launch_operations
|
|
106
|
+
SET state = 'deleted', updated_at = ?, deleted_at = ?
|
|
107
|
+
WHERE session_id = ? AND state = 'active'`)
|
|
108
|
+
.run(now, now, sessionId).changes;
|
|
109
|
+
}
|
|
110
|
+
function requiredId(value, label) {
|
|
111
|
+
const normalized = value?.trim();
|
|
112
|
+
if (!normalized)
|
|
113
|
+
throw new Error(`${label} is required`);
|
|
114
|
+
if (normalized.length > MAX_ID_LENGTH)
|
|
115
|
+
throw new Error(`${label} is too long`);
|
|
116
|
+
return normalized;
|
|
117
|
+
}
|
|
118
|
+
function requiredRequestHash(value) {
|
|
119
|
+
const normalized = value?.trim();
|
|
120
|
+
if (!SHA256_PATTERN.test(normalized)) {
|
|
121
|
+
throw new Error("requestHash must be a SHA-256 hex digest");
|
|
122
|
+
}
|
|
123
|
+
return normalized.toLowerCase();
|
|
124
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The unified machine-session record (SQLite) — one row per session regardless
|
|
3
3
|
* of origin (the web console AND every channel). Holds identity/meta (source /
|
|
4
|
-
* agent / inline config / model / title / …) so the **session list survives a
|
|
4
|
+
* provider / agent / inline config / model / title / …) so the **session list survives a
|
|
5
5
|
* daemon restart**; the transcript lives in `session_items` (see
|
|
6
6
|
* {@link import("./session-log-store.js")}). Runtime `status` is intentionally
|
|
7
7
|
* not stored — it is reconstructed as `idle` after a restart.
|
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
* via the `ChannelContext`, so a channel registers its sessions the same way the
|
|
12
12
|
* console does.
|
|
13
13
|
*/
|
|
14
|
-
import type
|
|
14
|
+
import { type MachineSessionRecord, type SessionRegistry } from "@rynx-ai/core";
|
|
15
15
|
/** Back-compat alias for the unified record. */
|
|
16
16
|
export type StoredSessionMeta = MachineSessionRecord;
|
|
17
17
|
export declare function listSessionMetas(): StoredSessionMeta[];
|
|
18
18
|
export declare function getSessionMeta(id: string): StoredSessionMeta | undefined;
|
|
19
19
|
export declare function createSessionMeta(meta: StoredSessionMeta): void;
|
|
20
|
+
export declare function updateSessionMeta(meta: StoredSessionMeta): void;
|
|
20
21
|
export declare function setSessionTitle(id: string, title: string): void;
|
|
21
22
|
export declare function removeSessionMeta(id: string): void;
|
|
22
23
|
/** {@link SessionRegistry} over the same `sessions` table — the form channels
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified machine-session record (SQLite) — one row per session regardless
|
|
3
|
+
* of origin (the web console AND every channel). Holds identity/meta (source /
|
|
4
|
+
* provider / agent / inline config / model / title / …) so the **session list survives a
|
|
5
|
+
* daemon restart**; the transcript lives in `session_items` (see
|
|
6
|
+
* {@link import("./session-log-store.js")}). Runtime `status` is intentionally
|
|
7
|
+
* not stored — it is reconstructed as `idle` after a restart.
|
|
8
|
+
*
|
|
9
|
+
* Exposes both the standalone functions the control-plane deps wire in and a
|
|
10
|
+
* {@link SessionRegistry} adapter ({@link sessionRegistry}) injected into channels
|
|
11
|
+
* via the `ChannelContext`, so a channel registers its sessions the same way the
|
|
12
|
+
* console does.
|
|
13
|
+
*/
|
|
14
|
+
import { normalizeSessionTitle, } from "@rynx-ai/core";
|
|
1
15
|
import { db } from "./db.js";
|
|
2
|
-
const COLS = "id, agent, config, model, reasoning_effort, title, source, created_at, updated_at";
|
|
16
|
+
const COLS = "id, provider, agent, config, model, reasoning_effort, title, source, created_at, updated_at";
|
|
3
17
|
export function listSessionMetas() {
|
|
4
18
|
return db()
|
|
5
19
|
.prepare(`SELECT ${COLS} FROM sessions ORDER BY created_at`)
|
|
@@ -13,27 +27,60 @@ export function getSessionMeta(id) {
|
|
|
13
27
|
export function createSessionMeta(meta) {
|
|
14
28
|
db()
|
|
15
29
|
.prepare(`INSERT OR IGNORE INTO sessions
|
|
16
|
-
(id, agent, config, model, reasoning_effort, title, source, created_at, updated_at)
|
|
17
|
-
VALUES (@id, @agent, @config, @model, @reasoning_effort, @title, @source, @created_at, @updated_at)`)
|
|
30
|
+
(id, provider, agent, config, model, reasoning_effort, title, source, created_at, updated_at)
|
|
31
|
+
VALUES (@id, @provider, @agent, @config, @model, @reasoning_effort, @title, @source, @created_at, @updated_at)`)
|
|
18
32
|
.run({
|
|
19
33
|
id: meta.id,
|
|
34
|
+
provider: meta.provider ?? null,
|
|
20
35
|
agent: meta.agent ?? null,
|
|
21
36
|
config: meta.config ? JSON.stringify(meta.config) : null,
|
|
22
37
|
model: meta.model ?? null,
|
|
23
38
|
reasoning_effort: meta.reasoningEffort ?? null,
|
|
24
|
-
title: meta.title ?? null,
|
|
39
|
+
title: normalizeSessionTitle(meta.title) ?? null,
|
|
25
40
|
source: meta.source,
|
|
26
41
|
created_at: meta.createdAt,
|
|
27
42
|
updated_at: meta.updatedAt ?? meta.createdAt,
|
|
28
43
|
});
|
|
29
44
|
}
|
|
45
|
+
export function updateSessionMeta(meta) {
|
|
46
|
+
db()
|
|
47
|
+
.prepare(`UPDATE sessions
|
|
48
|
+
SET provider = @provider,
|
|
49
|
+
agent = @agent,
|
|
50
|
+
config = @config,
|
|
51
|
+
model = @model,
|
|
52
|
+
reasoning_effort = @reasoning_effort,
|
|
53
|
+
title = @title,
|
|
54
|
+
source = @source,
|
|
55
|
+
updated_at = @updated_at
|
|
56
|
+
WHERE id = @id`)
|
|
57
|
+
.run({
|
|
58
|
+
id: meta.id,
|
|
59
|
+
provider: meta.provider ?? null,
|
|
60
|
+
agent: meta.agent ?? null,
|
|
61
|
+
config: meta.config ? JSON.stringify(meta.config) : null,
|
|
62
|
+
model: meta.model ?? null,
|
|
63
|
+
reasoning_effort: meta.reasoningEffort ?? null,
|
|
64
|
+
title: normalizeSessionTitle(meta.title) ?? null,
|
|
65
|
+
source: meta.source,
|
|
66
|
+
updated_at: meta.updatedAt ?? new Date().toISOString(),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
30
69
|
export function setSessionTitle(id, title) {
|
|
31
70
|
db()
|
|
32
71
|
.prepare("UPDATE sessions SET title = ?, updated_at = ? WHERE id = ?")
|
|
33
|
-
.run(title, new Date().toISOString(), id);
|
|
72
|
+
.run(normalizeSessionTitle(title), new Date().toISOString(), id);
|
|
34
73
|
}
|
|
35
74
|
export function removeSessionMeta(id) {
|
|
36
|
-
db().
|
|
75
|
+
db().transaction(() => {
|
|
76
|
+
const now = new Date().toISOString();
|
|
77
|
+
db()
|
|
78
|
+
.prepare(`UPDATE plugin_session_launch_operations
|
|
79
|
+
SET state = 'deleted', updated_at = ?, deleted_at = ?
|
|
80
|
+
WHERE session_id = ? AND state = 'active'`)
|
|
81
|
+
.run(now, now, id);
|
|
82
|
+
db().prepare("DELETE FROM sessions WHERE id = ?").run(id);
|
|
83
|
+
}).immediate();
|
|
37
84
|
}
|
|
38
85
|
/** {@link SessionRegistry} over the same `sessions` table — the form channels
|
|
39
86
|
* receive through their `ChannelContext`. */
|
|
@@ -47,11 +94,14 @@ export const sessionRegistry = {
|
|
|
47
94
|
function rowToMeta(row) {
|
|
48
95
|
return {
|
|
49
96
|
id: row.id,
|
|
97
|
+
provider: row.provider === "codex" || row.provider === "claude" ? row.provider : undefined,
|
|
50
98
|
agent: row.agent ?? undefined,
|
|
51
99
|
config: row.config ? JSON.parse(row.config) : undefined,
|
|
52
100
|
model: row.model ?? undefined,
|
|
53
101
|
reasoningEffort: (row.reasoning_effort ?? undefined),
|
|
54
|
-
|
|
102
|
+
// Normalize on read as well so pre-boundary database rows cannot poison an
|
|
103
|
+
// entire Remote Session list page after upgrading the daemon.
|
|
104
|
+
title: normalizeSessionTitle(row.title ?? undefined),
|
|
55
105
|
source: row.source ?? "console",
|
|
56
106
|
createdAt: row.created_at,
|
|
57
107
|
updatedAt: row.updated_at ?? row.created_at,
|
package/dist/setup.js
CHANGED
|
@@ -17,7 +17,7 @@ import { AGENT_RUNTIME_IDS, getRuntimeProfile, listAgentSpecs, loadConfig, resol
|
|
|
17
17
|
import { listChannels } from "./channel-store.js";
|
|
18
18
|
import { dbPath } from "./db.js";
|
|
19
19
|
import { listInstances } from "./instance-store.js";
|
|
20
|
-
import { listPlugins } from "./plugin-store.js";
|
|
20
|
+
import { isRunnablePluginRecord, listPlugins } from "./plugin-store.js";
|
|
21
21
|
import { formatControlUrl } from "./pm2.js";
|
|
22
22
|
const MIN_NODE_MAJOR = 20;
|
|
23
23
|
// ── rynx setup — interactive wizard ─────────────────────────────────────────
|
|
@@ -26,7 +26,7 @@ export async function runSetup() {
|
|
|
26
26
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
27
27
|
// Non-interactive (CI / piped): never block on a prompt — just ensure defaults.
|
|
28
28
|
if (!existsSync(rynxConfigFile()))
|
|
29
|
-
writeConfig({ HOST: "
|
|
29
|
+
writeConfig({ HOST: "127.0.0.1", PORT: 3000, LOG_LEVEL: "info" });
|
|
30
30
|
console.log(`非交互环境:已确保 ${rynxConfigFile()} 存在(默认值)。` +
|
|
31
31
|
"在终端(TTY)下重跑 `rynx setup` 可分步配置,或直接编辑该文件。");
|
|
32
32
|
return 0;
|
|
@@ -81,12 +81,12 @@ export async function runSetup() {
|
|
|
81
81
|
if (p.isCancel(port))
|
|
82
82
|
return bail();
|
|
83
83
|
cfg.PORT = Number(port);
|
|
84
|
-
cfg.HOST = cfg.HOST ?? "
|
|
84
|
+
cfg.HOST = cfg.HOST ?? "127.0.0.1";
|
|
85
85
|
cfg.LOG_LEVEL = cfg.LOG_LEVEL ?? "info";
|
|
86
86
|
writeConfig(cfg);
|
|
87
87
|
p.log.success(`已写入 ${rynxConfigFile()}(chmod 600)`);
|
|
88
88
|
p.log.message(`HOST=${cfg.HOST} PORT=${cfg.PORT} AGENT_RUNTIME=${cfg.AGENT_RUNTIME ?? "(默认)"}`);
|
|
89
|
-
const url = formatControlUrl(String(cfg.HOST ?? "
|
|
89
|
+
const url = formatControlUrl(String(cfg.HOST ?? "127.0.0.1"), Number(cfg.PORT ?? 3000));
|
|
90
90
|
p.note([
|
|
91
91
|
`启动常驻服务 rynx start`,
|
|
92
92
|
`打开控制台 ${url}`,
|
|
@@ -146,6 +146,11 @@ export async function runDoctor() {
|
|
|
146
146
|
else {
|
|
147
147
|
const plugins = listPlugins();
|
|
148
148
|
p.log.info(`插件:${plugins.length} 个${plugins.length ? `(${plugins.map((pl) => pl.name).join(", ")})` : ""}`);
|
|
149
|
+
const legacyPlugins = plugins.filter((plugin) => !isRunnablePluginRecord(plugin));
|
|
150
|
+
if (legacyPlugins.length > 0) {
|
|
151
|
+
p.log.warn(`需重新安装的旧插件:${legacyPlugins.map((plugin) => plugin.name).join(", ")}`);
|
|
152
|
+
warn++;
|
|
153
|
+
}
|
|
149
154
|
const channels = listChannels();
|
|
150
155
|
const configured = channels.filter((c) => c.options && Object.keys(c.options).length > 0).length;
|
|
151
156
|
p.log.info(`channel:${channels.length} 个(${configured} 已配置凭据) · instance:${listInstances().length} 个`);
|
package/dist/skills-catalog.js
CHANGED
|
@@ -12,7 +12,7 @@ const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
|
12
12
|
/** A single file's contents are capped to keep responses small. */
|
|
13
13
|
const MAX_SKILL_FILE_BYTES = 256 * 1024;
|
|
14
14
|
export function listCatalogSkills() {
|
|
15
|
-
return
|
|
15
|
+
return listMergedSkills();
|
|
16
16
|
}
|
|
17
17
|
/** Install a skill (or a repo's skills) into the catalog per a full install
|
|
18
18
|
* recipe; delegates to the core catalog layer (which also writes each skill's
|
|
@@ -20,7 +20,7 @@ export function listCatalogSkills() {
|
|
|
20
20
|
* the refreshed catalog. */
|
|
21
21
|
export async function installCatalogSkill(recipe) {
|
|
22
22
|
await installIntoCatalog(recipe, rynxSkillsDir());
|
|
23
|
-
return
|
|
23
|
+
return listMergedSkills();
|
|
24
24
|
}
|
|
25
25
|
/** Detail for one catalog skill: its metadata + the file tree under its dir. */
|
|
26
26
|
export async function getCatalogSkill(name) {
|
|
@@ -30,6 +30,7 @@ export async function getCatalogSkill(name) {
|
|
|
30
30
|
const meta = await readSkillMeta(dir);
|
|
31
31
|
if (!meta)
|
|
32
32
|
return null;
|
|
33
|
+
const ref = meta.ref;
|
|
33
34
|
const files = await walkSkillFiles(dir, dir);
|
|
34
35
|
let frontmatter;
|
|
35
36
|
try {
|
|
@@ -41,7 +42,7 @@ export async function getCatalogSkill(name) {
|
|
|
41
42
|
return {
|
|
42
43
|
name: meta.name,
|
|
43
44
|
description: meta.description,
|
|
44
|
-
...(
|
|
45
|
+
...(ref ? { ref } : {}),
|
|
45
46
|
frontmatter,
|
|
46
47
|
totalSize: files.reduce((sum, f) => sum + f.size, 0),
|
|
47
48
|
files,
|
|
@@ -63,7 +64,7 @@ async function walkSkillFiles(root, dir) {
|
|
|
63
64
|
export async function readCatalogSkillFile(name, relPath) {
|
|
64
65
|
if (!SKILL_NAME_PATTERN.test(name))
|
|
65
66
|
throw new Error(`invalid skill name: ${name}`);
|
|
66
|
-
const dir = path.resolve(rynxSkillsDir(), name);
|
|
67
|
+
const dir = path.resolve(path.join(rynxSkillsDir(), name));
|
|
67
68
|
const target = path.resolve(dir, relPath);
|
|
68
69
|
if (target !== dir && !target.startsWith(dir + path.sep)) {
|
|
69
70
|
throw new Error("path escapes the skill directory");
|
|
@@ -78,6 +79,9 @@ export async function readCatalogSkillFile(name, relPath) {
|
|
|
78
79
|
throw new Error("binary file — not viewable");
|
|
79
80
|
return { path: relPath, content: buf.toString("utf8") };
|
|
80
81
|
}
|
|
82
|
+
async function listMergedSkills() {
|
|
83
|
+
return (await scanSkillsDir(rynxSkillsDir())).sort((a, b) => a.name.localeCompare(b.name));
|
|
84
|
+
}
|
|
81
85
|
/** Remove a catalog skill by name. Returns false if it wasn't present. */
|
|
82
86
|
export async function removeCatalogSkill(name) {
|
|
83
87
|
if (!SKILL_NAME_PATTERN.test(name))
|
package/dist/update.d.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import { type UpdateCheck } from "@rynx-ai/core";
|
|
2
1
|
export interface UpdateOptions {
|
|
3
2
|
check?: boolean;
|
|
4
3
|
/** With `check`, emit a typed {@link UpdateCheck} as JSON instead of a human line. */
|
|
5
4
|
json?: boolean;
|
|
6
5
|
version?: string;
|
|
7
|
-
resultFile?: string;
|
|
8
6
|
}
|
|
7
|
+
export type UpdateCheck = {
|
|
8
|
+
status: "up_to_date";
|
|
9
|
+
current: string;
|
|
10
|
+
} | {
|
|
11
|
+
status: "behind";
|
|
12
|
+
current: string;
|
|
13
|
+
latest: string;
|
|
14
|
+
} | {
|
|
15
|
+
status: "error";
|
|
16
|
+
detail: string;
|
|
17
|
+
};
|
|
9
18
|
/** Is `latest` a newer release than `current`? Numeric major.minor.patch (prerelease ignored). */
|
|
10
19
|
export declare function isNewer(latest: string, current: string): boolean;
|
|
11
20
|
/** Typed `--check` result a channel consumes via `--check --json`. */
|
package/dist/update.js
CHANGED
|
@@ -7,12 +7,8 @@
|
|
|
7
7
|
* rynx update [version] upgrade (to a version, or latest) + restart
|
|
8
8
|
* rynx update --check report status only (human one-liner)
|
|
9
9
|
* rynx update --check --json report status only (typed UpdateCheck JSON)
|
|
10
|
-
* rynx update --result-file <path> after finishing, write the outcome JSON to
|
|
11
|
-
* <path> (channel-agnostic UpdateOutcome; a
|
|
12
|
-
* plugin reads it post-restart to notify a chat)
|
|
13
10
|
*/
|
|
14
11
|
import { spawnSync } from "node:child_process";
|
|
15
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
16
12
|
import { createRequire } from "node:module";
|
|
17
13
|
import { dirname, sep } from "node:path";
|
|
18
14
|
import { loadConfig } from "@rynx-ai/core";
|
|
@@ -98,19 +94,6 @@ async function verifyHealthy() {
|
|
|
98
94
|
}
|
|
99
95
|
return false;
|
|
100
96
|
}
|
|
101
|
-
/** Write the post-restart outcome to `--result-file` (best-effort; stamps `at`). */
|
|
102
|
-
function writeOutcome(file, outcome) {
|
|
103
|
-
if (!file)
|
|
104
|
-
return;
|
|
105
|
-
try {
|
|
106
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
107
|
-
const full = { ...outcome, at: new Date().toISOString() };
|
|
108
|
-
writeFileSync(file, `${JSON.stringify(full, null, 2)}\n`);
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
/* best-effort */
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
97
|
/** Run `rynx update`. Returns a process exit code. */
|
|
115
98
|
export async function runUpdate(opts) {
|
|
116
99
|
const { name, version: current } = selfPackage();
|
|
@@ -122,13 +105,11 @@ export async function runUpdate(opts) {
|
|
|
122
105
|
// Never `npm install -g` over a working checkout / linked dev build.
|
|
123
106
|
if (isLocalDevInstall()) {
|
|
124
107
|
console.error(`update: ${name} is a local dev install — update your checkout (e.g. pnpm build), not npm`);
|
|
125
|
-
writeOutcome(opts.resultFile, { status: "fail", from: current, to: current, error: "local dev install" });
|
|
126
108
|
return 1;
|
|
127
109
|
}
|
|
128
110
|
const target = opts.version ?? latest;
|
|
129
111
|
if (!target) {
|
|
130
112
|
console.error(`update: could not resolve a target version for ${name} (registry unreachable?)`);
|
|
131
|
-
writeOutcome(opts.resultFile, { status: "fail", from: current, to: current, error: "could not resolve target version" });
|
|
132
113
|
return 1;
|
|
133
114
|
}
|
|
134
115
|
if (!opts.version && latest && !isNewer(latest, current)) {
|
|
@@ -138,12 +119,6 @@ export async function runUpdate(opts) {
|
|
|
138
119
|
console.log(`Updating ${name}: ${current} → ${target} …`);
|
|
139
120
|
if (!npmInstallGlobal(name, target)) {
|
|
140
121
|
console.error("update: npm install failed");
|
|
141
|
-
writeOutcome(opts.resultFile, { status: "fail", from: current, to: target, error: "npm install failed" });
|
|
142
|
-
// The install failed before any restart, so the channel that spawned us never
|
|
143
|
-
// reboots to drain the outcome. Bounce the daemon (still on the old, healthy
|
|
144
|
-
// version) so its boot-time drain delivers the failure to chat.
|
|
145
|
-
if (opts.resultFile)
|
|
146
|
-
startDaemon({ force: true });
|
|
147
122
|
return 1;
|
|
148
123
|
}
|
|
149
124
|
startDaemon({ force: true });
|
|
@@ -151,10 +126,8 @@ export async function runUpdate(opts) {
|
|
|
151
126
|
console.error(`update: ${name}@${target} did not come back up — rolling back to ${current}`);
|
|
152
127
|
npmInstallGlobal(name, current);
|
|
153
128
|
startDaemon({ force: true });
|
|
154
|
-
writeOutcome(opts.resultFile, { status: "fail", from: current, to: target, error: "did not come up; rolled back" });
|
|
155
129
|
return 1;
|
|
156
130
|
}
|
|
157
131
|
console.log(`Updated ${name} → ${target} and restarted.`);
|
|
158
|
-
writeOutcome(opts.resultFile, { status: "ok", from: current, to: target });
|
|
159
132
|
return 0;
|
|
160
133
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynx-ai/daemon",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "Rynx daemon CLI: supervises the local agent control plane and isolated optional plugin runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -15,20 +15,28 @@
|
|
|
15
15
|
},
|
|
16
16
|
"main": "./dist/index-daemon.js",
|
|
17
17
|
"exports": {
|
|
18
|
-
"./
|
|
19
|
-
"types": "./dist/
|
|
20
|
-
"default": "./dist/
|
|
18
|
+
"./client": {
|
|
19
|
+
"types": "./dist/control-client.d.ts",
|
|
20
|
+
"default": "./dist/control-client.js"
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@clack/prompts": "^1.6.0",
|
|
25
|
+
"@electron-internal/extract-zip": "^1.0.4",
|
|
25
26
|
"better-sqlite3": "^12.11.1",
|
|
26
27
|
"pm2": "^6.0.0",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"@rynx-ai/
|
|
30
|
-
"@rynx-ai/
|
|
31
|
-
"@rynx-ai/
|
|
28
|
+
"tar": "^7.5.19",
|
|
29
|
+
"ws": "^8.21.0",
|
|
30
|
+
"@rynx-ai/emulator": "0.1.9",
|
|
31
|
+
"@rynx-ai/core": "0.1.9",
|
|
32
|
+
"@rynx-ai/plugin-runner": "0.1.9",
|
|
33
|
+
"@rynx-ai/protocol": "0.1.9",
|
|
34
|
+
"@rynx-ai/plugin-sdk": "0.1.9",
|
|
35
|
+
"@rynx-ai/remote-runtime-client": "0.1.9",
|
|
36
|
+
"@rynx-ai/server": "0.1.9"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/ws": "^8.18.1"
|
|
32
40
|
},
|
|
33
41
|
"scripts": {
|
|
34
42
|
"build": "rm -rf dist && tsc -p tsconfig.json && chmod +x dist/cli.js dist/index-daemon.js",
|