@swarmclawai/swarmclaw 1.9.2 → 1.9.4
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/README.md +23 -3
- package/electron-dist/main.js +218 -0
- package/package.json +2 -2
- package/src/app/api/extensions/managed-resources/route.test.ts +117 -0
- package/src/app/api/extensions/managed-resources/route.ts +116 -0
- package/src/app/api/gateways/[id]/environments/[environmentId]/route.ts +16 -0
- package/src/app/api/gateways/[id]/environments/route.ts +13 -0
- package/src/app/api/gateways/topology-route.test.ts +30 -0
- package/src/app/api/tasks/task-workspace-route.test.ts +4 -0
- package/src/cli/index.js +4 -0
- package/src/cli/spec.js +4 -0
- package/src/components/providers/provider-list.tsx +34 -1
- package/src/components/tasks/task-sheet.tsx +50 -0
- package/src/features/gateways/queries.ts +3 -0
- package/src/lib/server/extension-managed-resources.test.ts +159 -0
- package/src/lib/server/extension-managed-resources.ts +905 -0
- package/src/lib/server/extensions.ts +113 -2
- package/src/lib/server/gateways/gateway-profile-service.ts +2 -0
- package/src/lib/server/gateways/gateway-topology.test.ts +59 -3
- package/src/lib/server/gateways/gateway-topology.ts +129 -3
- package/src/lib/server/operations/operation-pulse.test.ts +29 -0
- package/src/lib/server/operations/operation-pulse.ts +9 -0
- package/src/lib/server/session-tools/extension-creator.ts +50 -0
- package/src/lib/server/tasks/task-execution-workspace.test.ts +14 -0
- package/src/lib/server/tasks/task-execution-workspace.ts +133 -6
- package/src/types/agent.ts +2 -0
- package/src/types/app-settings.ts +8 -0
- package/src/types/extension.ts +132 -0
- package/src/types/misc.ts +31 -0
- package/src/types/schedule.ts +3 -0
- package/src/types/task.ts +30 -0
- package/src/views/settings/extension-manager.tsx +157 -1
package/README.md
CHANGED
|
@@ -185,7 +185,7 @@ Full hosted deployment guides live at https://swarmclaw.ai/docs/deployment
|
|
|
185
185
|
- **Wallets**: linked Base wallet generation, address management, approval-oriented limits, and agent payout identity.
|
|
186
186
|
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, SwarmDock, SwarmFeed, and more.
|
|
187
187
|
- **MCP Servers**: connect any Model Context Protocol server (stdio, SSE, or streamable HTTP) and inject its tools into agents alongside built-ins. Configure, test, and assign per-agent from the MCP Servers panel.
|
|
188
|
-
- **Extensions**: external tool extensions, UI modules, hooks,
|
|
188
|
+
- **Extensions**: external tool extensions, UI modules, hooks, install/update flows, and managed resource manifests for extension-owned agents, routines, local folders, gateways, and setup checks.
|
|
189
189
|
|
|
190
190
|
## What SwarmClaw Focuses On
|
|
191
191
|
|
|
@@ -399,9 +399,29 @@ Operational docs: https://swarmclaw.ai/docs/observability
|
|
|
399
399
|
|
|
400
400
|
## Releases
|
|
401
401
|
|
|
402
|
+
### v1.9.4 Highlights
|
|
403
|
+
|
|
404
|
+
Bundled runtime-environment release: gateway execution visibility, task context handoff, and operator triage in one release cycle.
|
|
405
|
+
|
|
406
|
+
- **OpenClaw environments.** Gateway topology now calls `environments.list`, stores available environment counts, exposes `/api/gateways/:id/environments`, and adds CLI commands for list/status checks.
|
|
407
|
+
- **Provider dashboard visibility.** The Providers screen now shows fleet-wide and per-gateway execution environment availability alongside nodes, sessions, presence, and pairings.
|
|
408
|
+
- **Task context packets.** Prepared task workspaces now write `context.json` with task, preview, runtime, blocker, tag, and upstream-result context for external workers.
|
|
409
|
+
- **Runtime env handoff.** Workspaces now include `.env.swarmclaw` plus SwarmClaw, portable task/workspace, and `AGENT_HOME` env hints without embedding secrets.
|
|
410
|
+
- **Operations Pulse triage.** Gateway actions now surface zero-available-environment states as high-priority operator work.
|
|
411
|
+
|
|
412
|
+
### v1.9.3 Highlights
|
|
413
|
+
|
|
414
|
+
Bundled extension-orchestration release: managed plugin resources, gateway/setup declarations, and safer local folder access in one release cycle.
|
|
415
|
+
|
|
416
|
+
- **Managed extension resources.** Extensions can now declare provisionable agents, schedules/routines, local folders, gateway platforms, and setup checks through `managedResources` or top-level manifest aliases.
|
|
417
|
+
- **Deterministic reconciliation.** `/api/extensions/managed-resources` can preview and reconcile extension-owned agents and routines with stable IDs and `managedByExtension` markers.
|
|
418
|
+
- **Trusted local folders.** Extension-declared local folders support root-bounded inspection and recursive listing with traversal and symlink-escape protection.
|
|
419
|
+
- **Operator UI.** The Extensions screen now shows managed-resource badges and a Managed tab with totals plus per-extension reconcile controls.
|
|
420
|
+
- **Extension authoring spec.** `extension_creator` now documents managed resources, gateway declarations, setup checks, and manifest aliases.
|
|
421
|
+
|
|
402
422
|
### v1.9.2 Highlights
|
|
403
423
|
|
|
404
|
-
Bundled
|
|
424
|
+
Bundled runtime-polish release: reasoning hygiene, deterministic delegation routing, task workflow polish, OpenClaw export hardening, and timeout hygiene.
|
|
405
425
|
|
|
406
426
|
- **Stateful reasoning tag scrubber.** String-streamed `<think>`, `<thinking>`, `<reasoning>`, `<thought>`, and `<REASONING_SCRATCHPAD>` blocks are removed across split deltas and routed into SwarmClaw's thinking stream instead of leaking into visible answers.
|
|
407
427
|
- **Deterministic delegation profiles.** `manage_tasks` now accepts explicit `workType` and `requiredCapabilities` routing hints, returns a stable `routeKey`, and can auto-assign unowned work without a classifier call when the profile is explicit.
|
|
@@ -411,7 +431,7 @@ Bundled competitor-parity release: Hermes-style reasoning hygiene, deterministic
|
|
|
411
431
|
|
|
412
432
|
### v1.9.1 Highlights
|
|
413
433
|
|
|
414
|
-
Task execution workspace release:
|
|
434
|
+
Task execution workspace release: task-scoped workspaces, preview handoffs, and liveness evidence.
|
|
415
435
|
|
|
416
436
|
- **Task-scoped execution workspaces.** Tasks can now provision a deterministic workspace under the SwarmClaw workspace root, preserving source cwd and project context while creating a task-local README for artifacts and handoffs.
|
|
417
437
|
- **Preview and runtime metadata.** Tasks can carry preview links and runtime services, and the task board surfaces those links directly on task cards and sheets.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const electron_1 = require("electron");
|
|
40
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
41
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
42
|
+
const paths_1 = require("./paths");
|
|
43
|
+
const server_lifecycle_1 = require("./server-lifecycle");
|
|
44
|
+
const menu_1 = require("./menu");
|
|
45
|
+
const DEV_URL_DEFAULT = 'http://127.0.0.1:3456';
|
|
46
|
+
const LOG_TAIL_BYTES = 1500;
|
|
47
|
+
let mainWindow = null;
|
|
48
|
+
let serverHandle = null;
|
|
49
|
+
let serverLogFile = null;
|
|
50
|
+
let isQuitting = false;
|
|
51
|
+
const gotLock = electron_1.app.requestSingleInstanceLock();
|
|
52
|
+
if (!gotLock) {
|
|
53
|
+
electron_1.app.quit();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
electron_1.app.on('second-instance', () => {
|
|
57
|
+
if (mainWindow) {
|
|
58
|
+
if (mainWindow.isMinimized())
|
|
59
|
+
mainWindow.restore();
|
|
60
|
+
mainWindow.focus();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
electron_1.app.on('ready', () => void onReady());
|
|
64
|
+
electron_1.app.on('window-all-closed', () => {
|
|
65
|
+
if (process.platform !== 'darwin')
|
|
66
|
+
electron_1.app.quit();
|
|
67
|
+
});
|
|
68
|
+
electron_1.app.on('activate', () => {
|
|
69
|
+
if (mainWindow !== null)
|
|
70
|
+
return;
|
|
71
|
+
if (serverHandle) {
|
|
72
|
+
createMainWindow(serverHandle.url);
|
|
73
|
+
}
|
|
74
|
+
else if (!electron_1.app.isPackaged) {
|
|
75
|
+
createMainWindow(process.env.SWARMCLAW_DEV_URL || DEV_URL_DEFAULT);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
electron_1.app.on('before-quit', () => {
|
|
79
|
+
isQuitting = true;
|
|
80
|
+
});
|
|
81
|
+
electron_1.app.on('will-quit', async (event) => {
|
|
82
|
+
if (!serverHandle)
|
|
83
|
+
return;
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
try {
|
|
86
|
+
await serverHandle.stop();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
serverHandle = null;
|
|
90
|
+
electron_1.app.exit(0);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function onReady() {
|
|
95
|
+
const paths = (0, paths_1.resolveRuntimePaths)();
|
|
96
|
+
(0, menu_1.buildAppMenu)(paths, () => mainWindow);
|
|
97
|
+
const iconPath = resolveIconPath();
|
|
98
|
+
if (process.platform === 'darwin' && iconPath && electron_1.app.dock) {
|
|
99
|
+
const img = electron_1.nativeImage.createFromPath(iconPath);
|
|
100
|
+
if (!img.isEmpty())
|
|
101
|
+
electron_1.app.dock.setIcon(img);
|
|
102
|
+
}
|
|
103
|
+
if (!electron_1.app.isPackaged) {
|
|
104
|
+
const devUrl = process.env.SWARMCLAW_DEV_URL || DEV_URL_DEFAULT;
|
|
105
|
+
console.log(`[swarmclaw] dev mode, loading ${devUrl}`);
|
|
106
|
+
createMainWindow(devUrl);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
serverLogFile = node_path_1.default.join(electron_1.app.getPath('userData'), 'logs', 'server.log');
|
|
110
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(serverLogFile), { recursive: true });
|
|
111
|
+
try {
|
|
112
|
+
serverHandle = await (0, server_lifecycle_1.startEmbeddedServer)({
|
|
113
|
+
paths,
|
|
114
|
+
logFile: serverLogFile,
|
|
115
|
+
onStdout: (c) => process.stdout.write(`[swarmclaw] ${c}`),
|
|
116
|
+
onStderr: (c) => process.stderr.write(`[swarmclaw] ${c}`),
|
|
117
|
+
onExit: (code, signal) => {
|
|
118
|
+
if (!isQuitting) {
|
|
119
|
+
console.error(`[swarmclaw] server exited unexpectedly (code=${code}, signal=${signal ?? 'none'})`);
|
|
120
|
+
void showServerCrashDialog(code, signal);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
await showStartupFailureDialog(err, paths);
|
|
127
|
+
electron_1.app.exit(1);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
createMainWindow(serverHandle.url);
|
|
131
|
+
void Promise.resolve().then(() => __importStar(require('./updater'))).then((m) => m.initAutoUpdater());
|
|
132
|
+
}
|
|
133
|
+
function resolveIconPath() {
|
|
134
|
+
const candidate = electron_1.app.isPackaged
|
|
135
|
+
? node_path_1.default.join(process.resourcesPath, 'icon.png')
|
|
136
|
+
: node_path_1.default.join(__dirname, '..', 'resources', 'icon.png');
|
|
137
|
+
return node_fs_1.default.existsSync(candidate) ? candidate : undefined;
|
|
138
|
+
}
|
|
139
|
+
function createMainWindow(startUrl) {
|
|
140
|
+
const iconPath = resolveIconPath();
|
|
141
|
+
mainWindow = new electron_1.BrowserWindow({
|
|
142
|
+
width: 1440,
|
|
143
|
+
height: 900,
|
|
144
|
+
minWidth: 1024,
|
|
145
|
+
minHeight: 640,
|
|
146
|
+
backgroundColor: '#0b0b0f',
|
|
147
|
+
show: true,
|
|
148
|
+
...(iconPath ? { icon: iconPath } : {}),
|
|
149
|
+
webPreferences: {
|
|
150
|
+
contextIsolation: true,
|
|
151
|
+
nodeIntegration: false,
|
|
152
|
+
sandbox: false,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
const wc = mainWindow.webContents;
|
|
156
|
+
if (!electron_1.app.isPackaged)
|
|
157
|
+
wc.openDevTools({ mode: 'detach' });
|
|
158
|
+
wc.on('did-start-loading', () => console.log('[swarmclaw] did-start-loading'));
|
|
159
|
+
wc.on('did-finish-load', () => console.log('[swarmclaw] did-finish-load'));
|
|
160
|
+
wc.on('did-fail-load', (_e, code, desc, url) => console.error(`[swarmclaw] did-fail-load code=${code} desc=${desc} url=${url}`));
|
|
161
|
+
wc.on('render-process-gone', (_e, details) => console.error(`[swarmclaw] render-process-gone reason=${details.reason}`));
|
|
162
|
+
wc.on('unresponsive', () => console.error('[swarmclaw] webContents unresponsive'));
|
|
163
|
+
mainWindow.on('closed', () => {
|
|
164
|
+
mainWindow = null;
|
|
165
|
+
});
|
|
166
|
+
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
167
|
+
if (url.startsWith(startUrl))
|
|
168
|
+
return { action: 'allow' };
|
|
169
|
+
void electron_1.shell.openExternal(url);
|
|
170
|
+
return { action: 'deny' };
|
|
171
|
+
});
|
|
172
|
+
void mainWindow.loadURL(startUrl).catch((err) => {
|
|
173
|
+
console.error('[swarmclaw] loadURL rejected:', err);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async function showServerCrashDialog(code, signal) {
|
|
177
|
+
const buttons = serverLogFile ? ['Open Logs Folder', 'Quit'] : ['Quit'];
|
|
178
|
+
const quitButtonId = buttons.length - 1;
|
|
179
|
+
const detail = buildLogDetail(`code=${code ?? 'null'} signal=${signal ?? 'none'}`);
|
|
180
|
+
const res = await electron_1.dialog.showMessageBox({
|
|
181
|
+
type: 'error',
|
|
182
|
+
buttons,
|
|
183
|
+
defaultId: quitButtonId,
|
|
184
|
+
cancelId: quitButtonId,
|
|
185
|
+
title: 'SwarmClaw stopped',
|
|
186
|
+
message: 'The SwarmClaw server exited unexpectedly.',
|
|
187
|
+
detail,
|
|
188
|
+
});
|
|
189
|
+
if (serverLogFile && res.response === 0)
|
|
190
|
+
electron_1.shell.showItemInFolder(serverLogFile);
|
|
191
|
+
electron_1.app.exit(1);
|
|
192
|
+
}
|
|
193
|
+
async function showStartupFailureDialog(err, paths) {
|
|
194
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
195
|
+
const base = `${message}\n\nStandalone entry: ${paths.standaloneEntry}\nData dir: ${paths.dataDir}`;
|
|
196
|
+
const detail = buildLogDetail(base);
|
|
197
|
+
const buttons = serverLogFile ? ['Open Logs Folder', 'Quit'] : ['Quit'];
|
|
198
|
+
const quitButtonId = buttons.length - 1;
|
|
199
|
+
const res = await electron_1.dialog.showMessageBox({
|
|
200
|
+
type: 'error',
|
|
201
|
+
buttons,
|
|
202
|
+
defaultId: quitButtonId,
|
|
203
|
+
cancelId: quitButtonId,
|
|
204
|
+
title: 'SwarmClaw failed to start',
|
|
205
|
+
message: 'The embedded server did not start.',
|
|
206
|
+
detail,
|
|
207
|
+
});
|
|
208
|
+
if (serverLogFile && res.response === 0)
|
|
209
|
+
electron_1.shell.showItemInFolder(serverLogFile);
|
|
210
|
+
}
|
|
211
|
+
function buildLogDetail(base) {
|
|
212
|
+
if (!serverLogFile)
|
|
213
|
+
return base;
|
|
214
|
+
const tail = (0, server_lifecycle_1.tailLogFile)(serverLogFile, LOG_TAIL_BYTES).trim();
|
|
215
|
+
if (!tail)
|
|
216
|
+
return `${base}\n\nLog file: ${serverLogFile}\n(no output captured yet)`;
|
|
217
|
+
return `${base}\n\nLog tail (${serverLogFile}):\n${tail}`;
|
|
218
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.4",
|
|
4
4
|
"description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
|
|
5
5
|
"main": "electron-dist/main.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/electron-after-pack.test.mjs scripts/ensure-sandbox-browser-image.test.mjs scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
|
|
88
88
|
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
|
|
89
89
|
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/connectors/swarmdock.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/gateways/gateway-topology.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/gateways/topology-route.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
|
|
90
|
-
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
|
|
90
|
+
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
|
|
91
91
|
"test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
|
|
92
92
|
"test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
|
|
93
93
|
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test, { afterEach } from 'node:test'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import os from 'node:os'
|
|
6
|
+
|
|
7
|
+
import { getExtensionManager } from '@/lib/server/extensions'
|
|
8
|
+
import { loadAgents, loadSchedules, loadSettings, saveAgents, saveSchedules, saveSettings } from '@/lib/server/storage'
|
|
9
|
+
import { GET, POST } from './route'
|
|
10
|
+
|
|
11
|
+
const originalAgents = loadAgents()
|
|
12
|
+
const originalSchedules = loadSchedules()
|
|
13
|
+
const originalSettings = loadSettings()
|
|
14
|
+
|
|
15
|
+
let seq = 0
|
|
16
|
+
|
|
17
|
+
function extensionId(prefix: string): string {
|
|
18
|
+
seq += 1
|
|
19
|
+
return `${prefix}_${Date.now()}_${seq}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
saveAgents(originalAgents)
|
|
24
|
+
saveSchedules(originalSchedules)
|
|
25
|
+
saveSettings(originalSettings)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('managed resources route reconciles one extension', async () => {
|
|
29
|
+
const id = extensionId('route_managed_resources')
|
|
30
|
+
getExtensionManager().registerBuiltin(id, {
|
|
31
|
+
name: 'Route Managed Fixture',
|
|
32
|
+
managedResources: {
|
|
33
|
+
agents: [
|
|
34
|
+
{
|
|
35
|
+
agentKey: 'operator',
|
|
36
|
+
displayName: 'Route Operator',
|
|
37
|
+
provider: 'openai',
|
|
38
|
+
model: 'gpt-4o-mini',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const before = await GET(new Request('http://local/api/extensions/managed-resources'))
|
|
45
|
+
assert.equal(before.status, 200)
|
|
46
|
+
const beforeBody = await before.json()
|
|
47
|
+
const extension = beforeBody.extensions.find((entry: { extensionId: string }) => entry.extensionId === id)
|
|
48
|
+
assert.equal(extension.agents[0].status, 'missing')
|
|
49
|
+
|
|
50
|
+
const response = await POST(new Request('http://local/api/extensions/managed-resources', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'content-type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({ action: 'reconcile', extensionId: id }),
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
assert.equal(response.status, 200)
|
|
57
|
+
const body = await response.json()
|
|
58
|
+
assert.equal(body.createdAgents.length, 1)
|
|
59
|
+
const agent = loadAgents()[body.createdAgents[0]]
|
|
60
|
+
assert.ok(agent)
|
|
61
|
+
assert.equal(agent.managedByExtension?.extensionId, id)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('managed resources route configures and lists a local folder', async () => {
|
|
65
|
+
const id = extensionId('route_managed_folder')
|
|
66
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-route-folder-'))
|
|
67
|
+
fs.mkdirSync(path.join(tempDir, 'inputs'))
|
|
68
|
+
fs.writeFileSync(path.join(tempDir, 'inputs', 'task.txt'), 'task\n')
|
|
69
|
+
|
|
70
|
+
getExtensionManager().registerBuiltin(id, {
|
|
71
|
+
name: 'Route Folder Fixture',
|
|
72
|
+
managedResources: {
|
|
73
|
+
localFolders: [
|
|
74
|
+
{
|
|
75
|
+
folderKey: 'workspace',
|
|
76
|
+
displayName: 'Workspace Folder',
|
|
77
|
+
access: 'readWrite',
|
|
78
|
+
requiredDirectories: ['inputs'],
|
|
79
|
+
requiredFiles: ['inputs/task.txt'],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const configure = await POST(new Request('http://local/api/extensions/managed-resources', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { 'content-type': 'application/json' },
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
action: 'configure_local_folder',
|
|
90
|
+
extensionId: id,
|
|
91
|
+
folderKey: 'workspace',
|
|
92
|
+
path: tempDir,
|
|
93
|
+
}),
|
|
94
|
+
}))
|
|
95
|
+
assert.equal(configure.status, 200)
|
|
96
|
+
const configured = await configure.json()
|
|
97
|
+
assert.equal(configured.status.healthy, true)
|
|
98
|
+
|
|
99
|
+
const listing = await GET(new Request(`http://local/api/extensions/managed-resources?action=list_local_folder&extensionId=${encodeURIComponent(id)}&folderKey=workspace&recursive=true`))
|
|
100
|
+
assert.equal(listing.status, 200)
|
|
101
|
+
const listingBody = await listing.json()
|
|
102
|
+
assert.ok(listingBody.entries.some((entry: { path: string }) => entry.path === 'inputs/task.txt'))
|
|
103
|
+
|
|
104
|
+
const traversal = await POST(new Request('http://local/api/extensions/managed-resources', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'content-type': 'application/json' },
|
|
107
|
+
body: JSON.stringify({
|
|
108
|
+
action: 'list_local_folder',
|
|
109
|
+
extensionId: id,
|
|
110
|
+
folderKey: 'workspace',
|
|
111
|
+
relativePath: '../outside',
|
|
112
|
+
}),
|
|
113
|
+
}))
|
|
114
|
+
assert.equal(traversal.status, 400)
|
|
115
|
+
|
|
116
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
117
|
+
})
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
3
|
+
import {
|
|
4
|
+
inspectExtensionLocalFolder,
|
|
5
|
+
listExtensionLocalFolderEntries,
|
|
6
|
+
listExtensionManagedResources,
|
|
7
|
+
reconcileExtensionManagedResources,
|
|
8
|
+
setExtensionLocalFolderConfig,
|
|
9
|
+
} from '@/lib/server/extension-managed-resources'
|
|
10
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
11
|
+
import '@/lib/server/builtin-extensions'
|
|
12
|
+
|
|
13
|
+
export const dynamic = 'force-dynamic'
|
|
14
|
+
|
|
15
|
+
function badRequest(message: string) {
|
|
16
|
+
return NextResponse.json({ error: message }, { status: 400 })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function optionalText(value: unknown): string | null {
|
|
20
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function GET(req: Request) {
|
|
24
|
+
const { searchParams } = new URL(req.url)
|
|
25
|
+
const action = searchParams.get('action')
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (action === 'inspect_local_folder') {
|
|
29
|
+
const extensionId = optionalText(searchParams.get('extensionId'))
|
|
30
|
+
const folderKey = optionalText(searchParams.get('folderKey'))
|
|
31
|
+
if (!extensionId || !folderKey) return badRequest('extensionId and folderKey required')
|
|
32
|
+
return NextResponse.json(await inspectExtensionLocalFolder({
|
|
33
|
+
extensionId,
|
|
34
|
+
folderKey,
|
|
35
|
+
}))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (action === 'list_local_folder') {
|
|
39
|
+
const extensionId = optionalText(searchParams.get('extensionId'))
|
|
40
|
+
const folderKey = optionalText(searchParams.get('folderKey'))
|
|
41
|
+
if (!extensionId || !folderKey) return badRequest('extensionId and folderKey required')
|
|
42
|
+
return NextResponse.json(await listExtensionLocalFolderEntries({
|
|
43
|
+
extensionId,
|
|
44
|
+
folderKey,
|
|
45
|
+
relativePath: optionalText(searchParams.get('relativePath')),
|
|
46
|
+
recursive: searchParams.get('recursive') === 'true',
|
|
47
|
+
maxEntries: Number(searchParams.get('maxEntries') || 1000),
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return NextResponse.json(listExtensionManagedResources())
|
|
52
|
+
} catch (err: unknown) {
|
|
53
|
+
return badRequest(errorMessage(err))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function POST(req: Request) {
|
|
58
|
+
const { data: body, error } = await safeParseBody(req)
|
|
59
|
+
if (error) return error
|
|
60
|
+
const input = body && typeof body === 'object' ? body as Record<string, unknown> : {}
|
|
61
|
+
const action = optionalText(input.action)
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
if (action === 'reconcile') {
|
|
65
|
+
return NextResponse.json(reconcileExtensionManagedResources(optionalText(input.extensionId)))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (action === 'configure_local_folder') {
|
|
69
|
+
const extensionId = optionalText(input.extensionId)
|
|
70
|
+
const folderKey = optionalText(input.folderKey)
|
|
71
|
+
const folderPath = optionalText(input.path)
|
|
72
|
+
if (!extensionId || !folderKey || !folderPath) {
|
|
73
|
+
return badRequest('extensionId, folderKey, and path required')
|
|
74
|
+
}
|
|
75
|
+
const access = input.access === 'read' || input.access === 'readWrite'
|
|
76
|
+
? input.access
|
|
77
|
+
: undefined
|
|
78
|
+
const config = setExtensionLocalFolderConfig({
|
|
79
|
+
extensionId,
|
|
80
|
+
folderKey,
|
|
81
|
+
path: folderPath,
|
|
82
|
+
access,
|
|
83
|
+
})
|
|
84
|
+
const status = await inspectExtensionLocalFolder({ extensionId, folderKey })
|
|
85
|
+
return NextResponse.json({ ok: true, config, status })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (action === 'inspect_local_folder') {
|
|
89
|
+
const extensionId = optionalText(input.extensionId)
|
|
90
|
+
const folderKey = optionalText(input.folderKey)
|
|
91
|
+
if (!extensionId || !folderKey) return badRequest('extensionId and folderKey required')
|
|
92
|
+
return NextResponse.json(await inspectExtensionLocalFolder({
|
|
93
|
+
extensionId,
|
|
94
|
+
folderKey,
|
|
95
|
+
overridePath: optionalText(input.path),
|
|
96
|
+
}))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (action === 'list_local_folder') {
|
|
100
|
+
const extensionId = optionalText(input.extensionId)
|
|
101
|
+
const folderKey = optionalText(input.folderKey)
|
|
102
|
+
if (!extensionId || !folderKey) return badRequest('extensionId and folderKey required')
|
|
103
|
+
return NextResponse.json(await listExtensionLocalFolderEntries({
|
|
104
|
+
extensionId,
|
|
105
|
+
folderKey,
|
|
106
|
+
relativePath: optionalText(input.relativePath),
|
|
107
|
+
recursive: input.recursive === true,
|
|
108
|
+
maxEntries: typeof input.maxEntries === 'number' ? input.maxEntries : undefined,
|
|
109
|
+
}))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return badRequest('action required')
|
|
113
|
+
} catch (err: unknown) {
|
|
114
|
+
return badRequest(errorMessage(err))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { getOpenClawGatewayEnvironmentStatus } from '@/lib/server/gateways/gateway-topology'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
export async function GET(
|
|
9
|
+
_req: Request,
|
|
10
|
+
{ params }: { params: Promise<{ id: string; environmentId: string }> },
|
|
11
|
+
) {
|
|
12
|
+
const { id, environmentId } = await params
|
|
13
|
+
const snapshot = await getOpenClawGatewayEnvironmentStatus(id, decodeURIComponent(environmentId))
|
|
14
|
+
if (!snapshot) return notFound()
|
|
15
|
+
return NextResponse.json(snapshot, { status: snapshot.errors.length > 0 ? 502 : 200 })
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { listOpenClawGatewayEnvironments } from '@/lib/server/gateways/gateway-topology'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
9
|
+
const { id } = await params
|
|
10
|
+
const snapshot = await listOpenClawGatewayEnvironments(id)
|
|
11
|
+
if (!snapshot) return notFound()
|
|
12
|
+
return NextResponse.json(snapshot)
|
|
13
|
+
}
|
|
@@ -18,6 +18,36 @@ test('gateway topology route returns 404 for unknown profiles', () => {
|
|
|
18
18
|
assert.equal(output.body.error, 'Not found')
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
test('gateway environments route returns 404 for unknown profiles', () => {
|
|
22
|
+
const output = runWithTempDataDir<{ status: number; body: { error: string } }>(`
|
|
23
|
+
const routeMod = await import('./src/app/api/gateways/[id]/environments/route')
|
|
24
|
+
const route = routeMod.default || routeMod
|
|
25
|
+
const response = await route.GET(
|
|
26
|
+
new Request('http://local/api/gateways/missing/environments'),
|
|
27
|
+
{ params: Promise.resolve({ id: 'missing' }) },
|
|
28
|
+
)
|
|
29
|
+
console.log(JSON.stringify({ status: response.status, body: await response.json() }))
|
|
30
|
+
`, { prefix: 'swarmclaw-gateway-environments-route-test-' })
|
|
31
|
+
|
|
32
|
+
assert.equal(output.status, 404)
|
|
33
|
+
assert.equal(output.body.error, 'Not found')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('gateway environment status route returns 404 for unknown profiles', () => {
|
|
37
|
+
const output = runWithTempDataDir<{ status: number; body: { error: string } }>(`
|
|
38
|
+
const routeMod = await import('./src/app/api/gateways/[id]/environments/[environmentId]/route')
|
|
39
|
+
const route = routeMod.default || routeMod
|
|
40
|
+
const response = await route.GET(
|
|
41
|
+
new Request('http://local/api/gateways/missing/environments/gateway'),
|
|
42
|
+
{ params: Promise.resolve({ id: 'missing', environmentId: 'gateway' }) },
|
|
43
|
+
)
|
|
44
|
+
console.log(JSON.stringify({ status: response.status, body: await response.json() }))
|
|
45
|
+
`, { prefix: 'swarmclaw-gateway-environment-status-route-test-' })
|
|
46
|
+
|
|
47
|
+
assert.equal(output.status, 404)
|
|
48
|
+
assert.equal(output.body.error, 'Not found')
|
|
49
|
+
})
|
|
50
|
+
|
|
21
51
|
test('gateway fleet route reports empty totals when no OpenClaw profiles exist', () => {
|
|
22
52
|
const output = runWithTempDataDir<{
|
|
23
53
|
status: number
|
|
@@ -81,9 +81,13 @@ test('PUT /api/tasks/:id provisions an execution workspace and preview links', a
|
|
|
81
81
|
assert.equal(response.status, 200)
|
|
82
82
|
const body = await response.json() as BoardTask
|
|
83
83
|
assert.equal(body.executionWorkspace?.sourceCwd, '/source/repo')
|
|
84
|
+
assert.equal(body.executionWorkspace?.context?.taskId, 'task-route-workspace')
|
|
85
|
+
assert.equal(body.executionWorkspace?.envHints?.some((hint) => hint.key === 'WORKSPACE_CWD'), true)
|
|
84
86
|
assert.equal(body.previewLinks?.[0]?.url, 'http://127.0.0.1:3456')
|
|
85
87
|
assert.equal(body.runtimeServices?.[0]?.name, 'Next dev')
|
|
86
88
|
assert.equal(fs.existsSync(body.executionWorkspace?.path || ''), true)
|
|
89
|
+
assert.equal(fs.existsSync(body.executionWorkspace?.contextPath || ''), true)
|
|
90
|
+
assert.equal(fs.existsSync(body.executionWorkspace?.envPath || ''), true)
|
|
87
91
|
})
|
|
88
92
|
|
|
89
93
|
test('GET /api/tasks returns computed blocked liveness without persisting a task patch', async () => {
|
package/src/cli/index.js
CHANGED
|
@@ -273,6 +273,8 @@ const COMMAND_GROUPS = [
|
|
|
273
273
|
cmd('delete', 'DELETE', '/gateways/:id', 'Delete a gateway profile'),
|
|
274
274
|
cmd('health', 'GET', '/gateways/:id/health', 'Run a gateway health check'),
|
|
275
275
|
cmd('topology', 'GET', '/gateways/:id/topology', 'Refresh and return one gateway topology snapshot'),
|
|
276
|
+
cmd('environments', 'GET', '/gateways/:id/environments', 'List OpenClaw gateway execution environments'),
|
|
277
|
+
cmd('environment-status', 'GET', '/gateways/:id/environments/:environmentId', 'Get one OpenClaw gateway execution environment status'),
|
|
276
278
|
cmd('fleet', 'GET', '/gateways/fleet', 'Refresh and return fleet-wide gateway topology'),
|
|
277
279
|
],
|
|
278
280
|
},
|
|
@@ -526,6 +528,8 @@ const COMMAND_GROUPS = [
|
|
|
526
528
|
cmd('settings-set', 'PUT', '/extensions/settings', 'Set extension settings (use --query extensionId=extension_name and --data JSON)', { expectsJsonBody: true }),
|
|
527
529
|
cmd('ui', 'GET', '/extensions/ui', 'List extension UI modules (use --query type=sidebar|header|chat_actions|connectors)'),
|
|
528
530
|
cmd('builtins', 'GET', '/extensions/builtins', 'List built-in extensions'),
|
|
531
|
+
cmd('managed-resources', 'GET', '/extensions/managed-resources', 'Preview extension-managed agents, routines, folders, gateways, and setup checks'),
|
|
532
|
+
cmd('managed-resources-action', 'POST', '/extensions/managed-resources', 'Reconcile or inspect extension-managed resources', { expectsJsonBody: true }),
|
|
529
533
|
],
|
|
530
534
|
},
|
|
531
535
|
{
|
package/src/cli/spec.js
CHANGED
|
@@ -221,6 +221,8 @@ const COMMAND_GROUPS = {
|
|
|
221
221
|
delete: { description: 'Delete a gateway profile', method: 'DELETE', path: '/gateways/:id', params: ['id'] },
|
|
222
222
|
health: { description: 'Run a gateway health check', method: 'GET', path: '/gateways/:id/health', params: ['id'] },
|
|
223
223
|
topology: { description: 'Refresh and return one gateway topology snapshot', method: 'GET', path: '/gateways/:id/topology', params: ['id'] },
|
|
224
|
+
environments: { description: 'List OpenClaw gateway execution environments', method: 'GET', path: '/gateways/:id/environments', params: ['id'] },
|
|
225
|
+
'environment-status': { description: 'Get one OpenClaw gateway execution environment status', method: 'GET', path: '/gateways/:id/environments/:environmentId', params: ['id', 'environmentId'] },
|
|
224
226
|
fleet: { description: 'Refresh and return fleet-wide gateway topology', method: 'GET', path: '/gateways/fleet' },
|
|
225
227
|
},
|
|
226
228
|
},
|
|
@@ -386,6 +388,8 @@ const COMMAND_GROUPS = {
|
|
|
386
388
|
install: { description: 'Install extension by URL', method: 'POST', path: '/extensions/install' },
|
|
387
389
|
'settings-get': { description: 'Read extension settings (supports --query extensionId=...)', method: 'GET', path: '/extensions/settings' },
|
|
388
390
|
'settings-set': { description: 'Write extension settings (supports --query extensionId=... and --data JSON)', method: 'PUT', path: '/extensions/settings' },
|
|
391
|
+
'managed-resources': { description: 'Preview extension-managed agents, routines, folders, gateways, and setup checks', method: 'GET', path: '/extensions/managed-resources' },
|
|
392
|
+
'managed-resources-action': { description: 'Reconcile or inspect extension-managed resources', method: 'POST', path: '/extensions/managed-resources' },
|
|
389
393
|
},
|
|
390
394
|
},
|
|
391
395
|
providers: {
|