@microi.net/cli 4.9.6 → 4.9.8
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/.codebuddy-plugin/marketplace.json +2 -2
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/marketplace.json +2 -2
- package/.workbuddy-plugin/plugin.json +1 -1
- package/assets/build-meta.json +6 -5
- package/package.json +1 -1
- package/scripts/mcp-server.js +83 -83
- package/scripts/microi-cli.js +55 -85
- package/scripts/microi-codex-broker.js +418 -0
- package/scripts/microi-codex-router.js +129 -65
- package/scripts/microi-skills.meta.json +310 -151
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +3566 -0
- package/skills/ai-platform-governance/SKILL.md +21 -166
- package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -0
- package/skills/microi-client-frontend/SKILL.md +17 -434
- package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +144 -0
- package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +178 -0
- package/skills/microi-client-frontend/references/progressive-03-vue3-/345/211/215/347/253/257/345/276/256/346/234/215/345/212/241/345/256/277/344/270/273/350/247/204/345/210/231.md +144 -0
- package/skills/microi-db-schema/SKILL.md +3 -3
- package/skills/microi-db-schema/references/schema-overview.md +1 -1
- package/skills/microi-db-schema/references/schema.md +1 -1
- package/skills/microi-db-schema/references/table-catalog.md +1 -1
- package/skills/microi-form-engine/SKILL.md +1 -1
- package/skills/microi-form-layout/SKILL.md +19 -225
- package/skills/microi-form-layout/references/progressive-01-3-/344/270/211/347/247/215/345/210/206/347/273/204/347/232/204/345/255/230/345/202/250/344/270/216/351/205/215/347/275/256.md +235 -0
- package/skills/microi-frontend-sdk/SKILL.md +17 -151
- package/skills/microi-frontend-sdk/references/progressive-01-token-/345/275/223/345/211/215/347/231/273/345/275/225/347/224/250/346/210/267/344/270/216/345/275/223/345/211/215/347/273/210/347/253/257/347/231/273/345/275/225/345/215/217/350/256/256.md +171 -0
- package/skills/microi-mobile-app-quality/SKILL.md +22 -288
- package/skills/microi-mobile-app-quality/references/progressive-01-4-/351/207/215/350/246/201/346/214/211/351/222/256/345/277/205/351/241/273/345/270/246/345/233/276/346/240/207.md +209 -0
- package/skills/microi-mobile-app-quality/references/progressive-02-9-/344/270/273/351/242/230/345/210/207/346/215/242/345/277/205/351/241/273/347/234/237/345/256/236/344/270/224/345/205/250/345/261/200/347/224/237/346/225/210.md +117 -0
- package/skills/microi-system-delivery/SKILL.md +16 -380
- package/skills/microi-system-delivery/references/progressive-01-/346/240/207/345/207/206/345/267/245/344/275/234/346/265/201.md +186 -0
- package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +210 -0
- package/skills/microi-ui/SKILL.md +19 -169
- package/skills/microi-ui/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/234/272/346/231/257/350/223/235/345/233/276.md +183 -0
- package/skills/microi-uniapp-frontend/SKILL.md +26 -335
- package/skills/microi-uniapp-frontend/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/210/206/347/261/273-/345/217/214/346/240/217/345/210/227/350/241/250/347/213/254/347/253/213/346/273/232/345/212/250.md +225 -0
- package/skills/microi-uniapp-frontend/references/progressive-02-/345/205/263/351/224/256/344/270/232/345/212/241/350/265/204/344/272/247/344/270/215/345/276/227/351/273/230/350/256/244/351/200/211/344/270/255.md +154 -0
- package/skills/page-engine/SKILL.md +23 -271
- package/skills/page-engine/references/progressive-01-/346/211/200/346/234/211/347/273/204/344/273/266/347/261/273/345/236/213.md +234 -0
- package/skills/page-engine/references/progressive-02-/347/211/210/346/234/254/345/216/206/345/217/262-/345/271/266/345/217/221/344/277/235/345/255/230/344/270/216/345/233/236/346/273/232.md +60 -0
- package/skills/playwright-e2e/SKILL.md +24 -590
- package/skills/playwright-e2e/references/progressive-01-/345/205/250/350/207/252/345/212/250/347/231/273/345/275/225-/345/205/215/351/252/214/350/257/201/347/240/201-/344/275/206/344/270/215/345/205/215/345/257/206/347/240/201-/345/277/205/350/257/273.md +173 -0
- package/skills/playwright-e2e/references/progressive-02-/346/226/207/345/255/227/345/257/271/346/257/224/345/272/246/344/270/216/345/217/257/350/257/273/346/200/247/350/207/252/345/212/250/345/214/226/346/243/200/346/237/245-/345/277/205/345/201/232.md +183 -0
- package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -0
- package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +69 -0
- package/skills/scripts/optimize-progressive-disclosure.mjs +204 -0
- package/skills/scripts/refresh-progressive-disclosure.mjs +64 -0
- package/skills/scripts/validate-progressive-disclosure.mjs +52 -0
- package/skills/ui-design/SKILL.md +26 -1461
- package/skills/ui-design/references/progressive-01-/351/242/234/350/211/262/344/275/223/347/263/273-css-variables-/346/224/257/346/214/201/344/270/273/351/242/230/345/210/207/346/215/242.md +218 -0
- package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +155 -0
- package/skills/ui-design/references/progressive-03-/345/212/250/346/225/210/350/247/204/350/214/203-/344/270/260/345/257/214/344/275/206/344/270/215/345/215/241.md +235 -0
- package/skills/ui-design/references/progressive-04-/347/273/204/344/273/266/351/243/216/346/240/274/351/200/237/346/237/245.md +152 -0
- package/skills/ui-design/references/progressive-05-/347/247/273/345/212/250/347/253/257/344/270/223/347/224/250/350/247/204/350/214/203.md +238 -0
- package/skills/ui-design/references/progressive-06-/344/270/273/351/242/230/345/210/207/346/215/242/345/256/236/347/216/260.md +194 -0
- package/skills/ui-design/references/progressive-07-/351/200/237/346/237/245-/344/273/216/345/244/264/346/220/255/345/273/272/344/270/200/344/270/252/347/247/273/345/212/250/347/253/257/351/241/265/351/235/242.md +207 -0
- package/skills/ui-design/references/progressive-08-/350/241/250/345/215/225/345/210/206/347/273/204/350/247/204/350/214/203-tabs-vs-collapsegroup-/345/274/272/345/210/266.md +142 -0
- package/skills/v8-crud-api/SKILL.md +20 -245
- package/skills/v8-crud-api/references/progressive-01-/346/237/245/350/257/242/345/210/227/350/241/250-/345/210/206/351/241/265.md +226 -0
- package/skills/v8-crud-api/references/progressive-02-where-/346/235/241/344/273/266/350/257/255/346/263/225/351/200/237/346/237/245.md +49 -0
- package/skills/v8-export-import/SKILL.md +15 -425
- package/skills/v8-export-import/references/progressive-01-excellayout-/351/253/230/347/272/247/350/207/252/347/224/261/345/270/203/345/261/200.md +211 -0
- package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -0
- package/skills/v8-export-import/references/progressive-03-/345/256/211/345/205/250-/346/200/247/350/203/275/346/263/250/346/204/217.md +42 -0
- package/skills/v8-file-upload/SKILL.md +16 -354
- package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +227 -0
- package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +149 -0
- package/skills/v8-frontend-events/SKILL.md +19 -205
- package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -0
- package/skills/v8-http-integration/SKILL.md +14 -236
- package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -0
- package/skills/v8-http-integration/references/progressive-02-/351/224/231/350/257/257/345/244/204/347/220/206/346/250/241/345/274/217.md +44 -0
- package/skills/v8-menu-buttons/SKILL.md +15 -511
- package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +221 -0
- package/skills/v8-menu-buttons/references/progressive-02-8-/346/250/241/345/274/217-f-/345/220/216/345/217/260/344/273/273/345/212/241/346/214/211/351/222/256-/351/225/277/344/273/273/345/212/241.md +224 -0
- package/skills/v8-menu-buttons/references/progressive-03-10-/345/217/215/346/250/241/345/274/217-/351/201/277/345/205/215.md +104 -0
- package/skills/v8-mq-mqtt/SKILL.md +11 -175
- package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -0
- package/skills/v8-security/SKILL.md +16 -329
- package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +199 -0
- package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +158 -0
- package/skills/v8-table-event/SKILL.md +16 -236
- package/skills/v8-table-event/references/progressive-01-informv8-js-/350/241/250/345/215/225/346/211/223/345/274/200/344/272/213/344/273/266.md +216 -0
- package/skills/v8-table-event/references/progressive-02-/345/211/215/347/253/257/344/272/213/344/273/266/345/220/215-v8-eventname-/345/217/257/350/203/275/347/232/204/345/200/274.md +46 -0
- package/skills/v8-workflow/SKILL.md +19 -160
- package/skills/v8-workflow/references/progressive-01-/350/212/202/347/202/271/345/274/200/345/247/213-v8-/344/272/213/344/273/266.md +180 -0
- package/skills/workspace-conventions/SKILL.md +29 -361
- package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +208 -0
- package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +196 -0
- package/skills/workspace-conventions/references/progressive-03-cli-/344/270/216-ide-/346/217/222/344/273/266/351/224/231/347/211/210/345/205/261/345/255/230/347/272/246/345/256/232.md +27 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Shared Microi Codex MCP broker.
|
|
4
|
+
*
|
|
5
|
+
* Every Codex task still owns a tiny stdio router, but all routers for the same
|
|
6
|
+
* workspace and tool version share this broker. The broker owns at most one
|
|
7
|
+
* real MCP child per connection profile, uses an exclusive lease to survive
|
|
8
|
+
* concurrent starts, and exits after the last router has been idle.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const childProcess = require('node:child_process');
|
|
14
|
+
const crypto = require('node:crypto');
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const net = require('node:net');
|
|
17
|
+
const os = require('node:os');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
|
|
20
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
21
|
+
const endpoint = String(process.env.MICROI_CODEX_BROKER_ENDPOINT || '');
|
|
22
|
+
const leasePath = path.resolve(String(process.env.MICROI_CODEX_BROKER_LEASE || ''));
|
|
23
|
+
const workspaceRoot = path.resolve(String(process.env.MICROI_WORKSPACE_ROOT || process.cwd()));
|
|
24
|
+
const mcpServerPath = path.resolve(String(process.env.MICROI_MCP_SERVER_PATH || path.join(__dirname, 'mcp-server.js')));
|
|
25
|
+
const toolVersion = String(process.env.MICROI_TOOL_VERSION || '0.0.0');
|
|
26
|
+
const idleMilliseconds = Math.max(250, Number(process.env.MICROI_CODEX_BROKER_IDLE_MS || 30000));
|
|
27
|
+
const heartbeatMilliseconds = Math.max(500, Number(process.env.MICROI_CODEX_BROKER_HEARTBEAT_MS || 5000));
|
|
28
|
+
const staleMilliseconds = Math.max(heartbeatMilliseconds * 3, Number(process.env.MICROI_CODEX_BROKER_STALE_MS || 20000));
|
|
29
|
+
|
|
30
|
+
if (!endpoint || !process.env.MICROI_CODEX_BROKER_LEASE) {
|
|
31
|
+
process.stderr.write('[microi-codex-broker] Missing endpoint or lease path.\n');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ownerToken = crypto.randomBytes(24).toString('hex');
|
|
36
|
+
const children = new Map();
|
|
37
|
+
const clients = new Set();
|
|
38
|
+
let requestSequence = 0;
|
|
39
|
+
let leaseOwned = false;
|
|
40
|
+
let shuttingDown = false;
|
|
41
|
+
let idleTimer = null;
|
|
42
|
+
let heartbeatTimer = null;
|
|
43
|
+
let server = null;
|
|
44
|
+
|
|
45
|
+
function processExists(pid) {
|
|
46
|
+
if (!Number.isInteger(pid) || pid <= 1) return false;
|
|
47
|
+
try {
|
|
48
|
+
process.kill(pid, 0);
|
|
49
|
+
return true;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return error?.code === 'EPERM';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readLease() {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
|
|
58
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function leaseRecord() {
|
|
65
|
+
return {
|
|
66
|
+
pid: process.pid,
|
|
67
|
+
token: ownerToken,
|
|
68
|
+
endpoint,
|
|
69
|
+
workspaceRoot,
|
|
70
|
+
toolVersion,
|
|
71
|
+
startedAt: startedAtIso,
|
|
72
|
+
heartbeatAt: new Date().toISOString(),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeLeaseRecord(record, exclusive = false) {
|
|
77
|
+
fs.mkdirSync(path.dirname(leasePath), { recursive: true });
|
|
78
|
+
const text = `${JSON.stringify(record, null, 2)}\n`;
|
|
79
|
+
if (exclusive) {
|
|
80
|
+
const fd = fs.openSync(leasePath, 'wx', 0o600);
|
|
81
|
+
try { fs.writeFileSync(fd, text, 'utf8'); } finally { fs.closeSync(fd); }
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const current = readLease();
|
|
85
|
+
if (!current || current.token !== ownerToken) throw new Error('Microi Codex broker lease ownership changed');
|
|
86
|
+
const staged = `${leasePath}.${process.pid}.${Date.now()}.tmp`;
|
|
87
|
+
try {
|
|
88
|
+
fs.writeFileSync(staged, text, { encoding: 'utf8', mode: 0o600 });
|
|
89
|
+
fs.renameSync(staged, leasePath);
|
|
90
|
+
} finally {
|
|
91
|
+
try { if (fs.existsSync(staged)) fs.rmSync(staged, { force: true }); } catch { /* best effort */ }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function acquireLease() {
|
|
96
|
+
fs.mkdirSync(path.dirname(leasePath), { recursive: true });
|
|
97
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
98
|
+
try {
|
|
99
|
+
writeLeaseRecord(leaseRecord(), true);
|
|
100
|
+
leaseOwned = true;
|
|
101
|
+
return true;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
104
|
+
const existing = readLease();
|
|
105
|
+
const heartbeat = Date.parse(String(existing?.heartbeatAt || ''));
|
|
106
|
+
const fresh = Number.isFinite(heartbeat) && Date.now() - heartbeat < staleMilliseconds;
|
|
107
|
+
if (existing && processExists(Number(existing.pid)) && fresh) return false;
|
|
108
|
+
const stalePath = `${leasePath}.stale.${process.pid}.${Date.now()}`;
|
|
109
|
+
try {
|
|
110
|
+
fs.renameSync(leasePath, stalePath);
|
|
111
|
+
try { fs.rmSync(stalePath, { force: true }); } catch { /* harmless */ }
|
|
112
|
+
} catch (renameError) {
|
|
113
|
+
if (!['ENOENT', 'EACCES', 'EPERM'].includes(renameError?.code)) throw renameError;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function releaseLease() {
|
|
121
|
+
if (!leaseOwned) return;
|
|
122
|
+
const current = readLease();
|
|
123
|
+
if (current?.token === ownerToken) {
|
|
124
|
+
try { fs.rmSync(leasePath, { force: true }); } catch { /* next owner can reclaim stale lease */ }
|
|
125
|
+
}
|
|
126
|
+
leaseOwned = false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function safeText(value) {
|
|
130
|
+
return String(value == null ? '' : value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function profileIdentity(profile) {
|
|
134
|
+
return safeText(profile?.identity) || [
|
|
135
|
+
safeText(profile?.apiBaseUrl).replace(/\/+$/, ''),
|
|
136
|
+
safeText(profile?.osClient).trim(),
|
|
137
|
+
safeText(profile?.osClientType).trim(),
|
|
138
|
+
safeText(profile?.osClientNetwork).trim(),
|
|
139
|
+
].join('|');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function validateProfile(raw) {
|
|
143
|
+
const profile = raw && typeof raw === 'object' ? raw : {};
|
|
144
|
+
const profileWorkspace = path.resolve(safeText(profile.workspaceRoot || workspaceRoot));
|
|
145
|
+
if (process.platform === 'win32') {
|
|
146
|
+
if (profileWorkspace.toLowerCase() !== workspaceRoot.toLowerCase()) throw new Error('Broker workspace mismatch');
|
|
147
|
+
} else if (profileWorkspace !== workspaceRoot) {
|
|
148
|
+
throw new Error('Broker workspace mismatch');
|
|
149
|
+
}
|
|
150
|
+
if (!/^https?:\/\//i.test(safeText(profile.apiBaseUrl))) throw new Error('Invalid Microi API URL');
|
|
151
|
+
return {
|
|
152
|
+
identity: profileIdentity(profile),
|
|
153
|
+
name: safeText(profile.name || profile.osClient || 'microi'),
|
|
154
|
+
label: safeText(profile.label),
|
|
155
|
+
apiBaseUrl: safeText(profile.apiBaseUrl).replace(/\/+$/, ''),
|
|
156
|
+
osClient: safeText(profile.osClient).trim(),
|
|
157
|
+
osClientType: safeText(profile.osClientType).trim(),
|
|
158
|
+
osClientNetwork: safeText(profile.osClientNetwork).trim(),
|
|
159
|
+
workspaceRoot: profileWorkspace,
|
|
160
|
+
syncRoot: path.resolve(safeText(profile.syncRoot || path.join(profileWorkspace, 'Microi-V8-Engine'))),
|
|
161
|
+
tokenPath: path.resolve(safeText(profile.tokenPath || path.join(profileWorkspace, 'Microi-V8-Engine', '.microi-mcp-tokens.json'))),
|
|
162
|
+
applicationsDir: path.resolve(safeText(profile.applicationsDir || path.join(profileWorkspace, 'Microi-V8-Engine'))),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function deviceId() {
|
|
167
|
+
const raw = `MCP:${os.hostname() || 'Unknown'}`;
|
|
168
|
+
if (/^[\x20-\x7E]{1,128}$/.test(raw)) return raw;
|
|
169
|
+
return `MCP-${crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16)}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
class ChildMcp {
|
|
173
|
+
constructor(profile) {
|
|
174
|
+
this.profile = profile;
|
|
175
|
+
this.pending = new Map();
|
|
176
|
+
this.buffer = '';
|
|
177
|
+
this.stopping = false;
|
|
178
|
+
this.ready = this.start();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async start() {
|
|
182
|
+
if (!fs.existsSync(mcpServerPath)) throw new Error(`Microi MCP bundle does not exist: ${mcpServerPath}`);
|
|
183
|
+
this.child = childProcess.spawn(process.execPath, [mcpServerPath], {
|
|
184
|
+
cwd: this.profile.workspaceRoot,
|
|
185
|
+
windowsHide: true,
|
|
186
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
187
|
+
env: {
|
|
188
|
+
...process.env,
|
|
189
|
+
MICROI_API_URL: this.profile.apiBaseUrl,
|
|
190
|
+
MICROI_TOKEN_FILE: this.profile.tokenPath,
|
|
191
|
+
MICROI_AUTH_RECOVERY_DIR: `${this.profile.tokenPath}.recovery`,
|
|
192
|
+
MICROI_OS_CLIENT: this.profile.osClient,
|
|
193
|
+
MICROI_OS_CLIENT_TYPE: this.profile.osClientType,
|
|
194
|
+
MICROI_OS_CLIENT_NETWORK: this.profile.osClientNetwork,
|
|
195
|
+
MICROI_WORKSPACE_ROOT: this.profile.workspaceRoot,
|
|
196
|
+
MICROI_SYNC_ROOT: this.profile.syncRoot,
|
|
197
|
+
MICROI_AI_APPLICATIONS_DIR: this.profile.applicationsDir,
|
|
198
|
+
MICROI_LABEL_BASE64: Buffer.from(this.profile.label, 'utf8').toString('base64'),
|
|
199
|
+
MICROI_MCP_DID: deviceId(),
|
|
200
|
+
MICROI_TOOL_SOURCE: 'codex-broker',
|
|
201
|
+
MICROI_TOOL_VERSION: toolVersion,
|
|
202
|
+
MICROI_CODEX_MODE: '1',
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
this.child.stdout.on('data', chunk => this.onData(chunk));
|
|
206
|
+
this.child.stderr.on('data', chunk => process.stderr.write(`[${this.profile.name}] ${chunk}`));
|
|
207
|
+
this.child.on('error', error => this.rejectAll(error));
|
|
208
|
+
this.child.on('exit', code => {
|
|
209
|
+
this.rejectAll(new Error(`Microi MCP ${this.profile.name} exited (${code ?? 'unknown'})`));
|
|
210
|
+
if (!this.stopping && children.get(this.profile.identity) === this) children.delete(this.profile.identity);
|
|
211
|
+
});
|
|
212
|
+
await this.request('initialize', {
|
|
213
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
214
|
+
capabilities: {},
|
|
215
|
+
clientInfo: { name: 'microi-codex-shared-broker', version: toolVersion },
|
|
216
|
+
}, true);
|
|
217
|
+
this.notify('notifications/initialized', {});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
onData(chunk) {
|
|
221
|
+
this.buffer += chunk.toString('utf8');
|
|
222
|
+
let newline;
|
|
223
|
+
while ((newline = this.buffer.indexOf('\n')) >= 0) {
|
|
224
|
+
const line = this.buffer.slice(0, newline).trim();
|
|
225
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
226
|
+
if (!line) continue;
|
|
227
|
+
let message;
|
|
228
|
+
try { message = JSON.parse(line); } catch { continue; }
|
|
229
|
+
if (message.id == null) continue;
|
|
230
|
+
const pending = this.pending.get(String(message.id));
|
|
231
|
+
if (!pending) continue;
|
|
232
|
+
this.pending.delete(String(message.id));
|
|
233
|
+
if (message.error) pending.reject(new Error(message.error.message || JSON.stringify(message.error)));
|
|
234
|
+
else pending.resolve(message.result);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
rejectAll(error) {
|
|
239
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
240
|
+
this.pending.clear();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
write(message) {
|
|
244
|
+
if (!this.child || this.child.stdin.destroyed) throw new Error(`Microi MCP ${this.profile.name} is unavailable`);
|
|
245
|
+
this.child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
request(method, params, skipReady = false) {
|
|
249
|
+
return (async () => {
|
|
250
|
+
if (!skipReady) await this.ready;
|
|
251
|
+
const id = `broker-${process.pid}-${++requestSequence}`;
|
|
252
|
+
const response = new Promise((resolve, reject) => this.pending.set(id, { resolve, reject }));
|
|
253
|
+
this.write({ jsonrpc: '2.0', id, method, params });
|
|
254
|
+
return response;
|
|
255
|
+
})();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
notify(method, params) {
|
|
259
|
+
this.write({ jsonrpc: '2.0', method, params });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
stop() {
|
|
263
|
+
this.stopping = true;
|
|
264
|
+
try { this.child?.stdin.end(); } catch { /* best effort */ }
|
|
265
|
+
try { this.child?.kill('SIGTERM'); } catch { /* best effort */ }
|
|
266
|
+
const child = this.child;
|
|
267
|
+
const forceTimer = setTimeout(() => {
|
|
268
|
+
if (child && child.exitCode === null) {
|
|
269
|
+
try { child.kill('SIGKILL'); } catch { /* best effort */ }
|
|
270
|
+
}
|
|
271
|
+
}, 2000);
|
|
272
|
+
forceTimer.unref();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function childFor(rawProfile) {
|
|
277
|
+
const profile = validateProfile(rawProfile);
|
|
278
|
+
const existing = children.get(profile.identity);
|
|
279
|
+
if (existing) return existing;
|
|
280
|
+
const child = new ChildMcp(profile);
|
|
281
|
+
children.set(profile.identity, child);
|
|
282
|
+
return child;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function send(socket, message) {
|
|
286
|
+
if (!socket.destroyed) socket.write(`${JSON.stringify(message)}\n`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function handleMessage(socket, message) {
|
|
290
|
+
const id = message?.id;
|
|
291
|
+
if (id == null) return;
|
|
292
|
+
if (message.auth !== ownerToken) {
|
|
293
|
+
send(socket, { id, error: 'Unauthorized local broker client' });
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
if (message.type === 'status') {
|
|
298
|
+
send(socket, {
|
|
299
|
+
id,
|
|
300
|
+
result: {
|
|
301
|
+
pid: process.pid,
|
|
302
|
+
workspaceRoot,
|
|
303
|
+
toolVersion,
|
|
304
|
+
clients: clients.size,
|
|
305
|
+
childCount: children.size,
|
|
306
|
+
childPids: [...children.values()].map(item => item.child?.pid).filter(Boolean),
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (message.type !== 'request') throw new Error('Unsupported broker request');
|
|
312
|
+
const result = await childFor(message.profile).request(safeText(message.method), message.params || {});
|
|
313
|
+
send(socket, { id, result });
|
|
314
|
+
} catch (error) {
|
|
315
|
+
send(socket, { id, error: error instanceof Error ? error.message : String(error) });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function cancelIdleShutdown() {
|
|
320
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
321
|
+
idleTimer = null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function scheduleIdleShutdown() {
|
|
325
|
+
cancelIdleShutdown();
|
|
326
|
+
if (clients.size > 0 || shuttingDown) return;
|
|
327
|
+
idleTimer = setTimeout(() => shutdown(0), idleMilliseconds);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function acceptClient(socket) {
|
|
331
|
+
cancelIdleShutdown();
|
|
332
|
+
clients.add(socket);
|
|
333
|
+
socket.setEncoding('utf8');
|
|
334
|
+
let buffer = '';
|
|
335
|
+
socket.on('data', chunk => {
|
|
336
|
+
buffer += chunk;
|
|
337
|
+
let newline;
|
|
338
|
+
while ((newline = buffer.indexOf('\n')) >= 0) {
|
|
339
|
+
const line = buffer.slice(0, newline).trim();
|
|
340
|
+
buffer = buffer.slice(newline + 1);
|
|
341
|
+
if (!line) continue;
|
|
342
|
+
let message;
|
|
343
|
+
try { message = JSON.parse(line); }
|
|
344
|
+
catch { send(socket, { id: null, error: 'Invalid broker JSON' }); continue; }
|
|
345
|
+
Promise.resolve(handleMessage(socket, message)).catch(() => { /* handled above */ });
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
const close = () => {
|
|
349
|
+
clients.delete(socket);
|
|
350
|
+
scheduleIdleShutdown();
|
|
351
|
+
};
|
|
352
|
+
socket.once('close', close);
|
|
353
|
+
socket.once('error', close);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function removeUnixSocket() {
|
|
357
|
+
if (process.platform === 'win32') return;
|
|
358
|
+
try { if (fs.existsSync(endpoint)) fs.rmSync(endpoint, { force: true }); } catch { /* stale socket is reclaimable */ }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function shutdown(exitCode = 0) {
|
|
362
|
+
if (shuttingDown) return;
|
|
363
|
+
shuttingDown = true;
|
|
364
|
+
cancelIdleShutdown();
|
|
365
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
366
|
+
for (const socket of clients) {
|
|
367
|
+
try { socket.end(); } catch { /* best effort */ }
|
|
368
|
+
}
|
|
369
|
+
clients.clear();
|
|
370
|
+
for (const child of children.values()) child.stop();
|
|
371
|
+
children.clear();
|
|
372
|
+
const finish = () => {
|
|
373
|
+
releaseLease();
|
|
374
|
+
removeUnixSocket();
|
|
375
|
+
process.exit(exitCode);
|
|
376
|
+
};
|
|
377
|
+
if (server) {
|
|
378
|
+
try { server.close(finish); } catch { finish(); }
|
|
379
|
+
const forceTimer = setTimeout(finish, 2200);
|
|
380
|
+
forceTimer.unref();
|
|
381
|
+
} else {
|
|
382
|
+
finish();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const startedAtIso = new Date().toISOString();
|
|
387
|
+
if (!acquireLease()) process.exit(0);
|
|
388
|
+
|
|
389
|
+
removeUnixSocket();
|
|
390
|
+
server = net.createServer(acceptClient);
|
|
391
|
+
server.on('error', error => {
|
|
392
|
+
process.stderr.write(`[microi-codex-broker] ${error.message}\n`);
|
|
393
|
+
shutdown(1);
|
|
394
|
+
});
|
|
395
|
+
server.listen(endpoint, () => {
|
|
396
|
+
if (process.platform !== 'win32') {
|
|
397
|
+
try { fs.chmodSync(endpoint, 0o600); } catch { /* best effort */ }
|
|
398
|
+
}
|
|
399
|
+
heartbeatTimer = setInterval(() => {
|
|
400
|
+
try { writeLeaseRecord(leaseRecord()); } catch { shutdown(1); }
|
|
401
|
+
}, heartbeatMilliseconds);
|
|
402
|
+
scheduleIdleShutdown();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, () => shutdown(0));
|
|
406
|
+
process.on('uncaughtException', error => {
|
|
407
|
+
process.stderr.write(`[microi-codex-broker] ${error?.stack || error}\n`);
|
|
408
|
+
shutdown(1);
|
|
409
|
+
});
|
|
410
|
+
process.on('exit', () => {
|
|
411
|
+
for (const child of children.values()) {
|
|
412
|
+
try { if (child.child?.exitCode === null) child.child.kill('SIGKILL'); } catch { /* best effort */ }
|
|
413
|
+
}
|
|
414
|
+
if (leaseOwned) {
|
|
415
|
+
releaseLease();
|
|
416
|
+
removeUnixSocket();
|
|
417
|
+
}
|
|
418
|
+
});
|