@jack200714/mafw 4.8.0 → 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/README.md +27 -3
- package/gateway/dist/core/manager/goal-snapshot.js +2 -2
- package/gateway/dist/core/manager/manager-session-runtime.js +59 -0
- package/gateway/dist/core/manager/milestone-push.js +25 -10
- package/gateway/dist/index.js +465 -440
- package/gateway/dist/media/media-plugin-loader.js +25 -1
- package/gateway/dist/media/resolve-prompt.js +20 -0
- package/gateway/dist/memory/gateway-db.js +23 -0
- package/gateway/dist/opencode-adapter.js +53 -4
- package/gateway/dist/plugins/package-context.js +24 -0
- package/gateway/dist/plugins/package-host.js +331 -0
- package/gateway/dist/plugins/package-types.js +2 -0
- package/gateway/dist/recall/gateway-db-migrate.js +5 -2
- package/gateway/dist/recall/redact.js +53 -0
- package/gateway/dist/recall/turn-pipeline.js +2 -0
- package/gateway/dist/routes/event-publish.js +44 -0
- package/gateway/dist/routes/plugins.js +4 -1
- 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/waitwhat-command.js +43 -0
- package/gateway/dist/routes/wave1-handlers.js +62 -0
- package/gateway/dist/routes/wave2-handlers.js +47 -0
- package/gateway/dist/runtime/contract.js +4 -1
- package/gateway/dist/runtime/event-broadcast.js +8 -0
- package/gateway/dist/runtime/loader.js +22 -1
- package/gateway/dist/runtime/normalize.js +13 -0
- package/gateway/dist/runtime/opencode-runtime.js +18 -2
- package/gateway/dist/runtime/pi/pi-approval-bridge.js +12 -2
- package/gateway/dist/runtime/pi/pi-approval-extension.js +11 -3
- package/gateway/dist/runtime/pi/pi-session.js +21 -3
- package/gateway/dist/runtime/plugins/pi-runtime.js +6 -3
- package/gateway/dist/runtime/serve-sidecar.js +4 -1
- package/gateway/dist/runtime/serve-supervisor.js +19 -20
- package/gateway/dist/runtime/validate.js +39 -0
- package/gateway/dist/skills/manager-identity.js +6 -1
- package/gateway/dist/usage/builtin-plugins/gateway.js +91 -36
- package/gateway/dist/usage/plugin-context.js +42 -2
- package/gateway/dist/usage/plugin-loader.js +32 -2
- package/gateway/package.json +2 -1
- package/package.json +3 -1
- package/packages/tui/dist/cli.js +114 -51
|
@@ -51,7 +51,10 @@ async function guarded(res, fn) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
async function handlePluginsList(_req, res, deps) {
|
|
54
|
-
await guarded(res, async () => ({
|
|
54
|
+
await guarded(res, async () => ({
|
|
55
|
+
plugins: (0, hub_1.listPlugins)(deps.hub),
|
|
56
|
+
packages: deps.hub.getPackages?.() ?? [],
|
|
57
|
+
}));
|
|
55
58
|
}
|
|
56
59
|
async function handlePluginsInstall(req, res, deps) {
|
|
57
60
|
await guarded(res, async () => {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* P4-A 声明式路由注册表(Shadow Registry 架构)。
|
|
4
|
+
*
|
|
5
|
+
* RouteDef 是 gateway HTTP 端点的声明式描述。Phase 2"shadow 登记"模式下 catalog
|
|
6
|
+
* 只贡献 OpenAPI spec(`toOpenApiPaths()` → emit-openapi 脚本),不参与 dispatch;
|
|
7
|
+
* Phase 3 逐步给 def 挂 handler 并接线 index.ts 主循环,路由器行为由本文件单测锁定。
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.RouteRegistry = void 0;
|
|
11
|
+
exports.toOpenApiPath = toOpenApiPath;
|
|
12
|
+
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
13
|
+
/** ':param' 形式 → OpenAPI '{param}' 形式。 */
|
|
14
|
+
function toOpenApiPath(path) {
|
|
15
|
+
return path.replace(/:([A-Za-z0-9_]+)/g, '{$1}');
|
|
16
|
+
}
|
|
17
|
+
class RouteRegistry {
|
|
18
|
+
defs = [];
|
|
19
|
+
operationIds = new Set();
|
|
20
|
+
compiled = null;
|
|
21
|
+
register(...defs) {
|
|
22
|
+
for (const def of defs) {
|
|
23
|
+
if (!def.path.startsWith('/'))
|
|
24
|
+
throw new Error(`RouteDef.path must start with '/': ${def.path}`);
|
|
25
|
+
if (!HTTP_METHODS.includes(def.method))
|
|
26
|
+
throw new Error(`RouteDef.method invalid: ${def.method}`);
|
|
27
|
+
if (!def.operationId)
|
|
28
|
+
throw new Error(`RouteDef.operationId required: ${def.method} ${def.path}`);
|
|
29
|
+
if (this.operationIds.has(def.operationId)) {
|
|
30
|
+
throw new Error(`Duplicate operationId: ${def.operationId}`);
|
|
31
|
+
}
|
|
32
|
+
this.operationIds.add(def.operationId);
|
|
33
|
+
this.defs.push(def);
|
|
34
|
+
this.compiled = null;
|
|
35
|
+
}
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
list() {
|
|
39
|
+
return this.defs;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* P5 Wave 迁移:给已 shadow 登记的 operationId 挂真实 handler。
|
|
43
|
+
* 幂等禁止——重复 attach 抛错(防两个迁移波次互相覆盖)。
|
|
44
|
+
*/
|
|
45
|
+
attachHandler(operationId, handler) {
|
|
46
|
+
const def = this.defs.find((d) => d.operationId === operationId);
|
|
47
|
+
if (!def)
|
|
48
|
+
throw new Error(`attachHandler: unknown operationId '${operationId}'(catalog 未登记?)`);
|
|
49
|
+
if (def.handler)
|
|
50
|
+
throw new Error(`attachHandler: '${operationId}' already has a handler`);
|
|
51
|
+
def.handler = handler;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
/** 已挂 handler 的 operationId 集合(迁移覆盖率测试用)。 */
|
|
55
|
+
attachedOperationIds() {
|
|
56
|
+
return new Set(this.defs.filter((d) => d.handler).map((d) => d.operationId));
|
|
57
|
+
}
|
|
58
|
+
compile() {
|
|
59
|
+
if (this.compiled)
|
|
60
|
+
return this.compiled;
|
|
61
|
+
this.compiled = this.defs.map((def) => {
|
|
62
|
+
const segments = def.path.split('/').filter((s) => s.length > 0);
|
|
63
|
+
const staticSegments = segments.filter((s) => !s.startsWith(':')).length;
|
|
64
|
+
return { def, segments, staticSegments };
|
|
65
|
+
});
|
|
66
|
+
return this.compiled;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 匹配:静态段多的优先(/api/automations/draft 先于 /api/automations/:id),
|
|
70
|
+
* 同特异度按注册序。pathname 只取 url 的 path 部分(剥 query)。
|
|
71
|
+
*/
|
|
72
|
+
match(method, url) {
|
|
73
|
+
const pathname = url.split('?')[0].replace(/\/+$/, '') || '/';
|
|
74
|
+
const pathnameSegments = pathname.split('/').filter((s) => s.length > 0);
|
|
75
|
+
const routes = this.compile()
|
|
76
|
+
.map((r, i) => ({ r, i }))
|
|
77
|
+
.sort((a, b) => b.r.staticSegments - a.r.staticSegments || a.i - b.i);
|
|
78
|
+
for (const { r } of routes) {
|
|
79
|
+
if (r.def.method !== method)
|
|
80
|
+
continue;
|
|
81
|
+
if (r.segments.length !== pathnameSegments.length)
|
|
82
|
+
continue;
|
|
83
|
+
const params = {};
|
|
84
|
+
let ok = true;
|
|
85
|
+
for (let i = 0; i < r.segments.length; i++) {
|
|
86
|
+
const seg = r.segments[i];
|
|
87
|
+
if (seg.startsWith(':')) {
|
|
88
|
+
params[seg.slice(1)] = decodeURIComponent(pathnameSegments[i]);
|
|
89
|
+
}
|
|
90
|
+
else if (seg !== pathnameSegments[i]) {
|
|
91
|
+
ok = false;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (ok)
|
|
96
|
+
return { def: r.def, params };
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
/** 生成 OpenAPI paths 片段(':param' → '{param}',同 path 多 method 合并)。 */
|
|
101
|
+
toOpenApiPaths() {
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const def of this.defs) {
|
|
104
|
+
const key = toOpenApiPath(def.path);
|
|
105
|
+
const entry = (out[key] ??= {});
|
|
106
|
+
const op = { operationId: def.operationId };
|
|
107
|
+
if (def.tags?.length)
|
|
108
|
+
op.tags = def.tags;
|
|
109
|
+
if (def.summary)
|
|
110
|
+
op.summary = def.summary;
|
|
111
|
+
const params = def.path.split('/').filter((s) => s.startsWith(':')).map((s) => ({
|
|
112
|
+
name: s.slice(1),
|
|
113
|
+
in: 'path',
|
|
114
|
+
required: true,
|
|
115
|
+
schema: { type: 'string' },
|
|
116
|
+
}));
|
|
117
|
+
if (params.length)
|
|
118
|
+
op.parameters = params;
|
|
119
|
+
op.responses = { 200: { description: 'OK' } };
|
|
120
|
+
entry[def.method.toLowerCase()] = op;
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.RouteRegistry = RouteRegistry;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* P4-B Shadow Route Catalog —— gateway 全量 HTTP 端点的声明式登记(无 handler)。
|
|
4
|
+
*
|
|
5
|
+
* 两个来源:
|
|
6
|
+
* - SDK_FACING_ROUTES:机械转录自 packages/gateway-sdk/contract/openapi.json
|
|
7
|
+
* (path+method+operationId+tags 与 openapi 逐字一致,'{param}' → ':param'),
|
|
8
|
+
* 漂移由 gateway-sdk/src/contract.test.ts 与 tests/unit/api-contract.test.ts 双向锁定。
|
|
9
|
+
* - NON_SDK_ROUTES:非 SDK 暴露面的补充登记(desktop/TUI/worker/dashboard 专用端点),
|
|
10
|
+
* operationId 自拟(ns.action 风格),tags 统一 'internal'。
|
|
11
|
+
*
|
|
12
|
+
* 刻意不登记:/mcp(传输端点非 REST)、/api/ws(websocket upgrade)、dashboard SPA
|
|
13
|
+
* fallback 与静态资源(/index.html、/assets/*、/static/*)、dashboard 委托内部子路由
|
|
14
|
+
* (/api/memory/{tier}/{id} 等,见 dashboard/api.ts)、/api/events?stream=true(与
|
|
15
|
+
* /api/events 同路径,不单独登记)。P5 Wave 2 反向审计后补齐:devices/mobile 全家、
|
|
16
|
+
* automations history、goals loops :n、A2A agent-card、runtime reload、permissions
|
|
17
|
+
* runtime 代理、legacy singular session delete。
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.buildRouteCatalog = buildRouteCatalog;
|
|
21
|
+
/** SDK-facing 端点(openapi.json 全量 94 条 operation,权威转录,勿手改——改请先改 openapi.json 再同步)。 */
|
|
22
|
+
const SDK_FACING_ROUTES = [
|
|
23
|
+
{ method: 'POST', path: '/api/session', operationId: 'session.create', tags: ['session'] },
|
|
24
|
+
{ method: 'GET', path: '/api/sessions', operationId: 'session.list', tags: ['session'] },
|
|
25
|
+
{ method: 'GET', path: '/api/sessions/:id', operationId: 'session.get', tags: ['session'] },
|
|
26
|
+
{ method: 'DELETE', path: '/api/sessions/:id', operationId: 'session.delete', tags: ['session'] },
|
|
27
|
+
{ method: 'PATCH', path: '/api/sessions/:id', operationId: 'session.rename', tags: ['session'] },
|
|
28
|
+
{ method: 'GET', path: '/api/sessions/:id/messages', operationId: 'session.messages', tags: ['session'] },
|
|
29
|
+
{ method: 'GET', path: '/api/sessions/:id/todo', operationId: 'session.todo', tags: ['session'] },
|
|
30
|
+
{ method: 'GET', path: '/api/sessions/:id/children', operationId: 'session.children', tags: ['session'] },
|
|
31
|
+
{ method: 'GET', path: '/api/sessions/:id/trajectory', operationId: 'session.trajectory', tags: ['session'] },
|
|
32
|
+
{ method: 'GET', path: '/api/sessions/:id/token-summary', operationId: 'session.tokenSummary', tags: ['session'] },
|
|
33
|
+
{ method: 'POST', path: '/api/sessions/:id/fork', operationId: 'session.fork', tags: ['session'] },
|
|
34
|
+
{ method: 'POST', path: '/api/sessions/:id/revert', operationId: 'session.revert', tags: ['session'] },
|
|
35
|
+
{ method: 'POST', path: '/api/sessions/:id/unrevert', operationId: 'session.unrevert', tags: ['session'] },
|
|
36
|
+
{ method: 'POST', path: '/api/session/:id/abort', operationId: 'session.abort', tags: ['session'] },
|
|
37
|
+
{ method: 'POST', path: '/api/session/:id/summarize', operationId: 'session.summarize', tags: ['session'] },
|
|
38
|
+
{ method: 'POST', path: '/api/session/:id/prompt', operationId: 'session.prompt', tags: ['session'] },
|
|
39
|
+
{ method: 'POST', path: '/api/session/:id/promptAsync', operationId: 'session.promptAsync', tags: ['session'] },
|
|
40
|
+
{ method: 'POST', path: '/api/session/:id/command', operationId: 'session.command', tags: ['session'] },
|
|
41
|
+
{ method: 'GET', path: '/api/usage', operationId: 'session.usage', tags: ['session'] },
|
|
42
|
+
{ method: 'GET', path: '/api/usage/summary', operationId: 'session.usageSummary', tags: ['session'] },
|
|
43
|
+
{ method: 'GET', path: '/api/usage/plugins', operationId: 'session.usagePluginsList', tags: ['session'] },
|
|
44
|
+
{ method: 'POST', path: '/api/usage/plugins/reload', operationId: 'session.usagePluginsReload', tags: ['session'] },
|
|
45
|
+
{ method: 'POST', path: '/api/usage/plugins/create', operationId: 'session.usagePluginsCreate', tags: ['session'] },
|
|
46
|
+
{ method: 'GET', path: '/api/usage/plugins/:name/source', operationId: 'session.usagePluginSourceGet', tags: ['session'] },
|
|
47
|
+
{ method: 'PUT', path: '/api/usage/plugins/:name/source', operationId: 'session.usagePluginSourcePut', tags: ['session'] },
|
|
48
|
+
{ method: 'DELETE', path: '/api/usage/plugins/:name', operationId: 'session.usagePluginsDelete', tags: ['session'] },
|
|
49
|
+
{ method: 'POST', path: '/api/usage/plugins/:name/test', operationId: 'session.usagePluginTest', tags: ['session'] },
|
|
50
|
+
{ method: 'GET', path: '/command', operationId: 'command.list', tags: ['command'] },
|
|
51
|
+
{ method: 'GET', path: '/skill', operationId: 'skill.list', tags: ['skill'] },
|
|
52
|
+
{ method: 'POST', path: '/api/mafw-commands/run', operationId: 'mafwCommands.run', tags: ['mafwCommands'] },
|
|
53
|
+
{ method: 'GET', path: '/api/manager/session', operationId: 'manager.session', tags: ['manager'] },
|
|
54
|
+
{ method: 'POST', path: '/api/manager/session/rotate', operationId: 'manager.rotate', tags: ['manager'] },
|
|
55
|
+
{ method: 'GET', path: '/api/projects', operationId: 'project.list', tags: ['project'] },
|
|
56
|
+
{ method: 'GET', path: '/api/projects/current', operationId: 'project.current', tags: ['project'] },
|
|
57
|
+
{ method: 'POST', path: '/register', operationId: 'project.setCurrent', tags: ['project'] },
|
|
58
|
+
{ method: 'GET', path: '/api/events', operationId: 'event.subscribe', tags: ['event'] },
|
|
59
|
+
{ method: 'POST', path: '/api/events', operationId: 'event.publish', tags: ['event'] },
|
|
60
|
+
{ method: 'GET', path: '/api/runtime', operationId: 'runtime.get', tags: ['runtime'] },
|
|
61
|
+
{ method: 'POST', path: '/api/runtime/switch', operationId: 'runtime.switch', tags: ['runtime'] },
|
|
62
|
+
{ method: 'POST', path: '/api/runtime/restart-agent', operationId: 'runtime.restartAgent', tags: ['runtime'] },
|
|
63
|
+
// runtime.reload(POST /api/runtime/reload)在下方 NON_SDK 补遗块登记(SDK 不调用 → internal)
|
|
64
|
+
{ method: 'GET', path: '/api/plugins', operationId: 'plugins.list', tags: ['plugins'] },
|
|
65
|
+
{ method: 'POST', path: '/api/plugins/install', operationId: 'plugins.install', tags: ['plugins'] },
|
|
66
|
+
{ method: 'POST', path: '/api/plugins/enable', operationId: 'plugins.enable', tags: ['plugins'] },
|
|
67
|
+
{ method: 'POST', path: '/api/plugins/disable', operationId: 'plugins.disable', tags: ['plugins'] },
|
|
68
|
+
{ method: 'POST', path: '/api/plugins/delete', operationId: 'plugins.delete', tags: ['plugins'] },
|
|
69
|
+
{ method: 'GET', path: '/api/config', operationId: 'config.get', tags: ['config'] },
|
|
70
|
+
{ method: 'PUT', path: '/api/config', operationId: 'config.set', tags: ['config'] },
|
|
71
|
+
{ method: 'GET', path: '/api/opencode-config', operationId: 'opencodeConfig.get', tags: ['opencodeConfig'] },
|
|
72
|
+
{ method: 'PATCH', path: '/api/opencode-config', operationId: 'opencodeConfig.update', tags: ['opencodeConfig'] },
|
|
73
|
+
{ method: 'GET', path: '/api/goals', operationId: 'goals.list', tags: ['goals'] },
|
|
74
|
+
{ method: 'GET', path: '/api/goals/:id', operationId: 'goals.get', tags: ['goals'] },
|
|
75
|
+
{ method: 'GET', path: '/api/goals/:id/sessions', operationId: 'goals.sessions', tags: ['goals'] },
|
|
76
|
+
{ method: 'POST', path: '/api/goals/:goalId/questions/:questionId/respond', operationId: 'goals.respondQuestion', tags: ['goals'] },
|
|
77
|
+
{ method: 'POST', path: '/api/work/:goalId/validate', operationId: 'goals.validate', tags: ['goals'] },
|
|
78
|
+
{ method: 'POST', path: '/api/goals/control', operationId: 'goals.control', tags: ['goals'] },
|
|
79
|
+
{ method: 'GET', path: '/api/memory/search', operationId: 'memory.search', tags: ['memory'] },
|
|
80
|
+
{ method: 'GET', path: '/api/memory/merged-search', operationId: 'memory.mergedSearch', tags: ['memory'] },
|
|
81
|
+
{ method: 'GET', path: '/api/memory/energy-distribution', operationId: 'memory.getEnergyDistribution', tags: ['memory'] },
|
|
82
|
+
{ method: 'GET', path: '/api/l5/axioms', operationId: 'memory.getL5Axioms', tags: ['memory'] },
|
|
83
|
+
{ method: 'DELETE', path: '/api/memory/:id', operationId: 'memory.delete', tags: ['memory'] },
|
|
84
|
+
{ method: 'GET', path: '/api/memory/sticky', operationId: 'memory.listSticky', tags: ['memory'] },
|
|
85
|
+
{ method: 'POST', path: '/api/memory/pin', operationId: 'memory.setSticky', tags: ['memory'] },
|
|
86
|
+
{ method: 'GET', path: '/api/memory/embedding-config', operationId: 'embedding.get', tags: ['embedding'] },
|
|
87
|
+
{ method: 'POST', path: '/api/memory/embedding-config', operationId: 'embedding.update', tags: ['embedding'] },
|
|
88
|
+
{ method: 'GET', path: '/api/approvals', operationId: 'approvals.list', tags: ['approvals'] },
|
|
89
|
+
{ method: 'POST', path: '/api/approvals/:id/respond', operationId: 'approvals.respond', tags: ['approvals'] },
|
|
90
|
+
{ method: 'GET', path: '/api/triage', operationId: 'triage.list', tags: ['triage'] },
|
|
91
|
+
{ method: 'POST', path: '/api/triage/:id/dismiss', operationId: 'triage.dismiss', tags: ['triage'] },
|
|
92
|
+
{ method: 'POST', path: '/api/triage/:id/confirm', operationId: 'triage.confirm', tags: ['triage'] },
|
|
93
|
+
{ method: 'POST', path: '/api/triage/:id/reject', operationId: 'triage.reject', tags: ['triage'] },
|
|
94
|
+
{ method: 'POST', path: '/api/triage/:id/propose', operationId: 'triage.propose', tags: ['triage'] },
|
|
95
|
+
{ method: 'GET', path: '/api/questions', operationId: 'questions.list', tags: ['questions'] },
|
|
96
|
+
{ method: 'POST', path: '/api/questions/:id/reply', operationId: 'questions.reply', tags: ['questions'] },
|
|
97
|
+
{ method: 'POST', path: '/api/questions/:id/reject', operationId: 'questions.reject', tags: ['questions'] },
|
|
98
|
+
{ method: 'GET', path: '/api/permissions', operationId: 'permissions.list', tags: ['permissions'] },
|
|
99
|
+
{ method: 'POST', path: '/api/permissions/:id/reply', operationId: 'permissions.reply', tags: ['permissions'] },
|
|
100
|
+
{ method: 'POST', path: '/api/chat', operationId: 'chat.send', tags: ['chat'] },
|
|
101
|
+
{ method: 'POST', path: '/api/chat/enriched', operationId: 'chat.sendEnriched', tags: ['chat'] },
|
|
102
|
+
{ method: 'GET', path: '/api/media/plugins', operationId: 'media.plugins', tags: ['media'] },
|
|
103
|
+
{ method: 'POST', path: '/api/media/switch', operationId: 'media.switch', tags: ['media'] },
|
|
104
|
+
{ method: 'POST', path: '/api/media/upload', operationId: 'media.upload', tags: ['media'] },
|
|
105
|
+
{ method: 'POST', path: '/api/media/upload-and-create', operationId: 'media.uploadAndCreate', tags: ['media'] },
|
|
106
|
+
{ method: 'POST', path: '/a2a', operationId: 'media.createTask', tags: ['media'] },
|
|
107
|
+
{ method: 'GET', path: '/a2a/artifacts/:id', operationId: 'media.artifact', tags: ['media'] },
|
|
108
|
+
{ method: 'POST', path: '/api/tts', operationId: 'tts.speak', tags: ['tts'] },
|
|
109
|
+
{ method: 'GET', path: '/api/tts/voices', operationId: 'tts.voices', tags: ['tts'] },
|
|
110
|
+
{ method: 'POST', path: '/api/tts/stream', operationId: 'tts.speakStream', tags: ['tts'] },
|
|
111
|
+
{ method: 'GET', path: '/api/provider', operationId: 'providers.list', tags: ['providers'] },
|
|
112
|
+
{ method: 'GET', path: '/api/agents', operationId: 'agents.list', tags: ['agents'] },
|
|
113
|
+
{ method: 'GET', path: '/api/model-config', operationId: 'models.get', tags: ['models'] },
|
|
114
|
+
{ method: 'POST', path: '/api/model-config', operationId: 'models.update', tags: ['models'] },
|
|
115
|
+
{ method: 'GET', path: '/api/automations', operationId: 'automations.list', tags: ['automations'] },
|
|
116
|
+
{ method: 'PUT', path: '/api/automations/:id', operationId: 'automations.toggle', tags: ['automations'] },
|
|
117
|
+
{ method: 'POST', path: '/api/automations/draft', operationId: 'automations.draft', tags: ['automations'] },
|
|
118
|
+
];
|
|
119
|
+
/** 非 SDK 端点补充登记(desktop/TUI/worker/dashboard 专用面),operationId 自拟,tags 'internal'。 */
|
|
120
|
+
const NON_SDK_ROUTES = [
|
|
121
|
+
// Python 内核(§5.17)
|
|
122
|
+
{ method: 'POST', path: '/api/python/execute', operationId: 'python.execute', tags: ['internal'] },
|
|
123
|
+
{ method: 'POST', path: '/api/python/restart', operationId: 'python.restart', tags: ['internal'] },
|
|
124
|
+
{ method: 'GET', path: '/api/python/status', operationId: 'python.status', tags: ['internal'] },
|
|
125
|
+
// 观察捕获 / 边界 recall / pinned 披露层(§5.11/§5.13)
|
|
126
|
+
{ method: 'POST', path: '/api/obs/capture', operationId: 'obs.capture', tags: ['internal'] },
|
|
127
|
+
{ method: 'GET', path: '/api/recall/context', operationId: 'recall.context', tags: ['internal'] },
|
|
128
|
+
{ method: 'GET', path: '/api/recall/pinned', operationId: 'recall.pinned', tags: ['internal'] },
|
|
129
|
+
// 记忆补充(memory.mergedSearch 已在 SDK-facing,不重复)
|
|
130
|
+
{ method: 'POST', path: '/api/memory/add', operationId: 'memory.add', tags: ['internal'] },
|
|
131
|
+
{ method: 'GET', path: '/api/memory/get', operationId: 'memory.get', tags: ['internal'] },
|
|
132
|
+
{ method: 'GET', path: '/api/memory/stats', operationId: 'memory.stats', tags: ['internal'] },
|
|
133
|
+
{ method: 'POST', path: '/api/memory/embeddings/backfill', operationId: 'memory.embeddingsBackfill', tags: ['internal'] },
|
|
134
|
+
{ method: 'GET', path: '/api/l5/heuristics', operationId: 'memory.getL5Heuristics', tags: ['internal'] },
|
|
135
|
+
// Goal / 编排(§5.20)
|
|
136
|
+
{ method: 'GET', path: '/api/goals/:id/loops', operationId: 'goals.loops', tags: ['internal'] },
|
|
137
|
+
{ method: 'POST', path: '/api/work/:goalId/complete', operationId: 'goals.complete', tags: ['internal'] },
|
|
138
|
+
{ method: 'POST', path: '/control', operationId: 'goals.controlLegacy', summary: 'MCP 兼容别名,与 /api/goals/control 同 handler', tags: ['internal'] },
|
|
139
|
+
{ method: 'GET', path: '/api/orchestration/outcomes', operationId: 'orchestration.outcomes', tags: ['internal'] },
|
|
140
|
+
// 媒体补充(media.upload / uploadAndCreate / switch / plugins GET / a2a / artifacts GET 已在 SDK-facing)
|
|
141
|
+
{ method: 'POST', path: '/api/media/create-task', operationId: 'media.createTaskApi', tags: ['internal'] },
|
|
142
|
+
{ method: 'GET', path: '/api/media/resolve-task/:id', operationId: 'media.resolveTask', tags: ['internal'] },
|
|
143
|
+
{ method: 'POST', path: '/api/media/analyze-audio', operationId: 'media.analyzeAudio', tags: ['internal'] },
|
|
144
|
+
{ method: 'POST', path: '/api/media/plugins/reload', operationId: 'media.pluginsReload', tags: ['internal'] },
|
|
145
|
+
// 用量 dashboard / 健康
|
|
146
|
+
{ method: 'GET', path: '/api/stats', operationId: 'dashboard.stats', tags: ['internal'] },
|
|
147
|
+
{ method: 'GET', path: '/api/costs/:window', operationId: 'dashboard.costs', tags: ['internal'] },
|
|
148
|
+
{ method: 'GET', path: '/api/alignment', operationId: 'dashboard.alignment', tags: ['internal'] },
|
|
149
|
+
{ method: 'POST', path: '/api/feedback', operationId: 'feedback.record', tags: ['internal'] },
|
|
150
|
+
{ method: 'GET', path: '/api/user-answers/:id', operationId: 'dashboard.userAnswer', tags: ['internal'] },
|
|
151
|
+
{ method: 'GET', path: '/api/sessions/:id/metrics', operationId: 'session.metrics', tags: ['internal'] },
|
|
152
|
+
{ method: 'GET', path: '/api/health', operationId: 'health.check', tags: ['internal'] },
|
|
153
|
+
// Gateway 控制(goal PAUSE/ABORT 等控制面)
|
|
154
|
+
{ method: 'POST', path: '/api/gateway/pause', operationId: 'gateway.pause', tags: ['internal'] },
|
|
155
|
+
{ method: 'POST', path: '/api/gateway/resume', operationId: 'gateway.resume', tags: ['internal'] },
|
|
156
|
+
{ method: 'POST', path: '/api/gateway/cancel', operationId: 'gateway.cancel', tags: ['internal'] },
|
|
157
|
+
{ method: 'POST', path: '/api/gateway/checkpoint', operationId: 'gateway.checkpoint', tags: ['internal'] },
|
|
158
|
+
{ method: 'POST', path: '/api/gateway/rollback', operationId: 'gateway.rollback', tags: ['internal'] },
|
|
159
|
+
// 移动端(P5 Wave 2 反向审计后全量登记)
|
|
160
|
+
{ method: 'POST', path: '/api/mobile/media/tasks/:id/ask', operationId: 'mobile.mediaTaskAsk', tags: ['internal'] },
|
|
161
|
+
{ method: 'POST', path: '/api/mobile/media/tasks', operationId: 'mobile.mediaTaskUpload', tags: ['internal'] },
|
|
162
|
+
{ method: 'GET', path: '/api/mobile/tts/artifacts/:id', operationId: 'mobile.ttsArtifact', tags: ['internal'] },
|
|
163
|
+
{ method: 'GET', path: '/api/mobile/devices', operationId: 'mobile.devicesList', tags: ['internal'] },
|
|
164
|
+
{ method: 'DELETE', path: '/api/mobile/devices/:id', operationId: 'mobile.devicesDelete', tags: ['internal'] },
|
|
165
|
+
{ method: 'POST', path: '/api/mobile/devices/register', operationId: 'mobile.devicesRegister', tags: ['internal'] },
|
|
166
|
+
{ method: 'GET', path: '/api/mobile/pairing-code', operationId: 'mobile.pairingCode', tags: ['internal'] },
|
|
167
|
+
{ method: 'POST', path: '/api/mobile/pairing/verify', operationId: 'mobile.pairingVerify', tags: ['internal'] },
|
|
168
|
+
// 设备注册(mobile 的 legacy 别名族)
|
|
169
|
+
{ method: 'POST', path: '/api/devices', operationId: 'devices.register', tags: ['internal'] },
|
|
170
|
+
{ method: 'GET', path: '/api/devices', operationId: 'devices.list', tags: ['internal'] },
|
|
171
|
+
{ method: 'DELETE', path: '/api/devices/:id', operationId: 'devices.delete', tags: ['internal'] },
|
|
172
|
+
// P5 Wave 2 反向审计补遗
|
|
173
|
+
{ method: 'GET', path: '/.well-known/agent-card.json', operationId: 'a2a.agentCard', tags: ['internal'] },
|
|
174
|
+
{ method: 'GET', path: '/api/automations/:id/history', operationId: 'automations.history', tags: ['internal'] },
|
|
175
|
+
{ method: 'GET', path: '/api/goals/:id/loops/:n', operationId: 'goals.loopsAt', tags: ['internal'] },
|
|
176
|
+
{ method: 'POST', path: '/api/runtime/reload', operationId: 'runtime.reload', tags: ['internal'] },
|
|
177
|
+
{ method: 'POST', path: '/api/sessions/:sid/permissions/:rid', operationId: 'permissions.replyRuntime', tags: ['internal'] },
|
|
178
|
+
{ method: 'DELETE', path: '/api/session/:id', operationId: 'session.deleteLegacy', tags: ['internal'] },
|
|
179
|
+
// Eval / LLM 压缩
|
|
180
|
+
{ method: 'POST', path: '/api/eval/chat/completions', operationId: 'eval.chatCompletions', tags: ['internal'] },
|
|
181
|
+
{ method: 'POST', path: '/api/llm/compress', operationId: 'llm.compress', tags: ['internal'] },
|
|
182
|
+
// 跨 worktree 记忆融合(§5.3)
|
|
183
|
+
{ method: 'POST', path: '/api/merge-memory', operationId: 'memory.merge', tags: ['internal'] },
|
|
184
|
+
// 根健康检查(dashboard/桌面探测用)
|
|
185
|
+
{ method: 'GET', path: '/health', operationId: 'health.root', tags: ['internal'] },
|
|
186
|
+
];
|
|
187
|
+
/** gateway 全量 HTTP 端点 shadow 登记(无 handler;Phase 3 逐条挂 handler 前不参与 dispatch)。
|
|
188
|
+
* 每次调用返回全新 def 对象(浅拷贝)——attachHandler 会原地改写 def.handler,
|
|
189
|
+
* 共享模块级常量会让第二次 build 的 registry 带上一次的 handler(测试已抓过)。 */
|
|
190
|
+
function buildRouteCatalog() {
|
|
191
|
+
return [...SDK_FACING_ROUTES, ...NON_SDK_ROUTES].map((d) => ({ ...d }));
|
|
192
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleTriageDismiss = handleTriageDismiss;
|
|
4
|
+
function json(res, status, payload) {
|
|
5
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
6
|
+
res.end(JSON.stringify(payload));
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* POST /api/triage/:id/dismiss — 忽略 triage 项(SDK triage.dismiss 的服务端落地)。
|
|
10
|
+
* 语义 = reject(state REJECTED),仅账本 reason 区分(triage_dismissed),
|
|
11
|
+
* 供 SDK contract 对齐:此前该路由不存在,调用会落入 dashboard 兜底返回 HTML。
|
|
12
|
+
* 返回 handled=false 表示路径不匹配,交给后续路由。
|
|
13
|
+
*/
|
|
14
|
+
async function handleTriageDismiss(req, res, url, deps) {
|
|
15
|
+
const m = url.match(/^\/api\/triage\/([^/]+)\/dismiss(?:\?|$)/);
|
|
16
|
+
if (!m || req.method !== 'POST')
|
|
17
|
+
return false;
|
|
18
|
+
const triageId = decodeURIComponent(m[1]);
|
|
19
|
+
const ok = deps.rejectTriage(triageId);
|
|
20
|
+
try {
|
|
21
|
+
deps.appendLedger?.({
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
event: 'AUTOMATION_TRIGGERED',
|
|
24
|
+
source: 'user',
|
|
25
|
+
reason: 'triage_dismissed',
|
|
26
|
+
details: { triageId },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch { /* 账本失败不阻塞 */ }
|
|
30
|
+
json(res, 200, { status: ok ? 'dismissed' : 'not_found' });
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// /waitwhat command (UI-driven, via /api/mafw-commands/run): the user signals
|
|
3
|
+
// "that last reply didn't land" and the agent re-pitches its own last message
|
|
4
|
+
// in simplified language, using the project's CONTEXT.md glossary when one
|
|
5
|
+
// exists. Inspired by mattpocock/skills wait-what (MIT).
|
|
6
|
+
//
|
|
7
|
+
// The re-pitch is prompted INTO THE SAME session so every connected client
|
|
8
|
+
// (desktop / TUI / opencode) sees the answer inline via the event stream.
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.buildWaitwhatPrompt = buildWaitwhatPrompt;
|
|
11
|
+
exports.runWaitwhat = runWaitwhat;
|
|
12
|
+
function buildWaitwhatPrompt(original) {
|
|
13
|
+
return [
|
|
14
|
+
'[/waitwhat] 用户没看懂你上一条回复。请把它重述一遍:',
|
|
15
|
+
'1. 先用一两句补上"我们在做什么、刚才说到哪"的上下文定位;',
|
|
16
|
+
'2. 然后用简明语言重述:短句、一次一个概念、避免嵌套行话(STE100 简化技术英语的风格);',
|
|
17
|
+
'3. 如果项目根目录有 CONTEXT.md,先读它并用其中的项目术语(ubiquitous language)来表达;',
|
|
18
|
+
'4. 只是重述,不要新增内容、不引入新话题。',
|
|
19
|
+
'',
|
|
20
|
+
'—— 需要重述的上一条回复原文 ——',
|
|
21
|
+
original,
|
|
22
|
+
].join('\n');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Re-pitch the last assistant message of the session. Returns ok:false with
|
|
26
|
+
* an error string (without prompting) when there is nothing to re-pitch.
|
|
27
|
+
*/
|
|
28
|
+
async function runWaitwhat(sessionID, deps) {
|
|
29
|
+
const messages = await deps.listMessages(sessionID);
|
|
30
|
+
const lastAssistant = [...(Array.isArray(messages) ? messages : [])]
|
|
31
|
+
.reverse()
|
|
32
|
+
.find((m) => m?.info?.role === 'assistant');
|
|
33
|
+
const original = (lastAssistant?.parts || [])
|
|
34
|
+
.filter((p) => p?.type === 'text' && typeof p?.text === 'string')
|
|
35
|
+
.map((p) => p.text)
|
|
36
|
+
.join('\n')
|
|
37
|
+
.trim();
|
|
38
|
+
if (!original) {
|
|
39
|
+
return { ok: false, error: 'no assistant message to re-pitch' };
|
|
40
|
+
}
|
|
41
|
+
await deps.promptAsync(sessionID, buildWaitwhatPrompt(original));
|
|
42
|
+
return { ok: true };
|
|
43
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.attachWave1Handlers = attachWave1Handlers;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
const goal_sessions_1 = require("./goal-sessions");
|
|
6
|
+
const triage_dismiss_1 = require("./triage-dismiss");
|
|
7
|
+
const manager_rotate_1 = require("./manager-rotate");
|
|
8
|
+
const embedding_config_1 = require("./embedding-config");
|
|
9
|
+
const model_config_1 = require("./model-config");
|
|
10
|
+
const session_branch_1 = require("./session-branch");
|
|
11
|
+
const session_mutations_1 = require("./session-mutations");
|
|
12
|
+
const session_summarize_1 = require("./session-summarize");
|
|
13
|
+
const event_publish_1 = require("./event-publish");
|
|
14
|
+
const media_switch_1 = require("./media-switch");
|
|
15
|
+
const usage_plugins_1 = require("./usage-plugins");
|
|
16
|
+
/** 绑定 Wave 1 的 22 条路由。index.ts 在 createServer 之前调用一次。 */
|
|
17
|
+
function attachWave1Handlers(registry, gw) {
|
|
18
|
+
const H = (fn) => fn;
|
|
19
|
+
registry.attachHandler('goals.sessions', H(async (req, res, params) => (0, goal_sessions_1.handleGoalSessions)(req, res, req.url || '', {
|
|
20
|
+
listGoalSessions: (goalId) => gw.getGatewayDb().listGoalSessions(goalId),
|
|
21
|
+
getSession: (sessionID) => gw.opencodeClient?.session.get({ sessionID }).catch(() => null),
|
|
22
|
+
})));
|
|
23
|
+
registry.attachHandler('triage.dismiss', H(async (req, res, params) => (0, triage_dismiss_1.handleTriageDismiss)(req, res, req.url || '', {
|
|
24
|
+
rejectTriage: (id) => gw.automationEngine?.rejectTriage(id) ?? false,
|
|
25
|
+
appendLedger: (entry) => gw.ledger?.append(entry),
|
|
26
|
+
})));
|
|
27
|
+
registry.attachHandler('manager.rotate', H(async (req, res) => (0, manager_rotate_1.handleManagerRotate)(req, res, gw.rotateDeps())));
|
|
28
|
+
registry.attachHandler('embedding.get', H(async (req, res) => (0, embedding_config_1.handleEmbeddingConfigGet)(req, res, gw.embeddingConfigDeps())));
|
|
29
|
+
registry.attachHandler('embedding.update', H(async (req, res) => (0, embedding_config_1.handleEmbeddingConfigUpdate)(req, res, gw.embeddingConfigDeps())));
|
|
30
|
+
registry.attachHandler('models.get', H(async (req, res) => (0, model_config_1.handleModelConfigGet)(req, res, gw.modelConfigDeps())));
|
|
31
|
+
registry.attachHandler('models.update', H(async (req, res) => (0, model_config_1.handleModelConfigUpdate)(req, res, gw.modelConfigDeps())));
|
|
32
|
+
registry.attachHandler('session.fork', H(async (req, res, params) => (0, session_branch_1.handleSessionBranch)(req, res, req.url || '', { getRuntime: () => gw.opencodeClient })));
|
|
33
|
+
registry.attachHandler('session.revert', H(async (req, res, params) => (0, session_branch_1.handleSessionBranch)(req, res, req.url || '', { getRuntime: () => gw.opencodeClient })));
|
|
34
|
+
registry.attachHandler('session.unrevert', H(async (req, res, params) => (0, session_branch_1.handleSessionBranch)(req, res, req.url || '', { getRuntime: () => gw.opencodeClient })));
|
|
35
|
+
registry.attachHandler('session.delete', H(async (req, res) => (0, session_mutations_1.handleSessionMutations)(req, res, {
|
|
36
|
+
getCapabilities: () => gw.runtimeCaps,
|
|
37
|
+
getClient: () => (gw.opencodeClient ?? null),
|
|
38
|
+
})));
|
|
39
|
+
registry.attachHandler('session.rename', H(async (req, res) => (0, session_mutations_1.handleSessionMutations)(req, res, {
|
|
40
|
+
getCapabilities: () => gw.runtimeCaps,
|
|
41
|
+
getClient: () => (gw.opencodeClient ?? null),
|
|
42
|
+
})));
|
|
43
|
+
registry.attachHandler('session.summarize', H(async (req, res) => (0, session_summarize_1.handleSessionSummarize)(req, res, req.url || '', { getRuntime: () => gw.opencodeClient })));
|
|
44
|
+
registry.attachHandler('event.publish', H(async (req, res) => (0, event_publish_1.handleEventPublish)({ broadcast: (e) => gw.broadcast(e) }, req, res)));
|
|
45
|
+
registry.attachHandler('media.switch', H(async (req, res) => (0, media_switch_1.handleMediaSwitch)(req, res, {
|
|
46
|
+
persist: (o) => config_1.config.persistOverrides(o),
|
|
47
|
+
reloadPlugins: async () => { await gw.mediaPluginLoader?.reload(); },
|
|
48
|
+
availableEngines: () => (gw.mediaPluginLoader?.getState().map((s) => s.name).filter((n) => !!n) ?? []),
|
|
49
|
+
currentMedia: () => config_1.config.raw.media,
|
|
50
|
+
})));
|
|
51
|
+
const usageDeps = () => gw.usagePluginsDeps();
|
|
52
|
+
registry.attachHandler('session.usagePluginsList', H(async (req, res) => (0, usage_plugins_1.handleUsagePluginsList)(req, res, usageDeps())));
|
|
53
|
+
registry.attachHandler('session.usagePluginsReload', H(async (req, res) => {
|
|
54
|
+
await gw.pluginLoader?.reload();
|
|
55
|
+
await (0, usage_plugins_1.handleUsagePluginsList)(req, res, usageDeps());
|
|
56
|
+
}));
|
|
57
|
+
registry.attachHandler('session.usagePluginsCreate', H(async (req, res) => (0, usage_plugins_1.handleUsagePluginCreate)(req, res, usageDeps())));
|
|
58
|
+
registry.attachHandler('session.usagePluginSourceGet', H(async (req, res, params) => (0, usage_plugins_1.handleUsagePluginSourceGet)(req, res, usageDeps(), decodeURIComponent(params.name))));
|
|
59
|
+
registry.attachHandler('session.usagePluginSourcePut', H(async (req, res, params) => (0, usage_plugins_1.handleUsagePluginSourcePut)(req, res, usageDeps(), decodeURIComponent(params.name))));
|
|
60
|
+
registry.attachHandler('session.usagePluginTest', H(async (req, res, params) => (0, usage_plugins_1.handleUsagePluginTest)(req, res, usageDeps(), decodeURIComponent(params.name))));
|
|
61
|
+
registry.attachHandler('session.usagePluginsDelete', H(async (req, res, params) => (0, usage_plugins_1.handleUsagePluginDelete)(req, res, usageDeps(), decodeURIComponent(params.name))));
|
|
62
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.attachWave2Handlers = attachWave2Handlers;
|
|
4
|
+
const runtime_switch_1 = require("./runtime-switch");
|
|
5
|
+
const restart_agent_1 = require("./restart-agent");
|
|
6
|
+
const plugins_1 = require("./plugins");
|
|
7
|
+
const hub_1 = require("../plugins/hub");
|
|
8
|
+
const logger_1 = require("../core/utils/logger");
|
|
9
|
+
/** 绑定 Wave 2 的 9 条路由。index.ts 在 createServer 之前调用一次。 */
|
|
10
|
+
function attachWave2Handlers(registry, gw) {
|
|
11
|
+
const H = (fn) => fn;
|
|
12
|
+
registry.attachHandler('runtime.get', H(async (req, res) => (0, runtime_switch_1.handleRuntimeGet)(req, res, gw.runtimeDeps())));
|
|
13
|
+
registry.attachHandler('runtime.switch', H(async (req, res) => {
|
|
14
|
+
if (gw.runtimeSwitchBlocked()) {
|
|
15
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
16
|
+
res.end(JSON.stringify({ error: 'Cannot switch runtime during agent restart' }));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
gw.beginRuntimeSwitch();
|
|
20
|
+
try {
|
|
21
|
+
await (0, runtime_switch_1.handleRuntimeSwitch)(req, res, gw.runtimeDeps());
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
gw.endRuntimeSwitch();
|
|
25
|
+
}
|
|
26
|
+
}));
|
|
27
|
+
registry.attachHandler('runtime.reload', H(async (req, res) => (0, runtime_switch_1.handleRuntimeReload)(req, res, gw.runtimeDeps())));
|
|
28
|
+
registry.attachHandler('runtime.restartAgent', H(async (req, res) => (0, restart_agent_1.handleRestartAgent)(req, res, gw.restartAgentDeps())));
|
|
29
|
+
const withCleanup = async (req, fn) => {
|
|
30
|
+
try {
|
|
31
|
+
const cleaned = (0, hub_1.cleanupExamples)(gw.pluginHubDeps().hub);
|
|
32
|
+
if (cleaned.removed.length)
|
|
33
|
+
logger_1.log.info(`[PluginsHub] removed stale examples: ${cleaned.removed.length}`);
|
|
34
|
+
if (cleaned.failed.length)
|
|
35
|
+
logger_1.log.warn(`[PluginsHub] cleanupExamples failed: ${cleaned.failed.join(', ')}`);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
logger_1.log.warn(`[PluginsHub] cleanupExamples error: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
await fn();
|
|
41
|
+
};
|
|
42
|
+
registry.attachHandler('plugins.list', H(async (req, res) => withCleanup(req, () => (0, plugins_1.handlePluginsList)(req, res, gw.pluginHubDeps()))));
|
|
43
|
+
registry.attachHandler('plugins.install', H(async (req, res) => withCleanup(req, () => (0, plugins_1.handlePluginsInstall)(req, res, gw.pluginHubDeps()))));
|
|
44
|
+
registry.attachHandler('plugins.enable', H(async (req, res) => withCleanup(req, () => (0, plugins_1.handlePluginsEnable)(req, res, gw.pluginHubDeps()))));
|
|
45
|
+
registry.attachHandler('plugins.disable', H(async (req, res) => withCleanup(req, () => (0, plugins_1.handlePluginsDisable)(req, res, gw.pluginHubDeps()))));
|
|
46
|
+
registry.attachHandler('plugins.delete', H(async (req, res) => withCleanup(req, () => (0, plugins_1.handlePluginsDelete)(req, res, gw.pluginHubDeps()))));
|
|
47
|
+
}
|
|
@@ -15,7 +15,10 @@ function fullCapabilities() {
|
|
|
15
15
|
agentProcessApi: true,
|
|
16
16
|
completionApi: true,
|
|
17
17
|
sessionBranchApi: true,
|
|
18
|
-
|
|
18
|
+
// opencode 适配器不转发 maxTurns/maxCostUsd(SDK 无对应字段)——预算由
|
|
19
|
+
// gateway 侧 BudgetGuard 承担;声明 true 会让 attachBudgetGuardForGoal
|
|
20
|
+
// 跳过挂载,goal 预算在默认 runtime 上完全失效。
|
|
21
|
+
turnBudgetApi: false,
|
|
19
22
|
questionApi: true,
|
|
20
23
|
};
|
|
21
24
|
}
|
|
@@ -12,6 +12,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.opencodeBroadcast = opencodeBroadcast;
|
|
13
13
|
exports.projectRegisteredEvent = projectRegisteredEvent;
|
|
14
14
|
function opencodeBroadcast(data, internal) {
|
|
15
|
+
if (typeof data?.type !== 'string' || !data.type) {
|
|
16
|
+
// 形状守卫(fail-open):信封照常返回,但给出可定位诊断——畸形事件
|
|
17
|
+
// 在桌面/TUI 是"静默忽略",没有这行日志时插件作者无从排查。
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
console.error(`[EventBroadcast] malformed opencode_event data: 'type' must be a non-empty string ` +
|
|
20
|
+
`(got ${JSON.stringify(data?.type)}); payload keys: ${data ? Object.keys(data).join(',') : '(null)'} ` +
|
|
21
|
+
`— downstream (desktop/TUI) will ignore this event`);
|
|
22
|
+
}
|
|
15
23
|
return {
|
|
16
24
|
type: 'opencode_event',
|
|
17
25
|
data: {
|
|
@@ -54,6 +54,7 @@ class RuntimePluginLoader {
|
|
|
54
54
|
meta = new Map();
|
|
55
55
|
state = new Map();
|
|
56
56
|
builtins = new Map();
|
|
57
|
+
packageEntries = new Map();
|
|
57
58
|
constructor(pluginsDir) {
|
|
58
59
|
this.pluginsDir = pluginsDir;
|
|
59
60
|
}
|
|
@@ -149,12 +150,24 @@ class RuntimePluginLoader {
|
|
|
149
150
|
logger_1.log.warn(`[RuntimePluginLoader] ${file} load error: ${err.message}`);
|
|
150
151
|
}
|
|
151
152
|
}
|
|
153
|
+
/** PluginHost 推送的包贡献。查找顺序:legacy 文件 > 包 > 内置。scan() 不影响。 */
|
|
154
|
+
setPackageEntries(entries) {
|
|
155
|
+
this.packageEntries = new Map(entries.map((e) => [e.name, e]));
|
|
156
|
+
}
|
|
152
157
|
/** 插件不存在或未通过校验时返回 undefined(调用方回退内置 opencode)。文件插件优先于内置。 */
|
|
153
158
|
get(name) {
|
|
154
159
|
const createRuntime = this.factories.get(name);
|
|
155
160
|
const meta = this.meta.get(name);
|
|
156
161
|
if (createRuntime && meta)
|
|
157
162
|
return { createRuntime, ...meta };
|
|
163
|
+
const pkg = this.packageEntries.get(name);
|
|
164
|
+
if (pkg) {
|
|
165
|
+
return {
|
|
166
|
+
createRuntime: pkg.createRuntime,
|
|
167
|
+
capabilities: { ...(0, contract_1.minimalCapabilities)(), ...pkg.capabilities },
|
|
168
|
+
external: pkg.external,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
158
171
|
const builtin = this.builtins.get(name);
|
|
159
172
|
if (builtin)
|
|
160
173
|
return { createRuntime: builtin.factory, ...builtin };
|
|
@@ -168,16 +181,24 @@ class RuntimePluginLoader {
|
|
|
168
181
|
for (const [name, b] of this.builtins) {
|
|
169
182
|
this.state.set(`builtin:${name}`, { file: `builtin:${name}`, name, status: 'ok', capabilities: b.capabilities });
|
|
170
183
|
}
|
|
184
|
+
for (const [name, p] of this.packageEntries) {
|
|
185
|
+
this.state.set(`package:${name}`, {
|
|
186
|
+
file: `package:${name}`, name, status: 'ok',
|
|
187
|
+
capabilities: { ...(0, contract_1.minimalCapabilities)(), ...p.capabilities },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
171
190
|
return [...this.state.values()];
|
|
172
191
|
}
|
|
173
192
|
}
|
|
174
193
|
exports.RuntimePluginLoader = RuntimePluginLoader;
|
|
175
|
-
function createRuntimePluginContext(credentials) {
|
|
194
|
+
function createRuntimePluginContext(credentials, extra) {
|
|
176
195
|
return {
|
|
177
196
|
fetch: (url, opts) => fetch(url, { ...opts, signal: opts?.signal ?? AbortSignal.timeout(60000) }),
|
|
178
197
|
log: logger_1.log,
|
|
179
198
|
pluginConfig: (name) => config_1.config.raw?.runtime?.pluginConfig?.[name] ?? {},
|
|
180
199
|
credentials,
|
|
200
|
+
projectDir: extra?.projectDir,
|
|
201
|
+
gatewayPort: extra?.gatewayPort,
|
|
181
202
|
};
|
|
182
203
|
}
|
|
183
204
|
const README_CONTENT = `# Runtime Plugins
|