openclaw-swarm-layer 0.1.0
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/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/src/cli/context.d.ts +18 -0
- package/dist/src/cli/context.js +60 -0
- package/dist/src/cli/output.d.ts +1 -0
- package/dist/src/cli/output.js +9 -0
- package/dist/src/cli/register-swarm-cli.d.ts +6 -0
- package/dist/src/cli/register-swarm-cli.js +130 -0
- package/dist/src/cli/swarm-doctor.d.ts +40 -0
- package/dist/src/cli/swarm-doctor.js +69 -0
- package/dist/src/cli/swarm-init.d.ts +9 -0
- package/dist/src/cli/swarm-init.js +10 -0
- package/dist/src/cli/swarm-plan.d.ts +13 -0
- package/dist/src/cli/swarm-plan.js +39 -0
- package/dist/src/cli/swarm-report.d.ts +9 -0
- package/dist/src/cli/swarm-report.js +14 -0
- package/dist/src/cli/swarm-review.d.ts +8 -0
- package/dist/src/cli/swarm-review.js +34 -0
- package/dist/src/cli/swarm-run.d.ts +7 -0
- package/dist/src/cli/swarm-run.js +39 -0
- package/dist/src/cli/swarm-session-cancel.d.ts +6 -0
- package/dist/src/cli/swarm-session-cancel.js +64 -0
- package/dist/src/cli/swarm-session-cleanup.d.ts +16 -0
- package/dist/src/cli/swarm-session-cleanup.js +34 -0
- package/dist/src/cli/swarm-session-close.d.ts +6 -0
- package/dist/src/cli/swarm-session-close.js +53 -0
- package/dist/src/cli/swarm-session-followup.d.ts +7 -0
- package/dist/src/cli/swarm-session-followup.js +63 -0
- package/dist/src/cli/swarm-session-inspect.d.ts +5 -0
- package/dist/src/cli/swarm-session-inspect.js +12 -0
- package/dist/src/cli/swarm-session-list.d.ts +4 -0
- package/dist/src/cli/swarm-session-list.js +19 -0
- package/dist/src/cli/swarm-session-status.d.ts +5 -0
- package/dist/src/cli/swarm-session-status.js +85 -0
- package/dist/src/cli/swarm-session-steer.d.ts +6 -0
- package/dist/src/cli/swarm-session-steer.js +40 -0
- package/dist/src/cli/swarm-status.d.ts +81 -0
- package/dist/src/cli/swarm-status.js +56 -0
- package/dist/src/config.d.ts +159 -0
- package/dist/src/config.js +292 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +24 -0
- package/dist/src/lib/json-file.d.ts +5 -0
- package/dist/src/lib/json-file.js +42 -0
- package/dist/src/lib/paths.d.ts +25 -0
- package/dist/src/lib/paths.js +41 -0
- package/dist/src/planning/planner.d.ts +3 -0
- package/dist/src/planning/planner.js +39 -0
- package/dist/src/planning/task-graph.d.ts +8 -0
- package/dist/src/planning/task-graph.js +59 -0
- package/dist/src/reporting/obsidian-journal.d.ts +7 -0
- package/dist/src/reporting/obsidian-journal.js +126 -0
- package/dist/src/reporting/operator-summary.d.ts +32 -0
- package/dist/src/reporting/operator-summary.js +124 -0
- package/dist/src/reporting/reporter.d.ts +10 -0
- package/dist/src/reporting/reporter.js +128 -0
- package/dist/src/review/review-gate.d.ts +15 -0
- package/dist/src/review/review-gate.js +116 -0
- package/dist/src/runtime/acp-mapping.d.ts +23 -0
- package/dist/src/runtime/acp-mapping.js +50 -0
- package/dist/src/runtime/acp-runner.d.ts +11 -0
- package/dist/src/runtime/acp-runner.js +83 -0
- package/dist/src/runtime/bridge-errors.d.ts +8 -0
- package/dist/src/runtime/bridge-errors.js +59 -0
- package/dist/src/runtime/bridge-manifest.d.ts +30 -0
- package/dist/src/runtime/bridge-manifest.js +87 -0
- package/dist/src/runtime/bridge-openclaw-session-adapter.d.ts +48 -0
- package/dist/src/runtime/bridge-openclaw-session-adapter.js +142 -0
- package/dist/src/runtime/bridge-openclaw-subagent-adapter.d.ts +33 -0
- package/dist/src/runtime/bridge-openclaw-subagent-adapter.js +149 -0
- package/dist/src/runtime/manual-runner.d.ts +9 -0
- package/dist/src/runtime/manual-runner.js +53 -0
- package/dist/src/runtime/openclaw-exec-bridge.d.ts +211 -0
- package/dist/src/runtime/openclaw-exec-bridge.js +498 -0
- package/dist/src/runtime/openclaw-session-adapter.d.ts +48 -0
- package/dist/src/runtime/openclaw-session-adapter.js +14 -0
- package/dist/src/runtime/openclaw-subagent-adapter.d.ts +42 -0
- package/dist/src/runtime/openclaw-subagent-adapter.js +11 -0
- package/dist/src/runtime/public-api-seams.d.ts +23 -0
- package/dist/src/runtime/public-api-seams.js +79 -0
- package/dist/src/runtime/real-openclaw-session-adapter.d.ts +83 -0
- package/dist/src/runtime/real-openclaw-session-adapter.js +91 -0
- package/dist/src/runtime/retry-engine.d.ts +7 -0
- package/dist/src/runtime/retry-engine.js +29 -0
- package/dist/src/runtime/runner-registry.d.ts +6 -0
- package/dist/src/runtime/runner-registry.js +25 -0
- package/dist/src/runtime/session-sync.d.ts +9 -0
- package/dist/src/runtime/session-sync.js +165 -0
- package/dist/src/runtime/subagent-mapping.d.ts +9 -0
- package/dist/src/runtime/subagent-mapping.js +31 -0
- package/dist/src/runtime/subagent-runner.d.ts +9 -0
- package/dist/src/runtime/subagent-runner.js +63 -0
- package/dist/src/runtime/task-runner.d.ts +38 -0
- package/dist/src/runtime/task-runner.js +1 -0
- package/dist/src/schemas/run.schema.json +51 -0
- package/dist/src/schemas/spec.schema.json +30 -0
- package/dist/src/schemas/task.schema.json +48 -0
- package/dist/src/schemas/workflow-state.schema.json +46 -0
- package/dist/src/services/orchestrator.d.ts +47 -0
- package/dist/src/services/orchestrator.js +224 -0
- package/dist/src/session/session-lifecycle.d.ts +6 -0
- package/dist/src/session/session-lifecycle.js +84 -0
- package/dist/src/session/session-selector.d.ts +12 -0
- package/dist/src/session/session-selector.js +72 -0
- package/dist/src/session/session-store.d.ts +14 -0
- package/dist/src/session/session-store.js +84 -0
- package/dist/src/spec/spec-importer.d.ts +4 -0
- package/dist/src/spec/spec-importer.js +80 -0
- package/dist/src/state/state-store.d.ts +22 -0
- package/dist/src/state/state-store.js +187 -0
- package/dist/src/tools/index.d.ts +2 -0
- package/dist/src/tools/index.js +116 -0
- package/dist/src/types.d.ts +151 -0
- package/dist/src/types.js +1 -0
- package/dist/src/workspace/workspace-manager.d.ts +8 -0
- package/dist/src/workspace/workspace-manager.js +18 -0
- package/openclaw.plugin.json +121 -0
- package/package.json +62 -0
- package/scripts/openclaw-exec-bridge.mjs +4 -0
- package/skills/swarm-layer/SKILL.md +358 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { buildPatchedBridgeModuleSource, resolveBridgeCompatibility, resolveInternalModuleSpec, } from "./bridge-manifest.js";
|
|
7
|
+
import { buildMigrationChecklist, buildReplacementPlan, detectPublicApiAvailability } from "./public-api-seams.js";
|
|
8
|
+
export function deriveDoctorRemediation(report) {
|
|
9
|
+
const steps = [];
|
|
10
|
+
const text = report.blockers.join("\n");
|
|
11
|
+
const warningText = report.warnings.join("\n");
|
|
12
|
+
if (/not in bridge allowlist/i.test(text)) {
|
|
13
|
+
steps.push("Update bridge.versionAllow to include the current OpenClaw version, or switch back to a tested version.");
|
|
14
|
+
}
|
|
15
|
+
if (/No internal bridge mapping is registered|mapping is stale/i.test(text)) {
|
|
16
|
+
steps.push("Refresh INTERNAL_MODULES_BY_VERSION for the installed OpenClaw build and re-run swarm doctor.");
|
|
17
|
+
}
|
|
18
|
+
if (/ACP runtime backend is currently unavailable|ACP runtime backend is not configured/i.test(text)) {
|
|
19
|
+
steps.push("Ensure the acpx plugin is enabled, ACP global config is enabled, and rerun `openclaw swarm doctor --json`.");
|
|
20
|
+
}
|
|
21
|
+
if (/subagent helpers|subagent patch/i.test(text)) {
|
|
22
|
+
steps.push("Refresh the bridge patch export list for subagent helpers before using the subagent bridge path.");
|
|
23
|
+
}
|
|
24
|
+
if (/versionAllow is empty/i.test(warningText)) {
|
|
25
|
+
steps.push("Pin bridge.versionAllow to the exact OpenClaw versions you have validated.");
|
|
26
|
+
}
|
|
27
|
+
if (/openclawRoot is not pinned/i.test(warningText)) {
|
|
28
|
+
steps.push("Set bridge.openclawRoot explicitly so bridge execution does not rely on install auto-detection.");
|
|
29
|
+
}
|
|
30
|
+
if (steps.length === 0 && !report.ok) {
|
|
31
|
+
steps.push("Inspect bridge.blockers and bridge.risks, then re-run doctor after correcting configuration or version drift.");
|
|
32
|
+
}
|
|
33
|
+
if (steps.length === 0 && report.ok && report.warnings.length > 0) {
|
|
34
|
+
steps.push("Address bridge warnings to reduce upgrade and compatibility risk before relying on bridge mode broadly.");
|
|
35
|
+
}
|
|
36
|
+
return steps;
|
|
37
|
+
}
|
|
38
|
+
export function deriveDoctorSeverity(report) {
|
|
39
|
+
if (report.blockers.length > 0) {
|
|
40
|
+
return "blocked";
|
|
41
|
+
}
|
|
42
|
+
if (report.warnings.length > 0 || report.risks.length > 0) {
|
|
43
|
+
return "warning";
|
|
44
|
+
}
|
|
45
|
+
return "healthy";
|
|
46
|
+
}
|
|
47
|
+
export function deriveDoctorNextAction(report) {
|
|
48
|
+
if (report.remediation.length > 0) {
|
|
49
|
+
return report.remediation[0];
|
|
50
|
+
}
|
|
51
|
+
if (!report.ok && report.blockers.length > 0) {
|
|
52
|
+
return "Resolve bridge blockers before retrying execution.";
|
|
53
|
+
}
|
|
54
|
+
if (report.ok) {
|
|
55
|
+
return "Bridge checks passed. You can proceed with bridge-backed execution smoke or normal runs.";
|
|
56
|
+
}
|
|
57
|
+
return "Inspect doctor output and resolve blockers before retrying.";
|
|
58
|
+
}
|
|
59
|
+
export function dedupeStrings(values) {
|
|
60
|
+
return [...new Set(values)];
|
|
61
|
+
}
|
|
62
|
+
function getRequire() {
|
|
63
|
+
return createRequire(import.meta.url);
|
|
64
|
+
}
|
|
65
|
+
function resolveOpenClawRoot(override) {
|
|
66
|
+
if (override) {
|
|
67
|
+
return path.resolve(override);
|
|
68
|
+
}
|
|
69
|
+
const require = getRequire();
|
|
70
|
+
const sdkEntryPath = require.resolve("openclaw/plugin-sdk");
|
|
71
|
+
let cursor = path.dirname(sdkEntryPath);
|
|
72
|
+
while (true) {
|
|
73
|
+
const packageJsonPath = path.join(cursor, "package.json");
|
|
74
|
+
if (existsSync(packageJsonPath)) {
|
|
75
|
+
return cursor;
|
|
76
|
+
}
|
|
77
|
+
const parent = path.dirname(cursor);
|
|
78
|
+
if (parent === cursor) {
|
|
79
|
+
throw new Error("Unable to resolve openclaw package root from plugin-sdk entry");
|
|
80
|
+
}
|
|
81
|
+
cursor = parent;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function resolveInternalModule(openclawRoot, versionAllow) {
|
|
85
|
+
const packageJson = JSON.parse(await fs.readFile(path.join(openclawRoot, "package.json"), "utf8"));
|
|
86
|
+
const version = packageJson.version;
|
|
87
|
+
if (versionAllow && versionAllow.length > 0 && !versionAllow.includes(version)) {
|
|
88
|
+
throw new Error(`OpenClaw version ${version} is not in bridge allowlist (${versionAllow.join(", ")})`);
|
|
89
|
+
}
|
|
90
|
+
const spec = resolveInternalModuleSpec(version);
|
|
91
|
+
if (!spec) {
|
|
92
|
+
throw new Error(`No internal bridge mapping is registered for OpenClaw ${version}`);
|
|
93
|
+
}
|
|
94
|
+
const modulePath = path.join(openclawRoot, spec.relativeModulePath);
|
|
95
|
+
const mod = await import(pathToFileURL(modulePath).href);
|
|
96
|
+
const loadConfig = mod[spec.exportAliases.loadConfig];
|
|
97
|
+
const getAcpSessionManager = mod[spec.exportAliases.getAcpSessionManager];
|
|
98
|
+
if (!loadConfig || !getAcpSessionManager) {
|
|
99
|
+
throw new Error(`Internal bridge mapping for OpenClaw ${version} is stale`);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
version,
|
|
103
|
+
spec,
|
|
104
|
+
loadConfig,
|
|
105
|
+
getAcpSessionManager,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function resolvePatchedSubagentSpawner(openclawRoot, spec) {
|
|
109
|
+
const originalModulePath = path.join(openclawRoot, spec.relativeModulePath);
|
|
110
|
+
const patchedModulePath = path.join(openclawRoot, spec.patchedModulePath);
|
|
111
|
+
const source = await fs.readFile(originalModulePath, "utf8");
|
|
112
|
+
await fs.writeFile(patchedModulePath, buildPatchedBridgeModuleSource(source), "utf8");
|
|
113
|
+
const mod = await import(pathToFileURL(patchedModulePath).href);
|
|
114
|
+
const spawnSubagentDirect = mod[spec.patchedSubagentExports.spawn];
|
|
115
|
+
const findLatestSubagentRunByChildSession = mod[spec.patchedSubagentExports.findLatestRun];
|
|
116
|
+
const killSubagentRunByChildSession = mod[spec.patchedSubagentExports.killByChildSession];
|
|
117
|
+
const isSubagentSessionRunActive = mod[spec.patchedSubagentExports.isRunActive];
|
|
118
|
+
if (!spawnSubagentDirect || !findLatestSubagentRunByChildSession || !killSubagentRunByChildSession || !isSubagentSessionRunActive) {
|
|
119
|
+
throw new Error("Patched bridge module did not expose subagent helpers");
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
spawnSubagentDirect,
|
|
123
|
+
findLatestSubagentRunByChildSession,
|
|
124
|
+
killSubagentRunByChildSession,
|
|
125
|
+
isSubagentSessionRunActive,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function mapSubagentRunState(entry, active) {
|
|
129
|
+
if (active) {
|
|
130
|
+
return "running";
|
|
131
|
+
}
|
|
132
|
+
const endedReason = typeof entry.endedReason === "string" ? entry.endedReason : "";
|
|
133
|
+
const outcome = entry.outcome;
|
|
134
|
+
if (endedReason === "killed" || outcome?.error === "killed") {
|
|
135
|
+
return "cancelled";
|
|
136
|
+
}
|
|
137
|
+
if (outcome?.status === "error") {
|
|
138
|
+
return "failed";
|
|
139
|
+
}
|
|
140
|
+
return "completed";
|
|
141
|
+
}
|
|
142
|
+
async function runBridgeDoctor(input) {
|
|
143
|
+
const openclawRoot = resolveOpenClawRoot(input.bridge?.openclawRoot);
|
|
144
|
+
const report = {
|
|
145
|
+
ok: false,
|
|
146
|
+
severity: "blocked",
|
|
147
|
+
openclawRoot,
|
|
148
|
+
compatibility: {
|
|
149
|
+
supportedRunners: [],
|
|
150
|
+
replacementCandidates: [],
|
|
151
|
+
notes: [],
|
|
152
|
+
},
|
|
153
|
+
publicApi: {
|
|
154
|
+
acpControlPlaneExport: false,
|
|
155
|
+
subagentSpawnExport: false,
|
|
156
|
+
readyReplacementPoints: [],
|
|
157
|
+
},
|
|
158
|
+
replacementPlan: [],
|
|
159
|
+
migrationChecklist: [],
|
|
160
|
+
checks: {
|
|
161
|
+
versionMapped: false,
|
|
162
|
+
versionAllowed: false,
|
|
163
|
+
internalModuleResolved: false,
|
|
164
|
+
acpBackendHealthy: false,
|
|
165
|
+
subagentPatchable: false,
|
|
166
|
+
},
|
|
167
|
+
blockers: [],
|
|
168
|
+
warnings: [],
|
|
169
|
+
risks: [
|
|
170
|
+
"bridge mode depends on internal OpenClaw bundle aliases and version pinning",
|
|
171
|
+
"bridge mode is experimental and may break on upstream packaging changes",
|
|
172
|
+
],
|
|
173
|
+
remediation: [],
|
|
174
|
+
nextAction: "Resolve bridge blockers before using bridge-backed execution.",
|
|
175
|
+
};
|
|
176
|
+
if (!input.bridge?.versionAllow || input.bridge.versionAllow.length === 0) {
|
|
177
|
+
report.warnings.push("bridge.versionAllow is empty; version drift risk is high");
|
|
178
|
+
}
|
|
179
|
+
if (!input.bridge?.openclawRoot) {
|
|
180
|
+
report.warnings.push("bridge.openclawRoot is not pinned; bridge will rely on install auto-detection");
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const publicApi = await detectPublicApiAvailability();
|
|
184
|
+
report.publicApi = {
|
|
185
|
+
acpControlPlaneExport: publicApi.acpControlPlaneExport,
|
|
186
|
+
subagentSpawnExport: publicApi.subagentSpawnExport,
|
|
187
|
+
readyReplacementPoints: publicApi.readyReplacementPoints,
|
|
188
|
+
};
|
|
189
|
+
report.replacementPlan = buildReplacementPlan(publicApi);
|
|
190
|
+
report.migrationChecklist = buildMigrationChecklist(report.replacementPlan);
|
|
191
|
+
report.warnings.push(...publicApi.notes);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
report.warnings.push(`Unable to inspect public plugin SDK exports: ${error instanceof Error ? error.message : String(error)}`);
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const packageJson = JSON.parse(await fs.readFile(path.join(openclawRoot, "package.json"), "utf8"));
|
|
198
|
+
report.version = packageJson.version;
|
|
199
|
+
const compatibility = resolveBridgeCompatibility(packageJson.version);
|
|
200
|
+
if (compatibility) {
|
|
201
|
+
report.compatibility = {
|
|
202
|
+
strategy: compatibility.strategy,
|
|
203
|
+
testedAt: compatibility.testedAt,
|
|
204
|
+
supportedRunners: compatibility.supportedRunners,
|
|
205
|
+
replacementCandidates: Object.values(compatibility.replacementCandidates),
|
|
206
|
+
notes: compatibility.notes,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
report.checks.versionAllowed =
|
|
210
|
+
!input.bridge?.versionAllow || input.bridge.versionAllow.length === 0 || input.bridge.versionAllow.includes(packageJson.version);
|
|
211
|
+
if (!report.checks.versionAllowed) {
|
|
212
|
+
report.blockers.push(`OpenClaw version ${packageJson.version} is not in bridge allowlist (${(input.bridge?.versionAllow ?? []).join(", ")})`);
|
|
213
|
+
}
|
|
214
|
+
report.checks.versionMapped = Boolean(resolveInternalModuleSpec(packageJson.version));
|
|
215
|
+
if (!report.checks.versionMapped) {
|
|
216
|
+
report.blockers.push(`No internal bridge mapping is registered for OpenClaw ${packageJson.version}`);
|
|
217
|
+
return report;
|
|
218
|
+
}
|
|
219
|
+
const { loadConfig, spec } = await resolveInternalModule(openclawRoot, input.bridge?.versionAllow);
|
|
220
|
+
report.checks.internalModuleResolved = true;
|
|
221
|
+
const cfg = loadConfig();
|
|
222
|
+
try {
|
|
223
|
+
await ensureAcpxBackendRegistered(openclawRoot, cfg);
|
|
224
|
+
report.checks.acpBackendHealthy = true;
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
report.blockers.push(error instanceof Error ? error.message : String(error));
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
await resolvePatchedSubagentSpawner(openclawRoot, spec);
|
|
231
|
+
report.checks.subagentPatchable = true;
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
report.blockers.push(error instanceof Error ? error.message : String(error));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
report.blockers.push(error instanceof Error ? error.message : String(error));
|
|
239
|
+
}
|
|
240
|
+
report.ok = report.blockers.length === 0;
|
|
241
|
+
report.blockers = dedupeStrings(report.blockers);
|
|
242
|
+
report.warnings = dedupeStrings(report.warnings);
|
|
243
|
+
report.remediation = deriveDoctorRemediation(report);
|
|
244
|
+
report.remediation = dedupeStrings(report.remediation);
|
|
245
|
+
report.severity = deriveDoctorSeverity(report);
|
|
246
|
+
report.nextAction = deriveDoctorNextAction(report);
|
|
247
|
+
return report;
|
|
248
|
+
}
|
|
249
|
+
async function ensureAcpxBackendRegistered(openclawRoot, cfg) {
|
|
250
|
+
const serviceModulePath = pathToFileURL(path.join(openclawRoot, "extensions", "acpx", "src", "service.ts")).href;
|
|
251
|
+
const mod = await import(serviceModulePath);
|
|
252
|
+
const createAcpxRuntimeService = mod.createAcpxRuntimeService;
|
|
253
|
+
if (!createAcpxRuntimeService) {
|
|
254
|
+
throw new Error("Unable to load acpx runtime service for bridge bootstrap");
|
|
255
|
+
}
|
|
256
|
+
const workspaceDir = cfg?.agents?.defaults?.workspace ?? cfg?.workspace ?? path.join(openclawRoot, ".bridge-workspace");
|
|
257
|
+
const service = createAcpxRuntimeService({ pluginConfig: cfg?.plugins?.entries?.acpx?.config });
|
|
258
|
+
await service.start({
|
|
259
|
+
config: cfg,
|
|
260
|
+
workspaceDir,
|
|
261
|
+
stateDir: path.join(workspaceDir, ".openclaw-bridge-state"),
|
|
262
|
+
logger: {
|
|
263
|
+
debug() {
|
|
264
|
+
return;
|
|
265
|
+
},
|
|
266
|
+
info() {
|
|
267
|
+
return;
|
|
268
|
+
},
|
|
269
|
+
warn() {
|
|
270
|
+
return;
|
|
271
|
+
},
|
|
272
|
+
error() {
|
|
273
|
+
return;
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
const registryModulePath = pathToFileURL(path.join(openclawRoot, "dist", "plugin-sdk", "index.js")).href;
|
|
278
|
+
const registryModule = await import(registryModulePath);
|
|
279
|
+
await waitForAcpBackendHealthy(() => registryModule.getAcpRuntimeBackend?.(cfg?.acp?.backend ?? "acpx") ?? null, cfg?.acp?.backend ?? "acpx");
|
|
280
|
+
}
|
|
281
|
+
export async function waitForAcpBackendHealthy(getBackend, backendId, timeoutMs = 15_000, intervalMs = 250) {
|
|
282
|
+
const startedAt = Date.now();
|
|
283
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
284
|
+
const backend = getBackend();
|
|
285
|
+
if (backend && (!backend.healthy || backend.healthy())) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`ACP runtime backend is currently unavailable. Try again in a moment. (backend: ${backendId})`);
|
|
291
|
+
}
|
|
292
|
+
function mapManagerState(state) {
|
|
293
|
+
if (state === "running") {
|
|
294
|
+
return "running";
|
|
295
|
+
}
|
|
296
|
+
if (state === "error") {
|
|
297
|
+
return "failed";
|
|
298
|
+
}
|
|
299
|
+
return "completed";
|
|
300
|
+
}
|
|
301
|
+
async function handleAcp(command, input) {
|
|
302
|
+
const openclawRoot = resolveOpenClawRoot(input.bridge?.openclawRoot);
|
|
303
|
+
if (command === "doctor") {
|
|
304
|
+
const report = await runBridgeDoctor(input);
|
|
305
|
+
return {
|
|
306
|
+
ok: true,
|
|
307
|
+
version: report.version ?? "unknown",
|
|
308
|
+
result: report,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const { loadConfig, getAcpSessionManager, version, spec } = await resolveInternalModule(openclawRoot, input.bridge?.versionAllow);
|
|
312
|
+
const cfg = loadConfig();
|
|
313
|
+
const params = input.params ?? {};
|
|
314
|
+
if (command === "subagent-spawn") {
|
|
315
|
+
const subagentHelpers = await resolvePatchedSubagentSpawner(openclawRoot, spec);
|
|
316
|
+
const subagentParams = params;
|
|
317
|
+
const result = await subagentHelpers.spawnSubagentDirect({
|
|
318
|
+
task: subagentParams.task,
|
|
319
|
+
label: subagentParams.label,
|
|
320
|
+
agentId: subagentParams.agentId,
|
|
321
|
+
mode: subagentParams.mode,
|
|
322
|
+
thread: subagentParams.thread,
|
|
323
|
+
runTimeoutSeconds: subagentParams.runTimeoutSeconds,
|
|
324
|
+
expectsCompletionMessage: false,
|
|
325
|
+
}, {
|
|
326
|
+
requesterAgentIdOverride: "main",
|
|
327
|
+
});
|
|
328
|
+
if (result.status !== "accepted") {
|
|
329
|
+
throw new Error(result.error ? String(result.error) : `Subagent spawn failed with status ${String(result.status)}`);
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
ok: true,
|
|
333
|
+
version,
|
|
334
|
+
result: {
|
|
335
|
+
childSessionKey: result.childSessionKey,
|
|
336
|
+
runId: result.runId,
|
|
337
|
+
mode: result.mode ?? subagentParams.mode ?? "run",
|
|
338
|
+
acceptedAt: new Date().toISOString(),
|
|
339
|
+
note: result.note,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
if (command === "subagent-status") {
|
|
344
|
+
const subagentHelpers = await resolvePatchedSubagentSpawner(openclawRoot, spec);
|
|
345
|
+
const statusParams = params;
|
|
346
|
+
const entry = subagentHelpers.findLatestSubagentRunByChildSession(statusParams.childSessionKey);
|
|
347
|
+
if (!entry) {
|
|
348
|
+
throw new Error(`Subagent run not found for ${statusParams.childSessionKey}`);
|
|
349
|
+
}
|
|
350
|
+
const active = subagentHelpers.isSubagentSessionRunActive(statusParams.childSessionKey);
|
|
351
|
+
return {
|
|
352
|
+
ok: true,
|
|
353
|
+
version,
|
|
354
|
+
result: {
|
|
355
|
+
childSessionKey: statusParams.childSessionKey,
|
|
356
|
+
runId: typeof entry.runId === "string" ? entry.runId : undefined,
|
|
357
|
+
state: mapSubagentRunState(entry, active),
|
|
358
|
+
checkedAt: new Date().toISOString(),
|
|
359
|
+
message: typeof entry.outcome?.error === "string"
|
|
360
|
+
? entry.outcome.error
|
|
361
|
+
: typeof entry.frozenResultText === "string"
|
|
362
|
+
? entry.frozenResultText
|
|
363
|
+
: undefined,
|
|
364
|
+
outputText: typeof entry.frozenResultText === "string" ? entry.frozenResultText : undefined,
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (command === "subagent-kill") {
|
|
369
|
+
const subagentHelpers = await resolvePatchedSubagentSpawner(openclawRoot, spec);
|
|
370
|
+
const killParams = params;
|
|
371
|
+
const entry = subagentHelpers.findLatestSubagentRunByChildSession(killParams.childSessionKey);
|
|
372
|
+
if (!entry) {
|
|
373
|
+
throw new Error(`Subagent run not found for ${killParams.childSessionKey}`);
|
|
374
|
+
}
|
|
375
|
+
await subagentHelpers.killSubagentRunByChildSession(cfg, killParams.childSessionKey);
|
|
376
|
+
return {
|
|
377
|
+
ok: true,
|
|
378
|
+
version,
|
|
379
|
+
result: {
|
|
380
|
+
childSessionKey: killParams.childSessionKey,
|
|
381
|
+
killedAt: new Date().toISOString(),
|
|
382
|
+
message: killParams.reason ?? "killed",
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
await ensureAcpxBackendRegistered(openclawRoot, cfg);
|
|
387
|
+
const manager = getAcpSessionManager();
|
|
388
|
+
if (command === "acp-spawn") {
|
|
389
|
+
const spawnParams = params;
|
|
390
|
+
const initialized = await manager.initializeSession({
|
|
391
|
+
cfg,
|
|
392
|
+
sessionKey: spawnParams.sessionKey,
|
|
393
|
+
agent: spawnParams.agentId,
|
|
394
|
+
mode: spawnParams.mode === "session" ? "persistent" : "oneshot",
|
|
395
|
+
cwd: spawnParams.cwd,
|
|
396
|
+
backendId: spawnParams.backendId,
|
|
397
|
+
});
|
|
398
|
+
void manager.runTurn({
|
|
399
|
+
cfg,
|
|
400
|
+
sessionKey: spawnParams.sessionKey,
|
|
401
|
+
text: spawnParams.task,
|
|
402
|
+
mode: "prompt",
|
|
403
|
+
requestId: spawnParams.requestId,
|
|
404
|
+
});
|
|
405
|
+
return {
|
|
406
|
+
ok: true,
|
|
407
|
+
version,
|
|
408
|
+
result: {
|
|
409
|
+
sessionKey: spawnParams.sessionKey,
|
|
410
|
+
backend: initialized.handle.backend,
|
|
411
|
+
backendSessionId: initialized.handle.backendSessionId,
|
|
412
|
+
agentSessionId: initialized.handle.agentSessionId,
|
|
413
|
+
acceptedAt: new Date().toISOString(),
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (command === "acp-status") {
|
|
418
|
+
const statusParams = params;
|
|
419
|
+
const status = await manager.getSessionStatus({ cfg, sessionKey: statusParams.sessionKey });
|
|
420
|
+
return {
|
|
421
|
+
ok: true,
|
|
422
|
+
version,
|
|
423
|
+
result: {
|
|
424
|
+
sessionKey: statusParams.sessionKey,
|
|
425
|
+
state: mapManagerState(status.state),
|
|
426
|
+
backend: status.backend,
|
|
427
|
+
backendSessionId: status.runtimeStatus?.backendSessionId ?? status.identity?.acpxSessionId,
|
|
428
|
+
agentSessionId: status.runtimeStatus?.agentSessionId ?? status.identity?.agentSessionId,
|
|
429
|
+
checkedAt: new Date().toISOString(),
|
|
430
|
+
message: status.lastError ?? status.runtimeStatus?.summary,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
if (command === "acp-cancel") {
|
|
435
|
+
const cancelParams = params;
|
|
436
|
+
await manager.cancelSession({ cfg, sessionKey: cancelParams.sessionKey, reason: cancelParams.reason });
|
|
437
|
+
return {
|
|
438
|
+
ok: true,
|
|
439
|
+
version,
|
|
440
|
+
result: {
|
|
441
|
+
sessionKey: cancelParams.sessionKey,
|
|
442
|
+
cancelledAt: new Date().toISOString(),
|
|
443
|
+
message: cancelParams.reason,
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (command === "acp-close") {
|
|
448
|
+
const closeParams = params;
|
|
449
|
+
const closed = await manager.closeSession({
|
|
450
|
+
cfg,
|
|
451
|
+
sessionKey: closeParams.sessionKey,
|
|
452
|
+
reason: closeParams.reason ?? "closed by swarm bridge",
|
|
453
|
+
});
|
|
454
|
+
return {
|
|
455
|
+
ok: true,
|
|
456
|
+
version,
|
|
457
|
+
result: {
|
|
458
|
+
sessionKey: closeParams.sessionKey,
|
|
459
|
+
closedAt: new Date().toISOString(),
|
|
460
|
+
message: closed.runtimeNotice ?? closeParams.reason,
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
throw new Error(`Unsupported bridge command: ${command}`);
|
|
465
|
+
}
|
|
466
|
+
export async function runBridgeCommand(command, input) {
|
|
467
|
+
return handleAcp(command, input);
|
|
468
|
+
}
|
|
469
|
+
async function readStdin() {
|
|
470
|
+
const chunks = [];
|
|
471
|
+
for await (const chunk of process.stdin) {
|
|
472
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
473
|
+
}
|
|
474
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
475
|
+
}
|
|
476
|
+
export async function main(argv) {
|
|
477
|
+
const command = argv[2];
|
|
478
|
+
if (!command) {
|
|
479
|
+
process.stderr.write("Missing bridge command\n");
|
|
480
|
+
return 1;
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const rawInput = await readStdin();
|
|
484
|
+
const parsed = rawInput.trim().length > 0 ? JSON.parse(rawInput) : {};
|
|
485
|
+
const result = await runBridgeCommand(command, parsed);
|
|
486
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
487
|
+
return 0;
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
491
|
+
return 1;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const isMain = process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
|
|
495
|
+
if (isMain) {
|
|
496
|
+
const code = await main(process.argv);
|
|
497
|
+
process.exitCode = code;
|
|
498
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AcpSpawnParams } from "./acp-mapping.js";
|
|
2
|
+
export type AcpAcceptedSession = {
|
|
3
|
+
sessionKey: string;
|
|
4
|
+
backend?: string;
|
|
5
|
+
backendSessionId?: string;
|
|
6
|
+
agentSessionId?: string;
|
|
7
|
+
acceptedAt?: string;
|
|
8
|
+
outputText?: string;
|
|
9
|
+
threadId?: string;
|
|
10
|
+
};
|
|
11
|
+
export type AcpSessionStatus = {
|
|
12
|
+
sessionKey: string;
|
|
13
|
+
state: "accepted" | "running" | "completed" | "failed" | "cancelled" | "timed_out";
|
|
14
|
+
backend?: string;
|
|
15
|
+
backendSessionId?: string;
|
|
16
|
+
agentSessionId?: string;
|
|
17
|
+
checkedAt?: string;
|
|
18
|
+
message?: string;
|
|
19
|
+
outputText?: string;
|
|
20
|
+
};
|
|
21
|
+
export interface OpenClawSessionAdapter {
|
|
22
|
+
spawnAcpSession(params: AcpSpawnParams): Promise<AcpAcceptedSession>;
|
|
23
|
+
getAcpSessionStatus(sessionKey: string): Promise<AcpSessionStatus>;
|
|
24
|
+
cancelAcpSession(sessionKey: string, reason?: string): Promise<{
|
|
25
|
+
sessionKey: string;
|
|
26
|
+
cancelledAt?: string;
|
|
27
|
+
message?: string;
|
|
28
|
+
}>;
|
|
29
|
+
closeAcpSession(sessionKey: string, reason?: string): Promise<{
|
|
30
|
+
sessionKey: string;
|
|
31
|
+
closedAt?: string;
|
|
32
|
+
message?: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export declare class UnsupportedOpenClawSessionAdapter implements OpenClawSessionAdapter {
|
|
36
|
+
spawnAcpSession(_params: AcpSpawnParams): Promise<AcpAcceptedSession>;
|
|
37
|
+
getAcpSessionStatus(_sessionKey: string): Promise<AcpSessionStatus>;
|
|
38
|
+
cancelAcpSession(_sessionKey: string, _reason?: string): Promise<{
|
|
39
|
+
sessionKey: string;
|
|
40
|
+
cancelledAt?: string;
|
|
41
|
+
message?: string;
|
|
42
|
+
}>;
|
|
43
|
+
closeAcpSession(_sessionKey: string, _reason?: string): Promise<{
|
|
44
|
+
sessionKey: string;
|
|
45
|
+
closedAt?: string;
|
|
46
|
+
message?: string;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class UnsupportedOpenClawSessionAdapter {
|
|
2
|
+
async spawnAcpSession(_params) {
|
|
3
|
+
throw new Error("ACP execution is not wired to a public OpenClaw session adapter yet. Finish M2.0 T2 before enabling real ACP runs.");
|
|
4
|
+
}
|
|
5
|
+
async getAcpSessionStatus(_sessionKey) {
|
|
6
|
+
throw new Error("ACP session status is not wired to a public OpenClaw session adapter yet. Finish M2.0 T5 before enabling operator status sync.");
|
|
7
|
+
}
|
|
8
|
+
async cancelAcpSession(_sessionKey, _reason) {
|
|
9
|
+
throw new Error("ACP session cancel is not wired to a public OpenClaw session adapter yet. Finish M2.0 T7 before enabling operator cancel.");
|
|
10
|
+
}
|
|
11
|
+
async closeAcpSession(_sessionKey, _reason) {
|
|
12
|
+
throw new Error("ACP session close is not wired to a public OpenClaw session adapter yet. Finish M2.0 T7 before enabling operator close.");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type SubagentAcceptedRun = {
|
|
2
|
+
childSessionKey: string;
|
|
3
|
+
runId: string;
|
|
4
|
+
mode: "run" | "session";
|
|
5
|
+
acceptedAt?: string;
|
|
6
|
+
note?: string;
|
|
7
|
+
outputText?: string;
|
|
8
|
+
};
|
|
9
|
+
export type SubagentRunStatus = {
|
|
10
|
+
childSessionKey: string;
|
|
11
|
+
runId?: string;
|
|
12
|
+
state: "accepted" | "running" | "completed" | "failed" | "cancelled";
|
|
13
|
+
checkedAt?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
outputText?: string;
|
|
16
|
+
};
|
|
17
|
+
export type SubagentSpawnParams = {
|
|
18
|
+
task: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
agentId?: string;
|
|
21
|
+
mode: "run" | "session";
|
|
22
|
+
thread: boolean;
|
|
23
|
+
runTimeoutSeconds?: number;
|
|
24
|
+
};
|
|
25
|
+
export interface OpenClawSubagentAdapter {
|
|
26
|
+
spawnSubagent(params: SubagentSpawnParams): Promise<SubagentAcceptedRun>;
|
|
27
|
+
getSubagentRunStatus(childSessionKey: string): Promise<SubagentRunStatus>;
|
|
28
|
+
killSubagentRun(childSessionKey: string, reason?: string): Promise<{
|
|
29
|
+
childSessionKey: string;
|
|
30
|
+
killedAt?: string;
|
|
31
|
+
message?: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export declare class UnsupportedOpenClawSubagentAdapter implements OpenClawSubagentAdapter {
|
|
35
|
+
spawnSubagent(_params: SubagentSpawnParams): Promise<SubagentAcceptedRun>;
|
|
36
|
+
getSubagentRunStatus(_childSessionKey: string): Promise<SubagentRunStatus>;
|
|
37
|
+
killSubagentRun(_childSessionKey: string, _reason?: string): Promise<{
|
|
38
|
+
childSessionKey: string;
|
|
39
|
+
killedAt?: string;
|
|
40
|
+
message?: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export class UnsupportedOpenClawSubagentAdapter {
|
|
2
|
+
async spawnSubagent(_params) {
|
|
3
|
+
throw new Error("OpenClaw public plugin SDK does not expose a stable subagent spawn surface for plugins yet. A real subagent runner would require a future public export or an intentional private deep-import experiment.");
|
|
4
|
+
}
|
|
5
|
+
async getSubagentRunStatus(_childSessionKey) {
|
|
6
|
+
throw new Error("OpenClaw public plugin SDK does not expose a stable subagent status surface for plugins yet. A real subagent runner would require a future public export or an intentional private deep-import experiment.");
|
|
7
|
+
}
|
|
8
|
+
async killSubagentRun(_childSessionKey, _reason) {
|
|
9
|
+
throw new Error("OpenClaw public plugin SDK does not expose a stable subagent kill surface for plugins yet. A real subagent runner would require a future public export or an intentional private deep-import experiment.");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type PublicApiAvailability = {
|
|
2
|
+
acpControlPlaneExport: boolean;
|
|
3
|
+
subagentSpawnExport: boolean;
|
|
4
|
+
readyReplacementPoints: string[];
|
|
5
|
+
notes: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare const ACP_PUBLIC_REPLACEMENT_EXPORT = "getAcpSessionManager";
|
|
8
|
+
export declare const SUBAGENT_PUBLIC_REPLACEMENT_EXPORT = "spawnSubagentDirect";
|
|
9
|
+
export type ReplacementPlanItem = {
|
|
10
|
+
runner: "acp" | "subagent";
|
|
11
|
+
publicExport: string;
|
|
12
|
+
available: boolean;
|
|
13
|
+
status: "ready" | "blocked";
|
|
14
|
+
currentImplementation: string;
|
|
15
|
+
targetImplementation: string;
|
|
16
|
+
affectedModules: string[];
|
|
17
|
+
nextStep: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function buildMigrationChecklist(plan: ReplacementPlanItem[]): string[];
|
|
20
|
+
type SdkExports = Record<string, unknown>;
|
|
21
|
+
export declare function detectPublicApiAvailability(sdkLoader?: () => Promise<SdkExports>): Promise<PublicApiAvailability>;
|
|
22
|
+
export declare function buildReplacementPlan(availability: PublicApiAvailability): ReplacementPlanItem[];
|
|
23
|
+
export {};
|