@jack200714/mafw 4.8.0 → 4.10.1
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 +265 -103
- 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 +34 -0
- 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/waitwhat-command.js +43 -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/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 +12 -0
- 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 +1 -1
- package/package.json +3 -1
- package/packages/tui/dist/cli.js +20 -2
package/gateway/dist/index.js
CHANGED
|
@@ -63,6 +63,8 @@ const stale_verify_1 = require("./recall/stale-verify");
|
|
|
63
63
|
const session_worker_pool_1 = require("./recall/session-worker-pool");
|
|
64
64
|
const reflect_cursor_1 = require("./recall/reflect-cursor");
|
|
65
65
|
const index_scan_1 = require("./recall/index-scan");
|
|
66
|
+
const redact_1 = require("./recall/redact");
|
|
67
|
+
const waitwhat_command_1 = require("./routes/waitwhat-command");
|
|
66
68
|
const consolidation_service_1 = require("./memory/consolidation-service");
|
|
67
69
|
const auth_2 = require("./runtime/auth");
|
|
68
70
|
const harmonic_file_store_2 = require("./memory/harmonic-file-store");
|
|
@@ -72,6 +74,7 @@ const media_service_1 = require("./media/media-service");
|
|
|
72
74
|
const media_agent_1 = require("./media/media-agent");
|
|
73
75
|
const pi_adapter_1 = require("./media/pi-adapter");
|
|
74
76
|
const media_runtime_executor_1 = require("./media/media-runtime-executor");
|
|
77
|
+
const resolve_prompt_1 = require("./media/resolve-prompt");
|
|
75
78
|
const media_plugin_loader_1 = require("./media/media-plugin-loader");
|
|
76
79
|
const tts_service_1 = require("./media/tts-service");
|
|
77
80
|
const eval_endpoint_1 = require("./eval-endpoint");
|
|
@@ -103,11 +106,15 @@ const event_broadcast_1 = require("./runtime/event-broadcast");
|
|
|
103
106
|
const budget_guard_1 = require("./core/budget-guard");
|
|
104
107
|
const goal_budget_1 = require("./core/goal-budget");
|
|
105
108
|
const contract_1 = require("./runtime/contract");
|
|
109
|
+
const validate_1 = require("./runtime/validate");
|
|
106
110
|
const loader_1 = require("./runtime/loader");
|
|
107
111
|
const pi_runtime_1 = require("./runtime/plugins/pi-runtime");
|
|
108
112
|
const permission_1 = require("./routes/permission");
|
|
113
|
+
const event_publish_1 = require("./routes/event-publish");
|
|
109
114
|
const runtime_switch_1 = require("./routes/runtime-switch");
|
|
110
115
|
const plugins_1 = require("./routes/plugins");
|
|
116
|
+
const package_host_1 = require("./plugins/package-host");
|
|
117
|
+
const package_context_1 = require("./plugins/package-context");
|
|
111
118
|
const hub_1 = require("./plugins/hub");
|
|
112
119
|
const restart_agent_1 = require("./routes/restart-agent");
|
|
113
120
|
const session_mutations_1 = require("./routes/session-mutations");
|
|
@@ -146,6 +153,8 @@ async function isPortHealthy(port) {
|
|
|
146
153
|
return false;
|
|
147
154
|
}
|
|
148
155
|
}
|
|
156
|
+
const manager_session_runtime_1 = require("./core/manager/manager-session-runtime");
|
|
157
|
+
const INTERNAL_SESSION_TTL_DAYS = 7;
|
|
149
158
|
class MafwScheduler {
|
|
150
159
|
serveInstance;
|
|
151
160
|
serveUrl;
|
|
@@ -175,6 +184,8 @@ class MafwScheduler {
|
|
|
175
184
|
lastActiveBySession = new Map();
|
|
176
185
|
lastWriteBySession = new Map();
|
|
177
186
|
tokenWriterSession = null;
|
|
187
|
+
/** 畸形事件 warn 限频(每 runtime 30s 一次) */
|
|
188
|
+
malformedEventWarnAt = new Map();
|
|
178
189
|
stopTokenWatcher = null;
|
|
179
190
|
// Path 1 step-injection state (mark-before-async + dedup + queue). All
|
|
180
191
|
// per-session entries expire via TtlMap.
|
|
@@ -236,6 +247,8 @@ class MafwScheduler {
|
|
|
236
247
|
mediaPluginLoader;
|
|
237
248
|
mediaRuntimeExecutor;
|
|
238
249
|
mediaRuntimeExecutorRt;
|
|
250
|
+
pluginHost;
|
|
251
|
+
usageStatsProvider;
|
|
239
252
|
ttsService;
|
|
240
253
|
kernels;
|
|
241
254
|
automationEngine;
|
|
@@ -360,6 +373,16 @@ class MafwScheduler {
|
|
|
360
373
|
await this.runtimeLoader.init();
|
|
361
374
|
// 内置插件注册:pi-coding-agent runtime(进程内 SDK 嵌入)
|
|
362
375
|
this.runtimeLoader.registerBuiltin('pi', pi_runtime_1.createPiRuntime, pi_runtime_1.PI_CAPABILITIES, true);
|
|
376
|
+
// 2b. 统一插件包宿主(必须在 createRuntime 之前 init——包 runtime 贡献要先注册)
|
|
377
|
+
this.pluginHost = new package_host_1.PluginHost(process.env.MAFW_PLUGINS_DIR || config_1.config.resolvePath('plugins'), (name) => (0, package_context_1.createPluginPackageContext)(name, {
|
|
378
|
+
getCredentials: () => this.opencodeClient?.credentials ?? undefined,
|
|
379
|
+
usageStats: () => this.usageStatsProvider,
|
|
380
|
+
projectDir: this.projectDir,
|
|
381
|
+
gatewayPort: config_1.config.server.apiPort,
|
|
382
|
+
emit: (event) => this.broadcast(event),
|
|
383
|
+
}));
|
|
384
|
+
this.pluginHost.bindRuntime((entries) => this.runtimeLoader?.setPackageEntries(entries));
|
|
385
|
+
await this.pluginHost.init();
|
|
363
386
|
// 3. 创建 SDK 客户端(auth),用于健康检查和后续通信
|
|
364
387
|
const sdkConfig = {
|
|
365
388
|
baseUrl: this.serveUrl,
|
|
@@ -370,6 +393,12 @@ class MafwScheduler {
|
|
|
370
393
|
if (opencodePassword) {
|
|
371
394
|
sdkConfig.headers = { Authorization: 'Basic ' + Buffer.from(`opencode:${opencodePassword}`).toString('base64') };
|
|
372
395
|
}
|
|
396
|
+
// opencode 注册为正式内置插件(与用户插件同一接口;同名文件/包可覆盖)
|
|
397
|
+
const sdkSnapshot = { ...sdkConfig, headers: { ...sdkConfig.headers } };
|
|
398
|
+
this.runtimeLoader.registerBuiltin('opencode', async () => (await import('./runtime/opencode-runtime.js')).createOpencodeRuntime({
|
|
399
|
+
...sdkSnapshot,
|
|
400
|
+
headers: { ...sdkSnapshot.headers },
|
|
401
|
+
}), (0, contract_1.fullCapabilities)(), !!process.env.MAFW_SERVER_SERVE_URL);
|
|
373
402
|
const runtime = await this.createRuntime(sdkConfig);
|
|
374
403
|
this.opencodeClient = runtime;
|
|
375
404
|
this.runtimeCaps = runtime.capabilities;
|
|
@@ -600,7 +629,7 @@ class MafwScheduler {
|
|
|
600
629
|
}
|
|
601
630
|
else {
|
|
602
631
|
try {
|
|
603
|
-
const ms = this.
|
|
632
|
+
const ms = this.readManagerSessionEntry(this.projectDir);
|
|
604
633
|
if (ms?.sessionId)
|
|
605
634
|
targets.add(ms.sessionId);
|
|
606
635
|
}
|
|
@@ -644,8 +673,10 @@ class MafwScheduler {
|
|
|
644
673
|
if (changed.includes('runtime')) {
|
|
645
674
|
const newPlugin = config_1.config.runtime?.plugin;
|
|
646
675
|
if (newPlugin !== prevPlugin && !this.switchingRuntime && !this.serveRecovering) {
|
|
647
|
-
|
|
676
|
+
this.switchingRuntime = true;
|
|
677
|
+
const prev = this.opencodeClient;
|
|
648
678
|
try {
|
|
679
|
+
logger_1.log.info(`[Scheduler] Runtime plugin changed: '${prevPlugin ?? 'builtin'}' → '${newPlugin ?? 'builtin'}'; hot-switching...`);
|
|
649
680
|
const sdkConfig = {
|
|
650
681
|
baseUrl: this.serveUrl,
|
|
651
682
|
directory: this.projectDir,
|
|
@@ -670,11 +701,28 @@ class MafwScheduler {
|
|
|
670
701
|
if (this.automationEngine)
|
|
671
702
|
this.automationEngine.setRuntimeClient(runtime);
|
|
672
703
|
await this.resubscribeEvents(`config hot-reload runtime plugin changed to '${newPlugin ?? 'builtin'}'`);
|
|
704
|
+
// Same desktop hint as the route path: hand-edited config.yaml
|
|
705
|
+
// switches must also refresh the renderer (menus/tabs/manager kv).
|
|
706
|
+
this.broadcast({ type: 'runtime_switched', runtime: runtime.name, previous: prevPlugin ?? null });
|
|
707
|
+
// Dispose the previous runtime AFTER the new one is fully wired
|
|
708
|
+
// (mirror of the route path) — without this, switching away from
|
|
709
|
+
// pi leaks its AgentSessions/ApprovalBridges/event stream.
|
|
710
|
+
if (prev && prev.dispose) {
|
|
711
|
+
try {
|
|
712
|
+
await prev.dispose();
|
|
713
|
+
}
|
|
714
|
+
catch (err) {
|
|
715
|
+
logger_1.log.warn(`[Scheduler] dispose previous runtime failed: ${err.message}`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
673
718
|
logger_1.log.info(`[Scheduler] Runtime hot-switched to '${runtime.name}'`);
|
|
674
719
|
}
|
|
675
720
|
catch (err) {
|
|
676
721
|
logger_1.log.warn(`[Scheduler] Runtime hot-switch failed (non-fatal): ${err.message}`);
|
|
677
722
|
}
|
|
723
|
+
finally {
|
|
724
|
+
this.switchingRuntime = false;
|
|
725
|
+
}
|
|
678
726
|
}
|
|
679
727
|
}
|
|
680
728
|
}, 300);
|
|
@@ -849,6 +897,23 @@ class MafwScheduler {
|
|
|
849
897
|
}
|
|
850
898
|
handleOpencodeEvent(evt) {
|
|
851
899
|
const f = (0, normalize_1.normalizeOpencodeEvent)(evt);
|
|
900
|
+
// 畸形事件诊断(限频):runtime 插件发来的事件 type/properties 全空时
|
|
901
|
+
// 静默穿过会污染 trajectory 与桌面 SSE——这里给可定位诊断。
|
|
902
|
+
if ((0, normalize_1.isMalformedEvent)(evt)) {
|
|
903
|
+
const now = Date.now();
|
|
904
|
+
const last = this.malformedEventWarnAt.get(this.runtimeName) ?? 0;
|
|
905
|
+
if (now - last > 30_000) {
|
|
906
|
+
this.malformedEventWarnAt.set(this.runtimeName, now);
|
|
907
|
+
let summary;
|
|
908
|
+
try {
|
|
909
|
+
summary = JSON.stringify(evt)?.slice(0, 200) ?? '(unserializable)';
|
|
910
|
+
}
|
|
911
|
+
catch {
|
|
912
|
+
summary = '(unserializable)';
|
|
913
|
+
}
|
|
914
|
+
logger_1.log.warn(`[SSE] malformed event from runtime '${this.runtimeName}' (no type/properties) — dropped consumers may misbehave; payload: ${summary}`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
852
917
|
const { type, properties: props, sessionID } = f;
|
|
853
918
|
// Only memory-system sessions (index-scan / extract / reflect workers) are
|
|
854
919
|
// internal: their token-level deltas flooded the desktop renderer (per-delta
|
|
@@ -993,7 +1058,18 @@ class MafwScheduler {
|
|
|
993
1058
|
if (plugin) {
|
|
994
1059
|
try {
|
|
995
1060
|
const creds = await this.runtimeCredentialsForPlugin();
|
|
996
|
-
const rt = await plugin.createRuntime((0, loader_1.createRuntimePluginContext)(creds
|
|
1061
|
+
const rt = await plugin.createRuntime((0, loader_1.createRuntimePluginContext)(creds, {
|
|
1062
|
+
projectDir: this.projectDir,
|
|
1063
|
+
gatewayPort: config_1.config.server.apiPort,
|
|
1064
|
+
}));
|
|
1065
|
+
// 声明 vs 实现一致性校验:错位插件在切换/启动期给出字段级诊断并回退,
|
|
1066
|
+
// 而不是运行时深处才炸。
|
|
1067
|
+
const issues = (0, validate_1.validateRuntimeShape)(rt, pluginName);
|
|
1068
|
+
if (issues.length > 0) {
|
|
1069
|
+
for (const issue of issues)
|
|
1070
|
+
logger_1.log.error(`[Runtime] shape violation: ${issue}`);
|
|
1071
|
+
throw new Error(`runtime '${rt.name}' failed shape validation (${issues.length} issue(s)) — see logs`);
|
|
1072
|
+
}
|
|
997
1073
|
logger_1.log.info(`[Runtime] using plugin runtime '${rt.name}' (capabilities: ${JSON.stringify(rt.capabilities)})`);
|
|
998
1074
|
return rt;
|
|
999
1075
|
}
|
|
@@ -1005,6 +1081,11 @@ class MafwScheduler {
|
|
|
1005
1081
|
logger_1.log.warn(`[Runtime] plugin '${pluginName}' not found — falling back to opencode`);
|
|
1006
1082
|
}
|
|
1007
1083
|
}
|
|
1084
|
+
const builtin = this.runtimeLoader?.get('opencode');
|
|
1085
|
+
if (builtin) {
|
|
1086
|
+
return builtin.createRuntime((0, loader_1.createRuntimePluginContext)(undefined));
|
|
1087
|
+
}
|
|
1088
|
+
// 最终安全网:loader 未注册时直连(不应发生)
|
|
1008
1089
|
const { createOpencodeRuntime } = await import('./runtime/opencode-runtime.js');
|
|
1009
1090
|
return createOpencodeRuntime({
|
|
1010
1091
|
...sdkConfig,
|
|
@@ -1711,6 +1792,7 @@ class MafwScheduler {
|
|
|
1711
1792
|
}
|
|
1712
1793
|
this.pluginLoader?.stop();
|
|
1713
1794
|
this.mediaPluginLoader?.stop();
|
|
1795
|
+
this.pluginHost?.stop();
|
|
1714
1796
|
if (this.opencodeClient && typeof this.opencodeClient.dispose === 'function') {
|
|
1715
1797
|
void this.opencodeClient.dispose().catch((err) => {
|
|
1716
1798
|
logger_1.log.warn(`[Scheduler] runtime dispose error: ${err?.message ?? String(err)}`);
|
|
@@ -1724,32 +1806,6 @@ class MafwScheduler {
|
|
|
1724
1806
|
// }
|
|
1725
1807
|
logger_1.log.info('[Scheduler] Stopping...');
|
|
1726
1808
|
}
|
|
1727
|
-
// Proxy a native opencode request by trying every registered workspace.
|
|
1728
|
-
// Native reply/reject routes are workspace-scoped (WorkspaceRoutingMiddleware),
|
|
1729
|
-
// but the gateway's own projectDir is its cwd — the request may belong to any
|
|
1730
|
-
// registered project. GET list endpoints are cross-workspace and unaffected.
|
|
1731
|
-
async proxyNativeWorkspaces(path, method, body) {
|
|
1732
|
-
const dirs = new Set([this.projectDir || '.']);
|
|
1733
|
-
for (const key of this.registeredProjects.keys())
|
|
1734
|
-
dirs.add(key);
|
|
1735
|
-
let lastStatus = 502;
|
|
1736
|
-
for (const dir of dirs) {
|
|
1737
|
-
try {
|
|
1738
|
-
const r = await fetch(`${this.serveUrl}${path}?directory=${encodeURIComponent(dir)}`, {
|
|
1739
|
-
method,
|
|
1740
|
-
headers: { 'content-type': 'application/json', 'x-opencode-directory': encodeURIComponent(dir) },
|
|
1741
|
-
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
1742
|
-
});
|
|
1743
|
-
if (r.ok)
|
|
1744
|
-
return { ok: true, status: r.status };
|
|
1745
|
-
lastStatus = r.status;
|
|
1746
|
-
}
|
|
1747
|
-
catch (e) {
|
|
1748
|
-
logger_1.log.error(`[Native proxy] ${path} failed for ${dir}: ${e.message}`);
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
return { ok: false, status: lastStatus };
|
|
1752
|
-
}
|
|
1753
1809
|
// 鈹€鈹€ Services & Event Bus 鈹€鈹€
|
|
1754
1810
|
async initServices() {
|
|
1755
1811
|
const projectDir = this.projectDir;
|
|
@@ -1774,6 +1830,9 @@ class MafwScheduler {
|
|
|
1774
1830
|
getCredentials: () => this.opencodeClient?.credentials ?? undefined,
|
|
1775
1831
|
});
|
|
1776
1832
|
await this.mediaPluginLoader.init();
|
|
1833
|
+
this.pluginHost?.bindMedia((entries) => this.mediaPluginLoader?.setPackageEntries(entries));
|
|
1834
|
+
// 内置 pi 引擎登记(可被用户同名包/文件覆盖——resolveMediaPrompt 语义)
|
|
1835
|
+
this.mediaPluginLoader.registerBuiltinEngine('pi', ['image', 'video', 'audio']);
|
|
1777
1836
|
this.mediaService = new media_service_1.MediaService({
|
|
1778
1837
|
prompt: (0, pi_adapter_1.createPiPromptAdapter)({
|
|
1779
1838
|
getApiKey: (provider) => {
|
|
@@ -1783,31 +1842,24 @@ class MafwScheduler {
|
|
|
1783
1842
|
config: () => config_1.config.raw.media,
|
|
1784
1843
|
resolvePrompt: (kind, cfg) => {
|
|
1785
1844
|
const engineName = cfg[kind]?.engine ?? cfg.engine ?? 'pi';
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
return undefined;
|
|
1805
|
-
}
|
|
1806
|
-
if (!engine.modalities.includes(kind)) {
|
|
1807
|
-
logger_1.log.warn(`[MediaService] engine '${engineName}' does not support modality '${kind}', falling back to pi`);
|
|
1808
|
-
return undefined;
|
|
1809
|
-
}
|
|
1810
|
-
return engine.prompt;
|
|
1845
|
+
return (0, resolve_prompt_1.resolveMediaPrompt)(engineName, kind, {
|
|
1846
|
+
engines: this.mediaPluginLoader?.getEngines() ?? new Map(),
|
|
1847
|
+
builtinPi: () => {
|
|
1848
|
+
// pi runtime 激活时:图片走 AgentRuntime 会话(MediaRuntimeExecutor),
|
|
1849
|
+
// video/audio 由 executor 内部回退到 complete 路径
|
|
1850
|
+
if (this.opencodeClient?.name === 'pi') {
|
|
1851
|
+
// runtime 热切换后 opencodeClient 实例更换——旧 executor 持有 stale
|
|
1852
|
+
// runtime 引用,必须重建(dispose 尽力而为,不阻塞 prompt)。
|
|
1853
|
+
if (!this.mediaRuntimeExecutor || this.mediaRuntimeExecutorRt !== this.opencodeClient) {
|
|
1854
|
+
void this.mediaRuntimeExecutor?.dispose().catch(() => { });
|
|
1855
|
+
this.mediaRuntimeExecutor = (0, media_runtime_executor_1.createMediaRuntimeExecutor)(this.opencodeClient);
|
|
1856
|
+
this.mediaRuntimeExecutorRt = this.opencodeClient;
|
|
1857
|
+
}
|
|
1858
|
+
return this.mediaRuntimeExecutor.prompt;
|
|
1859
|
+
}
|
|
1860
|
+
return undefined;
|
|
1861
|
+
},
|
|
1862
|
+
});
|
|
1811
1863
|
},
|
|
1812
1864
|
});
|
|
1813
1865
|
// Determine workspace name: if projectDir resolves to a 'gateway' subdirectory,
|
|
@@ -1916,6 +1968,9 @@ class MafwScheduler {
|
|
|
1916
1968
|
const collector = new TrajectoryCollector(trajStore, this.getGatewayDb(), projectDir, () => config_1.config.trajectory.retentionDays);
|
|
1917
1969
|
collector.setRoleFor((sid) => this.internalSessionRoles.get(sid) ?? null);
|
|
1918
1970
|
this.trajectoryCollector = collector;
|
|
1971
|
+
const pruned = this.getGatewayDb().kvPruneOlderThan('internal-session', INTERNAL_SESSION_TTL_DAYS);
|
|
1972
|
+
if (pruned > 0)
|
|
1973
|
+
logger_1.log.info(`[Scheduler] pruned ${pruned} stale internal-session kv entries (> ${INTERNAL_SESSION_TTL_DAYS}d)`);
|
|
1919
1974
|
const restored = this.getGatewayDb().kvAll('internal-session');
|
|
1920
1975
|
for (const { key: sid, value } of restored) {
|
|
1921
1976
|
if (value?.role && !this.internalSessionRoles.has(sid)) {
|
|
@@ -1932,10 +1987,12 @@ class MafwScheduler {
|
|
|
1932
1987
|
const pluginsDir = path.join(os.homedir(), '.mafw', 'usage-plugins');
|
|
1933
1988
|
const builtinPluginsDir = path.join(__dirname, 'usage', 'builtin-plugins');
|
|
1934
1989
|
const disabledPlugins = Array.isArray(config_1.config.usage?.disabledPlugins) ? config_1.config.usage.disabledPlugins : [];
|
|
1990
|
+
const statsProvider = createUsageStatsProvider(trajStore);
|
|
1991
|
+
this.usageStatsProvider = statsProvider;
|
|
1935
1992
|
const pluginLoader = new PluginLoader(pluginsDir, [], {
|
|
1936
1993
|
builtinPluginsDir,
|
|
1937
1994
|
disabledPlugins,
|
|
1938
|
-
usageStats:
|
|
1995
|
+
usageStats: statsProvider,
|
|
1939
1996
|
// inline provider key 兜底(auth.json 无条目的自建 provider,如 gateway)——
|
|
1940
1997
|
// thunk 惰性求值,opencodeClient 此时尚未初始化也不影响。
|
|
1941
1998
|
resolveInlineApiKey: async (providerID) => {
|
|
@@ -1951,6 +2008,7 @@ class MafwScheduler {
|
|
|
1951
2008
|
});
|
|
1952
2009
|
await pluginLoader.init();
|
|
1953
2010
|
this.pluginLoader = pluginLoader;
|
|
2011
|
+
this.pluginHost?.bindUsage((entries) => pluginLoader.setPackageEntries(entries));
|
|
1954
2012
|
const { UsagePoller } = require('./usage/usage-poller');
|
|
1955
2013
|
this.usagePoller = new UsagePoller(trajStore, () => config_1.config.usage.limits, () => config_1.config.usage.budgets, pluginLoader);
|
|
1956
2014
|
logger_1.log.info('[Trajectory] store initialized');
|
|
@@ -2055,6 +2113,18 @@ class MafwScheduler {
|
|
|
2055
2113
|
handleServeExit(code) {
|
|
2056
2114
|
if (!this.serveOwned || this.serveRecovering)
|
|
2057
2115
|
return;
|
|
2116
|
+
// After a hot-switch to an in-process/external runtime, the (still owned)
|
|
2117
|
+
// opencode serve is no longer the active backend: its exit must NOT trigger
|
|
2118
|
+
// recovery — pi has no spawnServe, so the orchestrator would fall into the
|
|
2119
|
+
// supervisor's kill+spawn path and retry forever (killing whatever listens
|
|
2120
|
+
// on the serve port each cycle). A later switch back re-ensures via
|
|
2121
|
+
// ensureServeForBuiltinRuntime (probe → adopt or spawn).
|
|
2122
|
+
const rt = this.opencodeClient;
|
|
2123
|
+
if (rt?.external || !rt?.agentProcess?.spawnServe) {
|
|
2124
|
+
this.serveOwned = false;
|
|
2125
|
+
logger_1.log.info('[Scheduler] owned serve exited after runtime switch; recovery skipped (active runtime does not own serve)');
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2058
2128
|
this.serveInstance = undefined;
|
|
2059
2129
|
if (this.serveStableTimer) {
|
|
2060
2130
|
clearTimeout(this.serveStableTimer);
|
|
@@ -3141,6 +3211,32 @@ class MafwScheduler {
|
|
|
3141
3211
|
res.end(JSON.stringify({ ok: true, text: answer }));
|
|
3142
3212
|
return;
|
|
3143
3213
|
}
|
|
3214
|
+
if (cmd === "waitwhat") {
|
|
3215
|
+
if (!sessionID) {
|
|
3216
|
+
res.writeHead(400);
|
|
3217
|
+
res.end(JSON.stringify({ ok: false, error: "sessionID required" }));
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
if (!this.opencodeClient) {
|
|
3221
|
+
res.writeHead(503);
|
|
3222
|
+
res.end(JSON.stringify({ ok: false, error: "LLM client not available" }));
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
const result = await (0, waitwhat_command_1.runWaitwhat)(String(sessionID), {
|
|
3226
|
+
listMessages: async (sid) => {
|
|
3227
|
+
const r = await this.opencodeClient.session.messages({ sessionID: sid, limit: 50 });
|
|
3228
|
+
return (r?.data || []);
|
|
3229
|
+
},
|
|
3230
|
+
promptAsync: async (sid, text) => {
|
|
3231
|
+
await this.opencodeClient.session.promptAsync({ sessionID: sid, parts: [{ type: "text", text }] });
|
|
3232
|
+
},
|
|
3233
|
+
});
|
|
3234
|
+
res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json' });
|
|
3235
|
+
res.end(JSON.stringify(result.ok
|
|
3236
|
+
? { ok: true, message: '重述请求已发送到当前会话' }
|
|
3237
|
+
: { ok: false, error: result.error }));
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3144
3240
|
if (cmd === "status") {
|
|
3145
3241
|
const statusPath = path.join(this.mafwDir, 'STATUS.md');
|
|
3146
3242
|
const text = fs.existsSync(statusPath) ? fs.readFileSync(statusPath, 'utf-8') : 'No active Goals. Use /goal to create one.';
|
|
@@ -3750,8 +3846,14 @@ class MafwScheduler {
|
|
|
3750
3846
|
const filter = parsedUrl.searchParams.get('projectDir') || '';
|
|
3751
3847
|
try {
|
|
3752
3848
|
const all = this.getGatewayDb().kvAll('manager-session');
|
|
3849
|
+
// Per-runtime slots: surface only the ACTIVE runtime's manager
|
|
3850
|
+
// slot per project. Other runtimes' slots stay dormant (resumed
|
|
3851
|
+
// when switching back) — nothing is deleted here.
|
|
3852
|
+
const live = all
|
|
3853
|
+
.map((e) => ({ key: e.key, value: (0, manager_session_runtime_1.readManagerSlot)(e.value, this.runtimeName) }))
|
|
3854
|
+
.filter((e) => e.value !== null);
|
|
3753
3855
|
if (filter) {
|
|
3754
|
-
const found =
|
|
3856
|
+
const found = live.find((e) => e.key === filter);
|
|
3755
3857
|
if (!found) {
|
|
3756
3858
|
res.writeHead(404);
|
|
3757
3859
|
res.end(JSON.stringify({ error: 'No manager session for project' }));
|
|
@@ -3765,13 +3867,13 @@ class MafwScheduler {
|
|
|
3765
3867
|
}));
|
|
3766
3868
|
return;
|
|
3767
3869
|
}
|
|
3768
|
-
if (
|
|
3870
|
+
if (live.length === 0) {
|
|
3769
3871
|
res.writeHead(404);
|
|
3770
3872
|
res.end(JSON.stringify({ error: 'No manager session' }));
|
|
3771
3873
|
return;
|
|
3772
3874
|
}
|
|
3773
3875
|
res.writeHead(200);
|
|
3774
|
-
res.end(JSON.stringify(
|
|
3876
|
+
res.end(JSON.stringify(live.map((e) => ({
|
|
3775
3877
|
projectDir: e.key,
|
|
3776
3878
|
sessionId: e.value.sessionId,
|
|
3777
3879
|
createdAt: e.value.createdAt || null,
|
|
@@ -3982,17 +4084,15 @@ class MafwScheduler {
|
|
|
3982
4084
|
return;
|
|
3983
4085
|
}
|
|
3984
4086
|
// 鈹€鈹€ Question endpoints (AskCard 鈹€ proxy to native opencode Question API) 鈹€鈹€
|
|
3985
|
-
// GET /api/questions
|
|
4087
|
+
// GET /api/questions ── list pending questions(契约:session.question.list)
|
|
3986
4088
|
if (req.url?.match(/^\/api\/questions(?:\?|$)/) && req.method === 'GET') {
|
|
3987
4089
|
if (this.capGuardQuestion(res))
|
|
3988
4090
|
return;
|
|
3989
4091
|
try {
|
|
3990
|
-
const dir = new URL(req.url, this.serveUrl).searchParams.get('directory') || this.projectDir ||
|
|
3991
|
-
const
|
|
3992
|
-
|
|
3993
|
-
});
|
|
3994
|
-
const items = await r.json();
|
|
3995
|
-
res.end(JSON.stringify({ items }));
|
|
4092
|
+
const dir = new URL(req.url, this.serveUrl).searchParams.get('directory') || this.projectDir || undefined;
|
|
4093
|
+
const question = this.opencodeClient?.session?.question;
|
|
4094
|
+
const items = question ? await question.list(dir ? { directory: dir } : undefined) : [];
|
|
4095
|
+
res.end(JSON.stringify({ items: items ?? [] }));
|
|
3996
4096
|
}
|
|
3997
4097
|
catch (err) {
|
|
3998
4098
|
logger_1.log.error('[Question] list error:', err.message);
|
|
@@ -4000,20 +4100,34 @@ class MafwScheduler {
|
|
|
4000
4100
|
}
|
|
4001
4101
|
return;
|
|
4002
4102
|
}
|
|
4003
|
-
// POST /api/questions/{id}/reply
|
|
4103
|
+
// POST /api/questions/{id}/reply ── { answers: string[][] }(契约 + workspace 重试)
|
|
4004
4104
|
const qReplyMatch = req.url?.match(/^\/api\/questions\/([^/]+)\/reply(?:\?|$)/);
|
|
4005
4105
|
if (qReplyMatch && req.method === 'POST') {
|
|
4006
4106
|
if (this.capGuardQuestion(res))
|
|
4007
4107
|
return;
|
|
4108
|
+
const question = this.opencodeClient?.session?.question;
|
|
4109
|
+
if (!question) {
|
|
4110
|
+
res.writeHead(503);
|
|
4111
|
+
res.end(JSON.stringify({ status: 'error', error: 'question API not available on this runtime' }));
|
|
4112
|
+
return;
|
|
4113
|
+
}
|
|
4008
4114
|
try {
|
|
4009
4115
|
const body = JSON.parse(await readBody(req));
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4116
|
+
// workspace 路由:默认(SDK 自带 projectDir)→ 各注册项目逐试
|
|
4117
|
+
const dirs = [undefined, this.projectDir, ...this.registeredProjects.keys()];
|
|
4118
|
+
let lastErr = null;
|
|
4119
|
+
for (const dir of [...new Set(dirs)]) {
|
|
4120
|
+
try {
|
|
4121
|
+
await question.reply({ requestID: qReplyMatch[1], answers: body.answers, ...(dir ? { directory: dir } : {}) });
|
|
4122
|
+
res.end(JSON.stringify({ status: 'ok' }));
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
catch (err) {
|
|
4126
|
+
lastErr = err;
|
|
4127
|
+
}
|
|
4015
4128
|
}
|
|
4016
|
-
res.
|
|
4129
|
+
res.writeHead(400);
|
|
4130
|
+
res.end(JSON.stringify({ status: 'error', error: lastErr?.message ?? 'question reply failed on all workspaces' }));
|
|
4017
4131
|
}
|
|
4018
4132
|
catch (err) {
|
|
4019
4133
|
logger_1.log.error('[Question] reply error:', err.message);
|
|
@@ -4022,19 +4136,32 @@ class MafwScheduler {
|
|
|
4022
4136
|
}
|
|
4023
4137
|
return;
|
|
4024
4138
|
}
|
|
4025
|
-
// POST /api/questions/{id}/reject
|
|
4139
|
+
// POST /api/questions/{id}/reject(契约 + workspace 重试)
|
|
4026
4140
|
const qRejectMatch = req.url?.match(/^\/api\/questions\/([^/]+)\/reject(?:\?|$)/);
|
|
4027
4141
|
if (qRejectMatch && req.method === 'POST') {
|
|
4028
4142
|
if (this.capGuardQuestion(res))
|
|
4029
4143
|
return;
|
|
4144
|
+
const question = this.opencodeClient?.session?.question;
|
|
4145
|
+
if (!question) {
|
|
4146
|
+
res.writeHead(503);
|
|
4147
|
+
res.end(JSON.stringify({ status: 'error', error: 'question API not available on this runtime' }));
|
|
4148
|
+
return;
|
|
4149
|
+
}
|
|
4030
4150
|
try {
|
|
4031
|
-
const
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4151
|
+
const dirs = [undefined, this.projectDir, ...this.registeredProjects.keys()];
|
|
4152
|
+
let lastErr = null;
|
|
4153
|
+
for (const dir of [...new Set(dirs)]) {
|
|
4154
|
+
try {
|
|
4155
|
+
await question.reject({ requestID: qRejectMatch[1], ...(dir ? { directory: dir } : {}) });
|
|
4156
|
+
res.end(JSON.stringify({ status: 'ok' }));
|
|
4157
|
+
return;
|
|
4158
|
+
}
|
|
4159
|
+
catch (err) {
|
|
4160
|
+
lastErr = err;
|
|
4161
|
+
}
|
|
4036
4162
|
}
|
|
4037
|
-
res.
|
|
4163
|
+
res.writeHead(400);
|
|
4164
|
+
res.end(JSON.stringify({ status: 'error', error: lastErr?.message ?? 'question reject failed on all workspaces' }));
|
|
4038
4165
|
}
|
|
4039
4166
|
catch (err) {
|
|
4040
4167
|
logger_1.log.error('[Question] reject error:', err.message);
|
|
@@ -4044,17 +4171,16 @@ class MafwScheduler {
|
|
|
4044
4171
|
return;
|
|
4045
4172
|
}
|
|
4046
4173
|
// 鈹€鈹€ Permission endpoints (PermissionCard 鈹€ proxy to native opencode Permission API) 鈹€鈹€
|
|
4047
|
-
// GET /api/permissions
|
|
4174
|
+
// GET /api/permissions ── list pending permission requests(契约:session.permissionList)
|
|
4048
4175
|
if (req.url?.match(/^\/api\/permissions(?:\?|$)/) && req.method === 'GET') {
|
|
4049
4176
|
if (this.capGuard(res, 'nativeApprovals'))
|
|
4050
4177
|
return;
|
|
4051
4178
|
try {
|
|
4052
|
-
const dir = new URL(req.url, this.serveUrl).searchParams.get('directory') || this.projectDir ||
|
|
4053
|
-
const
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
res.end(JSON.stringify({ items }));
|
|
4179
|
+
const dir = new URL(req.url, this.serveUrl).searchParams.get('directory') || this.projectDir || undefined;
|
|
4180
|
+
const items = this.opencodeClient?.session?.permissionList
|
|
4181
|
+
? await this.opencodeClient.session.permissionList(dir ? { directory: dir } : undefined)
|
|
4182
|
+
: [];
|
|
4183
|
+
res.end(JSON.stringify({ items: items ?? [] }));
|
|
4058
4184
|
}
|
|
4059
4185
|
catch (err) {
|
|
4060
4186
|
logger_1.log.error('[Permission] list error:', err.message);
|
|
@@ -4163,10 +4289,12 @@ class MafwScheduler {
|
|
|
4163
4289
|
builtinEntries: () => {
|
|
4164
4290
|
const entries = [];
|
|
4165
4291
|
const rt = (name) => ({ type: 'runtime', name, file: '(builtin)', status: 'enabled', size: 0, mtime: '' });
|
|
4166
|
-
|
|
4292
|
+
// opencode 经 registerBuiltin 注册,getBuiltinNames 已含——不再手工 push(防重复)
|
|
4167
4293
|
for (const name of this.runtimeLoader?.getBuiltinNames?.() ?? [])
|
|
4168
4294
|
entries.push(rt(name));
|
|
4169
|
-
|
|
4295
|
+
for (const name of this.mediaPluginLoader?.getBuiltinEngineNames?.() ?? []) {
|
|
4296
|
+
entries.push({ type: 'media', name, file: '(builtin)', status: 'enabled', size: 0, mtime: '' });
|
|
4297
|
+
}
|
|
4170
4298
|
const usageState = this.pluginLoader?.getState?.() ?? [];
|
|
4171
4299
|
for (const s of usageState) {
|
|
4172
4300
|
if (s.builtin && s.status === 'ok' && s.name) {
|
|
@@ -4195,6 +4323,7 @@ class MafwScheduler {
|
|
|
4195
4323
|
await this.pluginLoader?.reload();
|
|
4196
4324
|
// ui: desktop main fs.watch picks it up automatically
|
|
4197
4325
|
},
|
|
4326
|
+
getPackages: () => this.pluginHost?.getState() ?? [],
|
|
4198
4327
|
},
|
|
4199
4328
|
};
|
|
4200
4329
|
try {
|
|
@@ -4322,20 +4451,37 @@ class MafwScheduler {
|
|
|
4322
4451
|
}
|
|
4323
4452
|
return;
|
|
4324
4453
|
}
|
|
4325
|
-
// POST /api/permissions/{id}/reply
|
|
4454
|
+
// POST /api/permissions/{id}/reply ── { reply, message? }(契约:列表反查 sessionID → permissionReply)
|
|
4326
4455
|
const pReplyMatch = req.url?.match(/^\/api\/permissions\/([^/]+)\/reply(?:\?|$)/);
|
|
4327
4456
|
if (pReplyMatch && req.method === 'POST') {
|
|
4328
4457
|
if (this.capGuard(res, 'nativeApprovals'))
|
|
4329
4458
|
return;
|
|
4459
|
+
const runtime = this.opencodeClient;
|
|
4460
|
+
if (!runtime?.session?.permissionList || !runtime?.session?.permissionReply) {
|
|
4461
|
+
res.writeHead(503);
|
|
4462
|
+
res.end(JSON.stringify({ status: 'error', error: 'permission API not available on this runtime' }));
|
|
4463
|
+
return;
|
|
4464
|
+
}
|
|
4330
4465
|
try {
|
|
4331
4466
|
const body = JSON.parse(await readBody(req));
|
|
4332
|
-
const
|
|
4333
|
-
if (
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4467
|
+
const reply = body.reply;
|
|
4468
|
+
if (reply !== 'once' && reply !== 'always' && reply !== 'reject') {
|
|
4469
|
+
res.writeHead(400);
|
|
4470
|
+
res.end(JSON.stringify({ status: 'error', error: "reply must be 'once'|'always'|'reject'" }));
|
|
4471
|
+
return;
|
|
4472
|
+
}
|
|
4473
|
+
// 反查 sessionID:pending 列表项携带(跨 runtime 形状一致)
|
|
4474
|
+
const pending = await runtime.session.permissionList();
|
|
4475
|
+
const found = (Array.isArray(pending) ? pending : []).find((p) => p?.id === pReplyMatch[1]);
|
|
4476
|
+
if (!found?.sessionID) {
|
|
4477
|
+
res.writeHead(404);
|
|
4478
|
+
res.end(JSON.stringify({ status: 'error', error: 'permission request not found' }));
|
|
4479
|
+
return;
|
|
4480
|
+
}
|
|
4481
|
+
const ok = await runtime.session.permissionReply(found.sessionID, pReplyMatch[1], reply, body.message);
|
|
4482
|
+
if (!ok) {
|
|
4483
|
+
res.writeHead(404);
|
|
4484
|
+
res.end(JSON.stringify({ status: 'error', error: 'permission request not found' }));
|
|
4339
4485
|
return;
|
|
4340
4486
|
}
|
|
4341
4487
|
res.end(JSON.stringify({ status: 'ok' }));
|
|
@@ -5183,7 +5329,10 @@ class MafwScheduler {
|
|
|
5183
5329
|
session_id: sessionID,
|
|
5184
5330
|
turn_id: turnId,
|
|
5185
5331
|
source: source,
|
|
5186
|
-
|
|
5332
|
+
// Redact secrets once at capture so every downstream consumer
|
|
5333
|
+
// (turnCompress transcripts, worker prompts, archive) only
|
|
5334
|
+
// ever sees redacted content.
|
|
5335
|
+
content: (0, redact_1.redactSecrets)(content).slice(0, 100_000),
|
|
5187
5336
|
failure,
|
|
5188
5337
|
});
|
|
5189
5338
|
// Async scan prefetch: precompute semantic recall for this turn
|
|
@@ -5203,6 +5352,11 @@ class MafwScheduler {
|
|
|
5203
5352
|
});
|
|
5204
5353
|
return;
|
|
5205
5354
|
}
|
|
5355
|
+
// POST /api/events —— 事件发布(插件/外部程序 → 全 UI 通道;契约见 routes/event-publish.ts)
|
|
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
|
+
}
|
|
5206
5360
|
// SSE 事件(→ Dashboard / Chat)
|
|
5207
5361
|
if (req.url && req.url.startsWith('/api/events') && req.method === 'GET') {
|
|
5208
5362
|
const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
@@ -6204,6 +6358,14 @@ ${observations.map((o, i) => `[${i + 1}] ${o}`).join('\n')}`;
|
|
|
6204
6358
|
});
|
|
6205
6359
|
return run;
|
|
6206
6360
|
}
|
|
6361
|
+
// Slot-based read of a manager-session kv entry: each runtime owns a slot
|
|
6362
|
+
// in the value (byRuntime), so switching runtimes never drops a topic —
|
|
6363
|
+
// switching back resumes the previous manager. Legacy v1 entries are
|
|
6364
|
+
// inferred by session id prefix and only surface under their own runtime.
|
|
6365
|
+
// See manager-session-runtime.ts.
|
|
6366
|
+
readManagerSessionEntry(projectDir) {
|
|
6367
|
+
return (0, manager_session_runtime_1.readManagerSlot)(this.getGatewayDb().kvGet('manager-session', projectDir), this.runtimeName);
|
|
6368
|
+
}
|
|
6207
6369
|
// Serialized rotate: joins any in-flight ensure/create for the same project
|
|
6208
6370
|
// so rotate and lazy-init never double-create (spec §4).
|
|
6209
6371
|
async runManagerExclusive(projectDir, fn) {
|
|
@@ -6220,7 +6382,7 @@ ${observations.map((o, i) => `[${i + 1}] ${o}`).join('\n')}`;
|
|
|
6220
6382
|
// The inflight map is shared with ensureManagerSession, whose joiners
|
|
6221
6383
|
// expect the promise to resolve to a session id string — map the rotate
|
|
6222
6384
|
// result onto the resulting kv entry.
|
|
6223
|
-
const mapped = run.then(() => this.
|
|
6385
|
+
const mapped = run.then(() => this.readManagerSessionEntry(projectDir)?.sessionId ?? '', () => '');
|
|
6224
6386
|
this.managerSessionInflight.set(projectDir, mapped);
|
|
6225
6387
|
try {
|
|
6226
6388
|
return await run;
|
|
@@ -6235,7 +6397,7 @@ ${observations.map((o, i) => `[${i + 1}] ${o}`).join('\n')}`;
|
|
|
6235
6397
|
throw new Error('opencodeClient not available');
|
|
6236
6398
|
// Manager identity lives in the gateway DB (kv_store), so it survives
|
|
6237
6399
|
// project-directory churn and never gets orphaned by directory moves.
|
|
6238
|
-
const existing = this.
|
|
6400
|
+
const existing = this.readManagerSessionEntry(projectDir);
|
|
6239
6401
|
if (existing?.sessionId) {
|
|
6240
6402
|
await this.sdkSession.registerExternal(existing.sessionId, projectDir, {
|
|
6241
6403
|
mafw: { role: 'manager', pinned: true, exemptFromTrim: true, exemptFromEvict: true, exemptFromArchive: true },
|
|
@@ -6251,7 +6413,7 @@ ${observations.map((o, i) => `[${i + 1}] ${o}`).join('\n')}`;
|
|
|
6251
6413
|
throw new Error('Failed to create manager session: no id returned');
|
|
6252
6414
|
}
|
|
6253
6415
|
const createdAt = new Date().toISOString();
|
|
6254
|
-
this.getGatewayDb().kvSet('manager-session', projectDir, { sessionId, createdAt });
|
|
6416
|
+
this.getGatewayDb().kvSet('manager-session', projectDir, (0, manager_session_runtime_1.writeManagerSlot)(this.getGatewayDb().kvGet('manager-session', projectDir), this.runtimeName, { sessionId, createdAt }));
|
|
6255
6417
|
this.registerInternalSession(sessionId, 'manager');
|
|
6256
6418
|
try {
|
|
6257
6419
|
await this.sdkSession.registerExternal(sessionId, projectDir, {
|
|
@@ -6283,7 +6445,7 @@ ${observations.map((o, i) => `[${i + 1}] ${o}`).join('\n')}`;
|
|
|
6283
6445
|
}
|
|
6284
6446
|
rotateDeps() {
|
|
6285
6447
|
return {
|
|
6286
|
-
getManagerSession: (pd) => this.
|
|
6448
|
+
getManagerSession: (pd) => this.readManagerSessionEntry(pd),
|
|
6287
6449
|
lock: (pd, fn) => this.runManagerExclusive(pd, fn),
|
|
6288
6450
|
ensure: (pd) => this.ensureManagerSession(pd, this.mafwDirFor(pd)),
|
|
6289
6451
|
downgrade: async (sid) => {
|
|
@@ -6330,7 +6492,7 @@ ${observations.map((o, i) => `[${i + 1}] ${o}`).join('\n')}`;
|
|
|
6330
6492
|
return undefined;
|
|
6331
6493
|
if (!this.milestonePush) {
|
|
6332
6494
|
this.milestonePush = new milestone_push_1.MilestonePushNotifier({
|
|
6333
|
-
getManagerSession: (pd) => this.
|
|
6495
|
+
getManagerSession: (pd) => this.readManagerSessionEntry(pd),
|
|
6334
6496
|
wasNotified: (key) => !!this.getGatewayDb().kvGet('milestone-notified', key),
|
|
6335
6497
|
markNotified: (key) => this.getGatewayDb().kvSet('milestone-notified', key, { at: new Date().toISOString() }),
|
|
6336
6498
|
readGoalState: (goalId, pd) => {
|
|
@@ -6359,7 +6521,7 @@ ${observations.map((o, i) => `[${i + 1}] ${o}`).join('\n')}`;
|
|
|
6359
6521
|
const sessionId = session.id;
|
|
6360
6522
|
if (!sessionId)
|
|
6361
6523
|
throw new Error('Failed to create manager session: no id returned');
|
|
6362
|
-
this.getGatewayDb().kvSet('manager-session', projectDir, { sessionId, createdAt: new Date().toISOString() });
|
|
6524
|
+
this.getGatewayDb().kvSet('manager-session', projectDir, (0, manager_session_runtime_1.writeManagerSlot)(this.getGatewayDb().kvGet('manager-session', projectDir), this.runtimeName, { sessionId, createdAt: new Date().toISOString() }));
|
|
6363
6525
|
this.registerInternalSession(sessionId, 'manager');
|
|
6364
6526
|
await this.sdkSession.registerExternal(sessionId, projectDir, {
|
|
6365
6527
|
mafw: { role: 'manager', pinned: true, exemptFromTrim: true, exemptFromEvict: true, exemptFromArchive: true },
|