@xopcai/xopc 0.0.19 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/feishu/src/adapters/cli-login.d.ts +8 -0
- package/dist/extensions/feishu/src/adapters/cli-login.js +225 -0
- package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -0
- package/dist/extensions/feishu/src/adapters/onboard-cli.js +1 -105
- package/dist/extensions/feishu/src/adapters/onboard-cli.js.map +1 -1
- package/dist/extensions/feishu/src/auth/app-registration.d.ts +47 -0
- package/dist/extensions/feishu/src/auth/app-registration.js +122 -0
- package/dist/extensions/feishu/src/auth/app-registration.js.map +1 -0
- package/dist/extensions/feishu/src/plugin.d.ts +2 -0
- package/dist/extensions/feishu/src/plugin.js +2 -0
- package/dist/extensions/feishu/src/plugin.js.map +1 -1
- package/dist/extensions/telegram/src/inbound-processor.js +1 -1
- package/dist/extensions/telegram/src/plugin.d.ts +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agents-ByiiWRbv.js → agents-MbH57-L9.js} +2 -2
- package/dist/gateway/static/root/assets/{agents-ByiiWRbv.js.map → agents-MbH57-L9.js.map} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-BpR0gguZ.js → apps-page-3i3DvI7i.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-BpR0gguZ.js.map → apps-page-3i3DvI7i.js.map} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-CcuSzoB6.js +9 -0
- package/dist/gateway/static/root/assets/channels-settings-CcuSzoB6.js.map +1 -0
- package/dist/gateway/static/root/assets/{cron-page-D6wtd-hq.js → cron-page-Be1h9Yub.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-page-D6wtd-hq.js.map → cron-page-Be1h9Yub.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-utils-o-QI_XCC.js → cron-utils-CR97EvZS.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-utils-o-QI_XCC.js.map → cron-utils-CR97EvZS.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-D2Td6E_v.js → dist-r_Gy-XJv.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-D2Td6E_v.js.map → dist-r_Gy-XJv.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-BIni4Qq4.js → extension-debug-page-QfYEYruq.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-BIni4Qq4.js.map → extension-debug-page-QfYEYruq.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-BRLScNkx.js → extension-page-4FW-BmKG.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-BRLScNkx.js.map → extension-page-4FW-BmKG.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-DjiK9Igx.js → extension-settings-page-E_Wq9LL8.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-DjiK9Igx.js.map → extension-settings-page-E_Wq9LL8.js.map} +1 -1
- package/dist/gateway/static/root/assets/{index-KGmhufWu.js → index-CcQtNJKo.js} +60 -54
- package/dist/gateway/static/root/assets/{index-KGmhufWu.js.map → index-CcQtNJKo.js.map} +1 -1
- package/dist/gateway/static/root/assets/index-D9Wmfh2f.css +1 -0
- package/dist/gateway/static/root/assets/{logs-page-C76F4Y0H.js → logs-page-DFhTU-kG.js} +2 -2
- package/dist/gateway/static/root/assets/{logs-page-C76F4Y0H.js.map → logs-page-DFhTU-kG.js.map} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-9jwUqGtS.js → sessions-page-wmnnIj6Z.js} +2 -2
- package/dist/gateway/static/root/assets/{sessions-page-9jwUqGtS.js.map → sessions-page-wmnnIj6Z.js.map} +1 -1
- package/dist/gateway/static/root/assets/settings-page-BTmUXY4s.js +2 -0
- package/dist/gateway/static/root/assets/settings-page-BTmUXY4s.js.map +1 -0
- package/dist/gateway/static/root/assets/{skills-page-BY1cLNEz.js → skills-page-D-fRbJG0.js} +2 -2
- package/dist/gateway/static/root/assets/{skills-page-BY1cLNEz.js.map → skills-page-D-fRbJG0.js.map} +1 -1
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/memory/builtin-memory-store.d.ts +2 -1
- package/dist/src/agent/memory/builtin-memory-store.js +7 -6
- package/dist/src/agent/memory/builtin-memory-store.js.map +1 -1
- package/dist/src/agent/prompt/memory/index.d.ts +4 -2
- package/dist/src/agent/prompt/memory/index.js +22 -10
- package/dist/src/agent/prompt/memory/index.js.map +1 -1
- package/dist/src/agent/service.js +1 -1
- package/dist/src/agent/tools/factory.js +9 -2
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/index.d.ts +1 -1
- package/dist/src/agent/tools/memory-tool.d.ts +7 -2
- package/dist/src/agent/tools/memory-tool.js +11 -5
- package/dist/src/agent/tools/memory-tool.js.map +1 -1
- package/dist/src/channels/registry.d.ts +1 -1
- package/dist/src/channels/registry.js +25 -1
- package/dist/src/channels/registry.js.map +1 -1
- package/dist/src/chat-commands/builtins/config.js +1 -1
- package/dist/src/chat-commands/builtins/session.js +1 -1
- package/dist/src/chat-commands/index.js +1 -1
- package/dist/src/chat-commands/processor.js +1 -1
- package/dist/src/cli/commands/channels.js +20 -2
- package/dist/src/cli/commands/channels.js.map +1 -1
- package/dist/src/cli/commands/gateway/call.d.ts +2 -0
- package/dist/src/cli/commands/gateway/call.js +90 -0
- package/dist/src/cli/commands/gateway/call.js.map +1 -0
- package/dist/src/cli/commands/gateway/health.d.ts +2 -0
- package/dist/src/cli/commands/gateway/health.js +77 -0
- package/dist/src/cli/commands/gateway/health.js.map +1 -0
- package/dist/src/cli/commands/gateway/index.d.ts +3 -0
- package/dist/src/cli/commands/gateway/index.js +4 -1
- package/dist/src/cli/commands/gateway/probe.d.ts +2 -0
- package/dist/src/cli/commands/gateway/probe.js +102 -0
- package/dist/src/cli/commands/gateway/probe.js.map +1 -0
- package/dist/src/cli/commands/gateway/status.d.ts +0 -3
- package/dist/src/cli/commands/gateway/status.js +107 -24
- package/dist/src/cli/commands/gateway/status.js.map +1 -1
- package/dist/src/cli/commands/gateway.js +7 -1
- package/dist/src/cli/commands/gateway.js.map +1 -1
- package/dist/src/cli/commands/update.js +19 -1
- package/dist/src/cli/commands/update.js.map +1 -1
- package/dist/src/cli/utils/gateway-client.d.ts +28 -0
- package/dist/src/cli/utils/gateway-client.js +115 -0
- package/dist/src/cli/utils/gateway-client.js.map +1 -0
- package/dist/src/config/paths-state.d.ts +4 -0
- package/dist/src/config/paths-state.js +9 -1
- package/dist/src/config/paths-state.js.map +1 -1
- package/dist/src/config/reload.d.ts +2 -0
- package/dist/src/config/reload.js +9 -1
- package/dist/src/config/reload.js.map +1 -1
- package/dist/src/config/rules.js +12 -2
- package/dist/src/config/rules.js.map +1 -1
- package/dist/src/extensions/api.d.ts +6 -1
- package/dist/src/extensions/api.js +52 -1
- package/dist/src/extensions/api.js.map +1 -1
- package/dist/src/extensions/loader.d.ts +6 -1
- package/dist/src/extensions/loader.js +20 -1
- package/dist/src/extensions/loader.js.map +1 -1
- package/dist/src/extensions/normalize-manifest.js +33 -0
- package/dist/src/extensions/normalize-manifest.js.map +1 -1
- package/dist/src/extensions/sdk/index.d.ts +1 -1
- package/dist/src/extensions/sdk/index.js.map +1 -1
- package/dist/src/extensions/types/core.d.ts +35 -1
- package/dist/src/extensions/types/manifest.d.ts +14 -0
- package/dist/src/gateway/hono/lib/config-payload.d.ts +3 -0
- package/dist/src/gateway/hono/lib/config-payload.js +1 -0
- package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
- package/dist/src/gateway/hono/routes/channels.js +111 -0
- package/dist/src/gateway/hono/routes/channels.js.map +1 -1
- package/dist/src/gateway/hono/routes/commands-skills.js +13 -2
- package/dist/src/gateway/hono/routes/commands-skills.js.map +1 -1
- package/dist/src/gateway/hono/routes/config.js +81 -0
- package/dist/src/gateway/hono/routes/config.js.map +1 -1
- package/dist/src/gateway/hono/routes/public-gateway.js +17 -0
- package/dist/src/gateway/hono/routes/public-gateway.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +16 -0
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/routes/status.js +31 -7
- package/dist/src/gateway/hono/routes/status.js.map +1 -1
- package/dist/src/gateway/hono/routes/update.js +118 -15
- package/dist/src/gateway/hono/routes/update.js.map +1 -1
- package/dist/src/gateway/index.js +1 -1
- package/dist/src/gateway/server.js +3 -0
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +23 -0
- package/dist/src/gateway/service.js +107 -0
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/infra/update-check.js +54 -21
- package/dist/src/infra/update-check.js.map +1 -1
- package/dist/src/infra/update-lock.d.ts +13 -0
- package/dist/src/infra/update-lock.js +67 -0
- package/dist/src/infra/update-lock.js.map +1 -0
- package/dist/src/infra/update-runner.d.ts +6 -5
- package/dist/src/infra/update-runner.js +93 -13
- package/dist/src/infra/update-runner.js.map +1 -1
- package/dist/src/infra/update-startup.js +37 -11
- package/dist/src/infra/update-startup.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/channels-settings-h3eQwIPi.js +0 -9
- package/dist/gateway/static/root/assets/channels-settings-h3eQwIPi.js.map +0 -1
- package/dist/gateway/static/root/assets/index-BQNdJlkw.css +0 -1
- package/dist/gateway/static/root/assets/settings-page-DNG3Zijx.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-DNG3Zijx.js.map +0 -1
|
@@ -12,6 +12,9 @@ import { checkPortAvailable, forceFreePortAndWait } from "../../gateway/ports.js
|
|
|
12
12
|
import "../../gateway/index.js";
|
|
13
13
|
import { createTokenCommand } from "./gateway/token.js";
|
|
14
14
|
import { createStatusCommand } from "./gateway/status.js";
|
|
15
|
+
import { createHealthCommand } from "./gateway/health.js";
|
|
16
|
+
import { createCallCommand } from "./gateway/call.js";
|
|
17
|
+
import { createProbeCommand } from "./gateway/probe.js";
|
|
15
18
|
import { createStopCommand } from "./gateway/stop.js";
|
|
16
19
|
import { createRestartCommand } from "./gateway/restart.js";
|
|
17
20
|
import { createLogsCommand } from "./gateway/logs.js";
|
|
@@ -54,10 +57,13 @@ function createGatewayCommand(_ctx) {
|
|
|
54
57
|
"xopc gateway stop # Stop gateway",
|
|
55
58
|
"xopc gateway restart # Restart gateway",
|
|
56
59
|
"xopc gateway status # Check gateway status",
|
|
60
|
+
"xopc gateway health # Check gateway health",
|
|
61
|
+
"xopc gateway call status # Call gateway API (alias)",
|
|
62
|
+
"xopc gateway probe # Probe reachability / auth",
|
|
57
63
|
"xopc gateway logs # View recent logs",
|
|
58
64
|
"xopc gateway token # Show current token",
|
|
59
65
|
"xopc gateway token --generate # Generate new token"
|
|
60
|
-
])).option("--host <address>", "Host to bind to", "127.0.0.1").option("--port <number>", "Port to listen on", "18790").option("--token <token>", "Authentication token").option("--force", "Force kill existing process on port", false).option("--no-hot-reload", "Disable config hot reload").option("--foreground", "Start gateway in foreground mode (blocks terminal)", true).option("--background", "Start gateway in background mode (detached)", false).addCommand(createTokenCommand()).addCommand(createStatusCommand()).addCommand(createStopCommand()).addCommand(createRestartCommand()).addCommand(createLogsCommand()).addCommand(createInstallCommand()).addCommand(createUninstallCommand()).addCommand(createServiceStartCommand()).addCommand(createServiceStatusCommand()).action(async (options) => {
|
|
66
|
+
])).option("--host <address>", "Host to bind to", "127.0.0.1").option("--port <number>", "Port to listen on", "18790").option("--token <token>", "Authentication token").option("--force", "Force kill existing process on port", false).option("--no-hot-reload", "Disable config hot reload").option("--foreground", "Start gateway in foreground mode (blocks terminal)", true).option("--background", "Start gateway in background mode (detached)", false).addCommand(createTokenCommand()).addCommand(createStatusCommand()).addCommand(createHealthCommand()).addCommand(createCallCommand()).addCommand(createProbeCommand()).addCommand(createStopCommand()).addCommand(createRestartCommand()).addCommand(createLogsCommand()).addCommand(createInstallCommand()).addCommand(createUninstallCommand()).addCommand(createServiceStartCommand()).addCommand(createServiceStatusCommand()).action(async (options) => {
|
|
61
67
|
const ctx = getContextWithOpts();
|
|
62
68
|
const port = parseInt(options.port, 10);
|
|
63
69
|
const host = options.host;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.js","names":[],"sources":["../../../../src/cli/commands/gateway.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { spawn } from 'child_process';\nimport { GatewayServer } from '../../gateway/index.js';\nimport { loadConfig } from '../../config/index.js';\nimport { resolveConfigPath } from '../../config/paths.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { register, formatExamples, type CLIContext } from '../registry.js';\nimport { getContextWithOpts } from '../index.js';\nimport { runGatewayLoop } from '../../gateway/run-loop.js';\nimport { forceFreePortAndWait, checkPortAvailable } from '../../gateway/ports.js';\nimport { seedMainAgentBootstrap } from '../../agent/context/workspace-seed.js';\nimport { initWorkspace } from '../utils/init-workspace.js';\nimport {\n createTokenCommand,\n createStatusCommand,\n createStopCommand,\n createRestartCommand,\n createLogsCommand,\n createInstallCommand,\n createUninstallCommand,\n createServiceStartCommand,\n createServiceStatusCommand,\n} from './gateway/index.js';\n\nconst _log = createLogger('GatewayCommand');\n\nasync function ensureGatewayReady(\n configPath: string,\n workspacePath: string,\n gatewayHost: string,\n gatewayPort: number,\n): Promise<void> {\n const result = await initWorkspace({\n configPath,\n workspacePath,\n gatewayHost,\n gatewayPort,\n });\n\n if (result.configCreated || result.workspaceCreated) {\n console.log('');\n console.log('👋 Welcome to xopc! Running first-time setup before starting the gateway...');\n console.log('');\n console.log('✅ First-time setup complete!');\n console.log(` Config: ${configPath}`);\n console.log(` Workspace: ${workspacePath}`);\n console.log(` Token: ${result.token.slice(0, 8)}...${result.token.slice(-8)}`);\n console.log('');\n console.log('💡 Tip: run `xopc onboard` anytime to configure models, channels, and more.');\n console.log('');\n seedMainAgentBootstrap(result.config);\n }\n}\n\nfunction createGatewayCommand(_ctx: CLIContext): Command {\n const cmd = new Command('gateway')\n .description('Start the xopc gateway server')\n .addHelpText(\n 'after',\n formatExamples([\n 'xopc gateway # Start gateway (foreground, default)',\n 'xopc gateway --background # Start gateway in background',\n 'xopc gateway --port 8080 # Custom port',\n 'xopc gateway --force # Force kill existing process',\n 'xopc gateway stop # Stop gateway',\n 'xopc gateway restart # Restart gateway',\n 'xopc gateway status # Check gateway status',\n 'xopc gateway logs # View recent logs',\n 'xopc gateway token # Show current token',\n 'xopc gateway token --generate # Generate new token',\n ])\n )\n .option('--host <address>', 'Host to bind to', '127.0.0.1')\n .option('--port <number>', 'Port to listen on', '18790')\n .option('--token <token>', 'Authentication token')\n .option('--force', 'Force kill existing process on port', false)\n .option('--no-hot-reload', 'Disable config hot reload')\n .option('--foreground', 'Start gateway in foreground mode (blocks terminal)', true)\n .option('--background', 'Start gateway in background mode (detached)', false)\n .addCommand(createTokenCommand())\n .addCommand(createStatusCommand())\n .addCommand(createStopCommand())\n .addCommand(createRestartCommand())\n .addCommand(createLogsCommand())\n .addCommand(createInstallCommand())\n .addCommand(createUninstallCommand())\n .addCommand(createServiceStartCommand())\n .addCommand(createServiceStatusCommand())\n .action(async (options) => {\n const ctx = getContextWithOpts();\n const port = parseInt(options.port, 10);\n const host = options.host;\n\n await ensureGatewayReady(ctx.configPath, ctx.workspacePath, host, port);\n const config = loadConfig(ctx.configPath);\n\n // --force: Force free port\n if (options.force) {\n try {\n const result = await forceFreePortAndWait(port, {\n timeoutMs: 2000,\n sigtermTimeoutMs: 700,\n });\n if (result.killed.length > 0) {\n console.log(`Force killed ${result.killed.length} process(es) on port ${port}`);\n if (result.escalatedToSigkill) {\n console.log('Escalated to SIGKILL');\n }\n }\n } catch (err) {\n console.error(`Failed to free port ${port}: ${String(err)}`);\n process.exit(1);\n }\n }\n\n // Check if port is available\n const portAvailable = await checkPortAvailable(port, host);\n if (!portAvailable) {\n console.error(`Port ${port} is already in use. Use --force to kill existing process.`);\n process.exit(1);\n }\n\n // Determine if background mode (default is foreground, --background overrides)\n const isBackground = options.background === true;\n\n // Background mode: spawn detached process\n if (isBackground) {\n console.log('🚀 Starting xopc gateway in background...');\n console.log(` Host: ${host}`);\n console.log(` Port: ${port}`);\n console.log('');\n\n const args = [\n ...process.execArgv,\n ...process.argv.slice(1).filter(arg => arg !== '--background'),\n '--foreground', // Force foreground mode in child to prevent infinite spawn loop\n ];\n\n const child = spawn(process.execPath, args, {\n detached: true,\n stdio: 'ignore',\n env: process.env,\n });\n\n child.unref();\n\n // Wait a moment to check if process started successfully\n await new Promise(resolve => setTimeout(resolve, 500));\n\n if (child.pid && !child.killed) {\n const displayHost = host === '0.0.0.0' ? 'localhost' : host;\n console.log('✅ Gateway started in background');\n console.log(` PID: ${child.pid}`);\n console.log(` URL: http://${displayHost}:${port}`);\n const token = options.token || config?.gateway?.auth?.token;\n if (token) {\n console.log(` Token: ${token.slice(0, 8)}...${token.slice(-8)}`);\n }\n console.log('');\n console.log('📝 Management commands:');\n console.log(` xopc gateway status # Check status`);\n console.log(` xopc gateway stop # Stop gateway`);\n console.log(` xopc gateway restart # Restart gateway`);\n process.exit(0);\n } else {\n console.error('❌ Failed to start gateway in background');\n process.exit(1);\n }\n return;\n }\n\n // Foreground mode: Start gateway with run loop\n console.log('🚀 Starting xopc gateway...');\n console.log(` Host: ${host}`);\n console.log(` Port: ${port}`);\n console.log('');\n console.log('Press Ctrl+C to stop');\n console.log('');\n\n await runGatewayLoop({\n configPath: ctx.configPath || resolveConfigPath(),\n port,\n start: async () => {\n const server = new GatewayServer({\n host,\n port,\n token: options.token || config?.gateway?.auth?.token,\n verbose: ctx.isVerbose,\n configPath: ctx.configPath,\n enableHotReload: options.hotReload,\n });\n await server.start();\n\n const displayHost = host === '0.0.0.0' ? 'localhost' : host;\n const token = options.token || config?.gateway?.auth?.token;\n console.log('✅ Gateway started');\n console.log(` URL: http://${displayHost}:${port}`);\n if (token) {\n console.log(` Token: ${token.slice(0, 8)}...${token.slice(-8)}`);\n }\n console.log('');\n\n return server;\n },\n });\n });\n\n return cmd;\n}\n\nregister({\n id: 'gateway',\n name: 'gateway',\n description: 'Start the xopc gateway server',\n factory: createGatewayCommand,\n metadata: {\n category: 'runtime',\n examples: [\n 'xopc gateway',\n 'xopc gateway --background',\n 'xopc gateway --port 8080',\n ],\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gateway.js","names":[],"sources":["../../../../src/cli/commands/gateway.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { spawn } from 'child_process';\nimport { GatewayServer } from '../../gateway/index.js';\nimport { loadConfig } from '../../config/index.js';\nimport { resolveConfigPath } from '../../config/paths.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { register, formatExamples, type CLIContext } from '../registry.js';\nimport { getContextWithOpts } from '../index.js';\nimport { runGatewayLoop } from '../../gateway/run-loop.js';\nimport { forceFreePortAndWait, checkPortAvailable } from '../../gateway/ports.js';\nimport { seedMainAgentBootstrap } from '../../agent/context/workspace-seed.js';\nimport { initWorkspace } from '../utils/init-workspace.js';\nimport {\n createTokenCommand,\n createStatusCommand,\n createHealthCommand,\n createCallCommand,\n createProbeCommand,\n createStopCommand,\n createRestartCommand,\n createLogsCommand,\n createInstallCommand,\n createUninstallCommand,\n createServiceStartCommand,\n createServiceStatusCommand,\n} from './gateway/index.js';\n\nconst _log = createLogger('GatewayCommand');\n\nasync function ensureGatewayReady(\n configPath: string,\n workspacePath: string,\n gatewayHost: string,\n gatewayPort: number,\n): Promise<void> {\n const result = await initWorkspace({\n configPath,\n workspacePath,\n gatewayHost,\n gatewayPort,\n });\n\n if (result.configCreated || result.workspaceCreated) {\n console.log('');\n console.log('👋 Welcome to xopc! Running first-time setup before starting the gateway...');\n console.log('');\n console.log('✅ First-time setup complete!');\n console.log(` Config: ${configPath}`);\n console.log(` Workspace: ${workspacePath}`);\n console.log(` Token: ${result.token.slice(0, 8)}...${result.token.slice(-8)}`);\n console.log('');\n console.log('💡 Tip: run `xopc onboard` anytime to configure models, channels, and more.');\n console.log('');\n seedMainAgentBootstrap(result.config);\n }\n}\n\nfunction createGatewayCommand(_ctx: CLIContext): Command {\n const cmd = new Command('gateway')\n .description('Start the xopc gateway server')\n .addHelpText(\n 'after',\n formatExamples([\n 'xopc gateway # Start gateway (foreground, default)',\n 'xopc gateway --background # Start gateway in background',\n 'xopc gateway --port 8080 # Custom port',\n 'xopc gateway --force # Force kill existing process',\n 'xopc gateway stop # Stop gateway',\n 'xopc gateway restart # Restart gateway',\n 'xopc gateway status # Check gateway status',\n 'xopc gateway health # Check gateway health',\n 'xopc gateway call status # Call gateway API (alias)',\n 'xopc gateway probe # Probe reachability / auth',\n 'xopc gateway logs # View recent logs',\n 'xopc gateway token # Show current token',\n 'xopc gateway token --generate # Generate new token',\n ])\n )\n .option('--host <address>', 'Host to bind to', '127.0.0.1')\n .option('--port <number>', 'Port to listen on', '18790')\n .option('--token <token>', 'Authentication token')\n .option('--force', 'Force kill existing process on port', false)\n .option('--no-hot-reload', 'Disable config hot reload')\n .option('--foreground', 'Start gateway in foreground mode (blocks terminal)', true)\n .option('--background', 'Start gateway in background mode (detached)', false)\n .addCommand(createTokenCommand())\n .addCommand(createStatusCommand())\n .addCommand(createHealthCommand())\n .addCommand(createCallCommand())\n .addCommand(createProbeCommand())\n .addCommand(createStopCommand())\n .addCommand(createRestartCommand())\n .addCommand(createLogsCommand())\n .addCommand(createInstallCommand())\n .addCommand(createUninstallCommand())\n .addCommand(createServiceStartCommand())\n .addCommand(createServiceStatusCommand())\n .action(async (options) => {\n const ctx = getContextWithOpts();\n const port = parseInt(options.port, 10);\n const host = options.host;\n\n await ensureGatewayReady(ctx.configPath, ctx.workspacePath, host, port);\n const config = loadConfig(ctx.configPath);\n\n // --force: Force free port\n if (options.force) {\n try {\n const result = await forceFreePortAndWait(port, {\n timeoutMs: 2000,\n sigtermTimeoutMs: 700,\n });\n if (result.killed.length > 0) {\n console.log(`Force killed ${result.killed.length} process(es) on port ${port}`);\n if (result.escalatedToSigkill) {\n console.log('Escalated to SIGKILL');\n }\n }\n } catch (err) {\n console.error(`Failed to free port ${port}: ${String(err)}`);\n process.exit(1);\n }\n }\n\n // Check if port is available\n const portAvailable = await checkPortAvailable(port, host);\n if (!portAvailable) {\n console.error(`Port ${port} is already in use. Use --force to kill existing process.`);\n process.exit(1);\n }\n\n // Determine if background mode (default is foreground, --background overrides)\n const isBackground = options.background === true;\n\n // Background mode: spawn detached process\n if (isBackground) {\n console.log('🚀 Starting xopc gateway in background...');\n console.log(` Host: ${host}`);\n console.log(` Port: ${port}`);\n console.log('');\n\n const args = [\n ...process.execArgv,\n ...process.argv.slice(1).filter(arg => arg !== '--background'),\n '--foreground', // Force foreground mode in child to prevent infinite spawn loop\n ];\n\n const child = spawn(process.execPath, args, {\n detached: true,\n stdio: 'ignore',\n env: process.env,\n });\n\n child.unref();\n\n // Wait a moment to check if process started successfully\n await new Promise(resolve => setTimeout(resolve, 500));\n\n if (child.pid && !child.killed) {\n const displayHost = host === '0.0.0.0' ? 'localhost' : host;\n console.log('✅ Gateway started in background');\n console.log(` PID: ${child.pid}`);\n console.log(` URL: http://${displayHost}:${port}`);\n const token = options.token || config?.gateway?.auth?.token;\n if (token) {\n console.log(` Token: ${token.slice(0, 8)}...${token.slice(-8)}`);\n }\n console.log('');\n console.log('📝 Management commands:');\n console.log(` xopc gateway status # Check status`);\n console.log(` xopc gateway stop # Stop gateway`);\n console.log(` xopc gateway restart # Restart gateway`);\n process.exit(0);\n } else {\n console.error('❌ Failed to start gateway in background');\n process.exit(1);\n }\n return;\n }\n\n // Foreground mode: Start gateway with run loop\n console.log('🚀 Starting xopc gateway...');\n console.log(` Host: ${host}`);\n console.log(` Port: ${port}`);\n console.log('');\n console.log('Press Ctrl+C to stop');\n console.log('');\n\n await runGatewayLoop({\n configPath: ctx.configPath || resolveConfigPath(),\n port,\n start: async () => {\n const server = new GatewayServer({\n host,\n port,\n token: options.token || config?.gateway?.auth?.token,\n verbose: ctx.isVerbose,\n configPath: ctx.configPath,\n enableHotReload: options.hotReload,\n });\n await server.start();\n\n const displayHost = host === '0.0.0.0' ? 'localhost' : host;\n const token = options.token || config?.gateway?.auth?.token;\n console.log('✅ Gateway started');\n console.log(` URL: http://${displayHost}:${port}`);\n if (token) {\n console.log(` Token: ${token.slice(0, 8)}...${token.slice(-8)}`);\n }\n console.log('');\n\n return server;\n },\n });\n });\n\n return cmd;\n}\n\nregister({\n id: 'gateway',\n name: 'gateway',\n description: 'Start the xopc gateway server',\n factory: createGatewayCommand,\n metadata: {\n category: 'runtime',\n examples: [\n 'xopc gateway',\n 'xopc gateway --background',\n 'xopc gateway --port 8080',\n ],\n },\n});\n"],"mappings":"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"}
|
|
@@ -89,7 +89,25 @@ function createUpdateCommand(_ctx) {
|
|
|
89
89
|
const packageManager = detectGlobalPackageManager();
|
|
90
90
|
const spec = `@xopcai/xopc@${resolved.version}`;
|
|
91
91
|
if (!options.json) console.log(`Installing ${spec} via ${packageManager}...`);
|
|
92
|
-
const
|
|
92
|
+
const installArgs = buildInstallArgs(packageManager, spec);
|
|
93
|
+
const { acquireUpdateLock } = await import("../../infra/update-lock.js");
|
|
94
|
+
const lock = await acquireUpdateLock("cli");
|
|
95
|
+
if (!lock) {
|
|
96
|
+
const message = "Another update is already in progress. Try again later.";
|
|
97
|
+
if (options.json) console.log(JSON.stringify({
|
|
98
|
+
status: "error",
|
|
99
|
+
reason: "lock-held",
|
|
100
|
+
message
|
|
101
|
+
}));
|
|
102
|
+
else console.error(`❌ ${message}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
let exitCode;
|
|
106
|
+
try {
|
|
107
|
+
exitCode = await runInstallCommand(installArgs);
|
|
108
|
+
} finally {
|
|
109
|
+
await lock.release();
|
|
110
|
+
}
|
|
93
111
|
if (exitCode === 0) if (options.json) console.log(JSON.stringify({
|
|
94
112
|
status: "ok",
|
|
95
113
|
previousVersion: PACKAGE_VERSION,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.js","names":[],"sources":["../../../../src/cli/commands/update.ts"],"sourcesContent":["// src/cli/commands/update.ts\n\nimport { Command } from 'commander';\nimport { spawn } from 'node:child_process';\n\nimport { loadConfig } from '../../config/index.js';\nimport { PACKAGE_VERSION } from '../../package-version.js';\nimport { register, formatExamples, type CLIContext } from '../registry.js';\nimport { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from '../../infra/update-channels.js';\nimport {\n resolveNpmChannelTag,\n compareSemver,\n detectInstallKind,\n resolvePackageRoot,\n} from '../../infra/update-check.js';\n\nfunction createUpdateCommand(_ctx: CLIContext): Command {\n return new Command('update')\n .description('Check for and install xopc updates')\n .option('--check', 'Only check for updates without installing')\n .option('--yes', 'Skip confirmation prompts')\n .option('--channel <channel>', 'Update channel: stable, beta, or dev (default: from config, else stable)')\n .option('--json', 'Output results as JSON')\n .addHelpText(\n 'after',\n formatExamples([\n 'xopc update',\n 'xopc update --check',\n 'xopc update --channel beta',\n 'xopc update --yes',\n 'xopc update --json',\n ]),\n )\n .action(\n async (options: { check?: boolean; yes?: boolean; channel?: string; json?: boolean }) => {\n const fromCli = options.channel;\n const fromConfig = (() => {\n try {\n return loadConfig().update?.channel;\n } catch {\n return undefined;\n }\n })();\n const channel = normalizeUpdateChannel(fromCli ?? fromConfig) ?? DEFAULT_PACKAGE_CHANNEL;\n\n // Check current install kind\n const root = await resolvePackageRoot();\n if (root) {\n const installKind = await detectInstallKind(root);\n if (installKind === 'git') {\n const message = 'Running from a git checkout. Use `git pull` to update instead.';\n if (options.json) {\n console.log(JSON.stringify({ status: 'skipped', reason: 'git-checkout', message }));\n } else {\n console.log(message);\n }\n return;\n }\n }\n\n if (!options.json) {\n console.log(`Checking for updates (channel: ${channel})...`);\n }\n\n const resolved = await resolveNpmChannelTag({ channel });\n if (!resolved.version) {\n const message = 'Could not reach npm registry. Check your network connection.';\n if (options.json) {\n console.log(JSON.stringify({ status: 'error', reason: 'registry-unreachable', message }));\n } else {\n console.error(message);\n }\n process.exit(1);\n }\n\n const comparison = compareSemver(PACKAGE_VERSION, resolved.version);\n if (comparison === null || comparison >= 0) {\n const message = `Already up to date: v${PACKAGE_VERSION} (${resolved.tag}: v${resolved.version})`;\n if (options.json) {\n console.log(\n JSON.stringify({\n status: 'up-to-date',\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n }),\n );\n } else {\n console.log(`✅ ${message}`);\n }\n return;\n }\n\n if (options.check) {\n const message = `Update available: v${PACKAGE_VERSION} → v${resolved.version} (${resolved.tag})`;\n if (options.json) {\n console.log(\n JSON.stringify({\n status: 'update-available',\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n }),\n );\n } else {\n console.log(`📦 ${message}`);\n console.log('Run `xopc update` to install.');\n }\n return;\n }\n\n if (!options.yes && !process.env.XOPC_AUTO_UPDATE) {\n const { confirm } = await import('@inquirer/prompts');\n const shouldUpdate = await confirm({\n message: `Update from v${PACKAGE_VERSION} to v${resolved.version} (${resolved.tag})?`,\n default: true,\n });\n if (!shouldUpdate) {\n console.log('Update cancelled.');\n return;\n }\n }\n\n const packageManager = detectGlobalPackageManager();\n const spec = `@xopcai/xopc@${resolved.version}`;\n\n if (!options.json) {\n console.log(`Installing ${spec} via ${packageManager}...`);\n }\n\n const installArgs = buildInstallArgs(packageManager, spec);\n const exitCode = await runInstallCommand(installArgs);\n\n if (exitCode === 0) {\n if (options.json) {\n console.log(\n JSON.stringify({\n status: 'ok',\n previousVersion: PACKAGE_VERSION,\n installedVersion: resolved.version,\n channel: resolved.tag,\n packageManager,\n }),\n );\n } else {\n console.log(`✅ Updated to v${resolved.version}`);\n console.log('Restart the gateway to use the new version: xopc gateway restart');\n }\n } else {\n if (options.json) {\n console.log(\n JSON.stringify({\n status: 'error',\n reason: 'install-failed',\n exitCode,\n packageManager,\n }),\n );\n } else {\n console.error(`❌ Update failed (exit code ${exitCode})`);\n console.error(`Try manually: ${packageManager} install -g ${spec}`);\n }\n process.exit(1);\n }\n },\n );\n}\n\n/**\n * Detect which package manager was used to install xopc globally.\n * Checks common indicators: npm_config_user_agent, process.env, argv paths.\n */\nfunction detectGlobalPackageManager(): 'npm' | 'pnpm' {\n const userAgent = process.env.npm_config_user_agent ?? '';\n if (userAgent.startsWith('pnpm/')) return 'pnpm';\n return 'npm';\n}\n\nfunction buildInstallArgs(manager: 'npm' | 'pnpm', spec: string): string[] {\n if (manager === 'pnpm') {\n return ['pnpm', 'add', '-g', spec];\n }\n return ['npm', 'install', '-g', spec, '--no-fund', '--no-audit'];\n}\n\nfunction runInstallCommand(argv: string[]): Promise<number> {\n return new Promise((resolve) => {\n const child = spawn(argv[0], argv.slice(1), {\n stdio: 'inherit',\n env: process.env,\n });\n child.on('error', () => resolve(1));\n child.on('exit', (code) => resolve(code ?? 1));\n });\n}\n\nregister({\n id: 'update',\n name: 'update',\n description: 'Check for and install xopc updates',\n factory: createUpdateCommand,\n metadata: {\n category: 'maintenance',\n examples: [\n 'xopc update',\n 'xopc update --check',\n 'xopc update --channel beta',\n 'xopc update --yes --json',\n ],\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"update.js","names":[],"sources":["../../../../src/cli/commands/update.ts"],"sourcesContent":["// src/cli/commands/update.ts\n\nimport { Command } from 'commander';\nimport { spawn } from 'node:child_process';\n\nimport { loadConfig } from '../../config/index.js';\nimport { PACKAGE_VERSION } from '../../package-version.js';\nimport { register, formatExamples, type CLIContext } from '../registry.js';\nimport { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from '../../infra/update-channels.js';\nimport {\n resolveNpmChannelTag,\n compareSemver,\n detectInstallKind,\n resolvePackageRoot,\n} from '../../infra/update-check.js';\n\nfunction createUpdateCommand(_ctx: CLIContext): Command {\n return new Command('update')\n .description('Check for and install xopc updates')\n .option('--check', 'Only check for updates without installing')\n .option('--yes', 'Skip confirmation prompts')\n .option('--channel <channel>', 'Update channel: stable, beta, or dev (default: from config, else stable)')\n .option('--json', 'Output results as JSON')\n .addHelpText(\n 'after',\n formatExamples([\n 'xopc update',\n 'xopc update --check',\n 'xopc update --channel beta',\n 'xopc update --yes',\n 'xopc update --json',\n ]),\n )\n .action(\n async (options: { check?: boolean; yes?: boolean; channel?: string; json?: boolean }) => {\n const fromCli = options.channel;\n const fromConfig = (() => {\n try {\n return loadConfig().update?.channel;\n } catch {\n return undefined;\n }\n })();\n const channel = normalizeUpdateChannel(fromCli ?? fromConfig) ?? DEFAULT_PACKAGE_CHANNEL;\n\n // Check current install kind\n const root = await resolvePackageRoot();\n if (root) {\n const installKind = await detectInstallKind(root);\n if (installKind === 'git') {\n const message = 'Running from a git checkout. Use `git pull` to update instead.';\n if (options.json) {\n console.log(JSON.stringify({ status: 'skipped', reason: 'git-checkout', message }));\n } else {\n console.log(message);\n }\n return;\n }\n }\n\n if (!options.json) {\n console.log(`Checking for updates (channel: ${channel})...`);\n }\n\n const resolved = await resolveNpmChannelTag({ channel });\n if (!resolved.version) {\n const message = 'Could not reach npm registry. Check your network connection.';\n if (options.json) {\n console.log(JSON.stringify({ status: 'error', reason: 'registry-unreachable', message }));\n } else {\n console.error(message);\n }\n process.exit(1);\n }\n\n const comparison = compareSemver(PACKAGE_VERSION, resolved.version);\n if (comparison === null || comparison >= 0) {\n const message = `Already up to date: v${PACKAGE_VERSION} (${resolved.tag}: v${resolved.version})`;\n if (options.json) {\n console.log(\n JSON.stringify({\n status: 'up-to-date',\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n }),\n );\n } else {\n console.log(`✅ ${message}`);\n }\n return;\n }\n\n if (options.check) {\n const message = `Update available: v${PACKAGE_VERSION} → v${resolved.version} (${resolved.tag})`;\n if (options.json) {\n console.log(\n JSON.stringify({\n status: 'update-available',\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n }),\n );\n } else {\n console.log(`📦 ${message}`);\n console.log('Run `xopc update` to install.');\n }\n return;\n }\n\n if (!options.yes && !process.env.XOPC_AUTO_UPDATE) {\n const { confirm } = await import('@inquirer/prompts');\n const shouldUpdate = await confirm({\n message: `Update from v${PACKAGE_VERSION} to v${resolved.version} (${resolved.tag})?`,\n default: true,\n });\n if (!shouldUpdate) {\n console.log('Update cancelled.');\n return;\n }\n }\n\n const packageManager = detectGlobalPackageManager();\n const spec = `@xopcai/xopc@${resolved.version}`;\n\n if (!options.json) {\n console.log(`Installing ${spec} via ${packageManager}...`);\n }\n\n const installArgs = buildInstallArgs(packageManager, spec);\n\n const { acquireUpdateLock } = await import('../../infra/update-lock.js');\n const lock = await acquireUpdateLock('cli');\n if (!lock) {\n const message = 'Another update is already in progress. Try again later.';\n if (options.json) {\n console.log(JSON.stringify({ status: 'error', reason: 'lock-held', message }));\n } else {\n console.error(`❌ ${message}`);\n }\n process.exit(1);\n }\n\n let exitCode: number;\n try {\n exitCode = await runInstallCommand(installArgs);\n } finally {\n await lock.release();\n }\n\n if (exitCode === 0) {\n if (options.json) {\n console.log(\n JSON.stringify({\n status: 'ok',\n previousVersion: PACKAGE_VERSION,\n installedVersion: resolved.version,\n channel: resolved.tag,\n packageManager,\n }),\n );\n } else {\n console.log(`✅ Updated to v${resolved.version}`);\n console.log('Restart the gateway to use the new version: xopc gateway restart');\n }\n } else {\n if (options.json) {\n console.log(\n JSON.stringify({\n status: 'error',\n reason: 'install-failed',\n exitCode,\n packageManager,\n }),\n );\n } else {\n console.error(`❌ Update failed (exit code ${exitCode})`);\n console.error(`Try manually: ${packageManager} install -g ${spec}`);\n }\n process.exit(1);\n }\n },\n );\n}\n\n/**\n * Detect which package manager was used to install xopc globally.\n * Checks common indicators: npm_config_user_agent, process.env, argv paths.\n */\nfunction detectGlobalPackageManager(): 'npm' | 'pnpm' {\n const userAgent = process.env.npm_config_user_agent ?? '';\n if (userAgent.startsWith('pnpm/')) return 'pnpm';\n return 'npm';\n}\n\nfunction buildInstallArgs(manager: 'npm' | 'pnpm', spec: string): string[] {\n if (manager === 'pnpm') {\n return ['pnpm', 'add', '-g', spec];\n }\n return ['npm', 'install', '-g', spec, '--no-fund', '--no-audit'];\n}\n\nfunction runInstallCommand(argv: string[]): Promise<number> {\n return new Promise((resolve) => {\n const child = spawn(argv[0], argv.slice(1), {\n stdio: 'inherit',\n env: process.env,\n });\n child.on('error', () => resolve(1));\n child.on('exit', (code) => resolve(code ?? 1));\n });\n}\n\nregister({\n id: 'update',\n name: 'update',\n description: 'Check for and install xopc updates',\n factory: createUpdateCommand,\n metadata: {\n category: 'maintenance',\n examples: [\n 'xopc update',\n 'xopc update --check',\n 'xopc update --channel beta',\n 'xopc update --yes --json',\n ],\n },\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway API client — CLI-side HTTP client for gateway REST routes.
|
|
3
|
+
*/
|
|
4
|
+
import type { Command } from 'commander';
|
|
5
|
+
export interface GatewayClientOptions {
|
|
6
|
+
url?: string;
|
|
7
|
+
token?: string;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
json?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface GatewayCallResult<T = unknown> {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
status: number;
|
|
14
|
+
data?: T;
|
|
15
|
+
error?: string;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function resolveGatewayUrl(opts?: {
|
|
19
|
+
url?: string;
|
|
20
|
+
configPath?: string;
|
|
21
|
+
}): string;
|
|
22
|
+
export declare function resolveGatewayToken(opts?: {
|
|
23
|
+
token?: string;
|
|
24
|
+
configPath?: string;
|
|
25
|
+
}): string | undefined;
|
|
26
|
+
export declare function callGatewayApi<T = unknown>(method: 'GET' | 'POST' | 'PATCH' | 'DELETE', path: string, opts?: GatewayClientOptions, body?: unknown): Promise<GatewayCallResult<T>>;
|
|
27
|
+
export declare function addGatewayClientOptions(cmd: Command): Command;
|
|
28
|
+
export declare function parseGatewayClientOptions(opts: Record<string, unknown>): GatewayClientOptions;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createLogger } from "../../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../../utils/logger.js";
|
|
3
|
+
import { init_paths, resolveConfigPath } from "../../config/paths.js";
|
|
4
|
+
import { loadConfig } from "../../config/loader.js";
|
|
5
|
+
import "../../config/index.js";
|
|
6
|
+
//#region src/cli/utils/gateway-client.ts
|
|
7
|
+
init_paths();
|
|
8
|
+
init_logger();
|
|
9
|
+
const log = createLogger("GatewayClient");
|
|
10
|
+
function resolveGatewayUrl(opts) {
|
|
11
|
+
if (opts?.url) return opts.url.replace(/\/+$/, "");
|
|
12
|
+
try {
|
|
13
|
+
const config = loadConfig(opts?.configPath ?? resolveConfigPath());
|
|
14
|
+
const host = config?.gateway?.host || "127.0.0.1";
|
|
15
|
+
const port = config?.gateway?.port ?? 18790;
|
|
16
|
+
return `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
|
|
17
|
+
} catch {
|
|
18
|
+
return "http://127.0.0.1:18790";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function resolveGatewayToken(opts) {
|
|
22
|
+
if (opts?.token) return opts.token;
|
|
23
|
+
const envToken = process.env.XOPC_GATEWAY_TOKEN;
|
|
24
|
+
if (envToken) return envToken;
|
|
25
|
+
try {
|
|
26
|
+
return loadConfig(opts?.configPath ?? resolveConfigPath())?.gateway?.auth?.token;
|
|
27
|
+
} catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function callGatewayApi(method, path, opts, body) {
|
|
32
|
+
const baseUrl = resolveGatewayUrl(opts);
|
|
33
|
+
const token = resolveGatewayToken(opts);
|
|
34
|
+
const timeoutMs = opts?.timeoutMs ?? 1e4;
|
|
35
|
+
const url = `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
36
|
+
const headers = { Accept: "application/json" };
|
|
37
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
38
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
39
|
+
const startedAt = Date.now();
|
|
40
|
+
try {
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
43
|
+
const response = await fetch(url, {
|
|
44
|
+
method,
|
|
45
|
+
headers,
|
|
46
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
47
|
+
signal: controller.signal
|
|
48
|
+
});
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
const durationMs = Date.now() - startedAt;
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
let errorMessage;
|
|
53
|
+
try {
|
|
54
|
+
const errorBody = await response.json();
|
|
55
|
+
errorMessage = errorBody.error || errorBody.message || response.statusText;
|
|
56
|
+
} catch {
|
|
57
|
+
errorMessage = response.statusText;
|
|
58
|
+
}
|
|
59
|
+
log.debug({
|
|
60
|
+
url,
|
|
61
|
+
status: response.status,
|
|
62
|
+
durationMs
|
|
63
|
+
}, `Gateway call failed: ${errorMessage}`);
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
status: response.status,
|
|
67
|
+
error: errorMessage,
|
|
68
|
+
durationMs
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
log.debug({
|
|
73
|
+
url,
|
|
74
|
+
status: response.status,
|
|
75
|
+
durationMs
|
|
76
|
+
}, "Gateway call succeeded");
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
status: response.status,
|
|
80
|
+
data,
|
|
81
|
+
durationMs
|
|
82
|
+
};
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const durationMs = Date.now() - startedAt;
|
|
85
|
+
const errorMessage = err instanceof Error && err.name === "AbortError" ? `Request timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : String(err);
|
|
86
|
+
log.debug({
|
|
87
|
+
url,
|
|
88
|
+
err,
|
|
89
|
+
durationMs
|
|
90
|
+
}, `Gateway call error: ${errorMessage}`);
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
status: 0,
|
|
94
|
+
error: errorMessage,
|
|
95
|
+
durationMs
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function addGatewayClientOptions(cmd) {
|
|
100
|
+
return cmd.option("--url <url>", "Gateway HTTP URL (defaults to config or http://127.0.0.1:18790)").option("--token <token>", "Gateway auth token").option("--timeout <ms>", "Request timeout in ms", "10000").option("--json", "Output raw JSON", false);
|
|
101
|
+
}
|
|
102
|
+
function parseGatewayClientOptions(opts) {
|
|
103
|
+
const rawTimeout = opts.timeout;
|
|
104
|
+
const timeoutMs = typeof rawTimeout === "string" ? Number.parseInt(rawTimeout, 10) || 1e4 : typeof rawTimeout === "number" && Number.isFinite(rawTimeout) ? rawTimeout : 1e4;
|
|
105
|
+
return {
|
|
106
|
+
url: typeof opts.url === "string" ? opts.url : void 0,
|
|
107
|
+
token: typeof opts.token === "string" ? opts.token : void 0,
|
|
108
|
+
timeoutMs,
|
|
109
|
+
json: Boolean(opts.json)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { addGatewayClientOptions, callGatewayApi, parseGatewayClientOptions, resolveGatewayToken, resolveGatewayUrl };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=gateway-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-client.js","names":[],"sources":["../../../../src/cli/utils/gateway-client.ts"],"sourcesContent":["/**\n * Gateway API client — CLI-side HTTP client for gateway REST routes.\n */\n\nimport type { Command } from 'commander';\n\nimport { loadConfig } from '../../config/index.js';\nimport { resolveConfigPath } from '../../config/paths.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('GatewayClient');\n\nexport interface GatewayClientOptions {\n url?: string;\n token?: string;\n timeoutMs?: number;\n json?: boolean;\n}\n\nexport interface GatewayCallResult<T = unknown> {\n ok: boolean;\n status: number;\n data?: T;\n error?: string;\n durationMs: number;\n}\n\nexport function resolveGatewayUrl(opts?: { url?: string; configPath?: string }): string {\n if (opts?.url) {\n return opts.url.replace(/\\/+$/, '');\n }\n\n try {\n const configPath = opts?.configPath ?? resolveConfigPath();\n const config = loadConfig(configPath);\n const host = config?.gateway?.host || '127.0.0.1';\n const port = config?.gateway?.port ?? 18790;\n const displayHost = host === '0.0.0.0' ? '127.0.0.1' : host;\n return `http://${displayHost}:${port}`;\n } catch {\n return 'http://127.0.0.1:18790';\n }\n}\n\nexport function resolveGatewayToken(opts?: { token?: string; configPath?: string }): string | undefined {\n if (opts?.token) return opts.token;\n\n const envToken = process.env.XOPC_GATEWAY_TOKEN;\n if (envToken) return envToken;\n\n try {\n const configPath = opts?.configPath ?? resolveConfigPath();\n const config = loadConfig(configPath);\n return config?.gateway?.auth?.token;\n } catch {\n return undefined;\n }\n}\n\nexport async function callGatewayApi<T = unknown>(\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE',\n path: string,\n opts?: GatewayClientOptions,\n body?: unknown,\n): Promise<GatewayCallResult<T>> {\n const baseUrl = resolveGatewayUrl(opts);\n const token = resolveGatewayToken(opts);\n const timeoutMs = opts?.timeoutMs ?? 10_000;\n const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n const startedAt = Date.now();\n\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const response = await fetch(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n const durationMs = Date.now() - startedAt;\n\n if (!response.ok) {\n let errorMessage: string;\n try {\n const errorBody = (await response.json()) as { error?: string; message?: string };\n errorMessage = errorBody.error || errorBody.message || response.statusText;\n } catch {\n errorMessage = response.statusText;\n }\n\n log.debug({ url, status: response.status, durationMs }, `Gateway call failed: ${errorMessage}`);\n return { ok: false, status: response.status, error: errorMessage, durationMs };\n }\n\n const data = (await response.json()) as T;\n log.debug({ url, status: response.status, durationMs }, 'Gateway call succeeded');\n return { ok: true, status: response.status, data, durationMs };\n } catch (err) {\n const durationMs = Date.now() - startedAt;\n const isAbort = err instanceof Error && err.name === 'AbortError';\n const errorMessage = isAbort\n ? `Request timed out after ${timeoutMs}ms`\n : err instanceof Error\n ? err.message\n : String(err);\n\n log.debug({ url, err, durationMs }, `Gateway call error: ${errorMessage}`);\n return { ok: false, status: 0, error: errorMessage, durationMs };\n }\n}\n\nexport function addGatewayClientOptions(cmd: Command): Command {\n return cmd\n .option('--url <url>', 'Gateway HTTP URL (defaults to config or http://127.0.0.1:18790)')\n .option('--token <token>', 'Gateway auth token')\n .option('--timeout <ms>', 'Request timeout in ms', '10000')\n .option('--json', 'Output raw JSON', false);\n}\n\nexport function parseGatewayClientOptions(opts: Record<string, unknown>): GatewayClientOptions {\n const rawTimeout = opts.timeout;\n const timeoutMs =\n typeof rawTimeout === 'string'\n ? Number.parseInt(rawTimeout, 10) || 10_000\n : typeof rawTimeout === 'number' && Number.isFinite(rawTimeout)\n ? rawTimeout\n : 10_000;\n return {\n url: typeof opts.url === 'string' ? opts.url : undefined,\n token: typeof opts.token === 'string' ? opts.token : undefined,\n timeoutMs,\n json: Boolean(opts.json),\n };\n}\n"],"mappings":"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"}
|
|
@@ -13,3 +13,7 @@ export declare const ENV_VARS: {
|
|
|
13
13
|
};
|
|
14
14
|
export declare function resolveHomeDir(env?: NodeJS.ProcessEnv): string;
|
|
15
15
|
export declare function resolveStateDir(env?: NodeJS.ProcessEnv): string;
|
|
16
|
+
/** Persisted npm update check throttle / notification state (`~/.xopc/update-check.json`). */
|
|
17
|
+
export declare function resolveUpdateCheckStatePath(env?: NodeJS.ProcessEnv): string;
|
|
18
|
+
/** Cross-process lock for one-click / CLI / auto npm updates (`~/.xopc/update.lock`). */
|
|
19
|
+
export declare function resolveUpdateLockPath(env?: NodeJS.ProcessEnv): string;
|
|
@@ -12,6 +12,14 @@ function resolveStateDir(env = process.env) {
|
|
|
12
12
|
if (profile && profile !== "default") return join(home, `.xopc-${profile}`);
|
|
13
13
|
return join(home, ".xopc");
|
|
14
14
|
}
|
|
15
|
+
/** Persisted npm update check throttle / notification state (`~/.xopc/update-check.json`). */
|
|
16
|
+
function resolveUpdateCheckStatePath(env = process.env) {
|
|
17
|
+
return join(resolveStateDir(env), "update-check.json");
|
|
18
|
+
}
|
|
19
|
+
/** Cross-process lock for one-click / CLI / auto npm updates (`~/.xopc/update.lock`). */
|
|
20
|
+
function resolveUpdateLockPath(env = process.env) {
|
|
21
|
+
return join(resolveStateDir(env), "update.lock");
|
|
22
|
+
}
|
|
15
23
|
var ENV_VARS;
|
|
16
24
|
var init_paths_state = __esmMin((() => {
|
|
17
25
|
ENV_VARS = {
|
|
@@ -30,6 +38,6 @@ var init_paths_state = __esmMin((() => {
|
|
|
30
38
|
}));
|
|
31
39
|
//#endregion
|
|
32
40
|
init_paths_state();
|
|
33
|
-
export { ENV_VARS, init_paths_state, resolveHomeDir, resolveStateDir };
|
|
41
|
+
export { ENV_VARS, init_paths_state, resolveHomeDir, resolveStateDir, resolveUpdateCheckStatePath, resolveUpdateLockPath };
|
|
34
42
|
|
|
35
43
|
//# sourceMappingURL=paths-state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths-state.js","names":[],"sources":["../../../src/config/paths-state.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport const ENV_VARS = {\n STATE_DIR: 'XOPC_STATE_DIR',\n PROFILE: 'XOPC_PROFILE',\n HOME: 'XOPC_HOME',\n CONFIG_PATH: 'XOPC_CONFIG_PATH',\n CREDENTIALS_DIR: 'XOPC_CREDENTIALS_DIR',\n LOG_LEVEL: 'XOPC_LOG_LEVEL',\n LOG_DIR: 'XOPC_LOG_DIR',\n LOG_CONSOLE: 'XOPC_LOG_CONSOLE',\n LOG_FILE: 'XOPC_LOG_FILE',\n LOG_RETENTION_DAYS: 'XOPC_LOG_RETENTION_DAYS',\n PRETTY_LOGS: 'XOPC_PRETTY_LOGS',\n} as const;\n\nexport function resolveHomeDir(env: NodeJS.ProcessEnv = process.env): string {\n return env[ENV_VARS.HOME] || homedir();\n}\n\nexport function resolveStateDir(env: NodeJS.ProcessEnv = process.env): string {\n if (env[ENV_VARS.STATE_DIR]) {\n return env[ENV_VARS.STATE_DIR]!;\n }\n\n const profile = env[ENV_VARS.PROFILE];\n const home = resolveHomeDir(env);\n\n if (profile && profile !== 'default') {\n return join(home, `.xopc-${profile}`);\n }\n\n return join(home, '.xopc');\n}\n"],"mappings":";;;;AAiBA,SAAgB,eAAe,MAAyB,QAAQ,KAAa;AAC3E,QAAO,IAAI,SAAS,SAAS,SAAS;;AAGxC,SAAgB,gBAAgB,MAAyB,QAAQ,KAAa;AAC5E,KAAI,IAAI,SAAS,WACf,QAAO,IAAI,SAAS;CAGtB,MAAM,UAAU,IAAI,SAAS;CAC7B,MAAM,OAAO,eAAe,IAAI;AAEhC,KAAI,WAAW,YAAY,UACzB,QAAO,KAAK,MAAM,SAAS,UAAU;AAGvC,QAAO,KAAK,MAAM,QAAQ;;;;
|
|
1
|
+
{"version":3,"file":"paths-state.js","names":[],"sources":["../../../src/config/paths-state.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport const ENV_VARS = {\n STATE_DIR: 'XOPC_STATE_DIR',\n PROFILE: 'XOPC_PROFILE',\n HOME: 'XOPC_HOME',\n CONFIG_PATH: 'XOPC_CONFIG_PATH',\n CREDENTIALS_DIR: 'XOPC_CREDENTIALS_DIR',\n LOG_LEVEL: 'XOPC_LOG_LEVEL',\n LOG_DIR: 'XOPC_LOG_DIR',\n LOG_CONSOLE: 'XOPC_LOG_CONSOLE',\n LOG_FILE: 'XOPC_LOG_FILE',\n LOG_RETENTION_DAYS: 'XOPC_LOG_RETENTION_DAYS',\n PRETTY_LOGS: 'XOPC_PRETTY_LOGS',\n} as const;\n\nexport function resolveHomeDir(env: NodeJS.ProcessEnv = process.env): string {\n return env[ENV_VARS.HOME] || homedir();\n}\n\nexport function resolveStateDir(env: NodeJS.ProcessEnv = process.env): string {\n if (env[ENV_VARS.STATE_DIR]) {\n return env[ENV_VARS.STATE_DIR]!;\n }\n\n const profile = env[ENV_VARS.PROFILE];\n const home = resolveHomeDir(env);\n\n if (profile && profile !== 'default') {\n return join(home, `.xopc-${profile}`);\n }\n\n return join(home, '.xopc');\n}\n\n/** Persisted npm update check throttle / notification state (`~/.xopc/update-check.json`). */\nexport function resolveUpdateCheckStatePath(env: NodeJS.ProcessEnv = process.env): string {\n return join(resolveStateDir(env), 'update-check.json');\n}\n\n/** Cross-process lock for one-click / CLI / auto npm updates (`~/.xopc/update.lock`). */\nexport function resolveUpdateLockPath(env: NodeJS.ProcessEnv = process.env): string {\n return join(resolveStateDir(env), 'update.lock');\n}\n"],"mappings":";;;;AAiBA,SAAgB,eAAe,MAAyB,QAAQ,KAAa;AAC3E,QAAO,IAAI,SAAS,SAAS,SAAS;;AAGxC,SAAgB,gBAAgB,MAAyB,QAAQ,KAAa;AAC5E,KAAI,IAAI,SAAS,WACf,QAAO,IAAI,SAAS;CAGtB,MAAM,UAAU,IAAI,SAAS;CAC7B,MAAM,OAAO,eAAe,IAAI;AAEhC,KAAI,WAAW,YAAY,UACzB,QAAO,KAAK,MAAM,SAAS,UAAU;AAGvC,QAAO,KAAK,MAAM,QAAQ;;;AAI5B,SAAgB,4BAA4B,MAAyB,QAAQ,KAAa;AACxF,QAAO,KAAK,gBAAgB,IAAI,EAAE,oBAAoB;;;AAIxD,SAAgB,sBAAsB,MAAyB,QAAQ,KAAa;AAClF,QAAO,KAAK,gBAAgB,IAAI,EAAE,cAAc;;;;AAxCrC,YAAW;EACtB,WAAW;EACX,SAAS;EACT,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,SAAS;EACT,aAAa;EACb,UAAU;EACV,oBAAoB;EACpB,aAAa;EACd"}
|
|
@@ -24,6 +24,8 @@ export interface ReloadCallbacks {
|
|
|
24
24
|
onHeartbeatReload?: ReloadCallback;
|
|
25
25
|
onToolsReload?: ReloadCallback;
|
|
26
26
|
onWebSearchReload?: ReloadCallback;
|
|
27
|
+
/** All `extensions.*` hot paths in one batch (deduplicated in applyReload). */
|
|
28
|
+
onExtensionsReload?: (newConfig: Config, changedPaths: string[]) => void | Promise<void>;
|
|
27
29
|
onFullRestart?: ReloadCallback;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
@@ -147,7 +147,15 @@ var ConfigHotReloader = class {
|
|
|
147
147
|
if (this.callbacks.onFullRestart) this.callbacks.onFullRestart(newConfig);
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
|
-
if (plan.requiresHotReload)
|
|
150
|
+
if (!plan.requiresHotReload) {
|
|
151
|
+
logger.info({ plan }, "Config hot reload completed");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const isExtensionPath = (p) => p === "extensions" || p.startsWith("extensions.");
|
|
155
|
+
const extensionPaths = plan.hotPaths.filter(isExtensionPath);
|
|
156
|
+
const otherPaths = plan.hotPaths.filter((p) => !isExtensionPath(p));
|
|
157
|
+
for (const path of otherPaths) await this.applyHotPath(path, newConfig);
|
|
158
|
+
if (extensionPaths.length > 0 && this.callbacks.onExtensionsReload) await Promise.resolve(this.callbacks.onExtensionsReload(newConfig, extensionPaths));
|
|
151
159
|
logger.info({ plan }, "Config hot reload completed");
|
|
152
160
|
}
|
|
153
161
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reload.js","names":[],"sources":["../../../src/config/reload.ts"],"sourcesContent":["/**\n * Configuration hot reload manager\n */\n\nimport { watch, type FSWatcher } from 'fs';\nimport { loadConfig } from './loader.js';\nimport type { Config } from './schema.js';\nimport { diffConfigPaths } from './diff.js';\nimport { buildReloadPlan, type ReloadPlan } from './rules.js';\nimport { resolveModelsJsonPath } from './paths.js';\nimport { logger as log } from '../utils/logger.js';\n\nexport interface HotReloadConfig {\n debounceMs: number;\n enabled: boolean;\n}\n\nexport interface ReloadResult {\n success: boolean;\n plan?: ReloadPlan;\n error?: string;\n}\n\n/**\n * Callback types for different reload actions\n */\nexport type ReloadCallback = (newConfig: Config) => void | Promise<void>;\n\nexport interface ReloadCallbacks {\n onModelsReload?: ReloadCallback;\n onAgentDefaultsReload?: ReloadCallback;\n onChannelsReload?: ReloadCallback;\n onCronReload?: ReloadCallback;\n onHeartbeatReload?: ReloadCallback;\n onToolsReload?: ReloadCallback;\n onWebSearchReload?: ReloadCallback;\n onFullRestart?: ReloadCallback;\n}\n\n/**\n * Configuration hot reload manager\n */\nexport class ConfigHotReloader {\n private configPath: string;\n private callbacks: ReloadCallbacks;\n private watcher: FSWatcher | null = null;\n private modelsJsonWatcher: FSWatcher | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private currentConfig: Config;\n private debounceMs: number;\n private enabled: boolean;\n\n constructor(\n configPath: string,\n initialConfig: Config,\n callbacks: ReloadCallbacks,\n options: HotReloadConfig = { debounceMs: 300, enabled: true }\n ) {\n this.configPath = configPath;\n this.currentConfig = initialConfig;\n this.callbacks = callbacks;\n this.debounceMs = options.debounceMs;\n this.enabled = options.enabled;\n }\n\n /**\n * Start watching config file for changes\n */\n start(): void {\n if (!this.enabled) {\n log.info('Config hot reload disabled');\n return;\n }\n\n try {\n this.watcher = watch(this.configPath, (eventType) => {\n if (eventType === 'change') {\n this.scheduleReload();\n }\n });\n log.info({ path: this.configPath }, 'Config hot reload enabled');\n } catch (err) {\n log.error({ err }, 'Failed to setup config watcher');\n }\n\n this.startModelsJsonWatcher();\n }\n\n /**\n * Stop watching config file\n */\n async stop(): Promise<void> {\n if (this.watcher) {\n this.watcher.close();\n this.watcher = null;\n }\n if (this.modelsJsonWatcher) {\n this.modelsJsonWatcher.close();\n this.modelsJsonWatcher = null;\n }\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n log.info('Config hot reload stopped');\n }\n\n /**\n * Schedule a reload with debounce\n */\n private scheduleReload(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n this.debounceTimer = setTimeout(() => {\n void this.reload();\n }, this.debounceMs);\n }\n\n /**\n * Watch models.json independently so direct file edits are picked up without restarting.\n * The watcher is best-effort: if the file does not exist yet, it will not be watched.\n */\n private startModelsJsonWatcher(): void {\n const modelsJsonPath = resolveModelsJsonPath();\n try {\n this.modelsJsonWatcher = watch(modelsJsonPath, (eventType) => {\n if (eventType === 'change') {\n this.scheduleModelsJsonReload();\n }\n });\n log.info({ path: modelsJsonPath }, 'models.json hot reload enabled');\n } catch {\n log.debug({ path: modelsJsonPath }, 'models.json not found, skipping watcher');\n }\n }\n\n /**\n * Debounced handler for models.json changes.\n * Calls onModelsReload with the current config so the registry is refreshed.\n */\n private scheduleModelsJsonReload(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n this.debounceTimer = setTimeout(() => {\n log.info('models.json changed on disk — refreshing ModelRegistry');\n if (this.callbacks.onModelsReload) {\n void Promise.resolve(this.callbacks.onModelsReload(this.currentConfig));\n }\n }, this.debounceMs);\n }\n\n /**\n * Reload configuration and apply changes\n */\n async reload(): Promise<ReloadResult> {\n try {\n log.info('Reloading configuration...');\n \n // Load new config\n const newConfig = loadConfig(this.configPath);\n \n // Diff with current config\n const changedPaths = diffConfigPaths(this.currentConfig, newConfig);\n \n if (changedPaths.length === 0) {\n log.debug('No config changes detected');\n return { success: true };\n }\n \n log.info({ changedPaths }, 'Config changes detected');\n \n // Build reload plan\n const plan = buildReloadPlan(changedPaths);\n \n // Apply changes based on plan\n await this.applyReload(plan, newConfig);\n \n // Update current config\n this.currentConfig = newConfig;\n \n return { success: true, plan };\n \n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n log.error({ err, configPath: this.configPath, errorMessage: error }, `Config hot reload failed: ${error}`);\n return { success: false, error };\n }\n }\n\n /**\n * Apply reload based on plan\n */\n private async applyReload(plan: ReloadPlan, newConfig: Config): Promise<void> {\n // Handle restart-required changes first\n if (plan.requiresRestart) {\n log.info(\n { restartPaths: plan.restartPaths },\n 'Config changes require gateway restart'\n );\n \n if (this.callbacks.onFullRestart) {\n this.callbacks.onFullRestart(newConfig);\n }\n return;\n }\n\n // Handle hot reload changes\n if (plan.requiresHotReload) {\n for (const path of plan.hotPaths) {\n await this.applyHotPath(path, newConfig);\n }\n }\n\n log.info({ plan }, 'Config hot reload completed');\n }\n\n /**\n * Apply a single hot-reloadable path\n */\n private async applyHotPath(path: string, newConfig: Config): Promise<void> {\n if (path.startsWith('models.')) {\n if (this.callbacks.onModelsReload) {\n await Promise.resolve(this.callbacks.onModelsReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('agents.defaults.')) {\n if (this.callbacks.onAgentDefaultsReload) {\n await Promise.resolve(this.callbacks.onAgentDefaultsReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('channels.')) {\n if (this.callbacks.onChannelsReload) {\n await Promise.resolve(this.callbacks.onChannelsReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('cron.')) {\n if (this.callbacks.onCronReload) {\n await Promise.resolve(this.callbacks.onCronReload(newConfig));\n }\n return;\n }\n\n if (path === 'gateway.heartbeat' || path.startsWith('gateway.heartbeat.')) {\n if (this.callbacks.onHeartbeatReload) {\n await Promise.resolve(this.callbacks.onHeartbeatReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('tools.')) {\n if (this.callbacks.onToolsReload) {\n await Promise.resolve(this.callbacks.onToolsReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('webSearch.') || path.startsWith('webTools.')) {\n if (this.callbacks.onWebSearchReload) {\n await Promise.resolve(this.callbacks.onWebSearchReload(newConfig));\n }\n return;\n }\n\n log.debug({ path }, 'No handler for hot reload path');\n }\n\n /**\n * Manually trigger a reload\n */\n async triggerReload(): Promise<ReloadResult> {\n return this.reload();\n }\n\n /**\n * Align diff baseline with a freshly loaded config (e.g. Weixin QR wrote token files but JSON unchanged).\n */\n syncCurrentConfig(config: Config): void {\n this.currentConfig = config;\n }\n\n /**\n * Get current config\n */\n getConfig(): Config {\n return this.currentConfig;\n }\n\n /**\n * Check if hot reload is enabled\n */\n isEnabled(): boolean {\n return this.enabled;\n }\n}\n\nexport { diffConfigPaths } from './diff.js';\nexport { buildReloadPlan, matchReloadRule, BASE_RELOAD_RULES } from './rules.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reload.js","names":[],"sources":["../../../src/config/reload.ts"],"sourcesContent":["/**\n * Configuration hot reload manager\n */\n\nimport { watch, type FSWatcher } from 'fs';\nimport { loadConfig } from './loader.js';\nimport type { Config } from './schema.js';\nimport { diffConfigPaths } from './diff.js';\nimport { buildReloadPlan, type ReloadPlan } from './rules.js';\nimport { resolveModelsJsonPath } from './paths.js';\nimport { logger as log } from '../utils/logger.js';\n\nexport interface HotReloadConfig {\n debounceMs: number;\n enabled: boolean;\n}\n\nexport interface ReloadResult {\n success: boolean;\n plan?: ReloadPlan;\n error?: string;\n}\n\n/**\n * Callback types for different reload actions\n */\nexport type ReloadCallback = (newConfig: Config) => void | Promise<void>;\n\nexport interface ReloadCallbacks {\n onModelsReload?: ReloadCallback;\n onAgentDefaultsReload?: ReloadCallback;\n onChannelsReload?: ReloadCallback;\n onCronReload?: ReloadCallback;\n onHeartbeatReload?: ReloadCallback;\n onToolsReload?: ReloadCallback;\n onWebSearchReload?: ReloadCallback;\n /** All `extensions.*` hot paths in one batch (deduplicated in applyReload). */\n onExtensionsReload?: (\n newConfig: Config,\n changedPaths: string[],\n ) => void | Promise<void>;\n onFullRestart?: ReloadCallback;\n}\n\n/**\n * Configuration hot reload manager\n */\nexport class ConfigHotReloader {\n private configPath: string;\n private callbacks: ReloadCallbacks;\n private watcher: FSWatcher | null = null;\n private modelsJsonWatcher: FSWatcher | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private currentConfig: Config;\n private debounceMs: number;\n private enabled: boolean;\n\n constructor(\n configPath: string,\n initialConfig: Config,\n callbacks: ReloadCallbacks,\n options: HotReloadConfig = { debounceMs: 300, enabled: true }\n ) {\n this.configPath = configPath;\n this.currentConfig = initialConfig;\n this.callbacks = callbacks;\n this.debounceMs = options.debounceMs;\n this.enabled = options.enabled;\n }\n\n /**\n * Start watching config file for changes\n */\n start(): void {\n if (!this.enabled) {\n log.info('Config hot reload disabled');\n return;\n }\n\n try {\n this.watcher = watch(this.configPath, (eventType) => {\n if (eventType === 'change') {\n this.scheduleReload();\n }\n });\n log.info({ path: this.configPath }, 'Config hot reload enabled');\n } catch (err) {\n log.error({ err }, 'Failed to setup config watcher');\n }\n\n this.startModelsJsonWatcher();\n }\n\n /**\n * Stop watching config file\n */\n async stop(): Promise<void> {\n if (this.watcher) {\n this.watcher.close();\n this.watcher = null;\n }\n if (this.modelsJsonWatcher) {\n this.modelsJsonWatcher.close();\n this.modelsJsonWatcher = null;\n }\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n log.info('Config hot reload stopped');\n }\n\n /**\n * Schedule a reload with debounce\n */\n private scheduleReload(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n this.debounceTimer = setTimeout(() => {\n void this.reload();\n }, this.debounceMs);\n }\n\n /**\n * Watch models.json independently so direct file edits are picked up without restarting.\n * The watcher is best-effort: if the file does not exist yet, it will not be watched.\n */\n private startModelsJsonWatcher(): void {\n const modelsJsonPath = resolveModelsJsonPath();\n try {\n this.modelsJsonWatcher = watch(modelsJsonPath, (eventType) => {\n if (eventType === 'change') {\n this.scheduleModelsJsonReload();\n }\n });\n log.info({ path: modelsJsonPath }, 'models.json hot reload enabled');\n } catch {\n log.debug({ path: modelsJsonPath }, 'models.json not found, skipping watcher');\n }\n }\n\n /**\n * Debounced handler for models.json changes.\n * Calls onModelsReload with the current config so the registry is refreshed.\n */\n private scheduleModelsJsonReload(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n this.debounceTimer = setTimeout(() => {\n log.info('models.json changed on disk — refreshing ModelRegistry');\n if (this.callbacks.onModelsReload) {\n void Promise.resolve(this.callbacks.onModelsReload(this.currentConfig));\n }\n }, this.debounceMs);\n }\n\n /**\n * Reload configuration and apply changes\n */\n async reload(): Promise<ReloadResult> {\n try {\n log.info('Reloading configuration...');\n \n // Load new config\n const newConfig = loadConfig(this.configPath);\n \n // Diff with current config\n const changedPaths = diffConfigPaths(this.currentConfig, newConfig);\n \n if (changedPaths.length === 0) {\n log.debug('No config changes detected');\n return { success: true };\n }\n \n log.info({ changedPaths }, 'Config changes detected');\n \n // Build reload plan\n const plan = buildReloadPlan(changedPaths);\n \n // Apply changes based on plan\n await this.applyReload(plan, newConfig);\n \n // Update current config\n this.currentConfig = newConfig;\n \n return { success: true, plan };\n \n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n log.error({ err, configPath: this.configPath, errorMessage: error }, `Config hot reload failed: ${error}`);\n return { success: false, error };\n }\n }\n\n /**\n * Apply reload based on plan\n */\n private async applyReload(plan: ReloadPlan, newConfig: Config): Promise<void> {\n // Handle restart-required changes first\n if (plan.requiresRestart) {\n log.info(\n { restartPaths: plan.restartPaths },\n 'Config changes require gateway restart'\n );\n \n if (this.callbacks.onFullRestart) {\n this.callbacks.onFullRestart(newConfig);\n }\n return;\n }\n\n if (!plan.requiresHotReload) {\n log.info({ plan }, 'Config hot reload completed');\n return;\n }\n\n const isExtensionPath = (p: string) => p === 'extensions' || p.startsWith('extensions.');\n const extensionPaths = plan.hotPaths.filter(isExtensionPath);\n const otherPaths = plan.hotPaths.filter((p) => !isExtensionPath(p));\n\n for (const path of otherPaths) {\n await this.applyHotPath(path, newConfig);\n }\n\n if (extensionPaths.length > 0 && this.callbacks.onExtensionsReload) {\n await Promise.resolve(this.callbacks.onExtensionsReload(newConfig, extensionPaths));\n }\n\n log.info({ plan }, 'Config hot reload completed');\n }\n\n /**\n * Apply a single hot-reloadable path\n */\n private async applyHotPath(path: string, newConfig: Config): Promise<void> {\n if (path.startsWith('models.')) {\n if (this.callbacks.onModelsReload) {\n await Promise.resolve(this.callbacks.onModelsReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('agents.defaults.')) {\n if (this.callbacks.onAgentDefaultsReload) {\n await Promise.resolve(this.callbacks.onAgentDefaultsReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('channels.')) {\n if (this.callbacks.onChannelsReload) {\n await Promise.resolve(this.callbacks.onChannelsReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('cron.')) {\n if (this.callbacks.onCronReload) {\n await Promise.resolve(this.callbacks.onCronReload(newConfig));\n }\n return;\n }\n\n if (path === 'gateway.heartbeat' || path.startsWith('gateway.heartbeat.')) {\n if (this.callbacks.onHeartbeatReload) {\n await Promise.resolve(this.callbacks.onHeartbeatReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('tools.')) {\n if (this.callbacks.onToolsReload) {\n await Promise.resolve(this.callbacks.onToolsReload(newConfig));\n }\n return;\n }\n\n if (path.startsWith('webSearch.') || path.startsWith('webTools.')) {\n if (this.callbacks.onWebSearchReload) {\n await Promise.resolve(this.callbacks.onWebSearchReload(newConfig));\n }\n return;\n }\n\n log.debug({ path }, 'No handler for hot reload path');\n }\n\n /**\n * Manually trigger a reload\n */\n async triggerReload(): Promise<ReloadResult> {\n return this.reload();\n }\n\n /**\n * Align diff baseline with a freshly loaded config (e.g. Weixin QR wrote token files but JSON unchanged).\n */\n syncCurrentConfig(config: Config): void {\n this.currentConfig = config;\n }\n\n /**\n * Get current config\n */\n getConfig(): Config {\n return this.currentConfig;\n }\n\n /**\n * Check if hot reload is enabled\n */\n isEnabled(): boolean {\n return this.enabled;\n }\n}\n\nexport { diffConfigPaths } from './diff.js';\nexport { buildReloadPlan, matchReloadRule, BASE_RELOAD_RULES } from './rules.js';\n"],"mappings":"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"}
|
package/dist/src/config/rules.js
CHANGED
|
@@ -136,9 +136,19 @@ const BASE_RELOAD_RULES = [
|
|
|
136
136
|
description: "Web tools settings"
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
|
-
prefix: "extensions",
|
|
139
|
+
prefix: "extensions.enabled",
|
|
140
|
+
kind: "restart",
|
|
141
|
+
description: "Extension enable list (requires restart)"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
prefix: "extensions.disabled",
|
|
140
145
|
kind: "restart",
|
|
141
|
-
description: "Extension
|
|
146
|
+
description: "Extension disable list (requires restart)"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
prefix: "extensions",
|
|
150
|
+
kind: "hot",
|
|
151
|
+
description: "Extension configuration (hot-reloaded via extension handlers)"
|
|
142
152
|
},
|
|
143
153
|
{
|
|
144
154
|
prefix: "tools",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.js","names":[],"sources":["../../../src/config/rules.ts"],"sourcesContent":["/**\n * Configuration reload rules\n * \n * Defines how different config paths are handled:\n * - hot: Apply changes immediately without restart\n * - restart: Require gateway restart\n * - none: Ignore changes (no action needed)\n */\n\nimport type { ChannelPlugin } from '../channels/plugin-types.js';\nimport { bundledChannelPlugins } from '../generated/bundled-channel-plugins.js';\nimport { listChannelPlugins } from '../channels/plugins/registry.js';\n\nexport type ReloadKind = 'hot' | 'restart' | 'none';\n\nexport interface ReloadRule {\n prefix: string;\n kind: ReloadKind;\n description?: string;\n}\n\nexport interface ReloadPlan {\n changedPaths: string[];\n hotPaths: string[];\n restartPaths: string[];\n noopPaths: string[];\n requiresRestart: boolean;\n requiresHotReload: boolean;\n}\n\n/**\n * Base reload rules for config paths\n */\nexport const BASE_RELOAD_RULES: ReloadRule[] = [\n // Models config - hot reload\n { prefix: 'models.providers', kind: 'hot', description: 'Model provider API keys, base URLs' },\n { prefix: 'models.mode', kind: 'hot', description: 'Model merge mode' },\n \n // Agent defaults - hot reload\n { prefix: 'agents.defaults.model', kind: 'hot', description: 'Model configuration' },\n { prefix: 'agents.defaults.maxTaskDurationMs', kind: 'hot', description: 'Per-turn wall-clock timeout (ms)' },\n { prefix: 'agents.defaults.maxTokens', kind: 'hot', description: 'Max tokens' },\n { prefix: 'agents.defaults.temperature', kind: 'hot', description: 'Temperature' },\n { prefix: 'agents.defaults.maxToolIterations', kind: 'hot', description: 'Max tool iterations' },\n { prefix: 'agents.defaults.compaction', kind: 'hot', description: 'Compaction settings' },\n { prefix: 'agents.defaults.pruning', kind: 'hot', description: 'Pruning settings' },\n { prefix: 'agents.defaults.webExtract', kind: 'hot', description: 'Web extract model and limits' },\n { prefix: 'agents.defaults.browser', kind: 'hot', description: 'Browser automation (Playwright) tools' },\n { prefix: 'agents.defaults.delegate', kind: 'hot', description: 'delegate_task sub-agent tool' },\n { prefix: 'agents.defaults.executeCode', kind: 'hot', description: 'execute_code sandbox tool' },\n {\n prefix: 'agents.defaults.backgroundReview',\n kind: 'hot',\n description: 'Post-turn memory/skill nudge + background review',\n },\n { prefix: 'agents.defaults.workspace', kind: 'none', description: 'Workspace path - no runtime effect' },\n \n // Gateway - restart required\n { prefix: 'gateway.host', kind: 'restart', description: 'Host address' },\n { prefix: 'gateway.port', kind: 'restart', description: 'Port number' },\n { prefix: 'gateway.auth', kind: 'restart', description: 'Authentication settings' },\n { prefix: 'gateway.cors', kind: 'restart', description: 'CORS settings' },\n {\n prefix: 'gateway.skillsStoreBaseUrl',\n kind: 'restart',\n description: 'Skills marketplace API base URL',\n },\n { prefix: 'gateway.enableHotReload', kind: 'hot', description: 'Hot reload toggle' },\n \n // Channels - hot reload (channel-specific prefixes are registered by channel plugins)\n { prefix: 'channels', kind: 'hot', description: 'Any channel subtree (e.g. future extensions)' },\n \n // Cron - hot reload\n { prefix: 'cron', kind: 'hot', description: 'Scheduled tasks' },\n \n // Heartbeat lives under gateway.heartbeat in config JSON (not top-level `heartbeat`)\n { prefix: 'gateway.heartbeat', kind: 'hot', description: 'Heartbeat settings' },\n \n // Web search - hot reload\n { prefix: 'webSearch', kind: 'hot', description: 'Web search settings' },\n { prefix: 'webTools', kind: 'hot', description: 'Web tools settings' },\n \n //
|
|
1
|
+
{"version":3,"file":"rules.js","names":[],"sources":["../../../src/config/rules.ts"],"sourcesContent":["/**\n * Configuration reload rules\n * \n * Defines how different config paths are handled:\n * - hot: Apply changes immediately without restart\n * - restart: Require gateway restart\n * - none: Ignore changes (no action needed)\n */\n\nimport type { ChannelPlugin } from '../channels/plugin-types.js';\nimport { bundledChannelPlugins } from '../generated/bundled-channel-plugins.js';\nimport { listChannelPlugins } from '../channels/plugins/registry.js';\n\nexport type ReloadKind = 'hot' | 'restart' | 'none';\n\nexport interface ReloadRule {\n prefix: string;\n kind: ReloadKind;\n description?: string;\n}\n\nexport interface ReloadPlan {\n changedPaths: string[];\n hotPaths: string[];\n restartPaths: string[];\n noopPaths: string[];\n requiresRestart: boolean;\n requiresHotReload: boolean;\n}\n\n/**\n * Base reload rules for config paths\n */\nexport const BASE_RELOAD_RULES: ReloadRule[] = [\n // Models config - hot reload\n { prefix: 'models.providers', kind: 'hot', description: 'Model provider API keys, base URLs' },\n { prefix: 'models.mode', kind: 'hot', description: 'Model merge mode' },\n \n // Agent defaults - hot reload\n { prefix: 'agents.defaults.model', kind: 'hot', description: 'Model configuration' },\n { prefix: 'agents.defaults.maxTaskDurationMs', kind: 'hot', description: 'Per-turn wall-clock timeout (ms)' },\n { prefix: 'agents.defaults.maxTokens', kind: 'hot', description: 'Max tokens' },\n { prefix: 'agents.defaults.temperature', kind: 'hot', description: 'Temperature' },\n { prefix: 'agents.defaults.maxToolIterations', kind: 'hot', description: 'Max tool iterations' },\n { prefix: 'agents.defaults.compaction', kind: 'hot', description: 'Compaction settings' },\n { prefix: 'agents.defaults.pruning', kind: 'hot', description: 'Pruning settings' },\n { prefix: 'agents.defaults.webExtract', kind: 'hot', description: 'Web extract model and limits' },\n { prefix: 'agents.defaults.browser', kind: 'hot', description: 'Browser automation (Playwright) tools' },\n { prefix: 'agents.defaults.delegate', kind: 'hot', description: 'delegate_task sub-agent tool' },\n { prefix: 'agents.defaults.executeCode', kind: 'hot', description: 'execute_code sandbox tool' },\n {\n prefix: 'agents.defaults.backgroundReview',\n kind: 'hot',\n description: 'Post-turn memory/skill nudge + background review',\n },\n { prefix: 'agents.defaults.workspace', kind: 'none', description: 'Workspace path - no runtime effect' },\n \n // Gateway - restart required\n { prefix: 'gateway.host', kind: 'restart', description: 'Host address' },\n { prefix: 'gateway.port', kind: 'restart', description: 'Port number' },\n { prefix: 'gateway.auth', kind: 'restart', description: 'Authentication settings' },\n { prefix: 'gateway.cors', kind: 'restart', description: 'CORS settings' },\n {\n prefix: 'gateway.skillsStoreBaseUrl',\n kind: 'restart',\n description: 'Skills marketplace API base URL',\n },\n { prefix: 'gateway.enableHotReload', kind: 'hot', description: 'Hot reload toggle' },\n \n // Channels - hot reload (channel-specific prefixes are registered by channel plugins)\n { prefix: 'channels', kind: 'hot', description: 'Any channel subtree (e.g. future extensions)' },\n \n // Cron - hot reload\n { prefix: 'cron', kind: 'hot', description: 'Scheduled tasks' },\n \n // Heartbeat lives under gateway.heartbeat in config JSON (not top-level `heartbeat`)\n { prefix: 'gateway.heartbeat', kind: 'hot', description: 'Heartbeat settings' },\n \n // Web search - hot reload\n { prefix: 'webSearch', kind: 'hot', description: 'Web search settings' },\n { prefix: 'webTools', kind: 'hot', description: 'Web tools settings' },\n \n // Extension list toggles — still require process restart to load/unload modules\n {\n prefix: 'extensions.enabled',\n kind: 'restart',\n description: 'Extension enable list (requires restart)',\n },\n {\n prefix: 'extensions.disabled',\n kind: 'restart',\n description: 'Extension disable list (requires restart)',\n },\n // Extension instance config — hot-reloaded via extension registerReload handlers\n {\n prefix: 'extensions',\n kind: 'hot',\n description: 'Extension configuration (hot-reloaded via extension handlers)',\n },\n \n // Tools - hot reload (for tool-specific settings)\n { prefix: 'tools', kind: 'hot', description: 'Tools configuration' },\n];\n\nfunction pluginsForReloadRules(): ChannelPlugin[] {\n const listed = listChannelPlugins();\n return listed.length > 0 ? [...listed] : [...bundledChannelPlugins];\n}\n\nfunction getChannelReloadRules(): ReloadRule[] {\n return pluginsForReloadRules()\n .filter((plugin) => plugin.reload?.configPrefixes?.length)\n .flatMap((plugin) =>\n plugin.reload!.configPrefixes.map((prefix) => ({\n prefix,\n kind: 'hot' as const,\n description: `${plugin.meta.label} settings`,\n })),\n );\n}\n\nfunction mergedReloadRules(): ReloadRule[] {\n return [...BASE_RELOAD_RULES, ...getChannelReloadRules()];\n}\n\n/**\n * Find matching rule for a config path\n */\nexport function matchReloadRule(path: string): ReloadRule | null {\n const merged = mergedReloadRules();\n const exact = merged.find((r) => r.prefix === path);\n if (exact) return exact;\n const sorted = [...merged].sort((a, b) => b.prefix.length - a.prefix.length);\n for (const rule of sorted) {\n if (path === rule.prefix || path.startsWith(`${rule.prefix}.`)) {\n return rule;\n }\n }\n return null;\n}\n\n/**\n * Build reload plan from changed paths\n */\nexport function buildReloadPlan(changedPaths: string[]): ReloadPlan {\n const plan: ReloadPlan = {\n changedPaths,\n hotPaths: [],\n restartPaths: [],\n noopPaths: [],\n requiresRestart: false,\n requiresHotReload: false,\n };\n\n for (const path of changedPaths) {\n const rule = matchReloadRule(path);\n \n if (!rule) {\n // No rule matched - default to restart for safety\n plan.restartPaths.push(path);\n plan.requiresRestart = true;\n continue;\n }\n\n switch (rule.kind) {\n case 'hot':\n plan.hotPaths.push(path);\n plan.requiresHotReload = true;\n break;\n case 'restart':\n plan.restartPaths.push(path);\n plan.requiresRestart = true;\n break;\n case 'none':\n plan.noopPaths.push(path);\n break;\n }\n }\n\n return plan;\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { AgentTool } from '@mariozechner/pi-agent-core';
|
|
7
7
|
import type { Command } from 'commander';
|
|
8
|
-
import type { ExtensionApi, ExtensionLogger, ExtensionRuntime, GatewayMethodHandler, HttpRequestHandler, ExtensionCommand, ExtensionService, FlagConfig, FlagValue, ShortcutConfig, HookHandlerMap, ExtensionHookEvent } from './types/index.js';
|
|
8
|
+
import type { ExtensionApi, ExtensionLogger, ExtensionRuntime, GatewayMethodHandler, HttpRequestHandler, ExtensionCommand, ExtensionReloadHandler, ExtensionService, FlagConfig, FlagValue, ShortcutConfig, HookHandlerMap, ExtensionHookEvent } from './types/index.js';
|
|
9
9
|
import type { ChannelPlugin } from '../channels/plugin-types.js';
|
|
10
10
|
import type { Config } from '../config/config-surface.js';
|
|
11
11
|
import { EventEmitter } from 'events';
|
|
@@ -28,6 +28,8 @@ export declare class ExtensionApiImpl implements ExtensionApi {
|
|
|
28
28
|
private _typedEventBus;
|
|
29
29
|
private _registry;
|
|
30
30
|
private readonly _runtime;
|
|
31
|
+
private _reloadConfigPrefixes;
|
|
32
|
+
private _registeredCommandIds;
|
|
31
33
|
constructor(id: string, name: string, version: string | undefined, source: string, config: Config, extensionConfig: Record<string, unknown>, _logger: ExtensionLogger, _resolvePath: (input: string) => string, _coreRegistry?: ExtensionRegistryImpl, runtime?: ExtensionRuntime);
|
|
32
34
|
get logger(): ExtensionLogger;
|
|
33
35
|
get runtime(): ExtensionRuntime;
|
|
@@ -59,6 +61,9 @@ export declare class ExtensionApiImpl implements ExtensionApi {
|
|
|
59
61
|
}): void;
|
|
60
62
|
registerHttpRoute(path: string, handler: HttpRequestHandler): void;
|
|
61
63
|
registerCommand(command: ExtensionCommand): void;
|
|
64
|
+
registerReload(handler: ExtensionReloadHandler): void;
|
|
65
|
+
/** Called from loader when manifest declares `reload.configPrefixes`. */
|
|
66
|
+
_setReloadConfigPrefixes(prefixes: string[]): void;
|
|
62
67
|
registerService(service: ExtensionService): void;
|
|
63
68
|
registerGatewayMethod(method: string, handler: GatewayMethodHandler): void;
|
|
64
69
|
registerCli(factory: (ctx: {
|