@intent-systems/nexus 2026.1.5-3 → 2026.1.5-5

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 (144) hide show
  1. package/dist/agents/agent-id.js +41 -0
  2. package/dist/agents/auth-profiles.js +114 -25
  3. package/dist/agents/identity-state.js +79 -0
  4. package/dist/agents/model-auth.js +1 -0
  5. package/dist/agents/model-fallback.js +15 -9
  6. package/dist/agents/model-selection.js +1 -1
  7. package/dist/agents/models-config.js +17 -11
  8. package/dist/agents/pi-embedded-runner.js +101 -9
  9. package/dist/agents/sandbox.js +12 -3
  10. package/dist/agents/skill-runner.js +29 -4
  11. package/dist/agents/skill-usage.js +114 -11
  12. package/dist/agents/skills-status.js +4 -4
  13. package/dist/agents/skills.js +18 -7
  14. package/dist/agents/subagent-registry.js +25 -11
  15. package/dist/agents/system-prompt.js +16 -0
  16. package/dist/agents/tool-policy.js +19 -3
  17. package/dist/agents/tools/browser-tool.js +5 -2
  18. package/dist/agents/tools/image-tool.js +93 -8
  19. package/dist/agents/tools/sessions-announce-target.js +5 -1
  20. package/dist/agents/workspace.js +55 -46
  21. package/dist/auto-reply/command-detection.js +2 -1
  22. package/dist/auto-reply/reply/directive-handling.js +153 -28
  23. package/dist/auto-reply/reply/directives.js +17 -2
  24. package/dist/auto-reply/reply/model-selection.js +8 -3
  25. package/dist/auto-reply/reply/queue.js +2 -2
  26. package/dist/auto-reply/reply.js +1 -1
  27. package/dist/auto-reply/thinking.js +15 -0
  28. package/dist/browser/chrome.js +1 -1
  29. package/dist/browser/client.js +2 -0
  30. package/dist/browser/config.js +6 -2
  31. package/dist/browser/pw-tools-core.js +3 -0
  32. package/dist/browser/routes/agent.js +14 -0
  33. package/dist/canvas-host/server.js +1 -1
  34. package/dist/capabilities/detector.js +245 -0
  35. package/dist/capabilities/registry.js +99 -0
  36. package/dist/channels/location.js +44 -0
  37. package/dist/channels/web/index.js +2 -0
  38. package/dist/cli/cloud-cli.js +12 -7
  39. package/dist/cli/credential-cli.js +139 -17
  40. package/dist/cli/gateway-cli.js +1 -1
  41. package/dist/cli/log-cli.js +25 -0
  42. package/dist/cli/pairing-cli.js +1 -1
  43. package/dist/cli/program.js +58 -6
  44. package/dist/cli/run-main.js +1 -1
  45. package/dist/cli/skills-cli.js +144 -21
  46. package/dist/cli/skills-hub-cli.js +59 -29
  47. package/dist/cli/tool-connector-cli.js +99 -24
  48. package/dist/cli/upstream-sync-cli.js +253 -96
  49. package/dist/cli/usage-cli.js +14 -0
  50. package/dist/commands/auth-choice-options.js +6 -1
  51. package/dist/commands/auth-choice.js +157 -5
  52. package/dist/commands/bootstrap-preset.js +10 -6
  53. package/dist/commands/capabilities.js +33 -6
  54. package/dist/commands/claude-md.js +3 -2
  55. package/dist/commands/config-view.js +1 -1
  56. package/dist/commands/configure.js +4 -4
  57. package/dist/commands/credential.js +497 -36
  58. package/dist/commands/cursor-rules.js +39 -19
  59. package/dist/commands/doctor.js +5 -4
  60. package/dist/commands/identity.js +28 -31
  61. package/dist/commands/init.js +15 -18
  62. package/dist/commands/log.js +134 -0
  63. package/dist/commands/models/fallbacks.js +1 -1
  64. package/dist/commands/models/image-fallbacks.js +1 -1
  65. package/dist/commands/models/list.js +1 -1
  66. package/dist/commands/models/scan.js +1 -1
  67. package/dist/commands/onboard-auth.js +27 -2
  68. package/dist/commands/onboard-eve-identity.js +7 -8
  69. package/dist/commands/onboard-non-interactive.js +4 -2
  70. package/dist/commands/onboard-quickstart.js +18 -11
  71. package/dist/commands/quest-state.js +271 -0
  72. package/dist/commands/quest.js +53 -13
  73. package/dist/commands/reset.js +1 -1
  74. package/dist/commands/sessions-ingest.js +5 -4
  75. package/dist/commands/setup.js +4 -2
  76. package/dist/commands/skills-manifest.js +2 -2
  77. package/dist/commands/status.js +179 -61
  78. package/dist/commands/suggestions.js +1 -1
  79. package/dist/commands/usage-tracking.js +32 -0
  80. package/dist/commands/usage-upload.js +6 -1
  81. package/dist/config/defaults.js +1 -3
  82. package/dist/config/includes.js +5 -7
  83. package/dist/config/io.js +88 -16
  84. package/dist/config/legacy.js +4 -2
  85. package/dist/config/paths.js +16 -0
  86. package/dist/config/sessions.js +9 -5
  87. package/dist/config/zod-schema.js +4 -3
  88. package/dist/control-plane/broker/broker.js +1022 -0
  89. package/dist/control-plane/compaction.js +282 -0
  90. package/dist/control-plane/factory.js +31 -0
  91. package/dist/control-plane/index.js +10 -0
  92. package/dist/control-plane/odu/agents.js +192 -0
  93. package/dist/control-plane/odu/interaction-tools.js +208 -0
  94. package/dist/control-plane/odu/prompt-loader.js +95 -0
  95. package/dist/control-plane/odu/runtime.js +479 -0
  96. package/dist/control-plane/odu/types.js +6 -0
  97. package/dist/control-plane/odu-control-plane.js +316 -0
  98. package/dist/control-plane/single-agent.js +249 -0
  99. package/dist/control-plane/types.js +11 -0
  100. package/dist/credentials/store.js +449 -0
  101. package/dist/gateway/server-browser.js +5 -4
  102. package/dist/gateway/server-methods/cron.js +11 -1
  103. package/dist/gateway/server.js +14 -7
  104. package/dist/infra/bonjour.js +1 -1
  105. package/dist/infra/event-log.js +8 -2
  106. package/dist/infra/path-env.js +1 -2
  107. package/dist/infra/provider-usage.auth.js +5 -3
  108. package/dist/infra/provider-usage.fetch.claude.js +16 -6
  109. package/dist/infra/provider-usage.fetch.minimax.js +8 -3
  110. package/dist/infra/provider-usage.js +9 -5
  111. package/dist/infra/restart.js +2 -2
  112. package/dist/infra/usage-settings.js +78 -0
  113. package/dist/infra/usage-suggestions.js +17 -5
  114. package/dist/infra/usage-upload.js +38 -1
  115. package/dist/infra/voicewake.js +2 -2
  116. package/dist/logging/redact.js +109 -0
  117. package/dist/markdown/fences.js +58 -0
  118. package/dist/media/image-ops.js +3 -1
  119. package/dist/memory/embeddings.js +146 -0
  120. package/dist/memory/index.js +3 -0
  121. package/dist/memory/internal.js +163 -0
  122. package/dist/pairing/pairing-store.js +218 -0
  123. package/dist/plugins/cli.js +42 -0
  124. package/dist/plugins/discovery.js +253 -0
  125. package/dist/plugins/install.js +181 -0
  126. package/dist/plugins/loader.js +290 -0
  127. package/dist/plugins/registry.js +105 -0
  128. package/dist/plugins/status.js +29 -0
  129. package/dist/plugins/tools.js +39 -0
  130. package/dist/plugins/types.js +1 -0
  131. package/dist/providers/github-copilot-auth.js +1 -1
  132. package/dist/routing/resolve-route.js +144 -0
  133. package/dist/routing/session-key.js +65 -0
  134. package/dist/sessions/send-policy.js +5 -5
  135. package/dist/slack/monitor.js +22 -1
  136. package/dist/telegram/reaction-level.js +2 -1
  137. package/dist/utils/provider-utils.js +28 -0
  138. package/dist/utils.js +4 -3
  139. package/dist/wizard/onboarding.js +29 -7
  140. package/package.json +4 -29
  141. package/patches/@mariozechner__pi-ai.patch +215 -0
  142. package/patches/playwright-core@1.57.0.patch +13 -0
  143. package/patches/qrcode-terminal.patch +12 -0
  144. package/scripts/postinstall.js +202 -0
@@ -0,0 +1,181 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { runCommandWithTimeout } from "../process/exec.js";
5
+ import { CONFIG_DIR, resolveUserPath } from "../utils.js";
6
+ const defaultLogger = {};
7
+ function unscopedPackageName(name) {
8
+ const trimmed = name.trim();
9
+ if (!trimmed)
10
+ return trimmed;
11
+ return trimmed.includes("/")
12
+ ? (trimmed.split("/").pop() ?? trimmed)
13
+ : trimmed;
14
+ }
15
+ function safeDirName(input) {
16
+ const trimmed = input.trim();
17
+ if (!trimmed)
18
+ return trimmed;
19
+ return trimmed.replaceAll("/", "__");
20
+ }
21
+ async function readJsonFile(filePath) {
22
+ const raw = await fs.readFile(filePath, "utf-8");
23
+ return JSON.parse(raw);
24
+ }
25
+ async function fileExists(filePath) {
26
+ try {
27
+ await fs.stat(filePath);
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ async function resolvePackedPackageDir(extractDir) {
35
+ const direct = path.join(extractDir, "package");
36
+ if (await fileExists(direct))
37
+ return direct;
38
+ const entries = await fs.readdir(extractDir, { withFileTypes: true });
39
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
40
+ if (dirs.length !== 1) {
41
+ throw new Error(`unexpected archive layout (dirs: ${dirs.join(", ")})`);
42
+ }
43
+ const onlyDir = dirs[0];
44
+ if (!onlyDir) {
45
+ throw new Error("unexpected archive layout (no package dir found)");
46
+ }
47
+ return path.join(extractDir, onlyDir);
48
+ }
49
+ async function ensureNexusExtensions(manifest) {
50
+ // Try nexus first, fall back to clawdbot for backward compat
51
+ const extensions = manifest.nexus?.extensions ?? manifest.clawdbot?.extensions;
52
+ if (!Array.isArray(extensions)) {
53
+ throw new Error("package.json missing nexus.extensions (or clawdbot.extensions)");
54
+ }
55
+ const list = extensions
56
+ .map((e) => (typeof e === "string" ? e.trim() : ""))
57
+ .filter(Boolean);
58
+ if (list.length === 0) {
59
+ throw new Error("package.json nexus.extensions is empty");
60
+ }
61
+ return list;
62
+ }
63
+ export async function installPluginFromArchive(params) {
64
+ const logger = params.logger ?? defaultLogger;
65
+ const timeoutMs = params.timeoutMs ?? 120_000;
66
+ const archivePath = resolveUserPath(params.archivePath);
67
+ if (!(await fileExists(archivePath))) {
68
+ return { ok: false, error: `archive not found: ${archivePath}` };
69
+ }
70
+ const extensionsDir = params.extensionsDir
71
+ ? resolveUserPath(params.extensionsDir)
72
+ : path.join(CONFIG_DIR, "extensions");
73
+ await fs.mkdir(extensionsDir, { recursive: true });
74
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "nexus-plugin-"));
75
+ const extractDir = path.join(tmpDir, "extract");
76
+ await fs.mkdir(extractDir, { recursive: true });
77
+ logger.info?.(`Extracting ${archivePath}…`);
78
+ const tarRes = await runCommandWithTimeout(["tar", "-xzf", archivePath, "-C", extractDir], { timeoutMs });
79
+ if (tarRes.code !== 0) {
80
+ return {
81
+ ok: false,
82
+ error: `failed to extract archive: ${tarRes.stderr.trim() || tarRes.stdout.trim()}`,
83
+ };
84
+ }
85
+ let packageDir = "";
86
+ try {
87
+ packageDir = await resolvePackedPackageDir(extractDir);
88
+ }
89
+ catch (err) {
90
+ return { ok: false, error: String(err) };
91
+ }
92
+ const manifestPath = path.join(packageDir, "package.json");
93
+ if (!(await fileExists(manifestPath))) {
94
+ return { ok: false, error: "extracted package missing package.json" };
95
+ }
96
+ let manifest;
97
+ try {
98
+ manifest = await readJsonFile(manifestPath);
99
+ }
100
+ catch (err) {
101
+ return { ok: false, error: `invalid package.json: ${String(err)}` };
102
+ }
103
+ let extensions;
104
+ try {
105
+ extensions = await ensureNexusExtensions(manifest);
106
+ }
107
+ catch (err) {
108
+ return { ok: false, error: String(err) };
109
+ }
110
+ const pkgName = typeof manifest.name === "string" ? manifest.name : "";
111
+ const pluginId = pkgName ? unscopedPackageName(pkgName) : "plugin";
112
+ const targetDir = path.join(extensionsDir, safeDirName(pluginId));
113
+ if (await fileExists(targetDir)) {
114
+ return {
115
+ ok: false,
116
+ error: `plugin already exists: ${targetDir} (delete it first)`,
117
+ };
118
+ }
119
+ logger.info?.(`Installing to ${targetDir}…`);
120
+ await fs.cp(packageDir, targetDir, { recursive: true });
121
+ for (const entry of extensions) {
122
+ const resolvedEntry = path.resolve(targetDir, entry);
123
+ if (!(await fileExists(resolvedEntry))) {
124
+ logger.warn?.(`extension entry not found: ${entry}`);
125
+ }
126
+ }
127
+ const deps = manifest.dependencies ?? {};
128
+ const hasDeps = Object.keys(deps).length > 0;
129
+ if (hasDeps) {
130
+ logger.info?.("Installing plugin dependencies…");
131
+ const npmRes = await runCommandWithTimeout(["npm", "install", "--omit=dev", "--silent"], { timeoutMs: Math.max(timeoutMs, 300_000), cwd: targetDir });
132
+ if (npmRes.code !== 0) {
133
+ return {
134
+ ok: false,
135
+ error: `npm install failed: ${npmRes.stderr.trim() || npmRes.stdout.trim()}`,
136
+ };
137
+ }
138
+ }
139
+ return {
140
+ ok: true,
141
+ pluginId,
142
+ targetDir,
143
+ manifestName: pkgName || undefined,
144
+ extensions,
145
+ };
146
+ }
147
+ export async function installPluginFromNpmSpec(params) {
148
+ const logger = params.logger ?? defaultLogger;
149
+ const timeoutMs = params.timeoutMs ?? 120_000;
150
+ const spec = params.spec.trim();
151
+ if (!spec)
152
+ return { ok: false, error: "missing npm spec" };
153
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "nexus-npm-pack-"));
154
+ logger.info?.(`Downloading ${spec}…`);
155
+ const res = await runCommandWithTimeout(["npm", "pack", spec], {
156
+ timeoutMs: Math.max(timeoutMs, 300_000),
157
+ cwd: tmpDir,
158
+ env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
159
+ });
160
+ if (res.code !== 0) {
161
+ return {
162
+ ok: false,
163
+ error: `npm pack failed: ${res.stderr.trim() || res.stdout.trim()}`,
164
+ };
165
+ }
166
+ const packed = (res.stdout || "")
167
+ .split("\n")
168
+ .map((l) => l.trim())
169
+ .filter(Boolean)
170
+ .pop();
171
+ if (!packed) {
172
+ return { ok: false, error: "npm pack produced no archive" };
173
+ }
174
+ const archivePath = path.join(tmpDir, packed);
175
+ return await installPluginFromArchive({
176
+ archivePath,
177
+ extensionsDir: params.extensionsDir,
178
+ timeoutMs,
179
+ logger,
180
+ });
181
+ }
@@ -0,0 +1,290 @@
1
+ import { createJiti } from "jiti";
2
+ import { createSubsystemLogger } from "../logging.js";
3
+ import { resolveUserPath } from "../utils.js";
4
+ import { discoverNexusPlugins } from "./discovery.js";
5
+ import { createPluginRegistry, } from "./registry.js";
6
+ const registryCache = new Map();
7
+ const defaultLogger = () => createSubsystemLogger("plugins");
8
+ const normalizeList = (value) => {
9
+ if (!Array.isArray(value))
10
+ return [];
11
+ return value
12
+ .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
13
+ .filter(Boolean);
14
+ };
15
+ const normalizePluginEntries = (entries) => {
16
+ if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
17
+ return {};
18
+ }
19
+ const normalized = {};
20
+ for (const [key, value] of Object.entries(entries)) {
21
+ if (!key.trim())
22
+ continue;
23
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
24
+ normalized[key] = {};
25
+ continue;
26
+ }
27
+ const entry = value;
28
+ normalized[key] = {
29
+ enabled: typeof entry.enabled === "boolean" ? entry.enabled : undefined,
30
+ config: entry.config &&
31
+ typeof entry.config === "object" &&
32
+ !Array.isArray(entry.config)
33
+ ? entry.config
34
+ : undefined,
35
+ };
36
+ }
37
+ return normalized;
38
+ };
39
+ const normalizePluginsConfig = (config) => {
40
+ return {
41
+ enabled: config?.enabled !== false,
42
+ allow: normalizeList(config?.allow),
43
+ deny: normalizeList(config?.deny),
44
+ loadPaths: normalizeList(config?.load?.paths),
45
+ entries: normalizePluginEntries(config?.entries),
46
+ };
47
+ };
48
+ function buildCacheKey(params) {
49
+ const workspaceKey = params.workspaceDir
50
+ ? resolveUserPath(params.workspaceDir)
51
+ : "";
52
+ return `${workspaceKey}::${JSON.stringify(params.plugins)}`;
53
+ }
54
+ function resolveEnableState(id, config) {
55
+ if (!config.enabled) {
56
+ return { enabled: false, reason: "plugins disabled" };
57
+ }
58
+ if (config.deny.includes(id)) {
59
+ return { enabled: false, reason: "blocked by denylist" };
60
+ }
61
+ if (config.allow.length > 0 && !config.allow.includes(id)) {
62
+ return { enabled: false, reason: "not in allowlist" };
63
+ }
64
+ const entry = config.entries[id];
65
+ if (entry?.enabled === false) {
66
+ return { enabled: false, reason: "disabled in config" };
67
+ }
68
+ return { enabled: true };
69
+ }
70
+ function validatePluginConfig(params) {
71
+ const schema = params.schema;
72
+ if (!schema)
73
+ return { ok: true, value: params.value };
74
+ if (typeof schema.validate === "function") {
75
+ const result = schema.validate(params.value);
76
+ if (result.ok) {
77
+ return { ok: true, value: result.value };
78
+ }
79
+ return { ok: false, errors: result.errors };
80
+ }
81
+ if (typeof schema.safeParse === "function") {
82
+ const result = schema.safeParse(params.value);
83
+ if (result.success) {
84
+ return { ok: true, value: result.data };
85
+ }
86
+ const issues = result.error?.issues ?? [];
87
+ const errors = issues.map((issue) => {
88
+ const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
89
+ return `${path}: ${issue.message}`;
90
+ });
91
+ return { ok: false, errors };
92
+ }
93
+ if (typeof schema.parse === "function") {
94
+ try {
95
+ const parsed = schema.parse(params.value);
96
+ return { ok: true, value: parsed };
97
+ }
98
+ catch (err) {
99
+ return { ok: false, errors: [String(err)] };
100
+ }
101
+ }
102
+ return { ok: true, value: params.value };
103
+ }
104
+ function resolvePluginModuleExport(moduleExport) {
105
+ const resolved = moduleExport &&
106
+ typeof moduleExport === "object" &&
107
+ "default" in moduleExport
108
+ ? moduleExport.default
109
+ : moduleExport;
110
+ if (typeof resolved === "function") {
111
+ return {
112
+ register: resolved,
113
+ };
114
+ }
115
+ if (resolved && typeof resolved === "object") {
116
+ const def = resolved;
117
+ const register = def.register ?? def.activate;
118
+ return { definition: def, register };
119
+ }
120
+ return {};
121
+ }
122
+ function createPluginRecord(params) {
123
+ return {
124
+ id: params.id,
125
+ name: params.name ?? params.id,
126
+ description: params.description,
127
+ version: params.version,
128
+ source: params.source,
129
+ origin: params.origin,
130
+ workspaceDir: params.workspaceDir,
131
+ enabled: params.enabled,
132
+ status: params.enabled ? "loaded" : "disabled",
133
+ toolNames: [],
134
+ gatewayMethods: [],
135
+ cliCommands: [],
136
+ services: [],
137
+ configSchema: params.configSchema,
138
+ configUiHints: undefined,
139
+ };
140
+ }
141
+ function pushDiagnostics(diagnostics, append) {
142
+ diagnostics.push(...append);
143
+ }
144
+ export function loadNexusPlugins(options = {}) {
145
+ const cfg = options.config ?? {};
146
+ const logger = options.logger ?? defaultLogger();
147
+ const normalized = normalizePluginsConfig(cfg.plugins);
148
+ const cacheKey = buildCacheKey({
149
+ workspaceDir: options.workspaceDir,
150
+ plugins: normalized,
151
+ });
152
+ const cacheEnabled = options.cache !== false;
153
+ if (cacheEnabled) {
154
+ const cached = registryCache.get(cacheKey);
155
+ if (cached)
156
+ return cached;
157
+ }
158
+ const { registry, createApi } = createPluginRegistry({
159
+ logger,
160
+ coreGatewayHandlers: options.coreGatewayHandlers,
161
+ });
162
+ const discovery = discoverNexusPlugins({
163
+ workspaceDir: options.workspaceDir,
164
+ extraPaths: normalized.loadPaths,
165
+ });
166
+ pushDiagnostics(registry.diagnostics, discovery.diagnostics);
167
+ const jiti = createJiti(import.meta.url, {
168
+ interopDefault: true,
169
+ });
170
+ for (const candidate of discovery.candidates) {
171
+ const enableState = resolveEnableState(candidate.idHint, normalized);
172
+ const entry = normalized.entries[candidate.idHint];
173
+ const record = createPluginRecord({
174
+ id: candidate.idHint,
175
+ name: candidate.packageName ?? candidate.idHint,
176
+ description: candidate.packageDescription,
177
+ version: candidate.packageVersion,
178
+ source: candidate.source,
179
+ origin: candidate.origin,
180
+ workspaceDir: candidate.workspaceDir,
181
+ enabled: enableState.enabled,
182
+ configSchema: false,
183
+ });
184
+ if (!enableState.enabled) {
185
+ record.status = "disabled";
186
+ record.error = enableState.reason;
187
+ registry.plugins.push(record);
188
+ continue;
189
+ }
190
+ let mod = null;
191
+ try {
192
+ mod = jiti(candidate.source);
193
+ }
194
+ catch (err) {
195
+ record.status = "error";
196
+ record.error = String(err);
197
+ registry.plugins.push(record);
198
+ registry.diagnostics.push({
199
+ level: "error",
200
+ pluginId: record.id,
201
+ source: record.source,
202
+ message: `failed to load plugin: ${String(err)}`,
203
+ });
204
+ continue;
205
+ }
206
+ const resolved = resolvePluginModuleExport(mod);
207
+ const definition = resolved.definition;
208
+ const register = resolved.register;
209
+ if (definition?.id && definition.id !== record.id) {
210
+ registry.diagnostics.push({
211
+ level: "warn",
212
+ pluginId: record.id,
213
+ source: record.source,
214
+ message: `plugin id mismatch (config uses "${record.id}", export uses "${definition.id}")`,
215
+ });
216
+ }
217
+ record.name = definition?.name ?? record.name;
218
+ record.description = definition?.description ?? record.description;
219
+ record.version = definition?.version ?? record.version;
220
+ record.configSchema = Boolean(definition?.configSchema);
221
+ record.configUiHints =
222
+ definition?.configSchema &&
223
+ typeof definition.configSchema === "object" &&
224
+ definition.configSchema.uiHints &&
225
+ typeof definition.configSchema.uiHints ===
226
+ "object" &&
227
+ !Array.isArray(definition.configSchema.uiHints)
228
+ ? definition.configSchema.uiHints
229
+ : undefined;
230
+ const validatedConfig = validatePluginConfig({
231
+ schema: definition?.configSchema,
232
+ value: entry?.config,
233
+ });
234
+ if (!validatedConfig.ok) {
235
+ record.status = "error";
236
+ record.error = `invalid config: ${validatedConfig.errors?.join(", ")}`;
237
+ registry.plugins.push(record);
238
+ registry.diagnostics.push({
239
+ level: "error",
240
+ pluginId: record.id,
241
+ source: record.source,
242
+ message: record.error,
243
+ });
244
+ continue;
245
+ }
246
+ if (typeof register !== "function") {
247
+ record.status = "error";
248
+ record.error = "plugin export missing register/activate";
249
+ registry.plugins.push(record);
250
+ registry.diagnostics.push({
251
+ level: "error",
252
+ pluginId: record.id,
253
+ source: record.source,
254
+ message: record.error,
255
+ });
256
+ continue;
257
+ }
258
+ const api = createApi(record, {
259
+ config: cfg,
260
+ pluginConfig: validatedConfig.value,
261
+ });
262
+ try {
263
+ const result = register(api);
264
+ if (result && typeof result.then === "function") {
265
+ registry.diagnostics.push({
266
+ level: "warn",
267
+ pluginId: record.id,
268
+ source: record.source,
269
+ message: "plugin register returned a promise; async registration is ignored",
270
+ });
271
+ }
272
+ registry.plugins.push(record);
273
+ }
274
+ catch (err) {
275
+ record.status = "error";
276
+ record.error = String(err);
277
+ registry.plugins.push(record);
278
+ registry.diagnostics.push({
279
+ level: "error",
280
+ pluginId: record.id,
281
+ source: record.source,
282
+ message: `plugin failed during register: ${String(err)}`,
283
+ });
284
+ }
285
+ }
286
+ if (cacheEnabled) {
287
+ registryCache.set(cacheKey, registry);
288
+ }
289
+ return registry;
290
+ }
@@ -0,0 +1,105 @@
1
+ import { resolveUserPath } from "../utils.js";
2
+ export function createPluginRegistry(registryParams) {
3
+ const registry = {
4
+ plugins: [],
5
+ tools: [],
6
+ gatewayHandlers: {},
7
+ cliRegistrars: [],
8
+ services: [],
9
+ diagnostics: [],
10
+ };
11
+ const coreGatewayMethods = new Set(Object.keys(registryParams.coreGatewayHandlers ?? {}));
12
+ const pushDiagnostic = (diag) => {
13
+ registry.diagnostics.push(diag);
14
+ };
15
+ const registerTool = (record, tool, opts) => {
16
+ const names = opts?.names ?? (opts?.name ? [opts.name] : []);
17
+ const factory = typeof tool === "function"
18
+ ? tool
19
+ : (_ctx) => tool;
20
+ if (typeof tool !== "function") {
21
+ names.push(tool.name);
22
+ }
23
+ const normalized = names.map((name) => name.trim()).filter(Boolean);
24
+ if (normalized.length > 0) {
25
+ record.toolNames.push(...normalized);
26
+ }
27
+ registry.tools.push({
28
+ pluginId: record.id,
29
+ factory,
30
+ names: normalized,
31
+ source: record.source,
32
+ });
33
+ };
34
+ const registerGatewayMethod = (record, method, handler) => {
35
+ const trimmed = method.trim();
36
+ if (!trimmed)
37
+ return;
38
+ if (coreGatewayMethods.has(trimmed) || registry.gatewayHandlers[trimmed]) {
39
+ pushDiagnostic({
40
+ level: "error",
41
+ pluginId: record.id,
42
+ source: record.source,
43
+ message: `gateway method already registered: ${trimmed}`,
44
+ });
45
+ return;
46
+ }
47
+ registry.gatewayHandlers[trimmed] = handler;
48
+ record.gatewayMethods.push(trimmed);
49
+ };
50
+ const registerCli = (record, registrar, opts) => {
51
+ const commands = (opts?.commands ?? [])
52
+ .map((cmd) => cmd.trim())
53
+ .filter(Boolean);
54
+ record.cliCommands.push(...commands);
55
+ registry.cliRegistrars.push({
56
+ pluginId: record.id,
57
+ register: registrar,
58
+ commands,
59
+ source: record.source,
60
+ });
61
+ };
62
+ const registerService = (record, service) => {
63
+ const id = service.id.trim();
64
+ if (!id)
65
+ return;
66
+ record.services.push(id);
67
+ registry.services.push({
68
+ pluginId: record.id,
69
+ service,
70
+ source: record.source,
71
+ });
72
+ };
73
+ const normalizeLogger = (logger) => ({
74
+ info: logger.info,
75
+ warn: logger.warn,
76
+ error: logger.error,
77
+ debug: logger.debug,
78
+ });
79
+ const createApi = (record, params) => {
80
+ return {
81
+ id: record.id,
82
+ name: record.name,
83
+ version: record.version,
84
+ description: record.description,
85
+ source: record.source,
86
+ config: params.config,
87
+ pluginConfig: params.pluginConfig,
88
+ logger: normalizeLogger(registryParams.logger),
89
+ registerTool: (tool, opts) => registerTool(record, tool, opts),
90
+ registerGatewayMethod: (method, handler) => registerGatewayMethod(record, method, handler),
91
+ registerCli: (registrar, opts) => registerCli(record, registrar, opts),
92
+ registerService: (service) => registerService(record, service),
93
+ resolvePath: (input) => resolveUserPath(input),
94
+ };
95
+ };
96
+ return {
97
+ registry,
98
+ createApi,
99
+ pushDiagnostic,
100
+ registerTool,
101
+ registerGatewayMethod,
102
+ registerCli,
103
+ registerService,
104
+ };
105
+ }
@@ -0,0 +1,29 @@
1
+ import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
2
+ import { loadConfig } from "../config/config.js";
3
+ import { createSubsystemLogger } from "../logging.js";
4
+ import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
5
+ import { loadNexusPlugins } from "./loader.js";
6
+ const log = createSubsystemLogger("plugins");
7
+ function resolveDefaultAgentId(cfg) {
8
+ return normalizeAgentId(cfg.routing?.defaultAgentId ?? DEFAULT_AGENT_ID);
9
+ }
10
+ export function buildPluginStatusReport(params) {
11
+ const config = params?.config ?? loadConfig();
12
+ const workspaceDir = params?.workspaceDir
13
+ ? params.workspaceDir
14
+ : resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
15
+ const registry = loadNexusPlugins({
16
+ config,
17
+ workspaceDir,
18
+ logger: {
19
+ info: (msg) => log.info(msg),
20
+ warn: (msg) => log.warn(msg),
21
+ error: (msg) => log.error(msg),
22
+ debug: (msg) => log.debug(msg),
23
+ },
24
+ });
25
+ return {
26
+ workspaceDir,
27
+ ...registry,
28
+ };
29
+ }
@@ -0,0 +1,39 @@
1
+ import { createSubsystemLogger } from "../logging.js";
2
+ import { loadNexusPlugins } from "./loader.js";
3
+ const log = createSubsystemLogger("plugins");
4
+ export function resolvePluginTools(params) {
5
+ const registry = loadNexusPlugins({
6
+ config: params.context.config,
7
+ workspaceDir: params.context.workspaceDir,
8
+ logger: {
9
+ info: (msg) => log.info(msg),
10
+ warn: (msg) => log.warn(msg),
11
+ error: (msg) => log.error(msg),
12
+ debug: (msg) => log.debug(msg),
13
+ },
14
+ });
15
+ const tools = [];
16
+ const existing = params.existingToolNames ?? new Set();
17
+ for (const entry of registry.tools) {
18
+ let resolved = null;
19
+ try {
20
+ resolved = entry.factory(params.context);
21
+ }
22
+ catch (err) {
23
+ log.error(`plugin tool failed (${entry.pluginId}): ${String(err)}`);
24
+ continue;
25
+ }
26
+ if (!resolved)
27
+ continue;
28
+ const list = Array.isArray(resolved) ? resolved : [resolved];
29
+ for (const tool of list) {
30
+ if (existing.has(tool.name)) {
31
+ log.warn(`plugin tool name conflict (${entry.pluginId}): ${tool.name}`);
32
+ continue;
33
+ }
34
+ existing.add(tool.name);
35
+ tools.push(tool);
36
+ }
37
+ }
38
+ return tools;
39
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { intro, note, outro, spinner } from "@clack/prompts";
2
- import { ensureAuthProfileStore, upsertAuthProfile } from "../agents/auth-profiles.js";
2
+ import { ensureAuthProfileStore, upsertAuthProfile, } from "../agents/auth-profiles.js";
3
3
  import { updateConfig } from "../commands/models/shared.js";
4
4
  import { applyAuthProfileConfig } from "../commands/onboard-auth.js";
5
5
  import { CONFIG_PATH_NEXUS } from "../config/config.js";