@rynx-ai/daemon 0.1.10 → 0.1.11-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/bundled-plugins/.rynx-plugin/marketplace.json +10 -0
  2. package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +37 -0
  3. package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.d.ts +48 -0
  4. package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +54 -0
  5. package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.d.ts +85 -0
  6. package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.js +333 -0
  7. package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.d.ts +109 -0
  8. package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.js +347 -0
  9. package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +95 -0
  10. package/bundled-plugins/plugins/lark/dist/host-adapters.js +504 -0
  11. package/bundled-plugins/plugins/lark/dist/index.d.ts +7 -0
  12. package/bundled-plugins/plugins/lark/dist/index.js +7 -0
  13. package/bundled-plugins/plugins/lark/dist/lark-card-stream.d.ts +363 -0
  14. package/bundled-plugins/plugins/lark/dist/lark-card-stream.js +1335 -0
  15. package/bundled-plugins/plugins/lark/dist/lark-config-store.d.ts +30 -0
  16. package/bundled-plugins/plugins/lark/dist/lark-config-store.js +135 -0
  17. package/bundled-plugins/plugins/lark/dist/lark-content.d.ts +30 -0
  18. package/bundled-plugins/plugins/lark/dist/lark-content.js +138 -0
  19. package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.d.ts +65 -0
  20. package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.js +106 -0
  21. package/bundled-plugins/plugins/lark/dist/lark-diff-card.d.ts +25 -0
  22. package/bundled-plugins/plugins/lark/dist/lark-diff-card.js +58 -0
  23. package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.d.ts +16 -0
  24. package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.js +141 -0
  25. package/bundled-plugins/plugins/lark/dist/lark-resume-card.d.ts +31 -0
  26. package/bundled-plugins/plugins/lark/dist/lark-resume-card.js +105 -0
  27. package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.d.ts +18 -0
  28. package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.js +95 -0
  29. package/bundled-plugins/plugins/lark/dist/lark-sdk/client.d.ts +8 -0
  30. package/bundled-plugins/plugins/lark/dist/lark-sdk/client.js +32 -0
  31. package/bundled-plugins/plugins/lark/dist/lark-sdk/index.d.ts +1 -0
  32. package/bundled-plugins/plugins/lark/dist/lark-sdk/index.js +1 -0
  33. package/bundled-plugins/plugins/lark/dist/lark-sender.d.ts +114 -0
  34. package/bundled-plugins/plugins/lark/dist/lark-sender.js +323 -0
  35. package/bundled-plugins/plugins/lark/dist/lark-session-store.d.ts +13 -0
  36. package/bundled-plugins/plugins/lark/dist/lark-session-store.js +14 -0
  37. package/bundled-plugins/plugins/lark/dist/lark-settings-card.d.ts +55 -0
  38. package/bundled-plugins/plugins/lark/dist/lark-settings-card.js +158 -0
  39. package/bundled-plugins/plugins/lark/dist/lark-setup.d.ts +39 -0
  40. package/bundled-plugins/plugins/lark/dist/lark-setup.js +103 -0
  41. package/bundled-plugins/plugins/lark/dist/lark.d.ts +507 -0
  42. package/bundled-plugins/plugins/lark/dist/lark.js +1977 -0
  43. package/bundled-plugins/plugins/lark/dist/runtime.d.ts +13 -0
  44. package/bundled-plugins/plugins/lark/dist/runtime.js +131316 -0
  45. package/bundled-plugins/plugins/lark/dist/settings.d.ts +7 -0
  46. package/bundled-plugins/plugins/lark/dist/settings.js +126958 -0
  47. package/bundled-plugins/plugins/lark/package.json +57 -0
  48. package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +1 -0
  49. package/bundled-plugins/plugins/lark/ui/assets/index-D9N-AkLX.js +201 -0
  50. package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  51. package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  52. package/bundled-plugins/plugins/lark/ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  53. package/bundled-plugins/plugins/lark/ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  54. package/bundled-plugins/plugins/lark/ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  55. package/bundled-plugins/plugins/lark/ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  56. package/bundled-plugins/plugins/lark/ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  57. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  58. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  59. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  60. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  61. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  62. package/bundled-plugins/plugins/lark/ui/index.html +13 -0
  63. package/dist/bundled-plugins.d.ts +15 -0
  64. package/dist/bundled-plugins.js +134 -0
  65. package/dist/cli.js +26 -208
  66. package/dist/control-deps.js +2 -79
  67. package/dist/daemon-server.d.ts +4 -1
  68. package/dist/daemon-server.js +167 -57
  69. package/dist/db.js +145 -124
  70. package/dist/index-daemon.js +25 -4
  71. package/dist/migrations/cleanup-legacy-sessions.d.ts +1 -1
  72. package/dist/migrations/cleanup-legacy-sessions.js +33 -15
  73. package/dist/official-plugins.d.ts +2 -0
  74. package/dist/official-plugins.js +2 -0
  75. package/dist/plugin-cli.d.ts +1 -3
  76. package/dist/plugin-cli.js +56 -45
  77. package/dist/plugin-host-leases.d.ts +4 -0
  78. package/dist/plugin-host-leases.js +7 -0
  79. package/dist/plugin-host-rpc.d.ts +42 -4
  80. package/dist/plugin-host-rpc.js +144 -34
  81. package/dist/plugin-installer.d.ts +24 -55
  82. package/dist/plugin-installer.js +202 -415
  83. package/dist/plugin-management-service.d.ts +30 -23
  84. package/dist/plugin-management-service.js +290 -105
  85. package/dist/plugin-marketplace.d.ts +35 -0
  86. package/dist/plugin-marketplace.js +299 -0
  87. package/dist/plugin-package.d.ts +22 -24
  88. package/dist/plugin-package.js +520 -571
  89. package/dist/plugin-skill-context.d.ts +4 -0
  90. package/dist/plugin-skill-context.js +35 -0
  91. package/dist/plugin-store.d.ts +36 -37
  92. package/dist/plugin-store.js +140 -92
  93. package/dist/plugin-supervisor.d.ts +45 -125
  94. package/dist/plugin-supervisor.js +516 -1053
  95. package/dist/pm2.js +15 -10
  96. package/dist/runtime-project-store.d.ts +9 -0
  97. package/dist/runtime-project-store.js +83 -0
  98. package/dist/session-fallback-workspace.d.ts +22 -0
  99. package/dist/session-fallback-workspace.js +120 -0
  100. package/dist/session-fork-store.d.ts +41 -0
  101. package/dist/session-fork-store.js +288 -0
  102. package/dist/session-launch-operations.d.ts +14 -2
  103. package/dist/session-launch-operations.js +34 -2
  104. package/dist/session-meta-store.d.ts +7 -9
  105. package/dist/session-meta-store.js +28 -49
  106. package/dist/setup.js +26 -14
  107. package/package.json +14 -11
  108. package/dist/channel-store.d.ts +0 -22
  109. package/dist/channel-store.js +0 -65
  110. package/dist/instance-store.d.ts +0 -38
  111. package/dist/instance-store.js +0 -85
  112. package/dist/plugin-install-restart.d.ts +0 -13
  113. package/dist/plugin-install-restart.js +0 -24
  114. package/dist/plugin-runtime-control.d.ts +0 -2
  115. package/dist/plugin-runtime-control.js +0 -37
  116. package/dist/registry.d.ts +0 -10
  117. package/dist/registry.js +0 -52
@@ -31,12 +31,44 @@ export class SessionLaunchOperationTargetDeletedError extends Error {
31
31
  this.name = "SessionLaunchOperationTargetDeletedError";
32
32
  }
33
33
  }
34
+ /**
35
+ * Read the durable idempotency fence before resolving mutable launch selectors
36
+ * such as a Project or Agent. A replay must not fail merely because the
37
+ * original template was edited or deleted after the Session snapshot existed.
38
+ *
39
+ * `undefined` means the caller must resolve snapshots and proceed through
40
+ * {@link ensurePluginSessionLaunch}; that function remains the atomic race
41
+ * winner when two first attempts arrive concurrently.
42
+ */
43
+ export function replayPluginSessionLaunch(input) {
44
+ const pluginId = requiredId(input.pluginId, "pluginId");
45
+ const scopeId = requiredId(input.scopeId, "scopeId");
46
+ const operationId = requiredId(input.operationId, "operationId");
47
+ const requestHash = requiredRequestHash(input.requestHash);
48
+ const existing = readOperation(pluginId, scopeId, operationId);
49
+ if (!existing)
50
+ return undefined;
51
+ if (existing.request_hash !== requestHash) {
52
+ throw new SessionLaunchOperationConflictError(pluginId, scopeId, operationId);
53
+ }
54
+ if (existing.state === "deleted") {
55
+ throw new SessionLaunchOperationTargetDeletedError(pluginId, scopeId, operationId, existing.session_id);
56
+ }
57
+ if (!sessionExists(existing.session_id)) {
58
+ db().transaction(() => {
59
+ tombstoneOperations(existing.session_id, new Date().toISOString());
60
+ }).immediate();
61
+ throw new SessionLaunchOperationTargetDeletedError(pluginId, scopeId, operationId, existing.session_id);
62
+ }
63
+ return { disposition: "replayed", sessionId: existing.session_id };
64
+ }
34
65
  /**
35
66
  * Atomically allocate a persisted machine session for one plugin operation.
36
67
  *
37
68
  * 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.
69
+ * daemon restarts, and remote lease re-delivery. `scopeId` and `operationId`
70
+ * remain opaque; the Session row stores only the already-resolved workspace
71
+ * snapshot, never a Project identity.
40
72
  */
41
73
  export function ensurePluginSessionLaunch(input) {
42
74
  const pluginId = requiredId(input.pluginId, "pluginId");
@@ -1,15 +1,15 @@
1
1
  /**
2
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
3
+ * of origin (the web console AND every channel). Holds immutable workspace /
4
+ * execution snapshots plus display metadata so the **session list survives a
5
+ * daemon restart** without re-reading a Project or Agent; the transcript lives
6
+ * in `session_items` (see
6
7
  * {@link import("./session-log-store.js")}). Runtime `status` is intentionally
7
8
  * not stored — it is reconstructed as `idle` after a restart.
8
9
  *
9
10
  * 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.
11
+ * {@link SessionRegistry} adapter ({@link sessionRegistry}) used by daemon
12
+ * session services and capability-checked plugin Host RPC.
13
13
  */
14
14
  import { type MachineSessionRecord, type SessionRegistry } from "@rynx-ai/core";
15
15
  /** Back-compat alias for the unified record. */
@@ -17,9 +17,7 @@ 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;
21
20
  export declare function setSessionTitle(id: string, title: string): void;
22
21
  export declare function removeSessionMeta(id: string): void;
23
- /** {@link SessionRegistry} over the same `sessions` table — the form channels
24
- * receive through their `ChannelContext`. */
22
+ /** {@link SessionRegistry} over the same `sessions` table. */
25
23
  export declare const sessionRegistry: SessionRegistry;
@@ -1,19 +1,19 @@
1
1
  /**
2
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
3
+ * of origin (the web console AND every channel). Holds immutable workspace /
4
+ * execution snapshots plus display metadata so the **session list survives a
5
+ * daemon restart** without re-reading a Project or Agent; the transcript lives
6
+ * in `session_items` (see
6
7
  * {@link import("./session-log-store.js")}). Runtime `status` is intentionally
7
8
  * not stored — it is reconstructed as `idle` after a restart.
8
9
  *
9
10
  * 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.
11
+ * {@link SessionRegistry} adapter ({@link sessionRegistry}) used by daemon
12
+ * session services and capability-checked plugin Host RPC.
13
13
  */
14
- import { normalizeSessionTitle, } from "@rynx-ai/core";
14
+ import { normalizeSessionTitle, resolvedExecutionSnapshotSchema, sessionWorkspaceSnapshotSchema, } from "@rynx-ai/core";
15
15
  import { db } from "./db.js";
16
- const COLS = "id, provider, agent, config, model, reasoning_effort, title, source, created_at, updated_at";
16
+ const COLS = "id, workspace, execution, title, forked_from_session_id, source, created_at, updated_at";
17
17
  export function listSessionMetas() {
18
18
  return db()
19
19
  .prepare(`SELECT ${COLS} FROM sessions ORDER BY created_at`)
@@ -27,45 +27,22 @@ export function getSessionMeta(id) {
27
27
  export function createSessionMeta(meta) {
28
28
  db()
29
29
  .prepare(`INSERT OR IGNORE INTO sessions
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)`)
30
+ (id, workspace, execution, title, forked_from_session_id, source, created_at, updated_at)
31
+ VALUES (
32
+ @id, @workspace, @execution, @title, @forked_from_session_id,
33
+ @source, @created_at, @updated_at
34
+ )`)
32
35
  .run({
33
36
  id: meta.id,
34
- provider: meta.provider ?? null,
35
- agent: meta.agent ?? null,
36
- config: meta.config ? JSON.stringify(meta.config) : null,
37
- model: meta.model ?? null,
38
- reasoning_effort: meta.reasoningEffort ?? null,
37
+ workspace: JSON.stringify(sessionWorkspaceSnapshotSchema.parse(meta.workspace)),
38
+ execution: JSON.stringify(resolvedExecutionSnapshotSchema.parse(meta.execution)),
39
39
  title: normalizeSessionTitle(meta.title) ?? null,
40
+ forked_from_session_id: meta.forkedFromSessionId ?? null,
40
41
  source: meta.source,
41
42
  created_at: meta.createdAt,
42
43
  updated_at: meta.updatedAt ?? meta.createdAt,
43
44
  });
44
45
  }
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
- }
69
46
  export function setSessionTitle(id, title) {
70
47
  db()
71
48
  .prepare("UPDATE sessions SET title = ?, updated_at = ? WHERE id = ?")
@@ -79,11 +56,15 @@ export function removeSessionMeta(id) {
79
56
  SET state = 'deleted', updated_at = ?, deleted_at = ?
80
57
  WHERE session_id = ? AND state = 'active'`)
81
58
  .run(now, now, id);
59
+ db()
60
+ .prepare(`UPDATE session_fork_operations
61
+ SET state = 'target_deleted', updated_at = ?, deleted_at = ?
62
+ WHERE target_session_id = ? AND state IN ('reserved', 'completed')`)
63
+ .run(now, now, id);
82
64
  db().prepare("DELETE FROM sessions WHERE id = ?").run(id);
83
65
  }).immediate();
84
66
  }
85
- /** {@link SessionRegistry} over the same `sessions` table — the form channels
86
- * receive through their `ChannelContext`. */
67
+ /** {@link SessionRegistry} over the same `sessions` table. */
87
68
  export const sessionRegistry = {
88
69
  create: createSessionMeta,
89
70
  get: getSessionMeta,
@@ -94,17 +75,15 @@ export const sessionRegistry = {
94
75
  function rowToMeta(row) {
95
76
  return {
96
77
  id: row.id,
97
- provider: row.provider === "codex" || row.provider === "traex" || row.provider === "claude"
98
- ? row.provider
99
- : undefined,
100
- agent: row.agent ?? undefined,
101
- config: row.config ? JSON.parse(row.config) : undefined,
102
- model: row.model ?? undefined,
103
- reasoningEffort: (row.reasoning_effort ?? undefined),
78
+ workspace: sessionWorkspaceSnapshotSchema.parse(JSON.parse(row.workspace)),
79
+ execution: resolvedExecutionSnapshotSchema.parse(JSON.parse(row.execution)),
104
80
  // Normalize on read as well so pre-boundary database rows cannot poison an
105
81
  // entire Remote Session list page after upgrading the daemon.
106
82
  title: normalizeSessionTitle(row.title ?? undefined),
107
- source: row.source ?? "console",
83
+ ...(row.forked_from_session_id
84
+ ? { forkedFromSessionId: row.forked_from_session_id }
85
+ : {}),
86
+ source: row.source,
108
87
  createdAt: row.created_at,
109
88
  updatedAt: row.updated_at ?? row.created_at,
110
89
  };
package/dist/setup.js CHANGED
@@ -14,12 +14,11 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "n
14
14
  import { join } from "node:path";
15
15
  import * as p from "@clack/prompts";
16
16
  import { AGENT_RUNTIME_IDS, getRuntimeProfile, listAgentSpecs, loadConfig, resolveRuntimeHome, rynxAgentsDir, rynxConfigFile, rynxHome, } from "@rynx-ai/core";
17
- import { listChannels } from "./channel-store.js";
18
17
  import { resolveChromeForTestingRelease } from "./chrome-for-testing-release-resolver.js";
19
18
  import { createChromeForTestingArtifactStore, isChromeForTestingHostSupported, } from "./chrome-for-testing-store.js";
19
+ import { ensureBundledOfficialPlugins } from "./bundled-plugins.js";
20
20
  import { dbPath } from "./db.js";
21
- import { listInstances } from "./instance-store.js";
22
- import { isRunnablePluginRecord, listPlugins } from "./plugin-store.js";
21
+ import { listPlugins } from "./plugin-store.js";
23
22
  import { formatControlUrl } from "./pm2.js";
24
23
  const MIN_NODE_MAJOR = 20;
25
24
  export function controlListenModeFromHost(host) {
@@ -49,11 +48,13 @@ export async function runSetup() {
49
48
  if (!existsSync(rynxConfigFile()))
50
49
  writeConfig({ HOST: "127.0.0.1", PORT: 3000, LOG_LEVEL: "info" });
51
50
  const browser = inspectManagedBrowser();
51
+ const bundledMessage = await setupBundledPlugins();
52
52
  console.log(`非交互环境:已确保 ${rynxConfigFile()} 存在(默认值)。` +
53
53
  "在终端(TTY)下重跑 `rynx setup` 可分步配置,或直接编辑该文件。" +
54
54
  (browser.supported && !browser.installed
55
55
  ? "\nSession Browser 尚未安装;需要时运行 `rynx browser install`。"
56
- : ""));
56
+ : "") +
57
+ bundledMessage);
57
58
  return 0;
58
59
  }
59
60
  p.intro("rynx setup · 配置 ~/.rynx");
@@ -182,6 +183,9 @@ export async function runSetup() {
182
183
  else if (browserPlan.kind === "offer") {
183
184
  p.log.info("已跳过 Browser 安装;需要时运行 `rynx browser install`。");
184
185
  }
186
+ const bundledMessage = await setupBundledPlugins();
187
+ if (bundledMessage)
188
+ p.log.info(bundledMessage.trim());
185
189
  const url = formatControlUrl(String(cfg.HOST ?? "127.0.0.1"), Number(cfg.PORT ?? 3000));
186
190
  p.note([
187
191
  `启动常驻服务 rynx start`,
@@ -195,6 +199,24 @@ export async function runSetup() {
195
199
  p.outro("配置完成 🎉");
196
200
  return 0;
197
201
  }
202
+ async function setupBundledPlugins() {
203
+ try {
204
+ const result = await ensureBundledOfficialPlugins();
205
+ if (result.status === "unavailable")
206
+ return "";
207
+ const changed = [...result.installed, ...result.updated];
208
+ if (changed.length > 0) {
209
+ return `\n已准备官方插件: ${changed.join(", ")}。`;
210
+ }
211
+ if (result.unchanged.length > 0) {
212
+ return `\n官方插件已就绪: ${result.unchanged.join(", ")}。`;
213
+ }
214
+ return "";
215
+ }
216
+ catch (error) {
217
+ return `\n官方插件准备失败: ${error instanceof Error ? error.message : String(error)}`;
218
+ }
219
+ }
198
220
  // ── rynx doctor — read-only health check ────────────────────────────────────
199
221
  /** Read-only diagnostics. Returns a process exit code (0 ok, 1 on any failure). */
200
222
  export async function runDoctor() {
@@ -245,16 +267,6 @@ export async function runDoctor() {
245
267
  else {
246
268
  const plugins = listPlugins();
247
269
  p.log.info(`插件:${plugins.length} 个${plugins.length ? `(${plugins.map((pl) => pl.name).join(", ")})` : ""}`);
248
- const legacyPlugins = plugins.filter((plugin) => !isRunnablePluginRecord(plugin));
249
- if (legacyPlugins.length > 0) {
250
- p.log.warn(`需重新安装的旧插件:${legacyPlugins.map((plugin) => plugin.name).join(", ")}`);
251
- warn++;
252
- }
253
- const channels = listChannels();
254
- const configured = channels.filter((c) => c.options && Object.keys(c.options).length > 0).length;
255
- p.log.info(`channel:${channels.length} 个(${configured} 已配置凭据) · instance:${listInstances().length} 个`);
256
- if (channels.length === 0)
257
- (p.log.warn("还没有 channel —— 在 Web 控制台新建并授权"), warn++);
258
270
  }
259
271
  p.log.info(`agent:${(await listAgentSpecs()).length} 个(${rynxAgentsDir()})`);
260
272
  const browser = inspectManagedBrowser();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/daemon",
3
- "version": "0.1.10",
3
+ "version": "0.1.11-beta.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -13,7 +13,8 @@
13
13
  "access": "public"
14
14
  },
15
15
  "files": [
16
- "dist"
16
+ "dist",
17
+ "bundled-plugins"
17
18
  ],
18
19
  "bin": {
19
20
  "rynx": "./dist/cli.js"
@@ -32,19 +33,21 @@
32
33
  "pm2": "^6.0.0",
33
34
  "tar": "^7.5.19",
34
35
  "ws": "^8.21.0",
35
- "@rynx-ai/emulator": "0.1.10",
36
- "@rynx-ai/plugin-runner": "0.1.10",
37
- "@rynx-ai/core": "0.1.10",
38
- "@rynx-ai/protocol": "0.1.10",
39
- "@rynx-ai/remote-runtime-client": "0.1.10",
40
- "@rynx-ai/plugin-sdk": "0.1.10",
41
- "@rynx-ai/server": "0.1.10"
36
+ "@rynx-ai/cli": "0.1.11-beta.2",
37
+ "@rynx-ai/core": "0.1.11-beta.2",
38
+ "@rynx-ai/emulator": "0.1.11-beta.2",
39
+ "@rynx-ai/plugin-runner": "0.1.11-beta.2",
40
+ "@rynx-ai/plugin-sdk": "0.1.11-beta.2",
41
+ "@rynx-ai/protocol": "0.1.11-beta.2",
42
+ "@rynx-ai/remote-runtime-client": "0.1.11-beta.2",
43
+ "@rynx-ai/server": "0.1.11-beta.2"
42
44
  },
43
45
  "devDependencies": {
44
- "@types/ws": "^8.18.1"
46
+ "@types/ws": "^8.18.1",
47
+ "@rynx-ai/plugin-channel-lark": "0.1.11"
45
48
  },
46
49
  "scripts": {
47
- "build": "rm -rf dist && tsc -p tsconfig.json && chmod +x dist/cli.js dist/index-daemon.js",
50
+ "build": "rm -rf dist bundled-plugins && tsc -p tsconfig.json && chmod +x dist/cli.js dist/index-daemon.js && node ../../scripts/stage-bundled-plugins.mjs",
48
51
  "dev": "tsx watch --tsconfig ../../tsconfig.json src/index-daemon.ts"
49
52
  }
50
53
  }
@@ -1,22 +0,0 @@
1
- export interface ChannelConfig {
2
- /** Stable system-assigned id (never changes). */
3
- id: string;
4
- /** Human-visible, unique, editable label. */
5
- name: string;
6
- /** Channel type (which plugin-contributed factory). */
7
- type: string;
8
- options?: Record<string, unknown>;
9
- }
10
- export declare function listChannels(): ChannelConfig[];
11
- export declare function getChannel(id: string): ChannelConfig | undefined;
12
- /** Create a channel with a system-assigned id and a unique `name` label. */
13
- export declare function createChannel(input: {
14
- name: string;
15
- type: string;
16
- options?: Record<string, unknown>;
17
- }): ChannelConfig;
18
- export declare function renameChannel(id: string, name: string): void;
19
- /** Merge credentials/options into a channel (e.g. the result of an authorize flow). */
20
- export declare function setChannelOptions(id: string, options: Record<string, unknown>): void;
21
- /** Remove a channel; its bound instance (if any) cascades away (FK ON DELETE CASCADE). */
22
- export declare function removeChannel(id: string): void;
@@ -1,65 +0,0 @@
1
- /**
2
- * `channels` table CRUD — a channel is a plugin type + its config/credentials
3
- * (`options`), configured and authorized on its own. No agent binding here;
4
- * that lives on an {@link import("./instance-store.js")} instance.
5
- *
6
- * Credentials stay in each channel's `options` and are sent only to that
7
- * plugin runner instance; they are never copied into the daemon environment.
8
- */
9
- import { randomUUID } from "node:crypto";
10
- import { db } from "./db.js";
11
- function parseOptions(raw) {
12
- if (!raw)
13
- return undefined;
14
- try {
15
- const parsed = JSON.parse(raw);
16
- return parsed && typeof parsed === "object" ? parsed : undefined;
17
- }
18
- catch {
19
- return undefined;
20
- }
21
- }
22
- function toConfig(row) {
23
- return { id: row.id, name: row.name, type: row.type, options: parseOptions(row.options) };
24
- }
25
- export function listChannels() {
26
- const rows = db().prepare("SELECT * FROM channels ORDER BY name").all();
27
- return rows.map(toConfig);
28
- }
29
- export function getChannel(id) {
30
- const row = db().prepare("SELECT * FROM channels WHERE id = ?").get(id);
31
- return row ? toConfig(row) : undefined;
32
- }
33
- /** Create a channel with a system-assigned id and a unique `name` label. */
34
- export function createChannel(input) {
35
- const id = randomUUID();
36
- const now = new Date().toISOString();
37
- db()
38
- .prepare(`INSERT INTO channels (id, name, type, options, created_at, updated_at)
39
- VALUES (@id, @name, @type, @options, @now, @now)`)
40
- .run({
41
- id,
42
- name: input.name,
43
- type: input.type,
44
- options: input.options ? JSON.stringify(input.options) : null,
45
- now,
46
- });
47
- return getChannel(id);
48
- }
49
- export function renameChannel(id, name) {
50
- db().prepare("UPDATE channels SET name = ?, updated_at = ? WHERE id = ?").run(name, new Date().toISOString(), id);
51
- }
52
- /** Merge credentials/options into a channel (e.g. the result of an authorize flow). */
53
- export function setChannelOptions(id, options) {
54
- const current = getChannel(id);
55
- if (!current)
56
- return;
57
- const merged = { ...(current.options ?? {}), ...options };
58
- db()
59
- .prepare("UPDATE channels SET options = ?, updated_at = ? WHERE id = ?")
60
- .run(JSON.stringify(merged), new Date().toISOString(), id);
61
- }
62
- /** Remove a channel; its bound instance (if any) cascades away (FK ON DELETE CASCADE). */
63
- export function removeChannel(id) {
64
- db().prepare("DELETE FROM channels WHERE id = ?").run(id);
65
- }
@@ -1,38 +0,0 @@
1
- export interface InstanceConfig {
2
- id: string;
3
- channelId: string;
4
- agent?: string;
5
- enabled: boolean;
6
- }
7
- /** An instance joined with its channel — the shape the control plane lists. */
8
- export interface InstanceView {
9
- id: string;
10
- channelId: string;
11
- channelName: string;
12
- type: string;
13
- agent?: string;
14
- enabled: boolean;
15
- options?: Record<string, unknown>;
16
- }
17
- /** A mountable binding: instance id + its channel's type/options + bound agent. */
18
- export interface InstanceDescriptor {
19
- instanceId: string;
20
- type: string;
21
- options?: Record<string, unknown>;
22
- agent?: string;
23
- }
24
- export declare function listInstances(): InstanceView[];
25
- export declare function getInstance(id: string): InstanceView | undefined;
26
- /** Mountable descriptors for every enabled instance (joined with its channel). */
27
- export declare function listEnabledDescriptors(): InstanceDescriptor[];
28
- /** Bind a channel to an agent. Throws if the channel already has an instance. */
29
- export declare function createInstance(input: {
30
- channelId: string;
31
- agent?: string;
32
- enabled?: boolean;
33
- }): InstanceConfig;
34
- export declare function setInstanceAgent(id: string, agent: string | undefined): void;
35
- export declare function setInstanceEnabled(id: string, enabled: boolean): void;
36
- export declare function removeInstance(id: string): void;
37
- /** The instance bound to a channel, if any (1:1). */
38
- export declare function getInstanceByChannel(channelId: string): InstanceView | undefined;
@@ -1,85 +0,0 @@
1
- /**
2
- * `instances` table CRUD — a runnable binding of a channel to an agent (1:1 with
3
- * a channel, enforced by a UNIQUE channel_id). The daemon mounts enabled
4
- * instances; each resolves to its channel's type + options plus the bound agent.
5
- */
6
- import { randomUUID } from "node:crypto";
7
- import { db } from "./db.js";
8
- function parseOptions(raw) {
9
- if (!raw)
10
- return undefined;
11
- try {
12
- const parsed = JSON.parse(raw);
13
- return parsed && typeof parsed === "object" ? parsed : undefined;
14
- }
15
- catch {
16
- return undefined;
17
- }
18
- }
19
- const JOIN_SELECT = `
20
- SELECT i.id, i.channel_id, i.agent, i.enabled,
21
- c.name AS channel_name, c.type AS type, c.options AS options
22
- FROM instances i JOIN channels c ON c.id = i.channel_id`;
23
- function toView(row) {
24
- return {
25
- id: row.id,
26
- channelId: row.channel_id,
27
- channelName: row.channel_name,
28
- type: row.type,
29
- agent: row.agent ?? undefined,
30
- enabled: row.enabled !== 0,
31
- options: parseOptions(row.options),
32
- };
33
- }
34
- export function listInstances() {
35
- const rows = db().prepare(`${JOIN_SELECT} ORDER BY c.name`).all();
36
- return rows.map(toView);
37
- }
38
- export function getInstance(id) {
39
- const row = db().prepare(`${JOIN_SELECT} WHERE i.id = ?`).get(id);
40
- return row ? toView(row) : undefined;
41
- }
42
- /** Mountable descriptors for every enabled instance (joined with its channel). */
43
- export function listEnabledDescriptors() {
44
- const rows = db().prepare(`${JOIN_SELECT} WHERE i.enabled = 1`).all();
45
- return rows.map((row) => ({
46
- instanceId: row.id,
47
- type: row.type,
48
- options: parseOptions(row.options),
49
- agent: row.agent ?? undefined,
50
- }));
51
- }
52
- /** Bind a channel to an agent. Throws if the channel already has an instance. */
53
- export function createInstance(input) {
54
- const id = randomUUID();
55
- const now = new Date().toISOString();
56
- db()
57
- .prepare(`INSERT INTO instances (id, channel_id, agent, enabled, created_at, updated_at)
58
- VALUES (@id, @channelId, @agent, @enabled, @now, @now)`)
59
- .run({
60
- id,
61
- channelId: input.channelId,
62
- agent: input.agent ?? null,
63
- enabled: input.enabled === false ? 0 : 1,
64
- now,
65
- });
66
- return { id, channelId: input.channelId, agent: input.agent, enabled: input.enabled !== false };
67
- }
68
- export function setInstanceAgent(id, agent) {
69
- db()
70
- .prepare("UPDATE instances SET agent = ?, updated_at = ? WHERE id = ?")
71
- .run(agent ?? null, new Date().toISOString(), id);
72
- }
73
- export function setInstanceEnabled(id, enabled) {
74
- db()
75
- .prepare("UPDATE instances SET enabled = ?, updated_at = ? WHERE id = ?")
76
- .run(enabled ? 1 : 0, new Date().toISOString(), id);
77
- }
78
- export function removeInstance(id) {
79
- db().prepare("DELETE FROM instances WHERE id = ?").run(id);
80
- }
81
- /** The instance bound to a channel, if any (1:1). */
82
- export function getInstanceByChannel(channelId) {
83
- const row = db().prepare(`${JOIN_SELECT} WHERE i.channel_id = ?`).get(channelId);
84
- return row ? toView(row) : undefined;
85
- }
@@ -1,13 +0,0 @@
1
- export type PluginInstallRestartResult = "restarted" | "declined" | "not-running";
2
- export interface PluginInstallRestartOptions {
3
- restart: boolean;
4
- interactive: boolean;
5
- }
6
- export interface PluginInstallRestartDeps {
7
- isDaemonOnline(): boolean;
8
- confirmRestart(): Promise<boolean>;
9
- restartDaemon(): number;
10
- log(message: string): void;
11
- }
12
- /** Apply an installed plugin immediately without unexpectedly starting a stopped daemon. */
13
- export declare function restartAfterPluginInstall(options: PluginInstallRestartOptions, deps: PluginInstallRestartDeps): Promise<PluginInstallRestartResult>;
@@ -1,24 +0,0 @@
1
- /** Apply an installed plugin immediately without unexpectedly starting a stopped daemon. */
2
- export async function restartAfterPluginInstall(options, deps) {
3
- if (!options.interactive && !options.restart) {
4
- deps.log("Rynx was not restarted in non-interactive mode. Pass `--restart` to apply the plugin immediately.");
5
- return "declined";
6
- }
7
- if (!deps.isDaemonOnline()) {
8
- deps.log("Rynx is not running under PM2; the plugin will load when its owning service next starts or restarts.");
9
- return "not-running";
10
- }
11
- const shouldRestart = options.restart
12
- ? true
13
- : options.interactive
14
- ? await deps.confirmRestart()
15
- : false;
16
- if (!shouldRestart) {
17
- deps.log("Rynx restart skipped. Run `rynx restart` when you are ready to apply the plugin.");
18
- return "declined";
19
- }
20
- if (deps.restartDaemon() !== 0) {
21
- throw new Error("Plugin installed, but Rynx failed to restart. Run `rynx restart` manually.");
22
- }
23
- return "restarted";
24
- }
@@ -1,2 +0,0 @@
1
- export declare function reloadResidentPluginRuntime(pluginId: string): Promise<unknown>;
2
- export declare function ensureResidentPluginRuntime(pluginId: string): Promise<unknown>;
@@ -1,37 +0,0 @@
1
- import { ensureDaemonControlEndpoint } from "./control-endpoint.js";
2
- const RELOAD_TIMEOUT_MS = 30_000;
3
- export async function reloadResidentPluginRuntime(pluginId) {
4
- return requestResidentPluginRuntime(pluginId, "reload");
5
- }
6
- export async function ensureResidentPluginRuntime(pluginId) {
7
- return requestResidentPluginRuntime(pluginId, "ensure");
8
- }
9
- async function requestResidentPluginRuntime(pluginId, operation) {
10
- if (!/^[a-z][a-z0-9-]{0,62}$/.test(pluginId))
11
- throw new Error("invalid plugin id");
12
- const endpoint = await ensureDaemonControlEndpoint();
13
- const response = await fetch(`${endpoint.origin}/api/internal/plugin-runtimes/${encodeURIComponent(pluginId)}/${operation}`, {
14
- method: "POST",
15
- headers: { "x-rynx-management-token": endpoint.managementToken },
16
- signal: AbortSignal.timeout(RELOAD_TIMEOUT_MS),
17
- });
18
- const body = await response.text();
19
- if (!response.ok) {
20
- throw new Error(readError(body) || `plugin runtime refresh failed (${response.status})`);
21
- }
22
- try {
23
- return JSON.parse(body);
24
- }
25
- catch {
26
- throw new Error("daemon returned an invalid plugin runtime refresh response");
27
- }
28
- }
29
- function readError(body) {
30
- try {
31
- const parsed = JSON.parse(body);
32
- return typeof parsed.error === "string" ? parsed.error : undefined;
33
- }
34
- catch {
35
- return body.trim().slice(0, 2_000) || undefined;
36
- }
37
- }
@@ -1,10 +0,0 @@
1
- import { type ChannelInstanceDescriptor, type ChannelTypeDescriptor } from "@rynx-ai/core";
2
- import type { PluginSupervisor } from "./plugin-supervisor.js";
3
- /** Directory plugin packages and staging data live under. */
4
- export declare function pluginsDir(): string;
5
- export declare function listChannelTypes(): ChannelTypeDescriptor[];
6
- export declare function getChannelType(type: string): ChannelTypeDescriptor | undefined;
7
- /** Rebuild the catalog from static manifests projected by the supervisor. */
8
- export declare function loadPlugins(supervisor: PluginSupervisor, warn?: (message: string) => void): Promise<void>;
9
- /** Join enabled channel instances to their remote runner-backed factories. */
10
- export declare function resolveChannelInstances(warn?: (message: string) => void): Promise<ChannelInstanceDescriptor[]>;