@jack200714/mafw 4.10.1 → 4.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gateway/dist/index.js +211 -348
- package/gateway/dist/opencode-adapter.js +19 -4
- package/gateway/dist/routes/registry.js +125 -0
- package/gateway/dist/routes/route-catalog.js +192 -0
- package/gateway/dist/routes/triage-dismiss.js +32 -0
- package/gateway/dist/routes/wave1-handlers.js +62 -0
- package/gateway/dist/routes/wave2-handlers.js +47 -0
- package/gateway/dist/runtime/opencode-runtime.js +18 -2
- package/gateway/dist/runtime/serve-supervisor.js +19 -32
- package/gateway/package.json +1 -0
- package/package.json +1 -1
- package/packages/tui/dist/cli.js +96 -51
package/gateway/dist/index.js
CHANGED
|
@@ -97,7 +97,6 @@ const push_gateway_1 = require("./mobile/push-gateway");
|
|
|
97
97
|
const device_store_1 = require("./mobile/device-store");
|
|
98
98
|
const pairing_1 = require("./mobile/pairing");
|
|
99
99
|
const tray_1 = require("./tray");
|
|
100
|
-
const serve_sidecar_1 = require("./runtime/serve-sidecar");
|
|
101
100
|
const self_update_1 = require("./self-update");
|
|
102
101
|
const step_inject_1 = require("./recall/step-inject");
|
|
103
102
|
const inject_format_1 = require("./recall/inject-format");
|
|
@@ -110,24 +109,15 @@ const validate_1 = require("./runtime/validate");
|
|
|
110
109
|
const loader_1 = require("./runtime/loader");
|
|
111
110
|
const pi_runtime_1 = require("./runtime/plugins/pi-runtime");
|
|
112
111
|
const permission_1 = require("./routes/permission");
|
|
113
|
-
const event_publish_1 = require("./routes/event-publish");
|
|
114
|
-
const runtime_switch_1 = require("./routes/runtime-switch");
|
|
115
|
-
const plugins_1 = require("./routes/plugins");
|
|
116
112
|
const package_host_1 = require("./plugins/package-host");
|
|
117
113
|
const package_context_1 = require("./plugins/package-context");
|
|
118
|
-
const hub_1 = require("./plugins/hub");
|
|
119
|
-
const restart_agent_1 = require("./routes/restart-agent");
|
|
120
|
-
const session_mutations_1 = require("./routes/session-mutations");
|
|
121
114
|
const serve_supervisor_1 = require("./runtime/serve-supervisor");
|
|
122
115
|
const serve_for_runtime_1 = require("./runtime/serve-for-runtime");
|
|
123
|
-
const media_switch_1 = require("./routes/media-switch");
|
|
124
|
-
const usage_plugins_1 = require("./routes/usage-plugins");
|
|
125
116
|
const model_stats_1 = require("./usage/model-stats");
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const embedding_config_1 = require("./routes/embedding-config");
|
|
117
|
+
const registry_1 = require("./routes/registry");
|
|
118
|
+
const route_catalog_1 = require("./routes/route-catalog");
|
|
119
|
+
const wave1_handlers_1 = require("./routes/wave1-handlers");
|
|
120
|
+
const wave2_handlers_1 = require("./routes/wave2-handlers");
|
|
131
121
|
const manager_rotate_1 = require("./routes/manager-rotate");
|
|
132
122
|
function readBody(req) {
|
|
133
123
|
return new Promise((resolve, reject) => {
|
|
@@ -228,6 +218,12 @@ class MafwScheduler {
|
|
|
228
218
|
mcpEndpoint;
|
|
229
219
|
mcpStreamableEndpoint;
|
|
230
220
|
opencodeClient = null;
|
|
221
|
+
/**
|
|
222
|
+
* P4 shadow registry + P5 Wave 1 dispatch:catalog 全量登记(130 条,贡献 spec),
|
|
223
|
+
* Wave 1 已 attach 的 22 条在请求链顶端优先 dispatch(见 startApiServer),
|
|
224
|
+
* 未 attach 的 shadow 条目自然落回 legacy 内联链。
|
|
225
|
+
*/
|
|
226
|
+
routeRegistry = new registry_1.RouteRegistry().register(...(0, route_catalog_1.buildRouteCatalog)());
|
|
231
227
|
runtimeCaps = (0, contract_1.minimalCapabilities)();
|
|
232
228
|
runtimeName = 'opencode';
|
|
233
229
|
runtimeLoader;
|
|
@@ -272,51 +268,34 @@ class MafwScheduler {
|
|
|
272
268
|
this.registryPath = config_1.config.paths.registryFile;
|
|
273
269
|
this.chatSessions = new chat_sessions_1.ChatSessionManager();
|
|
274
270
|
this.serveSupervisor = (0, serve_supervisor_1.createServeSupervisor)({
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
271
|
+
// 全部经 runtime 契约且 late-bound(runtime 可热切换):
|
|
272
|
+
// managed = runtime 是否持有启停原语;health = 契约 healthCheck;
|
|
273
|
+
// baseUrl/killServe = runtime 自报地址与清场原语;spawn 不传 host/port。
|
|
274
|
+
managed: () => !!this.opencodeClient?.agentProcess?.spawnServe,
|
|
275
|
+
health: async () => {
|
|
276
|
+
try {
|
|
277
|
+
return (await this.opencodeClient?.healthCheck?.()) ?? false;
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
baseUrl: () => this.opencodeClient?.getBaseUrl?.() ?? this.serveUrl,
|
|
284
|
+
killServe: () => this.opencodeClient?.agentProcess?.killServe?.(),
|
|
280
285
|
spawn: async (opts) => {
|
|
281
286
|
const spawnServe = this.opencodeClient?.agentProcess?.spawnServe;
|
|
282
287
|
if (!spawnServe) {
|
|
283
288
|
throw new Error('active runtime does not own a server process (agentProcess.spawnServe missing)');
|
|
284
289
|
}
|
|
285
|
-
const sidecar = await spawnServe({
|
|
286
|
-
host: opts.host,
|
|
287
|
-
port: opts.port,
|
|
288
|
-
timeoutMs: opts.timeoutMs,
|
|
289
|
-
});
|
|
290
|
+
const sidecar = await spawnServe({ timeoutMs: opts.timeoutMs });
|
|
290
291
|
return { url: sidecar.url, close: () => sidecar.close() };
|
|
291
292
|
},
|
|
292
|
-
probe: async (url) => {
|
|
293
|
-
try {
|
|
294
|
-
const res = await fetch(`${url}/global/health`, { signal: AbortSignal.timeout(3000) });
|
|
295
|
-
return res.ok;
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
},
|
|
301
293
|
});
|
|
302
294
|
}
|
|
303
295
|
get serveRunning() {
|
|
304
296
|
return !!this.serveInstance;
|
|
305
297
|
}
|
|
306
298
|
/** Check actual serve health via TCP connection, not just object existence. */
|
|
307
|
-
async checkServeActualHealth() {
|
|
308
|
-
try {
|
|
309
|
-
const url = `${this.serveUrl}/global/health`;
|
|
310
|
-
const res = await fetch(url, {
|
|
311
|
-
signal: AbortSignal.timeout(2000),
|
|
312
|
-
headers: { 'User-Agent': 'MAFW-Gateway-HealthCheck' }
|
|
313
|
-
});
|
|
314
|
-
return res.ok;
|
|
315
|
-
}
|
|
316
|
-
catch {
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
299
|
async start() {
|
|
321
300
|
logger_1.log.info('MAFW Scheduler v5.0 starting...');
|
|
322
301
|
// Single-instance guard: if a healthy gateway already owns apiPort, this
|
|
@@ -433,7 +412,8 @@ class MafwScheduler {
|
|
|
433
412
|
}
|
|
434
413
|
else {
|
|
435
414
|
logger_1.log.info('OpenCode Serve not reachable, checking for stale process...');
|
|
436
|
-
|
|
415
|
+
// 清场原语归 runtime(serve 端口是实现细节;无原语的 runtime 无从清场)
|
|
416
|
+
this.opencodeClient?.agentProcess?.killServe?.();
|
|
437
417
|
try {
|
|
438
418
|
await this.startServe();
|
|
439
419
|
serveReady = !!this.serveInstance;
|
|
@@ -1279,6 +1259,120 @@ class MafwScheduler {
|
|
|
1279
1259
|
const result = await rt.indexer.backfill(ids);
|
|
1280
1260
|
return { ...result, vectors: rt.vectors.size(), indexEntries: entries.length };
|
|
1281
1261
|
}
|
|
1262
|
+
/** P5 Wave 2:/api/runtime 路由 deps(自内联块上移,行为逐字节等价)。 */
|
|
1263
|
+
runtimeDeps() {
|
|
1264
|
+
return {
|
|
1265
|
+
loader: this.runtimeLoader,
|
|
1266
|
+
persist: (o) => config_1.config.persistOverrides(o),
|
|
1267
|
+
getCurrent: () => this.opencodeClient,
|
|
1268
|
+
runtimeName: () => this.runtimeName,
|
|
1269
|
+
runtimeCaps: () => this.runtimeCaps,
|
|
1270
|
+
envOverride: () => !!process.env.MAFW_RUNTIME_PLUGIN,
|
|
1271
|
+
createRuntime: async () => {
|
|
1272
|
+
const sdkConfig = {
|
|
1273
|
+
baseUrl: this.serveUrl,
|
|
1274
|
+
directory: this.projectDir,
|
|
1275
|
+
headers: {},
|
|
1276
|
+
};
|
|
1277
|
+
const opencodePassword = process.env.MAFW_OPENCODE_PASSWORD;
|
|
1278
|
+
if (opencodePassword) {
|
|
1279
|
+
sdkConfig.headers = { Authorization: 'Basic ' + Buffer.from(`opencode:${opencodePassword}`).toString('base64') };
|
|
1280
|
+
}
|
|
1281
|
+
return this.createRuntime(sdkConfig);
|
|
1282
|
+
},
|
|
1283
|
+
onSwitched: async (rt, prev) => {
|
|
1284
|
+
this.opencodeClient = rt;
|
|
1285
|
+
this.runtimeCaps = rt.capabilities;
|
|
1286
|
+
this.runtimeName = rt.name;
|
|
1287
|
+
// Switching onto a runtime that owns serve (builtin opencode) must
|
|
1288
|
+
// ensure the sidecar exists — the gateway may have started under an
|
|
1289
|
+
// external runtime (pi) that never spawned one. Must run AFTER the
|
|
1290
|
+
// assignment above: the supervisor's spawn closure reads
|
|
1291
|
+
// this.opencodeClient to find agentProcess.spawnServe.
|
|
1292
|
+
if (rt.agentProcess?.spawnServe) {
|
|
1293
|
+
await (0, serve_for_runtime_1.ensureServeForBuiltinRuntime)(this.serveSupervisor, { startWatchdog: () => this.startServeWatchdog() }, logger_1.log);
|
|
1294
|
+
}
|
|
1295
|
+
this.sdkSession.setClient(this.opencodeClient);
|
|
1296
|
+
if (this.trajectoryCollector)
|
|
1297
|
+
this.trajectoryCollector.setOpencodeClient(this.opencodeClient);
|
|
1298
|
+
if (this.automationEngine)
|
|
1299
|
+
this.automationEngine.setRuntimeClient(rt);
|
|
1300
|
+
await this.resubscribeEvents(`runtime switched to '${rt.name}'`);
|
|
1301
|
+
// Desktop hint: a runtime switch swaps the session storage backend
|
|
1302
|
+
// (opencode SQLite vs pi), so cached session lists are stale.
|
|
1303
|
+
this.broadcast({ type: 'runtime_switched', runtime: rt.name, previous: prev?.name ?? null });
|
|
1304
|
+
if (prev && prev.dispose) {
|
|
1305
|
+
try {
|
|
1306
|
+
await prev.dispose();
|
|
1307
|
+
}
|
|
1308
|
+
catch (err) {
|
|
1309
|
+
logger_1.log.warn(`[Runtime] dispose old runtime failed: ${err.message}`);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
/** P5 Wave 2:/api/runtime/restart-agent deps。 */
|
|
1316
|
+
restartAgentDeps() {
|
|
1317
|
+
return {
|
|
1318
|
+
capabilities: () => this.runtimeCaps,
|
|
1319
|
+
isRecovering: () => this.serveRecovering,
|
|
1320
|
+
isSwitching: () => this.switchingRuntime,
|
|
1321
|
+
begin: () => { this.serveRecovering = true; },
|
|
1322
|
+
end: () => { this.serveRecovering = false; },
|
|
1323
|
+
restartAgent: () => this.restartAgentOrchestrated(),
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
/** P5 Wave 2:/api/plugins* deps(Plugin Hub 四类型统一面)。 */
|
|
1327
|
+
pluginHubDeps() {
|
|
1328
|
+
return {
|
|
1329
|
+
hub: {
|
|
1330
|
+
dirs: {
|
|
1331
|
+
runtime: config_1.config.resolvePath('runtime-plugins'),
|
|
1332
|
+
media: config_1.config.resolvePath('media-plugins'),
|
|
1333
|
+
usage: config_1.config.resolvePath('usage-plugins'),
|
|
1334
|
+
ui: process.env.MAFW_UI_PLUGINS_DIR || path.join(os.homedir(), '.mafw', 'ui-plugins'),
|
|
1335
|
+
},
|
|
1336
|
+
builtinEntries: () => {
|
|
1337
|
+
const entries = [];
|
|
1338
|
+
const rt = (name) => ({ type: 'runtime', name, file: '(builtin)', status: 'enabled', size: 0, mtime: '' });
|
|
1339
|
+
for (const name of this.runtimeLoader?.getBuiltinNames?.() ?? [])
|
|
1340
|
+
entries.push(rt(name));
|
|
1341
|
+
for (const name of this.mediaPluginLoader?.getBuiltinEngineNames?.() ?? []) {
|
|
1342
|
+
entries.push({ type: 'media', name, file: '(builtin)', status: 'enabled', size: 0, mtime: '' });
|
|
1343
|
+
}
|
|
1344
|
+
const usageState = this.pluginLoader?.getState?.() ?? [];
|
|
1345
|
+
for (const s of usageState) {
|
|
1346
|
+
if (s.builtin && s.status === 'ok' && s.name) {
|
|
1347
|
+
entries.push({ type: 'usage', name: s.name, file: s.file, status: 'enabled', size: 0, mtime: '', pluginType: s.pluginType });
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return entries;
|
|
1351
|
+
},
|
|
1352
|
+
getErrors: (type) => {
|
|
1353
|
+
const stateOf = (loader) => (loader && typeof loader.getState === 'function' ? loader.getState() : []);
|
|
1354
|
+
const source = type === 'runtime' ? this.runtimeLoader : type === 'media' ? this.mediaPluginLoader : type === 'usage' ? this.pluginLoader : null;
|
|
1355
|
+
const out = {};
|
|
1356
|
+
for (const p of stateOf(source)) {
|
|
1357
|
+
if (p && p.error)
|
|
1358
|
+
out[p.name || p.file] = p.error;
|
|
1359
|
+
}
|
|
1360
|
+
return out;
|
|
1361
|
+
},
|
|
1362
|
+
configDisabledUsage: () => new Set(Array.isArray(config_1.config.usage?.disabledPlugins) ? config_1.config.usage.disabledPlugins : []),
|
|
1363
|
+
reload: async (type) => {
|
|
1364
|
+
if (type === 'runtime')
|
|
1365
|
+
await this.runtimeLoader?.scan();
|
|
1366
|
+
else if (type === 'media')
|
|
1367
|
+
await this.mediaPluginLoader?.reload();
|
|
1368
|
+
else if (type === 'usage')
|
|
1369
|
+
await this.pluginLoader?.reload();
|
|
1370
|
+
// ui: desktop main fs.watch picks it up automatically
|
|
1371
|
+
},
|
|
1372
|
+
getPackages: () => this.pluginHost?.getState() ?? [],
|
|
1373
|
+
},
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1282
1376
|
embeddingConfigDeps() {
|
|
1283
1377
|
return {
|
|
1284
1378
|
currentConfig: () => {
|
|
@@ -2085,12 +2179,10 @@ class MafwScheduler {
|
|
|
2085
2179
|
return;
|
|
2086
2180
|
}
|
|
2087
2181
|
logger_1.log.info('Starting OpenCode Serve sidecar...');
|
|
2088
|
-
const port = config_1.config.server.servePort;
|
|
2089
|
-
const host = config_1.config.server.serveHost;
|
|
2090
2182
|
try {
|
|
2183
|
+
// host/port 归 runtime 自定(serve 端口是实现细节);sidecar 实际 URL
|
|
2184
|
+
// 由 runtime 吸收(getBaseUrl 跟随),gateway 只记 bookkeeping。
|
|
2091
2185
|
const sidecar = await spawnServe({
|
|
2092
|
-
host,
|
|
2093
|
-
port,
|
|
2094
2186
|
onOutput: (chunk) => logger_1.log.debug(`[Serve] ${chunk.trimEnd()}`),
|
|
2095
2187
|
onExit: (code) => this.handleServeExit(code),
|
|
2096
2188
|
});
|
|
@@ -2352,18 +2444,14 @@ class MafwScheduler {
|
|
|
2352
2444
|
return projectID ? await this.sdkSession.listByProject(projectID) : await this.sdkSession.list();
|
|
2353
2445
|
}
|
|
2354
2446
|
async isServeHealthy() {
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
req.destroy();
|
|
2364
|
-
resolve(false);
|
|
2365
|
-
});
|
|
2366
|
-
});
|
|
2447
|
+
// 健康唯一真相源 = runtime 契约 healthCheck()(serve 型探测 serve,
|
|
2448
|
+
// 进程内 runtime 探测自身引擎);gateway 不自带 HTTP 探测。
|
|
2449
|
+
try {
|
|
2450
|
+
return (await this.opencodeClient?.healthCheck?.()) ?? false;
|
|
2451
|
+
}
|
|
2452
|
+
catch {
|
|
2453
|
+
return false;
|
|
2454
|
+
}
|
|
2367
2455
|
}
|
|
2368
2456
|
async waitForServeReady() {
|
|
2369
2457
|
const maxRetries = config_1.config.timeouts.serveReadyMaxRetries;
|
|
@@ -2380,9 +2468,45 @@ class MafwScheduler {
|
|
|
2380
2468
|
// 鈹€鈹€ 2. HTTP API 鈹€鈹€
|
|
2381
2469
|
async startApiServer() {
|
|
2382
2470
|
return new Promise((resolve) => {
|
|
2471
|
+
// P5 Wave 1: catalog shadow 登记 + 模块 handler 绑定(一次性,重复 attach 会抛错)。
|
|
2472
|
+
// adapter 显式桥接私有成员(结构化类型不认 private)。
|
|
2473
|
+
(0, wave1_handlers_1.attachWave1Handlers)(this.routeRegistry, {
|
|
2474
|
+
getGatewayDb: () => this.getGatewayDb(),
|
|
2475
|
+
opencodeClient: this.opencodeClient,
|
|
2476
|
+
automationEngine: this.automationEngine,
|
|
2477
|
+
ledger: this.ledger,
|
|
2478
|
+
rotateDeps: () => this.rotateDeps(),
|
|
2479
|
+
embeddingConfigDeps: () => this.embeddingConfigDeps(),
|
|
2480
|
+
modelConfigDeps: () => this.modelConfigDeps(),
|
|
2481
|
+
usagePluginsDeps: () => this.usagePluginsDeps(),
|
|
2482
|
+
pluginLoader: this.pluginLoader,
|
|
2483
|
+
mediaPluginLoader: this.mediaPluginLoader,
|
|
2484
|
+
broadcast: (e) => this.broadcast(e),
|
|
2485
|
+
runtimeCaps: this.runtimeCaps,
|
|
2486
|
+
});
|
|
2487
|
+
(0, wave2_handlers_1.attachWave2Handlers)(this.routeRegistry, {
|
|
2488
|
+
runtimeDeps: () => this.runtimeDeps(),
|
|
2489
|
+
restartAgentDeps: () => this.restartAgentDeps(),
|
|
2490
|
+
pluginHubDeps: () => this.pluginHubDeps(),
|
|
2491
|
+
runtimeSwitchBlocked: () => this.serveRecovering || this.switchingRuntime,
|
|
2492
|
+
beginRuntimeSwitch: () => { this.switchingRuntime = true; },
|
|
2493
|
+
endRuntimeSwitch: () => { this.switchingRuntime = false; },
|
|
2494
|
+
});
|
|
2383
2495
|
const server = http.createServer(async (req, res) => {
|
|
2384
2496
|
try {
|
|
2385
2497
|
res.setHeader('Content-Type', 'application/json');
|
|
2498
|
+
// P5 Wave 1: registry dispatch —— attach 过 handler 的路由在 legacy 链之前
|
|
2499
|
+
// 统一接管(shadow 条目无 handler 自然落回内联链)。挂在鉴权之后、一切
|
|
2500
|
+
// /api matcher 之前:goals.sessions 须先于 dashboard 兜底、embedding-config
|
|
2501
|
+
// 须先于 /api/memory/* 委托(AGENTS.md §6.5 顺序教训由 dispatch 位置一次性满足)。
|
|
2502
|
+
{
|
|
2503
|
+
const matched = this.routeRegistry.match(req.method, req.url || '');
|
|
2504
|
+
if (matched?.def.handler) {
|
|
2505
|
+
const handled = await matched.def.handler(req, res, matched.params);
|
|
2506
|
+
if (handled !== false)
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2386
2510
|
// CORS headers for SSE
|
|
2387
2511
|
res.setHeader("Access-Control-Allow-Origin", config_1.config.server.cors.origin);
|
|
2388
2512
|
res.setHeader("Access-Control-Allow-Methods", config_1.config.server.cors.methods);
|
|
@@ -3350,16 +3474,8 @@ class MafwScheduler {
|
|
|
3350
3474
|
return;
|
|
3351
3475
|
}
|
|
3352
3476
|
// GET/POST /api/memory/embedding-config — memory embedding engine
|
|
3353
|
-
// settings
|
|
3354
|
-
//
|
|
3355
|
-
if (req.url?.match(/^\/api\/memory\/embedding-config(?:\?|$)/) && req.method === 'GET') {
|
|
3356
|
-
await (0, embedding_config_1.handleEmbeddingConfigGet)(req, res, this.embeddingConfigDeps());
|
|
3357
|
-
return;
|
|
3358
|
-
}
|
|
3359
|
-
if (req.url?.match(/^\/api\/memory\/embedding-config(?:\?|$)/) && req.method === 'POST') {
|
|
3360
|
-
await (0, embedding_config_1.handleEmbeddingConfigUpdate)(req, res, this.embeddingConfigDeps());
|
|
3361
|
-
return;
|
|
3362
|
-
}
|
|
3477
|
+
// settings(P5 Wave 1 起由 registry dispatch 接管,见 routes/wave1-handlers.ts;
|
|
3478
|
+
// 历史顺序约束"须高于 /api/memory/* dashboard 委托"由 dispatch 位置满足)
|
|
3363
3479
|
// GET /api/memory/stats — consolidation health + vector coverage (P2)
|
|
3364
3480
|
if (req.url?.match(/^\/api\/memory\/stats(?:\?|$)/) && req.method === 'GET') {
|
|
3365
3481
|
try {
|
|
@@ -3606,16 +3722,8 @@ class MafwScheduler {
|
|
|
3606
3722
|
res.end(JSON.stringify({ status: 'accepted' }));
|
|
3607
3723
|
return;
|
|
3608
3724
|
}
|
|
3609
|
-
// GET /api/goals/:id/sessions —
|
|
3610
|
-
//
|
|
3611
|
-
if (req.url?.match(/^\/api\/goals\/[^/]+\/sessions(?:\?|$)/) && req.method === 'GET') {
|
|
3612
|
-
const handled = await (0, goal_sessions_1.handleGoalSessions)(req, res, req.url, {
|
|
3613
|
-
listGoalSessions: (goalId) => this.getGatewayDb().listGoalSessions(goalId),
|
|
3614
|
-
getSession: (sessionID) => this.opencodeClient?.session.get({ sessionID }).catch(() => null),
|
|
3615
|
-
});
|
|
3616
|
-
if (handled)
|
|
3617
|
-
return;
|
|
3618
|
-
}
|
|
3725
|
+
// GET /api/goals/:id/sessions — P5 Wave 1 起由 registry dispatch 接管
|
|
3726
|
+
// (历史约束"须挂 Dashboard /api/goals* 兜底之前"由 dispatch 位置满足)。
|
|
3619
3727
|
// Dashboard API
|
|
3620
3728
|
if (req.url?.startsWith("/api/goals") || req.url?.startsWith("/api/stats") || req.url?.startsWith("/api/memory")) {
|
|
3621
3729
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -3729,8 +3837,8 @@ class MafwScheduler {
|
|
|
3729
3837
|
});
|
|
3730
3838
|
return;
|
|
3731
3839
|
}
|
|
3732
|
-
//
|
|
3733
|
-
if (req.url === '/control' && req.method === 'POST') {
|
|
3840
|
+
// 控制指令(/control 为 MCP handler 兼容路径;/api/goals/control 为 SDK goals.control 契约路径,两者等价)
|
|
3841
|
+
if ((req.url === '/control' || req.url === '/api/goals/control') && req.method === 'POST') {
|
|
3734
3842
|
let body = '';
|
|
3735
3843
|
req.on('data', chunk => body += chunk);
|
|
3736
3844
|
req.on('end', async () => {
|
|
@@ -3833,12 +3941,7 @@ class MafwScheduler {
|
|
|
3833
3941
|
}
|
|
3834
3942
|
return;
|
|
3835
3943
|
}
|
|
3836
|
-
// POST /api/manager/session/rotate —
|
|
3837
|
-
// (thin wiring → routes/manager-rotate.ts).
|
|
3838
|
-
if (req.method === 'POST' && req.url?.match(/^\/api\/manager\/session\/rotate(?:\?|$)/)) {
|
|
3839
|
-
await (0, manager_rotate_1.handleManagerRotate)(req, res, this.rotateDeps());
|
|
3840
|
-
return;
|
|
3841
|
-
}
|
|
3944
|
+
// POST /api/manager/session/rotate — P5 Wave 1 起由 registry dispatch 接管
|
|
3842
3945
|
// GET /api/manager/session — return manager session info (per-project,
|
|
3843
3946
|
// read from the gateway DB). ?projectDir= filters a single project.
|
|
3844
3947
|
if (req.url && req.url.startsWith('/api/manager/session') && req.method === 'GET') {
|
|
@@ -4189,173 +4292,12 @@ class MafwScheduler {
|
|
|
4189
4292
|
return;
|
|
4190
4293
|
}
|
|
4191
4294
|
// 鈹€鈹€ Provider & Agents (composer model pill / @agent mention) 鈹€鈹€
|
|
4192
|
-
// ── /api/runtime routes
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
runtimeName: () => this.runtimeName,
|
|
4199
|
-
runtimeCaps: () => this.runtimeCaps,
|
|
4200
|
-
envOverride: () => !!process.env.MAFW_RUNTIME_PLUGIN,
|
|
4201
|
-
createRuntime: async () => {
|
|
4202
|
-
const sdkConfig = {
|
|
4203
|
-
baseUrl: this.serveUrl,
|
|
4204
|
-
directory: this.projectDir,
|
|
4205
|
-
headers: {},
|
|
4206
|
-
};
|
|
4207
|
-
const opencodePassword = process.env.MAFW_OPENCODE_PASSWORD;
|
|
4208
|
-
if (opencodePassword) {
|
|
4209
|
-
sdkConfig.headers = { Authorization: 'Basic ' + Buffer.from(`opencode:${opencodePassword}`).toString('base64') };
|
|
4210
|
-
}
|
|
4211
|
-
return this.createRuntime(sdkConfig);
|
|
4212
|
-
},
|
|
4213
|
-
onSwitched: async (rt, prev) => {
|
|
4214
|
-
this.opencodeClient = rt;
|
|
4215
|
-
this.runtimeCaps = rt.capabilities;
|
|
4216
|
-
this.runtimeName = rt.name;
|
|
4217
|
-
// Switching onto a runtime that owns serve (builtin opencode) must
|
|
4218
|
-
// ensure the sidecar exists — the gateway may have started under an
|
|
4219
|
-
// external runtime (pi) that never spawned one. Must run AFTER the
|
|
4220
|
-
// assignment above: the supervisor's spawn closure reads
|
|
4221
|
-
// this.opencodeClient to find agentProcess.spawnServe.
|
|
4222
|
-
if (rt.agentProcess?.spawnServe) {
|
|
4223
|
-
await (0, serve_for_runtime_1.ensureServeForBuiltinRuntime)(this.serveSupervisor, { startWatchdog: () => this.startServeWatchdog() }, logger_1.log);
|
|
4224
|
-
}
|
|
4225
|
-
this.sdkSession.setClient(this.opencodeClient);
|
|
4226
|
-
if (this.trajectoryCollector)
|
|
4227
|
-
this.trajectoryCollector.setOpencodeClient(this.opencodeClient);
|
|
4228
|
-
if (this.automationEngine)
|
|
4229
|
-
this.automationEngine.setRuntimeClient(rt);
|
|
4230
|
-
await this.resubscribeEvents(`runtime switched to '${rt.name}'`);
|
|
4231
|
-
// Desktop hint: a runtime switch swaps the session storage backend
|
|
4232
|
-
// (opencode SQLite vs pi), so cached session lists are stale.
|
|
4233
|
-
this.broadcast({ type: 'runtime_switched', runtime: rt.name, previous: prev?.name ?? null });
|
|
4234
|
-
if (prev && prev.dispose) {
|
|
4235
|
-
try {
|
|
4236
|
-
await prev.dispose();
|
|
4237
|
-
}
|
|
4238
|
-
catch (err) {
|
|
4239
|
-
logger_1.log.warn(`[Runtime] dispose old runtime failed: ${err.message}`);
|
|
4240
|
-
}
|
|
4241
|
-
}
|
|
4242
|
-
},
|
|
4243
|
-
};
|
|
4244
|
-
if (req.method === 'GET' && req.url?.match(/^\/api\/runtime(?:\?|$)/)) {
|
|
4245
|
-
await (0, runtime_switch_1.handleRuntimeGet)(req, res, runtimeDeps);
|
|
4246
|
-
return;
|
|
4247
|
-
}
|
|
4248
|
-
if (req.method === 'POST' && req.url?.match(/^\/api\/runtime\/switch(?:\?|$)/)) {
|
|
4249
|
-
if (this.serveRecovering || this.switchingRuntime) {
|
|
4250
|
-
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
4251
|
-
res.end(JSON.stringify({ error: 'Cannot switch runtime during agent restart' }));
|
|
4252
|
-
return;
|
|
4253
|
-
}
|
|
4254
|
-
this.switchingRuntime = true;
|
|
4255
|
-
try {
|
|
4256
|
-
await (0, runtime_switch_1.handleRuntimeSwitch)(req, res, runtimeDeps);
|
|
4257
|
-
}
|
|
4258
|
-
finally {
|
|
4259
|
-
this.switchingRuntime = false;
|
|
4260
|
-
}
|
|
4261
|
-
return;
|
|
4262
|
-
}
|
|
4263
|
-
if (req.method === 'POST' && req.url?.match(/^\/api\/runtime\/reload(?:\?|$)/)) {
|
|
4264
|
-
await (0, runtime_switch_1.handleRuntimeReload)(req, res, runtimeDeps);
|
|
4265
|
-
return;
|
|
4266
|
-
}
|
|
4267
|
-
if (req.method === 'POST' && req.url?.match(/^\/api\/runtime\/restart-agent(?:\?|$)/)) {
|
|
4268
|
-
await (0, restart_agent_1.handleRestartAgent)(req, res, {
|
|
4269
|
-
capabilities: () => this.runtimeCaps,
|
|
4270
|
-
isRecovering: () => this.serveRecovering,
|
|
4271
|
-
isSwitching: () => this.switchingRuntime,
|
|
4272
|
-
begin: () => { this.serveRecovering = true; },
|
|
4273
|
-
end: () => { this.serveRecovering = false; },
|
|
4274
|
-
restartAgent: () => this.restartAgentOrchestrated(),
|
|
4275
|
-
});
|
|
4276
|
-
return;
|
|
4277
|
-
}
|
|
4278
|
-
return;
|
|
4279
|
-
}
|
|
4280
|
-
// ── Plugin Hub (all four plugin types) ──
|
|
4281
|
-
const pluginHubDeps = {
|
|
4282
|
-
hub: {
|
|
4283
|
-
dirs: {
|
|
4284
|
-
runtime: config_1.config.resolvePath('runtime-plugins'),
|
|
4285
|
-
media: config_1.config.resolvePath('media-plugins'),
|
|
4286
|
-
usage: config_1.config.resolvePath('usage-plugins'),
|
|
4287
|
-
ui: process.env.MAFW_UI_PLUGINS_DIR || path.join(os.homedir(), '.mafw', 'ui-plugins'),
|
|
4288
|
-
},
|
|
4289
|
-
builtinEntries: () => {
|
|
4290
|
-
const entries = [];
|
|
4291
|
-
const rt = (name) => ({ type: 'runtime', name, file: '(builtin)', status: 'enabled', size: 0, mtime: '' });
|
|
4292
|
-
// opencode 经 registerBuiltin 注册,getBuiltinNames 已含——不再手工 push(防重复)
|
|
4293
|
-
for (const name of this.runtimeLoader?.getBuiltinNames?.() ?? [])
|
|
4294
|
-
entries.push(rt(name));
|
|
4295
|
-
for (const name of this.mediaPluginLoader?.getBuiltinEngineNames?.() ?? []) {
|
|
4296
|
-
entries.push({ type: 'media', name, file: '(builtin)', status: 'enabled', size: 0, mtime: '' });
|
|
4297
|
-
}
|
|
4298
|
-
const usageState = this.pluginLoader?.getState?.() ?? [];
|
|
4299
|
-
for (const s of usageState) {
|
|
4300
|
-
if (s.builtin && s.status === 'ok' && s.name) {
|
|
4301
|
-
entries.push({ type: 'usage', name: s.name, file: s.file, status: 'enabled', size: 0, mtime: '', pluginType: s.pluginType });
|
|
4302
|
-
}
|
|
4303
|
-
}
|
|
4304
|
-
return entries;
|
|
4305
|
-
},
|
|
4306
|
-
getErrors: (type) => {
|
|
4307
|
-
const stateOf = (loader) => (loader && typeof loader.getState === 'function' ? loader.getState() : []);
|
|
4308
|
-
const source = type === 'runtime' ? this.runtimeLoader : type === 'media' ? this.mediaPluginLoader : type === 'usage' ? this.pluginLoader : null;
|
|
4309
|
-
const out = {};
|
|
4310
|
-
for (const p of stateOf(source)) {
|
|
4311
|
-
if (p && p.error)
|
|
4312
|
-
out[p.name || p.file] = p.error;
|
|
4313
|
-
}
|
|
4314
|
-
return out;
|
|
4315
|
-
},
|
|
4316
|
-
configDisabledUsage: () => new Set(Array.isArray(config_1.config.usage?.disabledPlugins) ? config_1.config.usage.disabledPlugins : []),
|
|
4317
|
-
reload: async (type) => {
|
|
4318
|
-
if (type === 'runtime')
|
|
4319
|
-
await this.runtimeLoader?.scan();
|
|
4320
|
-
else if (type === 'media')
|
|
4321
|
-
await this.mediaPluginLoader?.reload();
|
|
4322
|
-
else if (type === 'usage')
|
|
4323
|
-
await this.pluginLoader?.reload();
|
|
4324
|
-
// ui: desktop main fs.watch picks it up automatically
|
|
4325
|
-
},
|
|
4326
|
-
getPackages: () => this.pluginHost?.getState() ?? [],
|
|
4327
|
-
},
|
|
4328
|
-
};
|
|
4329
|
-
try {
|
|
4330
|
-
const cleaned = (0, hub_1.cleanupExamples)(pluginHubDeps.hub);
|
|
4331
|
-
if (cleaned.removed.length)
|
|
4332
|
-
logger_1.log.info(`[PluginsHub] removed stale examples: ${cleaned.removed.length}`);
|
|
4333
|
-
if (cleaned.failed.length)
|
|
4334
|
-
logger_1.log.warn(`[PluginsHub] cleanupExamples failed: ${cleaned.failed.join(', ')}`);
|
|
4335
|
-
}
|
|
4336
|
-
catch (err) {
|
|
4337
|
-
logger_1.log.warn(`[PluginsHub] cleanupExamples error: ${err.message}`);
|
|
4338
|
-
}
|
|
4339
|
-
if (req.method === 'GET' && req.url?.match(/^\/api\/plugins(?:\?|$)/)) {
|
|
4340
|
-
await (0, plugins_1.handlePluginsList)(req, res, pluginHubDeps);
|
|
4341
|
-
return;
|
|
4342
|
-
}
|
|
4343
|
-
if (req.method === 'POST' && req.url?.match(/^\/api\/plugins\/install(?:\?|$)/)) {
|
|
4344
|
-
await (0, plugins_1.handlePluginsInstall)(req, res, pluginHubDeps);
|
|
4345
|
-
return;
|
|
4346
|
-
}
|
|
4347
|
-
if (req.method === 'POST' && req.url?.match(/^\/api\/plugins\/enable(?:\?|$)/)) {
|
|
4348
|
-
await (0, plugins_1.handlePluginsEnable)(req, res, pluginHubDeps);
|
|
4349
|
-
return;
|
|
4350
|
-
}
|
|
4351
|
-
if (req.method === 'POST' && req.url?.match(/^\/api\/plugins\/disable(?:\?|$)/)) {
|
|
4352
|
-
await (0, plugins_1.handlePluginsDisable)(req, res, pluginHubDeps);
|
|
4353
|
-
return;
|
|
4354
|
-
}
|
|
4355
|
-
if (req.method === 'POST' && req.url?.match(/^\/api\/plugins\/delete(?:\?|$)/)) {
|
|
4356
|
-
await (0, plugins_1.handlePluginsDelete)(req, res, pluginHubDeps);
|
|
4357
|
-
return;
|
|
4358
|
-
}
|
|
4295
|
+
// ── /api/runtime routes — P5 Wave 2 起由 registry dispatch 接管
|
|
4296
|
+
// (runtimeDeps/restartAgentDeps 构造上移至私有方法;内联块对未知
|
|
4297
|
+
// /api/runtime/* 悬空 return 吞请求的行为随之终结——现落回 legacy 链)
|
|
4298
|
+
// ── Plugin Hub (all four plugin types) — P5 Wave 2 起由 registry dispatch
|
|
4299
|
+
// 接管(pluginHubDeps 构造上移至私有方法;cleanupExamples 改为命中
|
|
4300
|
+
// /api/plugins 路由时才执行——内联时代每个到达此处的请求都跑)。
|
|
4359
4301
|
// GET /api/orchestration/outcomes — goal outcome query
|
|
4360
4302
|
if (req.url?.match(/^\/api\/orchestration\/outcomes(?:\?|$)/) && req.method === 'GET') {
|
|
4361
4303
|
const u = new URL(req.url, 'http://localhost');
|
|
@@ -4818,13 +4760,7 @@ class MafwScheduler {
|
|
|
4818
4760
|
}
|
|
4819
4761
|
return;
|
|
4820
4762
|
}
|
|
4821
|
-
// DELETE/PATCH /api/sessions/:id —
|
|
4822
|
-
if (await (0, session_mutations_1.handleSessionMutations)(req, res, {
|
|
4823
|
-
getCapabilities: () => this.runtimeCaps,
|
|
4824
|
-
getClient: () => (this.opencodeClient ?? null),
|
|
4825
|
-
})) {
|
|
4826
|
-
return;
|
|
4827
|
-
}
|
|
4763
|
+
// DELETE/PATCH /api/sessions/:id — P5 Wave 1 起由 registry dispatch 接管
|
|
4828
4764
|
// GET /api/sessions — list sessions (optional ?projectID=xxx)
|
|
4829
4765
|
if (req.url?.match(/^\/api\/sessions(?:\?|$)/) && req.method === 'GET') {
|
|
4830
4766
|
try {
|
|
@@ -4988,44 +4924,7 @@ class MafwScheduler {
|
|
|
4988
4924
|
}
|
|
4989
4925
|
return;
|
|
4990
4926
|
}
|
|
4991
|
-
//
|
|
4992
|
-
if (req.url?.match(/^\/api\/usage\/plugins(?:\?|$)/) && req.method === 'GET') {
|
|
4993
|
-
await (0, usage_plugins_1.handleUsagePluginsList)(req, res, this.usagePluginsDeps());
|
|
4994
|
-
return;
|
|
4995
|
-
}
|
|
4996
|
-
// POST /api/usage/plugins/create — template wizard or raw source
|
|
4997
|
-
if (req.url?.match(/^\/api\/usage\/plugins\/create$/) && req.method === 'POST') {
|
|
4998
|
-
await (0, usage_plugins_1.handleUsagePluginCreate)(req, res, this.usagePluginsDeps());
|
|
4999
|
-
return;
|
|
5000
|
-
}
|
|
5001
|
-
// GET/PUT /api/usage/plugins/:name/source — user plugin code editor
|
|
5002
|
-
const mSource = req.url?.match(/^\/api\/usage\/plugins\/([^/]+)\/source$/);
|
|
5003
|
-
if (mSource && req.method === 'GET') {
|
|
5004
|
-
await (0, usage_plugins_1.handleUsagePluginSourceGet)(req, res, this.usagePluginsDeps(), decodeURIComponent(mSource[1]));
|
|
5005
|
-
return;
|
|
5006
|
-
}
|
|
5007
|
-
if (mSource && req.method === 'PUT') {
|
|
5008
|
-
await (0, usage_plugins_1.handleUsagePluginSourcePut)(req, res, this.usagePluginsDeps(), decodeURIComponent(mSource[1]));
|
|
5009
|
-
return;
|
|
5010
|
-
}
|
|
5011
|
-
// POST /api/usage/plugins/:name/test — one-shot adapter fetch
|
|
5012
|
-
const mTest = req.url?.match(/^\/api\/usage\/plugins\/([^/]+)\/test$/);
|
|
5013
|
-
if (mTest && req.method === 'POST') {
|
|
5014
|
-
await (0, usage_plugins_1.handleUsagePluginTest)(req, res, this.usagePluginsDeps(), decodeURIComponent(mTest[1]));
|
|
5015
|
-
return;
|
|
5016
|
-
}
|
|
5017
|
-
// DELETE /api/usage/plugins/:name — remove user plugin file
|
|
5018
|
-
const mDel = req.url?.match(/^\/api\/usage\/plugins\/([^/]+)$/);
|
|
5019
|
-
if (mDel && req.method === 'DELETE') {
|
|
5020
|
-
await (0, usage_plugins_1.handleUsagePluginDelete)(req, res, this.usagePluginsDeps(), decodeURIComponent(mDel[1]));
|
|
5021
|
-
return;
|
|
5022
|
-
}
|
|
5023
|
-
// POST /api/usage/plugins/reload — manual reload
|
|
5024
|
-
if (req.url?.match(/^\/api\/usage\/plugins\/reload$/) && req.method === 'POST') {
|
|
5025
|
-
await this.pluginLoader?.reload();
|
|
5026
|
-
await (0, usage_plugins_1.handleUsagePluginsList)(req, res, this.usagePluginsDeps());
|
|
5027
|
-
return;
|
|
5028
|
-
}
|
|
4927
|
+
// /api/usage/plugins* — P5 Wave 1 起由 registry dispatch 接管(7 条)
|
|
5029
4928
|
// GET /api/media/plugins — media engine plugin state list
|
|
5030
4929
|
if (req.url?.match(/^\/api\/media\/plugins(?:\?|$)/) && req.method === 'GET') {
|
|
5031
4930
|
const plugins = (this.mediaPluginLoader?.getState() ?? []).map(s => ({
|
|
@@ -5053,43 +4952,10 @@ class MafwScheduler {
|
|
|
5053
4952
|
res.end(JSON.stringify({ ok: true, plugins }));
|
|
5054
4953
|
return;
|
|
5055
4954
|
}
|
|
5056
|
-
// POST /api/media/switch —
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
reloadPlugins: async () => { await this.mediaPluginLoader?.reload(); },
|
|
5061
|
-
availableEngines: () => (this.mediaPluginLoader?.getState().map(s => s.name).filter((n) => !!n) ?? []),
|
|
5062
|
-
currentMedia: () => ({
|
|
5063
|
-
engine: config_1.config.raw.media.engine,
|
|
5064
|
-
image: config_1.config.raw.media.image,
|
|
5065
|
-
video: config_1.config.raw.media.video,
|
|
5066
|
-
audio: config_1.config.raw.media.audio,
|
|
5067
|
-
}),
|
|
5068
|
-
});
|
|
5069
|
-
return;
|
|
5070
|
-
}
|
|
5071
|
-
// GET/POST /api/model-config — recall worker model + media models (hot-apply)
|
|
5072
|
-
if (req.url?.match(/^\/api\/model-config(?:\?|$)/) && req.method === 'GET') {
|
|
5073
|
-
await (0, model_config_1.handleModelConfigGet)(req, res, this.modelConfigDeps());
|
|
5074
|
-
return;
|
|
5075
|
-
}
|
|
5076
|
-
if (req.url?.match(/^\/api\/model-config(?:\?|$)/) && req.method === 'POST') {
|
|
5077
|
-
await (0, model_config_1.handleModelConfigUpdate)(req, res, this.modelConfigDeps());
|
|
5078
|
-
return;
|
|
5079
|
-
}
|
|
5080
|
-
// POST /api/sessions/:id/fork|revert|unrevert — session branch primitives
|
|
5081
|
-
// (capability-gated: sessionBranchApi; unrevert is opencode-only → 404 on pi)
|
|
5082
|
-
if (req.url?.match(/^\/api\/sessions\/[^/]+\/(fork|revert|unrevert)(?:\?|$)/) && req.method === 'POST') {
|
|
5083
|
-
const handled = await (0, session_branch_1.handleSessionBranch)(req, res, req.url, { getRuntime: () => this.opencodeClient ?? null });
|
|
5084
|
-
if (handled)
|
|
5085
|
-
return;
|
|
5086
|
-
}
|
|
5087
|
-
// POST /api/session/:id/summarize — 手动压缩会话(TUI /compact;runtime 薄代理)
|
|
5088
|
-
if (req.url?.match(/^\/api\/session\/[^/]+\/summarize(?:\?|$)/) && req.method === 'POST') {
|
|
5089
|
-
const handled = await (0, session_summarize_1.handleSessionSummarize)(req, res, req.url, { getRuntime: () => this.opencodeClient ?? null });
|
|
5090
|
-
if (handled)
|
|
5091
|
-
return;
|
|
5092
|
-
}
|
|
4955
|
+
// POST /api/media/switch — P5 Wave 1 起由 registry dispatch 接管
|
|
4956
|
+
// GET/POST /api/model-config — P5 Wave 1 起由 registry dispatch 接管
|
|
4957
|
+
// POST /api/sessions/:id/fork|revert|unrevert + /api/session/:id/summarize
|
|
4958
|
+
// — P5 Wave 1 起由 registry dispatch 接管
|
|
5093
4959
|
// GET /api/usage?sessionID=xxx&projectID=xxx — consolidated usage (summary + providers)
|
|
5094
4960
|
if (req.url?.match(/^\/api\/usage(?:\?|$)/) && req.method === 'GET') {
|
|
5095
4961
|
try {
|
|
@@ -5352,11 +5218,7 @@ class MafwScheduler {
|
|
|
5352
5218
|
});
|
|
5353
5219
|
return;
|
|
5354
5220
|
}
|
|
5355
|
-
// POST /api/events
|
|
5356
|
-
if (req.url && req.url.startsWith('/api/events') && req.method === 'POST') {
|
|
5357
|
-
await (0, event_publish_1.handleEventPublish)({ broadcast: (e) => this.broadcast(e) }, req, res);
|
|
5358
|
-
return;
|
|
5359
|
-
}
|
|
5221
|
+
// POST /api/events — P5 Wave 1 起由 registry dispatch 接管
|
|
5360
5222
|
// SSE 事件(→ Dashboard / Chat)
|
|
5361
5223
|
if (req.url && req.url.startsWith('/api/events') && req.method === 'GET') {
|
|
5362
5224
|
const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
@@ -5385,8 +5247,9 @@ class MafwScheduler {
|
|
|
5385
5247
|
res.end(JSON.stringify({ status: 'ok' }));
|
|
5386
5248
|
return;
|
|
5387
5249
|
}
|
|
5388
|
-
// Reverse proxy to
|
|
5389
|
-
|
|
5250
|
+
// Reverse proxy to the agent backend for non-MAFW routes —
|
|
5251
|
+
// target from the runtime contract (getBaseUrl), config 仅 bootstrap 兜底
|
|
5252
|
+
const serveUrl = this.opencodeClient?.getBaseUrl?.() ?? config_1.config.server.serveUrl;
|
|
5390
5253
|
try {
|
|
5391
5254
|
const proxyUrl = new URL(req.url || '/', serveUrl);
|
|
5392
5255
|
const proxyReq = http.request(proxyUrl, {
|
|
@@ -5443,7 +5306,7 @@ class MafwScheduler {
|
|
|
5443
5306
|
server.listen(this.apiPort, () => {
|
|
5444
5307
|
logger_1.log.info(`[Scheduler] HTTP API on port ${this.apiPort}`);
|
|
5445
5308
|
logger_1.log.info(`[Scheduler] - POST /register { projectDir, mafwDir }`);
|
|
5446
|
-
logger_1.log.info(`[Scheduler] - POST /control { action, goalId, ... }`);
|
|
5309
|
+
logger_1.log.info(`[Scheduler] - POST /control | /api/goals/control { action, goalId, ... }`);
|
|
5447
5310
|
logger_1.log.info(`[Scheduler] - GET /health`);
|
|
5448
5311
|
logger_1.log.info(`[Scheduler] - GET /mcp (MCP legacy SSE / StreamableHTTP 405)`);
|
|
5449
5312
|
logger_1.log.info(`[Scheduler] - POST /mcp (MCP StreamableHTTP stateless + legacy SSE messages)`);
|