@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
@@ -1,49 +1,55 @@
1
- import { type PluginInstallCommitInput, type PluginInstallCommitResult, type PluginInstallPreparation, type PluginInstallPrepareInput } from "@rynx-ai/protocol/plugin-management";
2
- import { type PluginCapability } from "@rynx-ai/plugin-sdk";
1
+ import type { PluginInstallCommitInput, PluginInstallCommitResult, PluginInstallPreparation, PluginInstallPrepareInput, PluginManagementItem } from "@rynx-ai/protocol/plugin-management";
2
+ import type { PluginJsonValue } from "@rynx-ai/plugin-sdk";
3
3
  import { type PreparePluginInstallationOptions, type PreparedPluginInstallation } from "./plugin-installer.js";
4
- import { type PluginRecord, type PluginSource } from "./plugin-store.js";
5
- export type PluginManagementState = "enabled" | "disabled" | "reinstall-required";
6
- /** Redacted management view; installation paths and source specs never cross RPC. */
7
- export interface PluginManagementItem {
8
- id: string;
9
- version?: string;
10
- source: PluginSource;
11
- state: PluginManagementState;
12
- enabled: boolean;
13
- requestedCapabilities: PluginCapability[];
14
- grants: PluginCapability[];
15
- updatedAt: string;
16
- }
4
+ import { type PluginMarketplaceRecord } from "./plugin-marketplace.js";
5
+ import { type PluginRecord } from "./plugin-store.js";
6
+ import type { PluginSupervisor } from "./plugin-supervisor.js";
17
7
  export interface PluginManagementServiceDependencies {
18
8
  list(): PluginRecord[];
19
9
  get(id: string): PluginRecord | undefined;
20
10
  setEnabled(id: string, enabled: boolean): void;
21
11
  uninstall(id: string): Promise<boolean>;
22
12
  prepare(spec: string, onProgress: (line: string) => void, options: PreparePluginInstallationOptions): Promise<PreparedPluginInstallation>;
13
+ resolveMarketplace(identity: string): {
14
+ marketplace: {
15
+ marketplaceId: string;
16
+ };
17
+ pluginId: string;
18
+ spec: string;
19
+ };
20
+ getMarketplace(id: string): Pick<PluginMarketplaceRecord, "marketplaceId" | "sourceType" | "resolvedRevision" | "builtIn"> | undefined;
23
21
  }
24
22
  export interface PluginManagementServiceOptions {
25
23
  preparationTtlMs?: number;
26
24
  now?: () => number;
27
25
  token?: () => string;
28
- schedule?: (callback: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
26
+ supervisor?: PluginSupervisor;
29
27
  }
30
- /**
31
- * Daemon-owned plugin registry mutations exposed to the authenticated local
32
- * management API. Package installation remains outside this service until its
33
- * capability approval is cryptographically bound to one staged artifact.
34
- */
35
28
  export declare class PluginManagementService {
36
29
  private readonly dependencies;
37
30
  private readonly pending;
38
31
  private readonly now;
39
32
  private readonly token;
40
- private readonly schedule;
41
33
  private readonly preparationTtlMs;
34
+ private readonly supervisor?;
35
+ private readonly activeSettingsCalls;
42
36
  constructor(dependencies?: PluginManagementServiceDependencies, options?: PluginManagementServiceOptions);
43
37
  list(): {
44
38
  plugins: PluginManagementItem[];
45
39
  };
46
- setEnabled(id: string, enabled: boolean): PluginManagementItem | undefined;
40
+ settingsUi(id: string): {
41
+ generation: string;
42
+ assetGeneration: string;
43
+ html: string;
44
+ } | undefined;
45
+ settingsUiAsset(id: string, assetGeneration: string, assetPath: string): {
46
+ contentType: string;
47
+ data: Buffer;
48
+ } | undefined;
49
+ callSettings(id: string, generation: string, method: string, params: PluginJsonValue | undefined, options?: {
50
+ signal?: AbortSignal;
51
+ }): Promise<PluginJsonValue>;
52
+ setEnabled(id: string, enabled: boolean): Promise<PluginManagementItem | undefined>;
47
53
  uninstall(id: string): Promise<boolean>;
48
54
  prepareInstallation(input: PluginInstallPrepareInput, options?: {
49
55
  signal?: AbortSignal;
@@ -55,4 +61,5 @@ export declare class PluginManagementService {
55
61
  dispose(): void;
56
62
  private takePreparation;
57
63
  private uniqueToken;
64
+ private managementItem;
58
65
  }
@@ -1,27 +1,30 @@
1
- import { randomBytes } from "node:crypto";
2
- import { PLUGIN_ID_PATTERN } from "@rynx-ai/plugin-sdk";
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { lstatSync, readFileSync, realpathSync } from "node:fs";
3
+ import { dirname, extname, relative, resolve, sep } from "node:path";
3
4
  import { preparePluginInstallation, uninstallPlugin, } from "./plugin-installer.js";
4
- import { getPlugin, isRunnablePluginRecord, listPlugins, setPluginEnabled, } from "./plugin-store.js";
5
+ import { getPluginMarketplace, resolveMarketplacePlugin, } from "./plugin-marketplace.js";
6
+ import { resolvePackagePath } from "./plugin-package.js";
7
+ import { getPlugin, listPlugins, setPluginEnabled, } from "./plugin-store.js";
5
8
  const defaultDependencies = {
6
9
  list: listPlugins,
7
10
  get: getPlugin,
8
11
  setEnabled: setPluginEnabled,
9
12
  uninstall: (id) => uninstallPlugin(id),
10
13
  prepare: preparePluginInstallation,
14
+ resolveMarketplace: resolveMarketplacePlugin,
15
+ getMarketplace: getPluginMarketplace,
11
16
  };
12
17
  const DEFAULT_PREPARATION_TTL_MS = 5 * 60 * 1_000;
13
- /**
14
- * Daemon-owned plugin registry mutations exposed to the authenticated local
15
- * management API. Package installation remains outside this service until its
16
- * capability approval is cryptographically bound to one staged artifact.
17
- */
18
+ const MAX_SETTINGS_UI_DOCUMENT_BYTES = 2 * 1024 * 1024;
19
+ const MAX_SETTINGS_UI_ASSET_BYTES = 16 * 1024 * 1024;
18
20
  export class PluginManagementService {
19
21
  dependencies;
20
22
  pending = new Map();
21
23
  now;
22
24
  token;
23
- schedule;
24
25
  preparationTtlMs;
26
+ supervisor;
27
+ activeSettingsCalls = new Map();
25
28
  constructor(dependencies = defaultDependencies, options = {}) {
26
29
  this.dependencies = dependencies;
27
30
  this.preparationTtlMs = options.preparationTtlMs ?? DEFAULT_PREPARATION_TTL_MS;
@@ -30,62 +33,169 @@ export class PluginManagementService {
30
33
  }
31
34
  this.now = options.now ?? Date.now;
32
35
  this.token = options.token ?? (() => randomBytes(32).toString("base64url"));
33
- this.schedule = options.schedule ?? setTimeout;
36
+ this.supervisor = options.supervisor;
34
37
  }
35
38
  list() {
36
- return { plugins: this.dependencies.list().map(toManagementItem) };
39
+ return { plugins: this.dependencies.list().map((record) => this.managementItem(record)) };
37
40
  }
38
- setEnabled(id, enabled) {
39
- validatePluginId(id);
41
+ settingsUi(id) {
42
+ const record = this.dependencies.get(id);
43
+ const entry = record?.manifest.settings?.ui.entry;
44
+ if (!record || !entry)
45
+ return undefined;
46
+ const path = resolvePackagePath(record.installPath, entry, "settings.ui.entry", "file");
47
+ const info = lstatSync(path);
48
+ if (info.size > MAX_SETTINGS_UI_DOCUMENT_BYTES) {
49
+ throw new Error("plugin settings UI exceeds 2 MiB");
50
+ }
51
+ return {
52
+ generation: record.integrity,
53
+ assetGeneration: settingsUiAssetGeneration(record.integrity),
54
+ html: readFileSync(path, "utf8"),
55
+ };
56
+ }
57
+ settingsUiAsset(id, assetGeneration, assetPath) {
58
+ const record = this.dependencies.get(id);
59
+ const entry = record?.manifest.settings?.ui.entry;
60
+ if (!record || !entry)
61
+ return undefined;
62
+ if (assetGeneration !== settingsUiAssetGeneration(record.integrity))
63
+ return undefined;
64
+ if (!isSettingsUiAssetPath(assetPath))
65
+ return undefined;
66
+ const entryPath = resolvePackagePath(record.installPath, entry, "settings.ui.entry", "file");
67
+ const root = realpathSync(dirname(entryPath));
68
+ const unresolved = resolve(root, ...assetPath.split("/"));
69
+ let candidate;
70
+ let info;
71
+ try {
72
+ info = lstatSync(unresolved);
73
+ if (!info.isFile() || info.isSymbolicLink())
74
+ return undefined;
75
+ candidate = realpathSync(unresolved);
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ const rel = relative(root, candidate);
81
+ if (!rel || rel === ".." || rel.startsWith(`..${sep}`) || resolve(root, rel) !== candidate) {
82
+ return undefined;
83
+ }
84
+ if (info.size > MAX_SETTINGS_UI_ASSET_BYTES) {
85
+ throw new Error("plugin settings UI asset exceeds 16 MiB");
86
+ }
87
+ const contentType = settingsUiAssetContentType(candidate);
88
+ if (!contentType)
89
+ return undefined;
90
+ return { contentType, data: readFileSync(candidate) };
91
+ }
92
+ async callSettings(id, generation, method, params, options = {}) {
93
+ const record = this.dependencies.get(id);
94
+ if (!record)
95
+ throw new Error(`plugin "${id}" not found`);
96
+ if (record.integrity !== generation)
97
+ throw new Error("plugin settings UI generation is stale");
98
+ if (!this.supervisor)
99
+ throw new Error("plugin settings background is unavailable");
100
+ const active = this.activeSettingsCalls.get(record.canonicalId) ?? 0;
101
+ if (active >= 16)
102
+ throw new Error("plugin settings UI has too many requests in flight");
103
+ this.activeSettingsCalls.set(record.canonicalId, active + 1);
104
+ const deadline = AbortSignal.timeout(30_000);
105
+ const combined = options.signal
106
+ ? AbortSignal.any([options.signal, deadline])
107
+ : deadline;
108
+ try {
109
+ return await this.supervisor.settingsCall(record.canonicalId, generation, method, params, { signal: combined });
110
+ }
111
+ finally {
112
+ const remaining = (this.activeSettingsCalls.get(record.canonicalId) ?? 1) - 1;
113
+ if (remaining <= 0)
114
+ this.activeSettingsCalls.delete(record.canonicalId);
115
+ else
116
+ this.activeSettingsCalls.set(record.canonicalId, remaining);
117
+ }
118
+ }
119
+ async setEnabled(id, enabled) {
40
120
  const current = this.dependencies.get(id);
41
121
  if (!current)
42
122
  return undefined;
43
- if (!isRunnablePluginRecord(current)) {
44
- throw new Error(`plugin "${id}" must be reinstalled before it can be ${enabled ? "enabled" : "disabled"}`);
123
+ const mutate = () => {
124
+ this.dependencies.setEnabled(current.canonicalId, enabled);
125
+ const updated = this.dependencies.get(current.canonicalId);
126
+ if (!updated)
127
+ throw new Error(`plugin "${current.canonicalId}" disappeared`);
128
+ return updated;
129
+ };
130
+ if (this.supervisor) {
131
+ await this.supervisor.changeEnabled(current, enabled, mutate);
45
132
  }
46
- this.dependencies.setEnabled(id, enabled);
47
- const updated = this.dependencies.get(id);
48
- if (!updated) {
49
- throw new Error(`plugin "${id}" disappeared while updating its state`);
133
+ else {
134
+ mutate();
50
135
  }
51
- return toManagementItem(updated);
136
+ return this.managementItem(this.dependencies.get(current.canonicalId));
52
137
  }
53
138
  async uninstall(id) {
54
- validatePluginId(id);
55
- return this.dependencies.uninstall(id);
139
+ const current = this.dependencies.get(id);
140
+ if (!current)
141
+ return false;
142
+ if (this.dependencies.getMarketplace(current.marketplaceId)?.builtIn) {
143
+ throw new Error(`bundled plugin "${current.canonicalId}" cannot be uninstalled; disable it instead`);
144
+ }
145
+ if (this.supervisor) {
146
+ return await this.supervisor.uninstallInstallation(current.canonicalId, () => this.dependencies.uninstall(current.canonicalId));
147
+ }
148
+ return await this.dependencies.uninstall(current.canonicalId);
56
149
  }
57
150
  async prepareInstallation(input, options = {}) {
58
151
  options.signal?.throwIfAborted();
59
152
  let spec;
60
- let expectedId;
61
- if (input.operation === "install") {
62
- if (!input.spec)
63
- throw new Error("plugin install source is required");
153
+ let prepareOptions;
154
+ if (input.operation === "install" && input.marketplacePlugin) {
155
+ const resolved = this.dependencies.resolveMarketplace(input.marketplacePlugin);
156
+ spec = resolved.spec;
157
+ prepareOptions = {
158
+ marketplaceId: resolved.marketplace.marketplaceId,
159
+ expectedId: resolved.pluginId,
160
+ ...(options.signal ? { signal: options.signal } : {}),
161
+ };
162
+ }
163
+ else if (input.operation === "install" && input.spec) {
64
164
  spec = input.spec;
165
+ prepareOptions = options.signal ? { signal: options.signal } : {};
65
166
  }
66
- else {
67
- if (!input.pluginId)
68
- throw new Error("plugin update id is required");
69
- validatePluginId(input.pluginId);
167
+ else if (input.operation === "update" && input.pluginId) {
70
168
  const existing = this.dependencies.get(input.pluginId);
71
169
  if (!existing)
72
170
  throw new Error(`plugin "${input.pluginId}" not found`);
73
- if (!isRunnablePluginRecord(existing)) {
74
- throw new Error(`plugin "${input.pluginId}" uses the removed legacy format; reinstall it`);
171
+ const marketplace = this.dependencies.getMarketplace(existing.marketplaceId);
172
+ if (marketplace) {
173
+ const resolved = this.dependencies.resolveMarketplace(existing.canonicalId);
174
+ if (resolved.marketplace.marketplaceId !== existing.marketplaceId ||
175
+ resolved.pluginId !== existing.pluginId) {
176
+ throw new Error(`marketplace resolution changed identity for "${existing.canonicalId}"`);
177
+ }
178
+ spec = resolved.spec;
75
179
  }
76
- spec = existing.spec;
77
- expectedId = existing.name;
78
- }
79
- const prepared = await this.dependencies.prepare(spec, () => { }, {
80
- allowScripts: input.allowScripts,
81
- ...(expectedId ? { expectedId } : {}),
82
- ...(options.signal ? { signal: options.signal } : {}),
83
- });
180
+ else {
181
+ spec = existing.spec;
182
+ }
183
+ prepareOptions = {
184
+ expectedId: existing.pluginId,
185
+ expectedIdentity: existing.canonicalId,
186
+ marketplaceId: existing.marketplaceId,
187
+ ...(options.signal ? { signal: options.signal } : {}),
188
+ };
189
+ }
190
+ else {
191
+ throw new Error("invalid plugin installation request");
192
+ }
193
+ const prepared = await this.dependencies.prepare(spec, () => { }, prepareOptions);
84
194
  try {
85
195
  options.signal?.throwIfAborted();
86
196
  const token = this.uniqueToken();
87
197
  const expiresAtMs = this.now() + this.preparationTtlMs;
88
- const expiryTimer = this.schedule(() => {
198
+ const expiryTimer = setTimeout(() => {
89
199
  const current = this.pending.get(token);
90
200
  if (!current || current.prepared !== prepared)
91
201
  return;
@@ -117,23 +227,22 @@ export class PluginManagementService {
117
227
  throw new Error("prepared plugin digest does not match expected digest");
118
228
  }
119
229
  if (prepared.existing && !input.overwrite) {
120
- throw new Error(`plugin "${prepared.name}" already exists; explicit overwrite approval is required`);
230
+ throw new Error(`plugin "${prepared.canonicalId}" already exists; explicit overwrite confirmation is required`);
121
231
  }
122
- const grants = input.grants.map((grant) => {
123
- const capability = prepared.manifest.requestedCapabilities.find((requested) => requested === grant);
124
- if (!capability)
125
- throw new Error(`plugin capability was not requested: ${grant}`);
126
- return capability;
127
- });
128
- await prepared.commit({
129
- grants,
130
- expectedDigest: input.expectedDigest,
131
- signal: options.signal,
132
- });
133
- const installed = this.dependencies.get(prepared.name);
134
- if (!installed)
135
- throw new Error(`plugin "${prepared.name}" was not registered after commit`);
136
- return { plugin: toManagementItem(installed) };
232
+ const commit = async () => {
233
+ await prepared.commit({
234
+ expectedDigest: input.expectedDigest,
235
+ signal: options.signal,
236
+ });
237
+ const installed = this.dependencies.get(prepared.canonicalId);
238
+ if (!installed)
239
+ throw new Error(`plugin "${prepared.canonicalId}" was not registered`);
240
+ return installed;
241
+ };
242
+ const installed = this.supervisor
243
+ ? await this.supervisor.cutoverInstallation(prepared.canonicalId, commit).then(() => this.dependencies.get(prepared.canonicalId))
244
+ : await commit();
245
+ return { plugin: this.managementItem(installed) };
137
246
  }
138
247
  finally {
139
248
  prepared.discard();
@@ -170,71 +279,147 @@ export class PluginManagementService {
170
279
  }
171
280
  throw new Error("failed to allocate a plugin preparation token");
172
281
  }
282
+ managementItem(record) {
283
+ return toManagementItem(record, Boolean(record.manifest.settings), pluginUpdateState(record, this.dependencies.getMarketplace(record.marketplaceId)));
284
+ }
173
285
  }
174
- function toManagementItem(record) {
175
- const runnable = isRunnablePluginRecord(record);
286
+ function toManagementItem(record, settingsAvailable = false, updateState = "unknown") {
176
287
  return {
177
- id: record.name,
178
- ...(record.version ? { version: record.version } : {}),
288
+ id: record.canonicalId,
289
+ pluginId: record.pluginId,
290
+ marketplaceId: record.marketplaceId,
291
+ version: record.version,
179
292
  source: record.source,
180
- state: !runnable
181
- ? "reinstall-required"
182
- : record.enabled
183
- ? "enabled"
184
- : "disabled",
185
- enabled: runnable && record.enabled,
186
- requestedCapabilities: runnable ? [...record.manifest.requestedCapabilities] : [],
187
- grants: [...record.grants],
293
+ state: record.enabled ? "enabled" : "disabled",
294
+ updateState,
295
+ enabled: record.enabled,
296
+ runtimeState: record.manifest.runtime ? record.runtimeState : "absent",
297
+ ...(record.runtimeError ? { runtimeError: record.runtimeError } : {}),
298
+ capabilities: [...record.manifest.capabilities],
299
+ hasSettingsUi: settingsAvailable,
188
300
  updatedAt: record.updatedAt,
189
301
  };
190
302
  }
191
303
  function toInstallPreparation(token, expiresAtMs, prepared) {
192
- const requested = [...prepared.manifest.requestedCapabilities];
193
- const prior = prepared.existing?.grants ?? [];
194
- const requestedSet = new Set(requested);
195
- const priorSet = new Set(prior);
196
304
  return {
197
305
  token,
198
306
  expiresAt: new Date(expiresAtMs).toISOString(),
199
307
  artifact: {
200
- id: prepared.name,
201
- ...(prepared.version ? { version: prepared.version } : {}),
308
+ id: prepared.canonicalId,
309
+ pluginId: prepared.pluginId,
310
+ marketplaceId: prepared.marketplaceId,
311
+ version: prepared.version,
202
312
  source: prepared.source,
203
- digest: prepared.integrity,
204
- requestedCapabilities: requested,
205
- requiresBuildScripts: Boolean(prepared.manifest.requiresBuildScripts),
206
- },
207
- ...(prepared.existing
208
- ? {
209
- existing: {
210
- id: prepared.existing.name,
211
- ...(prepared.existing.version
212
- ? { version: prepared.existing.version }
213
- : {}),
214
- source: prepared.existing.source,
215
- state: managementState(prepared.existing),
216
- grants: [...prepared.existing.grants],
313
+ resolvedSource: prepared.requestedSource.type === "git"
314
+ ? {
315
+ type: "git",
316
+ repository: prepared.requestedSource.url,
317
+ subdirectory: prepared.requestedSource.subdir ?? ".",
318
+ commit: prepared.resolvedRevision,
319
+ }
320
+ : {
321
+ type: "local",
322
+ subdirectory: prepared.requestedSource.subdir ?? ".",
217
323
  },
218
- }
219
- : {}),
220
- capabilityDiff: {
221
- added: requested.filter((capability) => !priorSet.has(capability)),
222
- retained: requested.filter((capability) => priorSet.has(capability)),
223
- removed: prior.filter((capability) => !requestedSet.has(capability)),
324
+ digest: prepared.integrity,
325
+ capabilities: [...prepared.manifest.capabilities],
326
+ hasRuntime: Boolean(prepared.manifest.runtime),
327
+ hasSettingsUi: Boolean(prepared.manifest.settings),
328
+ ...(prepared.manifest.runtime ? {
329
+ runtimeEntry: "entry" in prepared.manifest.runtime
330
+ ? {
331
+ type: "module",
332
+ entry: prepared.manifest.runtime.entry,
333
+ }
334
+ : {
335
+ type: "process",
336
+ command: prepared.manifest.runtime.command,
337
+ args: [...(prepared.manifest.runtime.args ?? [])],
338
+ },
339
+ } : {}),
340
+ ...(prepared.manifest.settings?.background ? {
341
+ settingsBackgroundEntry: "entry" in prepared.manifest.settings.background
342
+ ? {
343
+ type: "module",
344
+ entry: prepared.manifest.settings.background.entry,
345
+ }
346
+ : {
347
+ type: "process",
348
+ command: prepared.manifest.settings.background.command,
349
+ args: [...(prepared.manifest.settings.background.args ?? [])],
350
+ },
351
+ } : {}),
352
+ ...(prepared.manifest.commands ? {
353
+ commandsEntry: prepared.manifest.commands.command,
354
+ } : {}),
355
+ ...(prepared.manifest.settings ? {
356
+ settingsUiEntry: prepared.manifest.settings.ui.entry,
357
+ } : {}),
358
+ ...(prepared.manifest.skills ? { skillDirectory: prepared.manifest.skills } : {}),
224
359
  },
360
+ ...(prepared.existing ? {
361
+ existing: {
362
+ id: prepared.existing.canonicalId,
363
+ version: prepared.existing.version,
364
+ source: prepared.existing.source,
365
+ state: prepared.existing.enabled ? "enabled" : "disabled",
366
+ },
367
+ unchanged: prepared.existing.integrity === prepared.integrity,
368
+ } : {}),
225
369
  };
226
370
  }
227
- function managementState(record) {
228
- return !isRunnablePluginRecord(record)
229
- ? "reinstall-required"
230
- : record.enabled
231
- ? "enabled"
232
- : "disabled";
371
+ function pluginUpdateState(record, marketplace) {
372
+ if (!marketplace || marketplace.sourceType !== "git" || record.source !== "git") {
373
+ return "unknown";
374
+ }
375
+ return record.resolvedRevision === marketplace.resolvedRevision
376
+ ? "current"
377
+ : "available";
233
378
  }
234
379
  function invalidPreparationError() {
235
- return new Error("plugin installation preparation is invalid or expired");
380
+ return new Error("plugin installation preparation is invalid, expired, or already consumed");
236
381
  }
237
- function validatePluginId(id) {
238
- if (!PLUGIN_ID_PATTERN.test(id))
239
- throw new Error("invalid plugin id");
382
+ function settingsUiAssetGeneration(integrity) {
383
+ return createHash("sha256").update(integrity).digest("base64url");
384
+ }
385
+ function isSettingsUiAssetPath(value) {
386
+ if (!value ||
387
+ value.length > 1024 ||
388
+ value.includes("\\") ||
389
+ value.includes("\0") ||
390
+ value.startsWith("/")) {
391
+ return false;
392
+ }
393
+ return value.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
394
+ }
395
+ function settingsUiAssetContentType(path) {
396
+ switch (extname(path).toLowerCase()) {
397
+ case ".js":
398
+ case ".mjs":
399
+ return "text/javascript; charset=utf-8";
400
+ case ".css":
401
+ return "text/css; charset=utf-8";
402
+ case ".json":
403
+ case ".map":
404
+ return "application/json; charset=utf-8";
405
+ case ".png":
406
+ return "image/png";
407
+ case ".jpg":
408
+ case ".jpeg":
409
+ return "image/jpeg";
410
+ case ".webp":
411
+ return "image/webp";
412
+ case ".gif":
413
+ return "image/gif";
414
+ case ".ico":
415
+ return "image/x-icon";
416
+ case ".woff":
417
+ return "font/woff";
418
+ case ".woff2":
419
+ return "font/woff2";
420
+ case ".ttf":
421
+ return "font/ttf";
422
+ default:
423
+ return undefined;
424
+ }
240
425
  }
@@ -0,0 +1,35 @@
1
+ import { type PluginMarketplaceV1, type PluginPackageSourceV1 } from "@rynx-ai/plugin-sdk";
2
+ import { type PluginPackageMaterializer } from "./plugin-package.js";
3
+ export interface PluginMarketplaceRecord {
4
+ marketplaceId: string;
5
+ sourceType: "git" | "local";
6
+ builtIn: boolean;
7
+ requestedSource: PluginPackageSourceV1;
8
+ resolvedRevision: string;
9
+ sourceFingerprint: string;
10
+ catalog: PluginMarketplaceV1;
11
+ createdAt: string;
12
+ refreshedAt: string;
13
+ }
14
+ export declare function listPluginMarketplaces(): PluginMarketplaceRecord[];
15
+ export declare function getPluginMarketplace(id: string): PluginMarketplaceRecord | undefined;
16
+ export declare function addPluginMarketplace(spec: string, options?: {
17
+ alias?: string;
18
+ allowSourceRebind?: boolean;
19
+ builtIn?: boolean;
20
+ signal?: AbortSignal;
21
+ materializer?: PluginPackageMaterializer;
22
+ onProgress?: (line: string) => void;
23
+ }): Promise<PluginMarketplaceRecord>;
24
+ export declare function refreshPluginMarketplace(marketplaceId: string, options?: {
25
+ signal?: AbortSignal;
26
+ materializer?: PluginPackageMaterializer;
27
+ onProgress?: (line: string) => void;
28
+ }): Promise<PluginMarketplaceRecord>;
29
+ export declare function removePluginMarketplace(marketplaceId: string): boolean;
30
+ export declare function isBuiltInPluginMarketplace(marketplaceId: string): boolean;
31
+ export declare function resolveMarketplacePlugin(identity: string): {
32
+ marketplace: PluginMarketplaceRecord;
33
+ pluginId: string;
34
+ spec: string;
35
+ };